Merge pull request #2950 from smilerz/deprecate_settings_form

deprecate unused forms
This commit is contained in:
vabene1111 2024-02-16 19:58:36 +01:00 committed by GitHub
commit 778f40eac4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 198 additions and 365 deletions

View File

@ -10,18 +10,17 @@ from django_scopes import scopes_disabled
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
from hcaptcha.fields import hCaptchaField 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 SelectWidget(widgets.Select):
class Media: class Media:
js = ('custom/js/form_select.js', ) js = ('custom/js/form_select.js', )
class MultiSelectWidget(widgets.SelectMultiple): class MultiSelectWidget(widgets.SelectMultiple):
class Media: class Media:
js = ('custom/js/form_multiselect.js', ) js = ('custom/js/form_multiselect.js', )
@ -43,9 +42,7 @@ class UserNameForm(forms.ModelForm):
model = User model = User
fields = ('first_name', 'last_name') fields = ('first_name', 'last_name')
help_texts = { help_texts = {'first_name': _('Both fields are optional. If none are given the username will be displayed instead')}
'first_name': _('Both fields are optional. If none are given the username will be displayed instead')
}
class ExternalRecipeForm(forms.ModelForm): class ExternalRecipeForm(forms.ModelForm):
@ -59,23 +56,14 @@ class ExternalRecipeForm(forms.ModelForm):
class Meta: class Meta:
model = Recipe model = Recipe
fields = ( fields = ('name', 'description', 'servings', 'working_time', 'waiting_time', 'file_path', 'file_uid', 'keywords')
'name', 'description', 'servings', 'working_time', 'waiting_time',
'file_path', 'file_uid', 'keywords'
)
labels = { labels = {
'name': _('Name'), 'name': _('Name'), 'keywords': _('Keywords'), 'working_time': _('Preparation time in minutes'), 'waiting_time': _('Waiting time (cooking/baking) in minutes'),
'keywords': _('Keywords'), 'file_path': _('Path'), 'file_uid': _('Storage UID'),
'working_time': _('Preparation time in minutes'),
'waiting_time': _('Waiting time (cooking/baking) in minutes'),
'file_path': _('Path'),
'file_uid': _('Storage UID'),
} }
widgets = {'keywords': MultiSelectWidget} widgets = {'keywords': MultiSelectWidget}
field_classes = { field_classes = {'keywords': SafeModelMultipleChoiceField, }
'keywords': SafeModelMultipleChoiceField,
}
class ImportExportBase(forms.Form): class ImportExportBase(forms.Form):
@ -102,14 +90,11 @@ class ImportExportBase(forms.Form):
REZEPTSUITEDE = 'REZEPTSUITEDE' REZEPTSUITEDE = 'REZEPTSUITEDE'
PDF = 'PDF' PDF = 'PDF'
type = forms.ChoiceField(choices=( type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'),
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'),
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), (DOMESTICA, 'Domestica'), (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
(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'), (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'),
(COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de') (COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de')))
))
class MultipleFileInput(forms.ClearableFileInput): class MultipleFileInput(forms.ClearableFileInput):
@ -117,6 +102,7 @@ class MultipleFileInput(forms.ClearableFileInput):
class MultipleFileField(forms.FileField): class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput()) kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -132,8 +118,7 @@ class MultipleFileField(forms.FileField):
class ImportForm(ImportExportBase): class ImportForm(ImportExportBase):
files = MultipleFileField(required=True) files = MultipleFileField(required=True)
duplicates = forms.BooleanField(help_text=_( duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'),
'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'),
required=False) required=False)
@ -155,58 +140,42 @@ class CommentForm(forms.ModelForm):
model = Comment model = Comment
fields = ('text', ) fields = ('text', )
labels = { labels = {'text': _('Add your comment: '), }
'text': _('Add your comment: '), widgets = {'text': forms.Textarea(attrs={'rows': 2, 'cols': 15}), }
}
widgets = {
'text': forms.Textarea(attrs={'rows': 2, 'cols': 15}),
}
class StorageForm(forms.ModelForm): class StorageForm(forms.ModelForm):
username = forms.CharField( username = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), required=False)
widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
required=False
)
password = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
required=False, required=False,
help_text=_('Leave empty for dropbox and enter app password for nextcloud.') help_text=_('Leave empty for dropbox and enter app password for nextcloud.'))
) token = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
token = forms.CharField(
widget=forms.TextInput(
attrs={'autocomplete': 'new-password', 'type': 'password'}
),
required=False, required=False,
help_text=_('Leave empty for nextcloud and enter api token for dropbox.') help_text=_('Leave empty for nextcloud and enter api token for dropbox.'))
)
class Meta: class Meta:
model = Storage model = Storage
fields = ('name', 'method', 'username', 'password', 'token', 'url', 'path') fields = ('name', 'method', 'username', 'password', 'token', 'url', 'path')
help_texts = { help_texts = {'url': _('Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'), }
'url': _(
'Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'),
}
# TODO: Deprecate # TODO: Deprecate
class RecipeBookEntryForm(forms.ModelForm): # class RecipeBookEntryForm(forms.ModelForm):
prefix = 'bookmark' # prefix = 'bookmark'
def __init__(self, *args, **kwargs): # def __init__(self, *args, **kwargs):
space = kwargs.pop('space') # space = kwargs.pop('space')
super().__init__(*args, **kwargs) # super().__init__(*args, **kwargs)
self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all() # self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all()
class Meta: # class Meta:
model = RecipeBookEntry # model = RecipeBookEntry
fields = ('book',) # fields = ('book',)
field_classes = { # field_classes = {
'book': SafeModelChoiceField, # 'book': SafeModelChoiceField,
} # }
class SyncForm(forms.ModelForm): class SyncForm(forms.ModelForm):
@ -220,25 +189,15 @@ class SyncForm(forms.ModelForm):
model = Sync model = Sync
fields = ('storage', 'path', 'active') fields = ('storage', 'path', 'active')
field_classes = { field_classes = {'storage': SafeModelChoiceField, }
'storage': SafeModelChoiceField,
}
labels = { labels = {'storage': _('Storage'), 'path': _('Path'), 'active': _('Active')}
'storage': _('Storage'),
'path': _('Path'),
'active': _('Active')
}
# TODO deprecate # TODO deprecate
class BatchEditForm(forms.Form): class BatchEditForm(forms.Form):
search = forms.CharField(label=_('Search String')) search = forms.CharField(label=_('Search String'))
keywords = forms.ModelMultipleChoiceField( keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.none(), required=False, widget=MultiSelectWidget)
queryset=Keyword.objects.none(),
required=False,
widget=MultiSelectWidget
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
space = kwargs.pop('space') space = kwargs.pop('space')
@ -247,6 +206,7 @@ class BatchEditForm(forms.Form):
class ImportRecipeForm(forms.ModelForm): class ImportRecipeForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
space = kwargs.pop('space') space = kwargs.pop('space')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -256,19 +216,13 @@ class ImportRecipeForm(forms.ModelForm):
model = Recipe model = Recipe
fields = ('name', 'keywords', 'file_path', 'file_uid') fields = ('name', 'keywords', 'file_path', 'file_uid')
labels = { labels = {'name': _('Name'), 'keywords': _('Keywords'), 'file_path': _('Path'), 'file_uid': _('File ID'), }
'name': _('Name'),
'keywords': _('Keywords'),
'file_path': _('Path'),
'file_uid': _('File ID'),
}
widgets = {'keywords': MultiSelectWidget} widgets = {'keywords': MultiSelectWidget}
field_classes = { field_classes = {'keywords': SafeModelChoiceField, }
'keywords': SafeModelChoiceField,
}
class InviteLinkForm(forms.ModelForm): class InviteLinkForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop('user') user = kwargs.pop('user')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -276,8 +230,8 @@ class InviteLinkForm(forms.ModelForm):
def clean(self): def clean(self):
space = self.cleaned_data['space'] space = self.cleaned_data['space']
if space.max_users != 0 and (UserPreference.objects.filter(space=space).count() + 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: + 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.')) raise ValidationError(_('Maximum number of users for this space reached.'))
def clean_email(self): def clean_email(self):
@ -291,12 +245,8 @@ class InviteLinkForm(forms.ModelForm):
class Meta: class Meta:
model = InviteLink model = InviteLink
fields = ('email', 'group', 'valid_until', 'space') fields = ('email', 'group', 'valid_until', 'space')
help_texts = { help_texts = {'email': _('An email address is not required but if present the invite link will be sent to the user.'), }
'email': _('An email address is not required but if present the invite link will be sent to the user.'), field_classes = {'space': SafeModelChoiceField, }
}
field_classes = {
'space': SafeModelChoiceField,
}
class SpaceCreateForm(forms.Form): class SpaceCreateForm(forms.Form):
@ -342,133 +292,109 @@ class CustomPasswordResetForm(ResetPasswordForm):
class UserCreateForm(forms.Form): class UserCreateForm(forms.Form):
name = forms.CharField(label='Username') name = forms.CharField(label='Username')
password = forms.CharField( password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
widget=forms.TextInput( password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
attrs={'autocomplete': 'new-password', 'type': 'password'}
)
)
password_confirm = forms.CharField(
widget=forms.TextInput(
attrs={'autocomplete': 'new-password', 'type': 'password'}
)
)
class SearchPreferenceForm(forms.ModelForm): class SearchPreferenceForm(forms.ModelForm):
prefix = 'search' 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'}), widget=NumberInput(attrs={'class': "form-control-range", 'type': 'range'}),
help_text=_( help_text=_('Determines how fuzzy a search is if it uses trigram similarity matching (e.g. low values mean more typos are ignored).'))
'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) preset = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta: class Meta:
model = SearchPreference model = SearchPreference
fields = ( fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold')
'search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold')
help_texts = { help_texts = {
'search': _( 'search': _('Select type method of search. Click <a href="/docs/search/">here</a> for full description of choices.'), 'lookup':
'Select type method of search. Click <a href="/docs/search/">here</a> for full description of choices.'), _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), 'unaccent':
'lookup': _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), 'icontains':
'unaccent': _( _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), 'istartswith':
'Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), 'trigram':
'icontains': _( _("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 search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."),
'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 = { labels = {
'search': _('Search Method'), 'search': _('Search Method'), 'lookup': _('Fuzzy Lookups'), 'unaccent': _('Ignore Accent'), 'icontains': _("Partial Match"), 'istartswith': _("Starts With"),
'lookup': _('Fuzzy Lookups'), 'trigram': _("Fuzzy Search"), 'fulltext': _("Full Text")
'unaccent': _('Ignore Accent'),
'icontains': _("Partial Match"),
'istartswith': _("Starts With"),
'trigram': _("Fuzzy Search"),
'fulltext': _("Full Text")
} }
widgets = { widgets = {
'search': SelectWidget, 'search': SelectWidget, 'unaccent': MultiSelectWidget, 'icontains': MultiSelectWidget, 'istartswith': MultiSelectWidget, 'trigram': MultiSelectWidget, 'fulltext':
'unaccent': MultiSelectWidget, MultiSelectWidget,
'icontains': MultiSelectWidget,
'istartswith': MultiSelectWidget,
'trigram': MultiSelectWidget,
'fulltext': MultiSelectWidget,
} }
class ShoppingPreferenceForm(forms.ModelForm): # class ShoppingPreferenceForm(forms.ModelForm):
prefix = 'shopping' # prefix = 'shopping'
class Meta: # class Meta:
model = UserPreference # model = UserPreference
fields = ( # fields = (
'shopping_share', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'mealplan_autoexclude_onhand', # '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' # 'mealplan_autoinclude_related', 'shopping_add_onhand', 'default_delay', 'filter_to_supermarket', 'shopping_recent_days', 'csv_delim', 'csv_prefix'
) # )
help_texts = { # 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_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': _( # '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 ' # '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.' # '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_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_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.'), # '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.'), # 'default_delay': _('Default number of hours to delay a shopping list entry.'),
'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), # 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'),
'shopping_recent_days': _('Days of recent shopping list entries to display.'), # 'shopping_recent_days': _('Days of recent shopping list entries to display.'),
'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."), # 'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."),
'csv_delim': _('Delimiter to use for CSV exports.'), # 'csv_delim': _('Delimiter to use for CSV exports.'),
'csv_prefix': _('Prefix to add when copying list to the clipboard.'), # 'csv_prefix': _('Prefix to add when copying list to the clipboard.'),
} # }
labels = { # labels = {
'shopping_share': _('Share Shopping List'), # 'shopping_share': _('Share Shopping List'),
'shopping_auto_sync': _('Autosync'), # 'shopping_auto_sync': _('Autosync'),
'mealplan_autoadd_shopping': _('Auto Add Meal Plan'), # 'mealplan_autoadd_shopping': _('Auto Add Meal Plan'),
'mealplan_autoexclude_onhand': _('Exclude On Hand'), # 'mealplan_autoexclude_onhand': _('Exclude On Hand'),
'mealplan_autoinclude_related': _('Include Related'), # 'mealplan_autoinclude_related': _('Include Related'),
'default_delay': _('Default Delay Hours'), # 'default_delay': _('Default Delay Hours'),
'filter_to_supermarket': _('Filter to Supermarket'), # 'filter_to_supermarket': _('Filter to Supermarket'),
'shopping_recent_days': _('Recent Days'), # 'shopping_recent_days': _('Recent Days'),
'csv_delim': _('CSV Delimiter'), # 'csv_delim': _('CSV Delimiter'),
"csv_prefix_label": _("List Prefix"), # "csv_prefix_label": _("List Prefix"),
'shopping_add_onhand': _("Auto On Hand"), # 'shopping_add_onhand': _("Auto On Hand"),
} # }
widgets = { # widgets = {
'shopping_share': MultiSelectWidget # '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): # def __init__(self, *args, **kwargs):
prefix = 'space' # super().__init__(*args, **kwargs) # populates the post
reset_food_inherit = forms.BooleanField(label=_("Reset Food Inheritance"), initial=False, required=False, # self.fields['food_inherit'].queryset = Food.inheritable_fields
help_text=_("Reset all food to inherit the fields configured."))
def __init__(self, *args, **kwargs): # class Meta:
super().__init__(*args, **kwargs) # populates the post # model = Space
self.fields['food_inherit'].queryset = Food.inheritable_fields
class Meta: # fields = ('food_inherit', 'reset_food_inherit',)
model = Space
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 = { # widgets = {
'food_inherit': _('Fields on food that should be inherited by default.'), # 'food_inherit': MultiSelectWidget
'use_plural': _('Use the plural form for units and food inside this space.'), # }
}
widgets = {
'food_inherit': MultiSelectWidget
}

View File

@ -9,8 +9,7 @@ from django.views.generic import UpdateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm
from cookbook.helper.permission_helper import (GroupRequiredMixin, OwnerRequiredMixin, from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, above_space_limit, group_required
above_space_limit, group_required)
from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
@ -102,26 +101,16 @@ def edit_storage(request, pk):
instance.save() instance.save()
messages.add_message( messages.add_message(request, messages.SUCCESS, _('Storage saved!'))
request, messages.SUCCESS, _('Storage saved!')
)
else: else:
messages.add_message( messages.add_message(request, messages.ERROR, _('There was an error updating this storage backend!'))
request,
messages.ERROR,
_('There was an error updating this storage backend!')
)
else: else:
pseudo_instance = instance pseudo_instance = instance
pseudo_instance.password = '__NO__CHANGE__' pseudo_instance.password = '__NO__CHANGE__'
pseudo_instance.token = '__NO__CHANGE__' pseudo_instance.token = '__NO__CHANGE__'
form = StorageForm(instance=pseudo_instance) form = StorageForm(instance=pseudo_instance)
return render( return render(request, 'generic/edit_template.html', {'form': form, 'title': _('Storage')})
request,
'generic/edit_template.html',
{'form': form, 'title': _('Storage')}
)
class CommentUpdate(OwnerRequiredMixin, UpdateView): class CommentUpdate(OwnerRequiredMixin, UpdateView):
@ -135,9 +124,7 @@ class CommentUpdate(OwnerRequiredMixin, UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CommentUpdate, self).get_context_data(**kwargs) context = super(CommentUpdate, self).get_context_data(**kwargs)
context['title'] = _("Comment") context['title'] = _("Comment")
context['view_url'] = reverse( context['view_url'] = reverse('view_recipe', args=[self.object.recipe.pk])
'view_recipe', args=[self.object.recipe.pk]
)
return context return context
@ -176,11 +163,7 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
if self.object.storage.method == Storage.LOCAL: if self.object.storage.method == Storage.LOCAL:
Local.rename_file(old_recipe, self.object.name) Local.rename_file(old_recipe, self.object.name)
self.object.file_path = "%s/%s%s" % ( 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])
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!')) messages.add_message(self.request, messages.SUCCESS, _('Changes saved!'))
return super(ExternalRecipeUpdate, self).form_valid(form) return super(ExternalRecipeUpdate, self).form_valid(form)
@ -197,7 +180,5 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
context['title'] = _("Recipe") context['title'] = _("Recipe")
context['view_url'] = reverse('view_recipe', args=[self.object.pk]) context['view_url'] = reverse('view_recipe', args=[self.object.pk])
if self.object.storage: if self.object.storage:
context['delete_external_url'] = reverse( context['delete_external_url'] = reverse('delete_recipe_source', args=[self.object.pk])
'delete_recipe_source', args=[self.object.pk]
)
return context return context

View File

@ -1,10 +1,10 @@
import json import json
import os import os
import re import re
import subprocess
from datetime import datetime from datetime import datetime
from io import StringIO from io import StringIO
from uuid import UUID from uuid import UUID
import subprocess
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
@ -23,17 +23,14 @@ from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, from cookbook.forms import CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User, UserCreateForm, UserPreference
SpaceJoinForm, User, UserCreateForm, UserPreference)
from cookbook.helper.HelperFunctions import str2bool from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.permission_helper import (group_required, has_group_permission, from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space
share_link_valid, switch_user_active_space) from cookbook.models import Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, Space, UserSpace, ViewLog
from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference,
ShareLink, Space, UserSpace, ViewLog)
from cookbook.tables import CookLogTable, ViewLogTable from cookbook.tables import CookLogTable, ViewLogTable
from cookbook.templatetags.theming_tags import get_theming_values from cookbook.templatetags.theming_tags import get_theming_values
from cookbook.version_info import VERSION_INFO from cookbook.version_info import VERSION_INFO
from recipes.settings import PLUGINS, BASE_DIR from recipes.settings import BASE_DIR, PLUGINS
def index(request): def index(request):
@ -44,11 +41,7 @@ def index(request):
return HttpResponseRedirect(reverse_lazy('view_search')) return HttpResponseRedirect(reverse_lazy('view_search'))
try: try:
page_map = { page_map = {UserPreference.SEARCH: reverse_lazy('view_search'), UserPreference.PLAN: reverse_lazy('view_plan'), UserPreference.BOOKS: reverse_lazy('view_books'), }
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)) return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page))
except UserPreference.DoesNotExist: except UserPreference.DoesNotExist:
@ -84,8 +77,7 @@ 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})') _('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')) return HttpResponseRedirect(reverse('view_space_overview'))
created_space = Space.objects.create( created_space = Space.objects.create(name=create_form.cleaned_data['name'],
name=create_form.cleaned_data['name'],
created_by=request.user, created_by=request.user,
max_file_storage_mb=settings.SPACE_DEFAULT_MAX_FILES, max_file_storage_mb=settings.SPACE_DEFAULT_MAX_FILES,
max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES, max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES,
@ -135,23 +127,18 @@ def recipe_view(request, pk, share=None):
recipe = get_object_or_404(Recipe, pk=pk) recipe = get_object_or_404(Recipe, pk=pk)
if not request.user.is_authenticated and not share_link_valid(recipe, share): if not request.user.is_authenticated and not share_link_valid(recipe, share):
messages.add_message(request, messages.ERROR, messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
_('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path) return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path)
if not (has_group_permission(request.user, if not (has_group_permission(request.user, ('guest', )) and recipe.space == request.space) and not share_link_valid(recipe, share):
('guest',)) and recipe.space == request.space) and not share_link_valid(recipe, messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
share):
messages.add_message(request, messages.ERROR,
_('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
comments = Comment.objects.filter(recipe__space=request.space, recipe=recipe) comments = Comment.objects.filter(recipe__space=request.space, recipe=recipe)
if request.method == "POST": if request.method == "POST":
if not request.user.is_authenticated: if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, messages.add_message(request, messages.ERROR, _('You do not have the required permissions to perform this action!'))
_('You do not have the required permissions to perform this action!'))
return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': recipe.pk, 'share': share})) return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': recipe.pk, 'share': share}))
comment_form = CommentForm(request.POST, prefix='comment') comment_form = CommentForm(request.POST, prefix='comment')
@ -167,16 +154,16 @@ def recipe_view(request, pk, share=None):
comment_form = CommentForm() comment_form = CommentForm()
if request.user.is_authenticated: if request.user.is_authenticated:
if not ViewLog.objects.filter(recipe=recipe, created_by=request.user, if not ViewLog.objects.filter(recipe=recipe, created_by=request.user, created_at__gt=(timezone.now() - timezone.timedelta(minutes=5)), space=request.space).exists():
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) ViewLog.objects.create(recipe=recipe, created_by=request.user, space=request.space)
if request.method == "GET": if request.method == "GET":
servings = request.GET.get("servings") servings = request.GET.get("servings")
return render(request, 'recipe_view.html', return render(request, 'recipe_view.html',
{'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings }) {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings })
@group_required('user') @group_required('user')
def books(request): def books(request):
return render(request, 'books.html', {}) return render(request, 'books.html', {})
@ -239,12 +226,8 @@ def shopping_settings(request):
if search_form.is_valid(): if search_form.is_valid():
if not sp: if not sp:
sp = SearchPreferenceForm(user=request.user) sp = SearchPreferenceForm(user=request.user)
fields_searched = ( 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['icontains']) + len(search_form.cleaned_data['fulltext']))
+ 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': if search_form.cleaned_data['preset'] == 'fuzzy':
sp.search = SearchPreference.SIMPLE sp.search = SearchPreference.SIMPLE
sp.lookup = True sp.lookup = True
@ -269,13 +252,10 @@ def shopping_settings(request):
elif fields_searched == 0: elif fields_searched == 0:
search_form.add_error(None, _('You must select at least one field to search!')) search_form.add_error(None, _('You must select at least one field to search!'))
search_error = True search_error = True
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len( elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['fulltext']) == 0:
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_form.add_error('search',
_('To use this search method you must select at least one full text search field!'))
search_error = True search_error = True
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len( elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['trigram']) > 0:
search_form.cleaned_data['trigram']) > 0:
search_form.add_error(None, _('Fuzzy search is not compatible with this search method!')) search_form.add_error(None, _('Fuzzy search is not compatible with this search method!'))
search_error = True search_error = True
else: else:
@ -291,8 +271,7 @@ def shopping_settings(request):
else: else:
search_error = True search_error = True
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len( fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(sp.fulltext.all())
sp.fulltext.all())
if sp and not search_error and fields_searched > 0: if sp and not search_error and fields_searched > 0:
search_form = SearchPreferenceForm(instance=sp) search_form = SearchPreferenceForm(instance=sp)
elif not search_error: elif not search_error:
@ -305,23 +284,13 @@ def shopping_settings(request):
sp.fulltext.clear() sp.fulltext.clear()
sp.save() sp.save()
return render(request, 'settings.html', { return render(request, 'settings.html', {'search_form': search_form, })
'search_form': search_form,
})
@group_required('guest') @group_required('guest')
def history(request): def history(request):
view_log = ViewLogTable( view_log = ViewLogTable(ViewLog.objects.filter(created_by=request.user, space=request.space).order_by('-created_at').all())
ViewLog.objects.filter( cook_log = CookLogTable(CookLog.objects.filter(created_by=request.user).order_by('-created_at').all())
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}) 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!') database_message = _('Everything is fine!')
elif postgres_ver < postgres_current - 2: elif postgres_ver < postgres_current - 2:
database_status = 'danger' database_status = 'danger'
database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % { database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % {'v': postgres_ver}
'v': postgres_ver}
else: else:
database_status = 'info' database_status = 'info'
database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % { database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % {'v1': postgres_ver, 'v2': postgres_current}
'v1': postgres_ver, 'v2': postgres_current}
else: else:
database_status = 'info' database_status = 'info'
database_message = _( database_message = _(
@ -378,25 +345,15 @@ def system(request):
pass pass
else: else:
current_app = row current_app = row
migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [], migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [], 'total': 0}
'total': 0}
for key in migration_info.keys(): for key in migration_info.keys():
migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len( migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len(migration_info[key]['applied_migrations'])
migration_info[key]['applied_migrations'])
return render(request, 'system.html', { return render(
'gunicorn_media': settings.GUNICORN_MEDIA, request, 'system.html', {
'debug': settings.DEBUG, 'gunicorn_media': settings.GUNICORN_MEDIA, 'debug': settings.DEBUG, 'postgres': postgres, 'postgres_version': postgres_ver, 'postgres_status': database_status,
'postgres': postgres, 'postgres_message': database_message, 'version_info': VERSION_INFO, 'plugins': PLUGINS, 'secret_key': secret_key, 'orphans': orphans, 'migration_info': migration_info,
'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, 'missing_migration': missing_migration,
}) })
@ -404,8 +361,10 @@ def system(request):
def setup(request): def setup(request):
with scopes_disabled(): with scopes_disabled():
if User.objects.count() > 0 or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS: if User.objects.count() > 0 or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS:
messages.add_message(request, messages.ERROR, messages.add_message(
_('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.')) 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')) return HttpResponseRedirect(reverse('account_login'))
if request.method == 'POST': if request.method == 'POST':
@ -445,8 +404,7 @@ def invite_link(request, token):
link.used_by = request.user link.used_by = request.user
link.save() link.save()
user_space = UserSpace.objects.create(user=request.user, space=link.space, user_space = UserSpace.objects.create(user=request.user, space=link.space, internal_note=link.internal_note, invite_link=link, active=False)
internal_note=link.internal_note, invite_link=link, active=False)
if request.user.userspace_set.count() == 1: if request.user.userspace_set.count() == 1:
user_space.active = True user_space.active = True
@ -476,66 +434,36 @@ def space_manage(request, space_id):
def report_share_abuse(request, token): def report_share_abuse(request, token):
if not settings.SHARING_ABUSE: if not settings.SHARING_ABUSE:
messages.add_message(request, messages.WARNING, messages.add_message(request, messages.WARNING, _('Reporting share links is not enabled for this instance. Please notify the page administrator to report problems.'))
_('Reporting share links is not enabled for this instance. Please notify the page administrator to report problems.'))
else: else:
if link := ShareLink.objects.filter(uuid=token).first(): if link := ShareLink.objects.filter(uuid=token).first():
link.abuse_blocked = True link.abuse_blocked = True
link.save() link.save()
messages.add_message(request, messages.WARNING, messages.add_message(request, messages.WARNING, _('Recipe sharing link has been disabled! For additional information please contact the page administrator.'))
_('Recipe sharing link has been disabled! For additional information please contact the page administrator.'))
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
def web_manifest(request): def web_manifest(request):
theme_values = get_theming_values(request) theme_values = get_theming_values(request)
icons = [ 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_svg'], "sizes": "any"}, {"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"}]
{"src": theme_values['logo_color_144'], "type": "image/png", "sizes": "144x144"},
{"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"}
]
manifest_info = { manifest_info = {
"name": theme_values['app_name'], "name":
"short_name": theme_values['app_name'], theme_values['app_name'], "short_name":
"description": _("Manage recipes, shopping list, meal plans and more."), theme_values['app_name'], "description":
"icons": icons, _("Manage recipes, shopping list, meal plans and more."), "icons":
"start_url": "./search", icons, "start_url":
"background_color": theme_values['nav_bg_color'], "./search", "background_color":
"display": "standalone", theme_values['nav_bg_color'], "display":
"scope": ".", "standalone", "scope":
"theme_color": theme_values['nav_bg_color'], ".", "theme_color":
"shortcuts": [ theme_values['nav_bg_color'], "shortcuts":
{ [{"name": _("Plan"), "short_name": _("Plan"), "description": _("View your meal Plan"), "url":
"name": _("Plan"), "./plan"}, {"name": _("Books"), "short_name": _("Books"), "description": _("View your cookbooks"), "url": "./books"},
"short_name": _("Plan"), {"name": _("Shopping"), "short_name": _("Shopping"), "description": _("View your shopping lists"), "url":
"description": _("View your meal Plan"), "./list/shopping-list/"}], "share_target": {"action": "/data/import/url", "method": "GET", "params": {"title": "title", "url": "url", "text": "text"}}
"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}) return JsonResponse(manifest_info, json_dumps_params={'indent': 4})
@ -565,9 +493,7 @@ def test(request):
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
parser = IngredientParser(request, False) parser = IngredientParser(request, False)
data = { data = {'original': '90g golden syrup'}
'original': '90g golden syrup'
}
data['parsed'] = parser.parse(data['original']) data['parsed'] = parser.parse(data['original'])
return render(request, 'test.html', {'data': data}) return render(request, 'test.html', {'data': data})