usr signup and more

This commit is contained in:
vabene1111
2021-02-21 15:08:43 +01:00
parent beb4aa634f
commit 846c660811
15 changed files with 287 additions and 106 deletions

View File

@ -1,4 +1,6 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User, Group
from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
MealPlan, MealType, NutritionInformation, Recipe,
@ -8,6 +10,17 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation)
class CustomUserAdmin(UserAdmin):
def has_add_permission(self, request, obj=None):
return False
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
admin.site.unregister(Group)
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'message')
@ -16,10 +29,7 @@ admin.site.register(Space, SpaceAdmin)
class UserPreferenceAdmin(admin.ModelAdmin):
list_display = (
'name', 'theme', 'nav_color',
'default_page', 'search_style', 'comments'
)
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',)
@staticmethod
def name(obj):

View File

@ -6,7 +6,7 @@ from emoji_picker.widgets import EmojiPickerTextInput
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, Storage, Sync, Unit, User,
UserPreference, SupermarketCategory, MealType)
UserPreference, SupermarketCategory, MealType, Space)
class SelectWidget(widgets.Select):
@ -371,12 +371,20 @@ class MealPlanForm(forms.ModelForm):
class InviteLinkForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['space'].queryset = Space.objects.filter(created_by=user).all()
class Meta:
model = InviteLink
fields = ('username', 'group', 'valid_until')
fields = ('username', 'group', 'valid_until', 'space')
help_texts = {
'username': _('A username is not required, if left blank the new user can choose one.') # noqa: E501
}
field_classes = {
'space': SafeModelChoiceField,
}
class UserCreateForm(forms.Form):

View File

@ -123,9 +123,13 @@ class GroupRequiredMixin(object):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
if self.get_object().get_space() != request.space:
try:
obj = self.get_object()
if obj.get_space() != request.space:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
except AttributeError:
pass
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
@ -141,9 +145,13 @@ class OwnerRequiredMixin(object):
messages.add_message(request, messages.ERROR, _('You cannot interact with this object as it is not owned by you!'))
return HttpResponseRedirect(reverse('index'))
if self.get_object().get_space() != request.space:
try:
obj = self.get_object()
if obj.get_space() != request.space:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
except AttributeError:
pass
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)

View File

@ -1,3 +1,5 @@
from django.shortcuts import redirect
from django.urls import reverse
from django_scopes import scope, scopes_disabled
@ -7,10 +9,21 @@ class ScopeMiddleware:
def __call__(self, request):
if request.user.is_authenticated:
request.space = request.user.userpreference.space
if request.user.groups.count() == 0:
return redirect('view_no_group')
with scopes_disabled():
#with scope(space=request.space):
if request.user.userpreference.space is None and not reverse('view_no_space') in request.path and not reverse('account_logout') in request.path:
return redirect(reverse('view_no_space'))
if request.path.startswith('/admin/'):
with scopes_disabled():
return self.get_response(request)
request.space = request.user.userpreference.space
# with scopes_disabled():
with scope(space=request.space):
return self.get_response(request)
else:
return self.get_response(request)

View File

@ -27,7 +27,8 @@ class Integration:
self.keyword = Keyword.objects.create(
name=f'Import {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}.{datetime.datetime.now().strftime("%S")}',
description=f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}',
icon='📥'
icon='📥',
space=request.space
)
def do_export(self, recipes):

View File

@ -0,0 +1,63 @@
# Generated by Django 3.1.6 on 2021-02-21 11:04
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0108_auto_20210219_1410'),
]
operations = [
migrations.RemoveField(
model_name='recipebookentry',
name='space',
),
migrations.AlterField(
model_name='food',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='keyword',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='supermarket',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='supermarketcategory',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='unit',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterUniqueTogether(
name='food',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='keyword',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarket',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarketcategory',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='unit',
unique_together={('space', 'name')},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-21 13:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0109_auto_20210221_1204'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 3.1.6 on 2021-02-21 13:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def set_default_owner(apps, schema_editor):
Space = apps.get_model('cookbook', 'Space')
User = apps.get_model('auth', 'user')
with scopes_disabled():
for x in Space.objects.all():
x.created_by = User.objects.filter(is_superuser=True).first()
x.save()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0110_auto_20210221_1406'),
]
operations = [
migrations.AddField(
model_name='space',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.RunPython(set_default_owner),
]

View File

@ -45,8 +45,12 @@ class PermissionModelMixin:
class Space(models.Model):
name = models.CharField(max_length=128, default='Default')
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
message = models.CharField(max_length=512, default='', blank=True)
def __str__(self):
return self.name
class UserPreference(models.Model, PermissionModelMixin):
# Themes
@ -121,7 +125,7 @@ class UserPreference(models.Model, PermissionModelMixin):
shopping_auto_sync = models.IntegerField(default=5)
sticky_navbar = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
objects = ScopedManager(space='space')
def __str__(self):
@ -168,7 +172,7 @@ class Sync(models.Model, PermissionModelMixin):
class SupermarketCategory(models.Model, PermissionModelMixin):
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
@ -177,9 +181,12 @@ class SupermarketCategory(models.Model, PermissionModelMixin):
def __str__(self):
return self.name
class Meta:
unique_together = (('space', 'name'),)
class Supermarket(models.Model, PermissionModelMixin):
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
@ -189,6 +196,9 @@ class Supermarket(models.Model, PermissionModelMixin):
def __str__(self):
return self.name
class Meta:
unique_together = (('space', 'name'),)
class SupermarketCategoryRelation(models.Model, PermissionModelMixin):
supermarket = models.ForeignKey(Supermarket, on_delete=models.CASCADE, related_name='category_to_supermarket')
@ -218,7 +228,7 @@ class SyncLog(models.Model, PermissionModelMixin):
class Keyword(models.Model, PermissionModelMixin):
name = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=64)
icon = models.CharField(max_length=16, blank=True, null=True)
description = models.TextField(default="", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
@ -233,9 +243,12 @@ class Keyword(models.Model, PermissionModelMixin):
else:
return f"{self.name}"
class Meta:
unique_together = (('space', 'name'),)
class Unit(models.Model, PermissionModelMixin):
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
@ -244,9 +257,12 @@ class Unit(models.Model, PermissionModelMixin):
def __str__(self):
return self.name
class Meta:
unique_together = (('space', 'name'),)
class Food(models.Model, PermissionModelMixin):
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
ignore_shopping = models.BooleanField(default=False)
@ -258,6 +274,9 @@ class Food(models.Model, PermissionModelMixin):
def __str__(self):
return self.name
class Meta:
unique_together = (('space', 'name'),)
class Ingredient(models.Model, PermissionModelMixin):
food = models.ForeignKey(Food, on_delete=models.PROTECT, null=True, blank=True)

View File

@ -111,17 +111,12 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
return str(obj)
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
obj, created = Keyword.objects.get_or_create(name=validated_data['name'])
obj, created = Keyword.objects.get_or_create(name=validated_data['name'], space=self.context['request'].space)
return obj
class Meta:
model = Keyword
fields = (
'id', 'name', 'icon', 'label', 'description',
'created_at', 'updated_at'
)
fields = ('id', 'name', 'icon', 'label', 'description', 'created_at', 'updated_at')
read_only_fields = ('id',)
@ -129,9 +124,7 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
obj, created = Unit.objects.get_or_create(name=validated_data['name'])
obj, created = Unit.objects.get_or_create(name=validated_data['name'], space=self.context['request'].space)
return obj
class Meta:
@ -143,9 +136,7 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
obj, created = SupermarketCategory.objects.get_or_create(name=validated_data['name'])
obj, created = SupermarketCategory.objects.get_or_create(name=validated_data['name'], space=self.context['request'].space)
return obj
def update(self, instance, validated_data):
@ -176,9 +167,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
obj, created = Food.objects.get_or_create(name=validated_data['name'])
obj, created = Food.objects.get_or_create(name=validated_data['name'], space=self.context['request'].space)
return obj
def update(self, instance, validated_data):
@ -256,6 +245,7 @@ class RecipeSerializer(WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
@ -455,4 +445,5 @@ class RecipeExportSerializer(WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)

View File

@ -2,7 +2,7 @@
{% load static %}
{% load i18n %}
{% block title %}{% trans "Offline" %}{% endblock %}
{% block title %}{% trans "No Permissions" %}{% endblock %}
{% block content %}

View File

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "No Space" %}{% endblock %}
{% block content %}
<div style="text-align: center">
<h1 class="">{% trans 'No Space' %}</h1>
<br/>
<span>{% trans 'You are not a member of any space. Please contact your administrator.' %}</span> <br/>
</div>
{% endblock %}

View File

@ -40,6 +40,7 @@ urlpatterns = [
path('', views.index, name='index'),
path('setup/', views.setup, name='view_setup'),
path('no-group', views.no_groups, name='view_no_group'),
path('no-space', views.no_space, name='view_no_space'),
path('signup/<slug:token>', views.signup, name='view_signup'),
path('system/', views.system, name='view_system'),
path('search/', views.search, name='view_search'),
@ -105,7 +106,6 @@ urlpatterns = [
path('offline/', views.offline, name='view_offline'),
path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', )), name='service_worker'),
path('manifest.json', (TemplateView.as_view(template_name="manifest.json", content_type='application/json', )), name='web_manifest'),
]

View File

@ -212,3 +212,8 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
context = super(InviteLinkCreate, self).get_context_data(**kwargs)
context['title'] = _("Invite Link")
return context
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

View File

@ -26,7 +26,7 @@ from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
UserPreferenceForm)
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList)
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space)
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
ViewLogTable)
from recipes.settings import DEMO
@ -92,6 +92,10 @@ def no_groups(request):
return render(request, 'no_groups_info.html')
def no_space(request):
return render(request, 'no_space_info.html')
def recipe_view(request, pk, share=None):
with scopes_disabled():
recipe = get_object_or_404(Recipe, pk=pk)
@ -331,11 +335,8 @@ def system(request):
def setup(request):
if (User.objects.count() > 0
or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS): # noqa: E501
messages.add_message(request, messages.ERROR,
_('The setup page can only be used to create the first user! If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.') # noqa: E501
)
if User.objects.count() > 0 or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS:
messages.add_message(request, messages.ERROR, _('The setup page can only be used to create the first user! If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.'))
return HttpResponseRedirect(reverse('account_login'))
if request.method == 'POST':
@ -344,20 +345,19 @@ def setup(request):
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
form.add_error('password', _('Passwords dont match!'))
else:
user = User(
username=form.cleaned_data['name'],
is_superuser=True,
is_staff=True
)
user = User(username=form.cleaned_data['name'], is_superuser=True, is_staff=True)
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!')
)
user.userpreference.space = Space.objects.first()
user.userpreference.save()
with scopes_disabled():
for x in Space.objects.all():
x.created_by = user
x.save()
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
return HttpResponseRedirect(reverse('account_login'))
except ValidationError as e:
for m in e:
@ -369,6 +369,7 @@ def setup(request):
def signup(request, token):
with scopes_disabled():
try:
token = UUID(token, version=4)
except ValueError:
@ -387,24 +388,19 @@ def signup(request, token):
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
form.add_error('password', _('Passwords dont match!'))
else:
user = User(
username=form.cleaned_data['name'],
)
user = User(username=form.cleaned_data['name'], )
try:
validate_password(
form.cleaned_data['password'], user=user
)
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!')
)
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
link.used_by = user
link.save()
user.groups.add(link.group)
user.userpreference.space = link.space
user.userpreference.save()
return HttpResponseRedirect(reverse('account_login'))
except ValidationError as e:
for m in e:
@ -415,13 +411,9 @@ def signup(request, token):
if link.username != '':
form.fields['name'].initial = link.username
form.fields['name'].disabled = True
return render(
request, 'account/signup.html', {'form': form, 'link': link}
)
return render(request, 'account/signup.html', {'form': form, 'link': link})
messages.add_message(
request, messages.ERROR, _('Invite Link not valid or already used!')
)
messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!'))
return HttpResponseRedirect(reverse('index'))