diff --git a/cookbook/admin.py b/cookbook/admin.py index b4e4de26c..0115f8fc6 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -125,6 +125,13 @@ class ViewLogAdmin(admin.ModelAdmin): admin.site.register(ViewLog, ViewLogAdmin) +class InviteLinkAdmin(admin.ModelAdmin): + list_display = ('username', 'group', 'valid_until', 'created_by', 'created_at', 'used_by') + + +admin.site.register(InviteLink, InviteLinkAdmin) + + class CookLogAdmin(admin.ModelAdmin): list_display = ('recipe', 'created_by', 'created_at', 'rating', 'servings') diff --git a/cookbook/forms.py b/cookbook/forms.py index 27db9f60b..e0dc3801c 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -280,7 +280,7 @@ class InviteLinkForm(forms.ModelForm): } -class SuperUserForm(forms.Form): +class UserCreateForm(forms.Form): name = forms.CharField() 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'})) diff --git a/cookbook/models.py b/cookbook/models.py index a3a474491..92b2be402 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -307,6 +307,9 @@ class InviteLink(models.Model): created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): + return f'{self.uuid}' + class CookLog(models.Model): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) diff --git a/cookbook/tables.py b/cookbook/tables.py index 836018aed..657510eba 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -118,10 +118,13 @@ class ShoppingListTable(tables.Table): class InviteLinkTable(tables.Table): + link = tables.TemplateColumn("" + _('Link') + "") + delete = tables.TemplateColumn("" + _('Delete') + "") + class Meta: model = InviteLink template_name = 'generic/table_template.html' - fields = ('id', 'username', 'group', 'valid_until', 'created_by', 'created_at', 'used_by') + fields = ('username', 'group', 'valid_until', 'created_by', 'created_at') class ViewLogTable(tables.Table): diff --git a/cookbook/templates/registration/login.html b/cookbook/templates/registration/login.html index ea2446d2a..5d90ebba5 100644 --- a/cookbook/templates/registration/login.html +++ b/cookbook/templates/registration/login.html @@ -1,6 +1,8 @@ {% extends "base.html" %} {% load i18n %} +{% block title %}{% trans 'Login' %}{% endblock %} + {% block content %} {% if form.errors %} diff --git a/cookbook/templates/registration/signup.html b/cookbook/templates/registration/signup.html new file mode 100644 index 000000000..e774cfeb1 --- /dev/null +++ b/cookbook/templates/registration/signup.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} +{% load i18n %} + +{% block title %}{% trans 'Register' %}{% endblock %} + +{% block content %} + +

{% trans 'Create your Account' %}

+ +
+ {% csrf_token %} + {{ form|crispy }} + +
+ +{% endblock %} \ No newline at end of file diff --git a/cookbook/urls.py b/cookbook/urls.py index 7f6354b2a..143667ceb 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -29,6 +29,7 @@ router.register(r'view-log', api.ViewLogViewSet) urlpatterns = [ path('', views.index, name='index'), path('setup/', views.setup, name='view_setup'), + path('signup/', views.signup, name='view_signup'), path('system/', views.system, name='view_system'), path('search/', views.search, name='view_search'), path('books/', views.books, name='view_books'), diff --git a/cookbook/views/delete.py b/cookbook/views/delete.py index a8bf4d57f..a94e8661c 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 group_required, GroupRequiredMixin, OwnerRequiredMixin from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeBook, \ - RecipeBookEntry, MealPlan, Food + RecipeBookEntry, MealPlan, Food, InviteLink from cookbook.provider.dropbox import Dropbox from cookbook.provider.nextcloud import Nextcloud @@ -148,3 +148,14 @@ class MealPlanDelete(OwnerRequiredMixin, DeleteView): context = super(MealPlanDelete, self).get_context_data(**kwargs) context['title'] = _("Meal-Plan") return context + + +class InviteLinkDelete(OwnerRequiredMixin, DeleteView): + template_name = "generic/delete_template.html" + model = InviteLink + success_url = reverse_lazy('list_invite_link') + + def get_context_data(self, **kwargs): + context = super(InviteLinkDelete, self).get_context_data(**kwargs) + context['title'] = _("Invite Link") + return context diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py index 574e1ce74..0c0dc20d5 100644 --- a/cookbook/views/lists.py +++ b/cookbook/views/lists.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.contrib.auth.decorators import login_required from django.db.models.functions import Lower from django.shortcuts import render @@ -63,7 +65,7 @@ def storage(request): @group_required('admin') def invite_link(request): - table = InviteLinkTable(InviteLink.objects.all()) + table = InviteLinkTable(InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None).all()) RequestConfig(request, paginate={'per_page': 25}).configure(table) return render(request, 'generic/list_template.html', {'title': _("Invite Links"), 'table': table, 'create_url': 'new_invite_link'}) diff --git a/cookbook/views/views.py b/cookbook/views/views.py index c3bd4806a..1ecdaa453 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -1,7 +1,7 @@ import copy import os from datetime import datetime, timedelta - +from uuid import UUID from django.contrib import messages from django.contrib.auth import update_session_auth_hash, authenticate from django.contrib.auth.forms import PasswordChangeForm @@ -240,7 +240,7 @@ def setup(request): return HttpResponseRedirect(reverse('login')) if request.method == 'POST': - form = SuperUserForm(request.POST) + form = UserCreateForm(request.POST) if form.is_valid(): if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: form.add_error('password', _('Passwords dont match!')) @@ -260,11 +260,59 @@ def setup(request): for m in e: form.add_error('password', m) else: - form = SuperUserForm() + form = UserCreateForm() return render(request, 'setup.html', {'form': form}) +def signup(request, token): + try: + token = UUID(token, version=4) + except ValueError: + messages.add_message(request, messages.ERROR, _('Malformed Invite Link supplied!')) + return HttpResponseRedirect(reverse('index')) + + if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first(): + if request.method == 'POST': + + form = UserCreateForm(request.POST) + if link.username != '': + data = dict(form.data) + data['name'] = link.username + form.data = data + + if form.is_valid(): + if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: + form.add_error('password', _('Passwords dont match!')) + else: + user = User( + username=form.cleaned_data['name'], + ) + try: + validate_password(form.cleaned_data['password'], user=user) + user.set_password(form.cleaned_data['password']) + user.save() + messages.add_message(request, messages.SUCCESS, _('User has been created, please login!')) + + link.used_by = user + link.save() + user.groups.add(link.group) + return HttpResponseRedirect(reverse('login')) + except ValidationError as e: + for m in e: + form.add_error('password', m) + else: + form = UserCreateForm() + + if link.username != '': + form.fields['name'].initial = link.username + form.fields['name'].disabled = True + return render(request, 'registration/signup.html', {'form': form, 'link': link}) + + messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!')) + return HttpResponseRedirect(reverse('index')) + + def markdown_info(request): return render(request, 'markdown_info.html', {})