Skip to content

Commit

Permalink
Merge d7b63bc into fdb772e
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanNoelk committed Nov 19, 2018
2 parents fdb772e + d7b63bc commit 715d7d7
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 173 deletions.
207 changes: 207 additions & 0 deletions v1/recipe/save_recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#!/usr/bin/env python
# encoding: utf-8

from django.db.models import Count
from django.core.exceptions import FieldDoesNotExist
from rest_framework.exceptions import ParseError

from v1.recipe.models import Recipe, SubRecipe
from v1.recipe_groups.models import Tag, Course, Cuisine
from v1.ingredient.models import IngredientGroup, Ingredient


class Validators(object):
def __init__(self, partial=False):
self.partial = partial

def required(self, item):
if not self.partial:
return None if item else "This item is required."
return None

def is_digit(self, item):
if self.partial and item is None:
return None
try:
int(item)
except:
return "This item must be a number."
return None


class SaveRecipe(Validators):
def __init__(self, data, author, partial=False):
super(SaveRecipe, self).__init__(partial=partial)

# Extract the data from data for use in the saving process
self.author = author
self.data = data

# Remove bad fields from the data
self._clean_data()

# Check the data from any errors
self._validate()

# Remove the complex data objects from processing
self.tags = self.data.pop('tags', None)
self.course = self.data.pop('course', None)
self.cuisine = self.data.pop('cuisine', None)
self.subrecipes = self.data.pop('subrecipes', None)
self.ingredients = self.data.pop('ingredient_groups', None)

def _save_course(self):
"""
Add the Course instance to the self.data dict for use when we save the recipe
If the Course doesn't exist create a new one.
"""
if self.course:
# Lookup the course by ID. If the ID isn't there, lookup by title
# if that isn't found, create it.
if self.course.get('id'):
self.data['course'] = Course.objects.get(id=self.course.get('id'))
elif self.course.get('title'):
self.data['course'], created = Course.objects.get_or_create(
title=self.course.get('title'),
defaults={'author': self.author},
)

def _save_cuisine(self):
"""
Add the Cuisine instance to the self.data dict for use when we save the recipe.
If the Cuisine doesn't exist create a new one.
"""
if self.cuisine:
# Lookup the cuisine by ID. If the ID isn't there, lookup by title
# if that isn't found, create it.
if self.cuisine.get('id'):
self.data['cuisine'] = Cuisine.objects.get(id=self.cuisine.get('id'))
elif self.cuisine.get('title'):
self.data['cuisine'], created = Cuisine.objects.get_or_create(
title=self.cuisine.get('title'),
defaults={'author': self.author},
)

@staticmethod
def _delete_recipe_groups():
# Check to see if we have any Cuisines or Courses with no recipes associated with them.
# Id we do, delete them.
Cuisine.objects.all().annotate(total=Count('recipe', distinct=True)).filter(total=0).delete()
Course.objects.all().annotate(total=Count('recipe', distinct=True)).filter(total=0).delete()

def _save_tags(self, recipe):
if self.tags:
# TODO: don't delete everything when we edit the recipe. Use an ID.
for tag in recipe.tags.all():
recipe.tags.remove(tag)

for tag in self.tags:
obj, created = Tag.objects.get_or_create(title=tag['title'].strip())
recipe.tags.add(obj)

def _save_ingredient_data(self, recipe):
if self.ingredients:
# TODO: don't delete everything when we edit the recipe. Use an ID.
for ingredient_group in recipe.ingredient_groups.all():
for ingredient in ingredient_group.ingredients.all():
ingredient.delete()
ingredient_group.delete()

for ingredient_group in self.ingredients:
group = IngredientGroup.objects.create(
recipe=recipe, title=ingredient_group.get('title')
)
for ingredient in ingredient_group.get('ingredients'):
ingredient.pop('id') if ingredient.get('id') else None
Ingredient.objects.create(
ingredient_group=group, **ingredient
)

def _save_subrecipe_data(self, recipe):
if self.subrecipes:
for subrecipe in SubRecipe.objects.filter(parent_recipe=recipe):
subrecipe.delete()

for subrecipe in self.subrecipes:
if subrecipe.get('title'):
child_recipe = Recipe.objects.filter(title=subrecipe.get('title', '')).first()
if recipe:
obj = SubRecipe.objects.create(
quantity=subrecipe.get('quantity', ''),
measurement=subrecipe.get('measurement', ''),
child_recipe=child_recipe,
parent_recipe=recipe
)
obj.save()

def _clean_data(self):
"""
Clean up the data before we try and save it into the Recipe model.
Remove fields that not not apart of the model or shouldn't be saved.
"""
for key in ['author', 'id', 'slug']:
self.data.pop(key) if self.data.get(key) is not None else None
keys = []
for key, value in self.data.items():
try:
Recipe._meta.get_field(key)
except FieldDoesNotExist:
keys.append(key)
for key in keys:
self.data.pop(key)

def _validate(self):
fields = {
"title": [self.required],
"directions": [self.required],

"servings": [self.required, self.is_digit],
"prep_time": [self.required, self.is_digit],
"cook_time": [self.required, self.is_digit],

"ingredient_groups": [self.required],
"cuisine": [self.required],
"course": [self.required],
}

errors = {}
for key, validators in fields.items():
for validator in validators:
error = validator(self.data.get(key))
if error:
errors[key] = [error]
# Only send one error at a time
break

if len(errors) > 0:
raise ParseError(errors)

def update(self, instance):
""" Update and return a new `Recipe` instance, given the validated data """
self._save_course()
self._save_cuisine()
for attr, value in self.data.items():
setattr(instance, attr, value)
self._save_ingredient_data(instance)
self._save_subrecipe_data(instance)
self._save_tags(instance)
instance.save()

self._delete_recipe_groups()

return instance

def create(self):
""" Create and return a new `Recipe` instance, given the validated data """
self._save_course()
self._save_cuisine()
recipe = Recipe.objects.create(
author=self.author,
**self.data
)
self._save_ingredient_data(recipe)
self._save_subrecipe_data(recipe)
self._save_tags(recipe)
self._delete_recipe_groups()

return recipe
122 changes: 3 additions & 119 deletions v1/recipe/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
from rest_framework.fields import SerializerMethodField

from v1.recipe.models import Recipe, SubRecipe
from v1.recipe_groups.models import Tag
from v1.ingredient.serializers import IngredientGroupSerializer
from v1.recipe_groups.serializers import TagSerializer
from v1.ingredient.models import IngredientGroup, Ingredient
from v1.recipe_groups.serializers import TagSerializer, CourseSerializer, CuisineSerializer
from v1.recipe.mixins import FieldLimiter
from v1.rating.average_rating import average_rating

Expand Down Expand Up @@ -102,6 +100,8 @@ class RecipeSerializer(FieldLimiter, serializers.ModelSerializer):
pub_date = serializers.DateTimeField(format="%Y-%m-%d", read_only=True)
update_date = serializers.DateTimeField(format="%Y-%m-%d", read_only=True)
username = serializers.ReadOnlyField(source='author.username')
course = CourseSerializer()
cuisine = CuisineSerializer()

def get_subrecipes(self, obj):
try:
Expand All @@ -113,119 +113,3 @@ def get_subrecipes(self, obj):
class Meta:
model = Recipe
fields = '__all__'

def update(self, instance, validated_data):
"""
Update and return a new `Recipe` instance, given the validated data.
This will also update or create all the ingredient,
or tag objects required.
"""
# Pop tags, and ingredients
ingredient_data = validated_data.pop('ingredient_groups', None)
tag_data = validated_data.pop('tags', None)
# ManytoMany fields in django rest don't work very well, so we are getting the data directly fron teh context
subrecipe_data = None
if 'request' in self.context:
subrecipe_data = self.context['request'].data.get('subrecipes')

for attr, value in validated_data.items():
setattr(instance, attr, value)

# Create the Ingredients
# TODO: don't delete everything when we edit the recipe. Use an ID.
if ingredient_data:
for ingredient_group in instance.ingredient_groups.all():
for ingredient in ingredient_group.ingredients.all():
ingredient.delete()
ingredient_group.delete()

for ingredient_group in ingredient_data:
group = IngredientGroup.objects.create(
recipe=instance, title=ingredient_group.get('title')
)
for ingredient in ingredient_group.get('ingredients'):
Ingredient.objects.create(
ingredient_group=group, **ingredient
)

# Create the Tags
# TODO: don't delete everything when we edit the recipe. Use an ID.
if tag_data:
for tag in instance.tags.all():
instance.tags.remove(tag)

for tag in tag_data:
obj, created = Tag.objects.get_or_create(title=tag['title'].strip())
instance.tags.add(obj)

# Create the sub-recipes
# TODO: don't delete everything when we edit the recipe. Use an ID.
if subrecipe_data:
for subrecipe in SubRecipe.objects.filter(parent_recipe=instance):
subrecipe.delete()

for subrecipe in subrecipe_data:
if subrecipe.get('title'):
recipe = Recipe.objects.filter(title=subrecipe.get('title', '')).first()
if recipe:
obj = SubRecipe.objects.create(
quantity=subrecipe.get('quantity', ''),
measurement=subrecipe.get('measurement', ''),
child_recipe=recipe,
parent_recipe=instance
)
obj.save()

instance.save()
return instance

def create(self, validated_data):
"""
Create and return a new `Recipe` instance, given the validated data.
This will also create all the ingredient objects required and
Create all Tags that are new.
"""
# Pop tags, and ingredients
ingredient_data = validated_data.pop('ingredient_groups', None)
tag_data = validated_data.pop('tags', None)
# ManytoMany fields in django rest don't work very well, so we are getting the data directly fron teh context
subrecipe_data = None
if 'request' in self.context:
subrecipe_data = self.context['request'].data.get('subrecipes')

# Create the recipe.
# Use the log-in user as the author.
recipe = Recipe.objects.create(
author=self.context['request'].user,
**validated_data
)

# Create the Ingredients
for ingredient_group in ingredient_data:
group = IngredientGroup.objects.create(
recipe=recipe, title=ingredient_group.get('title')
)
for ingredient in ingredient_group.get('ingredients'):
Ingredient.objects.create(ingredient_group=group, **ingredient)

# Create the Tags
if tag_data:
for tag in tag_data:
obj, created = Tag.objects.get_or_create(title=tag['title'].strip())
recipe.tags.add(obj)

# Create the sub-recipes
if subrecipe_data:
for subrecipe in subrecipe_data:
if subrecipe.get('title'):
child_recipe = Recipe.objects.filter(title=subrecipe.get('title', '')).first()
if child_recipe:
obj = SubRecipe.objects.create(
quantity=subrecipe.get('quantity', ''),
measurement=subrecipe.get('measurement', ''),
child_recipe=child_recipe,
parent_recipe=recipe
)
obj.save()

return recipe
8 changes: 4 additions & 4 deletions v1/recipe/tests/test_create_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,18 @@ def test_simple_create_recipe(self):
}
],
"directions": '',
"tags": ['hi', 'hello'],
"tags": [{'title': 'hi'}, {'title': 'hello'}],
"title": "Recipe name",
"info": "Recipe info",
"source": "google.com",
"prep_time": 60,
"cook_time": 60,
"servings": 8,
"rating": 0,
"cuisine": 1,
"course": 2
"cuisine": {"id": 1},
"course": {"id": 2}
}
request = self.factory.post('/api/v1/recipe/recipes/', data=data)
request = self.factory.post('/api/v1/recipe/recipes/', data=data, format='json')
request.user = self.staff

root_path = os.path.join(settings.PROJECT_PATH, 'v1', 'fixtures', 'test', 'food.jpg')
Expand Down
Loading

0 comments on commit 715d7d7

Please sign in to comment.