diff --git a/cookbook/forms.py b/cookbook/forms.py index 239b1701..50f88228 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -37,12 +37,9 @@ class UserPreferenceForm(forms.ModelForm): prefix = 'preference' def __init__(self, *args, **kwargs): - if x := kwargs.get('instance', None): - space = x.space - else: - space = kwargs.pop('space') + space = kwargs.pop('space') super().__init__(*args, **kwargs) - self.fields['plan_share'].queryset = User.objects.filter(userpreference__space=space).all() + self.fields['plan_share'].queryset = User.objects.filter(userspace__space=space).all() class Meta: model = UserPreference diff --git a/cookbook/helper/permission_helper.py b/cookbook/helper/permission_helper.py index 13de26bd..d889a747 100644 --- a/cookbook/helper/permission_helper.py +++ b/cookbook/helper/permission_helper.py @@ -166,7 +166,7 @@ class OwnerRequiredMixin(object): try: obj = self.get_object() - if obj.get_space() != request.space: + if not request.user.userspace.filter(space=obj.get_space()).exists(): messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!')) return HttpResponseRedirect(reverse_lazy('index')) diff --git a/cookbook/helper/scope_middleware.py b/cookbook/helper/scope_middleware.py index df63d5df..34490e6c 100644 --- a/cookbook/helper/scope_middleware.py +++ b/cookbook/helper/scope_middleware.py @@ -26,15 +26,18 @@ class ScopeMiddleware: if request.path.startswith(prefix + '/accounts/'): return self.get_response(request) + if request.path.startswith(prefix + '/switch-space/'): + return self.get_response(request) + with scopes_disabled(): if request.user.userspace_set.count() == 0 and not reverse('account_logout') in request.path: - return views.no_space(request) + return views.space_overview(request) # get active user space, if for some reason more than one space is active select first (group permission checks will fail, this is not intended at this point) user_space = request.user.userspace_set.filter(active=True).first() if not user_space: - pass # TODO show space selection page (maybe include in no space page) + return views.space_overview(request) if user_space.groups.count() == 0 and not reverse('account_logout') in request.path: return views.no_groups(request) diff --git a/cookbook/models.py b/cookbook/models.py index 5b8251dd..746414cb 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -343,7 +343,7 @@ class UserPreference(models.Model, PermissionModelMixin): return str(self.user) -class UserSpace(models.Model): +class UserSpace(models.Model, PermissionModelMixin): user = models.ForeignKey(User, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE) groups = models.ManyToManyField(Group) diff --git a/cookbook/tables.py b/cookbook/tables.py index 2831c39d..be05fb44 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -1,3 +1,4 @@ + import django_tables2 as tables from django.utils.html import format_html from django.utils.translation import gettext as _ diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 67a6f8c4..820d8dbe 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -304,19 +304,19 @@ {% trans 'Admin' %} {% endif %} - {% if request.user.is_authenticated and request.user.userspace_set.all|length > 1 %} + {% if request.user.is_authenticated %} {% for us in request.user.userspace_set.all %} {% if us.active %} - + {% else %} {% endif %} {{ us.space.name }} {% endfor %} - + {% trans 'Create New' %} {% endif %} {% blocktrans %}Are you sure you want to delete the {{ title }}: {{ object }} {% endblocktrans %} + {% trans 'This cannot be undone!' %} {{ form|crispy }} diff --git a/cookbook/templates/no_space_info.html b/cookbook/templates/no_space_info.html deleted file mode 100644 index 980ce68c..00000000 --- a/cookbook/templates/no_space_info.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends "base.html" %} -{% load crispy_forms_filters %} -{% load static %} -{% load i18n %} - -{% block title %}{% trans "No Space" %}{% endblock %} - - -{% block content %} - -
- -

{% trans 'No Space' %}

- -
-
- {% trans 'Recipes, foods, shopping lists and more are organized in spaces of one or more people.' %} - {% trans 'You can either be invited into an existing space or create your own one.' %} -
-
- - -
-
- -
- - -
-
- {% trans 'Join Space' %} -
-
-
{% trans 'Join an existing space.' %}
-

{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}

- -
- {% csrf_token %} - {{ join_form | crispy }} - -
- -
-
- -
-
- {% trans 'Create Space' %} -
-
-
{% trans 'Create your own recipe space.' %}
-

{% trans 'Start your own recipe space and invite other users to it.' %}

-
- {% csrf_token %} - {{ create_form | crispy }} - -
-
-
- - -
-
- -
- -
- -{% endblock %} - diff --git a/cookbook/templates/space_overview.html b/cookbook/templates/space_overview.html new file mode 100644 index 00000000..bf4bb3bf --- /dev/null +++ b/cookbook/templates/space_overview.html @@ -0,0 +1,106 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} +{% load static %} +{% load i18n %} + +{% block title %}{% trans "No Space" %}{% endblock %} + + +{% block content %} + +
+ +

{% trans 'Space' %}

+ +
+
+ {% trans 'Recipes, foods, shopping lists and more are organized in spaces of one or more people.' %} + {% trans 'You can either be invited into an existing space or create your own one.' %} +
+
+ + {% if request.user.userspace_set.all|length > 0 %} +
+
+
{% trans 'Your Spaces' %}
+
+
+ +
+
+
+ {% for us in request.user.userspace_set.all %} + +
+
+
{{ us.space.name }} +
+

{% trans 'Owner' %}: {{ us.space.created_by }} + {% if us.space.created_by != us.user %} +

{% trans 'Leave Space' %} + {% endif %} + +

+
+
+ {% endfor %} +
+
+ +
+ {% endif %} + +
+
+ +
+ + +
+
+ {% trans 'Join Space' %} +
+
+
{% trans 'Join an existing space.' %}
+

{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}

+ +
+ {% csrf_token %} + {{ join_form | crispy }} + +
+ +
+
+ +
+
+ {% trans 'Create Space' %} +
+
+
{% trans 'Create your own recipe space.' %}
+

{% trans 'Start your own recipe space and invite other users to it.' %}

+
+ {% csrf_token %} + {{ create_form | crispy }} + +
+
+
+ + +
+
+ +
+ +
+ +{% endblock %} + diff --git a/cookbook/urls.py b/cookbook/urls.py index 546e985a..db3a066c 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -12,7 +12,7 @@ from recipes.version import VERSION_NUMBER from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile, - get_model_name) + get_model_name, UserSpace) from .views import api, data, delete, edit, import_export, lists, new, telegram, views from .views.api import CustomAuthToken @@ -55,7 +55,7 @@ urlpatterns = [ path('space/member///', views.space_change_member, name='change_space_member'), path('no-group', views.no_groups, name='view_no_group'), - path('no-space', views.no_space, name='view_no_space'), + path('space-overview', views.space_overview, name='view_space_overview'), path('switch-space/', views.switch_space, name='view_switch_space'), path('no-perm', views.no_perm, name='view_no_perm'), path('signup/', views.signup, name='view_signup'), # TODO deprecated with 0.16.2 remove at some point @@ -146,7 +146,7 @@ urlpatterns = [ generic_models = ( Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, - Comment, RecipeBookEntry, ShoppingList, InviteLink + Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace ) for m in generic_models: diff --git a/cookbook/views/delete.py b/cookbook/views/delete.py index 6b94298c..153298cc 100644 --- a/cookbook/views/delete.py +++ b/cookbook/views/delete.py @@ -9,7 +9,7 @@ from django.views.generic import DeleteView from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required from cookbook.models import (Comment, InviteLink, MealPlan, Recipe, RecipeBook, RecipeBookEntry, - RecipeImport, Storage, Sync) + RecipeImport, Storage, Sync, UserSpace) from cookbook.provider.dropbox import Dropbox from cookbook.provider.local import Local from cookbook.provider.nextcloud import Nextcloud @@ -188,3 +188,14 @@ class InviteLinkDelete(OwnerRequiredMixin, DeleteView): context = super(InviteLinkDelete, self).get_context_data(**kwargs) context['title'] = _("Invite Link") return context + + +class UserSpaceDelete(OwnerRequiredMixin, DeleteView): + template_name = "generic/delete_template.html" + model = UserSpace + success_url = reverse_lazy('view_space_overview') + + def get_context_data(self, **kwargs): + context = super(UserSpaceDelete, self).get_context_data(**kwargs) + context['title'] = _("Space Membership") + return context diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 8407c1e3..4a09d0d6 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -103,10 +103,7 @@ def no_groups(request): @login_required -def no_space(request): - if request.user.userspace_set.count() > 0: - return HttpResponseRedirect(reverse('index')) - +def space_overview(request): if request.POST: create_form = SpaceCreateForm(request.POST, prefix='create') join_form = SpaceJoinForm(request.POST, prefix='join') @@ -125,7 +122,7 @@ def no_space(request): messages.add_message(request, messages.SUCCESS, _('You have successfully created your own recipe space. Start by adding some recipes or invite other people to join you.')) - return HttpResponseRedirect(reverse('index')) + return HttpResponseRedirect(reverse('view_switch_space', args=[user_space.pk])) if join_form.is_valid(): return HttpResponseRedirect(reverse('view_invite', args=[join_form.cleaned_data['token']])) @@ -141,7 +138,7 @@ def no_space(request): create_form = SpaceCreateForm(initial={'name': f'{request.user.username}\'s Space'}) join_form = SpaceJoinForm() - return render(request, 'no_space_info.html', {'create_form': create_form, 'join_form': join_form}) + return render(request, 'space_overview.html', {'create_form': create_form, 'join_form': join_form}) @login_required @@ -391,7 +388,7 @@ def user_settings(request): up.shopping_auto_sync = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL up.save() if up: - preference_form = UserPreferenceForm(instance=up) + preference_form = UserPreferenceForm(instance=up, space=request.space) shopping_form = ShoppingPreferenceForm(instance=up) else: preference_form = UserPreferenceForm(space=request.space) @@ -510,27 +507,25 @@ def invite_link(request, token): if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first(): if request.user.is_authenticated: - if request.user.userpreference.space: - messages.add_message(request, messages.WARNING, - _('You are already member of a space and therefore cannot join this one.')) - return HttpResponseRedirect(reverse('index')) - link.used_by = request.user link.save() - request.user.groups.clear() - request.user.groups.add(link.group) - request.user.userpreference.space = link.space - request.user.userpreference.save() + user_space = UserSpace.objects.create(user=request.user, space=link.space, active=False) + + if request.user.userspace_set.count() == 1: + user_space.active = True + user_space.save() + + user_space.groups.add(link.group) messages.add_message(request, messages.SUCCESS, _('Successfully joined space.')) - return HttpResponseRedirect(reverse('index')) + return HttpResponseRedirect(reverse('view_space_overview')) else: request.session['signup_token'] = str(token) return HttpResponseRedirect(reverse('account_signup')) messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!')) - return HttpResponseRedirect(reverse('index')) + return HttpResponseRedirect(reverse('view_space_overview')) # TODO deprecated with 0.16.2 remove at some point @@ -540,7 +535,7 @@ def signup(request, token): @group_required('admin') def space(request): - space_users = UserPreference.objects.filter(space=request.space).all() + space_users = UserSpace.objects.filter(space=request.space).all() counts = Object() counts.recipes = Recipe.objects.filter(space=request.space).count()