join and leave spaces

This commit is contained in:
vabene1111 2022-05-31 20:38:53 +02:00
parent b3fcfdfc96
commit 007b7294d9
12 changed files with 149 additions and 105 deletions

View File

@ -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

View File

@ -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'))

View File

@ -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)

View File

@ -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)

View File

@ -1,3 +1,4 @@
import django_tables2 as tables
from django.utils.html import format_html
from django.utils.translation import gettext as _

View File

@ -304,19 +304,19 @@
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
{% endif %}
{% if request.user.is_authenticated and request.user.userspace_set.all|length > 1 %}
{% if request.user.is_authenticated %}
<div class="dropdown-divider"></div>
<h6 class="dropdown-header">{% trans 'Your Spaces' %}</h6>
{% for us in request.user.userspace_set.all %}
<a class="dropdown-item" href="{% url 'view_switch_space' us.space.id %}">
{% if us.active %}
<i class="far fa-dot-circle fa-fw"></i>
<i class="far fa-dot-circle fa-fw"></i>
{% else %}
<i class="far fa-circle fa-fw"></i>
{% endif %}
{{ us.space.name }}</a>
{% endfor %}
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i class="fas fa-plus fa-fw"></i> {% trans 'Create New' %}</a>
{% endif %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i

View File

@ -19,6 +19,7 @@
{% csrf_token %}
<div class="alert alert-warning" role="alert">
{% blocktrans %}Are you sure you want to delete the {{ title }}: <b>{{ object }}</b> {% endblocktrans %}
{% trans 'This cannot be undone!' %}
</div>
{{ form|crispy }}

View File

@ -1,70 +0,0 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "No Space" %}{% endblock %}
{% block content %}
<div style="text-align: center">
<h3 class="">{% trans 'No Space' %}</h3>
<div class="row">
<div class="col col-md-12">
{% 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.' %}
</div>
</div>
<div class="row" style="margin-top: 2vh">
<div class="col col-md-12">
<div class="card-group">
<div class="card">
<div class="card-header">
{% trans 'Join Space' %}
</div>
<div class="card-body">
<h5 class="card-title">{% trans 'Join an existing space.' %}</h5>
<p class="card-text" style="height: 64px">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
<form method="POST" action="{% url 'view_no_space' %}">
{% csrf_token %}
{{ join_form | crispy }}
<input type="submit" class="btn btn-primary" value="{% trans 'Join Space' %}"/>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
{% trans 'Create Space' %}
</div>
<div class="card-body">
<h5 class="card-title">{% trans 'Create your own recipe space.' %}</h5>
<p class="card-text" style="height: 64px">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
<form method="POST" action="{% url 'view_no_space' %}">
{% csrf_token %}
{{ create_form | crispy }}
<input type="submit" class="btn btn-primary" value="{% trans 'Create Space' %}"/>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,106 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "No Space" %}{% endblock %}
{% block content %}
<div style="text-align: center">
<h3 class="">{% trans 'Space' %}</h3>
<div class="row">
<div class="col col-md-12">
{% 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.' %}
</div>
</div>
{% if request.user.userspace_set.all|length > 0 %}
<div class="row mt-2">
<div class="col col-12">
<h5>{% trans 'Your Spaces' %}</h5>
</div>
</div>
<div class="row mt-2">
<div class="col col-12">
<div class="card-deck">
{% for us in request.user.userspace_set.all %}
<div class="card">
<div class="card-body">
<h5 class="card-title"><a
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
</h5>
<p class="card-text"><small
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
{% if us.space.created_by != us.user %}
<p class="card-text"><small
class="text-muted"><a
href="{% url 'delete_user_space' us.pk %}">{% trans 'Leave Space' %}</a></small>
{% endif %}
<!--TODO add direct link to management page -->
</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<div class="row" style="margin-top: 2vh">
<div class="col col-md-12">
<div class="card-group">
<div class="card">
<div class="card-header">
{% trans 'Join Space' %}
</div>
<div class="card-body">
<h5 class="card-title">{% trans 'Join an existing space.' %}</h5>
<p class="card-text"
style="height: 64px">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
<form method="POST" action="{% url 'view_space_overview' %}">
{% csrf_token %}
{{ join_form | crispy }}
<input type="submit" class="btn btn-primary" value="{% trans 'Join Space' %}"/>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
{% trans 'Create Space' %}
</div>
<div class="card-body">
<h5 class="card-title">{% trans 'Create your own recipe space.' %}</h5>
<p class="card-text"
style="height: 64px">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
<form method="POST" action="{% url 'view_space_overview' %}">
{% csrf_token %}
{{ create_form | crispy }}
<input type="submit" class="btn btn-primary" value="{% trans 'Create Space' %}"/>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -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/<int:user_id>/<int:space_id>/<slug:group>', 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/<int:space_id>', views.switch_space, name='view_switch_space'),
path('no-perm', views.no_perm, name='view_no_perm'),
path('signup/<slug:token>', 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:

View File

@ -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

View File

@ -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()