diff --git a/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py b/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py new file mode 100644 index 00000000..44837d48 --- /dev/null +++ b/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2024-01-14 23:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='space', + name='app_name', + field=models.CharField(blank=True, max_length=40, null=True), + ), + migrations.AddField( + model_name='userpreference', + name='max_owned_spaces', + field=models.IntegerField(default=100), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index 0614c974..8cb239ae 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -24,7 +24,7 @@ from PIL import Image from treebeard.mp_tree import MP_Node, MP_NodeManager from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, KJ_PREF_DEFAULT, - SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT) + SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT, MAX_OWNED_SPACES_PREF_DEFAULT) def get_user_display_name(self): @@ -288,7 +288,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model): nav_logo = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_nav_logo') nav_bg_color = models.CharField(max_length=8, default='', blank=True, ) nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=BLANK) - + app_name = models.CharField(max_length=40, null=True, blank=True, ) logo_color_32 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_32') logo_color_128 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_128') logo_color_144 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_144') @@ -409,7 +409,7 @@ class UserPreference(models.Model, PermissionModelMixin): nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=DARK) nav_show_logo = models.BooleanField(default=True) nav_sticky = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT) - + max_owned_spaces = models.IntegerField(default=MAX_OWNED_SPACES_PREF_DEFAULT) default_unit = models.CharField(max_length=32, default='g') use_fractions = models.BooleanField(default=FRACTION_PREF_DEFAULT) use_kj = models.BooleanField(default=KJ_PREF_DEFAULT) @@ -434,6 +434,15 @@ class UserPreference(models.Model, PermissionModelMixin): created_at = models.DateTimeField(auto_now_add=True) objects = ScopedManager(space='space') + def save(self, *args, **kwargs): + if not self.pk: + self.max_owned_spaces = MAX_OWNED_SPACES_PREF_DEFAULT + self.comments = COMMENT_PREF_DEFAULT + self.nav_sticky = STICKY_NAV_PREF_DEFAULT + self.use_kj = KJ_PREF_DEFAULT + self.use_fractions = FRACTION_PREF_DEFAULT + + return super().save(*args, **kwargs) def __str__(self): return str(self.user) diff --git a/cookbook/templatetags/theming_tags.py b/cookbook/templatetags/theming_tags.py index a3b40e56..d69ae7b0 100644 --- a/cookbook/templatetags/theming_tags.py +++ b/cookbook/templatetags/theming_tags.py @@ -15,7 +15,7 @@ def theme_values(request): def get_theming_values(request): space = None - if 'space' in request: + if getattr(request,'space',None): space = request.space if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0: with scopes_disabled(): @@ -46,16 +46,17 @@ def get_theming_values(request): 'nav_bg_color': '#ddbf86', 'nav_text_class': 'navbar-light', 'sticky_nav': 'position: sticky; top: 0; left: 0; z-index: 1000;', + 'app_name': 'Tandoor Recipes', } - if request.user.is_authenticated: - if request.user.userpreference.theme in themes: - tv['theme'] = static(themes[ request.user.userpreference.theme]) - if request.user.userpreference.nav_bg_color: - tv['nav_bg_color'] = request.user.userpreference.nav_bg_color - if request.user.userpreference.nav_text_color and request.user.userpreference.nav_text_color in nav_text_type_mapping: - tv['nav_text_class'] = nav_text_type_mapping[ request.user.userpreference.nav_text_color] - if not request.user.userpreference.nav_sticky: + if request.user.is_authenticated: + if request.user.userpreference.theme in themes: + tv['theme'] = static(themes[request.user.userpreference.theme]) + if request.user.userpreference.nav_bg_color: + tv['nav_bg_color'] = request.user.userpreference.nav_bg_color + if request.user.userpreference.nav_text_color and request.user.userpreference.nav_text_color in nav_text_type_mapping: + tv['nav_text_class'] = nav_text_type_mapping[request.user.userpreference.nav_text_color] + if not request.user.userpreference.nav_sticky: tv['sticky_nav'] = '' if space: @@ -73,4 +74,6 @@ def get_theming_values(request): tv['nav_bg_color'] = space.nav_bg_color if space.nav_text_color and space.nav_text_color in nav_text_type_mapping: tv['nav_text_class'] = nav_text_type_mapping[space.nav_text_color] + if space.app_name: + tv['app_name'] = space.app_name return tv diff --git a/cookbook/tests/other/test_theming.py b/cookbook/tests/other/test_theming.py index 68e90e99..7abaccda 100644 --- a/cookbook/tests/other/test_theming.py +++ b/cookbook/tests/other/test_theming.py @@ -1,6 +1,7 @@ from django.contrib import auth from django.templatetags.static import static from django.test import RequestFactory +from django_scopes import scopes_disabled from cookbook.models import Space, UserPreference, UserFile from cookbook.templatetags.theming_tags import theme_values, get_theming_values @@ -20,32 +21,48 @@ def test_theming_function(space_1, u1_s1): assert get_theming_values(request)['nav_text_class'] == 'navbar-light' assert get_theming_values(request)['nav_logo'] == static('assets/brand_logo.png') assert get_theming_values(request)['sticky_nav'] == 'position: sticky; top: 0; left: 0; z-index: 1000;' + assert get_theming_values(request)['app_name'] == 'Tandoor Recipes' - user.userpreference.theme = UserPreference.TANDOOR_DARK - user.userpreference.nav_bg_color = '#ffffff' - user.userpreference.nav_text_color = UserPreference.LIGHT - user.userpreference.nav_sticky = False - user.userpreference.save() + with scopes_disabled(): + up = UserPreference.objects.filter(user=request.user).first() + up.theme = UserPreference.TANDOOR_DARK + up.nav_bg_color = '#ffffff' + up.nav_text_color = UserPreference.LIGHT + up.nav_sticky = False + up.save() + + request = RequestFactory() + request.user = auth.get_user(u1_s1) + request.space = space_1 # user values apply if only those are present assert get_theming_values(request)['theme'] == static('themes/tandoor_dark.min.css') assert get_theming_values(request)['nav_bg_color'] == '#ffffff' assert get_theming_values(request)['nav_text_class'] == 'navbar-dark' assert get_theming_values(request)['sticky_nav'] == '' + assert get_theming_values(request)['app_name'] == 'Tandoor Recipes' space_1.space_theme = Space.BOOTSTRAP space_1.nav_bg_color = '#000000' space_1.nav_text_color = UserPreference.DARK + space_1.app_name = 'test_app_name' space_1.save() + request = RequestFactory() + request.user = auth.get_user(u1_s1) + request.space = space_1 + # space settings apply when set assert get_theming_values(request)['theme'] == static('themes/bootstrap.min.css') assert get_theming_values(request)['nav_bg_color'] == '#000000' assert get_theming_values(request)['nav_text_class'] == 'navbar-light' + assert get_theming_values(request)['app_name'] == 'test_app_name' user.userspace_set.all().delete() + request = RequestFactory() + request.user = auth.get_user(u1_s1) # default user settings should apply when user has no space - assert get_theming_values(request)['nav_bg_color'] == '#ddbf86' - assert get_theming_values(request)['nav_text_class'] == 'navbar-light' + assert get_theming_values(request)['nav_bg_color'] == '#ffffff' + assert get_theming_values(request)['nav_text_class'] == 'navbar-dark' assert get_theming_values(request)['nav_logo'] == static('assets/brand_logo.png') diff --git a/cookbook/views/views.py b/cookbook/views/views.py index cedc5a83..1c12c77d 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -79,6 +79,11 @@ def space_overview(request): messages.add_message(request, messages.WARNING, _('This feature is not available in the demo version!')) else: if create_form.is_valid(): + if Space.objects.filter(created_by=request.user).count() >= request.user.userpreference.max_owned_spaces: + messages.add_message(request, messages.ERROR, + _('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, @@ -491,8 +496,8 @@ def web_manifest(request): ] manifest_info = { - "name": "Tandoor Recipes", - "short_name": "Tandoor", + "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", diff --git a/docs/system/configuration.md b/docs/system/configuration.md index c2351134..f6e64d24 100644 --- a/docs/system/configuration.md +++ b/docs/system/configuration.md @@ -524,6 +524,18 @@ The default value for the user preference 'sticky navigation' (always show navba STICKY_NAV_PREF_DEFAULT=1 ``` +#### Max owned spaces + +> default `100` - options: `0-X` + +The default for the number of spaces a user can own. By setting to 0 space creation for users will be disabled. +Superusers can always bypass this limit. + +``` +MAX_OWNED_SPACES_PREF_DEFAULT=100 +``` + + ### Cosmetic / Preferences #### Timezone diff --git a/recipes/settings.py b/recipes/settings.py index 644bf8a4..4f4328d0 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -57,6 +57,7 @@ COMMENT_PREF_DEFAULT = bool(int(os.getenv('COMMENT_PREF_DEFAULT', True))) FRACTION_PREF_DEFAULT = bool(int(os.getenv('FRACTION_PREF_DEFAULT', False))) KJ_PREF_DEFAULT = bool(int(os.getenv('KJ_PREF_DEFAULT', False))) STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True))) +MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100)) UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0)) # minimum interval that users can set for automatic sync of shopping lists