reworked invite system
This commit is contained in:
parent
a14e33973c
commit
c8054349b2
@ -68,7 +68,8 @@ GUNICORN_MEDIA=0
|
||||
# EMAIL_HOST_PASSWORD=
|
||||
# EMAIL_USE_TLS=0
|
||||
# EMAIL_USE_SSL=0
|
||||
# ACCOUNT_EMAIL_SUBJECT_PREFIX
|
||||
# DEFAULT_FROM_EMAIL= # email sender address (default 'webmaster@localhost')
|
||||
# ACCOUNT_EMAIL_SUBJECT_PREFIX= # prefix used for account related emails (default "[Tandoor Recipes] ")
|
||||
|
||||
# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
|
||||
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
|
||||
|
@ -1,6 +1,8 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import widgets
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scopes_disabled
|
||||
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||
from emoji_picker.widgets import EmojiPickerTextInput
|
||||
|
||||
@ -42,10 +44,15 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
)
|
||||
|
||||
help_texts = {
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'), # noqa: E501
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
|
||||
# noqa: E501
|
||||
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'), # noqa: E501
|
||||
'use_fractions': _('Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'), # noqa: E501
|
||||
'plan_share': _('Users with whom newly created meal plan/shopping list entries should be shared by default.'), # noqa: E501
|
||||
'use_fractions': _(
|
||||
'Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'),
|
||||
# noqa: E501
|
||||
'plan_share': _(
|
||||
'Users with whom newly created meal plan/shopping list entries should be shared by default.'),
|
||||
# noqa: E501
|
||||
'show_recent': _('Show recently viewed recipes on search page.'), # noqa: E501
|
||||
'ingredient_decimals': _('Number of decimals to round ingredients.'), # noqa: E501
|
||||
'comments': _('If you want to be able to create and see comments underneath recipes.'), # noqa: E501
|
||||
@ -69,7 +76,8 @@ class UserNameForm(forms.ModelForm):
|
||||
fields = ('first_name', 'last_name')
|
||||
|
||||
help_texts = {
|
||||
'first_name': _('Both fields are optional. If none are given the username will be displayed instead') # noqa: E501
|
||||
'first_name': _('Both fields are optional. If none are given the username will be displayed instead')
|
||||
# noqa: E501
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +136,9 @@ class ImportExportBase(forms.Form):
|
||||
|
||||
class ImportForm(ImportExportBase):
|
||||
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
|
||||
duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), required=False)
|
||||
duplicates = forms.BooleanField(help_text=_(
|
||||
'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'),
|
||||
required=False)
|
||||
|
||||
|
||||
class ExportForm(ImportExportBase):
|
||||
@ -251,7 +261,8 @@ class StorageForm(forms.ModelForm):
|
||||
fields = ('name', 'method', 'username', 'password', 'token', 'url', 'path')
|
||||
|
||||
help_texts = {
|
||||
'url': _('Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'),
|
||||
'url': _(
|
||||
'Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'),
|
||||
}
|
||||
|
||||
|
||||
@ -366,7 +377,8 @@ class MealPlanForm(forms.ModelForm):
|
||||
|
||||
help_texts = {
|
||||
'shared': _('You can list default users to share recipes with in the settings.'), # noqa: E501
|
||||
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>') # noqa: E501
|
||||
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
|
||||
# noqa: E501
|
||||
}
|
||||
|
||||
widgets = {
|
||||
@ -387,17 +399,50 @@ class InviteLinkForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['space'].queryset = Space.objects.filter(created_by=user).all()
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
with scopes_disabled():
|
||||
if User.objects.filter(email=email).exists():
|
||||
raise ValidationError(_('Email address already taken!'))
|
||||
|
||||
return email
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data['username']
|
||||
with scopes_disabled():
|
||||
if User.objects.filter(username=username).exists() or InviteLink.objects.filter(username=username).exists():
|
||||
raise ValidationError(_('Username already taken!'))
|
||||
return username
|
||||
|
||||
class Meta:
|
||||
model = InviteLink
|
||||
fields = ('username', 'group', 'valid_until', 'space')
|
||||
fields = ('username', 'email', 'group', 'valid_until', 'space')
|
||||
help_texts = {
|
||||
'username': _('A username is not required, if left blank the new user can choose one.') # noqa: E501
|
||||
'username': _('A username is not required, if left blank the new user can choose one.'),
|
||||
'email': _('An email address is not required but if present the invite link will be send to the user.')
|
||||
}
|
||||
field_classes = {
|
||||
'space': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
|
||||
class SpaceCreateForm(forms.Form):
|
||||
prefix = 'create'
|
||||
name = forms.CharField()
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
with scopes_disabled():
|
||||
if Space.objects.filter(name=name).exists():
|
||||
raise ValidationError(_('Name already taken.'))
|
||||
return name
|
||||
|
||||
|
||||
class SpaceJoinForm(forms.Form):
|
||||
prefix = 'join'
|
||||
token = forms.CharField()
|
||||
|
||||
|
||||
class UserCreateForm(forms.Form):
|
||||
name = forms.CharField(label='Username')
|
||||
password = forms.CharField(
|
||||
|
@ -16,7 +16,7 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
|
||||
|
||||
# disable password reset for now
|
||||
def send_mail(self, template_prefix, email, context):
|
||||
if settings.EMAIL_HOST != '':
|
||||
if settings.EMAIL_HOST == '':
|
||||
super(AllAuthCustomAdapter, self).send_mail(template_prefix, email, context)
|
||||
else:
|
||||
pass
|
||||
|
@ -16,6 +16,9 @@ class ScopeMiddleware:
|
||||
with scopes_disabled():
|
||||
return self.get_response(request)
|
||||
|
||||
if request.path.startswith('/signup/'):
|
||||
return self.get_response(request)
|
||||
|
||||
with scopes_disabled():
|
||||
if request.user.userpreference.space is None and not reverse('account_logout') in request.path:
|
||||
return views.no_space(request)
|
||||
|
23
cookbook/migrations/0122_auto_20210527_1712.py
Normal file
23
cookbook/migrations/0122_auto_20210527_1712.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.3 on 2021-05-27 15:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0121_auto_20210518_1638'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='allow_files',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='max_users',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
18
cookbook/migrations/0123_invitelink_email.py
Normal file
18
cookbook/migrations/0123_invitelink_email.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.3 on 2021-05-28 12:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0122_auto_20210527_1712'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invitelink',
|
||||
name='email',
|
||||
field=models.EmailField(blank=True, max_length=254),
|
||||
),
|
||||
]
|
@ -62,6 +62,8 @@ class Space(models.Model):
|
||||
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
|
||||
message = models.CharField(max_length=512, default='', blank=True)
|
||||
max_recipes = models.IntegerField(default=0)
|
||||
allow_files = models.BooleanField(default=True)
|
||||
max_users = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -607,6 +609,7 @@ def default_valid_until():
|
||||
class InviteLink(models.Model, PermissionModelMixin):
|
||||
uuid = models.UUIDField(default=uuid.uuid4)
|
||||
username = models.CharField(blank=True, max_length=64)
|
||||
email = models.EmailField(blank=True)
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
valid_until = models.DateField(default=default_valid_until)
|
||||
used_by = models.ForeignKey(
|
||||
|
@ -19,7 +19,7 @@
|
||||
{% csrf_token %}
|
||||
{{ form | crispy}}
|
||||
<input type="submit" class="btn btn-primary" value="{% trans 'Reset My Password' %}" />
|
||||
<a class="btn btn-primary" href="{% url 'account_signup' %}">{% trans "Sign In" %}</a>
|
||||
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||
<a class="btn btn-success" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
||||
</form>
|
||||
|
||||
|
@ -11,8 +11,9 @@
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}
|
||||
</button>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}</button>
|
||||
</form>
|
||||
|
||||
<p>{% trans 'Already have an account?' %} <a href="{% url 'account_login' %}">{% trans "Sign In" %}</a></p>
|
||||
|
||||
{% endblock %}
|
14
cookbook/templates/account/signup_closed.html
Normal file
14
cookbook/templates/account/signup_closed.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Sign Up Closed" %}</h1>
|
||||
|
||||
<p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
|
||||
|
||||
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||
<a class="btn btn-secondary" href="{% url 'account_reset_password' %}">{% trans "Reset Password" %}</a>
|
||||
{% endblock %}
|
@ -144,6 +144,9 @@
|
||||
class="fas fa-user-cog fa-fw"></i> {% trans 'Settings' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'view_history' %}"><i
|
||||
class="fas fa-history"></i> {% trans 'History' %}</a>
|
||||
{% if request.user == request.space.created_by %}
|
||||
<a class="dropdown-item" href="{% url 'view_space' %}"><i class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
|
||||
{% endif %}
|
||||
{% if user.is_superuser %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'view_system' %}"><i
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@ -9,10 +10,59 @@
|
||||
|
||||
<div style="text-align: center">
|
||||
|
||||
<h1 class="">{% trans 'No Space' %}</h1>
|
||||
<br/>
|
||||
<h3 class="">{% trans 'No Space' %}</h3>
|
||||
|
||||
<span>{% trans 'You are not a member of any space.' %} {% trans 'Please contact your administrator.' %}</span> <br/>
|
||||
<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">{% 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">{% 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>
|
||||
|
||||
|
93
cookbook/templates/space.html
Normal file
93
cookbook/templates/space.html
Normal file
@ -0,0 +1,93 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Space Settings" %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ request.space.name }}</h3>
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{% trans 'Number of objects' %}
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">{% trans 'Recipes' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.recipes }} /
|
||||
{% if request.space.max_recipes > 0 %}
|
||||
{{ request.space.max_recipes }}{% else %}∞{% endif %}</span></li>
|
||||
<li class="list-group-item">{% trans 'Keywords' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.keywords }}</span></li>
|
||||
<li class="list-group-item">{% trans 'Units' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.units }}</span></li>
|
||||
<li class="list-group-item">{% trans 'Ingredients' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.ingredients }}</span></li>
|
||||
<li class="list-group-item">{% trans 'Recipe Imports' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.recipe_import }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{% trans 'Objects stats' %}
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">{% trans 'Recipes without Keywords' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.recipes_no_keyword }}</span></li>
|
||||
<li class="list-group-item">{% trans 'External Recipes' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.recipes_external }}</span></li>
|
||||
<li class="list-group-item">{% trans 'Internal Recipes' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.recipes_internal }}</span></li>
|
||||
<li class="list-group-item">{% trans 'Comments' %} : <span
|
||||
class="badge badge-pill badge-info">{{ counts.comments }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
|
||||
<h4>{% trans 'Members' %} <small class="text-muted">{{ space_users|length }}/
|
||||
{% if request.space.max_users > 0 %}
|
||||
{{ request.space.max_users }}{% else %}∞{% endif %}</small>
|
||||
</h4><a href="{% url 'new_invite_link' %}">{% trans 'Invite User' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% if space_users %}
|
||||
<table class="table table-bordered">
|
||||
{% for u in space_users %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ u.user.username }}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-success btn-sm" href="">{% trans 'Remove' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
<p>{% trans 'There are no members in your space yet!' %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
@ -41,6 +41,7 @@ router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
path('setup/', views.setup, name='view_setup'),
|
||||
path('space/', views.space, name='view_space'),
|
||||
path('no-group', views.no_groups, name='view_no_group'),
|
||||
path('no-space', views.no_space, name='view_no_space'),
|
||||
path('no-perm', views.no_perm, name='view_no_perm'),
|
||||
|
@ -1,7 +1,11 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from html import escape
|
||||
from smtplib import SMTPException
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.mail import send_mail, BadHeaderError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
@ -154,7 +158,8 @@ class MealPlanCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = self.form_class(**self.get_form_kwargs())
|
||||
form.fields['meal_type'].queryset = MealType.objects.filter(created_by=self.request.user, space=self.request.space).all()
|
||||
form.fields['meal_type'].queryset = MealType.objects.filter(created_by=self.request.user,
|
||||
space=self.request.space).all()
|
||||
return form
|
||||
|
||||
def get_initial(self):
|
||||
@ -207,6 +212,32 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
|
||||
obj.created_by = self.request.user
|
||||
obj.space = self.request.space
|
||||
obj.save()
|
||||
if obj.email:
|
||||
try:
|
||||
if InviteLink.objects.filter(space=self.request.space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.request.user.username)
|
||||
message += _(' to join their Tandoor Recipes space ') + escape(self.request.space.name) + '.\n\n'
|
||||
message += _('Click the following link to activate your account: ') + self.request.build_absolute_uri(reverse('view_signup', args=[str(obj.uuid)])) + '\n\n'
|
||||
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
|
||||
message += _('The invitation is valid until ') + obj.valid_until + '\n\n'
|
||||
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
|
||||
|
||||
send_mail(
|
||||
_('Tandoor Recipes Invite'),
|
||||
message,
|
||||
None,
|
||||
[obj.email],
|
||||
fail_silently=False,
|
||||
|
||||
)
|
||||
messages.add_message(self.request, messages.SUCCESS,
|
||||
_('Invite link successfully send to user.'))
|
||||
else:
|
||||
messages.add_message(self.request, messages.ERROR,
|
||||
_('You have send to many emails, please share the link manually or wait a few hours.'))
|
||||
except (SMTPException, BadHeaderError, TimeoutError):
|
||||
messages.add_message(self.request, messages.ERROR, _('Email to user could not be send, please share link manually.'))
|
||||
|
||||
return HttpResponseRedirect(reverse('list_invite_link'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -218,3 +249,9 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.update({'user': self.request.user})
|
||||
return kwargs
|
||||
|
||||
def get_initial(self):
|
||||
return dict(
|
||||
space=self.request.space,
|
||||
group=Group.objects.get(name='user')
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ from uuid import UUID
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
@ -24,12 +25,14 @@ from rest_framework.authtoken.models import Token
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm)
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm)
|
||||
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, Space)
|
||||
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit,
|
||||
Food)
|
||||
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
|
||||
ViewLogTable)
|
||||
from cookbook.views.data import Object
|
||||
from recipes.settings import DEMO
|
||||
from recipes.version import BUILD_REF, VERSION_NUMBER
|
||||
|
||||
@ -99,13 +102,38 @@ def no_groups(request):
|
||||
return render(request, 'no_groups_info.html')
|
||||
|
||||
|
||||
@login_required
|
||||
def no_space(request):
|
||||
if settings.SOCIAL_DEFAULT_ACCESS:
|
||||
request.user.userpreference.space = Space.objects.first()
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.get(name=settings.SOCIAL_DEFAULT_GROUP))
|
||||
if request.user.userpreference.space:
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
return render(request, 'no_space_info.html')
|
||||
|
||||
if request.POST:
|
||||
create_form = SpaceCreateForm(request.POST, prefix='create')
|
||||
join_form = SpaceJoinForm(request.POST, prefix='join')
|
||||
if create_form.is_valid():
|
||||
created_space = Space.objects.create(name=create_form.cleaned_data['name'], created_by=request.user)
|
||||
request.user.userpreference.space = created_space
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.filter(name='admin').get())
|
||||
|
||||
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'))
|
||||
|
||||
if join_form.is_valid():
|
||||
return HttpResponseRedirect(reverse('view_signup', args=[join_form.cleaned_data['token']]))
|
||||
else:
|
||||
if settings.SOCIAL_DEFAULT_ACCESS:
|
||||
request.user.userpreference.space = Space.objects.first()
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.get(name=settings.SOCIAL_DEFAULT_GROUP))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_signup', args=[request.session.pop('signup_token', '')]))
|
||||
|
||||
create_form = SpaceCreateForm()
|
||||
join_form = SpaceJoinForm()
|
||||
|
||||
return render(request, 'no_space_info.html', {'create_form': create_form, 'join_form': join_form})
|
||||
|
||||
|
||||
def no_perm(request):
|
||||
@ -390,36 +418,53 @@ def signup(request, token):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first():
|
||||
if request.method == 'POST':
|
||||
updated_request = request.POST.copy()
|
||||
if link.username != '':
|
||||
updated_request.update({'name': link.username})
|
||||
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'))
|
||||
|
||||
form = UserCreateForm(updated_request)
|
||||
link.used_by = request.user
|
||||
link.save()
|
||||
request.user.groups.add(link.group)
|
||||
|
||||
if form.is_valid():
|
||||
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'], )
|
||||
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!'))
|
||||
request.user.userpreference.space = link.space
|
||||
request.user.userpreference.save()
|
||||
|
||||
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:
|
||||
form.add_error('password', m)
|
||||
messages.add_message(request, messages.SUCCESS, _('Successfully joined space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
else:
|
||||
form = UserCreateForm()
|
||||
request.session['signup_token'] = token
|
||||
|
||||
if request.method == 'POST':
|
||||
updated_request = request.POST.copy()
|
||||
if link.username != '':
|
||||
updated_request.update({'name': link.username})
|
||||
|
||||
form = UserCreateForm(updated_request)
|
||||
|
||||
if form.is_valid():
|
||||
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'], )
|
||||
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)
|
||||
|
||||
user.userpreference.space = link.space
|
||||
user.userpreference.save()
|
||||
return HttpResponseRedirect(reverse('account_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
|
||||
@ -430,6 +475,26 @@ def signup(request, token):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
|
||||
@group_required('admin')
|
||||
def space(request):
|
||||
space_users = UserPreference.objects.filter(space=request.space).all()
|
||||
|
||||
counts = Object()
|
||||
counts.recipes = Recipe.objects.filter(space=request.space).count()
|
||||
counts.keywords = Keyword.objects.filter(space=request.space).count()
|
||||
counts.recipe_import = RecipeImport.objects.filter(space=request.space).count()
|
||||
counts.units = Unit.objects.filter(space=request.space).count()
|
||||
counts.ingredients = Food.objects.filter(space=request.space).count()
|
||||
counts.comments = Comment.objects.filter(recipe__space=request.space).count()
|
||||
|
||||
counts.recipes_internal = Recipe.objects.filter(internal=True, space=request.space).count()
|
||||
counts.recipes_external = counts.recipes - counts.recipes_internal
|
||||
|
||||
counts.recipes_no_keyword = Recipe.objects.filter(keywords=None, space=request.space).count()
|
||||
|
||||
return render(request, 'space.html', {'space_users': space_users, 'counts': counts})
|
||||
|
||||
|
||||
def markdown_info(request):
|
||||
return render(request, 'markdown_info.html', {})
|
||||
|
||||
|
@ -330,4 +330,5 @@ EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
|
||||
EMAIL_USE_TLS = bool(int(os.getenv('EMAIL_USE_TLS', False)))
|
||||
EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False)))
|
||||
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv('ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
|
||||
|
Loading…
Reference in New Issue
Block a user