forked from CGM_Public/pretix_original
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2cd633248 | ||
|
|
0acee0e362 | ||
|
|
33265d05fb | ||
|
|
2182a4e361 | ||
|
|
2336505309 | ||
|
|
15b5e66da9 | ||
|
|
c7676cd17a | ||
|
|
e53562dda2 | ||
|
|
d134dcf6a9 | ||
|
|
981d82b0ee | ||
|
|
e75bce37bc | ||
|
|
ef432252f0 | ||
|
|
e9e743f312 | ||
|
|
0998814e69 | ||
|
|
d3f21353ca | ||
|
|
f6d8b825d5 | ||
|
|
4012658596 |
@@ -1,6 +1,9 @@
|
||||
pretix
|
||||
======
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pretix.svg
|
||||
:target: https://pypi.python.org/pypi/pretix
|
||||
|
||||
.. image:: https://readthedocs.org/projects/pretix/badge/?version=latest
|
||||
:target: https://docs.pretix.eu/en/latest/
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ fi
|
||||
|
||||
if [ "$1" == "taskworker" ]; then
|
||||
export C_FORCE_ROOT=True
|
||||
exec celery -A pretix worker -l info
|
||||
exec celery -A pretix.celery_app worker -l info
|
||||
fi
|
||||
|
||||
if [ "$1" == "shell" ]; then
|
||||
|
||||
@@ -235,6 +235,26 @@ Updates are fairly simple, but require at least a short downtime::
|
||||
|
||||
Restarting the service can take a few seconds, especially if the update requires changes to the database.
|
||||
|
||||
Install a plugin
|
||||
----------------
|
||||
|
||||
To install a plugin, you need to build your own docker image. To do so, create a new directory and place a file
|
||||
named ``Dockerfile`` in it. The Dockerfile could look like this (replace ``pretix-passbook`` with the plugins of your
|
||||
choice)::
|
||||
|
||||
FROM pretix/standalone
|
||||
USER root
|
||||
RUN pip3 install pretix-passbook
|
||||
USER pretixuser
|
||||
RUN make production
|
||||
|
||||
Then, go to that directory and build the image::
|
||||
|
||||
$ docker build -t mypretix
|
||||
|
||||
You can now use that image ``mypretix`` instead of ``pretix/standalone`` in your service file (see above). Be sure
|
||||
to re-build your custom image after you pulled ``pretix/standalone`` if you want to perform an update.
|
||||
|
||||
.. _Docker: https://docs.docker.com/engine/installation/linux/debian/
|
||||
.. _Postfix: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-16-04
|
||||
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
|
||||
|
||||
@@ -105,8 +105,8 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
|
||||
|
||||
See :ref:`email configuration <mail-settings>` to learn more about configuring mail features.
|
||||
|
||||
Install pretix from source
|
||||
--------------------------
|
||||
Install pretix from PyPI
|
||||
------------------------
|
||||
|
||||
Now we will install pretix itself. The following steps are to be executed as the ``pretix`` user. Before we
|
||||
actually install pretix, we will create a virtual environment to isolate the python packages from your global
|
||||
@@ -116,14 +116,10 @@ python installation::
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 install -U pip setuptools wheel
|
||||
|
||||
We now clone pretix and install its Python dependencies (replace ``mysql`` with ``postgres`` if you're running
|
||||
PostgreSQL)::
|
||||
We now install pretix, its direct dependencies and gunicorn. Replace ``mysql`` with ``postgres`` in the following
|
||||
command if you're running PostgreSQL::
|
||||
|
||||
(venv)$ git clone https://github.com/pretix/pretix.git /var/pretix/source
|
||||
(venv)$ cd /var/pretix/source/src
|
||||
(venv)$ pip3 install -r requirements.txt -r requirements/mysql.txt \
|
||||
-r requirements/redis.txt \
|
||||
-r requirements/py34.txt gunicorn
|
||||
(venv)$ pip install "pretix[mysql]" gunicorn
|
||||
|
||||
We also need to create a data directory::
|
||||
|
||||
@@ -131,8 +127,8 @@ We also need to create a data directory::
|
||||
|
||||
Finally, we compile static files and translation data and create the database structure::
|
||||
|
||||
(venv)$ make production
|
||||
(venv)$ python manage.py migrate
|
||||
(venv)$ python -m pretix rebuild
|
||||
(venv)$ python -m pretix migrate
|
||||
|
||||
|
||||
Start pretix as a service
|
||||
@@ -171,7 +167,7 @@ For background tasks we need a second service ``/etc/systemd/system/pretix-worke
|
||||
Group=pretix
|
||||
Environment="VIRTUAL_ENV=/var/pretix/venv"
|
||||
Environment="PATH=/var/pretix/venv/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
ExecStart=/var/pretix/venv/bin/celery -A pretix worker -l info
|
||||
ExecStart=/var/pretix/venv/bin/celery -A pretix.celery_app worker -l info
|
||||
WorkingDirectory=/var/pretix/source/src
|
||||
Restart=on-failure
|
||||
|
||||
@@ -191,7 +187,7 @@ Cronjob
|
||||
You need to set up a cronjob that runs the management command ``runperiodic``. The exact interval is not important
|
||||
but should be something between every minute and every hour. You could for example configure cron like this::
|
||||
|
||||
15,45 * * * * export PATH=/var/pretix/venv/bin:$PATH && cd /var/pretix/source/src && ./manage.py runperiodic
|
||||
15,45 * * * * export PATH=/var/pretix/venv/bin:$PATH && cd /var/pretix/source/src && python -m pretix runperiodic
|
||||
|
||||
The cronjob should run as the ``pretix`` user (``crontab -e -u pretix``).
|
||||
|
||||
@@ -254,16 +250,27 @@ To upgrade to a new pretix release, pull the latest code changes and run the fol
|
||||
``mysql`` with ``postgres`` if necessary)::
|
||||
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ cd /var/pretix/source/src
|
||||
(venv)$ git pull origin master
|
||||
(venv)$ pip3 install -r requirements.txt -r requirements/mysql.txt \
|
||||
-r requirements/redis.txt \
|
||||
-r requirements/py34.txt gunicorn
|
||||
(venv)$ python manage.py migrate
|
||||
(venv)$ make production
|
||||
(venv)$ python manage.py updatestyles
|
||||
(venv)$ pip3 install -U pretix[mysql] gunicorn
|
||||
(venv)$ python -m pretix migrate
|
||||
(venv)$ python -m pretix rebuild
|
||||
(venv)$ python -m pretix updatestyles
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
|
||||
Install a plugin
|
||||
----------------
|
||||
|
||||
To install a plugin, just use ``pip``! Depending on the plugin, you should probably apply database migrations and
|
||||
rebuild the static files afterwards. Replace ``pretix-passbook`` with the plugin of your choice in the following
|
||||
example::
|
||||
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 install pretix-passbook
|
||||
(venv)$ python -m pretix migrate
|
||||
(venv)$ python -m pretix rebuild
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
|
||||
.. _Postfix: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-16-04
|
||||
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
|
||||
.. _Let's Encrypt: https://letsencrypt.org/
|
||||
|
||||
@@ -11,7 +11,7 @@ Core
|
||||
----
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: periodic_task
|
||||
:members: periodic_task, event_live_issues
|
||||
|
||||
Order events
|
||||
""""""""""""
|
||||
|
||||
@@ -6,7 +6,7 @@ localecompile:
|
||||
|
||||
localegen:
|
||||
./manage.py makemessages --all --ignore "pretix/helpers/*"
|
||||
./manage.py makemessages --all -d djangojs --ignore "pretix/helpers/*" --ignore "static/jsi18n/*"
|
||||
./manage.py makemessages --all -d djangojs --ignore "pretix/helpers/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static.dist/*" --ignore "build/*"
|
||||
|
||||
staticfiles: jsi18n
|
||||
./manage.py collectstatic --noinput
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.0.0b1"
|
||||
__version__ = "1.0.0b2"
|
||||
|
||||
@@ -18,3 +18,7 @@ class PretixBaseConfig(AppConfig):
|
||||
|
||||
|
||||
default_app_config = 'pretix.base.PretixBaseConfig'
|
||||
try:
|
||||
import pretix.celery_app as celery # NOQA
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -39,8 +39,7 @@ class UserSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'givenname',
|
||||
'familyname',
|
||||
'fullname',
|
||||
'locale',
|
||||
# 'timezone',
|
||||
'email'
|
||||
|
||||
15
src/pretix/base/management/commands/rebuild.py
Normal file
15
src/pretix/base/management/commands/rebuild.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from ...signals import periodic_task
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Rebuild static files and language files"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
periodic_task.send(self)
|
||||
call_command('compilemessages', verbosity=1, interactive=False)
|
||||
call_command('compilejsi18n', verbosity=1, interactive=False)
|
||||
call_command('collectstatic', verbosity=1, interactive=False)
|
||||
call_command('compress', verbosity=1, interactive=False)
|
||||
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),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-07 12:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0054_auto_20170107_1058'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organizerpermission',
|
||||
name='can_change_permissions',
|
||||
field=models.BooleanField(default=True, verbose_name='Can change permissions'),
|
||||
),
|
||||
]
|
||||
34
src/pretix/base/migrations/0056_auto_20170107_1251.py
Normal file
34
src/pretix/base/migrations/0056_auto_20170107_1251.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-07 12:51
|
||||
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.organizer
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0055_organizerpermission_can_change_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organizerpermission',
|
||||
name='invite_email',
|
||||
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizerpermission',
|
||||
name='invite_token',
|
||||
field=models.CharField(blank=True, default=pretix.base.models.organizer.generate_invite_token, max_length=64, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organizerpermission',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organizer_perms', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
41
src/pretix/base/migrations/0057_auto_20170107_1531.py
Normal file
41
src/pretix/base/migrations/0057_auto_20170107_1531.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-07 15:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import pretix.base.validators
|
||||
|
||||
|
||||
def merge_names(apps, schema_editor):
|
||||
User = apps.get_model('pretixbase', 'User')
|
||||
for u in User.objects.all():
|
||||
if u.givenname:
|
||||
if u.familyname:
|
||||
u.fullname = u.givenname + " " + u.familyname
|
||||
else:
|
||||
u.fullname = u.givenname
|
||||
elif u.familyname:
|
||||
u.fullname = u.familyname
|
||||
u.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0056_auto_20170107_1251'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='fullname',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Full name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organizer',
|
||||
name='slug',
|
||||
field=models.SlugField(help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'),
|
||||
),
|
||||
migrations.RunPython(merge_names, migrations.RunPython.noop)
|
||||
]
|
||||
23
src/pretix/base/migrations/0058_auto_20170107_1533.py
Normal file
23
src/pretix/base/migrations/0058_auto_20170107_1533.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-07 15:33
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0057_auto_20170107_1531'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='familyname',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='givenname',
|
||||
),
|
||||
]
|
||||
@@ -3,6 +3,7 @@ from .base import CachedFile, LoggedModel, cachedfile_name
|
||||
from .checkin import Checkin
|
||||
from .event import (
|
||||
Event, EventLock, EventPermission, EventSetting, RequiredAction,
|
||||
generate_invite_token,
|
||||
)
|
||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||
from .items import (
|
||||
|
||||
@@ -43,10 +43,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
:param email: The user's email address, used for identification.
|
||||
:type email: str
|
||||
:param givenname: The user's given name. May be empty or null.
|
||||
:type givenname: str
|
||||
:param familyname: The user's given name. May be empty or null.
|
||||
:type familyname: str
|
||||
:param fullname: The user's full name. May be empty or null.
|
||||
:type fullname: str
|
||||
:param is_active: Whether this user account is activated.
|
||||
:type is_active: bool
|
||||
:param is_staff: ``True`` for system operators.
|
||||
@@ -64,10 +62,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
|
||||
verbose_name=_('E-mail'))
|
||||
givenname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Given name'))
|
||||
familyname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Family name'))
|
||||
fullname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Full name'))
|
||||
is_active = models.BooleanField(default=True,
|
||||
verbose_name=_('Is active'))
|
||||
is_staff = models.BooleanField(default=False,
|
||||
@@ -100,14 +96,13 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
"""
|
||||
Returns the first of the following user properties that is found to exist:
|
||||
|
||||
* Given name
|
||||
* Family name
|
||||
* Full name
|
||||
* Email address
|
||||
|
||||
Only present for backwards compatibility
|
||||
"""
|
||||
if self.givenname:
|
||||
return self.givenname
|
||||
elif self.familyname:
|
||||
return self.familyname
|
||||
if self.fullname:
|
||||
return self.fullname
|
||||
else:
|
||||
return self.email
|
||||
|
||||
@@ -115,20 +110,11 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
"""
|
||||
Returns the first of the following user properties that is found to exist:
|
||||
|
||||
* A combination of given name and family name, depending on the locale
|
||||
* Given name
|
||||
* Family name
|
||||
* User name
|
||||
* Full name
|
||||
* Email address
|
||||
"""
|
||||
if self.givenname and not self.familyname:
|
||||
return self.givenname
|
||||
elif not self.givenname and self.familyname:
|
||||
return self.familyname
|
||||
elif self.familyname and self.givenname:
|
||||
return _('%(family)s, %(given)s') % {
|
||||
'family': self.familyname,
|
||||
'given': self.givenname
|
||||
}
|
||||
if self.fullname:
|
||||
return self.fullname
|
||||
else:
|
||||
return self.email
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import string
|
||||
import uuid
|
||||
from datetime import date, datetime, time
|
||||
|
||||
@@ -294,6 +295,10 @@ class Event(LoggedModel):
|
||||
s.save()
|
||||
|
||||
|
||||
def generate_invite_token():
|
||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||
|
||||
|
||||
class EventPermission(models.Model):
|
||||
"""
|
||||
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)
|
||||
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(
|
||||
default=True,
|
||||
verbose_name=_("Can change event settings")
|
||||
|
||||
@@ -101,7 +101,7 @@ class LogEntry(models.Model):
|
||||
elif isinstance(co, ItemCategory):
|
||||
a_text = _('Category {val}')
|
||||
a_map = {
|
||||
'href': reverse('control:event.items.categories.show', kwargs={
|
||||
'href': reverse('control:event.items.categories.edit', kwargs={
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
'category': co.id
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import string
|
||||
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -38,7 +41,7 @@ class Organizer(LoggedModel):
|
||||
),
|
||||
OrganizerSlugBlacklistValidator()
|
||||
],
|
||||
verbose_name=_("Slug"),
|
||||
verbose_name=_("Short form"),
|
||||
)
|
||||
permitted = models.ManyToManyField(User, through='OrganizerPermission',
|
||||
related_name="organizers")
|
||||
@@ -76,6 +79,10 @@ class Organizer(LoggedModel):
|
||||
return ObjectRelatedCache(self)
|
||||
|
||||
|
||||
def generate_invite_token():
|
||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||
|
||||
|
||||
class OrganizerPermission(models.Model):
|
||||
"""
|
||||
The relation between an Organizer and a User who has permissions to
|
||||
@@ -91,11 +98,17 @@ class OrganizerPermission(models.Model):
|
||||
"""
|
||||
|
||||
organizer = models.ForeignKey(Organizer, related_name="user_perms", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, related_name="organizer_perms")
|
||||
user = models.ForeignKey(User, related_name="organizer_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_create_events = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Can create events"),
|
||||
)
|
||||
can_change_permissions = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Can change permissions"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Organizer permission")
|
||||
|
||||
@@ -52,6 +52,18 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
return responses
|
||||
|
||||
|
||||
event_live_issues = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
"""
|
||||
This signal is sent out to determine whether an event can be taken live. If you want to
|
||||
prevent the event from going live, return a string that will be displayed to the user
|
||||
as the error message. If you don't, your receiver should return ``None``.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
|
||||
register_payment_providers = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
|
||||
@@ -109,6 +109,14 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.plugins.disabled': _('A plugin has been disabled.'),
|
||||
'pretix.event.live.activated': _('The shop has been taken live.'),
|
||||
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
||||
'pretix.event.changed': _('The event settings have been changed.'),
|
||||
'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.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)
|
||||
|
||||
@@ -9,7 +9,9 @@ from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from pretix.base.models import Event, EventPermission, Organizer
|
||||
from pretix.base.models import (
|
||||
Event, EventPermission, Organizer, OrganizerPermission,
|
||||
)
|
||||
|
||||
|
||||
class PermissionMiddleware(MiddlewareMixin):
|
||||
@@ -24,7 +26,8 @@ class PermissionMiddleware(MiddlewareMixin):
|
||||
"auth.login.2fa",
|
||||
"auth.register",
|
||||
"auth.forgot",
|
||||
"auth.forgot.recover"
|
||||
"auth.forgot.recover",
|
||||
"auth.invite",
|
||||
)
|
||||
|
||||
def process_request(self, request):
|
||||
@@ -81,6 +84,10 @@ class PermissionMiddleware(MiddlewareMixin):
|
||||
slug=url.kwargs['organizer'],
|
||||
permitted__id__exact=request.user.id,
|
||||
)[0]
|
||||
request.orgaperm = OrganizerPermission.objects.get(
|
||||
organizer=request.organizer,
|
||||
user=request.user
|
||||
)
|
||||
except IndexError:
|
||||
raise Http404(_("The selected organizer was not found or you "
|
||||
"have no permission to administrate it."))
|
||||
|
||||
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 %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
|
||||
|
||||
you have been invited to the team of an event organizer that uses pretix
|
||||
for their ticket sales.
|
||||
|
||||
Organizer: {{ organizer }}
|
||||
|
||||
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 %}
|
||||
@@ -3,12 +3,21 @@
|
||||
{% load eventurl %}
|
||||
{% block title %}{{ request.event.name }}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ request.event.name }}
|
||||
<a href="{% eventurl request.event "presale:event.index" %}" class="btn btn-default btn-sm" target="_blank">
|
||||
{% trans "Go to shop" %}
|
||||
</a>
|
||||
</h1>
|
||||
<form action="{% eventurl request.event "presale:event.auth" %}" method="post" target="_blank">
|
||||
<h1>
|
||||
{{ request.event.name }}
|
||||
{% if has_domain and not request.event.live %}
|
||||
<input type="hidden" value="{{ new_session }}" name="session">
|
||||
<button type="submit" class="btn btn-default btn-sm">
|
||||
{% trans "Go to shop" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% eventurl request.event "presale:event.index" %}" class="btn btn-default btn-sm" target="_blank">
|
||||
{% trans "Go to shop" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</h1>
|
||||
</form>
|
||||
|
||||
{% if actions|length > 0 %}
|
||||
<div class="panel panel-danger">
|
||||
|
||||
@@ -26,7 +26,16 @@
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
<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_items }}</td>
|
||||
<td>{{ form.can_view_orders }}</td>
|
||||
@@ -37,6 +46,19 @@
|
||||
<td>{{ form.DELETE }}</td>
|
||||
</tr>
|
||||
{% 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>
|
||||
<td>
|
||||
<div class="row-fluid">
|
||||
@@ -53,7 +75,7 @@
|
||||
<td>{{ add_form.can_change_vouchers }}</td>
|
||||
<td>{{ add_form.can_view_vouchers }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -6,22 +6,33 @@
|
||||
<h1>{% trans "Create a new event" %} <small>{% blocktrans trimmed with step=wizard.steps.step1 %}
|
||||
Step {{ step }}
|
||||
{% endblocktrans %}</small></h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% block form %}
|
||||
{% endblock %}
|
||||
<div class="form-group submit-group">
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}"
|
||||
class="btn btn-default btn-lg pull-left">
|
||||
{% trans "Back" %}
|
||||
{% if has_organizer %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% block form %}
|
||||
{% endblock %}
|
||||
<div class="form-group submit-group">
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}"
|
||||
class="btn btn-default btn-lg pull-left">
|
||||
{% trans "Back" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "Every event needs to be created as part of an organizer account. Currently, you do not have access to any organizer accounts." %}
|
||||
</div>
|
||||
</form>
|
||||
{% if request.user.is_superuser %}
|
||||
<a href="{% url "control:organizers.add" %}" class="btn btn-default">
|
||||
{% trans "Create a new organizer" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,19 +3,131 @@
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Organizer" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.slug layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
<h1>
|
||||
{% blocktrans with name=organizer.name %}Organizer: {{ name }}{% endblocktrans %}
|
||||
<a href="{% url "control:organizer.edit" organizer=organizer.slug %}"
|
||||
class="btn btn-default">
|
||||
<span class="fa fa-edit"></span>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
</h1>
|
||||
<div class="row">
|
||||
<div class="{% if request.orgaperm.can_change_permissions %}col-lg-6{% endif %} col-xs-12">
|
||||
<fieldset>
|
||||
<legend>{% trans "Events" %}</legend>
|
||||
{% if events|length == 0 %}
|
||||
<p>
|
||||
<em>{% trans "You currently do not have access to any events." %}</em>
|
||||
</p>
|
||||
{% else %}
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Event name" %}</th>
|
||||
<th>{% trans "Start date" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in events %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong><a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
|
||||
</td>
|
||||
<td>{{ e.get_date_from_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<a href="{% url "control:events.add" %}" class="btn btn-default">
|
||||
<span class="fa fa-plus"></span>
|
||||
{% trans "Create a new event" %}
|
||||
</a>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if request.orgaperm.can_change_permissions %}
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
<form action="" method="post" class="form-horizontal form-permissions">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Team" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can use the following list to control who can create new events in the name of this
|
||||
organizer and who can add more people to this list. This does <strong>not</strong>
|
||||
control who has access to a particular event. You can control the access to an
|
||||
event in the "Permissions" section of the event's settings. A user does not need to be
|
||||
on the list here to get access to an event.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% bootstrap_formset_errors formset %}
|
||||
{{ formset.management_form }}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Create events" %}</th>
|
||||
<th>{% trans "Change permissions" %}</th>
|
||||
<th>{% trans "Delete" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
<tr>
|
||||
<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_create_events }}</td>
|
||||
<td>{{ form.can_change_permissions }}</td>
|
||||
<td>{{ form.DELETE }}</td>
|
||||
</tr>
|
||||
{% 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 team. Otherwise, they will
|
||||
be sent an email with an invitation.
|
||||
{% endblocktrans %}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{% bootstrap_field add_form.user layout='inline' %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ add_form.can_create_events }}</td>
|
||||
<td>{{ add_form.can_change_permissions }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Organizer" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.slug layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -20,7 +20,7 @@
|
||||
{% for o in organizers %}
|
||||
<tr>
|
||||
<td><strong>
|
||||
<a href="{% url "control:organizer.edit" organizer=o.slug %}">{{ o.name }}</a>
|
||||
<a href="{% url "control:organizer" organizer=o.slug %}">{{ o.name }}</a>
|
||||
</strong></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General settings" %}</legend>
|
||||
{% bootstrap_field form.givenname layout='horizontal' %}
|
||||
{% bootstrap_field form.familyname layout='horizontal' %}
|
||||
{% bootstrap_field form.fullname layout='horizontal' %}
|
||||
{% bootstrap_field form.locale layout='horizontal' %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -10,6 +10,7 @@ urlpatterns = [
|
||||
url(r'^login$', auth.login, name='auth.login'),
|
||||
url(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'),
|
||||
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/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
|
||||
url(r'^$', dashboards.user_index, name='index'),
|
||||
@@ -31,6 +32,7 @@ urlpatterns = [
|
||||
name='user.settings.2fa.delete'),
|
||||
url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
|
||||
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
|
||||
url(r'^events/$', main.EventList.as_view(), name='events'),
|
||||
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
|
||||
|
||||
@@ -23,7 +23,9 @@ from u2flib_server.utils import rand_bytes
|
||||
from pretix.base.forms.auth import (
|
||||
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
|
||||
)
|
||||
from pretix.base.models import U2FDevice, User
|
||||
from pretix.base.models import (
|
||||
EventPermission, OrganizerPermission, U2FDevice, User,
|
||||
)
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
|
||||
@@ -99,6 +101,68 @@ def register(request):
|
||||
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)
|
||||
desc = perm.event.name
|
||||
except EventPermission.DoesNotExist:
|
||||
try:
|
||||
perm = OrganizerPermission.objects.get(invite_token=token)
|
||||
desc = perm.organizer.name
|
||||
except OrganizerPermission.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:
|
||||
if isinstance(perm, EventPermission):
|
||||
EventPermission.objects.get(event=perm.event, user=request.user)
|
||||
else:
|
||||
OrganizerPermission.objects.get(organizer=perm.organizer, user=request.user)
|
||||
messages.error(request, _('You cannot accept the invitation for "{}" as you already are part of '
|
||||
'this team.').format(desc))
|
||||
return redirect('control:index')
|
||||
except (EventPermission.DoesNotExist, OrganizerPermission.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(desc))
|
||||
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(desc))
|
||||
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):
|
||||
template_name = 'pretixcontrol/auth/forgot.html'
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from decimal import Decimal
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Sum
|
||||
@@ -17,6 +19,7 @@ from pretix.control.signals import (
|
||||
|
||||
from ..logdisplay import OVERVIEW_BLACKLIST
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
|
||||
|
||||
|
||||
@@ -167,11 +170,22 @@ def event_index(request, organizer, event):
|
||||
|
||||
a_qs = request.event.requiredaction_set.filter(done=False)
|
||||
|
||||
has_domain = request.event.organizer.domains.exists()
|
||||
|
||||
ctx = {
|
||||
'widgets': rearrange(widgets),
|
||||
'logs': qs[:5],
|
||||
'actions': a_qs[:5] if request.eventperm.can_change_orders else []
|
||||
'actions': a_qs[:5] if request.eventperm.can_change_orders else [],
|
||||
'has_domain': has_domain
|
||||
}
|
||||
|
||||
if not request.event.live and has_domain:
|
||||
s = SessionStore()
|
||||
s['pretix_event_access_{}'.format(request.event.pk)] = request.session.session_key
|
||||
s.create()
|
||||
ctx['new_session'] = s.session_key
|
||||
request.session['event_access'] = True
|
||||
|
||||
for a in ctx['actions']:
|
||||
a.display = a.display(request)
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import build_preview_invoice_pdf
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.signals import (
|
||||
register_payment_providers, register_ticket_outputs,
|
||||
event_live_issues, register_payment_providers, register_ticket_outputs,
|
||||
)
|
||||
from pretix.control.forms.event import (
|
||||
DisplaySettingsForm, EventSettingsForm, EventUpdateForm,
|
||||
@@ -31,6 +32,7 @@ from pretix.control.forms.event import (
|
||||
TicketSettingsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.presale.style import regenerate_css
|
||||
|
||||
from . import UpdateView
|
||||
@@ -544,27 +546,57 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
|
||||
ctx['add_form'] = self.add_form
|
||||
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
|
||||
def post(self, *args, **kwargs):
|
||||
if self.formset.is_valid() and self.add_form.is_valid():
|
||||
if self.add_form.has_changed():
|
||||
logdata = {
|
||||
k: v for k, v in self.add_form.cleaned_data.items()
|
||||
}
|
||||
|
||||
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_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:
|
||||
messages.error(self.request, _('There is no user with the email address you entered.'))
|
||||
return self.get(*args, **kwargs)
|
||||
self.add_form.instance.invite_email = self.add_form.cleaned_data['user']
|
||||
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:
|
||||
if EventPermission.objects.filter(user=self.add_form.instance.user,
|
||||
event=self.request.event).exists():
|
||||
messages.error(self.request, _('This user already has permissions for this event.'))
|
||||
return self.get(*args, **kwargs)
|
||||
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
|
||||
self.request.event.log_action(
|
||||
'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.'))
|
||||
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()
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return redirect(self.get_success_url())
|
||||
@@ -628,6 +668,11 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
|
||||
if not self.request.event.quotas.exists():
|
||||
issues.append(_('You need to configure at least one quota to sell anything.'))
|
||||
|
||||
responses = event_live_issues.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
if response:
|
||||
issues.append(response)
|
||||
|
||||
return issues
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import ListView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from pretix.base.models import Event, EventPermission
|
||||
from pretix.base.models import Event, EventPermission, OrganizerPermission
|
||||
from pretix.control.forms.event import (
|
||||
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
|
||||
)
|
||||
@@ -48,6 +48,12 @@ class EventWizard(SessionWizardView):
|
||||
'copy': condition_copy
|
||||
}
|
||||
|
||||
def get_context_data(self, form, **kwargs):
|
||||
ctx = super().get_context_data(form, **kwargs)
|
||||
ctx['has_organizer'] = OrganizerPermission.objects.filter(user=self.request.user,
|
||||
can_create_events=True).exists()
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self, step=None):
|
||||
kwargs = {
|
||||
'user': self.request.user
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.forms import modelformset_factory
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import CreateView, ListView, UpdateView
|
||||
from django.views.generic import CreateView, DetailView, ListView, UpdateView
|
||||
|
||||
from pretix.base.models import Organizer, OrganizerPermission
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models import Organizer, OrganizerPermission, User
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.control.forms.organizer import OrganizerForm, OrganizerUpdateForm
|
||||
from pretix.control.permissions import OrganizerPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
|
||||
|
||||
class OrganizerList(ListView):
|
||||
@@ -24,10 +32,147 @@ class OrganizerList(ListView):
|
||||
)
|
||||
|
||||
|
||||
class OrganizerPermissionForm(I18nModelForm):
|
||||
class Meta:
|
||||
model = OrganizerPermission
|
||||
fields = (
|
||||
'can_create_events', 'can_change_permissions'
|
||||
)
|
||||
|
||||
|
||||
class OrganizerPermissionCreateForm(OrganizerPermissionForm):
|
||||
user = forms.EmailField(required=False, label=_('User'))
|
||||
|
||||
|
||||
class OrganizerDetail(OrganizerPermissionRequiredMixin, DetailView):
|
||||
model = Organizer
|
||||
template_name = 'pretixcontrol/organizers/detail.html'
|
||||
permission = None
|
||||
context_object_name = 'organizer'
|
||||
|
||||
def get_object(self, queryset=None) -> Organizer:
|
||||
return self.request.organizer
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
fs = modelformset_factory(
|
||||
OrganizerPermission,
|
||||
form=OrganizerPermissionForm,
|
||||
can_delete=True, can_order=False, extra=0
|
||||
)
|
||||
return fs(data=self.request.POST if self.request.method == "POST" else None,
|
||||
prefix="formset",
|
||||
queryset=OrganizerPermission.objects.filter(organizer=self.request.organizer))
|
||||
|
||||
@cached_property
|
||||
def add_form(self):
|
||||
return OrganizerPermissionCreateForm(data=self.request.POST if self.request.method == "POST" else None,
|
||||
prefix="add")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['formset'] = self.formset
|
||||
ctx['add_form'] = self.add_form
|
||||
ctx['events'] = self.request.organizer.events.all()
|
||||
return ctx
|
||||
|
||||
def _send_invite(self, instance):
|
||||
try:
|
||||
mail(
|
||||
instance.invite_email,
|
||||
_('Account information changed'),
|
||||
'pretixcontrol/email/invitation_organizer.txt',
|
||||
{
|
||||
'user': self,
|
||||
'organizer': self.request.organizer.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
|
||||
def post(self, *args, **kwargs):
|
||||
if not self.request.orgaperm.can_change_permissions:
|
||||
raise PermissionDenied(_("You have no permission to do this."))
|
||||
|
||||
if self.formset.is_valid() and self.add_form.is_valid():
|
||||
if self.add_form.has_changed():
|
||||
logdata = {
|
||||
k: v for k, v in self.add_form.cleaned_data.items()
|
||||
}
|
||||
|
||||
try:
|
||||
self.add_form.instance.organizer = self.request.organizer
|
||||
self.add_form.instance.organizer_id = self.request.organizer.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:
|
||||
self.add_form.instance.invite_email = self.add_form.cleaned_data['user']
|
||||
if OrganizerPermission.objects.filter(invite_email=self.add_form.instance.invite_email,
|
||||
organizer=self.request.organizer).exists():
|
||||
messages.error(self.request, _('This user already has been invited for this team.'))
|
||||
return self.get(*args, **kwargs)
|
||||
|
||||
self.add_form.save()
|
||||
self._send_invite(self.add_form.instance)
|
||||
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.permissions.invited', user=self.request.user, data=logdata
|
||||
)
|
||||
else:
|
||||
if OrganizerPermission.objects.filter(user=self.add_form.instance.user,
|
||||
organizer=self.request.organizer).exists():
|
||||
messages.error(self.request, _('This user already has permissions for this team.'))
|
||||
return self.get(*args, **kwargs)
|
||||
self.add_form.save()
|
||||
logdata['user'] = self.add_form.instance.user_id
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.permissions.added', user=self.request.user, data=logdata
|
||||
)
|
||||
for form in self.formset.forms:
|
||||
if form.has_changed():
|
||||
changedata = {
|
||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||
}
|
||||
changedata['user'] = form.instance.user_id
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.permissions.changed', user=self.request.user, data=changedata
|
||||
)
|
||||
if form.instance.user_id == self.request.user.pk:
|
||||
if not form.cleaned_data['can_change_permissions'] or form in self.formset.deleted_forms:
|
||||
messages.error(self.request, _('You cannot remove your own permission to view this page.'))
|
||||
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.organizer.log_action(
|
||||
'pretix.organizer.permissions.deleted', user=self.request.user, data=logdata
|
||||
)
|
||||
|
||||
self.formset.save()
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
messages.error(self.request, _('Your changes could not be saved.'))
|
||||
return self.get(*args, **kwargs)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:organizer', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
|
||||
class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
model = Organizer
|
||||
form_class = OrganizerUpdateForm
|
||||
template_name = 'pretixcontrol/organizers/detail.html'
|
||||
template_name = 'pretixcontrol/organizers/edit.html'
|
||||
permission = None
|
||||
context_object_name = 'organizer'
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-03 23:46+0000\n"
|
||||
"POT-Creation-Date: 2017-01-07 12:08+0000\n"
|
||||
"PO-Revision-Date: 2017-01-01 20:40+0100\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -46,33 +46,34 @@ msgstr "Gesamtumsatz"
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr "Kontaktiere Stripe …"
|
||||
|
||||
#: static/pretixcontrol/js/clipboard.js:23
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: static/pretixcontrol/js/clipboard.js:29
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr "Drücken Sie Strg+C zum Kopieren!"
|
||||
|
||||
#: static/pretixcontrol/js/ui/main.js:28 static/pretixpresale/js/ui/main.js:99
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:28
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:99
|
||||
msgid "Close message"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: static/pretixcontrol/js/ui/main.js:43
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:43
|
||||
msgid "Unknown error."
|
||||
msgstr "Unbekannter Fehler."
|
||||
|
||||
#: static/pretixcontrol/js/ui/question.js:41
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:41
|
||||
msgid "Others"
|
||||
msgstr "Sonstige"
|
||||
|
||||
#: static/pretixcontrol/js/ui/question.js:70
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:70
|
||||
msgid "Count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:27
|
||||
#: static/pretixpresale/js/ui/asynctask.js:27
|
||||
#: static/pretixpresale/js/ui/asynctask.js:64
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:27
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:27
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:64
|
||||
msgid ""
|
||||
"Your request has been queued on the server and will now be processed. If "
|
||||
"this takes longer than two minutes, please contact us or go back in your "
|
||||
@@ -82,19 +83,19 @@ msgstr ""
|
||||
"dies länger als zwei Minuten dauert, kontaktieren Sie uns bitte oder gehen "
|
||||
"Sie in Ihrem Browser einen Schritt zurück und versuchen es erneut."
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:40
|
||||
#: static/pretixpresale/js/ui/asynctask.js:43
|
||||
#: static/pretixpresale/js/ui/asynctask.js:85
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:40
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:43
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:85
|
||||
msgid "An error of type {code} occured."
|
||||
msgstr "Ein Fehler ist aufgetreten. Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:53
|
||||
#: static/pretixpresale/js/ui/asynctask.js:103
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:53
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:103
|
||||
msgid "We are processing your request …"
|
||||
msgstr "Wir verarbeiten deine Anfrage …"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:54
|
||||
#: static/pretixpresale/js/ui/asynctask.js:104
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:54
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:104
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
@@ -104,7 +105,7 @@ msgstr ""
|
||||
"dauert, prüfen Sie bitte Ihre Internetverbindung. Danach können Sie diese "
|
||||
"Seite neu laden und es erneut versuchen."
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:46
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:46
|
||||
msgid ""
|
||||
"We currenctly cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
@@ -112,22 +113,22 @@ msgstr ""
|
||||
"Wir können den Server aktuell nicht erreichen, versuchen es aber weiter. "
|
||||
"Letzter Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:76
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:76
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:88
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:88
|
||||
msgid ""
|
||||
"We currenctly cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"Wir können den Server aktuell nicht erreichen. Bitte versuchen Sie es noch "
|
||||
"einmal. Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/cart.js:10
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:10
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr "Die Produkte in Ihrem Warenkorb sind nicht mehr für Sie reserviert."
|
||||
|
||||
#: static/pretixpresale/js/ui/cart.js:14
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:14
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-03 23:46+0000\n"
|
||||
"POT-Creation-Date: 2017-01-07 12:08+0000\n"
|
||||
"PO-Revision-Date: 2017-01-01 20:41+0100\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -46,33 +46,34 @@ msgstr "Gesamtumsatz"
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr "Kontaktiere Stripe …"
|
||||
|
||||
#: static/pretixcontrol/js/clipboard.js:23
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: static/pretixcontrol/js/clipboard.js:29
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr "Drücke Strg+C zum kopieren!"
|
||||
|
||||
#: static/pretixcontrol/js/ui/main.js:28 static/pretixpresale/js/ui/main.js:99
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:28
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:99
|
||||
msgid "Close message"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: static/pretixcontrol/js/ui/main.js:43
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:43
|
||||
msgid "Unknown error."
|
||||
msgstr "Unbekannter Fehler."
|
||||
|
||||
#: static/pretixcontrol/js/ui/question.js:41
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:41
|
||||
msgid "Others"
|
||||
msgstr "Sonstige"
|
||||
|
||||
#: static/pretixcontrol/js/ui/question.js:70
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:70
|
||||
msgid "Count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:27
|
||||
#: static/pretixpresale/js/ui/asynctask.js:27
|
||||
#: static/pretixpresale/js/ui/asynctask.js:64
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:27
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:27
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:64
|
||||
msgid ""
|
||||
"Your request has been queued on the server and will now be processed. If "
|
||||
"this takes longer than two minutes, please contact us or go back in your "
|
||||
@@ -82,19 +83,19 @@ msgstr ""
|
||||
"dies länger als zwei Minuten dauert, kontaktiere uns bitte oder gehe in "
|
||||
"deinem Browser einen Schritt zurück und versuche es erneut."
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:40
|
||||
#: static/pretixpresale/js/ui/asynctask.js:43
|
||||
#: static/pretixpresale/js/ui/asynctask.js:85
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:40
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:43
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:85
|
||||
msgid "An error of type {code} occured."
|
||||
msgstr "Ein Fehler ist aufgetreten. Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:53
|
||||
#: static/pretixpresale/js/ui/asynctask.js:103
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:53
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:103
|
||||
msgid "We are processing your request …"
|
||||
msgstr "Wir verarbeiten deine Anfrage …"
|
||||
|
||||
#: static/pretixpresale/js/ui/asyncdownload.js:54
|
||||
#: static/pretixpresale/js/ui/asynctask.js:104
|
||||
#: pretix/static/pretixpresale/js/ui/asyncdownload.js:54
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:104
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
@@ -104,7 +105,7 @@ msgstr ""
|
||||
"dauert, prüfe bitte deine Internetverbindung. Danach kannst du diese Seite "
|
||||
"neu laden und es erneut versuchen."
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:46
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:46
|
||||
msgid ""
|
||||
"We currenctly cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
@@ -112,22 +113,22 @@ msgstr ""
|
||||
"Wir können den Server aktuell nicht erreichen, versuchen es aber weiter. "
|
||||
"Letzter Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:76
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:76
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: static/pretixpresale/js/ui/asynctask.js:88
|
||||
#: pretix/static/pretixpresale/js/ui/asynctask.js:88
|
||||
msgid ""
|
||||
"We currenctly cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"Wir können den Server aktuell nicht erreichen. Bitte versuche es noch "
|
||||
"einmal. Fehlercode: {code}"
|
||||
|
||||
#: static/pretixpresale/js/ui/cart.js:10
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:10
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr "Die Produkte in deinem Warenkorb sind nicht mehr für dich reserviert."
|
||||
|
||||
#: static/pretixpresale/js/ui/cart.js:14
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:14
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
|
||||
@@ -51,6 +51,7 @@ event_patterns = [
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice/(?P<invoice>[0-9]+)$',
|
||||
pretix.presale.views.order.InvoiceDownload.as_view(),
|
||||
name='event.invoice.download'),
|
||||
url(r'^auth/$', pretix.presale.views.event.EventAuth.as_view(), name='event.auth'),
|
||||
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from importlib import import_module
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import resolve
|
||||
from django.http import Http404
|
||||
@@ -11,6 +13,8 @@ from pretix.base.models import Event, EventPermission, Organizer
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.presale.signals import process_request, process_response
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
def _detect_event(request, require_live=True):
|
||||
url = resolve(request.path_info)
|
||||
@@ -59,8 +63,24 @@ def _detect_event(request, require_live=True):
|
||||
LocaleMiddleware().process_request(request)
|
||||
|
||||
if require_live and not request.event.live:
|
||||
if not request.user.is_authenticated or not EventPermission.objects.filter(
|
||||
event=request.event, user=request.user).exists():
|
||||
can_access = (
|
||||
url.url_name == 'event.auth'
|
||||
or (
|
||||
request.user.is_authenticated
|
||||
and EventPermission.objects.filter(event=request.event, user=request.user).exists()
|
||||
)
|
||||
|
||||
)
|
||||
if not can_access and 'pretix_event_access_{}'.format(request.event.pk) in request.session:
|
||||
sparent = SessionStore(request.session.get('pretix_event_access_{}'.format(request.event.pk)))
|
||||
try:
|
||||
parentdata = sparent.load()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
can_access = 'event_access' in parentdata
|
||||
|
||||
if not can_access:
|
||||
raise PermissionDenied(_('The selected ticket shop is currently not available.'))
|
||||
|
||||
for receiver, response in process_request.send(request.event, request=request):
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import sys
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.base.models import ItemVariation
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
|
||||
from . import CartMixin, EventViewMixin
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
def item_group_by_category(items):
|
||||
return sorted(
|
||||
@@ -90,3 +101,32 @@ class EventIndex(EventViewMixin, CartMixin, TemplateView):
|
||||
context['cart'] = self.get_cart()
|
||||
context['frontpage_text'] = str(self.request.event.settings.frontpage_text)
|
||||
return context
|
||||
|
||||
|
||||
class EventAuth(View):
|
||||
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
s = SessionStore(request.POST.get('session'))
|
||||
|
||||
try:
|
||||
data = s.load()
|
||||
except:
|
||||
raise PermissionDenied(_('Please go back and try again.'))
|
||||
|
||||
parent = data.get('pretix_event_access_{}'.format(request.event.pk))
|
||||
|
||||
sparent = SessionStore(parent)
|
||||
try:
|
||||
parentdata = sparent.load()
|
||||
except:
|
||||
raise PermissionDenied(_('Please go back and try again.'))
|
||||
else:
|
||||
if 'event_access' not in parentdata:
|
||||
raise PermissionDenied(_('Please go back and try again.'))
|
||||
|
||||
request.session['pretix_event_access_{}'.format(request.event.pk)] = parent
|
||||
return redirect(eventreverse(request.event, 'presale:event.index'))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Django==1.10.*
|
||||
python-dateutil
|
||||
pytz
|
||||
django-bootstrap3==7.1.*
|
||||
django-bootstrap3==8.0.*
|
||||
django-formset-js-improved==0.5.0.1
|
||||
django-compressor==2.1
|
||||
reportlab==3.2.*
|
||||
|
||||
@@ -23,22 +23,12 @@ from pretix.base.services.orders import (
|
||||
class UserTestCase(TestCase):
|
||||
def test_name(self):
|
||||
u = User.objects.create_user('test@foo.bar', 'test')
|
||||
u.givenname = "Christopher"
|
||||
u.familyname = "Nolan"
|
||||
u.fullname = "Christopher Nolan"
|
||||
u.set_password("test")
|
||||
u.save()
|
||||
self.assertEqual(u.get_full_name(), 'Nolan, Christopher')
|
||||
self.assertEqual(u.get_short_name(), 'Christopher')
|
||||
u.givenname = None
|
||||
u.save()
|
||||
self.assertEqual(u.get_full_name(), 'Nolan')
|
||||
self.assertEqual(u.get_short_name(), 'Nolan')
|
||||
u.givenname = "Christopher"
|
||||
u.familyname = None
|
||||
u.save()
|
||||
self.assertEqual(u.get_full_name(), 'Christopher')
|
||||
self.assertEqual(u.get_short_name(), 'Christopher')
|
||||
u.givenname = None
|
||||
self.assertEqual(u.get_full_name(), 'Christopher Nolan')
|
||||
self.assertEqual(u.get_short_name(), 'Christopher Nolan')
|
||||
u.fullname = None
|
||||
u.save()
|
||||
self.assertEqual(u.get_full_name(), 'test@foo.bar')
|
||||
self.assertEqual(u.get_short_name(), 'test@foo.bar')
|
||||
|
||||
@@ -72,6 +72,7 @@ event_urls = [
|
||||
|
||||
organizer_urls = [
|
||||
'organizer/abc/edit',
|
||||
'organizer/abc/',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -26,13 +26,11 @@ class UserSettingsTest(SoupTest):
|
||||
|
||||
def test_set_name(self):
|
||||
doc = self.save({
|
||||
'givenname': 'Peter',
|
||||
'familyname': 'Miller'
|
||||
'fullname': 'Peter Miller',
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
assert self.user.givenname == 'Peter'
|
||||
assert self.user.familyname == 'Miller'
|
||||
assert self.user.fullname == 'Peter Miller'
|
||||
|
||||
def test_change_email_require_password(self):
|
||||
doc = self.save({
|
||||
|
||||
Reference in New Issue
Block a user