diff --git a/cookbook/admin.py b/cookbook/admin.py
index fc148afe5..4c862c9f6 100644
--- a/cookbook/admin.py
+++ b/cookbook/admin.py
@@ -13,7 +13,7 @@ from cookbook.managers import DICTIONARY
from .models import (BookmarkletImport, Comment, CookLog, Food, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
- ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
+ ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
ViewLog)
@@ -361,13 +361,6 @@ class ShoppingListEntryAdmin(admin.ModelAdmin):
admin.site.register(ShoppingListEntry, ShoppingListEntryAdmin)
-# class ShoppingListAdmin(admin.ModelAdmin):
-# list_display = ('id', 'created_by', 'created_at')
-
-
-# admin.site.register(ShoppingList, ShoppingListAdmin)
-
-
class ShareLinkAdmin(admin.ModelAdmin):
list_display = ('recipe', 'created_by', 'uuid', 'created_at',)
diff --git a/cookbook/helper/permission_helper.py b/cookbook/helper/permission_helper.py
index 889d75055..b6e49771f 100644
--- a/cookbook/helper/permission_helper.py
+++ b/cookbook/helper/permission_helper.py
@@ -75,7 +75,7 @@ def is_object_owner(user, obj):
if not user.is_authenticated:
return False
try:
- return obj.get_owner() == user
+ return obj.get_owner() == 'orphan' or obj.get_owner() == user
except Exception:
return False
diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py
index c7772a078..c4c5659b2 100644
--- a/cookbook/helper/shopping_helper.py
+++ b/cookbook/helper/shopping_helper.py
@@ -1,9 +1,8 @@
-from datetime import timedelta
+
from decimal import Decimal
from django.db.models import F, OuterRef, Q, Subquery, Value
from django.db.models.functions import Coalesce
-from django.utils import timezone
from django.utils.translation import gettext as _
from cookbook.models import (Ingredient, MealPlan, Recipe, ShoppingListEntry, ShoppingListRecipe,
@@ -76,10 +75,8 @@ class RecipeShoppingEditor():
@staticmethod
def get_shopping_list_recipe(id, user, space):
- return ShoppingListRecipe.objects.filter(id=id).filter(Q(shoppinglist__space=space) | Q(entries__space=space)).filter(
- Q(shoppinglist__created_by=user)
- | Q(shoppinglist__shared=user)
- | Q(entries__created_by=user)
+ return ShoppingListRecipe.objects.filter(id=id).filter(entries__space=space).filter(
+ Q(entries__created_by=user)
| Q(entries__created_by__in=list(user.get_shopping_share()))
).prefetch_related('entries').first()
diff --git a/cookbook/migrations/0214_delete_shopping_model.py b/cookbook/migrations/0214_delete_shopping_model.py
new file mode 100644
index 000000000..74941a830
--- /dev/null
+++ b/cookbook/migrations/0214_delete_shopping_model.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.10 on 2024-02-19 20:21
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "cookbook",
+ "0213_remove_property_property_unique_import_food_per_space_and_more",
+ ),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name="ShoppingList",
+ ),
+ ]
diff --git a/cookbook/models.py b/cookbook/models.py
index fc1a62ad1..5786599ca 100644
--- a/cookbook/models.py
+++ b/cookbook/models.py
@@ -368,9 +368,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
Sync.objects.filter(space=self).delete()
Storage.objects.filter(space=self).delete()
- ShoppingListEntry.objects.filter(shoppinglist__space=self).delete()
- ShoppingListRecipe.objects.filter(shoppinglist__space=self).delete()
- ShoppingList.objects.filter(space=self).delete()
+ ShoppingListEntry.objects.filter(space=self).delete()
+ ShoppingListRecipe.objects.filter(recipe__space=self).delete()
SupermarketCategoryRelation.objects.filter(supermarket__space=self).delete()
SupermarketCategory.objects.filter(space=self).delete()
@@ -1169,7 +1168,10 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
def get_owner(self):
try:
- return getattr(self.entries.first(), 'created_by', None) or getattr(self.shoppinglist_set.first(), 'created_by', None)
+ if not self.entries.exists():
+ return 'orphan'
+ else:
+ return getattr(self.entries.first(), 'created_by', None)
except AttributeError:
return None
@@ -1192,53 +1194,19 @@ class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), model
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
- @staticmethod
- def get_space_key():
- return 'shoppinglist', 'space'
-
- def get_space(self):
- return self.shoppinglist_set.first().space
-
def __str__(self):
return f'Shopping list entry {self.id}'
def get_shared(self):
- try:
- return self.shoppinglist_set.first().shared.all()
- except AttributeError:
- return self.created_by.userpreference.shopping_share.all()
+ return self.created_by.userpreference.shopping_share.all()
def get_owner(self):
try:
- return self.created_by or self.shoppinglist_set.first().created_by
+ return self.created_by
except AttributeError:
return None
-class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
- uuid = models.UUIDField(default=uuid.uuid4)
- note = models.TextField(blank=True, null=True)
- recipes = models.ManyToManyField(ShoppingListRecipe, blank=True)
- entries = models.ManyToManyField(ShoppingListEntry, blank=True)
- shared = models.ManyToManyField(User, blank=True, related_name='list_share')
- supermarket = models.ForeignKey(Supermarket, null=True, blank=True, on_delete=models.SET_NULL)
- finished = models.BooleanField(default=False)
- created_by = models.ForeignKey(User, on_delete=models.CASCADE)
- created_at = models.DateTimeField(auto_now_add=True)
-
- space = models.ForeignKey(Space, on_delete=models.CASCADE)
- objects = ScopedManager(space='space')
-
- def __str__(self):
- return f'Shopping list {self.id}'
-
- def get_shared(self):
- try:
- return self.shared.all() or self.created_by.userpreference.shopping_share.all()
- except AttributeError:
- return []
-
-
class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
uuid = models.UUIDField(default=uuid.uuid4)
diff --git a/cookbook/serializer.py b/cookbook/serializer.py
index 8446e3fb5..06e80129e 100644
--- a/cookbook/serializer.py
+++ b/cookbook/serializer.py
@@ -1,4 +1,3 @@
-import traceback
import uuid
from datetime import datetime, timedelta
from decimal import Decimal
@@ -31,7 +30,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, NutritionInformation, Property,
PropertyType, Recipe, RecipeBook, RecipeBookEntry, RecipeImport,
- ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
+ ShareLink, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog)
@@ -82,14 +81,12 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
class OpenDataModelMixin(serializers.ModelSerializer):
def create(self, validated_data):
- if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data[
- 'open_data_slug'].strip() == '':
+ if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().create(validated_data)
def update(self, instance, validated_data):
- if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data[
- 'open_data_slug'].strip() == '':
+ if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().update(instance, validated_data)
@@ -336,8 +333,7 @@ class UserSpaceSerializer(WritableNestedModelSerializer):
class Meta:
model = UserSpace
- fields = (
- 'id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
+ fields = ('id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space')
@@ -845,8 +841,7 @@ class UnitConversionSerializer(WritableNestedModelSerializer, OpenDataModelMixin
class Meta:
model = UnitConversion
- fields = (
- 'id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
+ fields = ('id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
class NutritionInformationSerializer(serializers.ModelSerializer):
@@ -1150,7 +1145,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
'recipe_mealplan',
'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until'
)
- read_only_fields = ('id', 'created_by', 'created_at','updated_at',)
+ read_only_fields = ('id', 'created_by', 'created_at', 'updated_at',)
class ShoppingListEntryBulkSerializer(serializers.Serializer):
@@ -1165,37 +1160,6 @@ class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer):
fields = ('id', 'checked')
-# TODO deprecate
-class ShoppingListSerializer(WritableNestedModelSerializer):
- recipes = ShoppingListRecipeSerializer(many=True, allow_null=True)
- entries = ShoppingListEntrySerializer(many=True, allow_null=True)
- shared = UserSerializer(many=True)
- supermarket = SupermarketSerializer(allow_null=True)
-
- def create(self, validated_data):
- validated_data['space'] = self.context['request'].space
- validated_data['created_by'] = self.context['request'].user
- return super().create(validated_data)
-
- class Meta:
- model = ShoppingList
- fields = (
- 'id', 'uuid', 'note', 'recipes', 'entries',
- 'shared', 'finished', 'supermarket', 'created_by', 'created_at'
- )
- read_only_fields = ('id', 'created_by',)
-
-
-# TODO deprecate
-class ShoppingListAutoSyncSerializer(WritableNestedModelSerializer):
- entries = ShoppingListEntryCheckedSerializer(many=True, allow_null=True)
-
- class Meta:
- model = ShoppingList
- fields = ('id', 'entries',)
- read_only_fields = ('id',)
-
-
class ShareLinkSerializer(SpacedModelSerializer):
class Meta:
model = ShareLink
diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html
index 14b3a18f9..ba5b4ecf7 100644
--- a/cookbook/templates/base.html
+++ b/cookbook/templates/base.html
@@ -114,7 +114,7 @@
class="fas fa-fw fa-calendar"> {% trans 'Meal-Plan' %}
- {% trans 'Shopping' %}
diff --git a/cookbook/tests/api/test_api_shopping_list.py b/cookbook/tests/api/test_api_shopping_list.py
deleted file mode 100644
index 6f026cdf6..000000000
--- a/cookbook/tests/api/test_api_shopping_list.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import json
-
-import pytest
-from django.contrib import auth
-from django.urls import reverse
-from django_scopes import scopes_disabled
-
-from cookbook.models import ShoppingList
-
-LIST_URL = 'api:shoppinglist-list'
-DETAIL_URL = 'api:shoppinglist-detail'
-
-
-@pytest.fixture()
-def obj_1(space_1, u1_s1):
- return ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
-
-
-@pytest.fixture
-def obj_2(space_1, u1_s1):
- return ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 200],
- ['u1_s1', 200],
- ['a1_s1', 200],
-])
-def test_list_permission(arg, request):
- c = request.getfixturevalue(arg[0])
- assert c.get(reverse(LIST_URL)).status_code == arg[1]
-
-
-def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
- obj_1.space = space_2
- obj_1.save()
-
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
-
-def test_share(obj_1, u1_s1, u2_s1, u1_s2):
- assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
- assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
-
- obj_1.shared.add(auth.get_user(u2_s1))
- obj_1.shared.add(auth.get_user(u1_s2))
-
- assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
-
-
-def test_new_share(request, obj_1, u1_s1, u2_s1, u1_s2):
- assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
- assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
-
- with scopes_disabled():
- user = auth.get_user(u1_s1)
- user.userpreference.shopping_share.add(auth.get_user(u2_s1))
- user.userpreference.save()
-
- assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
- assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 404],
- ['u1_s1', 200],
- ['a1_s1', 404],
- ['g1_s2', 404],
- ['u1_s2', 404],
- ['a1_s2', 404],
-])
-def test_update(arg, request, obj_1):
- c = request.getfixturevalue(arg[0])
- r = c.patch(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- ),
- {'note': 'new'},
- content_type='application/json'
- )
- assert r.status_code == arg[1]
- if r.status_code == 200:
- response = json.loads(r.content)
- assert response['note'] == 'new'
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 201],
- ['u1_s1', 201],
- ['a1_s1', 201],
-])
-def test_add(arg, request):
- c = request.getfixturevalue(arg[0])
- r = c.post(
- reverse(LIST_URL),
- {'note': 'test', 'recipes': [], 'shared': [], 'entries': [], 'supermarket': None},
- content_type='application/json'
- )
- response = json.loads(r.content)
- print(r.content)
- assert r.status_code == arg[1]
- if r.status_code == 201:
- assert response['note'] == 'test'
-
-
-def test_delete(u1_s1, u1_s2, obj_1):
- r = u1_s2.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
- assert r.status_code == 404
-
- r = u1_s1.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
-
- assert r.status_code == 204
diff --git a/cookbook/tests/api/test_api_shopping_list_entry.py b/cookbook/tests/api/test_api_shopping_list_entry.py
deleted file mode 100644
index 06319bf6f..000000000
--- a/cookbook/tests/api/test_api_shopping_list_entry.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import json
-
-import pytest
-from django.contrib import auth
-from django.urls import reverse
-from django_scopes import scopes_disabled
-
-from cookbook.models import Food, ShoppingList, ShoppingListEntry
-
-LIST_URL = 'api:shoppinglistentry-list'
-DETAIL_URL = 'api:shoppinglistentry-detail'
-
-
-@pytest.fixture()
-def obj_1(space_1, u1_s1):
- e = ShoppingListEntry.objects.create(created_by=auth.get_user(u1_s1),
- food=Food.objects.get_or_create(name='test 1', space=space_1)[0],
- space=space_1)
- s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
- s.entries.add(e)
- return e
-
-
-@pytest.fixture
-def obj_2(space_1, u1_s1):
- e = ShoppingListEntry.objects.create(created_by=auth.get_user(u1_s1),
- food=Food.objects.get_or_create(name='test 2', space=space_1)[0],
- space=space_1)
- s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
- s.entries.add(e)
- return e
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 200],
- ['u1_s1', 200],
- ['a1_s1', 200],
-])
-def test_list_permission(arg, request):
- c = request.getfixturevalue(arg[0])
- assert c.get(reverse(LIST_URL)).status_code == arg[1]
-
-
-def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
- with scopes_disabled():
- s = ShoppingList.objects.first()
- e = ShoppingListEntry.objects.first()
- s.space = space_2
- e.space = space_2
- s.save()
- e.save()
-
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 404],
- ['u1_s1', 200],
- ['a1_s1', 404],
- ['g1_s2', 404],
- ['u1_s2', 404],
- ['a1_s2', 404],
-])
-def test_update(arg, request, obj_1):
- c = request.getfixturevalue(arg[0])
- r = c.patch(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- ),
- {'amount': 2},
- content_type='application/json'
- )
- assert r.status_code == arg[1]
- if r.status_code == 200:
- response = json.loads(r.content)
- assert response['amount'] == 2
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 403],
- ['u1_s1', 200],
- ['a1_s1', 200],
- ['g1_s2', 403],
- ['u1_s2', 200],
- ['a1_s2', 200],
-])
-def test_bulk_update(arg, request, obj_1, obj_2):
- c = request.getfixturevalue(arg[0])
- r = c.post(
- reverse(LIST_URL, ) + 'bulk/',
- {'ids': [obj_1.id, obj_2.id], 'checked': True},
- content_type='application/json'
- )
- assert r.status_code == arg[1]
- assert r
- if r.status_code == 200:
- obj_1.refresh_from_db()
- assert obj_1.checked == (arg[0] == 'u1_s1')
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 201],
- ['u1_s1', 201],
- ['a1_s1', 201],
-])
-def test_add(arg, request, obj_1):
- c = request.getfixturevalue(arg[0])
- r = c.post(
- reverse(LIST_URL),
- {'food': {
- 'id': obj_1.food.__dict__['id'],
- 'name': obj_1.food.__dict__['name'],
- }, 'amount': 1},
- content_type='application/json'
- )
- response = json.loads(r.content)
- print(r.content)
- assert r.status_code == arg[1]
- if r.status_code == 201:
- assert response['food']['id'] == obj_1.food.pk
-
-
-def test_delete(u1_s1, u1_s2, obj_1):
- r = u1_s2.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
- assert r.status_code == 404
-
- r = u1_s1.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
-
- assert r.status_code == 204
diff --git a/cookbook/tests/api/test_api_shopping_list_entryv2.py b/cookbook/tests/api/test_api_shopping_list_entryv2.py
index f1266d1e4..72c3c9435 100644
--- a/cookbook/tests/api/test_api_shopping_list_entryv2.py
+++ b/cookbook/tests/api/test_api_shopping_list_entryv2.py
@@ -214,7 +214,7 @@ def test_completed(sle, u1_s1):
def test_recent(sle, u1_s1):
user = auth.get_user(u1_s1)
- user.userpreference.shopping_recent_days = 7 # hardcoded API limit 14 days
+ user.userpreference.shopping_recent_days = 7 # hardcoded API limit 14 days
user.userpreference.save()
today_start = timezone.now().replace(hour=0, minute=0, second=0)
diff --git a/cookbook/tests/api/test_api_shopping_list_recipe.py b/cookbook/tests/api/test_api_shopping_list_recipe.py
index 034ffb8b9..bd8de2f40 100644
--- a/cookbook/tests/api/test_api_shopping_list_recipe.py
+++ b/cookbook/tests/api/test_api_shopping_list_recipe.py
@@ -3,9 +3,8 @@ import json
import pytest
from django.contrib import auth
from django.urls import reverse
-from django_scopes import scopes_disabled
-from cookbook.models import ShoppingList, ShoppingListRecipe
+from cookbook.models import ShoppingListEntry, ShoppingListRecipe
LIST_URL = 'api:shoppinglistrecipe-list'
DETAIL_URL = 'api:shoppinglistrecipe-detail'
@@ -14,81 +13,31 @@ DETAIL_URL = 'api:shoppinglistrecipe-detail'
@pytest.fixture()
def obj_1(space_1, u1_s1, recipe_1_s1):
r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1)
- s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
- s.recipes.add(r)
+ for ing in r.recipe.steps.first().ingredients.all():
+ ShoppingListEntry.objects.create(list_recipe=r, ingredient=ing, food=ing.food, unit=ing.unit, amount=ing.amount, created_by=auth.get_user(u1_s1), space=space_1)
return r
-@pytest.fixture
-def obj_2(space_1, u1_s1, recipe_1_s1):
- r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1)
- s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
- s.recipes.add(r)
- return r
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 200],
- ['u1_s1', 200],
- ['a1_s1', 200],
-])
+@pytest.mark.parametrize("arg", [['a_u', 403], ['g1_s1', 200], ['u1_s1', 200], ['a1_s1', 200], ])
def test_list_permission(arg, request):
c = request.getfixturevalue(arg[0])
assert c.get(reverse(LIST_URL)).status_code == arg[1]
-def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
- with scopes_disabled():
- s = ShoppingList.objects.first()
- s.space = space_2
- s.save()
-
- assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
- assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
-
-
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 404],
- ['u1_s1', 200],
- ['a1_s1', 404],
- ['g1_s2', 404],
- ['u1_s2', 404],
- ['a1_s2', 404],
-])
+@pytest.mark.parametrize("arg", [['a_u', 403], ['g1_s1', 404], ['u1_s1', 200], ['a1_s1', 404], ['g1_s2', 404], ['u1_s2', 404], ['a1_s2', 404], ])
def test_update(arg, request, obj_1):
c = request.getfixturevalue(arg[0])
- r = c.patch(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- ),
- {'servings': 2},
- content_type='application/json'
- )
+ r = c.patch(reverse(DETAIL_URL, args={obj_1.id}), {'servings': 2}, content_type='application/json')
assert r.status_code == arg[1]
if r.status_code == 200:
response = json.loads(r.content)
assert response['servings'] == 2
-@pytest.mark.parametrize("arg", [
- ['a_u', 403],
- ['g1_s1', 201],
- ['u1_s1', 201],
- ['a1_s1', 201],
-])
+@pytest.mark.parametrize("arg", [['a_u', 403], ['g1_s1', 201], ['u1_s1', 201], ['a1_s1', 201], ])
def test_add(arg, request, obj_1, recipe_1_s1):
c = request.getfixturevalue(arg[0])
- r = c.post(
- reverse(LIST_URL),
- {'recipe': recipe_1_s1.pk, 'servings': 1},
- content_type='application/json'
- )
+ r = c.post(reverse(LIST_URL), {'recipe': recipe_1_s1.pk, 'servings': 1}, content_type='application/json')
response = json.loads(r.content)
print(r.content)
assert r.status_code == arg[1]
@@ -97,19 +46,9 @@ def test_add(arg, request, obj_1, recipe_1_s1):
def test_delete(u1_s1, u1_s2, obj_1):
- r = u1_s2.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
+ r = u1_s2.delete(reverse(DETAIL_URL, args={obj_1.id}))
assert r.status_code == 404
- r = u1_s1.delete(
- reverse(
- DETAIL_URL,
- args={obj_1.id}
- )
- )
+ r = u1_s1.delete(reverse(DETAIL_URL, args={obj_1.id}))
assert r.status_code == 204
diff --git a/cookbook/tests/api/test_api_unit.py b/cookbook/tests/api/test_api_unit.py
index 8c4099ce7..6a8eea624 100644
--- a/cookbook/tests/api/test_api_unit.py
+++ b/cookbook/tests/api/test_api_unit.py
@@ -6,7 +6,7 @@ from django.contrib import auth
from django.urls import reverse
from django_scopes import scopes_disabled
-from cookbook.models import Food, Ingredient, ShoppingList, ShoppingListEntry, Unit
+from cookbook.models import Food, Ingredient, ShoppingListEntry, Unit
LIST_URL = 'api:unit-list'
DETAIL_URL = 'api:unit-detail'
@@ -50,8 +50,6 @@ def ing_3_s2(obj_3, space_2, u2_s2):
@pytest.fixture()
def sle_1_s1(obj_1, u1_s1, space_1):
e = ShoppingListEntry.objects.create(unit=obj_1, food=random_food(space_1, u1_s1), created_by=auth.get_user(u1_s1), space=space_1,)
- s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
- s.entries.add(e)
return e
@@ -63,8 +61,6 @@ def sle_2_s1(obj_2, u1_s1, space_1):
@pytest.fixture()
def sle_3_s2(obj_3, u2_s2, space_2):
e = ShoppingListEntry.objects.create(unit=obj_3, food=random_food(space_2, u2_s2), created_by=auth.get_user(u2_s2), space=space_2)
- s = ShoppingList.objects.create(created_by=auth.get_user(u2_s2), space=space_2)
- s.entries.add(e)
return e
diff --git a/cookbook/urls.py b/cookbook/urls.py
index 9566541a2..6bd2c26df 100644
--- a/cookbook/urls.py
+++ b/cookbook/urls.py
@@ -10,7 +10,7 @@ from cookbook.version_info import TANDOOR_VERSION
from recipes.settings import DEBUG, PLUGINS
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, PropertyType,
- Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Space, Step,
+ Recipe, RecipeBook, RecipeBookEntry, RecipeImport, Space, Step,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserSpace, get_model_name)
from .views import api, data, delete, edit, import_export, lists, new, telegram, views
@@ -45,7 +45,6 @@ router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'unit-conversion', api.UnitConversionViewSet)
router.register(r'food-property-type', api.PropertyTypeViewSet) # TODO rename + regenerate
router.register(r'food-property', api.PropertyViewSet)
-router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
router.register(r'space', api.SpaceViewSet)
@@ -87,7 +86,7 @@ urlpatterns = [
path('plan/', views.meal_plan, name='view_plan'),
path('shopping/', lists.shopping_list, name='view_shopping'),
path('settings/', views.user_settings, name='view_settings'),
- path('settings-shopping/', views.shopping_settings, name='view_shopping_settings'),
+ path('settings-shopping/', views.shopping_settings, name='view_shopping_settings'), # TODO rename to search settings
path('history/', views.history, name='view_history'),
path('supermarket/', views.supermarket, name='view_supermarket'),
path('ingredient-editor/', views.ingredient_editor, name='view_ingredient_editor'),
@@ -167,7 +166,7 @@ urlpatterns = [
generic_models = (
Recipe, RecipeImport, Storage, RecipeBook, SyncLog, Sync,
- Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space
+ Comment, RecipeBookEntry, InviteLink, UserSpace, Space
)
for m in generic_models:
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index 98e5f3a2b..df6ea8db0 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -30,7 +30,6 @@ from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
-from django.utils.timezone import make_aware
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
@@ -58,59 +57,40 @@ from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.open_data_importer import OpenDataImporter
-from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, CustomIsOwnerReadOnly,
- CustomIsShared, CustomIsSpaceOwner, CustomIsUser,
- CustomRecipePermission, CustomTokenHasReadWriteScope,
- CustomTokenHasScope, CustomUserPermission,
- IsReadOnlyDRF, above_space_limit, group_required,
- has_group_permission, is_space_owner,
- switch_user_active_space)
+from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, CustomIsOwnerReadOnly, CustomIsShared, CustomIsSpaceOwner, CustomIsUser, CustomRecipePermission,
+ CustomTokenHasReadWriteScope, CustomTokenHasScope, CustomUserPermission, IsReadOnlyDRF, above_space_limit, group_required,
+ has_group_permission, is_space_owner, switch_user_active_space,
+ )
from cookbook.helper.recipe_search import RecipeSearch
-from cookbook.helper.recipe_url_import import (clean_dict, get_from_youtube_scraper,
- get_images_from_soup)
+from cookbook.helper.recipe_url_import import clean_dict, get_from_youtube_scraper, get_images_from_soup
from cookbook.helper.scrapers.scrapers import text_scraper
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
-from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
- FoodInheritField, FoodProperty, ImportLog, Ingredient, InviteLink,
- Keyword, MealPlan, MealType, Property, PropertyType, Recipe,
- RecipeBook, RecipeBookEntry, ShareLink, ShoppingList,
- ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
- Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
- SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
- ViewLog)
+from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food, FoodInheritField, FoodProperty, ImportLog, Ingredient, InviteLink, Keyword,
+ MealPlan, MealType, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingListEntry, ShoppingListRecipe, Space, Step,
+ Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
+ ViewLog,
+ )
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
from cookbook.schemas import FilterSchema, QueryParam, QueryParamAutoSchema, TreeSchema
-from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
- AutoMealPlanSerializer, BookmarkletImportListSerializer,
- BookmarkletImportSerializer, CookLogSerializer,
- CustomFilterSerializer, ExportLogSerializer,
- FoodInheritFieldSerializer, FoodSerializer,
- FoodShoppingUpdateSerializer, FoodSimpleSerializer,
- GroupSerializer, ImportLogSerializer, IngredientSerializer,
- IngredientSimpleSerializer, InviteLinkSerializer,
- KeywordSerializer, MealPlanSerializer, MealTypeSerializer,
- PropertySerializer, PropertyTypeSerializer,
- RecipeBookEntrySerializer, RecipeBookSerializer,
- RecipeExportSerializer, RecipeFromSourceSerializer,
- RecipeImageSerializer, RecipeOverviewSerializer, RecipeSerializer,
- RecipeShoppingUpdateSerializer, RecipeSimpleSerializer,
- ShoppingListAutoSyncSerializer, ShoppingListEntrySerializer,
- ShoppingListRecipeSerializer, ShoppingListSerializer,
- SpaceSerializer, StepSerializer, StorageSerializer,
- SupermarketCategoryRelationSerializer,
- SupermarketCategorySerializer, SupermarketSerializer,
- SyncLogSerializer, SyncSerializer, UnitConversionSerializer,
- UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
- UserSerializer, UserSpaceSerializer, ViewLogSerializer,
- ShoppingListEntryBulkSerializer)
+from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, AutoMealPlanSerializer, BookmarkletImportListSerializer, BookmarkletImportSerializer,
+ CookLogSerializer, CustomFilterSerializer, ExportLogSerializer, FoodInheritFieldSerializer, FoodSerializer, FoodShoppingUpdateSerializer,
+ FoodSimpleSerializer, GroupSerializer, ImportLogSerializer, IngredientSerializer, IngredientSimpleSerializer, InviteLinkSerializer,
+ KeywordSerializer, MealPlanSerializer, MealTypeSerializer, PropertySerializer, PropertyTypeSerializer, RecipeBookEntrySerializer,
+ RecipeBookSerializer, RecipeExportSerializer, RecipeFromSourceSerializer, RecipeImageSerializer, RecipeOverviewSerializer, RecipeSerializer,
+ RecipeShoppingUpdateSerializer, RecipeSimpleSerializer, ShoppingListEntryBulkSerializer, ShoppingListEntrySerializer,
+ ShoppingListRecipeSerializer, SpaceSerializer, StepSerializer, StorageSerializer, SupermarketCategoryRelationSerializer,
+ SupermarketCategorySerializer, SupermarketSerializer, SyncLogSerializer, SyncSerializer, UnitConversionSerializer, UnitSerializer,
+ UserFileSerializer, UserPreferenceSerializer, UserSerializer, UserSpaceSerializer, ViewLogSerializer,
+ )
from cookbook.views.import_export import get_integration
from recipes import settings
-from recipes.settings import FDC_API_KEY, DRF_THROTTLE_RECIPE_URL_IMPORT
+from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY
class StandardFilterMixin(ViewSetMixin):
+
def get_queryset(self):
queryset = self.queryset
query = self.request.query_params.get('query', None)
@@ -161,12 +141,13 @@ class ExtendedRecipeMixin():
queryset = queryset.annotate(recipe_count=Coalesce(Subquery(recipe_count), 0))
# add a recipe image annotation to the query
- image_subquery = Recipe.objects.filter(**{recipe_filter: OuterRef('id')}, space=space).exclude(
- image__isnull=True).exclude(image__exact='').order_by("?").values('image')[:1]
+ image_subquery = Recipe.objects.filter(**{
+ recipe_filter: OuterRef('id')
+ }, space=space).exclude(image__isnull=True).exclude(image__exact='').order_by("?").values('image')[:1]
if tree:
- image_children_subquery = Recipe.objects.filter(
- **{f"{recipe_filter}__path__startswith": OuterRef('path')},
- space=space).exclude(image__isnull=True).exclude(image__exact='').order_by("?").values('image')[:1]
+ image_children_subquery = Recipe.objects.filter(**{
+ f"{recipe_filter}__path__startswith": OuterRef('path')
+ }, space=space).exclude(image__isnull=True).exclude(image__exact='').order_by("?").values('image')[:1]
else:
image_children_subquery = None
if images:
@@ -183,17 +164,17 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
query = self.request.query_params.get('query', None)
if self.request.user.is_authenticated:
- fuzzy = self.request.user.searchpreference.lookup or any([self.model.__name__.lower() in x for x in
- self.request.user.searchpreference.trigram.values_list(
- 'field', flat=True)])
+ fuzzy = self.request.user.searchpreference.lookup or any(
+ [self.model.__name__.lower() in x for x in self.request.user.searchpreference.trigram.values_list('field', flat=True)])
else:
fuzzy = True
if query is not None and query not in ["''", '']:
if fuzzy and (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'):
if self.request.user.is_authenticated and any(
- [self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
- self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
+ [self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]
+ ):
+ self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
else:
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
self.queryset = self.queryset.order_by('-trigram')
@@ -205,10 +186,9 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
filter |= Q(name__unaccent__icontains=query)
self.queryset = (
- self.queryset.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
- default=Value(0))) # put exact matches at the top of the result set
- .filter(filter).order_by('-starts', Lower('name').asc())
- )
+ self.queryset.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))), default=Value(0))) # put exact matches at the top of the result set
+ .filter(filter).order_by('-starts',
+ Lower('name').asc()))
updated_at = self.request.query_params.get('updated_at', None)
if updated_at is not None:
@@ -229,6 +209,7 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
class MergeMixin(ViewSetMixin):
+
@decorators.action(detail=True, url_path='merge/(?P[^/.]+)', methods=['PUT'], )
@decorators.renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def merge(self, request, pk, target):
@@ -296,8 +277,7 @@ class MergeMixin(ViewSetMixin):
return Response(content, status=status.HTTP_200_OK)
except Exception:
traceback.print_exc()
- content = {'error': True,
- 'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')}
+ content = {'error': True, 'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
@@ -330,8 +310,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin, ExtendedRecipeMixin):
return self.annotate_recipe(queryset=super().get_queryset(), request=self.request, serializer=self.serializer_class, tree=True)
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
- return self.annotate_recipe(queryset=self.queryset, request=self.request, serializer=self.serializer_class,
- tree=True)
+ return self.annotate_recipe(queryset=self.queryset, request=self.request, serializer=self.serializer_class, tree=True)
@decorators.action(detail=True, url_path='move/(?P[^/.]+)', methods=['PUT'], )
@decorators.renderer_classes((TemplateHTMLRenderer, JSONRenderer))
@@ -552,23 +531,18 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
def get_queryset(self):
shared_users = []
- if c := caches['default'].get(
- f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}', None):
+ if c := caches['default'].get(f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}', None):
shared_users = c
else:
try:
- shared_users = [x.id for x in list(self.request.user.get_shopping_share())] + [
- self.request.user.id]
- caches['default'].set(
- f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}',
- shared_users, timeout=5 * 60)
+ shared_users = [x.id for x in list(self.request.user.get_shopping_share())] + [self.request.user.id]
+ caches['default'].set(f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}', shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users
pass
self.queryset = super().get_queryset()
- shopping_status = ShoppingListEntry.objects.filter(space=self.request.space, food=OuterRef('id'),
- checked=False).values('id')
+ shopping_status = ShoppingListEntry.objects.filter(space=self.request.space, food=OuterRef('id'), checked=False).values('id')
# onhand_status = self.queryset.annotate(onhand_status=Exists(onhand_users_set__in=[shared_users]))
return self.queryset \
.annotate(shopping_status=Exists(shopping_status)) \
@@ -589,8 +563,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
shared_users = list(self.request.user.get_shopping_share())
shared_users.append(request.user)
if request.data.get('_delete', False) == 'true':
- ShoppingListEntry.objects.filter(food=obj, checked=False, space=request.space,
- created_by__in=shared_users).delete()
+ ShoppingListEntry.objects.filter(food=obj, checked=False, space=request.space, created_by__in=shared_users).delete()
content = {'msg': _(f'{obj.name} was removed from the shopping list.')}
return Response(content, status=status.HTTP_204_NO_CONTENT)
@@ -598,8 +571,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
unit = request.data.get('unit', None)
content = {'msg': _(f'{obj.name} was added to the shopping list.')}
- ShoppingListEntry.objects.create(food=obj, amount=amount, unit=unit, space=request.space,
- created_by=request.user)
+ ShoppingListEntry.objects.create(food=obj, amount=amount, unit=unit, space=request.space, created_by=request.user)
return Response(content, status=status.HTTP_204_NO_CONTENT)
@decorators.action(detail=True, methods=['POST'], )
@@ -610,19 +582,31 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
"""
food = self.get_object()
if not food.fdc_id:
- return JsonResponse({'msg': 'Food has no FDC ID associated.'}, status=400,
- json_dumps_params={'indent': 4})
+ return JsonResponse({'msg': 'Food has no FDC ID associated.'}, status=400, json_dumps_params={'indent': 4})
response = requests.get(f'https://api.nal.usda.gov/fdc/v1/food/{food.fdc_id}?api_key={FDC_API_KEY}')
if response.status_code == 429:
- return JsonResponse({'msg': 'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. Configure your key in Tandoor using environment FDC_API_KEY variable.'}, status=429,
- json_dumps_params={'indent': 4})
+ return JsonResponse(
+ {
+ 'msg':
+ 'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. \
+ Configure your key in Tandoor using environment FDC_API_KEY variable.'
+ },
+ status=429,
+ json_dumps_params={'indent': 4})
if response.status_code != 200:
- return JsonResponse({'msg': f'Error while requesting FDC data using url https://api.nal.usda.gov/fdc/v1/food/{food.fdc_id}?api_key=****'}, status=response.status_code,
+ return JsonResponse({'msg': f'Error while requesting FDC data using url https://api.nal.usda.gov/fdc/v1/food/{food.fdc_id}?api_key=****'},
+ status=response.status_code,
json_dumps_params={'indent': 4})
food.properties_food_amount = 100
- food.properties_food_unit = Unit.objects.get_or_create(base_unit__iexact='g', space=self.request.space, defaults={'name': 'g', 'base_unit': 'g', 'space': self.request.space})[0]
+ food.properties_food_unit = Unit.objects.get_or_create(base_unit__iexact='g',
+ space=self.request.space,
+ defaults={
+ 'name': 'g',
+ 'base_unit': 'g',
+ 'space': self.request.space
+ })[0]
food.save()
try:
@@ -641,27 +625,27 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
for fn in data['foodNutrients']:
if fn['nutrient']['id'] == pt.fdc_id:
property_found = True
- food_property_list.append(Property(
- property_type_id=pt.id,
- property_amount=max(0, round(fn['amount'], 2)), # sometimes FDC might return negative values which make no sense, set to 0
- import_food_id=food.id,
- space=self.request.space,
- ))
+ food_property_list.append(
+ Property(property_type_id=pt.id,
+ property_amount=max(0, round(fn['amount'], 2)), # sometimes FDC might return negative values which make no sense, set to 0
+ import_food_id=food.id,
+ space=self.request.space,
+ ))
if not property_found:
- food_property_list.append(Property(
- property_type_id=pt.id,
- property_amount=0, # if field not in FDC data the food does not have that property
- import_food_id=food.id,
- space=self.request.space,
- ))
+ food_property_list.append(
+ Property(property_type_id=pt.id,
+ property_amount=0, # if field not in FDC data the food does not have that property
+ import_food_id=food.id,
+ space=self.request.space,
+ ))
- Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',))
+ Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type', ))
property_food_relation_list = []
for p in Property.objects.filter(space=self.request.space, import_food_id=food.id).values_list('import_food_id', 'id', ):
property_food_relation_list.append(Food.properties.through(food_id=p[0], property_id=p[1]))
- FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
+ FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id', ))
Property.objects.filter(space=self.request.space, import_food_id=food.id).update(import_food_id=None)
return self.retrieve(request, pk)
@@ -691,8 +675,7 @@ class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
ordering = f"{'' if order_direction == 'asc' else '-'}{order_field}"
- self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
- space=self.request.space).distinct().order_by(ordering)
+ self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct().order_by(ordering)
return super().get_queryset()
@@ -710,9 +693,7 @@ class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
- queryset = self.queryset.filter(
- Q(book__created_by=self.request.user) | Q(book__shared=self.request.user)).filter(
- book__space=self.request.space).distinct()
+ queryset = self.queryset.filter(Q(book__created_by=self.request.user) | Q(book__shared=self.request.user)).filter(book__space=self.request.space).distinct()
recipe_id = self.request.query_params.get('recipe', None)
if recipe_id is not None:
@@ -745,10 +726,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
schema = QueryParamAutoSchema()
def get_queryset(self):
- queryset = self.queryset.filter(
- Q(created_by=self.request.user)
- | Q(shared=self.request.user)
- ).filter(space=self.request.space).distinct().all()
+ queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct().all()
from_date = self.request.query_params.get('from_date', None)
if from_date is not None:
@@ -766,6 +744,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
class AutoPlanViewSet(viewsets.ViewSet):
+
def create(self, request):
serializer = AutoMealPlanSerializer(data=request.data)
@@ -795,10 +774,16 @@ class AutoPlanViewSet(viewsets.ViewSet):
for i in range(0, days):
day = start_date + datetime.timedelta(i)
recipe = recipes[i % len(recipes)]
- args = {'recipe_id': recipe['id'], 'servings': servings,
- 'created_by': request.user,
- 'meal_type_id': serializer.validated_data['meal_type_id'],
- 'note': '', 'from_date': day, 'to_date': day, 'space': request.space}
+ args = {
+ 'recipe_id': recipe['id'],
+ 'servings': servings,
+ 'created_by': request.user,
+ 'meal_type_id': serializer.validated_data['meal_type_id'],
+ 'note': '',
+ 'from_date': day,
+ 'to_date': day,
+ 'space': request.space
+ }
m = MealPlan(**args)
meal_plans.append(m)
@@ -813,12 +798,7 @@ class AutoPlanViewSet(viewsets.ViewSet):
SLR.create(mealplan=m, servings=servings)
else:
- post_save.send(
- sender=m.__class__,
- instance=m,
- created=True,
- update_fields=None,
- )
+ post_save.send(sender=m.__class__, instance=m, created=True, update_fields=None, )
return Response(serializer.data)
@@ -835,8 +815,7 @@ class MealTypeViewSet(viewsets.ModelViewSet):
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
def get_queryset(self):
- queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(
- space=self.request.space).all()
+ queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(space=self.request.space).all()
return queryset
@@ -896,12 +875,7 @@ class RecipePagination(PageNumberPagination):
return super().paginate_queryset(queryset, request, view)
def get_paginated_response(self, data):
- return Response(OrderedDict([
- ('count', self.page.paginator.count),
- ('next', self.get_next_link()),
- ('previous', self.get_previous_link()),
- ('results', data),
- ]))
+ return Response(OrderedDict([('count', self.page.paginator.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('results', data), ]))
class RecipeViewSet(viewsets.ModelViewSet):
@@ -930,15 +904,15 @@ class RecipeViewSet(viewsets.ModelViewSet):
QueryParam(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), qtype='int'),
QueryParam(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), qtype='int'),
QueryParam(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), qtype='int'),
- QueryParam(name='internal', description=_('If only internal recipes should be returned. [''true''/''false'']')),
- QueryParam(name='random', description=_('Returns the results in randomized order. [''true''/''false'']')),
- QueryParam(name='new', description=_('Returns new results first in search results. [''true''/''false'']')),
+ QueryParam(name='internal', description=_('If only internal recipes should be returned. [true/false]')),
+ QueryParam(name='random', description=_('Returns the results in randomized order. [true/false]')),
+ QueryParam(name='new', description=_('Returns new results first in search results. [true/false]')),
QueryParam(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), qtype='int'),
- QueryParam(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
- QueryParam(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
- QueryParam(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
- QueryParam(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
- QueryParam(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''false'']')),
+ QueryParam(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on or before date.')),
+ QueryParam(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or before date.')),
+ QueryParam(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or before date.')),
+ QueryParam(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on or before date.')),
+ QueryParam(name='makenow', description=_('Filter recipes that can be made with OnHand food. [true/false]')),
]
schema = QueryParamAutoSchema()
@@ -947,49 +921,32 @@ class RecipeViewSet(viewsets.ModelViewSet):
if self.detail: # if detail request and not list, private condition is verified by permission class
if not share: # filter for space only if not shared
- self.queryset = self.queryset.filter(space=self.request.space).prefetch_related(
- 'keywords',
- 'shared',
- 'properties',
- 'properties__property_type',
- 'steps',
- 'steps__ingredients',
- 'steps__ingredients__step_set',
- 'steps__ingredients__step_set__recipe_set',
- 'steps__ingredients__food',
- 'steps__ingredients__food__properties',
- 'steps__ingredients__food__properties__property_type',
- 'steps__ingredients__food__inherit_fields',
- 'steps__ingredients__food__supermarket_category',
- 'steps__ingredients__food__onhand_users',
- 'steps__ingredients__food__substitute',
- 'steps__ingredients__food__child_inherit_fields',
-
- 'steps__ingredients__unit',
- 'steps__ingredients__unit__unit_conversion_base_relation',
- 'steps__ingredients__unit__unit_conversion_base_relation__base_unit',
- 'steps__ingredients__unit__unit_conversion_converted_relation',
- 'steps__ingredients__unit__unit_conversion_converted_relation__converted_unit',
- 'cooklog_set',
- ).select_related('nutrition')
+ self.queryset = self.queryset.filter(
+ space=self.request.space).prefetch_related('keywords', 'shared', 'properties', 'properties__property_type', 'steps', 'steps__ingredients',
+ 'steps__ingredients__step_set', 'steps__ingredients__step_set__recipe_set', 'steps__ingredients__food',
+ 'steps__ingredients__food__properties', 'steps__ingredients__food__properties__property_type',
+ 'steps__ingredients__food__inherit_fields', 'steps__ingredients__food__supermarket_category',
+ 'steps__ingredients__food__onhand_users', 'steps__ingredients__food__substitute',
+ 'steps__ingredients__food__child_inherit_fields', 'steps__ingredients__unit',
+ 'steps__ingredients__unit__unit_conversion_base_relation',
+ 'steps__ingredients__unit__unit_conversion_base_relation__base_unit',
+ 'steps__ingredients__unit__unit_conversion_converted_relation',
+ 'steps__ingredients__unit__unit_conversion_converted_relation__converted_unit', 'cooklog_set',
+ ).select_related('nutrition')
return super().get_queryset()
- self.queryset = self.queryset.filter(space=self.request.space).filter(
- Q(private=False) | (Q(private=True) & (Q(created_by=self.request.user) | Q(shared=self.request.user)))
- )
+ self.queryset = self.queryset.filter(
+ space=self.request.space).filter(Q(private=False) | (Q(private=True) & (Q(created_by=self.request.user) | Q(shared=self.request.user))))
- params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x
- in list(self.request.GET)}
+ params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x in list(self.request.GET)}
search = RecipeSearch(self.request, **params)
self.queryset = search.get_queryset(self.queryset).prefetch_related('keywords', 'cooklog_set')
return self.queryset
def list(self, request, *args, **kwargs):
if self.request.GET.get('debug', False):
- return JsonResponse({
- 'new': str(self.get_queryset().query),
- })
+ return JsonResponse({'new': str(self.get_queryset().query), })
return super().list(request, *args, **kwargs)
def get_serializer_class(self):
@@ -997,12 +954,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
return RecipeOverviewSerializer
return self.serializer_class
- @decorators.action(
- detail=True,
- methods=['PUT'],
- serializer_class=RecipeImageSerializer,
- parser_classes=[MultiPartParser],
- )
+ @decorators.action(detail=True, methods=['PUT'], serializer_class=RecipeImageSerializer, parser_classes=[MultiPartParser], )
def image(self, request, pk):
obj = self.get_object()
@@ -1050,11 +1002,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
# TODO: refactor API to use post/put/delete or leave as put and change VUE to use list_recipe after creating
# DRF only allows one action in a decorator action without overriding get_operation_id_base()
- @decorators.action(
- detail=True,
- methods=['PUT'],
- serializer_class=RecipeShoppingUpdateSerializer,
- )
+ @decorators.action(detail=True, methods=['PUT'], serializer_class=RecipeShoppingUpdateSerializer, )
def shopping(self, request, pk):
if self.request.space.demo:
raise PermissionDenied(detail='Not available in demo', code=None)
@@ -1084,11 +1032,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
return Response(content, status=http_status)
- @decorators.action(
- detail=True,
- methods=['GET'],
- serializer_class=RecipeSimpleSerializer
- )
+ @decorators.action(detail=True, methods=['GET'], serializer_class=RecipeSimpleSerializer)
def related(self, request, pk):
obj = self.get_object()
if obj.get_space() != request.space:
@@ -1097,8 +1041,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
levels = int(request.query_params.get('levels', 1))
except (ValueError, TypeError):
levels = 1
- qs = obj.get_related_recipes(
- levels=levels) # TODO: make levels a user setting, included in request data?, keep solely in the backend?
+ qs = obj.get_related_recipes(levels=levels) # TODO: make levels a user setting, included in request data?, keep solely in the backend?
return Response(self.serializer_class(qs, many=True).data)
@@ -1106,9 +1049,7 @@ class UnitConversionViewSet(viewsets.ModelViewSet):
queryset = UnitConversion.objects
serializer_class = UnitConversionSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
- query_params = [
- QueryParam(name='food_id', description='ID of food to filter for', qtype='int'),
- ]
+ query_params = [QueryParam(name='food_id', description='ID of food to filter for', qtype='int'), ]
schema = QueryParamAutoSchema()
def get_queryset(self):
@@ -1143,14 +1084,12 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
- self.queryset = self.queryset.filter(
- Q(shoppinglist__space=self.request.space) | Q(entries__space=self.request.space))
+ self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space))
return self.queryset.filter(
- Q(shoppinglist__created_by=self.request.user)
- | Q(shoppinglist__shared=self.request.user)
+ Q(entries__isnull=True)
| Q(entries__created_by=self.request.user)
| Q(entries__created_by__in=list(self.request.user.get_shopping_share()))
- ).distinct().all()
+ ).distinct().all()
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
@@ -1159,8 +1098,10 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
query_params = [
QueryParam(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), qtype='int'),
- QueryParam(name='checked', description=_('Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''recent'']
- ''recent'' includes unchecked items and recently completed items.')
- ),
+ QueryParam(name='checked',
+ description=_(
+ 'Filter shopping list entries on checked. [true, false, both, recent]
- recent includeks unchecked items and recently completed items.'
+ )),
QueryParam(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), qtype='int'),
]
schema = QueryParamAutoSchema()
@@ -1170,24 +1111,11 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
self.queryset = self.queryset.filter(
Q(created_by=self.request.user)
- | Q(shoppinglist__shared=self.request.user)
- | Q(created_by__in=list(self.request.user.get_shopping_share()))
- ).prefetch_related(
- 'created_by',
- 'food',
- 'food__properties',
- 'food__properties__property_type',
- 'food__inherit_fields',
- 'food__supermarket_category',
- 'food__onhand_users',
- 'food__substitute',
- 'food__child_inherit_fields',
-
- 'unit',
- 'list_recipe',
- 'list_recipe__mealplan',
- 'list_recipe__mealplan__recipe',
- ).distinct().all()
+ | Q(created_by__in=list(self.request.user.get_shopping_share()))).prefetch_related('created_by', 'food', 'food__properties', 'food__properties__property_type',
+ 'food__inherit_fields', 'food__supermarket_category', 'food__onhand_users',
+ 'food__substitute', 'food__child_inherit_fields', 'unit', 'list_recipe',
+ 'list_recipe__mealplan', 'list_recipe__mealplan__recipe',
+ ).distinct().all()
if pk := self.request.query_params.getlist('id', []):
self.queryset = self.queryset.filter(food__id__in=[int(i) for i in pk])
@@ -1204,7 +1132,7 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
if last_autosync:
last_autosync = datetime.datetime.fromtimestamp(int(last_autosync) / 1000, datetime.timezone.utc)
self.queryset = self.queryset.filter(updated_at__gte=last_autosync)
- except:
+ except Exception:
traceback.print_exc()
# TODO once old shopping list is removed this needs updated to sharing users in preferences
@@ -1213,53 +1141,20 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
else:
return self.queryset[:1000]
- @decorators.action(
- detail=False,
- methods=['POST'],
- serializer_class=ShoppingListEntryBulkSerializer,
- permission_classes=[CustomIsUser]
- )
+ @decorators.action(detail=False, methods=['POST'], serializer_class=ShoppingListEntryBulkSerializer, permission_classes=[CustomIsUser])
def bulk(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
- ShoppingListEntry.objects.filter(
- Q(created_by=self.request.user)
- | Q(shoppinglist__shared=self.request.user)
- | Q(created_by__in=list(self.request.user.get_shopping_share()))
- ).filter(space=request.space, id__in=serializer.validated_data['ids']).update(
- checked=serializer.validated_data['checked'],
- updated_at=timezone.now(),
- )
+ ShoppingListEntry.objects.filter(Q(created_by=self.request.user)
+ | Q(created_by__in=list(self.request.user.get_shopping_share()))).filter(space=request.space, id__in=serializer.validated_data['ids']
+ ).update(checked=serializer.validated_data['checked'],
+ updated_at=timezone.now(),
+ )
return Response(serializer.data)
else:
return Response(serializer.errors, 400)
-# TODO deprecate
-class ShoppingListViewSet(viewsets.ModelViewSet):
- queryset = ShoppingList.objects
- serializer_class = ShoppingListSerializer
- permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
-
- def get_queryset(self):
- self.queryset = self.queryset.filter(
- Q(created_by=self.request.user)
- | Q(shared=self.request.user)
- | Q(created_by__in=list(self.request.user.get_shopping_share()))
- ).filter(space=self.request.space)
-
- return self.queryset.distinct()
-
- def get_serializer_class(self):
- try:
- autosync = self.request.query_params.get('autosync', False)
- if autosync:
- return ShoppingListAutoSyncSerializer
- except AttributeError: # Needed for the openapi schema to determine a serializer without a request
- pass
- return self.serializer_class
-
-
class ViewLogViewSet(viewsets.ModelViewSet):
queryset = ViewLog.objects
serializer_class = ViewLogSerializer
@@ -1362,8 +1257,7 @@ class CustomFilterViewSet(viewsets.ModelViewSet, StandardFilterMixin):
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
def get_queryset(self):
- self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
- space=self.request.space).distinct()
+ self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct()
return super().get_queryset()
@@ -1378,6 +1272,7 @@ class AccessTokenViewSet(viewsets.ModelViewSet):
# -------------- DRF custom views --------------------
+
class AuthTokenThrottle(AnonRateThrottle):
rate = '10/day'
@@ -1390,15 +1285,14 @@ class CustomAuthToken(ObtainAuthToken):
throttle_classes = [AuthTokenThrottle]
def post(self, request, *args, **kwargs):
- serializer = self.serializer_class(data=request.data,
- context={'request': request})
+ serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
- if token := AccessToken.objects.filter(user=user, expires__gt=timezone.now(), scope__contains='read').filter(
- scope__contains='write').first():
+ if token := AccessToken.objects.filter(user=user, expires__gt=timezone.now(), scope__contains='read').filter(scope__contains='write').first():
access_token = token
else:
- access_token = AccessToken.objects.create(user=user, token=f'tda_{str(uuid.uuid4()).replace("-", "_")}',
+ access_token = AccessToken.objects.create(user=user,
+ token=f'tda_{str(uuid.uuid4()).replace("-", "_")}',
expires=(timezone.now() + timezone.timedelta(days=365 * 5)),
scope='read write app')
return Response({
@@ -1428,8 +1322,7 @@ class RecipeUrlImportView(APIView):
serializer = RecipeFromSourceSerializer(data=request.data)
if serializer.is_valid():
- if (b_pk := serializer.validated_data.get('bookmarklet', None)) and (
- bookmarklet := BookmarkletImport.objects.filter(pk=b_pk).first()):
+ if (b_pk := serializer.validated_data.get('bookmarklet', None)) and (bookmarklet := BookmarkletImport.objects.filter(pk=b_pk).first()):
serializer.validated_data['url'] = bookmarklet.url
serializer.validated_data['data'] = bookmarklet.html
bookmarklet.delete()
@@ -1437,61 +1330,40 @@ class RecipeUrlImportView(APIView):
url = serializer.validated_data.get('url', None)
data = unquote(serializer.validated_data.get('data', None))
if not url and not data:
- return Response({
- 'error': True,
- 'msg': _('Nothing to do.')
- }, status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('Nothing to do.')}, status=status.HTTP_400_BAD_REQUEST)
elif url and not data:
if re.match('^(https?://)?(www\\.youtube\\.com|youtu\\.be)/.+$', url):
if validators.url(url, public=True):
- return Response({
- 'recipe_json': get_from_youtube_scraper(url, request),
- 'recipe_images': [],
- }, status=status.HTTP_200_OK)
- if re.match(
- '^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
- url):
+ return Response({'recipe_json': get_from_youtube_scraper(url, request), 'recipe_images': [], }, status=status.HTTP_200_OK)
+ if re.match('^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', url):
recipe_json = requests.get(
- url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1],
- '') + '?share=' +
- re.split('/view/recipe/[0-9]+', url)[1].replace('/', '')).json()
+ url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1], '') + '?share='
+ + re.split('/view/recipe/[0-9]+', url)[1].replace('/', '')).json()
recipe_json = clean_dict(recipe_json, 'id')
serialized_recipe = RecipeExportSerializer(data=recipe_json, context={'request': request})
if serialized_recipe.is_valid():
recipe = serialized_recipe.save()
if validators.url(recipe_json['image'], public=True):
recipe.image = File(handle_image(request,
- File(io.BytesIO(requests.get(recipe_json['image']).content),
- name='image'),
+ File(io.BytesIO(requests.get(recipe_json['image']).content), name='image'),
filetype=pathlib.Path(recipe_json['image']).suffix),
name=f'{uuid.uuid4()}_{recipe.pk}{pathlib.Path(recipe_json["image"]).suffix}')
recipe.save()
- return Response({
- 'link': request.build_absolute_uri(reverse('view_recipe', args={recipe.pk}))
- }, status=status.HTTP_201_CREATED)
+ return Response({'link': request.build_absolute_uri(reverse('view_recipe', args={recipe.pk}))}, status=status.HTTP_201_CREATED)
else:
try:
if validators.url(url, public=True):
scrape = scrape_me(url_path=url, wild_mode=True)
else:
- return Response({
- 'error': True,
- 'msg': _('Invalid Url')
- }, status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('Invalid Url')}, status=status.HTTP_400_BAD_REQUEST)
except NoSchemaFoundInWildMode:
pass
except requests.exceptions.ConnectionError:
- return Response({
- 'error': True,
- 'msg': _('Connection Refused.')
- }, status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('Connection Refused.')}, status=status.HTTP_400_BAD_REQUEST)
except requests.exceptions.MissingSchema:
- return Response({
- 'error': True,
- 'msg': _('Bad URL Schema.')
- }, status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('Bad URL Schema.')}, status=status.HTTP_400_BAD_REQUEST)
else:
try:
data_json = json.loads(data)
@@ -1510,13 +1382,11 @@ class RecipeUrlImportView(APIView):
return Response({
'recipe_json': helper.get_from_scraper(scrape, request),
'recipe_images': list(dict.fromkeys(get_images_from_soup(scrape.soup, url))),
- }, status=status.HTTP_200_OK)
+ },
+ status=status.HTTP_200_OK)
else:
- return Response({
- 'error': True,
- 'msg': _('No usable data could be found.')
- }, status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('No usable data could be found.')}, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -1608,8 +1478,7 @@ def import_files(request):
return Response({'import_id': il.pk}, status=status.HTTP_200_OK)
except NotImplementedError:
- return Response({'error': True, 'msg': _('Importing is not implemented for this provider')},
- status=status.HTTP_400_BAD_REQUEST)
+ return Response({'error': True, 'msg': _('Importing is not implemented for this provider')}, status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'error': True, 'msg': form.errors}, status=status.HTTP_400_BAD_REQUEST)
@@ -1693,8 +1562,7 @@ def get_recipe_file(request, recipe_id):
@group_required('user')
def sync_all(request):
if request.space.demo or settings.HOSTED:
- messages.add_message(request, messages.ERROR,
- _('This feature is not yet available in the hosted version of tandoor!'))
+ messages.add_message(request, messages.ERROR, _('This feature is not yet available in the hosted version of tandoor!'))
return redirect('index')
monitors = Sync.objects.filter(active=True).filter(space=request.user.userspace_set.filter(active=1).first().space)
@@ -1715,14 +1583,10 @@ def sync_all(request):
error = True
if not error:
- messages.add_message(
- request, messages.SUCCESS, _('Sync successful!')
- )
+ messages.add_message(request, messages.SUCCESS, _('Sync successful!'))
return redirect('list_recipe_import')
else:
- messages.add_message(
- request, messages.ERROR, _('Error synchronizing with Storage')
- )
+ messages.add_message(request, messages.ERROR, _('Error synchronizing with Storage'))
return redirect('list_recipe_import')
@@ -1730,11 +1594,10 @@ def sync_all(request):
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
def share_link(request, pk):
- if request.space.allow_sharing and has_group_permission(request.user, ('user',)):
+ if request.space.allow_sharing and has_group_permission(request.user, ('user', )):
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space)
- return JsonResponse({'pk': pk, 'share': link.uuid,
- 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))})
+ return JsonResponse({'pk': pk, 'share': link.uuid, 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))})
else:
return JsonResponse({'error': 'sharing_disabled'}, status=403)
@@ -1760,9 +1623,8 @@ def log_cooking(request, recipe_id):
@group_required('user')
def get_plan_ical(request, from_date, to_date):
- queryset = MealPlan.objects.filter(
- Q(created_by=request.user) | Q(shared=request.user)
- ).filter(space=request.user.userspace_set.filter(active=1).first().space).distinct().all()
+ queryset = MealPlan.objects.filter(Q(created_by=request.user)
+ | Q(shared=request.user)).filter(space=request.user.userspace_set.filter(active=1).first().space).distinct().all()
if from_date is not None:
queryset = queryset.filter(from_date__gte=from_date)
@@ -1803,12 +1665,4 @@ def ingredient_from_string(request):
ingredient_parser = IngredientParser(request, False)
amount, unit, food, note = ingredient_parser.parse(text)
- return JsonResponse(
- {
- 'amount': amount,
- 'unit': unit,
- 'food': food,
- 'note': note
- },
- status=200
- )
+ return JsonResponse({'amount': amount, 'unit': unit, 'food': food, 'note': note}, status=200)
diff --git a/cookbook/views/views.py b/cookbook/views/views.py
index 55474494d..5b9726f53 100644
--- a/cookbook/views/views.py
+++ b/cookbook/views/views.py
@@ -1,7 +1,5 @@
-import json
import os
import re
-import subprocess
from datetime import datetime
from io import StringIO
from uuid import UUID
@@ -17,7 +15,6 @@ from django.core.management import call_command
from django.db import models
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
-from django.templatetags.static import static
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext as _
@@ -30,7 +27,7 @@ from cookbook.models import Comment, CookLog, InviteLink, SearchFields, SearchPr
from cookbook.tables import CookLogTable, ViewLogTable
from cookbook.templatetags.theming_tags import get_theming_values
from cookbook.version_info import VERSION_INFO
-from recipes.settings import BASE_DIR, PLUGINS
+from recipes.settings import PLUGINS
def index(request):
@@ -159,9 +156,7 @@ def recipe_view(request, pk, share=None):
if request.method == "GET":
servings = request.GET.get("servings")
- return render(request, 'recipe_view.html',
- {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings })
-
+ return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings})
@group_required('user')
@@ -363,7 +358,8 @@ def setup(request):
if User.objects.count() > 0 or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS:
messages.add_message(
request, messages.ERROR,
- _('The setup page can only be used to create the first user! If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.'
+ _('The setup page can only be used to create the first user! \
+ If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.'
))
return HttpResponseRedirect(reverse('account_login'))
diff --git a/recipes/settings.py b/recipes/settings.py
index b8bf35571..4cd707d6b 100644
--- a/recipes/settings.py
+++ b/recipes/settings.py
@@ -25,8 +25,7 @@ load_dotenv()
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Get vars from .env files
-SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv(
- 'SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV'
+SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV'
DEBUG = bool(int(os.getenv('DEBUG', True)))
DEBUG_TOOLBAR = bool(int(os.getenv('DEBUG_TOOLBAR', True)))
@@ -37,11 +36,9 @@ SOCIAL_DEFAULT_GROUP = os.getenv('SOCIAL_DEFAULT_GROUP', 'guest')
SPACE_DEFAULT_MAX_RECIPES = int(os.getenv('SPACE_DEFAULT_MAX_RECIPES', 0))
SPACE_DEFAULT_MAX_USERS = int(os.getenv('SPACE_DEFAULT_MAX_USERS', 0))
SPACE_DEFAULT_MAX_FILES = int(os.getenv('SPACE_DEFAULT_MAX_FILES', 0))
-SPACE_DEFAULT_ALLOW_SHARING = bool(
- int(os.getenv('SPACE_DEFAULT_ALLOW_SHARING', True)))
+SPACE_DEFAULT_ALLOW_SHARING = bool(int(os.getenv('SPACE_DEFAULT_ALLOW_SHARING', True)))
-INTERNAL_IPS = os.getenv('INTERNAL_IPS').split(
- ',') if os.getenv('INTERNAL_IPS') else ['127.0.0.1']
+INTERNAL_IPS = os.getenv('INTERNAL_IPS').split(',') if os.getenv('INTERNAL_IPS') else ['127.0.0.1']
# allow djangos wsgi server to server mediafiles
GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', False)))
@@ -62,18 +59,15 @@ UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPA
FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0))
# minimum interval that users can set for automatic sync of shopping lists
-SHOPPING_MIN_AUTOSYNC_INTERVAL = int(
- os.getenv('SHOPPING_MIN_AUTOSYNC_INTERVAL', 5))
+SHOPPING_MIN_AUTOSYNC_INTERVAL = int(os.getenv('SHOPPING_MIN_AUTOSYNC_INTERVAL', 5))
-ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(
- ',') if os.getenv('ALLOWED_HOSTS') else ['*']
+ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(',') if os.getenv('ALLOWED_HOSTS') else ['*']
if os.getenv('CSRF_TRUSTED_ORIGINS'):
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(',')
if CORS_ORIGIN_ALLOW_ALL := os.getenv('CORS_ORIGIN_ALLOW_ALL') is not None:
- print(
- 'DEPRECATION WARNING: Environment var "CORS_ORIGIN_ALLOW_ALL" is deprecated. Please use "CORS_ALLOW_ALL_ORIGINS."')
+ print('DEPRECATION WARNING: Environment var "CORS_ORIGIN_ALLOW_ALL" is deprecated. Please use "CORS_ALLOW_ALL_ORIGINS."')
CORS_ALLOW_ALL_ORIGINS = CORS_ORIGIN_ALLOW_ALL
else:
CORS_ALLOW_ALL_ORIGINS = bool(int(os.getenv("CORS_ALLOW_ALL_ORIGINS", True)))
@@ -105,40 +99,15 @@ PRIVACY_URL = os.getenv('PRIVACY_URL', '')
IMPRINT_URL = os.getenv('IMPRINT_URL', '')
HOSTED = bool(int(os.getenv('HOSTED', False)))
-MESSAGE_TAGS = {
- messages.ERROR: 'danger'
-}
+MESSAGE_TAGS = {messages.ERROR: 'danger'}
# Application definition
INSTALLED_APPS = [
- 'dal',
- 'dal_select2',
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.sites',
- 'django.contrib.staticfiles',
- 'django.contrib.postgres',
- 'oauth2_provider',
- 'django_prometheus',
- 'django_tables2',
- 'corsheaders',
- 'crispy_forms',
- 'crispy_bootstrap4',
- 'rest_framework',
- 'rest_framework.authtoken',
- 'django_cleanup.apps.CleanupConfig',
- 'webpack_loader',
- 'django_js_reverse',
- 'hcaptcha',
- 'allauth',
- 'allauth.account',
- 'allauth.socialaccount',
- 'cookbook.apps.CookbookConfig',
- 'treebeard',
+ 'dal', 'dal_select2', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages',
+ 'django.contrib.sites', 'django.contrib.staticfiles', 'django.contrib.postgres', 'oauth2_provider', 'django_prometheus', 'django_tables2', 'corsheaders', 'crispy_forms',
+ 'crispy_bootstrap4', 'rest_framework', 'rest_framework.authtoken', 'django_cleanup.apps.CleanupConfig', 'webpack_loader', 'django_js_reverse', 'hcaptcha', 'allauth',
+ 'allauth.account', 'allauth.socialaccount', 'cookbook.apps.CookbookConfig', 'treebeard',
]
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
@@ -160,8 +129,7 @@ try:
INSTALLED_APPS.append(plugin_module)
plugin_config = {
- 'name': plugin_class.verbose_name if hasattr(plugin_class,
- 'verbose_name') else plugin_class.name,
+ 'name': plugin_class.verbose_name if hasattr(plugin_class, 'verbose_name') else plugin_class.name,
'version': plugin_class.VERSION if hasattr(plugin_class, 'VERSION') else 'unknown',
'website': plugin_class.website if hasattr(plugin_class, 'website') else '',
'github': plugin_class.github if hasattr(plugin_class, 'github') else '',
@@ -169,8 +137,7 @@ try:
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
'base_url': plugin_class.base_url,
'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '',
- 'api_router_name': plugin_class.api_router_name if hasattr(plugin_class,
- 'api_router_name') else '',
+ 'api_router_name': plugin_class.api_router_name if hasattr(plugin_class, 'api_router_name') else '',
'nav_main': plugin_class.nav_main if hasattr(plugin_class, 'nav_main') else '',
'nav_dropdown': plugin_class.nav_dropdown if hasattr(plugin_class, 'nav_dropdown') else '',
}
@@ -184,8 +151,7 @@ except Exception:
if DEBUG:
print('ERROR failed to initialize plugins')
-SOCIAL_PROVIDERS = os.getenv('SOCIAL_PROVIDERS').split(
- ',') if os.getenv('SOCIAL_PROVIDERS') else []
+SOCIAL_PROVIDERS = os.getenv('SOCIAL_PROVIDERS').split(',') if os.getenv('SOCIAL_PROVIDERS') else []
SOCIALACCOUNT_EMAIL_VERIFICATION = 'none'
INSTALLED_APPS = INSTALLED_APPS + SOCIAL_PROVIDERS
@@ -196,11 +162,9 @@ ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 90
ACCOUNT_LOGOUT_ON_GET = True
try:
- SOCIALACCOUNT_PROVIDERS = ast.literal_eval(
- os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
+ SOCIALACCOUNT_PROVIDERS = ast.literal_eval(os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
except ValueError:
- SOCIALACCOUNT_PROVIDERS = json.loads(
- os.getenv('SOCIALACCOUNT_PROVIDERS').replace("'", '"') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
+ SOCIALACCOUNT_PROVIDERS = json.loads(os.getenv('SOCIALACCOUNT_PROVIDERS').replace("'", '"') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
SESSION_COOKIE_DOMAIN = os.getenv('SESSION_COOKIE_DOMAIN', None)
SESSION_COOKIE_NAME = os.getenv('SESSION_COOKIE_NAME', 'sessionid')
@@ -213,30 +177,21 @@ ENABLE_PDF_EXPORT = bool(int(os.getenv('ENABLE_PDF_EXPORT', False)))
EXPORT_FILE_CACHE_DURATION = int(os.getenv('EXPORT_FILE_CACHE_DURATION', 600))
MIDDLEWARE = [
- 'corsheaders.middleware.CorsMiddleware',
- 'django.middleware.security.SecurityMiddleware',
- 'whitenoise.middleware.WhiteNoiseMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'cookbook.helper.scope_middleware.ScopeMiddleware',
- 'allauth.account.middleware.AccountMiddleware',
+ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'cookbook.helper.scope_middleware.ScopeMiddleware', 'allauth.account.middleware.AccountMiddleware',
]
if DEBUG_TOOLBAR:
- MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
- INSTALLED_APPS += ('debug_toolbar',)
+ MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware', )
+ INSTALLED_APPS += ('debug_toolbar', )
SORT_TREE_BY_NAME = bool(int(os.getenv('SORT_TREE_BY_NAME', False)))
-DISABLE_TREE_FIX_STARTUP = bool(
- int(os.getenv('DISABLE_TREE_FIX_STARTUP', False)))
+DISABLE_TREE_FIX_STARTUP = bool(int(os.getenv('DISABLE_TREE_FIX_STARTUP', False)))
if bool(int(os.getenv('SQL_DEBUG', False))):
- MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware',)
+ MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware', )
if ENABLE_METRICS:
MIDDLEWARE += 'django_prometheus.middleware.PrometheusAfterMiddleware',
@@ -255,35 +210,34 @@ if LDAP_AUTH:
AUTH_LDAP_START_TLS = bool(int(os.getenv('AUTH_LDAP_START_TLS', False)))
AUTH_LDAP_BIND_DN = os.getenv('AUTH_LDAP_BIND_DN')
AUTH_LDAP_BIND_PASSWORD = os.getenv('AUTH_LDAP_BIND_PASSWORD')
- AUTH_LDAP_USER_SEARCH = LDAPSearch(
- os.getenv('AUTH_LDAP_USER_SEARCH_BASE_DN'),
- ldap.SCOPE_SUBTREE,
- os.getenv('AUTH_LDAP_USER_SEARCH_FILTER_STR', '(uid=%(user)s)'),
- )
- AUTH_LDAP_USER_ATTR_MAP = ast.literal_eval(os.getenv('AUTH_LDAP_USER_ATTR_MAP')) if os.getenv(
- 'AUTH_LDAP_USER_ATTR_MAP') else {
+ AUTH_LDAP_USER_SEARCH = LDAPSearch(os.getenv('AUTH_LDAP_USER_SEARCH_BASE_DN'), ldap.SCOPE_SUBTREE, os.getenv('AUTH_LDAP_USER_SEARCH_FILTER_STR', '(uid=%(user)s)'), )
+ AUTH_LDAP_USER_ATTR_MAP = ast.literal_eval(os.getenv('AUTH_LDAP_USER_ATTR_MAP')) if os.getenv('AUTH_LDAP_USER_ATTR_MAP') else {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}
- AUTH_LDAP_ALWAYS_UPDATE_USER = bool(
- int(os.getenv('AUTH_LDAP_ALWAYS_UPDATE_USER', True)))
+ AUTH_LDAP_ALWAYS_UPDATE_USER = bool(int(os.getenv('AUTH_LDAP_ALWAYS_UPDATE_USER', True)))
AUTH_LDAP_CACHE_TIMEOUT = int(os.getenv('AUTH_LDAP_CACHE_TIMEOUT', 3600))
if 'AUTH_LDAP_TLS_CACERTFILE' in os.environ:
- AUTH_LDAP_GLOBAL_OPTIONS = {
- ldap.OPT_X_TLS_CACERTFILE: os.getenv('AUTH_LDAP_TLS_CACERTFILE')}
+ AUTH_LDAP_GLOBAL_OPTIONS = {ldap.OPT_X_TLS_CACERTFILE: os.getenv('AUTH_LDAP_TLS_CACERTFILE')}
if DEBUG:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
- "handlers": {"console": {"class": "logging.StreamHandler"}},
- "loggers": {"django_auth_ldap": {"level": "DEBUG", "handlers": ["console"]}},
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler"
+ }
+ },
+ "loggers": {
+ "django_auth_ldap": {
+ "level": "DEBUG",
+ "handlers": ["console"]
+ }
+ },
}
-AUTHENTICATION_BACKENDS += [
- 'django.contrib.auth.backends.ModelBackend',
- 'allauth.account.auth_backends.AuthenticationBackend',
-]
+AUTHENTICATION_BACKENDS += ['django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', ]
# django allauth site id
SITE_ID = int(os.getenv('ALLAUTH_SITE_ID', 1))
@@ -292,75 +246,55 @@ ACCOUNT_ADAPTER = 'cookbook.helper.AllAuthCustomAdapter'
if REMOTE_USER_AUTH:
MIDDLEWARE.insert(8, 'recipes.middleware.CustomRemoteUser')
- AUTHENTICATION_BACKENDS.append(
- 'django.contrib.auth.backends.RemoteUserBackend')
+ AUTHENTICATION_BACKENDS.append('django.contrib.auth.backends.RemoteUserBackend')
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
-AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
-]
+AUTH_PASSWORD_VALIDATORS = [{
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+}, {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+}, {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+}, {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+}, ]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
-OAUTH2_PROVIDER = {
- 'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'bookmarklet': 'only access to bookmarklet'}
-}
+OAUTH2_PROVIDER = {'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'bookmarklet': 'only access to bookmarklet'}}
READ_SCOPE = 'read'
WRITE_SCOPE = 'write'
REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': (
- 'rest_framework.authentication.SessionAuthentication',
- 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
- 'rest_framework.authentication.BasicAuthentication',
- ),
- 'DEFAULT_PERMISSION_CLASSES': [
- 'rest_framework.permissions.IsAuthenticated',
- ],
+ 'DEFAULT_AUTHENTICATION_CLASSES':
+ ('rest_framework.authentication.SessionAuthentication', 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework.authentication.BasicAuthentication',
+ ),
+ 'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ],
}
ROOT_URLCONF = 'recipes.urls'
-TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'cookbook', 'templates')],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- 'django.template.context_processors.media',
- 'cookbook.helper.context_processors.context_settings',
- ],
- },
+TEMPLATES = [{
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'cookbook', 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.media', 'cookbook.helper.context_processors.context_settings',
+ ],
},
-]
+}, ]
WSGI_APPLICATION = 'recipes.wsgi.application'
# Database
# Load settings from env files
if os.getenv('DATABASE_URL'):
- match = re.match(
- r'(?P\w+):\/\/(?:(?P[\w\d_-]+)(?::(?P[^@]+))?@)?(?P[^:/]+)(?::(?P\d+))?(?:/(?P[\w\d/._-]+))?',
- os.getenv('DATABASE_URL')
- )
+ match = re.match(r'(?P\w+):\/\/(?:(?P[\w\d_-]+)(?::(?P[^@]+))?@)?(?P[^:/]+)(?::(?P\d+))?(?:/(?P[\w\d/._-]+))?',
+ os.getenv('DATABASE_URL'))
settings = match.groupdict()
schema = settings['schema']
if schema.startswith('postgres'):
@@ -421,12 +355,7 @@ else:
# }
# }
-CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'default',
- }
-}
+CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'default', }}
# Vue webpack settings
VUE_DIR = os.path.join(BASE_DIR, 'vue')
@@ -470,33 +399,16 @@ USE_L10N = True
USE_TZ = True
-LANGUAGES = [
- ('hy', _('Armenian ')),
- ('bg', _('Bulgarian')),
- ('ca', _('Catalan')),
- ('cs', _('Czech')),
- ('da', _('Danish')),
- ('nl', _('Dutch')),
- ('en', _('English')),
- ('fr', _('French')),
- ('de', _('German')),
- ('hu', _('Hungarian')),
- ('it', _('Italian')),
- ('lv', _('Latvian')),
- ('nb', _('Norwegian ')),
- ('pl', _('Polish')),
- ('ru', _('Russian')),
- ('es', _('Spanish')),
- ('sv', _('Swedish')),
-]
+LANGUAGES = [('hy', _('Armenian ')), ('bg', _('Bulgarian')), ('ca', _('Catalan')), ('cs', _('Czech')), ('da', _('Danish')), ('nl', _('Dutch')), ('en', _('English')),
+ ('fr', _('French')), ('de', _('German')), ('hu', _('Hungarian')), ('it', _('Italian')), ('lv', _('Latvian')), ('nb', _('Norwegian ')), ('pl', _('Polish')),
+ ('ru', _('Russian')), ('es', _('Spanish')), ('sv', _('Swedish')), ]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
SCRIPT_NAME = os.getenv('SCRIPT_NAME', '')
# path for django_js_reverse to generate the javascript file containing all urls. Only done because the default command (collectstatic_js_reverse) fails to update the manifest
-JS_REVERSE_OUTPUT_PATH = os.path.join(
- BASE_DIR, "cookbook/static/django_js_reverse")
+JS_REVERSE_OUTPUT_PATH = os.path.join(BASE_DIR, "cookbook/static/django_js_reverse")
JS_REVERSE_SCRIPT_PREFIX = os.getenv('JS_REVERSE_SCRIPT_PREFIX', SCRIPT_NAME)
STATIC_URL = os.getenv('STATIC_URL', '/static/')
@@ -551,22 +463,12 @@ EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = bool(int(os.getenv('EMAIL_USE_TLS', False)))
EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False)))
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
-ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv(
- 'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
+ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv('ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
# ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm'
-ACCOUNT_FORMS = {
- 'signup': 'cookbook.forms.AllAuthSignupForm',
- 'reset_password': 'cookbook.forms.CustomPasswordResetForm'
-}
+ACCOUNT_FORMS = {'signup': 'cookbook.forms.AllAuthSignupForm', 'reset_password': 'cookbook.forms.CustomPasswordResetForm'}
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
-ACCOUNT_RATE_LIMITS = {
- "change_password": "1/m/user",
- "reset_password": "1/m/ip,1/m/key",
- "reset_password_from_key": "1/m/ip",
- "signup": "5/m/ip",
- "login": "5/m/ip",
-}
+ACCOUNT_RATE_LIMITS = {"change_password": "1/m/user", "reset_password": "1/m/ip,1/m/key", "reset_password_from_key": "1/m/ip", "signup": "5/m/ip", "login": "5/m/ip", }
mimetypes.add_type("text/javascript", ".js", True)