init
This commit is contained in:
0
recipe_book/__init__.py
Normal file
0
recipe_book/__init__.py
Normal file
13
recipe_book/admin.py
Normal file
13
recipe_book/admin.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Recipe,IngredientWithAmount, Ingredient
|
||||
# Register your models here.
|
||||
|
||||
class IngredientWithAmountInline(admin.TabularInline):
|
||||
model=IngredientWithAmount
|
||||
|
||||
class RecipeAdmin(admin.ModelAdmin):
|
||||
inlines = [ IngredientWithAmountInline, ]
|
||||
admin.site.register(Recipe, RecipeAdmin)
|
||||
|
||||
admin.site.register(Ingredient)
|
||||
5
recipe_book/apps.py
Normal file
5
recipe_book/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RecipeBookConfig(AppConfig):
|
||||
name = 'recipe_book'
|
||||
45
recipe_book/migrations/0001_initial.py
Normal file
45
recipe_book/migrations/0001_initial.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Generated by Django 2.2 on 2019-04-11 13:20
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Ingredient',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IngredientWithAmount',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||
('unit', models.CharField(choices=[('gr', 'Grammes'), ('u', 'Units'), ('l', 'Litres')], max_length=2)),
|
||||
('ingredients', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='recipe_book.Ingredient')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Recipe',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('category', models.CharField(choices=[('0', 'Petit-déjeuner'), ('1', 'Entrée'), ('2', 'Plat'), ('3', 'Dessert')], max_length=2)),
|
||||
('ingredients', models.ManyToManyField(through='recipe_book.IngredientWithAmount', to='recipe_book.Ingredient')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ingredientwithamount',
|
||||
name='recipe',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='recipe_book.Recipe'),
|
||||
),
|
||||
]
|
||||
18
recipe_book/migrations/0002_auto_20190411_1556.py
Normal file
18
recipe_book/migrations/0002_auto_20190411_1556.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2 on 2019-04-11 13:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recipe_book', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='ingredientwithamount',
|
||||
old_name='ingredients',
|
||||
new_name='ingredient',
|
||||
),
|
||||
]
|
||||
0
recipe_book/migrations/__init__.py
Normal file
0
recipe_book/migrations/__init__.py
Normal file
103
recipe_book/models.py
Normal file
103
recipe_book/models.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
"""
|
||||
Recipe's creation workflow :
|
||||
|
||||
1. Create recipe with required info, optional preparation
|
||||
2. Create recipe's list of ingredients
|
||||
a. Create new ingredients instances
|
||||
b. Create IngredientWithAmount instances
|
||||
3. Add preparation steps if necessary
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class Recipe(models.Model):
|
||||
name = models.CharField(max_length=512)
|
||||
category = models.CharField(
|
||||
max_length=2,
|
||||
choices=(
|
||||
('0', 'Petit-déjeuner'),
|
||||
('1', 'Entrée'),
|
||||
('2', 'Plat'),
|
||||
('3', 'Dessert'),
|
||||
))
|
||||
ingredients = models.ManyToManyField(
|
||||
'Ingredient',
|
||||
through='IngredientWithAmount'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "{}".format(self.name)
|
||||
|
||||
|
||||
class Ingredient(models.Model):
|
||||
name = models.CharField(max_length=256)
|
||||
|
||||
def __str__(self):
|
||||
return "{}".format(self.name)
|
||||
|
||||
|
||||
class IngredientWithAmount(models.Model):
|
||||
""" A recipe-ingredient relation associated with amounts
|
||||
"""
|
||||
|
||||
recipe = models.ForeignKey(
|
||||
'Recipe',
|
||||
models.CASCADE)
|
||||
ingredient = models.ForeignKey(
|
||||
'Ingredient',
|
||||
models.CASCADE )
|
||||
amount = models.DecimalField(
|
||||
max_digits=6,
|
||||
decimal_places=2,
|
||||
)
|
||||
unit = models.CharField(
|
||||
max_length=2,
|
||||
choices=(
|
||||
('gr', 'Grammes'),
|
||||
('u', 'Units'),
|
||||
('l', 'Litres'),
|
||||
))
|
||||
|
||||
def __repr__(self):
|
||||
return "<{}>".format(self.display())
|
||||
|
||||
def display(self):
|
||||
""" Print info in a human friendly way.
|
||||
|
||||
* Correct grammar
|
||||
* Apropriate units according to amount
|
||||
"""
|
||||
# Note: amount has a .is_integer() method :)
|
||||
amount = self.amount
|
||||
name = self.ingredient.name.lower()
|
||||
particule = ""
|
||||
unit = ""
|
||||
if self.unit == 'u':
|
||||
integer = int(amount)
|
||||
fraction = amount - integer
|
||||
# Print subfractions of one as ratio
|
||||
if fraction: #TODO: if is not integer...
|
||||
fraction = "{}/{}".format(
|
||||
*amount.as_integer_ratio()
|
||||
)
|
||||
else:
|
||||
fraction = ""
|
||||
if integer:
|
||||
# Plural
|
||||
if integer >= 2:
|
||||
name += "s"
|
||||
integer = str(integer)
|
||||
else:
|
||||
integer = ""
|
||||
amount = "{}{}".format(integer, fraction)
|
||||
else:
|
||||
particule = "de "
|
||||
unit = self.unit
|
||||
|
||||
return "{}{} {}{}".format(
|
||||
amount, unit, particule, name
|
||||
)
|
||||
56
recipe_book/serializers.py
Normal file
56
recipe_book/serializers.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers as ser
|
||||
from .models import Recipe, Ingredient, IngredientWithAmount
|
||||
|
||||
class RecipeSerializer(ser.ModelSerializer):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ('id', 'name', 'category', 'ingredients')
|
||||
|
||||
|
||||
|
||||
class IngredientSerializer(ser.ModelSerializer):
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class IngredientWithAmountSerializer(ser.ModelSerializer):
|
||||
ingredient = IngredientSerializer()
|
||||
|
||||
class Meta:
|
||||
model = IngredientWithAmount
|
||||
fields = ('recipe', 'ingredient', 'amount', 'unit', 'display')
|
||||
extra_kwargs = {
|
||||
'recipe': { 'write_only': True },
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
# TODO: better management of ingredient finding
|
||||
ingredient, created = Ingredient.objects.get_or_create(
|
||||
**validated_data.pop('ingredient')
|
||||
)
|
||||
if created:
|
||||
ingredient.save()
|
||||
validated_data['ingredient'] = ingredient
|
||||
return IngredientWithAmount.objects.create(
|
||||
**validated_data
|
||||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for attr in ('recipe', 'amount', 'unit'):
|
||||
setattr(
|
||||
instance,
|
||||
attr,
|
||||
validated_data.get(attr,
|
||||
getattr(instance, attr))
|
||||
)
|
||||
if 'ingredient' in validated_data:
|
||||
ingdt, created = Ingredient.objects.get_or_create(
|
||||
**validated_data['ingredient']
|
||||
)
|
||||
if created:
|
||||
ingdt.save()
|
||||
instance.ingredient = ingdt
|
||||
instance.save()
|
||||
|
||||
return instance
|
||||
3
recipe_book/tests.py
Normal file
3
recipe_book/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
33
recipe_book/views.py
Normal file
33
recipe_book/views.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from recipe_book.serializers import (
|
||||
RecipeSerializer,
|
||||
IngredientSerializer,
|
||||
IngredientWithAmountSerializer,
|
||||
)
|
||||
from recipe_book.models import (
|
||||
Recipe,
|
||||
Ingredient,
|
||||
IngredientWithAmount,
|
||||
)
|
||||
|
||||
class RecipesViewSet(viewsets.ModelViewSet):
|
||||
queryset = Recipe.objects.all()
|
||||
serializer_class = RecipeSerializer
|
||||
|
||||
|
||||
class IngredientViewSet(viewsets.ModelViewSet):
|
||||
queryset = Ingredient.objects.all()
|
||||
serializer_class = IngredientSerializer
|
||||
|
||||
|
||||
class IngredientWAmountViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = IngredientWithAmountSerializer
|
||||
lookup_field = 'ingredient'
|
||||
|
||||
def get_queryset(self):
|
||||
return IngredientWithAmount.objects.filter(
|
||||
recipe=self.kwargs['recipe_pk']
|
||||
)
|
||||
Reference in New Issue
Block a user