diff --git a/cookbook/forms.py b/cookbook/forms.py
index 395209b7d..27db9f60b 100644
--- a/cookbook/forms.py
+++ b/cookbook/forms.py
@@ -271,6 +271,15 @@ class MealPlanForm(forms.ModelForm):
widgets = {'recipe': SelectWidget, 'date': DateWidget, 'shared': MultiSelectWidget}
+class InviteLinkForm(forms.ModelForm):
+ class Meta:
+ model = InviteLink
+ fields = ('username', 'group', 'valid_until')
+ help_texts = {
+ 'username': _('A username is not required, if left blank the new user can choose one.')
+ }
+
+
class SuperUserForm(forms.Form):
name = forms.CharField()
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
diff --git a/cookbook/migrations/0077_invitelink.py b/cookbook/migrations/0077_invitelink.py
new file mode 100644
index 000000000..99210c576
--- /dev/null
+++ b/cookbook/migrations/0077_invitelink.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.0.7 on 2020-09-01 11:31
+
+import datetime
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('cookbook', '0076_shoppinglist_entries'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='InviteLink',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('uuid', models.UUIDField(default=uuid.uuid4)),
+ ('username', models.CharField(blank=True, max_length=64)),
+ ('valid_until', models.DateField(default=datetime.date(2020, 9, 15))),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/cookbook/migrations/0078_invitelink_used_by.py b/cookbook/migrations/0078_invitelink_used_by.py
new file mode 100644
index 000000000..1637427c2
--- /dev/null
+++ b/cookbook/migrations/0078_invitelink_used_by.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.7 on 2020-09-01 11:39
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('cookbook', '0077_invitelink'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='invitelink',
+ name='used_by',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/cookbook/migrations/0079_invitelink_group.py b/cookbook/migrations/0079_invitelink_group.py
new file mode 100644
index 000000000..eb1725920
--- /dev/null
+++ b/cookbook/migrations/0079_invitelink_group.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.7 on 2020-09-01 12:54
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0011_update_proxy_permissions'),
+ ('cookbook', '0078_invitelink_used_by'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='invitelink',
+ name='group',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/cookbook/models.py b/cookbook/models.py
index 7f6e7450c..a3a474491 100644
--- a/cookbook/models.py
+++ b/cookbook/models.py
@@ -1,8 +1,10 @@
import re
import uuid
+from datetime import date, timedelta
+
from annoying.fields import AutoOneToOneField
from django.contrib import auth
-from django.contrib.auth.models import User
+from django.contrib.auth.models import User, Group
from django.utils.translation import gettext as _
from django.db import models
@@ -296,6 +298,16 @@ class ShareLink(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
+class InviteLink(models.Model):
+ uuid = models.UUIDField(default=uuid.uuid4)
+ username = models.CharField(blank=True, max_length=64)
+ group = models.ForeignKey(Group, on_delete=models.CASCADE)
+ valid_until = models.DateField(default=date.today()+timedelta(days=14))
+ used_by = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='used_by')
+ created_by = models.ForeignKey(User, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+
class CookLog(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
diff --git a/cookbook/tables.py b/cookbook/tables.py
index 0d04010ab..836018aed 100644
--- a/cookbook/tables.py
+++ b/cookbook/tables.py
@@ -117,6 +117,13 @@ class ShoppingListTable(tables.Table):
fields = ('id', 'created_by', 'created_at')
+class InviteLinkTable(tables.Table):
+ class Meta:
+ model = InviteLink
+ template_name = 'generic/table_template.html'
+ fields = ('id', 'username', 'group', 'valid_until', 'created_by', 'created_at', 'used_by')
+
+
class ViewLogTable(tables.Table):
recipe = tables.LinkColumn('view_recipe', args=[A('recipe_id')])
diff --git a/cookbook/templates/system.html b/cookbook/templates/system.html
index 50e86e4e2..3ad9cc923 100644
--- a/cookbook/templates/system.html
+++ b/cookbook/templates/system.html
@@ -16,8 +16,18 @@
-
SECRET_KEY configured in your .env file. Django defaulted to the standard key
+ You do not have a SECRET_KEY configured in your .env file. Django defaulted to the
+ standard key
provided with the installation which is publicly know and insecure! Please set
SECRET_KEY int the .env configuration file.
{% endblocktrans %}
diff --git a/cookbook/urls.py b/cookbook/urls.py
index ecf8ac202..7f6354b2a 100644
--- a/cookbook/urls.py
+++ b/cookbook/urls.py
@@ -91,7 +91,7 @@ urlpatterns = [
]
-generic_models = (Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Comment, RecipeBookEntry, Keyword, Food, ShoppingList)
+generic_models = (Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Comment, RecipeBookEntry, Keyword, Food, ShoppingList, InviteLink)
for m in generic_models:
py_name = get_model_name(m)
diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py
index 8ae428e71..574e1ce74 100644
--- a/cookbook/views/lists.py
+++ b/cookbook/views/lists.py
@@ -6,8 +6,8 @@ from django_tables2 import RequestConfig
from cookbook.filters import IngredientFilter
from cookbook.helper.permission_helper import group_required
-from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Food, ShoppingList
-from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable, ShoppingListTable
+from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Food, ShoppingList, InviteLink
+from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable, ShoppingListTable, InviteLinkTable
@group_required('user')
@@ -59,3 +59,11 @@ def storage(request):
RequestConfig(request, paginate={'per_page': 25}).configure(table)
return render(request, 'generic/list_template.html', {'title': _("Storage Backend"), 'table': table, 'create_url': 'new_storage'})
+
+
+@group_required('admin')
+def invite_link(request):
+ table = InviteLinkTable(InviteLink.objects.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/new.py b/cookbook/views/new.py
index f2fdd925b..464fbe7cc 100644
--- a/cookbook/views/new.py
+++ b/cookbook/views/new.py
@@ -9,9 +9,9 @@ from django.utils.translation import gettext as _
from django.views.generic import CreateView
from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \
- RecipeBookForm, MealPlanForm
+ RecipeBookForm, MealPlanForm, InviteLinkForm
from cookbook.helper.permission_helper import GroupRequiredMixin, group_required
-from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan, ShareLink, MealType, Step
+from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan, ShareLink, MealType, Step, InviteLink
class RecipeCreate(GroupRequiredMixin, CreateView):
@@ -162,3 +162,21 @@ class MealPlanCreate(GroupRequiredMixin, CreateView):
context['default_recipe'] = Recipe.objects.get(pk=int(recipe))
return context
+
+
+class InviteLinkCreate(GroupRequiredMixin, CreateView):
+ groups_required = ['admin']
+ template_name = "generic/new_template.html"
+ model = InviteLink
+ form_class = InviteLinkForm
+
+ def form_valid(self, form):
+ obj = form.save(commit=False)
+ obj.created_by = self.request.user
+ obj.save()
+ return HttpResponseRedirect(reverse('list_invite_link'))
+
+ def get_context_data(self, **kwargs):
+ context = super(InviteLinkCreate, self).get_context_data(**kwargs)
+ context['title'] = _("Invite Link")
+ return context