diff --git a/cookbook/forms.py b/cookbook/forms.py index a1aa4b22..ba716c0a 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -10,20 +10,19 @@ from django_scopes import scopes_disabled from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from hcaptcha.fields import hCaptchaField - - -from .models import (Comment, Food, InviteLink, Keyword, Recipe, RecipeBook, RecipeBookEntry, - SearchPreference, Space, Storage, Sync, User, UserPreference) +from .models import Comment, Food, InviteLink, Keyword, Recipe, RecipeBook, RecipeBookEntry, SearchPreference, Space, Storage, Sync, User, UserPreference class SelectWidget(widgets.Select): + class Media: - js = ('custom/js/form_select.js',) + js = ('custom/js/form_select.js', ) class MultiSelectWidget(widgets.SelectMultiple): + class Media: - js = ('custom/js/form_multiselect.js',) + js = ('custom/js/form_multiselect.js', ) # Yes there are some stupid browsers that still dont support this but @@ -43,9 +42,7 @@ class UserNameForm(forms.ModelForm): model = User fields = ('first_name', 'last_name') - help_texts = { - 'first_name': _('Both fields are optional. If none are given the username will be displayed instead') - } + help_texts = {'first_name': _('Both fields are optional. If none are given the username will be displayed instead')} class ExternalRecipeForm(forms.ModelForm): @@ -59,23 +56,14 @@ class ExternalRecipeForm(forms.ModelForm): class Meta: model = Recipe - fields = ( - 'name', 'description', 'servings', 'working_time', 'waiting_time', - 'file_path', 'file_uid', 'keywords' - ) + fields = ('name', 'description', 'servings', 'working_time', 'waiting_time', 'file_path', 'file_uid', 'keywords') labels = { - 'name': _('Name'), - 'keywords': _('Keywords'), - 'working_time': _('Preparation time in minutes'), - 'waiting_time': _('Waiting time (cooking/baking) in minutes'), - 'file_path': _('Path'), - 'file_uid': _('Storage UID'), + 'name': _('Name'), 'keywords': _('Keywords'), 'working_time': _('Preparation time in minutes'), 'waiting_time': _('Waiting time (cooking/baking) in minutes'), + 'file_path': _('Path'), 'file_uid': _('Storage UID'), } widgets = {'keywords': MultiSelectWidget} - field_classes = { - 'keywords': SafeModelMultipleChoiceField, - } + field_classes = {'keywords': SafeModelMultipleChoiceField, } class ImportExportBase(forms.Form): @@ -102,14 +90,11 @@ class ImportExportBase(forms.Form): REZEPTSUITEDE = 'REZEPTSUITEDE' PDF = 'PDF' - type = forms.ChoiceField(choices=( - (DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), - (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), - (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'), - (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'), - (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'), - (COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de') - )) + type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), + (SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), + (DOMESTICA, 'Domestica'), (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'), + (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'), + (COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de'))) class MultipleFileInput(forms.ClearableFileInput): @@ -117,6 +102,7 @@ class MultipleFileInput(forms.ClearableFileInput): class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): kwargs.setdefault("widget", MultipleFileInput()) super().__init__(*args, **kwargs) @@ -132,9 +118,8 @@ class MultipleFileField(forms.FileField): class ImportForm(ImportExportBase): files = MultipleFileField(required=True) - duplicates = forms.BooleanField(help_text=_( - 'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), - required=False) + duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), + required=False) class ExportForm(ImportExportBase): @@ -153,60 +138,44 @@ class CommentForm(forms.ModelForm): class Meta: model = Comment - fields = ('text',) + fields = ('text', ) - labels = { - 'text': _('Add your comment: '), - } - widgets = { - 'text': forms.Textarea(attrs={'rows': 2, 'cols': 15}), - } + labels = {'text': _('Add your comment: '), } + widgets = {'text': forms.Textarea(attrs={'rows': 2, 'cols': 15}), } class StorageForm(forms.ModelForm): - username = forms.CharField( - widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), - required=False - ) - password = forms.CharField( - widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}), - required=False, - help_text=_('Leave empty for dropbox and enter app password for nextcloud.') - ) - token = forms.CharField( - widget=forms.TextInput( - attrs={'autocomplete': 'new-password', 'type': 'password'} - ), - required=False, - help_text=_('Leave empty for nextcloud and enter api token for dropbox.') - ) + username = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), required=False) + password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}), + required=False, + help_text=_('Leave empty for dropbox and enter app password for nextcloud.')) + token = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}), + required=False, + help_text=_('Leave empty for nextcloud and enter api token for dropbox.')) class Meta: model = Storage fields = ('name', 'method', 'username', 'password', 'token', 'url', 'path') - help_texts = { - 'url': _( - 'Leave empty for dropbox and enter only base url for nextcloud (/remote.php/webdav/ is added automatically)'), - } + help_texts = {'url': _('Leave empty for dropbox and enter only base url for nextcloud (/remote.php/webdav/ is added automatically)'), } # TODO: Deprecate -class RecipeBookEntryForm(forms.ModelForm): - prefix = 'bookmark' +# class RecipeBookEntryForm(forms.ModelForm): +# prefix = 'bookmark' - def __init__(self, *args, **kwargs): - space = kwargs.pop('space') - super().__init__(*args, **kwargs) - self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all() +# def __init__(self, *args, **kwargs): +# space = kwargs.pop('space') +# super().__init__(*args, **kwargs) +# self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all() - class Meta: - model = RecipeBookEntry - fields = ('book',) +# class Meta: +# model = RecipeBookEntry +# fields = ('book',) - field_classes = { - 'book': SafeModelChoiceField, - } +# field_classes = { +# 'book': SafeModelChoiceField, +# } class SyncForm(forms.ModelForm): @@ -220,25 +189,15 @@ class SyncForm(forms.ModelForm): model = Sync fields = ('storage', 'path', 'active') - field_classes = { - 'storage': SafeModelChoiceField, - } + field_classes = {'storage': SafeModelChoiceField, } - labels = { - 'storage': _('Storage'), - 'path': _('Path'), - 'active': _('Active') - } + labels = {'storage': _('Storage'), 'path': _('Path'), 'active': _('Active')} # TODO deprecate class BatchEditForm(forms.Form): search = forms.CharField(label=_('Search String')) - keywords = forms.ModelMultipleChoiceField( - queryset=Keyword.objects.none(), - required=False, - widget=MultiSelectWidget - ) + keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.none(), required=False, widget=MultiSelectWidget) def __init__(self, *args, **kwargs): space = kwargs.pop('space') @@ -247,6 +206,7 @@ class BatchEditForm(forms.Form): class ImportRecipeForm(forms.ModelForm): + def __init__(self, *args, **kwargs): space = kwargs.pop('space') super().__init__(*args, **kwargs) @@ -256,19 +216,13 @@ class ImportRecipeForm(forms.ModelForm): model = Recipe fields = ('name', 'keywords', 'file_path', 'file_uid') - labels = { - 'name': _('Name'), - 'keywords': _('Keywords'), - 'file_path': _('Path'), - 'file_uid': _('File ID'), - } + labels = {'name': _('Name'), 'keywords': _('Keywords'), 'file_path': _('Path'), 'file_uid': _('File ID'), } widgets = {'keywords': MultiSelectWidget} - field_classes = { - 'keywords': SafeModelChoiceField, - } + field_classes = {'keywords': SafeModelChoiceField, } class InviteLinkForm(forms.ModelForm): + def __init__(self, *args, **kwargs): user = kwargs.pop('user') super().__init__(*args, **kwargs) @@ -276,8 +230,8 @@ class InviteLinkForm(forms.ModelForm): def clean(self): space = self.cleaned_data['space'] - if space.max_users != 0 and (UserPreference.objects.filter(space=space).count() + - InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=space).count()) >= space.max_users: + if space.max_users != 0 and (UserPreference.objects.filter(space=space).count() + + InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=space).count()) >= space.max_users: raise ValidationError(_('Maximum number of users for this space reached.')) def clean_email(self): @@ -291,12 +245,8 @@ class InviteLinkForm(forms.ModelForm): class Meta: model = InviteLink fields = ('email', 'group', 'valid_until', 'space') - help_texts = { - 'email': _('An email address is not required but if present the invite link will be sent to the user.'), - } - field_classes = { - 'space': SafeModelChoiceField, - } + help_texts = {'email': _('An email address is not required but if present the invite link will be sent to the user.'), } + field_classes = {'space': SafeModelChoiceField, } class SpaceCreateForm(forms.Form): @@ -342,133 +292,109 @@ class CustomPasswordResetForm(ResetPasswordForm): class UserCreateForm(forms.Form): name = forms.CharField(label='Username') - password = forms.CharField( - widget=forms.TextInput( - attrs={'autocomplete': 'new-password', 'type': 'password'} - ) - ) - password_confirm = forms.CharField( - widget=forms.TextInput( - attrs={'autocomplete': 'new-password', 'type': 'password'} - ) - ) + password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'})) + password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'})) class SearchPreferenceForm(forms.ModelForm): prefix = 'search' - trigram_threshold = forms.DecimalField(min_value=0.01, max_value=1, decimal_places=2, + trigram_threshold = forms.DecimalField(min_value=0.01, + max_value=1, + decimal_places=2, widget=NumberInput(attrs={'class': "form-control-range", 'type': 'range'}), - help_text=_( - 'Determines how fuzzy a search is if it uses trigram similarity matching (e.g. low values mean more typos are ignored).')) + help_text=_('Determines how fuzzy a search is if it uses trigram similarity matching (e.g. low values mean more typos are ignored).')) preset = forms.CharField(widget=forms.HiddenInput(), required=False) class Meta: model = SearchPreference - fields = ( - 'search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold') + fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold') help_texts = { - 'search': _( - 'Select type method of search. Click here for full description of choices.'), - 'lookup': _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), - 'unaccent': _( - 'Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), - 'icontains': _( - "Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), - 'istartswith': _( - "Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), - 'trigram': _( - "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."), - 'fulltext': _( - "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."), + 'search': _('Select type method of search. Click here for full description of choices.'), 'lookup': + _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), 'unaccent': + _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), 'icontains': + _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), 'istartswith': + _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), 'trigram': + _("Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."), 'fulltext': + _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."), } labels = { - 'search': _('Search Method'), - 'lookup': _('Fuzzy Lookups'), - 'unaccent': _('Ignore Accent'), - 'icontains': _("Partial Match"), - 'istartswith': _("Starts With"), - 'trigram': _("Fuzzy Search"), - 'fulltext': _("Full Text") + 'search': _('Search Method'), 'lookup': _('Fuzzy Lookups'), 'unaccent': _('Ignore Accent'), 'icontains': _("Partial Match"), 'istartswith': _("Starts With"), + 'trigram': _("Fuzzy Search"), 'fulltext': _("Full Text") } widgets = { - 'search': SelectWidget, - 'unaccent': MultiSelectWidget, - 'icontains': MultiSelectWidget, - 'istartswith': MultiSelectWidget, - 'trigram': MultiSelectWidget, - 'fulltext': MultiSelectWidget, + 'search': SelectWidget, 'unaccent': MultiSelectWidget, 'icontains': MultiSelectWidget, 'istartswith': MultiSelectWidget, 'trigram': MultiSelectWidget, 'fulltext': + MultiSelectWidget, } -class ShoppingPreferenceForm(forms.ModelForm): - prefix = 'shopping' +# class ShoppingPreferenceForm(forms.ModelForm): +# prefix = 'shopping' - class Meta: - model = UserPreference +# class Meta: +# model = UserPreference - fields = ( - 'shopping_share', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'mealplan_autoexclude_onhand', - 'mealplan_autoinclude_related', 'shopping_add_onhand', 'default_delay', 'filter_to_supermarket', 'shopping_recent_days', 'csv_delim', 'csv_prefix' - ) +# fields = ( +# 'shopping_share', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'mealplan_autoexclude_onhand', +# 'mealplan_autoinclude_related', 'shopping_add_onhand', 'default_delay', 'filter_to_supermarket', 'shopping_recent_days', 'csv_delim', 'csv_prefix' +# ) - help_texts = { - 'shopping_share': _('Users will see all items you add to your shopping list. They must add you to see items on their list.'), - 'shopping_auto_sync': _( - 'Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but might use a little bit ' - 'of mobile data. If lower than instance limit it is reset when saving.' - ), - 'mealplan_autoadd_shopping': _('Automatically add meal plan ingredients to shopping list.'), - 'mealplan_autoinclude_related': _('When adding a meal plan to the shopping list (manually or automatically), include all related recipes.'), - 'mealplan_autoexclude_onhand': _('When adding a meal plan to the shopping list (manually or automatically), exclude ingredients that are on hand.'), - 'default_delay': _('Default number of hours to delay a shopping list entry.'), - 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), - 'shopping_recent_days': _('Days of recent shopping list entries to display.'), - 'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."), - 'csv_delim': _('Delimiter to use for CSV exports.'), - 'csv_prefix': _('Prefix to add when copying list to the clipboard.'), +# help_texts = { +# 'shopping_share': _('Users will see all items you add to your shopping list. They must add you to see items on their list.'), +# 'shopping_auto_sync': _( +# 'Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but might use a little bit ' +# 'of mobile data. If lower than instance limit it is reset when saving.' +# ), +# 'mealplan_autoadd_shopping': _('Automatically add meal plan ingredients to shopping list.'), +# 'mealplan_autoinclude_related': _('When adding a meal plan to the shopping list (manually or automatically), include all related recipes.'), +# 'mealplan_autoexclude_onhand': _('When adding a meal plan to the shopping list (manually or automatically), exclude ingredients that are on hand.'), +# 'default_delay': _('Default number of hours to delay a shopping list entry.'), +# 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), +# 'shopping_recent_days': _('Days of recent shopping list entries to display.'), +# 'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."), +# 'csv_delim': _('Delimiter to use for CSV exports.'), +# 'csv_prefix': _('Prefix to add when copying list to the clipboard.'), - } - labels = { - 'shopping_share': _('Share Shopping List'), - 'shopping_auto_sync': _('Autosync'), - 'mealplan_autoadd_shopping': _('Auto Add Meal Plan'), - 'mealplan_autoexclude_onhand': _('Exclude On Hand'), - 'mealplan_autoinclude_related': _('Include Related'), - 'default_delay': _('Default Delay Hours'), - 'filter_to_supermarket': _('Filter to Supermarket'), - 'shopping_recent_days': _('Recent Days'), - 'csv_delim': _('CSV Delimiter'), - "csv_prefix_label": _("List Prefix"), - 'shopping_add_onhand': _("Auto On Hand"), - } +# } +# labels = { +# 'shopping_share': _('Share Shopping List'), +# 'shopping_auto_sync': _('Autosync'), +# 'mealplan_autoadd_shopping': _('Auto Add Meal Plan'), +# 'mealplan_autoexclude_onhand': _('Exclude On Hand'), +# 'mealplan_autoinclude_related': _('Include Related'), +# 'default_delay': _('Default Delay Hours'), +# 'filter_to_supermarket': _('Filter to Supermarket'), +# 'shopping_recent_days': _('Recent Days'), +# 'csv_delim': _('CSV Delimiter'), +# "csv_prefix_label": _("List Prefix"), +# 'shopping_add_onhand': _("Auto On Hand"), +# } - widgets = { - 'shopping_share': MultiSelectWidget - } +# widgets = { +# 'shopping_share': MultiSelectWidget +# } +# class SpacePreferenceForm(forms.ModelForm): +# prefix = 'space' +# reset_food_inherit = forms.BooleanField(label=_("Reset Food Inheritance"), initial=False, required=False, +# help_text=_("Reset all food to inherit the fields configured.")) -class SpacePreferenceForm(forms.ModelForm): - prefix = 'space' - reset_food_inherit = forms.BooleanField(label=_("Reset Food Inheritance"), initial=False, required=False, - help_text=_("Reset all food to inherit the fields configured.")) +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs) # populates the post +# self.fields['food_inherit'].queryset = Food.inheritable_fields - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) # populates the post - self.fields['food_inherit'].queryset = Food.inheritable_fields +# class Meta: +# model = Space - class Meta: - model = Space +# fields = ('food_inherit', 'reset_food_inherit',) - fields = ('food_inherit', 'reset_food_inherit',) +# help_texts = { +# 'food_inherit': _('Fields on food that should be inherited by default.'), +# 'use_plural': _('Use the plural form for units and food inside this space.'), +# } - help_texts = { - 'food_inherit': _('Fields on food that should be inherited by default.'), - 'use_plural': _('Use the plural form for units and food inside this space.'), - } - - widgets = { - 'food_inherit': MultiSelectWidget - } +# widgets = { +# 'food_inherit': MultiSelectWidget +# } diff --git a/cookbook/views/edit.py b/cookbook/views/edit.py index 1726b2b5..84b6c411 100644 --- a/cookbook/views/edit.py +++ b/cookbook/views/edit.py @@ -9,8 +9,7 @@ from django.views.generic import UpdateView from django.views.generic.edit import FormMixin from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm -from cookbook.helper.permission_helper import (GroupRequiredMixin, OwnerRequiredMixin, - above_space_limit, group_required) +from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, above_space_limit, group_required from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync from cookbook.provider.dropbox import Dropbox from cookbook.provider.local import Local @@ -102,26 +101,16 @@ def edit_storage(request, pk): instance.save() - messages.add_message( - request, messages.SUCCESS, _('Storage saved!') - ) + messages.add_message(request, messages.SUCCESS, _('Storage saved!')) else: - messages.add_message( - request, - messages.ERROR, - _('There was an error updating this storage backend!') - ) + messages.add_message(request, messages.ERROR, _('There was an error updating this storage backend!')) else: pseudo_instance = instance pseudo_instance.password = '__NO__CHANGE__' pseudo_instance.token = '__NO__CHANGE__' form = StorageForm(instance=pseudo_instance) - return render( - request, - 'generic/edit_template.html', - {'form': form, 'title': _('Storage')} - ) + return render(request, 'generic/edit_template.html', {'form': form, 'title': _('Storage')}) class CommentUpdate(OwnerRequiredMixin, UpdateView): @@ -135,9 +124,7 @@ class CommentUpdate(OwnerRequiredMixin, UpdateView): def get_context_data(self, **kwargs): context = super(CommentUpdate, self).get_context_data(**kwargs) context['title'] = _("Comment") - context['view_url'] = reverse( - 'view_recipe', args=[self.object.recipe.pk] - ) + context['view_url'] = reverse('view_recipe', args=[self.object.recipe.pk]) return context @@ -176,11 +163,7 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing): if self.object.storage.method == Storage.LOCAL: Local.rename_file(old_recipe, self.object.name) - self.object.file_path = "%s/%s%s" % ( - os.path.dirname(self.object.file_path), - self.object.name, - os.path.splitext(self.object.file_path)[1] - ) + self.object.file_path = "%s/%s%s" % (os.path.dirname(self.object.file_path), self.object.name, os.path.splitext(self.object.file_path)[1]) messages.add_message(self.request, messages.SUCCESS, _('Changes saved!')) return super(ExternalRecipeUpdate, self).form_valid(form) @@ -197,7 +180,5 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing): context['title'] = _("Recipe") context['view_url'] = reverse('view_recipe', args=[self.object.pk]) if self.object.storage: - context['delete_external_url'] = reverse( - 'delete_recipe_source', args=[self.object.pk] - ) + context['delete_external_url'] = reverse('delete_recipe_source', args=[self.object.pk]) return context diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 6454cf0d..55474494 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -1,10 +1,10 @@ import json import os import re +import subprocess from datetime import datetime from io import StringIO from uuid import UUID -import subprocess from django.apps import apps from django.conf import settings @@ -23,17 +23,14 @@ from django.utils import timezone from django.utils.translation import gettext as _ from django_scopes import scopes_disabled -from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, - SpaceJoinForm, User, UserCreateForm, UserPreference) +from cookbook.forms import CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User, UserCreateForm, UserPreference from cookbook.helper.HelperFunctions import str2bool -from cookbook.helper.permission_helper import (group_required, has_group_permission, - share_link_valid, switch_user_active_space) -from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference, - ShareLink, Space, UserSpace, ViewLog) +from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space +from cookbook.models import Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, Space, UserSpace, ViewLog 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 PLUGINS, BASE_DIR +from recipes.settings import BASE_DIR, PLUGINS def index(request): @@ -44,11 +41,7 @@ def index(request): return HttpResponseRedirect(reverse_lazy('view_search')) try: - page_map = { - UserPreference.SEARCH: reverse_lazy('view_search'), - UserPreference.PLAN: reverse_lazy('view_plan'), - UserPreference.BOOKS: reverse_lazy('view_books'), - } + page_map = {UserPreference.SEARCH: reverse_lazy('view_search'), UserPreference.PLAN: reverse_lazy('view_plan'), UserPreference.BOOKS: reverse_lazy('view_books'), } return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page)) except UserPreference.DoesNotExist: @@ -57,7 +50,7 @@ def index(request): # TODO need to deprecate def search(request): - if has_group_permission(request.user, ('guest',)): + if has_group_permission(request.user, ('guest', )): return render(request, 'search.html', {}) else: if request.user.is_authenticated: @@ -84,14 +77,13 @@ def space_overview(request): _('You have the reached the maximum amount of spaces that can be owned by you.') + f' ({request.user.userpreference.max_owned_spaces})') return HttpResponseRedirect(reverse('view_space_overview')) - created_space = Space.objects.create( - name=create_form.cleaned_data['name'], - created_by=request.user, - max_file_storage_mb=settings.SPACE_DEFAULT_MAX_FILES, - max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES, - max_users=settings.SPACE_DEFAULT_MAX_USERS, - allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING, - ) + created_space = Space.objects.create(name=create_form.cleaned_data['name'], + created_by=request.user, + max_file_storage_mb=settings.SPACE_DEFAULT_MAX_FILES, + max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES, + max_users=settings.SPACE_DEFAULT_MAX_USERS, + allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING, + ) user_space = UserSpace.objects.create(space=created_space, user=request.user, active=False) user_space.groups.add(Group.objects.filter(name='admin').get()) @@ -135,23 +127,18 @@ def recipe_view(request, pk, share=None): recipe = get_object_or_404(Recipe, pk=pk) 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!')) + 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) - 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!')) + 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')) comments = Comment.objects.filter(recipe__space=request.space, recipe=recipe) 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!')) + 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_form = CommentForm(request.POST, prefix='comment') @@ -167,16 +154,16 @@ def recipe_view(request, pk, share=None): comment_form = CommentForm() 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(): + 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 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 }) + @group_required('user') def books(request): return render(request, 'books.html', {}) @@ -239,12 +226,8 @@ def shopping_settings(request): if search_form.is_valid(): if not sp: sp = SearchPreferenceForm(user=request.user) - fields_searched = ( - len(search_form.cleaned_data['icontains']) - + len(search_form.cleaned_data['istartswith']) - + len(search_form.cleaned_data['trigram']) - + len(search_form.cleaned_data['fulltext']) - ) + fields_searched = (len(search_form.cleaned_data['icontains']) + len(search_form.cleaned_data['istartswith']) + len(search_form.cleaned_data['trigram']) + + len(search_form.cleaned_data['fulltext'])) if search_form.cleaned_data['preset'] == 'fuzzy': sp.search = SearchPreference.SIMPLE sp.lookup = True @@ -269,13 +252,10 @@ def shopping_settings(request): elif fields_searched == 0: search_form.add_error(None, _('You must select at least one field to search!')) search_error = True - elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len( - search_form.cleaned_data['fulltext']) == 0: - search_form.add_error('search', - _('To use this search method you must select at least one full text search field!')) + elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['fulltext']) == 0: + search_form.add_error('search', _('To use this search method you must select at least one full text search field!')) search_error = True - elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len( - search_form.cleaned_data['trigram']) > 0: + elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['trigram']) > 0: search_form.add_error(None, _('Fuzzy search is not compatible with this search method!')) search_error = True else: @@ -291,8 +271,7 @@ def shopping_settings(request): else: search_error = True - fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len( - sp.fulltext.all()) + fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(sp.fulltext.all()) if sp and not search_error and fields_searched > 0: search_form = SearchPreferenceForm(instance=sp) elif not search_error: @@ -305,23 +284,13 @@ def shopping_settings(request): sp.fulltext.clear() sp.save() - return render(request, 'settings.html', { - 'search_form': search_form, - }) + return render(request, 'settings.html', {'search_form': search_form, }) @group_required('guest') def history(request): - view_log = ViewLogTable( - ViewLog.objects.filter( - created_by=request.user, space=request.space - ).order_by('-created_at').all() - ) - cook_log = CookLogTable( - CookLog.objects.filter( - created_by=request.user - ).order_by('-created_at').all() - ) + view_log = ViewLogTable(ViewLog.objects.filter(created_by=request.user, space=request.space).order_by('-created_at').all()) + cook_log = CookLogTable(CookLog.objects.filter(created_by=request.user).order_by('-created_at').all()) return render(request, 'history.html', {'view_log': view_log, 'cook_log': cook_log}) @@ -344,12 +313,10 @@ def system(request): database_message = _('Everything is fine!') elif postgres_ver < postgres_current - 2: database_status = 'danger' - database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % { - 'v': postgres_ver} + database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % {'v': postgres_ver} else: database_status = 'info' - database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % { - 'v1': postgres_ver, 'v2': postgres_current} + database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % {'v1': postgres_ver, 'v2': postgres_current} else: database_status = 'info' database_message = _( @@ -378,34 +345,26 @@ def system(request): pass else: current_app = row - migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [], - 'total': 0} + migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [], 'total': 0} for key in migration_info.keys(): - migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len( - migration_info[key]['applied_migrations']) + migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len(migration_info[key]['applied_migrations']) - return render(request, 'system.html', { - 'gunicorn_media': settings.GUNICORN_MEDIA, - 'debug': settings.DEBUG, - 'postgres': postgres, - 'postgres_version': postgres_ver, - 'postgres_status': database_status, - 'postgres_message': database_message, - 'version_info': VERSION_INFO, - 'plugins': PLUGINS, - 'secret_key': secret_key, - 'orphans': orphans, - 'migration_info': migration_info, - 'missing_migration': missing_migration, - }) + return render( + request, 'system.html', { + 'gunicorn_media': settings.GUNICORN_MEDIA, 'debug': settings.DEBUG, 'postgres': postgres, 'postgres_version': postgres_ver, 'postgres_status': database_status, + 'postgres_message': database_message, 'version_info': VERSION_INFO, 'plugins': PLUGINS, 'secret_key': secret_key, 'orphans': orphans, 'migration_info': migration_info, + 'missing_migration': missing_migration, + }) def setup(request): 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.')) + 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': @@ -445,8 +404,7 @@ def invite_link(request, token): link.used_by = request.user link.save() - user_space = UserSpace.objects.create(user=request.user, space=link.space, - internal_note=link.internal_note, invite_link=link, active=False) + user_space = UserSpace.objects.create(user=request.user, space=link.space, internal_note=link.internal_note, invite_link=link, active=False) if request.user.userspace_set.count() == 1: user_space.active = True @@ -476,66 +434,36 @@ def space_manage(request, space_id): def report_share_abuse(request, token): if not settings.SHARING_ABUSE: - messages.add_message(request, messages.WARNING, - _('Reporting share links is not enabled for this instance. Please notify the page administrator to report problems.')) + messages.add_message(request, messages.WARNING, _('Reporting share links is not enabled for this instance. Please notify the page administrator to report problems.')) else: if link := ShareLink.objects.filter(uuid=token).first(): link.abuse_blocked = True link.save() - messages.add_message(request, messages.WARNING, - _('Recipe sharing link has been disabled! For additional information please contact the page administrator.')) + messages.add_message(request, messages.WARNING, _('Recipe sharing link has been disabled! For additional information please contact the page administrator.')) return HttpResponseRedirect(reverse('index')) def web_manifest(request): theme_values = get_theming_values(request) - icons = [ - {"src": theme_values['logo_color_svg'], "sizes": "any"}, - {"src": theme_values['logo_color_144'], "type": "image/png", "sizes": "144x144"}, - {"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"} - ] + icons = [{"src": theme_values['logo_color_svg'], "sizes": "any"}, {"src": theme_values['logo_color_144'], "type": "image/png", "sizes": "144x144"}, + {"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"}] manifest_info = { - "name": theme_values['app_name'], - "short_name": theme_values['app_name'], - "description": _("Manage recipes, shopping list, meal plans and more."), - "icons": icons, - "start_url": "./search", - "background_color": theme_values['nav_bg_color'], - "display": "standalone", - "scope": ".", - "theme_color": theme_values['nav_bg_color'], - "shortcuts": [ - { - "name": _("Plan"), - "short_name": _("Plan"), - "description": _("View your meal Plan"), - "url": "./plan" - }, - { - "name": _("Books"), - "short_name": _("Books"), - "description": _("View your cookbooks"), - "url": "./books" - }, - { - "name": _("Shopping"), - "short_name": _("Shopping"), - "description": _("View your shopping lists"), - "url": "./list/shopping-list/" - } - ], - "share_target": { - "action": "/data/import/url", - "method": "GET", - "params": { - "title": "title", - "url": "url", - "text": "text" - - } - } + "name": + theme_values['app_name'], "short_name": + theme_values['app_name'], "description": + _("Manage recipes, shopping list, meal plans and more."), "icons": + icons, "start_url": + "./search", "background_color": + theme_values['nav_bg_color'], "display": + "standalone", "scope": + ".", "theme_color": + theme_values['nav_bg_color'], "shortcuts": + [{"name": _("Plan"), "short_name": _("Plan"), "description": _("View your meal Plan"), "url": + "./plan"}, {"name": _("Books"), "short_name": _("Books"), "description": _("View your cookbooks"), "url": "./books"}, + {"name": _("Shopping"), "short_name": _("Shopping"), "description": _("View your shopping lists"), "url": + "./list/shopping-list/"}], "share_target": {"action": "/data/import/url", "method": "GET", "params": {"title": "title", "url": "url", "text": "text"}} } return JsonResponse(manifest_info, json_dumps_params={'indent': 4}) @@ -565,9 +493,7 @@ def test(request): from cookbook.helper.ingredient_parser import IngredientParser parser = IngredientParser(request, False) - data = { - 'original': '90g golden syrup' - } + data = {'original': '90g golden syrup'} data['parsed'] = parser.parse(data['original']) return render(request, 'test.html', {'data': data})