forked from CGM_Public/pretix_original
Added team invitations
This commit is contained in:
34
src/pretix/base/migrations/0054_auto_20170107_1058.py
Normal file
34
src/pretix/base/migrations/0054_auto_20170107_1058.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.4 on 2017-01-07 10:58
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import pretix.base.models.event
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0053_auto_20170104_1252'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventpermission',
|
||||||
|
name='invite_email',
|
||||||
|
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventpermission',
|
||||||
|
name='invite_token',
|
||||||
|
field=models.CharField(blank=True, default=pretix.base.models.event.generate_invite_token, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventpermission',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='event_perms', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,6 +3,7 @@ from .base import CachedFile, LoggedModel, cachedfile_name
|
|||||||
from .checkin import Checkin
|
from .checkin import Checkin
|
||||||
from .event import (
|
from .event import (
|
||||||
Event, EventLock, EventPermission, EventSetting, RequiredAction,
|
Event, EventLock, EventPermission, EventSetting, RequiredAction,
|
||||||
|
generate_invite_token,
|
||||||
)
|
)
|
||||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||||
from .items import (
|
from .items import (
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import string
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import date, datetime, time
|
from datetime import date, datetime, time
|
||||||
|
|
||||||
@@ -294,6 +295,10 @@ class Event(LoggedModel):
|
|||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_invite_token():
|
||||||
|
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||||
|
|
||||||
|
|
||||||
class EventPermission(models.Model):
|
class EventPermission(models.Model):
|
||||||
"""
|
"""
|
||||||
The relation between an Event and a User who has permissions to
|
The relation between an Event and a User who has permissions to
|
||||||
@@ -314,7 +319,9 @@ class EventPermission(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
event = models.ForeignKey(Event, related_name="user_perms", on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, related_name="user_perms", on_delete=models.CASCADE)
|
||||||
user = models.ForeignKey(User, related_name="event_perms", on_delete=models.CASCADE)
|
user = models.ForeignKey(User, related_name="event_perms", on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
invite_email = models.EmailField(null=True, blank=True)
|
||||||
|
invite_token = models.CharField(default=generate_invite_token, max_length=64, null=True, blank=True)
|
||||||
can_change_settings = models.BooleanField(
|
can_change_settings = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
verbose_name=_("Can change event settings")
|
verbose_name=_("Can change event settings")
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
|||||||
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
|
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
|
||||||
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
|
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
|
||||||
'pretix.event.question.option.changed': _('An answer option has been changed.'),
|
'pretix.event.question.option.changed': _('An answer option has been changed.'),
|
||||||
|
'pretix.event.permissions.added': _('A user has been added to the event team.'),
|
||||||
|
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
|
||||||
|
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),
|
||||||
|
'pretix.event.permissions.deleted': _('A user has been removed from the event team.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
data = json.loads(logentry.data)
|
data = json.loads(logentry.data)
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class PermissionMiddleware(MiddlewareMixin):
|
|||||||
"auth.login.2fa",
|
"auth.login.2fa",
|
||||||
"auth.register",
|
"auth.register",
|
||||||
"auth.forgot",
|
"auth.forgot",
|
||||||
"auth.forgot.recover"
|
"auth.forgot.recover",
|
||||||
|
"auth.invite",
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
|
|||||||
31
src/pretix/control/templates/pretixcontrol/auth/invite.html
Normal file
31
src/pretix/control/templates/pretixcontrol/auth/invite.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends "pretixcontrol/auth/base.html" %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<form class="form-signin" action="" method="post">
|
||||||
|
<h3>{% trans "Accept an invitation" %}</h3>
|
||||||
|
<p>
|
||||||
|
{% url "control:auth.login" as loginurl %}
|
||||||
|
{% blocktrans trimmed with login_href='href="'|add:loginurl|add:'"'|safe %}
|
||||||
|
If you already have an account on this site with a different email address, you can
|
||||||
|
<a {{ login_href }}>log in</a> first and then click this link again to accept the
|
||||||
|
invitation with your existing account.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_field form.email %}
|
||||||
|
{% bootstrap_field form.password %}
|
||||||
|
{% bootstrap_field form.password_repeat %}
|
||||||
|
<div class="form-group buttons">
|
||||||
|
<a href="{% url "control:auth.login" %}" class="btn btn-link">
|
||||||
|
« {% trans "Login" %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{% trans "Register" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
|
||||||
|
|
||||||
|
you have been invited to the team of an event that uses pretix for their
|
||||||
|
ticket sales.
|
||||||
|
|
||||||
|
Event: {{ event }}
|
||||||
|
|
||||||
|
If you want to join that team, just click on the following link:
|
||||||
|
{{ url }}
|
||||||
|
|
||||||
|
If you do not want to join, you can safely ignore or delete this email.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
|
||||||
|
Your pretix team
|
||||||
|
{% endblocktrans %}
|
||||||
@@ -26,7 +26,16 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ form.id }}{{ form.instance.user }}</td>
|
<td>
|
||||||
|
{{ form.id }}
|
||||||
|
{% if form.instance.user %}
|
||||||
|
{{ form.instance.user }}
|
||||||
|
{% else %}
|
||||||
|
{{ form.instance.invite_email }}
|
||||||
|
<span class="fa fa-envelope-o" data-toggle="tooltip"
|
||||||
|
title="{% trans "invited, pending response" %}"></span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ form.can_change_settings }}</td>
|
<td>{{ form.can_change_settings }}</td>
|
||||||
<td>{{ form.can_change_items }}</td>
|
<td>{{ form.can_change_items }}</td>
|
||||||
<td>{{ form.can_view_orders }}</td>
|
<td>{{ form.can_view_orders }}</td>
|
||||||
@@ -37,6 +46,19 @@
|
|||||||
<td>{{ form.DELETE }}</td>
|
<td>{{ form.DELETE }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="9">
|
||||||
|
<strong>{% trans "Adding a new user" %}</strong><br>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
To add a new user, you can enter their email address here. If they already have a
|
||||||
|
pretix account, they will immediately be added to the event. Otherwise, they will
|
||||||
|
be sent an email with an invitation.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
@@ -53,7 +75,7 @@
|
|||||||
<td>{{ add_form.can_change_vouchers }}</td>
|
<td>{{ add_form.can_change_vouchers }}</td>
|
||||||
<td>{{ add_form.can_view_vouchers }}</td>
|
<td>{{ add_form.can_view_vouchers }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ urlpatterns = [
|
|||||||
url(r'^login$', auth.login, name='auth.login'),
|
url(r'^login$', auth.login, name='auth.login'),
|
||||||
url(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'),
|
url(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'),
|
||||||
url(r'^register$', auth.register, name='auth.register'),
|
url(r'^register$', auth.register, name='auth.register'),
|
||||||
|
url(r'^invite/(?P<token>[a-zA-Z0-9]+)$', auth.invite, name='auth.invite'),
|
||||||
url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
|
url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
|
||||||
url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
|
url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
|
||||||
url(r'^$', dashboards.user_index, name='index'),
|
url(r'^$', dashboards.user_index, name='index'),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from u2flib_server.utils import rand_bytes
|
|||||||
from pretix.base.forms.auth import (
|
from pretix.base.forms.auth import (
|
||||||
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
|
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
|
||||||
)
|
)
|
||||||
from pretix.base.models import U2FDevice, User
|
from pretix.base.models import EventPermission, U2FDevice, User
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import SendMailException, mail
|
||||||
from pretix.helpers.urls import build_absolute_uri
|
from pretix.helpers.urls import build_absolute_uri
|
||||||
|
|
||||||
@@ -99,6 +99,60 @@ def register(request):
|
|||||||
return render(request, 'pretixcontrol/auth/register.html', ctx)
|
return render(request, 'pretixcontrol/auth/register.html', ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def invite(request, token):
|
||||||
|
"""
|
||||||
|
Registration form in case of an invite
|
||||||
|
"""
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
perm = EventPermission.objects.get(invite_token=token)
|
||||||
|
except EventPermission.DoesNotExist:
|
||||||
|
messages.error(request, _('You used an invalid link. Please copy the link from your email to the address bar '
|
||||||
|
'and make sure it is correct and that the link has not been used before.'))
|
||||||
|
return redirect('control:auth.login')
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
try:
|
||||||
|
EventPermission.objects.get(event=perm.event, user=request.user)
|
||||||
|
messages.error(request, _('You cannot accept the invitation for "{}" as you already are part of '
|
||||||
|
'that event\'s team.').format(perm.event.name))
|
||||||
|
return redirect('control:index')
|
||||||
|
except EventPermission.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
perm.invite_token = None
|
||||||
|
perm.invite_email = None
|
||||||
|
perm.user = request.user
|
||||||
|
perm.save()
|
||||||
|
messages.success(request, _('You have now access to "{}".').format(perm.event.name))
|
||||||
|
return redirect('control:index')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = RegistrationForm(data=request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
user = User.objects.create_user(
|
||||||
|
form.cleaned_data['email'], form.cleaned_data['password'],
|
||||||
|
locale=request.LANGUAGE_CODE,
|
||||||
|
timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE
|
||||||
|
)
|
||||||
|
user = authenticate(email=user.email, password=form.cleaned_data['password'])
|
||||||
|
user.log_action('pretix.control.auth.user.created', user=user)
|
||||||
|
auth_login(request, user)
|
||||||
|
request.session['pretix_auth_login_time'] = int(time.time())
|
||||||
|
|
||||||
|
perm.invite_token = None
|
||||||
|
perm.invite_email = None
|
||||||
|
perm.user = user
|
||||||
|
perm.save()
|
||||||
|
messages.success(request, _('Welcome to pretix! You have now access to "{}".').format(perm.event.name))
|
||||||
|
return redirect('control:index')
|
||||||
|
else:
|
||||||
|
form = RegistrationForm(initial={'email': perm.invite_email})
|
||||||
|
ctx['form'] = form
|
||||||
|
return render(request, 'pretixcontrol/auth/invite.html', ctx)
|
||||||
|
|
||||||
|
|
||||||
class Forgot(TemplateView):
|
class Forgot(TemplateView):
|
||||||
template_name = 'pretixcontrol/auth/forgot.html'
|
template_name = 'pretixcontrol/auth/forgot.html'
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.services import tickets
|
from pretix.base.services import tickets
|
||||||
from pretix.base.services.invoices import build_preview_invoice_pdf
|
from pretix.base.services.invoices import build_preview_invoice_pdf
|
||||||
|
from pretix.base.services.mail import SendMailException, mail
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import (
|
||||||
register_payment_providers, register_ticket_outputs,
|
register_payment_providers, register_ticket_outputs,
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,7 @@ from pretix.control.forms.event import (
|
|||||||
TicketSettingsForm,
|
TicketSettingsForm,
|
||||||
)
|
)
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
|
from pretix.helpers.urls import build_absolute_uri
|
||||||
from pretix.presale.style import regenerate_css
|
from pretix.presale.style import regenerate_css
|
||||||
|
|
||||||
from . import UpdateView
|
from . import UpdateView
|
||||||
@@ -544,27 +546,57 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
|
|||||||
ctx['add_form'] = self.add_form
|
ctx['add_form'] = self.add_form
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def _send_invite(self, instance):
|
||||||
|
try:
|
||||||
|
mail(
|
||||||
|
instance.invite_email,
|
||||||
|
_('Account information changed'),
|
||||||
|
'pretixcontrol/email/invitation.txt',
|
||||||
|
{
|
||||||
|
'user': self,
|
||||||
|
'event': self.request.event.name,
|
||||||
|
'url': build_absolute_uri('control:auth.invite', kwargs={
|
||||||
|
'token': instance.invite_token
|
||||||
|
})
|
||||||
|
},
|
||||||
|
event=None,
|
||||||
|
locale=self.request.LANGUAGE_CODE
|
||||||
|
)
|
||||||
|
except SendMailException:
|
||||||
|
pass # Already logged
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
if self.formset.is_valid() and self.add_form.is_valid():
|
if self.formset.is_valid() and self.add_form.is_valid():
|
||||||
if self.add_form.has_changed():
|
if self.add_form.has_changed():
|
||||||
|
logdata = {
|
||||||
|
k: v for k, v in self.add_form.cleaned_data.items()
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.add_form.instance.user = User.objects.get(email=self.add_form.cleaned_data['user'])
|
|
||||||
self.add_form.instance.user_id = self.add_form.instance.user.id
|
|
||||||
self.add_form.instance.event = self.request.event
|
self.add_form.instance.event = self.request.event
|
||||||
self.add_form.instance.event_id = self.request.event.id
|
self.add_form.instance.event_id = self.request.event.id
|
||||||
|
self.add_form.instance.user = User.objects.get(email=self.add_form.cleaned_data['user'])
|
||||||
|
self.add_form.instance.user_id = self.add_form.instance.user.id
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
messages.error(self.request, _('There is no user with the email address you entered.'))
|
self.add_form.instance.invite_email = self.add_form.cleaned_data['user']
|
||||||
return self.get(*args, **kwargs)
|
if EventPermission.objects.filter(invite_email=self.add_form.instance.invite_email,
|
||||||
|
event=self.request.event).exists():
|
||||||
|
messages.error(self.request, _('This user already has been invited for this event.'))
|
||||||
|
return self.get(*args, **kwargs)
|
||||||
|
|
||||||
|
self.add_form.save()
|
||||||
|
self._send_invite(self.add_form.instance)
|
||||||
|
|
||||||
|
self.request.event.log_action(
|
||||||
|
'pretix.event.permissions.invited', user=self.request.user, data=logdata
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if EventPermission.objects.filter(user=self.add_form.instance.user,
|
if EventPermission.objects.filter(user=self.add_form.instance.user,
|
||||||
event=self.request.event).exists():
|
event=self.request.event).exists():
|
||||||
messages.error(self.request, _('This user already has permissions for this event.'))
|
messages.error(self.request, _('This user already has permissions for this event.'))
|
||||||
return self.get(*args, **kwargs)
|
return self.get(*args, **kwargs)
|
||||||
self.add_form.save()
|
self.add_form.save()
|
||||||
logdata = {
|
|
||||||
k: v for k, v in self.add_form.cleaned_data.items()
|
|
||||||
}
|
|
||||||
logdata['user'] = self.add_form.instance.user_id
|
logdata['user'] = self.add_form.instance.user_id
|
||||||
self.request.event.log_action(
|
self.request.event.log_action(
|
||||||
'pretix.event.permissions.added', user=self.request.user, data=logdata
|
'pretix.event.permissions.added', user=self.request.user, data=logdata
|
||||||
@@ -583,6 +615,14 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
|
|||||||
messages.error(self.request, _('You cannot remove your own permission to view this page.'))
|
messages.error(self.request, _('You cannot remove your own permission to view this page.'))
|
||||||
return self.get(*args, **kwargs)
|
return self.get(*args, **kwargs)
|
||||||
|
|
||||||
|
for form in self.formset.deleted_forms:
|
||||||
|
logdata = {
|
||||||
|
k: v for k, v in form.cleaned_data.items()
|
||||||
|
}
|
||||||
|
self.request.event.log_action(
|
||||||
|
'pretix.event.permissions.deleted', user=self.request.user, data=logdata
|
||||||
|
)
|
||||||
|
|
||||||
self.formset.save()
|
self.formset.save()
|
||||||
messages.success(self.request, _('Your changes have been saved.'))
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|||||||
Reference in New Issue
Block a user