From 805575445589f7af3641b8d28e8fc4cc9d224e5d Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 11 Aug 2020 12:17:12 +0200 Subject: [PATCH 01/78] shopping list basics --- ...st_shoppinglistentry_shoppinglistrecipe.py | 49 ++++++++++++++++++ cookbook/models.py | 23 +++++++++ cookbook/tables.py | 9 ++++ cookbook/templates/base.html | 2 +- cookbook/templates/shopping_list.html | 51 +------------------ cookbook/urls.py | 2 +- cookbook/views/lists.py | 12 ++++- 7 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 cookbook/migrations/0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py diff --git a/cookbook/migrations/0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py b/cookbook/migrations/0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py new file mode 100644 index 000000000..c5e01f586 --- /dev/null +++ b/cookbook/migrations/0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py @@ -0,0 +1,49 @@ +# Generated by Django 3.0.7 on 2020-08-11 10:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cookbook', '0074_remove_keyword_created_by'), + ] + + operations = [ + migrations.CreateModel( + name='ShoppingListRecipe', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('multiplier', models.IntegerField(default=1)), + ('recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')), + ], + ), + migrations.CreateModel( + name='ShoppingListEntry', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.IntegerField(default=1)), + ('order', models.IntegerField(default=0)), + ('checked', models.BooleanField(default=False)), + ('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Food')), + ('list_recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ShoppingListRecipe')), + ('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Unit')), + ], + ), + migrations.CreateModel( + name='ShoppingList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4)), + ('note', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('recipes', models.ManyToManyField(blank=True, to='cookbook.ShoppingListRecipe')), + ('shared', models.ManyToManyField(blank=True, related_name='list_share', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index a6a2d8629..20dfd7e39 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -265,6 +265,29 @@ class MealPlan(models.Model): return f'{self.get_label()} - {self.date} - {self.meal_type.name}' +class ShoppingListRecipe(models.Model): + recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True) + multiplier = models.IntegerField(default=1) + + +class ShoppingListEntry(models.Model): + list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True) + food = models.ForeignKey(Food, on_delete=models.CASCADE) + unit = models.ForeignKey(Unit, on_delete=models.CASCADE, null=True, blank=True) + amount = models.IntegerField(default=1) + order = models.IntegerField(default=0) + checked = models.BooleanField(default=False) + + +class ShoppingList(models.Model): + uuid = models.UUIDField(default=uuid.uuid4) + note = models.TextField(blank=True, null=True) + recipes = models.ManyToManyField(ShoppingListRecipe, blank=True) + shared = models.ManyToManyField(User, blank=True, related_name='list_share') + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + + class ShareLink(models.Model): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) uuid = models.UUIDField(default=uuid.uuid4) diff --git a/cookbook/tables.py b/cookbook/tables.py index 30c9c67c2..0d04010ab 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -108,6 +108,15 @@ class RecipeImportTable(tables.Table): fields = ('id', 'name', 'file_path') +class ShoppingListTable(tables.Table): + id = tables.LinkColumn('edit_storage', args=[A('id')]) + + class Meta: + model = ShoppingList + template_name = 'generic/table_template.html' + fields = ('id', 'created_by', 'created_at') + + class ViewLogTable(tables.Table): recipe = tables.LinkColumn('view_recipe', args=[A('recipe_id')]) diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 8f5980371..f2977df67 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -72,7 +72,7 @@ {% trans 'Meal-Plan' %} - {% trans 'Shopping' %} {% trans 'Shopping List' %} -
- {% csrf_token %} - {{ form|crispy }} - -
- -
-
- -
-
- - - -
-
-
-
-
- -
-
- - {% endblock %} \ No newline at end of file diff --git a/cookbook/urls.py b/cookbook/urls.py index 13c29c279..b4e29d8f1 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -89,7 +89,7 @@ urlpatterns = [ ] -generic_models = (Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Comment, RecipeBookEntry, Keyword, Food) +generic_models = (Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Comment, RecipeBookEntry, Keyword, Food, ShoppingList) for m in generic_models: py_name = get_model_name(m) diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py index 6ebb2fda4..2869fad67 100644 --- a/cookbook/views/lists.py +++ b/cookbook/views/lists.py @@ -6,8 +6,8 @@ from django_tables2 import RequestConfig from cookbook.filters import IngredientFilter from cookbook.helper.permission_helper import group_required -from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Food -from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable +from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Food, ShoppingList +from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable, ShoppingListTable @group_required('user') @@ -45,6 +45,14 @@ def food(request): return render(request, 'generic/list_template.html', {'title': _("Ingredients"), 'table': table, 'filter': f}) +@group_required('user') +def shopping_list(request): + table = ShoppingListTable(ShoppingList.objects.filter(created_by=request.user).all()) + RequestConfig(request, paginate={'per_page': 25}).configure(table) + + return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'new_storage'}) + + @group_required('admin') def storage(request): table = StorageTable(Storage.objects.all()) From be55e034bf52c6e180fbc8db2eafa351f2ebbb1e Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 11 Aug 2020 15:24:12 +0200 Subject: [PATCH 02/78] first parts of shopping rework --- cookbook/serializer.py | 31 ++++++- cookbook/templates/shopping_list.html | 113 ++++++++++++++++++++++++++ cookbook/urls.py | 2 + cookbook/views/api.py | 14 +++- cookbook/views/lists.py | 2 +- cookbook/views/views.py | 40 +-------- 6 files changed, 160 insertions(+), 42 deletions(-) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index a843b56ee..f7a004c42 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -4,7 +4,8 @@ from rest_framework import serializers from rest_framework.exceptions import APIException, ValidationError from rest_framework.fields import CurrentUserDefault -from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step +from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step, ShoppingList, \ + ShoppingListEntry, ShoppingListRecipe from cookbook.templatetags.custom_tags import markdown @@ -193,6 +194,34 @@ class MealPlanSerializer(serializers.ModelSerializer): fields = ('id', 'title', 'recipe', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name') +class ShoppingListRecipeSerializer(serializers.ModelSerializer): + recipe = RecipeSerializer(read_only=True) + + def create(self, validated_data): + return ShoppingListRecipe.objects.create(**validated_data) + + def update(self, instance, validated_data): + return super(ShoppingListRecipeSerializer, self).update(instance, validated_data) + + class Meta: + model = ShoppingListRecipe + fields = ('recipe', 'multiplier') + + +class ShoppingListEntrySerializer(serializers.ModelSerializer): + class Meta: + model = ShoppingListEntry + fields = ('list_recipe', 'food', 'unit', 'amount', 'order', 'checked') + + +class ShoppingListSerializer(serializers.ModelSerializer): + recipes = ShoppingListRecipeSerializer(many=True, allow_null=True, read_only=True) + + class Meta: + model = ShoppingList + fields = ('id', 'uuid', 'note', 'recipes', 'shared', 'created_by', 'created_at',) + + class ShareLinkSerializer(serializers.ModelSerializer): class Meta: model = ShareLink diff --git a/cookbook/templates/shopping_list.html b/cookbook/templates/shopping_list.html index 0b9628e7e..2db45f28a 100644 --- a/cookbook/templates/shopping_list.html +++ b/cookbook/templates/shopping_list.html @@ -7,11 +7,124 @@ {% block title %}{% trans "Shopping List" %}{% endblock %} {% block extra_head %} + {% include 'include/vue_base.html' %} + + + + + {% endblock %} {% block content %} +
+
+

{% trans 'Shopping List' %}

+
+
+ {% trans 'Edit' %} +
+
+
+
+
+ + +
    +
  • [[x.name]]
  • +
+
+ +
+
+
+ Non Edit +
+ + + +{% endblock %} +{% block script %} + + {% endblock %} \ No newline at end of file diff --git a/cookbook/urls.py b/cookbook/urls.py index b4e29d8f1..ecf8ac202 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -23,6 +23,7 @@ router.register(r'recipe', api.RecipeViewSet) router.register(r'ingredient', api.IngredientViewSet) router.register(r'meal-plan', api.MealPlanViewSet) router.register(r'meal-type', api.MealTypeViewSet) +router.register(r'shopping-list', api.ShoppingListViewSet) router.register(r'view-log', api.ViewLogViewSet) urlpatterns = [ @@ -34,6 +35,7 @@ urlpatterns = [ path('plan/', views.meal_plan, name='view_plan'), path('plan/entry/', views.meal_plan_entry, name='view_plan_entry'), path('shopping/', views.shopping_list, name='view_shopping'), + path('shopping/', views.shopping_list, name='view_shopping'), path('settings/', views.user_settings, name='view_settings'), path('history/', views.history, name='view_history'), path('test/', views.test, name='view_test'), diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 7988fcce9..079a04bd5 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -28,11 +28,11 @@ from rest_framework.viewsets import ViewSetMixin from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest, CustomIsShare from cookbook.helper.recipe_url_import import get_from_html -from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog +from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog, ShoppingListRecipe, ShoppingList from cookbook.provider.dropbox import Dropbox from cookbook.provider.nextcloud import Nextcloud from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer, IngredientSerializer, FoodSerializer, StepSerializer, \ - KeywordSerializer, RecipeImageSerializer, StorageSerializer, SyncSerializer, SyncLogSerializer, UnitSerializer + KeywordSerializer, RecipeImageSerializer, StorageSerializer, SyncSerializer, SyncLogSerializer, UnitSerializer, ShoppingListSerializer class UserNameViewSet(viewsets.ReadOnlyModelViewSet): @@ -233,6 +233,16 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin): return Response(serializer.errors, 400) +class ShoppingListViewSet(viewsets.ModelViewSet): + queryset = ShoppingList.objects.all() + serializer_class = ShoppingListSerializer + permission_classes = [CustomIsOwner] + + def get_queryset(self): + queryset = self.queryset.filter(created_by=self.request.user).all() + return queryset + + class ViewLogViewSet(viewsets.ModelViewSet): queryset = ViewLog.objects.all() serializer_class = ViewLogSerializer diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py index 2869fad67..8ae428e71 100644 --- a/cookbook/views/lists.py +++ b/cookbook/views/lists.py @@ -50,7 +50,7 @@ def shopping_list(request): table = ShoppingListTable(ShoppingList.objects.filter(created_by=request.user).all()) RequestConfig(request, paginate={'per_page': 25}).configure(table) - return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'new_storage'}) + return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'view_shopping'}) @group_required('admin') diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 95b846cf2..c3bd4806a 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -164,44 +164,8 @@ def meal_plan_entry(request, pk): @group_required('user') -def shopping_list(request): - markdown_format = True - - if request.method == "POST": - form = ShoppingForm(request.POST) - if form.is_valid(): - recipes = form.cleaned_data['recipe'] - markdown_format = form.cleaned_data['markdown_format'] - else: - recipes = [] - else: - raw_list = request.GET.getlist('r') - - recipes = [] - for r in raw_list: - if re.match(r'^([1-9])+$', r): - if Recipe.objects.filter(pk=int(r)).exists(): - recipes.append(int(r)) - - markdown_format = False - form = ShoppingForm(initial={'recipe': recipes, 'markdown_format': False}) - - ingredients = [] - - for r in recipes: - for s in r.steps.all(): - for ri in s.ingredients.exclude(unit__name__contains='Special:').all(): - index = None - for x, ig in enumerate(ingredients): - if ri.food == ig.food and ri.unit == ig.unit: - index = x - - if index: - ingredients[index].amount = ingredients[index].amount + ri.amount - else: - ingredients.append(ri) - - return render(request, 'shopping_list.html', {'ingredients': ingredients, 'recipes': recipes, 'form': form, 'markdown_format': markdown_format}) +def shopping_list(request, pk=None): + return render(request, 'shopping_list.html', {}) @group_required('guest') From 587426e3d3c11a81119be0b41dee55355c166ed8 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2020 15:26:15 +0000 Subject: [PATCH 03/78] Apply translations in fr translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po' on the 'fr' language. --- cookbook/locale/fr/LC_MESSAGES/django.po | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cookbook/locale/fr/LC_MESSAGES/django.po b/cookbook/locale/fr/LC_MESSAGES/django.po index 01a801b8d..7264de44f 100644 --- a/cookbook/locale/fr/LC_MESSAGES/django.po +++ b/cookbook/locale/fr/LC_MESSAGES/django.po @@ -4,7 +4,7 @@ # FIRST AUTHOR , YEAR. # # Translators: -# Julien Teruel, 2020 +# vabene1111 , 2020 # nerdinator , 2020 # #, fuzzy @@ -15,11 +15,11 @@ msgstr "" "POT-Creation-Date: 2020-06-02 21:20+0200\n" "PO-Revision-Date: 2020-06-02 19:28+0000\n" "Last-Translator: nerdinator , 2020\n" -"Language-Team: French (France) (https://www.transifex.com/django-recipes/teams/110507/fr_FR/)\n" +"Language-Team: French (https://www.transifex.com/django-recipes/teams/110507/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: fr_FR\n" +"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: .\cookbook\filters.py:15 .\cookbook\templates\base.html:79 @@ -714,7 +714,7 @@ msgid "" msgstr "" "\n" "Markdown est un langage de balisage léger utilisé pour formatter du texte facilement.\n" -"Ce site utilise la bibliothèque
Python Markdown pour convertir votre texte en un joli format HTML. Sa documentation complète est consultable ici.\n" +"Ce site utilise la bibliothèque Python Markdown pour convertir votre texte en un joli format HTML. Sa documentation complète est consultable ici.\n" "Une documentation incomplète mais probablement suffisante se trouve plus bas." #: .\cookbook\templates\markdown_info.html:25 @@ -912,6 +912,7 @@ msgstr "Voir la recette externe" #: .\cookbook\templates\recipe_view.html:235 msgid "Cloud not show a file preview. Maybe its not a PDF ?" msgstr "" +"Impossible de prévisualiser le fichier. Il s'agit peut-être d'un PDF ?" #: .\cookbook\templates\recipe_view.html:242 msgid "External recipe" From 03bdcdf9b455a150a958ddccb3bfcdff6dbc6795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=A9vy?= Date: Tue, 18 Aug 2020 16:32:00 -0400 Subject: [PATCH 04/78] Fix markdown rules --- README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 29d665f5c..d96a5d4de 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # Recipes ![CI](https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop) + Recipes is a Django application to manage, tag and search recipes using either built in models or external storage providers hosting PDF's, Images or other files. ![Preview](docs/preview.png) [More Screenshots](https://imgur.com/a/V01151p) -### Features +## Features - :package: **Sync** files with Dropbox and Nextcloud (more can easily be added) - :mag: Powerful **search** with Djangos [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity) - :label: Create and search for **tags**, assign them in batch to all files matching certain filters -- :page_facing_up: **Create recipes** locally within a nice, standardized web interface +- :page_facing_up: **Create recipes** locally within a nice, standardized web interface - :arrow_down: **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe) - :iphone: Optimized for use on **mobile** devices like phones and tablets - :shopping_cart: Generate **shopping** lists from recipes @@ -21,22 +22,24 @@ Recipes is a Django application to manage, tag and search recipes using either b - :envelope: Export and import recipes from other users - :heavy_plus_sign: Many more like recipe scaling, image compression, cookbooks, printing views, ... -This application is meant for people with a collection of recipes they want to share with family and friends or simply +This application is meant for people with a collection of recipes they want to share with family and friends or simply store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page. Some Documentation can be found [here](https://github.com/vabene1111/recipes/wiki) -# Installation + +## Installation The docker image (`vabene1111/recipes`) simply exposes the application on port `8080`. You may choose any preferred installation method, the following are just examples to make it easier. ### Docker-Compose -2. Choose one of the included configurations [here](docs/docker). -2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env ` +1. Choose one of the included configurations [here](docs/docker). +2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env` 3. Start the container `docker-compose up -d` -4. Open the page to create the first user. Alternatively use `docker-compose exec web_recipes createsuperuser` +4. Open the page to create the first user. Alternatively use `docker-compose exec web_recipes createsuperuser` ### Manual -**Python >= 3.8** is required to run this! + +**Python >= 3.8** is required to run this! Copy `.env.template` to `.env` and fill in the missing values accordingly. Make sure all variables are available to whatever serves your application. @@ -45,6 +48,7 @@ Otherwise simply follow the instructions for any django based deployment (for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)). ## Updating + While intermediate updates can be skipped when updating please make sure to **read the release notes** in case some special action is required to update. 0. Before updating it is recommended to **create a backup!** @@ -57,25 +61,27 @@ While intermediate updates can be skipped when updating please make sure to **re You can find a basic kubernetes setup [here](docs/k8s/). Please see the README in the folder for more detail. ## Contributing + Pull Requests and ideas are welcome, feel free to contribute in any way. For any questions on how to work with django please refer to their excellent [documentation](https://www.djangoproject.com/start/). ### Translating + There is a [transifex project](https://www.transifex.com/django-recipes/django-cookbook/) project to enable community driven translations. If you want to contribute a new language or help maintain an already existing one feel free to create a transifex account (using the link above) and request to join the project. It is also possible to provide the translations directly by creating a new language using `manage.py makemessages -l -i venv`. Once finished simply open a PR with the changed files. ## License + Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with an [common clause](https://commonsclause.com/) selling exception. See [LICENSE.md](https://github.com/vabene1111/recipes/blob/develop/LICENSE.md) for details. **Reasoning** +**This software and *all* its features are and will always be free for everyone to use and enjoy.** -#### This software and **all** its features are and will always be free for everyone to use and enjoy. - -The reason for the selling exception is that a significant amount of time was spend over multiple years to develop this software. +The reason for the selling exception is that a significant amount of time was spend over multiple years to develop this software. A payed hosted version which will be identical in features and code base to the software offered in this repository will likely be released in the future (including all features needed to sell a hosted version as they might also be useful for personal use). -This will not only benefit me personally but also everyone who self-hosts this software as any profits made trough selling the hosted option +This will not only benefit me personally but also everyone who self-hosts this software as any profits made trough selling the hosted option allow me to spend more time developing and improving the software for everyone. Selling exceptions are [approved by Richard Stallman](http://www.gnu.org/philosophy/selling-exceptions.en.html) and the -common clause license is very permissive (see the [FAQ](https://commonsclause.com/)). \ No newline at end of file +common clause license is very permissive (see the [FAQ](https://commonsclause.com/)). From 5d5c5a8597d29d820c7740f279a2abf1fe3fa3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=A9vy?= Date: Wed, 19 Aug 2020 17:32:28 -0400 Subject: [PATCH 05/78] Instructions for manual installation --- README.md | 6 +- docs/manual_install/Readme.md | 143 ++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 docs/manual_install/Readme.md diff --git a/README.md b/README.md index d96a5d4de..bad3bec07 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,7 @@ The docker image (`vabene1111/recipes`) simply exposes the application on port ` **Python >= 3.8** is required to run this! -Copy `.env.template` to `.env` and fill in the missing values accordingly. -Make sure all variables are available to whatever serves your application. - -Otherwise simply follow the instructions for any django based deployment -(for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)). +Refer to [manual install](docs/manual_install) for detailled instructions. ## Updating diff --git a/docs/manual_install/Readme.md b/docs/manual_install/Readme.md new file mode 100644 index 000000000..f80dc0cbb --- /dev/null +++ b/docs/manual_install/Readme.md @@ -0,0 +1,143 @@ +# Manual installation instructions + +These intructions are inspired from a standard django/gunicorn/postgresql instructions ([for example](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04)) + +**Important note:** Be sure to use pyton3.8 and pip related to python 3.8. Depending on your distribution calling `python` or `pip` will use python2 instead of pyton 3.8. + +## Prerequisites + +*Optional*: create a virtual env and activate it + +Download the latest release from + +Install postgresql requirements: `sudo apt install libpq-dev postgresql` +Install project requirements: `pip3.8 install -r requirements.txt` + +## Setup postgresql + +Run `sudo -u postgres psql` + +In the psql console: + +```sql +CREATE DATABASE djangodb; +CREATE USER djangouser WITH PASSWORD 'password'; +GRANT ALL PRIVILEGES ON DATABASE djangodb TO djangouser; +ALTER DATABASE djangodb OWNER TO djangouser; + +--Maybe not necessary, but should be faster: +ALTER ROLE djangouser SET client_encoding TO 'utf8'; +ALTER ROLE djangouser SET default_transaction_isolation TO 'read committed'; +ALTER ROLE djangouser SET timezone TO 'UTC'; + +--Grant superuser right to your new user, it will be removed later +ALTER USER djangouser WITH SUPERUSER; +``` + +Move or copy `.env.template` to `.env` and update it with relevent values. For example: + +```env +# only set this to true when testing/debugging +# when unset: 1 (true) - dont unset this, just for development +DEBUG=0 + +# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,... +#ALLOWED_HOSTS=* + +# random secret key, use for example base64 /dev/urandom | head -c50 to generate one +SECRET_KEY=TOGENERATE + +# add only a database password if you want to run with the default postgres, otherwise change settings accordingly +DB_ENGINE=django.db.backends.postgresql_psycopg2 +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_USER=djangouser +POSTGRES_PASSWORD=password +POSTGRES_DB=djangodb + +# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples +# provided that include an additional nxginx container to handle media file serving. +# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method. +# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate +GUNICORN_MEDIA=0 + + +# allow authentication via reverse proxy (e.g. authelia), leave of if you dont know what you are doing +# docs: https://github.com/vabene1111/recipes/tree/develop/docs/docker/nginx-proxy%20with%20proxy%20authentication +# when unset: 0 (false) +REVERSE_PROXY_AUTH=0 + + +# the default value for the user preference 'comments' (enable/disable commenting system) +# when unset: 1 (true) +COMMENT_PREF_DEFAULT=1 +``` + +## Initialize the application + +Execute `export $(cat .env |grep "^[^#]" | xargs)` to load variables from `.env` + +Execute `/python3.8 manage.py migrate` + +And revert superuser from postgres: `sudo -u postgres psql` and `ALTER USER djangouser WITH NOSUPERUSER;` + +Generate static files: `python3.8 manage.py collectstatic` and remember the folder where files have been copied. + +## Setup web services + +### gunicorn + +Create a service that will start gunicorn at boot: `sudo nano /etc/systemd/system/gunicorn_recipes.service` + +And enter these lines: + +```service +[Unit] +Description=gunicorn daemon for recipes +After=network.target + +[Service] +Type=simple +Restart=always +RestartSec=3 +Group=www-data +WorkingDirectory=/media/data/recipes +EnvironmentFile=/media/data/recipes/.env +ExecStart=/opt/.pyenv/versions/3.8.5/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/media/data/recipes/recipes.sock recipes.wsgi:application + +[Install] +WantedBy=multi-user.target +``` + +*Note*: `-error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output` are usefull for debugging and can be removed later + +*Note2*: Fix the path in the `ExecStart` line to where you gunicorn and recipes are + +Finally, run `sudo systemctl enable gunicorn_recipes.service` and `sudo systemctl start gunicorn_recipes.service`. You can check that the service is correctly started with `systemctl status gunicorn_recipes.service` + +### nginx + +Now we tell nginx to listen to a new port and forward that to gunicorn. `sudo nano /etc/nginx/sites-available/recipes.conf` + +And enter these lines: + +```nginx +server { + listen 8002; + #access_log /var/log/nginx/access.log; + #error_log /var/log/nginx/error.log; + + # serve media files + location /static { + alias /media/data/recipes/staticfiles; + } + + location / { + proxy_pass http://unix:/media/data/recipes/recipes.sock; + } +} +``` + +*Note*: Enter the correct path in static and proxy_pass lines. + +Enable the website `sudo ln -s /etc/nginx/sites-available/recipes.conf /etc/nginx/sites-enabled` and restart nginx : `sudo systemctl restart nginx.service` From c5edeb7e8f78c7968194ab976c9cd0032dbaeed8 Mon Sep 17 00:00:00 2001 From: Mwoua Date: Wed, 19 Aug 2020 18:09:23 -0400 Subject: [PATCH 06/78] Missing alias for media files --- docs/manual_install/Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/manual_install/Readme.md b/docs/manual_install/Readme.md index f80dc0cbb..d5138ca6e 100644 --- a/docs/manual_install/Readme.md +++ b/docs/manual_install/Readme.md @@ -131,6 +131,10 @@ server { location /static { alias /media/data/recipes/staticfiles; } + + location /media { + alias /media/data/recipes/mediafiles; + } location / { proxy_pass http://unix:/media/data/recipes/recipes.sock; From 7acd72ff3a9126eec41b21995285ce0893044aa6 Mon Sep 17 00:00:00 2001 From: Mwoua Date: Fri, 21 Aug 2020 09:12:57 -0400 Subject: [PATCH 07/78] Clone master instead of getting release --- docs/manual_install/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual_install/Readme.md b/docs/manual_install/Readme.md index d5138ca6e..0781fbc0c 100644 --- a/docs/manual_install/Readme.md +++ b/docs/manual_install/Readme.md @@ -8,7 +8,7 @@ These intructions are inspired from a standard django/gunicorn/postgresql instru *Optional*: create a virtual env and activate it -Download the latest release from +Get the last version from the repository: `git clone https://github.com/vabene1111/recipes.git -b master` Install postgresql requirements: `sudo apt install libpq-dev postgresql` Install project requirements: `pip3.8 install -r requirements.txt` From 0b948618f33827836db35eb9ee0395f252b5e115 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 26 Aug 2020 11:37:59 +0200 Subject: [PATCH 08/78] improved website parser --- cookbook/helper/recipe_url_import.py | 6 +++--- cookbook/tests/other/test_edits_recipe.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py index 94e352ca1..a8dd84b4e 100644 --- a/cookbook/helper/recipe_url_import.py +++ b/cookbook/helper/recipe_url_import.py @@ -18,7 +18,7 @@ def get_from_html(html_text, url): # first try finding ld+json as its most common for ld in soup.find_all('script', type='application/ld+json'): try: - ld_json = json.loads(ld.string) + ld_json = json.loads(ld.string.replace('\n', '')) if type(ld_json) != list: ld_json = [ld_json] @@ -31,8 +31,8 @@ def get_from_html(html_text, url): if '@type' in ld_json_item and ld_json_item['@type'] == 'Recipe': return find_recipe_json(ld_json_item, url) - except JSONDecodeError: - JsonResponse({'error': True, 'msg': _('The requested site provided malformed data and cannot be read.')}, status=400) + except JSONDecodeError as e: + return JsonResponse({'error': True, 'msg': _('The requested site provided malformed data and cannot be read.')}, status=400) # now try to find microdata items = microdata.get_items(html_text) diff --git a/cookbook/tests/other/test_edits_recipe.py b/cookbook/tests/other/test_edits_recipe.py index 97dcdfcaa..0ad081a87 100644 --- a/cookbook/tests/other/test_edits_recipe.py +++ b/cookbook/tests/other/test_edits_recipe.py @@ -12,7 +12,7 @@ class TestEditsRecipe(TestBase): {'file': 'cookbook/tests/resources/websites/ld_json_2.html', 'result_length': 1450}, {'file': 'cookbook/tests/resources/websites/ld_json_3.html', 'result_length': 1545}, {'file': 'cookbook/tests/resources/websites/ld_json_4.html', 'result_length': 1657}, - {'file': 'cookbook/tests/resources/websites/ld_json_invalid.html', 'result_length': 115}, + {'file': 'cookbook/tests/resources/websites/ld_json_invalid.html', 'result_length': 88}, {'file': 'cookbook/tests/resources/websites/ld_json_itemList.html', 'result_length': 3131}, {'file': 'cookbook/tests/resources/websites/ld_json_multiple.html', 'result_length': 1546}, {'file': 'cookbook/tests/resources/websites/micro_data_1.html', 'result_length': 1022}, From 90dddd34f34e63aebd2b546f1922ca5bc725187d Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 26 Aug 2020 11:46:56 +0200 Subject: [PATCH 09/78] removed test for invalid recipe as its no longer invalid due to parser improvements --- cookbook/tests/other/test_edits_recipe.py | 2 +- .../resources/websites/ld_json_invalid.html | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 cookbook/tests/resources/websites/ld_json_invalid.html diff --git a/cookbook/tests/other/test_edits_recipe.py b/cookbook/tests/other/test_edits_recipe.py index 0ad081a87..f5857b75f 100644 --- a/cookbook/tests/other/test_edits_recipe.py +++ b/cookbook/tests/other/test_edits_recipe.py @@ -12,7 +12,6 @@ class TestEditsRecipe(TestBase): {'file': 'cookbook/tests/resources/websites/ld_json_2.html', 'result_length': 1450}, {'file': 'cookbook/tests/resources/websites/ld_json_3.html', 'result_length': 1545}, {'file': 'cookbook/tests/resources/websites/ld_json_4.html', 'result_length': 1657}, - {'file': 'cookbook/tests/resources/websites/ld_json_invalid.html', 'result_length': 88}, {'file': 'cookbook/tests/resources/websites/ld_json_itemList.html', 'result_length': 3131}, {'file': 'cookbook/tests/resources/websites/ld_json_multiple.html', 'result_length': 1546}, {'file': 'cookbook/tests/resources/websites/micro_data_1.html', 'result_length': 1022}, @@ -23,6 +22,7 @@ class TestEditsRecipe(TestBase): for test in test_list: with open(test['file'], 'rb') as file: + print(f'Testing {test["file"]} expecting length {test["result_length"]}') parsed_content = json.loads(get_from_html(file.read(), 'test_url').content) self.assertEqual(len(str(parsed_content)), test['result_length']) file.close() diff --git a/cookbook/tests/resources/websites/ld_json_invalid.html b/cookbook/tests/resources/websites/ld_json_invalid.html deleted file mode 100644 index 208f3e4e0..000000000 --- a/cookbook/tests/resources/websites/ld_json_invalid.html +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file From f6fb07926e72484ee5b441bb67445e14a6e76b1d Mon Sep 17 00:00:00 2001 From: Michael Kuckuk Date: Wed, 26 Aug 2020 12:39:21 +0200 Subject: [PATCH 10/78] Show amounts even when unit is empty --- cookbook/templates/recipe_view.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cookbook/templates/recipe_view.html b/cookbook/templates/recipe_view.html index 9b7179578..c3818e8dd 100644 --- a/cookbook/templates/recipe_view.html +++ b/cookbook/templates/recipe_view.html @@ -148,9 +148,12 @@ - @@ -339,12 +342,25 @@ mounted: function () { this.loadShoppingList() + {% if recipes %} + this.loading = true + this.edit_mode = true + let loadingRecipes = [] + {% for r in recipes %} + loadingRecipes.push(this.loadInitialRecipe({{ r.recipe }}, {{ r.multiplier }})) + {% endfor %} + + Promise.allSettled(loadingRecipes).then(() => { + this.loading = false + }) + {% endif %} + {% if request.user.userpreference.shopping_auto_sync > 0 %} setInterval(() => { if ((this.shopping_list_id !== null) && !this.edit_mode) { this.loadShoppingList(true) } - }, {{ request.user.userpreference.shopping_auto_sync }} * 1000 ) + }, {% widthratio request.user.userpreference.shopping_auto_sync 1 1000 %}) {% endif %} }, methods: { @@ -365,6 +381,14 @@ solid: true }) }, + loadInitialRecipe: function (recipe, multiplier) { + return this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe)).then((response) => { + this.addRecipeToList(response.data, multiplier) + }).catch((err) => { + console.log("getRecipes error: ", err); + this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger') + }) + }, loadShoppingList: function (autosync = false) { if (this.shopping_list_id) { @@ -433,6 +457,8 @@ this.shopping_list = response.body this.shopping_list_id = this.shopping_list.id + + window.history.pushState('shopping_list', '{% trans 'Shopping List' %}', "{% url 'view_shopping' 123456 %}".replace('123456', this.shopping_list_id)); }).catch((err) => { console.log(err) this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error creating a resource!' %}' + err.bodyText, 'danger') @@ -511,13 +537,13 @@ getRecipeUrl: function (id) { //TODO generic function that can be reused else were return '{% url 'view_recipe' 123456 %}'.replace('123456', id) }, - addRecipeToList: function (recipe) { + addRecipeToList: function (recipe, multiplier = 1) { let slr = { "created": true, "id": Math.random() * 1000, "recipe": recipe.id, "recipe_name": recipe.name, - "multiplier": 1 + "multiplier": multiplier } this.shopping_list.recipes.push(slr) diff --git a/cookbook/views/views.py b/cookbook/views/views.py index e4a3ef37e..183e222e9 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -165,7 +165,17 @@ def meal_plan_entry(request, pk): @group_required('user') def shopping_list(request, pk=None): - return render(request, 'shopping_list.html', {'shopping_list_id': pk}) + raw_list = request.GET.getlist('r') + + recipes = [] + for r in raw_list: + r = r.replace('[', '').replace(']', '') + if re.match(r'^([1-9])+,([1-9])+[.]*([1-9])*$', r): + rid, multiplier = r.split(',') + if recipe := Recipe.objects.filter(pk=int(rid)).first(): + recipes.append({'recipe': recipe.id, 'multiplier': multiplier}) + + return render(request, 'shopping_list.html', {'shopping_list_id': pk, 'recipes': recipes}) @group_required('guest') From 4641b81f70c3e951d54cdae55dad08e06292cf42 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 29 Sep 2020 12:48:28 +0200 Subject: [PATCH 42/78] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..cf7a39fb6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 23de4d42397534f996587e57cbd74d947c25f678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:48:50 +0000 Subject: [PATCH 43/78] Bump bleach-whitelist from 0.0.10 to 0.0.11 Bumps [bleach-whitelist](https://github.com/yourcelf/bleach-whitelist) from 0.0.10 to 0.0.11. - [Release notes](https://github.com/yourcelf/bleach-whitelist/releases) - [Commits](https://github.com/yourcelf/bleach-whitelist/commits) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 156a34765..e6ffbec9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bleach==3.1.5 -bleach-whitelist==0.0.10 +bleach-whitelist==0.0.11 Django==3.0.7 django-annoying==0.10.6 django-autocomplete-light==3.5.1 From 71b8ddd1bfb42c3b277332dd395f356e4d2af5c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:48:52 +0000 Subject: [PATCH 44/78] Bump django-filter from 2.2.0 to 2.4.0 Bumps [django-filter](https://github.com/carltongibson/django-filter) from 2.2.0 to 2.4.0. - [Release notes](https://github.com/carltongibson/django-filter/releases) - [Changelog](https://github.com/carltongibson/django-filter/blob/master/CHANGES.rst) - [Commits](https://github.com/carltongibson/django-filter/compare/2.2.0...2.4.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 156a34765..e9169aef8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ django-autocomplete-light==3.5.1 django-cleanup==4.0.0 django-crispy-forms==1.9.1 django-emoji-picker==0.0.6 -django-filter==2.2.0 +django-filter==2.4.0 django-tables2==2.3.1 djangorestframework==3.11.0 drf-writable-nested==0.6.0 From b317d7ba29cf71ac0f7dbe255fb9039e06408d72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:48:54 +0000 Subject: [PATCH 45/78] Bump beautifulsoup4 from 4.9.1 to 4.9.2 Bumps [beautifulsoup4](http://www.crummy.com/software/BeautifulSoup/bs4/) from 4.9.1 to 4.9.2. Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 156a34765..e2c201110 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,5 +24,5 @@ whitenoise==5.1.0 icalendar==4.0.6 pyyaml==5.3.1 uritemplate==3.0.1 -beautifulsoup4==4.9.1 +beautifulsoup4==4.9.2 microdata==0.7.1 \ No newline at end of file From 17ebdd7711aa47fa313c80ec8fcad7dc8a906e0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:56:07 +0000 Subject: [PATCH 46/78] Bump django from 3.0.7 to 3.1.1 Bumps [django](https://github.com/django/django) from 3.0.7 to 3.1.1. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.0.7...3.1.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2915e41f0..4729e8310 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ bleach==3.1.5 bleach-whitelist==0.0.11 -Django==3.0.7 +Django==3.1.1 django-annoying==0.10.6 django-autocomplete-light==3.5.1 django-cleanup==4.0.0 From db3c390d03f457bb2db3130377060a5684c0b637 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:56:26 +0000 Subject: [PATCH 47/78] Bump drf-writable-nested from 0.6.0 to 0.6.1 Bumps [drf-writable-nested](https://github.com/beda-software/drf-writable-nested) from 0.6.0 to 0.6.1. - [Release notes](https://github.com/beda-software/drf-writable-nested/releases) - [Changelog](https://github.com/beda-software/drf-writable-nested/blob/master/CHANGELOG.md) - [Commits](https://github.com/beda-software/drf-writable-nested/compare/v0.6.0...v0.6.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2915e41f0..7e54b9419 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ django-emoji-picker==0.0.6 django-filter==2.4.0 django-tables2==2.3.1 djangorestframework==3.11.0 -drf-writable-nested==0.6.0 +drf-writable-nested==0.6.1 gunicorn==20.0.4 lxml==4.5.1 Markdown==3.2.2 From 711dfbe55f494b917628f4818fe44bced7f18ec3 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 29 Sep 2020 13:19:06 +0200 Subject: [PATCH 48/78] cleanup import --- cookbook/serializer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index bd6ed59ad..b59653e23 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -3,8 +3,7 @@ from decimal import Decimal from django.contrib.auth.models import User from drf_writable_nested import WritableNestedModelSerializer, UniqueFieldsMixin from rest_framework import serializers -from rest_framework.exceptions import APIException, ValidationError -from rest_framework.fields import CurrentUserDefault +from rest_framework.exceptions import ValidationError from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step, ShoppingList, \ ShoppingListEntry, ShoppingListRecipe From 7bc09dfe89bc58121dd47c5d1db2bea4a77f32d2 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 29 Sep 2020 14:15:18 +0200 Subject: [PATCH 49/78] finishes shopping lists --- cookbook/filters.py | 8 +++++++- .../migrations/0089_shoppinglist_finished.py | 18 ++++++++++++++++++ cookbook/models.py | 1 + cookbook/serializer.py | 2 +- cookbook/tables.py | 2 +- cookbook/templates/generic/list_template.html | 2 +- cookbook/templates/shopping_list.html | 15 +++++++++++++-- cookbook/views/lists.py | 8 +++++--- 8 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 cookbook/migrations/0089_shoppinglist_finished.py diff --git a/cookbook/filters.py b/cookbook/filters.py index 916435009..6d0894d58 100644 --- a/cookbook/filters.py +++ b/cookbook/filters.py @@ -2,7 +2,7 @@ import django_filters from django.contrib.postgres.search import TrigramSimilarity from django.db.models import Q from cookbook.forms import MultiSelectWidget -from cookbook.models import Recipe, Keyword, Food +from cookbook.models import Recipe, Keyword, Food, ShoppingList from django.conf import settings from django.utils.translation import gettext as _ @@ -52,3 +52,9 @@ class IngredientFilter(django_filters.FilterSet): class Meta: model = Food fields = ['name'] + + +class ShoppingListFilter(django_filters.FilterSet): + class Meta: + model = ShoppingList + fields = ['finished'] diff --git a/cookbook/migrations/0089_shoppinglist_finished.py b/cookbook/migrations/0089_shoppinglist_finished.py new file mode 100644 index 000000000..3bad27b2e --- /dev/null +++ b/cookbook/migrations/0089_shoppinglist_finished.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.1 on 2020-09-29 11:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0088_auto_20200929_1202'), + ] + + operations = [ + migrations.AddField( + model_name='shoppinglist', + name='finished', + field=models.BooleanField(default=False), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index 06d858a22..2f1309e10 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -312,6 +312,7 @@ class ShoppingList(models.Model): recipes = models.ManyToManyField(ShoppingListRecipe, blank=True) entries = models.ManyToManyField(ShoppingListEntry, blank=True) shared = models.ManyToManyField(User, blank=True, related_name='list_share') + finished = models.BooleanField(default=False) created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index b59653e23..d37c145d4 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -234,7 +234,7 @@ class ShoppingListSerializer(WritableNestedModelSerializer): class Meta: model = ShoppingList - fields = ('id', 'uuid', 'note', 'recipes', 'entries', 'shared', 'created_by', 'created_at',) + fields = ('id', 'uuid', 'note', 'recipes', 'entries', 'shared', 'finished', 'created_by', 'created_at',) read_only_fields = ('id',) diff --git a/cookbook/tables.py b/cookbook/tables.py index e5c99cd9b..a29a75366 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -114,7 +114,7 @@ class ShoppingListTable(tables.Table): class Meta: model = ShoppingList template_name = 'generic/table_template.html' - fields = ('id', 'created_by', 'created_at') + fields = ('id', 'finished', 'created_by', 'created_at') class InviteLinkTable(tables.Table): diff --git a/cookbook/templates/generic/list_template.html b/cookbook/templates/generic/list_template.html index b330ef326..bd69e5260 100644 --- a/cookbook/templates/generic/list_template.html +++ b/cookbook/templates/generic/list_template.html @@ -9,7 +9,7 @@ {% block content %}
-

{{ title }} {% trans 'List' %} +

{{ title }} {% trans 'List' %} {% if create_url %} diff --git a/cookbook/templates/shopping_list.html b/cookbook/templates/shopping_list.html index 6b71c738e..dd0bfde8b 100644 --- a/cookbook/templates/shopping_list.html +++ b/cookbook/templates/shopping_list.html @@ -183,6 +183,17 @@

+
+
+ +
+ + +
+ +
+
+
@@ -450,7 +461,7 @@ console.log("proceeding to update shopping list", this.shopping_list) if (this.shopping_list_id === null) { - this.$http.post("{% url 'api:shoppinglist-list' %}", this.shopping_list, {}).then((response) => { + return this.$http.post("{% url 'api:shoppinglist-list' %}", this.shopping_list, {}).then((response) => { console.log(response) this.makeToast('{% trans 'Updated' %}', '{% trans 'Object created successfully!' %}', 'success') this.loading = false @@ -465,7 +476,7 @@ this.loading = false }) } else { - this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => { + return this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => { console.log(response) this.shopping_list = response.body this.makeToast('{% trans 'Updated' %}', '{% trans 'Changes saved successfully!' %}', 'success') diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py index 0c0dc20d5..1c2723621 100644 --- a/cookbook/views/lists.py +++ b/cookbook/views/lists.py @@ -6,7 +6,7 @@ from django.shortcuts import render from django.utils.translation import gettext as _ from django_tables2 import RequestConfig -from cookbook.filters import IngredientFilter +from cookbook.filters import IngredientFilter, ShoppingListFilter from cookbook.helper.permission_helper import group_required from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Food, ShoppingList, InviteLink from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable, ShoppingListTable, InviteLinkTable @@ -49,10 +49,12 @@ def food(request): @group_required('user') def shopping_list(request): - table = ShoppingListTable(ShoppingList.objects.filter(created_by=request.user).all()) + f = ShoppingListFilter(request.GET, queryset=ShoppingList.objects.filter(created_by=request.user).all().order_by('finished', 'created_at')) + + table = ShoppingListTable(f.qs) RequestConfig(request, paginate={'per_page': 25}).configure(table) - return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'view_shopping'}) + return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'filter': f, 'create_url': 'view_shopping'}) @group_required('admin') From 697de3d9fcb84462ad02d139bf2493beb505eabe Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 29 Sep 2020 14:19:17 +0200 Subject: [PATCH 50/78] small mobile layout improvements --- cookbook/templates/shopping_list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbook/templates/shopping_list.html b/cookbook/templates/shopping_list.html index dd0bfde8b..5c23d87d1 100644 --- a/cookbook/templates/shopping_list.html +++ b/cookbook/templates/shopping_list.html @@ -70,13 +70,13 @@
{% trans 'Shopping Recipes' %}
-
+