Merge branch 'develop' into url_import_recipes

# Conflicts:
#	cookbook/helper/recipe_url_import.py
#	cookbook/tests/api/test_api_keyword.py
#	cookbook/tests/other/test_edits_recipe.py
#	cookbook/views/api.py
#	requirements.txt
This commit is contained in:
vabene1111
2021-03-18 20:38:51 +01:00
137 changed files with 10564 additions and 6777 deletions

View File

@@ -4,6 +4,7 @@ import re
import uuid
import requests
from PIL import Image
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from django.contrib import messages
@@ -13,16 +14,11 @@ from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import Q
from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.formats import date_format
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import gettext as _
from icalendar import Calendar, Event
from PIL import Image
from rest_framework import decorators, permissions, viewsets
from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework.mixins import (ListModelMixin, RetrieveModelMixin,
UpdateModelMixin, CreateModelMixin)
from rest_framework import decorators, viewsets, status
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin
@@ -37,7 +33,7 @@ from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step,
Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, RecipeBookEntry, Supermarket)
ViewLog, RecipeBookEntry, Supermarket, ImportLog)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@@ -52,7 +48,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
StorageSerializer, SyncLogSerializer,
SyncSerializer, UnitSerializer,
UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer)
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer)
from recipes.settings import DEMO
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
@@ -71,12 +67,14 @@ class StandardFilterMixin(ViewSetMixin):
queryset = queryset.filter(updated_at__gte=updated_at)
except FieldError:
pass
except ValidationError:
raise APIException(_('Parameter updated_at incorrectly formatted'))
limit = self.request.query_params.get('limit', None)
random = self.request.query_params.get('random', False)
if limit is not None:
if random:
queryset = queryset.random(int(limit))
queryset = queryset.order_by("?")[:int(limit)]
else:
queryset = queryset[:int(limit)]
return queryset
@@ -89,65 +87,69 @@ class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
- **filter_list**: array of user id's to get names for
"""
queryset = User.objects.all()
queryset = User.objects
serializer_class = UserNameSerializer
permission_classes = [CustomIsGuest]
http_method_names = ['get']
def get_queryset(self):
queryset = self.queryset
queryset = self.queryset.filter(userpreference__space=self.request.space)
try:
filter_list = self.request.query_params.get('filter_list', None)
if filter_list is not None:
queryset = queryset.filter(pk__in=json.loads(filter_list))
except ValueError:
raise APIException(
_('Parameter filter_list incorrectly formatted')
)
raise APIException('Parameter filter_list incorrectly formatted')
return queryset
class UserPreferenceViewSet(viewsets.ModelViewSet):
queryset = UserPreference.objects.all()
queryset = UserPreference.objects
serializer_class = UserPreferenceSerializer
permission_classes = [CustomIsOwner, ]
def perform_create(self, serializer):
if UserPreference.objects.filter(user=self.request.user).exists():
raise APIException(_('Preference for given user already exists'))
serializer.save(user=self.request.user)
def get_queryset(self):
if self.request.user.is_superuser:
return self.queryset
return self.queryset.filter(user=self.request.user)
class StorageViewSet(viewsets.ModelViewSet):
# TODO handle delete protect error and adjust test
queryset = Storage.objects.all()
queryset = Storage.objects
serializer_class = StorageSerializer
permission_classes = [CustomIsAdmin, ]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
class SyncViewSet(viewsets.ModelViewSet):
queryset = Sync.objects.all()
queryset = Sync.objects
serializer_class = SyncSerializer
permission_classes = [CustomIsAdmin, ]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
queryset = SyncLog.objects.all()
queryset = SyncLog.objects
serializer_class = SyncLogSerializer
permission_classes = [CustomIsAdmin, ]
def get_queryset(self):
return self.queryset.filter(sync__space=self.request.space)
class SupermarketViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Supermarket.objects.all()
queryset = Supermarket.objects
serializer_class = SupermarketSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset()
class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
"""
@@ -158,44 +160,52 @@ class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
in the keyword name (case in-sensitive)
- **limit**: limits the amount of returned results
"""
queryset = Keyword.objects.all()
queryset = Keyword.objects
serializer_class = KeywordSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset()
class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Unit.objects.all()
queryset = Unit.objects
serializer_class = UnitSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset()
class FoodViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Food.objects.all()
queryset = Food.objects
serializer_class = FoodSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset()
class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = RecipeBook.objects.all()
queryset = RecipeBook.objects
serializer_class = RecipeBookSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
self.queryset = super(RecipeBookViewSet, self).get_queryset()
if self.request.user.is_superuser:
return self.queryset
return self.queryset.filter(created_by=self.request.user)
self.queryset = self.queryset.filter(created_by=self.request.user).filter(space=self.request.space)
return super().get_queryset()
class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
queryset = RecipeBookEntry.objects.all()
queryset = RecipeBookEntry.objects
serializer_class = RecipeBookEntrySerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
if self.request.user.is_superuser:
return self.queryset
return self.queryset.filter(created_by=self.request.user)
return self.queryset.filter(book__created_by=self.request.user).filter(book__space=self.request.space)
class MealPlanViewSet(viewsets.ModelViewSet):
@@ -207,15 +217,15 @@ class MealPlanViewSet(viewsets.ModelViewSet):
- **to_date**: filter upward to (inclusive) certain date
"""
queryset = MealPlan.objects.all()
queryset = MealPlan.objects
serializer_class = MealPlanSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
queryset = MealPlan.objects.filter(
queryset = self.queryset.filter(
Q(created_by=self.request.user) |
Q(shared=self.request.user)
).distinct().all()
).filter(space=self.request.space).distinct().all()
from_date = self.request.query_params.get('from_date', None)
if from_date is not None:
@@ -232,26 +242,32 @@ class MealTypeViewSet(viewsets.ModelViewSet):
returns list of meal types created by the
requesting user ordered by the order field.
"""
queryset = MealType.objects.order_by('order').all()
queryset = MealType.objects
serializer_class = MealTypeSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
queryset = MealType.objects.order_by('order', 'id').filter(created_by=self.request.user).all()
queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(space=self.request.space).all()
return queryset
class IngredientViewSet(viewsets.ModelViewSet):
queryset = Ingredient.objects.all()
queryset = Ingredient.objects
serializer_class = IngredientSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
return self.queryset.filter(step__recipe__space=self.request.space)
class StepViewSet(viewsets.ModelViewSet):
queryset = Step.objects.all()
queryset = Step.objects
serializer_class = StepSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
return self.queryset.filter(recipe__space=self.request.space)
class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
"""
@@ -262,12 +278,13 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
in the recipe name (case in-sensitive)
- **limit**: limits the amount of returned results
"""
queryset = Recipe.objects.all()
queryset = Recipe.objects
serializer_class = RecipeSerializer
# TODO split read and write permission for meal plan guest
permission_classes = [CustomIsShare | CustomIsGuest]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
internal = self.request.query_params.get('internal', None)
if internal:
@@ -290,6 +307,10 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
)
def image(self, request, pk):
obj = self.get_object()
if obj.get_space() != request.space:
raise PermissionDenied(detail='You do not have the required permission to perform this action', code=403)
serializer = self.serializer_class(
obj, data=request.data, partial=True
)
@@ -316,34 +337,30 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
queryset = ShoppingListRecipe.objects.all()
queryset = ShoppingListRecipe.objects
serializer_class = ShoppingListRecipeSerializer
permission_classes = [CustomIsOwner, ]
permission_classes = [CustomIsOwner | CustomIsShared]
def get_queryset(self):
return self.queryset.filter(shoppinglist__created_by=self.request.user).all()
return self.queryset.filter(Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(shoppinglist__space=self.request.space).all()
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
queryset = ShoppingListEntry.objects.all()
queryset = ShoppingListEntry.objects
serializer_class = ShoppingListEntrySerializer
permission_classes = [CustomIsOwner, ]
permission_classes = [CustomIsOwner | CustomIsShared]
def get_queryset(self):
return self.queryset.filter(shoppinglist__created_by=self.request.user).all()
return self.queryset.filter(Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(shoppinglist__space=self.request.space).all()
class ShoppingListViewSet(viewsets.ModelViewSet):
queryset = ShoppingList.objects.all()
queryset = ShoppingList.objects
serializer_class = ShoppingListSerializer
permission_classes = [CustomIsOwner | CustomIsShared]
def get_queryset(self):
if self.request.user.is_superuser:
return self.queryset
return self.queryset.filter(
Q(created_by=self.request.user) | Q(shared=self.request.user)
).all()
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct()
def get_serializer_class(self):
autosync = self.request.query_params.get('autosync', None)
@@ -353,22 +370,38 @@ class ShoppingListViewSet(viewsets.ModelViewSet):
class ViewLogViewSet(viewsets.ModelViewSet):
queryset = ViewLog.objects.all()
queryset = ViewLog.objects
serializer_class = ViewLogSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
return CookLog.objects.filter(created_by=self.request.user).all()[:5]
self.queryset = self.queryset.filter(created_by=self.request.user).filter(space=self.request.space).all()
if self.request.method == 'GET':
return self.queryset[:5]
else:
return self.queryset
class CookLogViewSet(viewsets.ModelViewSet):
queryset = CookLog.objects.all()
queryset = CookLog.objects
serializer_class = CookLogSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
queryset = CookLog.objects.filter(created_by=self.request.user).all()[:5]
return queryset
self.queryset = self.queryset.filter(created_by=self.request.user).filter(space=self.request.space).all()
if self.request.method == 'GET':
return self.queryset[:5]
else:
return self.queryset
class ImportLogViewSet(viewsets.ModelViewSet):
queryset = ImportLog.objects
serializer_class = ImportLogSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
return self.queryset.filter(space=self.request.space).all()
# -------------- non django rest api views --------------------
@@ -394,7 +427,7 @@ def update_recipe_links(recipe):
@group_required('user')
def get_external_file_link(request, recipe_id):
recipe = Recipe.objects.get(id=recipe_id)
recipe = get_object_or_404(Recipe, pk=recipe_id, space=request.space)
if not recipe.link:
update_recipe_links(recipe)
@@ -403,7 +436,7 @@ def get_external_file_link(request, recipe_id):
@group_required('guest')
def get_recipe_file(request, recipe_id):
recipe = Recipe.objects.get(id=recipe_id)
recipe = get_object_or_404(Recipe, pk=recipe_id, space=request.space)
if recipe.storage:
return FileResponse(get_recipe_provider(recipe).get_file(recipe))
else:
@@ -418,7 +451,7 @@ def sync_all(request):
)
return redirect('index')
monitors = Sync.objects.filter(active=True)
monitors = Sync.objects.filter(active=True).filter(space=request.user.userpreference.space)
error = False
for monitor in monitors:
@@ -470,7 +503,7 @@ def log_cooking(request, recipe_id):
def get_plan_ical(request, from_date, to_date):
queryset = MealPlan.objects.filter(
Q(created_by=request.user) | Q(shared=request.user)
).distinct().all()
).filter(space=request.user.userpreference.space).distinct().all()
if from_date is not None:
queryset = queryset.filter(date__gte=from_date)
@@ -548,24 +581,25 @@ def recipe_from_url_old(request):
},
status=400
)
return get_from_html(response.text, url)
return get_from_html(response.text, url, request.space)
@group_required('admin')
def get_backup(request):
if not request.user.is_superuser:
return HttpResponse('', status=403)
@group_required('user')
def recipe_from_json(request):
mjson = request.POST['json']
buf = io.StringIO()
management.call_command(
'dumpdata', exclude=['contenttypes', 'auth'], stdout=buf
md_json = json.loads(mjson)
if ('@type' in md_json
and md_json['@type'] == 'Recipe'):
return JsonResponse(find_recipe_json(md_json, '', request.space))
return JsonResponse(
{
'error': True,
'msg': _('Could not parse correctly...')
},
status=400
)
response = FileResponse(buf.getvalue())
response["Content-Disposition"] = f'attachment; filename=backup{date_format(timezone.now(), format="SHORT_DATETIME_FORMAT", use_l10n=True)}.json' # noqa: E501
return response
@group_required('user')
def ingredient_from_string(request):

View File

@@ -27,32 +27,24 @@ from cookbook.tables import SyncTable
def sync(request):
if request.method == "POST":
if not has_group_permission(request.user, ['admin']):
messages.add_message(
request, messages.ERROR,
_('You do not have the required permissions to view this page!') # noqa: E501
)
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('data_sync'))
form = SyncForm(request.POST)
form = SyncForm(request.POST, space=request.space)
if form.is_valid():
new_path = Sync()
new_path.path = form.cleaned_data['path']
new_path.storage = form.cleaned_data['storage']
new_path.last_checked = datetime.now()
new_path.space = request.space
new_path.save()
return redirect('data_sync')
else:
form = SyncForm()
form = SyncForm(space=request.space)
monitored_paths = SyncTable(Sync.objects.all())
RequestConfig(
request, paginate={'per_page': 25}
).configure(monitored_paths)
monitored_paths = SyncTable(Sync.objects.filter(space=request.space).all())
RequestConfig(request, paginate={'per_page': 25}).configure(monitored_paths)
return render(
request,
'batch/monitor.html',
{'form': form, 'monitored_paths': monitored_paths}
)
return render(request, 'batch/monitor.html', {'form': form, 'monitored_paths': monitored_paths})
@group_required('user')
@@ -62,14 +54,15 @@ def sync_wait(request):
@group_required('user')
def batch_import(request):
imports = RecipeImport.objects.all()
imports = RecipeImport.objects.filter(space=request.space).all()
for new_recipe in imports:
recipe = Recipe(
name=new_recipe.name,
file_path=new_recipe.file_path,
storage=new_recipe.storage,
file_uid=new_recipe.file_uid,
created_by=request.user
created_by=request.user,
space=request.space
)
recipe.save()
new_recipe.delete()
@@ -80,12 +73,12 @@ def batch_import(request):
@group_required('user')
def batch_edit(request):
if request.method == "POST":
form = BatchEditForm(request.POST)
form = BatchEditForm(request.POST, space=request.space)
if form.is_valid():
word = form.cleaned_data['search']
keywords = form.cleaned_data['keywords']
recipes = Recipe.objects.filter(name__icontains=word)
recipes = Recipe.objects.filter(name__icontains=word, space=request.space)
count = 0
for recipe in recipes:
edit = False
@@ -107,7 +100,7 @@ def batch_edit(request):
return redirect('data_batch_edit')
else:
form = BatchEditForm()
form = BatchEditForm(space=request.space)
return render(request, 'batch/edit.html', {'form': form})
@@ -125,6 +118,7 @@ def import_url(request):
servings=data['servings'],
internal=True,
created_by=request.user,
space=request.space,
)
step = Step.objects.create(
@@ -134,10 +128,10 @@ def import_url(request):
recipe.steps.add(step)
for kw in data['keywords']:
if k := Keyword.objects.filter(name=kw['text']).first():
if kw['id'] != "null" and (k := Keyword.objects.filter(id=kw['id'], space=request.space).first()):
recipe.keywords.add(k)
elif data['all_keywords']:
k = Keyword.objects.create(name=kw['text'])
k = Keyword.objects.create(name=kw['text'], space=request.space)
recipe.keywords.add(k)
for ing in data['recipeIngredient']:
@@ -145,12 +139,12 @@ def import_url(request):
if ing['ingredient']['text'] != '':
ingredient.food, f_created = Food.objects.get_or_create(
name=ing['ingredient']['text']
name=ing['ingredient']['text'], space=request.space
)
if ing['unit'] and ing['unit']['text'] != '':
ingredient.unit, u_created = Unit.objects.get_or_create(
name=ing['unit']['text']
name=ing['unit']['text'], space=request.space
)
# TODO properly handle no_amount recipes
@@ -201,16 +195,16 @@ class Object(object):
@group_required('user')
def statistics(request):
counts = Object()
counts.recipes = Recipe.objects.count()
counts.keywords = Keyword.objects.count()
counts.recipe_import = RecipeImport.objects.count()
counts.units = Unit.objects.count()
counts.ingredients = Food.objects.count()
counts.comments = Comment.objects.count()
counts.recipes = Recipe.objects.filter(space=request.space).count()
counts.keywords = Keyword.objects.filter(space=request.space).count()
counts.recipe_import = RecipeImport.objects.filter(space=request.space).count()
counts.units = Unit.objects.filter(space=request.space).count()
counts.ingredients = Food.objects.filter(space=request.space).count()
counts.comments = Comment.objects.filter(recipe__space=request.space).count()
counts.recipes_internal = Recipe.objects.filter(internal=True).count()
counts.recipes_internal = Recipe.objects.filter(internal=True, space=request.space).count()
counts.recipes_external = counts.recipes - counts.recipes_internal
counts.recipes_no_keyword = Recipe.objects.filter(keywords=None).count()
counts.recipes_no_keyword = Recipe.objects.filter(keywords=None, space=request.space).count()
return render(request, 'stats.html', {'counts': counts})

View File

@@ -31,7 +31,7 @@ class RecipeDelete(GroupRequiredMixin, DeleteView):
@group_required('user')
def delete_recipe_source(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
if recipe.storage.method == Storage.DROPBOX:
# TODO central location to handle storage type switches
@@ -130,25 +130,12 @@ class RecipeBookDelete(OwnerRequiredMixin, DeleteView):
return context
class RecipeBookEntryDelete(GroupRequiredMixin, DeleteView):
class RecipeBookEntryDelete(OwnerRequiredMixin, DeleteView):
groups_required = ['user']
template_name = "generic/delete_template.html"
model = RecipeBookEntry
success_url = reverse_lazy('view_books')
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if not (obj.book.created_by == request.user
or request.user.is_superuser):
messages.add_message(
request,
messages.ERROR,
_('You cannot interact with this object as it is not owned by you!') # noqa: E501
)
return HttpResponseRedirect(reverse('index'))
return super(RecipeBookEntryDelete, self) \
.dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(RecipeBookEntryDelete, self).get_context_data(**kwargs)
context['title'] = _("Bookmarks")

View File

@@ -3,9 +3,11 @@ import os
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from django.views.generic.edit import FormMixin
from django_scopes import scopes_disabled
from cookbook.forms import (CommentForm, ExternalRecipeForm, FoodForm,
FoodMergeForm, KeywordForm, MealPlanForm,
@@ -24,7 +26,7 @@ from cookbook.provider.nextcloud import Nextcloud
@group_required('guest')
def switch_recipe(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
if recipe.internal:
return HttpResponseRedirect(reverse('edit_internal_recipe', args=[pk]))
else:
@@ -33,7 +35,7 @@ def switch_recipe(request, pk):
@group_required('user')
def convert_recipe(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
if not recipe.internal:
recipe.internal = True
recipe.save()
@@ -43,14 +45,22 @@ def convert_recipe(request, pk):
@group_required('user')
def internal_recipe_update(request, pk):
recipe_instance = get_object_or_404(Recipe, pk=pk)
recipe_instance = get_object_or_404(Recipe, pk=pk, space=request.space)
return render(
request, 'forms/edit_internal_recipe.html', {'recipe': recipe_instance}
)
class SyncUpdate(GroupRequiredMixin, UpdateView):
class SpaceFormMixing(FormMixin):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'space': self.request.space})
return kwargs
class SyncUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
groups_required = ['admin']
template_name = "generic/edit_template.html"
model = Sync
@@ -62,7 +72,7 @@ class SyncUpdate(GroupRequiredMixin, UpdateView):
return reverse('edit_sync', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(SyncUpdate, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _("Sync")
return context
@@ -79,12 +89,12 @@ class KeywordUpdate(GroupRequiredMixin, UpdateView):
return reverse('edit_keyword', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(KeywordUpdate, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _("Keyword")
return context
class FoodUpdate(GroupRequiredMixin, UpdateView):
class FoodUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
groups_required = ['user']
template_name = "generic/edit_template.html"
model = Food
@@ -103,12 +113,10 @@ class FoodUpdate(GroupRequiredMixin, UpdateView):
@group_required('admin')
def edit_storage(request, pk):
instance = get_object_or_404(Storage, pk=pk)
instance = get_object_or_404(Storage, pk=pk, space=request.space)
if not (instance.created_by == request.user or request.user.is_superuser):
messages.add_message(
request, messages.ERROR, _('You cannot edit this storage!')
)
messages.add_message(request, messages.ERROR, _('You cannot edit this storage!'))
return HttpResponseRedirect(reverse('list_storage'))
if request.method == "POST":
@@ -183,7 +191,7 @@ class ImportUpdate(GroupRequiredMixin, UpdateView):
return context
class RecipeBookUpdate(OwnerRequiredMixin, UpdateView):
class RecipeBookUpdate(OwnerRequiredMixin, UpdateView, SpaceFormMixing):
template_name = "generic/edit_template.html"
model = RecipeBook
form_class = RecipeBookForm
@@ -197,7 +205,7 @@ class RecipeBookUpdate(OwnerRequiredMixin, UpdateView):
return context
class MealPlanUpdate(OwnerRequiredMixin, UpdateView):
class MealPlanUpdate(OwnerRequiredMixin, UpdateView, SpaceFormMixing):
template_name = "generic/edit_template.html"
model = MealPlan
form_class = MealPlanForm
@@ -217,7 +225,7 @@ class MealPlanUpdate(OwnerRequiredMixin, UpdateView):
return context
class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView):
class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
groups_required = ['user']
model = Recipe
form_class = ExternalRecipeForm
@@ -225,10 +233,10 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView):
def form_valid(self, form):
self.object = form.save(commit=False)
old_recipe = Recipe.objects.get(pk=self.object.pk)
old_recipe = Recipe.objects.get(pk=self.object.pk, space=self.request.space)
if not old_recipe.name == self.object.name:
# TODO central location to handle storage type switches
if self.object.storage.method == Storage.DROPBOX:
# TODO central location to handle storage type switches
Dropbox.rename_file(old_recipe, self.object.name)
if self.object.storage.method == Storage.NEXTCLOUD:
Nextcloud.rename_file(old_recipe, self.object.name)
@@ -241,24 +249,18 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView):
os.path.splitext(self.object.file_path)[1]
)
messages.add_message(
self.request, messages.SUCCESS, _('Changes saved!')
)
messages.add_message(self.request, messages.SUCCESS, _('Changes saved!'))
return super(ExternalRecipeUpdate, self).form_valid(form)
def form_invalid(self, form):
messages.add_message(
self.request,
messages.ERROR,
_('Error saving changes!')
)
messages.add_message(self.request, messages.ERROR, _('Error saving changes!'))
return super(ExternalRecipeUpdate, self).form_valid(form)
def get_success_url(self):
return reverse('edit_recipe', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(ExternalRecipeUpdate, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _("Recipe")
context['view_url'] = reverse('view_recipe', args=[self.object.pk])
if self.object.storage:
@@ -272,60 +274,43 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView):
def edit_ingredients(request):
if request.method == "POST":
success = False
units_form = UnitMergeForm(request.POST, prefix=UnitMergeForm.prefix)
units_form = UnitMergeForm(request.POST, prefix=UnitMergeForm.prefix, space=request.space)
if units_form.is_valid():
new_unit = units_form.cleaned_data['new_unit']
old_unit = units_form.cleaned_data['old_unit']
if new_unit != old_unit:
recipe_ingredients = Ingredient.objects \
.filter(unit=old_unit).all()
recipe_ingredients = Ingredient.objects.filter(unit=old_unit, step__recipe__space=request.space).all()
for i in recipe_ingredients:
i.unit = new_unit
i.save()
old_unit.delete()
success = True
messages.add_message(
request, messages.SUCCESS, _('Units merged!')
)
messages.add_message(request, messages.SUCCESS, _('Units merged!'))
else:
messages.add_message(
request,
messages.ERROR,
_('Cannot merge with the same object!')
)
messages.add_message(request, messages.ERROR, _('Cannot merge with the same object!'))
food_form = FoodMergeForm(request.POST, prefix=FoodMergeForm.prefix)
food_form = FoodMergeForm(request.POST, prefix=FoodMergeForm.prefix, space=request.space)
if food_form.is_valid():
new_food = food_form.cleaned_data['new_food']
old_food = food_form.cleaned_data['old_food']
if new_food != old_food:
ingredients = Ingredient.objects.filter(food=old_food).all()
ingredients = Ingredient.objects.filter(food=old_food, step__recipe__space=request.space).all()
for i in ingredients:
i.food = new_food
i.save()
old_food.delete()
success = True
messages.add_message(
request, messages.SUCCESS, _('Foods merged!')
)
messages.add_message(request, messages.SUCCESS, _('Foods merged!'))
else:
messages.add_message(
request,
messages.ERROR,
_('Cannot merge with the same object!')
)
messages.add_message(request, messages.ERROR, _('Cannot merge with the same object!'))
if success:
units_form = UnitMergeForm()
food_form = FoodMergeForm()
units_form = UnitMergeForm(space=request.space)
food_form = FoodMergeForm(space=request.space)
else:
units_form = UnitMergeForm()
food_form = FoodMergeForm()
units_form = UnitMergeForm(space=request.space)
food_form = FoodMergeForm(space=request.space)
return render(
request,
'forms/ingredients.html',
{'units_form': units_form, 'food_form': food_form}
)
return render(request, 'forms/ingredients.html', {'units_form': units_form, 'food_form': food_form})

View File

@@ -1,7 +1,11 @@
import re
import threading
from io import BytesIO
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext as _
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
@@ -12,22 +16,22 @@ from cookbook.integration.mealie import Mealie
from cookbook.integration.nextcloud_cookbook import NextcloudCookbook
from cookbook.integration.paprika import Paprika
from cookbook.integration.safron import Safron
from cookbook.models import Recipe
from cookbook.models import Recipe, ImportLog
def get_integration(request, export_type):
if export_type == ImportExportBase.DEFAULT:
return Default(request)
return Default(request, export_type)
if export_type == ImportExportBase.PAPRIKA:
return Paprika(request)
return Paprika(request, export_type)
if export_type == ImportExportBase.NEXTCLOUD:
return NextcloudCookbook(request)
return NextcloudCookbook(request, export_type)
if export_type == ImportExportBase.MEALIE:
return Mealie(request)
return Mealie(request, export_type)
if export_type == ImportExportBase.CHOWDOWN:
return Chowdown(request)
return Chowdown(request, export_type)
if export_type == ImportExportBase.SAFRON:
return Safron(request)
return Safron(request, export_type)
@group_required('user')
@@ -37,7 +41,16 @@ def import_recipe(request):
if form.is_valid():
try:
integration = get_integration(request, form.cleaned_data['type'])
return integration.do_import(request.FILES.getlist('files'))
il = ImportLog.objects.create(type=form.cleaned_data['type'], created_by=request.user, space=request.space)
files = []
for f in request.FILES.getlist('files'):
files.append({'file': BytesIO(f.read()), 'name': f.name})
t = threading.Thread(target=integration.do_import, args=[files, il])
t.setDaemon(True)
t.start()
return HttpResponseRedirect(reverse('view_import_response', args=[il.pk]))
except NotImplementedError:
messages.add_message(request, messages.ERROR, _('Importing is not implemented for this provider'))
else:
@@ -49,7 +62,7 @@ def import_recipe(request):
@group_required('user')
def export_recipe(request):
if request.method == "POST":
form = ExportForm(request.POST)
form = ExportForm(request.POST, space=request.space)
if form.is_valid():
try:
integration = get_integration(request, form.cleaned_data['type'])
@@ -58,11 +71,16 @@ def export_recipe(request):
messages.add_message(request, messages.ERROR, _('Exporting is not implemented for this provider'))
else:
form = ExportForm()
form = ExportForm(space=request.space)
recipe = request.GET.get('r')
if recipe:
if re.match(r'^([0-9])+$', recipe):
if recipe := Recipe.objects.filter(pk=int(recipe)).first():
form = ExportForm(initial={'recipes': recipe})
if recipe := Recipe.objects.filter(pk=int(recipe), space=request.space).first():
form = ExportForm(initial={'recipes': recipe}, space=request.space)
return render(request, 'export.html', {'form': form})
@group_required('user')
def import_response(request, pk):
return render(request, 'import_response.html', {'pk': pk})

View File

@@ -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, ShoppingListFilter
from cookbook.filters import FoodFilter, ShoppingListFilter
from cookbook.helper.permission_helper import group_required
from cookbook.models import (Food, InviteLink, Keyword, RecipeImport,
ShoppingList, Storage, SyncLog)
@@ -17,7 +17,7 @@ from cookbook.tables import (ImportLogTable, IngredientTable, InviteLinkTable,
@group_required('user')
def keyword(request):
table = KeywordTable(Keyword.objects.all())
table = KeywordTable(Keyword.objects.filter(space=request.space).all())
RequestConfig(request, paginate={'per_page': 25}).configure(table)
return render(
@@ -30,7 +30,7 @@ def keyword(request):
@group_required('admin')
def sync_log(request):
table = ImportLogTable(
SyncLog.objects.all().order_by('-created_at')
SyncLog.objects.filter(space=request.space).all().order_by('-created_at')
)
RequestConfig(request, paginate={'per_page': 25}).configure(table)
@@ -43,7 +43,7 @@ def sync_log(request):
@group_required('user')
def recipe_import(request):
table = RecipeImportTable(RecipeImport.objects.all())
table = RecipeImportTable(RecipeImport.objects.filter(space=request.space).all())
RequestConfig(request, paginate={'per_page': 25}).configure(table)
@@ -56,10 +56,7 @@ def recipe_import(request):
@group_required('user')
def food(request):
f = IngredientFilter(
request.GET,
queryset=Food.objects.all().order_by('pk')
)
f = FoodFilter(request.GET, queryset=Food.objects.filter(space=request.space).all().order_by('pk'))
table = IngredientTable(f.qs)
RequestConfig(request, paginate={'per_page': 25}).configure(table)
@@ -73,12 +70,10 @@ def food(request):
@group_required('user')
def shopping_list(request):
f = ShoppingListFilter(
request.GET,
queryset=ShoppingList.objects.filter(
Q(created_by=request.user) |
Q(shared=request.user)
).all().order_by('finished', 'created_at'))
f = ShoppingListFilter(request.GET, queryset=ShoppingList.objects.filter(
Q(created_by=request.user) |
Q(shared=request.user), space=request.space
).all().order_by('finished', 'created_at'))
table = ShoppingListTable(f.qs)
RequestConfig(request, paginate={'per_page': 25}).configure(table)
@@ -97,7 +92,7 @@ def shopping_list(request):
@group_required('admin')
def storage(request):
table = StorageTable(Storage.objects.all())
table = StorageTable(Storage.objects.filter(space=request.space).all())
RequestConfig(request, paginate={'per_page': 25}).configure(table)
return render(
@@ -113,18 +108,11 @@ def storage(request):
@group_required('admin')
def invite_link(request):
table = InviteLinkTable(
InviteLink.objects.filter(
valid_until__gte=datetime.today(), used_by=None
).all())
table = InviteLinkTable(InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=request.space).all())
RequestConfig(request, paginate={'per_page': 25}).configure(table)
return render(
request,
'generic/list_template.html',
{
'title': _("Invite Links"),
'table': table,
'create_url': 'new_invite_link'
}
)
return render(request, 'generic/list_template.html', {
'title': _("Invite Links"),
'table': table,
'create_url': 'new_invite_link'
})

View File

@@ -14,6 +14,7 @@ from cookbook.helper.permission_helper import (GroupRequiredMixin,
group_required)
from cookbook.models import (InviteLink, Keyword, MealPlan, MealType, Recipe,
RecipeBook, RecipeImport, ShareLink, Step)
from cookbook.views.edit import SpaceFormMixing
class RecipeCreate(GroupRequiredMixin, CreateView):
@@ -25,12 +26,11 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.internal = True
obj.save()
obj.steps.add(Step.objects.create())
return HttpResponseRedirect(
reverse('edit_recipe', kwargs={'pk': obj.pk})
)
return HttpResponseRedirect(reverse('edit_recipe', kwargs={'pk': obj.pk}))
def get_success_url(self):
return reverse('edit_recipe', kwargs={'pk': self.object.pk})
@@ -43,11 +43,9 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
@group_required('user')
def share_link(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
link = ShareLink.objects.create(recipe=recipe, created_by=request.user)
return HttpResponseRedirect(
reverse('view_recipe', kwargs={'pk': pk, 'share': link.uuid})
)
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 HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': pk, 'share': link.uuid}))
class KeywordCreate(GroupRequiredMixin, CreateView):
@@ -57,6 +55,12 @@ class KeywordCreate(GroupRequiredMixin, CreateView):
form_class = KeywordForm
success_url = reverse_lazy('list_keyword')
def form_valid(self, form):
obj = form.save(commit=False)
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(reverse('edit_keyword', kwargs={'pk': obj.pk}))
def get_context_data(self, **kwargs):
context = super(KeywordCreate, self).get_context_data(**kwargs)
context['title'] = _("Keyword")
@@ -73,10 +77,9 @@ class StorageCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(
reverse('edit_storage', kwargs={'pk': obj.pk})
)
return HttpResponseRedirect(reverse('edit_storage', kwargs={'pk': obj.pk}))
def get_context_data(self, **kwargs):
context = super(StorageCreate, self).get_context_data(**kwargs)
@@ -87,10 +90,11 @@ class StorageCreate(GroupRequiredMixin, CreateView):
@group_required('user')
def create_new_external_recipe(request, import_id):
if request.method == "POST":
form = ImportRecipeForm(request.POST)
form = ImportRecipeForm(request.POST, space=request.space)
if form.is_valid():
new_recipe = RecipeImport.objects.get(id=import_id)
new_recipe = get_object_or_404(RecipeImport, pk=import_id, space=request.space)
recipe = Recipe()
recipe.space = request.space
recipe.storage = new_recipe.storage
recipe.name = form.cleaned_data['name']
recipe.file_path = form.cleaned_data['file_path']
@@ -99,34 +103,29 @@ def create_new_external_recipe(request, import_id):
recipe.save()
recipe.keywords.set(form.cleaned_data['keywords'])
if form.cleaned_data['keywords']:
recipe.keywords.set(form.cleaned_data['keywords'])
RecipeImport.objects.get(id=import_id).delete()
new_recipe.delete()
messages.add_message(
request, messages.SUCCESS, _('Imported new recipe!')
)
messages.add_message(request, messages.SUCCESS, _('Imported new recipe!'))
return redirect('list_recipe_import')
else:
messages.add_message(
request,
messages.ERROR,
_('There was an error importing this recipe!')
)
messages.add_message(request, messages.ERROR, _('There was an error importing this recipe!'))
else:
new_recipe = RecipeImport.objects.get(id=import_id)
new_recipe = get_object_or_404(RecipeImport, pk=import_id, space=request.space)
form = ImportRecipeForm(
initial={
'file_path': new_recipe.file_path,
'name': new_recipe.name,
'file_uid': new_recipe.file_uid
}
}, space=request.space
)
return render(request, 'forms/edit_import_recipe.html', {'form': form})
class RecipeBookCreate(GroupRequiredMixin, CreateView):
class RecipeBookCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
groups_required = ['user']
template_name = "generic/new_template.html"
model = RecipeBook
@@ -136,6 +135,7 @@ class RecipeBookCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(reverse('view_books'))
@@ -145,7 +145,7 @@ class RecipeBookCreate(GroupRequiredMixin, CreateView):
return context
class MealPlanCreate(GroupRequiredMixin, CreateView):
class MealPlanCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
groups_required = ['user']
template_name = "generic/new_template.html"
model = MealPlan
@@ -154,9 +154,7 @@ class MealPlanCreate(GroupRequiredMixin, CreateView):
def get_form(self, form_class=None):
form = self.form_class(**self.get_form_kwargs())
form.fields['meal_type'].queryset = MealType.objects.filter(
created_by=self.request.user
).all()
form.fields['meal_type'].queryset = MealType.objects.filter(created_by=self.request.user, space=self.request.space).all()
return form
def get_initial(self):
@@ -181,6 +179,7 @@ class MealPlanCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(reverse('view_plan'))
@@ -191,8 +190,8 @@ class MealPlanCreate(GroupRequiredMixin, CreateView):
recipe = self.request.GET.get('recipe')
if recipe:
if re.match(r'^([0-9])+$', recipe):
if Recipe.objects.filter(pk=int(recipe)).exists():
context['default_recipe'] = Recipe.objects.get(pk=int(recipe)) # noqa: E501
if Recipe.objects.filter(pk=int(recipe), space=self.request.space).exists():
context['default_recipe'] = Recipe.objects.get(pk=int(recipe), space=self.request.space)
return context
@@ -206,6 +205,7 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(reverse('list_invite_link'))
@@ -213,3 +213,8 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
context = super(InviteLinkCreate, self).get_context_data(**kwargs)
context['title'] = _("Invite Link")
return context
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

View File

@@ -7,6 +7,7 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db import IntegrityError
@@ -16,6 +17,7 @@ from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled, scope
from django_tables2 import RequestConfig
from rest_framework.authtoken.models import Token
@@ -25,7 +27,7 @@ from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
UserPreferenceForm)
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList)
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space)
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
ViewLogTable)
from recipes.settings import DEMO
@@ -33,10 +35,12 @@ from recipes.version import BUILD_REF, VERSION_NUMBER
def index(request):
if not request.user.is_authenticated:
if User.objects.count() < 1 and 'django.contrib.auth.backends.RemoteUserBackend' not in settings.AUTHENTICATION_BACKENDS:
return HttpResponseRedirect(reverse_lazy('view_setup'))
return HttpResponseRedirect(reverse_lazy('view_search'))
with scopes_disabled():
if not request.user.is_authenticated:
if User.objects.count() < 1 and 'django.contrib.auth.backends.RemoteUserBackend' not in settings.AUTHENTICATION_BACKENDS:
return HttpResponseRedirect(reverse_lazy('view_setup'))
return HttpResponseRedirect(reverse_lazy('view_search'))
try:
page_map = {
UserPreference.SEARCH: reverse_lazy('view_search'),
@@ -46,15 +50,12 @@ def index(request):
return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page))
except UserPreference.DoesNotExist:
return HttpResponseRedirect(reverse('view_no_group') + '?next=' + request.path)
return HttpResponseRedirect(reverse('view_search'))
def search(request):
if has_group_permission(request.user, ('guest',)):
f = RecipeFilter(
request.GET,
queryset=Recipe.objects.all().order_by('name')
)
f = RecipeFilter(request.GET, queryset=Recipe.objects.filter(space=request.user.userpreference.space).all().order_by('name'), space=request.space)
if request.user.userpreference.search_style == UserPreference.LARGE:
table = RecipeTable(f.qs)
@@ -63,10 +64,7 @@ def search(request):
RequestConfig(request, paginate={'per_page': 25}).configure(table)
if request.GET == {} and request.user.userpreference.show_recent:
qs = Recipe.objects \
.filter(viewlog__created_by=request.user) \
.order_by('-viewlog__created_at') \
.all()
qs = Recipe.objects.filter(viewlog__created_by=request.user).filter(space=request.user.userpreference.space).order_by('-viewlog__created_at').all()
recent_list = []
for r in qs:
@@ -79,119 +77,83 @@ def search(request):
else:
last_viewed = None
return render(
request,
'index.html',
{'recipes': table, 'filter': f, 'last_viewed': last_viewed}
)
return render(request, 'index.html', {'recipes': table, 'filter': f, 'last_viewed': last_viewed})
else:
return HttpResponseRedirect(reverse('view_no_group') + '?next=' + request.path)
if request.user.is_authenticated:
return HttpResponseRedirect(reverse('view_no_group'))
else:
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path)
def no_groups(request):
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.GET['next'])
if request.user.is_authenticated and request.user.groups.count() > 0:
return HttpResponseRedirect(reverse('index'))
return render(request, 'no_groups_info.html')
def no_space(request):
return render(request, 'no_space_info.html')
def no_perm(request):
return render(request, 'no_perm_info.html')
def recipe_view(request, pk, share=None):
recipe = get_object_or_404(Recipe, pk=pk)
with scopes_disabled():
recipe = get_object_or_404(Recipe, pk=pk)
if not has_group_permission(request.user, ('guest',)) and not share_link_valid(recipe, share):
messages.add_message(
request,
messages.ERROR,
_('You do not have the required permissions to view this page!')
)
return HttpResponseRedirect(reverse('view_no_group') + '?next=' + request.path)
if not request.user.is_authenticated and not share_link_valid(recipe, share):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path)
comments = Comment.objects.filter(recipe=recipe)
if not (has_group_permission(request.user, ('guest',)) and recipe.space == request.space) and not share_link_valid(recipe, share):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('index'))
if request.method == "POST":
if not request.user.is_authenticated:
messages.add_message(
request,
messages.ERROR,
_('You do not have the required permissions to perform this action!') # noqa: E501
)
return HttpResponseRedirect(
reverse(
'view_recipe',
kwargs={'pk': recipe.pk, 'share': share}
)
)
comments = Comment.objects.filter(recipe__space=request.space, recipe=recipe)
comment_form = CommentForm(request.POST, prefix='comment')
if comment_form.is_valid():
comment = Comment()
comment.recipe = recipe
comment.text = comment_form.cleaned_data['text']
comment.created_by = request.user
if request.method == "POST":
if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to perform this action!'))
return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': recipe.pk, 'share': share}))
comment.save()
comment_form = CommentForm(request.POST, prefix='comment')
if comment_form.is_valid():
comment = Comment()
comment.recipe = recipe
comment.text = comment_form.cleaned_data['text']
comment.created_by = request.user
comment.save()
messages.add_message(
request, messages.SUCCESS, _('Comment saved!')
)
messages.add_message(request, messages.SUCCESS, _('Comment saved!'))
bookmark_form = RecipeBookEntryForm(request.POST, prefix='bookmark')
if bookmark_form.is_valid():
bookmark = RecipeBookEntry()
bookmark.recipe = recipe
bookmark.book = bookmark_form.cleaned_data['book']
comment_form = CommentForm()
try:
bookmark.save()
except IntegrityError as e:
if 'UNIQUE constraint' in str(e.args):
messages.add_message(
request,
messages.ERROR,
_('This recipe is already linked to the book!')
)
else:
messages.add_message(
request,
messages.SUCCESS,
_('Bookmark saved!')
)
user_servings = None
if request.user.is_authenticated:
user_servings = CookLog.objects.filter(
recipe=recipe,
created_by=request.user,
servings__gt=0,
space=request.space,
).all().aggregate(Avg('servings'))['servings__avg']
comment_form = CommentForm()
if not user_servings:
user_servings = 0
user_servings = None
if request.user.is_authenticated:
user_servings = CookLog.objects.filter(
recipe=recipe,
created_by=request.user,
servings__gt=0
).all().aggregate(Avg('servings'))['servings__avg']
if request.user.is_authenticated:
if not ViewLog.objects.filter(recipe=recipe, created_by=request.user, created_at__gt=(timezone.now() - timezone.timedelta(minutes=5)), space=request.space).exists():
ViewLog.objects.create(recipe=recipe, created_by=request.user, space=request.space)
if not user_servings:
user_servings = 0
if request.user.is_authenticated:
if not ViewLog.objects \
.filter(recipe=recipe) \
.filter(created_by=request.user) \
.filter(created_at__gt=(
timezone.now() - timezone.timedelta(minutes=5))) \
.exists():
ViewLog.objects.create(recipe=recipe, created_by=request.user)
return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'user_servings': user_servings})
return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'user_servings': user_servings})
@group_required('user')
def books(request):
book_list = []
books = RecipeBook.objects.filter(
Q(created_by=request.user) | Q(shared=request.user)
).distinct().all()
recipe_books = RecipeBook.objects.filter(Q(created_by=request.user) | Q(shared=request.user), space=request.space).distinct().all()
for b in books:
for b in recipe_books:
book_list.append(
{
'book': b,
@@ -209,32 +171,24 @@ def meal_plan(request):
@group_required('user')
def meal_plan_entry(request, pk):
plan = MealPlan.objects.get(pk=pk)
plan = MealPlan.objects.filter(space=request.space).get(pk=pk)
if plan.created_by != request.user and plan.shared != request.user:
messages.add_message(
request,
messages.ERROR,
_('You do not have the required permissions to view this page!')
)
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
same_day_plan = MealPlan.objects \
.filter(date=plan.date) \
.filter(date=plan.date, space=request.space) \
.exclude(pk=plan.pk) \
.filter(Q(created_by=request.user) | Q(shared=request.user)) \
.order_by('meal_type').all()
return render(
request,
'meal_plan_entry.html',
{'plan': plan, 'same_day_plan': same_day_plan}
)
return render(request, 'meal_plan_entry.html', {'plan': plan, 'same_day_plan': same_day_plan})
@group_required('user')
def latest_shopping_list(request):
sl = ShoppingList.objects.filter(Q(created_by=request.user) | Q(shared=request.user)).filter(finished=False).order_by('-created_at').first()
sl = ShoppingList.objects.filter(Q(created_by=request.user) | Q(shared=request.user)).filter(finished=False, space=request.space).order_by('-created_at').first()
if sl:
return HttpResponseRedirect(reverse('view_shopping', kwargs={'pk': sl.pk}) + '?edit=true')
@@ -251,7 +205,7 @@ def shopping_list(request, pk=None):
r = r.replace('[', '').replace(']', '')
if re.match(r'^([0-9])+,([0-9])+[.]*([0-9])*$', r):
rid, multiplier = r.split(',')
if recipe := Recipe.objects.filter(pk=int(rid)).first():
if recipe := Recipe.objects.filter(pk=int(rid), space=request.space).first():
recipes.append({'recipe': recipe.id, 'multiplier': multiplier})
edit = True if 'edit' in request.GET and request.GET['edit'] == 'true' else False
@@ -317,23 +271,19 @@ def user_settings(request):
if (api_token := Token.objects.filter(user=request.user).first()) is None:
api_token = Token.objects.create(user=request.user)
return render(
request,
'settings.html',
{
'preference_form': preference_form,
'user_name_form': user_name_form,
'password_form': password_form,
'api_token': api_token
}
)
return render(request, 'settings.html', {
'preference_form': preference_form,
'user_name_form': user_name_form,
'password_form': password_form,
'api_token': api_token
})
@group_required('guest')
def history(request):
view_log = ViewLogTable(
ViewLog.objects.filter(
created_by=request.user
created_by=request.user, space=request.space
).order_by('-created_at').all()
)
cook_log = CookLogTable(
@@ -341,11 +291,7 @@ def history(request):
created_by=request.user
).order_by('-created_at').all()
)
return render(
request,
'history.html',
{'view_log': view_log, 'cook_log': cook_log}
)
return render(request, 'history.html', {'view_log': view_log, 'cook_log': cook_log})
@group_required('admin')
@@ -357,101 +303,43 @@ def system(request):
secret_key = False if os.getenv('SECRET_KEY') else True
return render(
request,
'system.html',
{
'gunicorn_media': settings.GUNICORN_MEDIA,
'debug': settings.DEBUG,
'postgres': postgres,
'version': VERSION_NUMBER,
'ref': BUILD_REF,
'secret_key': secret_key
}
)
return render(request, 'system.html', {
'gunicorn_media': settings.GUNICORN_MEDIA,
'debug': settings.DEBUG,
'postgres': postgres,
'version': VERSION_NUMBER,
'ref': BUILD_REF,
'secret_key': secret_key
})
def setup(request):
if (User.objects.count() > 0
or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS): # noqa: E501
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.') # noqa: E501
)
return HttpResponseRedirect(reverse('account_login'))
with scopes_disabled():
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.'))
return HttpResponseRedirect(reverse('account_login'))
if request.method == 'POST':
form = UserCreateForm(request.POST)
if form.is_valid():
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
form.add_error('password', _('Passwords dont match!'))
else:
user = User(
username=form.cleaned_data['name'],
is_superuser=True,
is_staff=True
)
try:
validate_password(form.cleaned_data['password'], user=user)
user.set_password(form.cleaned_data['password'])
user.save()
messages.add_message(
request,
messages.SUCCESS,
_('User has been created, please login!')
)
return HttpResponseRedirect(reverse('account_login'))
except ValidationError as e:
for m in e:
form.add_error('password', m)
else:
form = UserCreateForm()
return render(request, 'setup.html', {'form': form})
def signup(request, token):
try:
token = UUID(token, version=4)
except ValueError:
messages.add_message(
request, messages.ERROR, _('Malformed Invite Link supplied!')
)
return HttpResponseRedirect(reverse('index'))
if link := InviteLink.objects.filter(
valid_until__gte=datetime.today(), used_by=None, uuid=token) \
.first():
if request.method == 'POST':
updated_request = request.POST.copy()
if link.username != '':
updated_request.update({'name': link.username})
form = UserCreateForm(updated_request)
form = UserCreateForm(request.POST)
if form.is_valid():
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']:
form.add_error('password', _('Passwords dont match!'))
else:
user = User(
username=form.cleaned_data['name'],
)
user = User(username=form.cleaned_data['name'], is_superuser=True, is_staff=True)
try:
validate_password(
form.cleaned_data['password'], user=user
)
validate_password(form.cleaned_data['password'], user=user)
user.set_password(form.cleaned_data['password'])
user.save()
messages.add_message(
request,
messages.SUCCESS,
_('User has been created, please login!')
)
link.used_by = user
link.save()
user.groups.add(link.group)
user.groups.add(Group.objects.get(name='admin'))
user.userpreference.space = Space.objects.first()
user.userpreference.save()
for x in Space.objects.all():
x.created_by = user
x.save()
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
return HttpResponseRedirect(reverse('account_login'))
except ValidationError as e:
for m in e:
@@ -459,17 +347,56 @@ def signup(request, token):
else:
form = UserCreateForm()
if link.username != '':
form.fields['name'].initial = link.username
form.fields['name'].disabled = True
return render(
request, 'account/signup.html', {'form': form, 'link': link}
)
return render(request, 'setup.html', {'form': form})
messages.add_message(
request, messages.ERROR, _('Invite Link not valid or already used!')
)
return HttpResponseRedirect(reverse('index'))
def signup(request, token):
with scopes_disabled():
try:
token = UUID(token, version=4)
except ValueError:
messages.add_message(request, messages.ERROR, _('Malformed Invite Link supplied!'))
return HttpResponseRedirect(reverse('index'))
if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first():
if request.method == 'POST':
updated_request = request.POST.copy()
if link.username != '':
updated_request.update({'name': link.username})
form = UserCreateForm(updated_request)
if form.is_valid():
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
form.add_error('password', _('Passwords dont match!'))
else:
user = User(username=form.cleaned_data['name'], )
try:
validate_password(form.cleaned_data['password'], user=user)
user.set_password(form.cleaned_data['password'])
user.save()
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
link.used_by = user
link.save()
user.groups.add(link.group)
user.userpreference.space = link.space
user.userpreference.save()
return HttpResponseRedirect(reverse('account_login'))
except ValidationError as e:
for m in e:
form.add_error('password', m)
else:
form = UserCreateForm()
if link.username != '':
form.fields['name'].initial = link.username
form.fields['name'].disabled = True
return render(request, 'account/signup.html', {'form': form, 'link': link})
messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!'))
return HttpResponseRedirect(reverse('index'))
def markdown_info(request):