basics of ingredient unit normalization

This commit is contained in:
vabene1111
2020-01-30 12:26:47 +01:00
parent 227d90d49d
commit a2348f531b
13 changed files with 196 additions and 21 deletions

2
.gitignore vendored
View File

@ -63,7 +63,7 @@ venv/
mediafiles/ mediafiles/
*.sqlite3 *.sqlite3*
\.idea/workspace\.xml \.idea/workspace\.xml

View File

@ -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>

View File

@ -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

View 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'),
),
]

View 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),
]

View 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',
),
]

View 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',
),
]

View File

@ -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):

View File

@ -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"

View File

@ -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()
}, },
}); });

View File

@ -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>

View File

@ -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'),
] ]

View File

@ -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])})