basics of ingredient unit normalization
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -63,7 +63,7 @@ venv/
|
|||||||
|
|
||||||
mediafiles/
|
mediafiles/
|
||||||
|
|
||||||
*.sqlite3
|
*.sqlite3*
|
||||||
|
|
||||||
\.idea/workspace\.xml
|
\.idea/workspace\.xml
|
||||||
|
|
||||||
|
2
.idea/jsLibraryMappings.xml
generated
2
.idea/jsLibraryMappings.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="JavaScriptLibraryMappings">
|
<component name="JavaScriptLibraryMappings">
|
||||||
<file url="file://$PROJECT_DIR$" libraries="{pretty-checkbox}" />
|
<file url="file://$PROJECT_DIR$" libraries="{jquery-3.4.1}" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -1,6 +1,6 @@
|
|||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
|
|
||||||
from cookbook.models import Keyword, RecipeIngredients, Recipe
|
from cookbook.models import Keyword, RecipeIngredients, Recipe, Unit
|
||||||
|
|
||||||
|
|
||||||
class KeywordAutocomplete(autocomplete.Select2QuerySetView):
|
class KeywordAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
@ -40,3 +40,16 @@ class RecipeAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
qs = qs.filter(name__icontains=self.q)
|
qs = qs.filter(name__icontains=self.q)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class UnitAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
|
def get_queryset(self):
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Unit.objects.none()
|
||||||
|
|
||||||
|
qs = Unit.objects.all()
|
||||||
|
|
||||||
|
if self.q:
|
||||||
|
qs = qs.filter(name__icontains=self.q)
|
||||||
|
|
||||||
|
return qs
|
||||||
|
27
cookbook/migrations/0009_auto_20200130_1056.py
Normal file
27
cookbook/migrations/0009_auto_20200130_1056.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.0.2 on 2020-01-30 09:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0008_mealplan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Unit',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, unique=True)),
|
||||||
|
('description', models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='recipeingredients',
|
||||||
|
name='unit_key',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
|
||||||
|
),
|
||||||
|
]
|
28
cookbook/migrations/0010_auto_20200130_1059.py
Normal file
28
cookbook/migrations/0010_auto_20200130_1059.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 3.0.2 on 2020-01-30 09:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_ingredient_units(apps, schema_editor):
|
||||||
|
Unit = apps.get_model('cookbook', 'Unit')
|
||||||
|
RecipeIngredients = apps.get_model('cookbook', 'RecipeIngredients')
|
||||||
|
|
||||||
|
for u in RecipeIngredients.objects.values('unit').distinct():
|
||||||
|
unit = Unit()
|
||||||
|
unit.name = u['unit']
|
||||||
|
unit.save()
|
||||||
|
|
||||||
|
for i in RecipeIngredients.objects.all():
|
||||||
|
i.unit_key = Unit.objects.get(name=i.unit)
|
||||||
|
i.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0009_auto_20200130_1056'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_ingredient_units),
|
||||||
|
]
|
17
cookbook/migrations/0011_remove_recipeingredients_unit.py
Normal file
17
cookbook/migrations/0011_remove_recipeingredients_unit.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.0.2 on 2020-01-30 10:16
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0010_auto_20200130_1059'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='recipeingredients',
|
||||||
|
name='unit',
|
||||||
|
),
|
||||||
|
]
|
18
cookbook/migrations/0012_auto_20200130_1116.py
Normal file
18
cookbook/migrations/0012_auto_20200130_1116.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.0.2 on 2020-01-30 10:16
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0011_remove_recipeingredients_unit'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='recipeingredients',
|
||||||
|
old_name='unit_key',
|
||||||
|
new_name='unit',
|
||||||
|
),
|
||||||
|
]
|
@ -75,14 +75,22 @@ class Recipe(models.Model):
|
|||||||
return ' '.join([(x.icon + x.name) for x in self.keywords.all()])
|
return ' '.join([(x.icon + x.name) for x in self.keywords.all()])
|
||||||
|
|
||||||
|
|
||||||
|
class Unit(models.Model):
|
||||||
|
name = models.CharField(unique=True, max_length=128)
|
||||||
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class RecipeIngredients(models.Model):
|
class RecipeIngredients(models.Model):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||||
unit = models.CharField(max_length=128)
|
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True)
|
||||||
amount = models.DecimalField(default=0, decimal_places=2, max_digits=16)
|
amount = models.DecimalField(default=0, decimal_places=2, max_digits=16)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.amount) + ' ' + self.unit + ' ' + self.name
|
return str(self.amount) + ' ' + str(self.unit) + ' ' + self.name
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
<!-- Bootstrap 4 -->
|
<!-- Bootstrap 4 -->
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
|
<script src="https://code.jquery.com/jquery-3.4.1.js"
|
||||||
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
|
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
|
||||||
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
|
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
|
||||||
@ -31,8 +31,8 @@
|
|||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Select2 for use with django autocomplete light -->
|
<!-- Select2 for use with django autocomplete light -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/css/select2.min.css" rel="stylesheet"/>
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet"/>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
<!-- Bootstrap theme for select2 -->
|
<!-- Bootstrap theme for select2 -->
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
|
@ -46,7 +46,54 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<select class="form-control" id="test"></select>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
$('#test').select2({
|
||||||
|
tags: true,
|
||||||
|
ajax: {
|
||||||
|
url: '{% url 'dal_unit' %}',
|
||||||
|
dataType: 'json',
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var select2Editor = function (cell, onRendered, success, cancel, editorParams) {
|
||||||
|
|
||||||
|
//create input element to hold select
|
||||||
|
var editor = document.createElement("select");
|
||||||
|
editor.setAttribute("class", "form-control");
|
||||||
|
editor.setAttribute("style", "height: 100%");
|
||||||
|
|
||||||
|
onRendered(function () {
|
||||||
|
var select_2 = $(editor);
|
||||||
|
|
||||||
|
select_2.select2({
|
||||||
|
tags: true,
|
||||||
|
ajax: {
|
||||||
|
url: '{% url 'dal_unit' %}',
|
||||||
|
dataType: 'json'
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
select_2.on('change', function (e) {
|
||||||
|
success(select_2.text());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
select_2.on('blur', function (e) {
|
||||||
|
cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//add editor to cell
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
function selectText(node) {
|
function selectText(node) {
|
||||||
|
|
||||||
if (document.body.createTextRange) {
|
if (document.body.createTextRange) {
|
||||||
@ -73,9 +120,9 @@
|
|||||||
|
|
||||||
ingredients.forEach(function (cur, i) {
|
ingredients.forEach(function (cur, i) {
|
||||||
cur.delete = false
|
cur.delete = false
|
||||||
})
|
});
|
||||||
|
|
||||||
var data = ingredients
|
var data = ingredients;
|
||||||
|
|
||||||
var table = new Tabulator("#ingredients-table", {
|
var table = new Tabulator("#ingredients-table", {
|
||||||
index: "id",
|
index: "id",
|
||||||
@ -85,7 +132,15 @@
|
|||||||
movableRows: true,
|
movableRows: true,
|
||||||
headerSort: false,
|
headerSort: false,
|
||||||
columns: [
|
columns: [
|
||||||
{ title: "<i class='fas fa-sort'></i>", rowHandle:true, formatter:"handle", headerSort:false, frozen:true, width:36, minWidth:36},
|
{
|
||||||
|
title: "<i class='fas fa-sort'></i>",
|
||||||
|
rowHandle: true,
|
||||||
|
formatter: "handle",
|
||||||
|
headerSort: false,
|
||||||
|
frozen: true,
|
||||||
|
width: 36,
|
||||||
|
minWidth: 36
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "{% trans 'Ingredient' %}",
|
title: "{% trans 'Ingredient' %}",
|
||||||
field: "name",
|
field: "name",
|
||||||
@ -93,7 +148,7 @@
|
|||||||
editor: "input"
|
editor: "input"
|
||||||
},
|
},
|
||||||
{title: "{% trans 'Amount' %}", field: "amount", validator: "required", editor: "input"},
|
{title: "{% trans 'Amount' %}", field: "amount", validator: "required", editor: "input"},
|
||||||
{title: "{% trans 'Unit' %}", field: "unit", validator: "required", editor: "input"},
|
{title: "{% trans 'Unit' %}", field: "unit__name", validator: "required", editor: select2Editor},
|
||||||
{
|
{
|
||||||
title: "{% trans 'Delete' %}",
|
title: "{% trans 'Delete' %}",
|
||||||
field: "delete",
|
field: "delete",
|
||||||
@ -113,9 +168,9 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
cellClick: function (e, cell) {
|
cellClick: function (e, cell) {
|
||||||
input = cell.getElement().childNodes[0]
|
//input = cell.getElement().childNodes[0]
|
||||||
input.focus()
|
//input.focus()
|
||||||
input.select()
|
//input.select()
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% for plan_key, plan_value in plan.items %}
|
{% for plan_key, plan_value in plan.items %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" style="text-align: center"><h5>{{ plan_value.type_name }}</h5></td>
|
<td colspan="7" style="text-align: center"><h5>{{ plan_value.type_name }}</h5></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -65,4 +65,5 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||||
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
|
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
|
||||||
|
path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'),
|
||||||
]
|
]
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib import messages
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
from django.db.models import Value, CharField
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, get_object_or_404, render
|
from django.shortcuts import redirect, get_object_or_404, render
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
@ -15,7 +16,7 @@ from django.views.generic import UpdateView, DeleteView
|
|||||||
|
|
||||||
from cookbook.forms import ExternalRecipeForm, KeywordForm, StorageForm, SyncForm, InternalRecipeForm, CommentForm, MealPlanForm
|
from cookbook.forms import ExternalRecipeForm, KeywordForm, StorageForm, SyncForm, InternalRecipeForm, CommentForm, MealPlanForm
|
||||||
from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeIngredients, RecipeBook, \
|
from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeIngredients, RecipeBook, \
|
||||||
RecipeBookEntry, MealPlan
|
RecipeBookEntry, MealPlan, Unit
|
||||||
from cookbook.provider.dropbox import Dropbox
|
from cookbook.provider.dropbox import Dropbox
|
||||||
from cookbook.provider.nextcloud import Nextcloud
|
from cookbook.provider.nextcloud import Nextcloud
|
||||||
|
|
||||||
@ -80,7 +81,15 @@ def internal_recipe_update(request, pk):
|
|||||||
ingredient.amount = float(i['amount'].replace(',', '.'))
|
ingredient.amount = float(i['amount'].replace(',', '.'))
|
||||||
else:
|
else:
|
||||||
ingredient.amount = i['amount']
|
ingredient.amount = i['amount']
|
||||||
ingredient.unit = i['unit']
|
|
||||||
|
if Unit.objects.filter(name=i['unit__name']).exists():
|
||||||
|
ingredient.unit = Unit.objects.get(name=i['unit__name'])
|
||||||
|
else:
|
||||||
|
unit = Unit()
|
||||||
|
unit.name = i['unit__name']
|
||||||
|
unit.save()
|
||||||
|
ingredient.unit = unit
|
||||||
|
|
||||||
ingredient.save()
|
ingredient.save()
|
||||||
|
|
||||||
recipe.keywords.set(form.cleaned_data['keywords'])
|
recipe.keywords.set(form.cleaned_data['keywords'])
|
||||||
@ -92,10 +101,10 @@ def internal_recipe_update(request, pk):
|
|||||||
else:
|
else:
|
||||||
form = InternalRecipeForm(instance=recipe_instance)
|
form = InternalRecipeForm(instance=recipe_instance)
|
||||||
|
|
||||||
ingredients = RecipeIngredients.objects.filter(recipe=recipe_instance)
|
ingredients = RecipeIngredients.objects.select_related('unit__name').filter(recipe=recipe_instance).values('name', 'unit__name', 'amount')
|
||||||
|
|
||||||
return render(request, 'forms/edit_internal_recipe.html',
|
return render(request, 'forms/edit_internal_recipe.html',
|
||||||
{'form': form, 'ingredients': json.dumps(list(ingredients.values())),
|
{'form': form, 'ingredients': json.dumps(list(ingredients)),
|
||||||
'view_url': reverse('view_recipe', args=[pk])})
|
'view_url': reverse('view_recipe', args=[pk])})
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user