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' %}
+
+
+
+{% 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', {})