This commit is contained in:
Raphael Michel
2015-02-17 20:13:48 +01:00
125 changed files with 800 additions and 281 deletions

10
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "src/pretixbase/static/bootstrap"] [submodule "src/pretix/base/static/fontawesome"]
path = src/pretixbase/static/bootstrap path = src/pretix/base/static/fontawesome
url = https://github.com/twbs/bootstrap.git
[submodule "src/pretixbase/static/fontawesome"]
path = src/pretixbase/static/fontawesome
url = https://github.com/FortAwesome/Font-Awesome.git url = https://github.com/FortAwesome/Font-Awesome.git
[submodule "src/pretix/base/static/bootstrap"]
path = src/pretix/base/static/bootstrap
url = https://github.com/twbs/bootstrap.git

View File

@@ -23,23 +23,7 @@ on the next pages.
Creating a plugin Creating a plugin
----------------- -----------------
To create a new plugin, create a new python package as a subpackage to ``pretixplugins``. To create a new plugin, create a new python package.
In order to do so, you can place your module into pretix's :file:`pretixplugins` folder *or
anywhere else in your python import path* inside a folder called ``pretixplugins``.
.. IMPORTANT::
This makes use of a design pattern called `namespace packages`_ which is only
implicitly available as of Python 3.4. As we aim to support Python 3.2 for a bit
longer, you **MUST** put **EXACLTY** the following content into ``pretixplugins/__init__.py``
if you create a new ``pretixplugins`` folder somewhere in your path::
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
Otherwise it **will break** on Python 3.2 systems *depending on the python path's order*,
which is not tolerable behaviour. Also, even on Python 3.4 the test runner seems to have
problems without this workaround.
Inside your newly created folder, you'll probably need the three python modules ``__init__.py``, Inside your newly created folder, you'll probably need the three python modules ``__init__.py``,
``models.py`` and ``signals.py``, although this is up to you. You can take the following ``models.py`` and ``signals.py``, although this is up to you. You can take the following
@@ -48,14 +32,14 @@ example, taken from the time restriction module (see next chapter) as a template
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.plugins import PluginType from pretix.base.plugins import PluginType
class TimeRestrictionApp(AppConfig): class TimeRestrictionApp(AppConfig):
name = 'pretixplugins.timerestriction' name = 'pretix.plugins.timerestriction'
verbose_name = _("Time restriction") verbose_name = _("Time restriction")
class TixlPluginMeta: class PretixPluginMeta:
type = PluginType.RESTRICTION type = PluginType.RESTRICTION
name = _("Restriciton by time") name = _("Restriciton by time")
author = _("the pretix team") author = _("the pretix team")
@@ -67,10 +51,10 @@ example, taken from the time restriction module (see next chapter) as a template
def ready(self): def ready(self):
from . import signals # NOQA from . import signals # NOQA
default_app_config = 'pretixplugins.timerestriction.TimeRestrictionApp' default_app_config = 'pretix.plugins.timerestriction.TimeRestrictionApp'
.. IMPORTANT:: .. IMPORTANT::
You have to implement a ``TixlPluginMeta`` class like in the example to make your You have to implement a ``PretixPluginMeta`` class like in the example to make your
plugin available to the users. plugin available to the users.
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/ .. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/

View File

@@ -15,14 +15,14 @@ The restriction model
It is very likely that your new restriction plugin needs to store data. In order to do It is very likely that your new restriction plugin needs to store data. In order to do
so, it should define its own model with a name related to what your restriction does, so, it should define its own model with a name related to what your restriction does,
e.g. ``TimeRestriction``. This model should be a child class of ``pretixbase.models.BaseRestriction``. e.g. ``TimeRestriction``. This model should be a child class of ``pretix.base.models.BaseRestriction``.
You do not need to define custom fields, but you should create at least an empty model. You do not need to define custom fields, but you should create at least an empty model.
In our example, we put the following into :file:`pretixplugins/timerestriction/models.py`:: In our example, we put the following into :file:`pretix/plugins/timerestriction/models.py`::
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.models import BaseRestriction from pretix.base.models import BaseRestriction
class TimeRestriction(BaseRestriction): class TimeRestriction(BaseRestriction):
@@ -52,14 +52,14 @@ Availability determination
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the one signal *every* restriction plugin has to listen for, as your plugin does not This is the one signal *every* restriction plugin has to listen for, as your plugin does not
restrict anything without doing so. It is available as ``pretixbase.signals.determine_availability`` restrict anything without doing so. It is available as ``pretix.base.signals.determine_availability``
and is sent out every time some component of pretix wants to know whether a specific item or and is sent out every time some component of pretix wants to know whether a specific item or
variation is available for sell. variation is available for sell.
It is sent out with several keyword arguments: It is sent out with several keyword arguments:
``item`` ``item``
The instance of ``pretixbase.models.Item`` in question. The instance of ``pretix.base.models.Item`` in question.
``variations`` ``variations``
A list of dictionaries in the same format as ``Item.get_all_variations``: A list of dictionaries in the same format as ``Item.get_all_variations``:
The list contains one dictionary per variation, where the ``Property`` IDs are The list contains one dictionary per variation, where the ``Property`` IDs are
@@ -68,7 +68,7 @@ It is sent out with several keyword arguments:
the item does not have any properties, the list will contain exactly one empty the item does not have any properties, the list will contain exactly one empty
dictionary. Please note: this is *not* the list of all possible variations, this is dictionary. Please note: this is *not* the list of all possible variations, this is
only the list of all variations the frontend likes to determine the status for. only the list of all variations the frontend likes to determine the status for.
Technically, you won't get ``dict`` objects but ``pretixbase.types.VariationDict`` Technically, you won't get ``dict`` objects but ``pretix.base.types.VariationDict``
objects, which behave exactly the same but add some extra methods. objects, which behave exactly the same but add some extra methods.
``context`` ``context``
A yet-to-be-defined context object containing information about the user and the order A yet-to-be-defined context object containing information about the user and the order
@@ -103,7 +103,7 @@ In our example, the implementation could look like this::
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.timezone import now
from pretixbase.signals import determine_availability from pretix.base.signals import determine_availability
from .models import TimeRestriction from .models import TimeRestriction
@@ -217,12 +217,12 @@ Control interface formsets
To make it possible for the event organizer to configure your restriction, there is a To make it possible for the event organizer to configure your restriction, there is a
'Restrictions' page in the item configuration. This page is able to show a formset for 'Restrictions' page in the item configuration. This page is able to show a formset for
each restriction plugin, but *you* are required to create this formset. This is why you each restriction plugin, but *you* are required to create this formset. This is why you
should listen to the the ``pretixcontrol.signals.restriction_formset`` signal. should listen to the the ``pretix.control.signals.restriction_formset`` signal.
Currently, the signal comes with only one keyword argument: Currently, the signal comes with only one keyword argument:
``item`` ``item``
The instance of ``pretixbase.models.Item`` we want a formset for. The instance of ``pretix.base.models.Item`` we want a formset for.
You are expected to return a dict containing the following items: You are expected to return a dict containing the following items:
@@ -245,9 +245,9 @@ Our time restriction example looks like this::
from django.dispatch import receiver from django.dispatch import receiver
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from pretixcontrol.signals import restriction_formset from pretix.control.signals import restriction_formset
from pretixbase.models import Item from pretix.base.models import Item
from pretixcontrol.views.forms import ( from pretix.control.views.forms import (
VariationsField, RestrictionInlineFormset, RestrictionForm VariationsField, RestrictionInlineFormset, RestrictionForm
) )

View File

@@ -9,26 +9,26 @@ The components
The project pretix is split into several components. The main three of them are: The project pretix is split into several components. The main three of them are:
**pretixbase** **pretix.base**
Tixlbase is the foundation below all other components. It is primarily Pretixbase is the foundation below all other components. It is primarily
responsible for the data structures and database communication. It also hosts responsible for the data structures and database communication. It also hosts
several utilities which are used by multiple other components. several utilities which are used by multiple other components.
**pretixcontrol** **pretix.control**
Tixlcontrol is the web-based backend software which allows organizers to Pretixcontrol is the web-based backend software which allows organizers to
create and manage their events, items, orders and tickets. create and manage their events, items, orders and tickets.
**pretixpresale** **pretix.presale**
Tixlpresale is the ticket-shop itself, containing all the parts visible to the Pretixpresale is the ticket-shop itself, containing all the parts visible to the
end user. end user.
Users and events Users and events
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
Tixl is all about **events**, which are defined as something happening somewhere. Pretix is all about **events**, which are defined as something happening somewhere.
Every Event is managed by the **organizer**, an abstract entity running the event. Every Event is managed by the **organizer**, an abstract entity running the event.
Tixl is used by **users**. We want to enable global users who can just login into Pretix is used by **users**. We want to enable global users who can just login into
pretix and buy tickets for as many events as they like but at the same time it pretix and buy tickets for as many events as they like but at the same time it
should be possible to create some kind of local user to have a temporary account should be possible to create some kind of local user to have a temporary account
just to buy tickets for one single event. just to buy tickets for one single event.

View File

@@ -1,7 +1,7 @@
Development goals Development goals
================= =================
Tixl is a web software handling presale of event tickets. Pretix is a web software handling presale of event tickets.
Technical goals Technical goals
--------------- ---------------
@@ -19,7 +19,7 @@ Feature goals
* One pretix software installation has to cope with multiple events by multiple organizers * One pretix software installation has to cope with multiple events by multiple organizers
* There is no code access necessary to create a new event * There is no code access necessary to create a new event
* Tixl is abstract in many ways to adopt to as much events as possible. *Tixe is abstract in many ways to adopt to as much events as possible.
* Tickets are only an instance of an abstract model called items, such that the system can also sell e.g. merchandise * Tickets are only an instance of an abstract model called items, such that the system can also sell e.g. merchandise
* An abstract concept of restriction is used to restrict the selling of tickets, for example by date, by number or by user permissions. * An abstract concept of restriction is used to restrict the selling of tickets, for example by date, by number or by user permissions.

View File

@@ -7,22 +7,22 @@ Python source code
All the source code lives in ``src/``, which has several subdirectories. All the source code lives in ``src/``, which has several subdirectories.
pretix/ pretix/
This directory contains the basic Django settings and URL routing. This directory contains nearly all source code.
pretixbase/ base/
This is the django app containing all the models and methods which are This is the django app containing all the models and methods which are
essential to all of pretix's features. essential to all of pretix's features.
pretixcontrol/ control/
This is the django app containing the frontend for organizers. This is the django app containing the frontend for organizers.
pretixpresale/ presale/
This is the django app containing the frontend for users buying tickets. This is the django app containing the frontend for users buying tickets.
helpers/ helpers/
Helpers contain a very few modules providing workarounds for low-level flaws in Helpers contain a very few modules providing workarounds for low-level flaws in
Django or installed 3rd-party packages, like a filter to combine the ``lessc`` Django or installed 3rd-party packages, like a filter to combine the ``lessc``
preprocessor with ``django-compressor``'s URL rewriting. preprocessor with ``django-compressor``'s URL rewriting.
Language files Language files
-------------- --------------
@@ -37,21 +37,25 @@ LESS source code
We use less as a preprocessor for CSS. Our own less code is built in the same We use less as a preprocessor for CSS. Our own less code is built in the same
step as Bootstrap and FontAwesome, so their mixins etc. are fully available. step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
pretixcontrol pretix.control
pretixcontrol has two main LESS files, ``pretixcontrol/static/pretixcontrol/less/main.less`` and pretixcontrol has two main LESS files, ``pretix/control/static/pretixcontrol/less/main.less`` and
``pretixcontrol/static/pretixcontrol/less/auth.less``, importing everything else. ``pretix/control/static/pretixcontrol/less/auth.less``, importing everything else.
pretix.presale
pretixpresale has one main LESS files, ``pretix/control/static/pretix/presale/less/main.less``,
importing everything else.
3rd-party assets 3rd-party assets
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
Bootstrap Bootstrap
Bootstrap lives as a git submodule at ``pretixbase/static/bootstrap/`` Bootstrap lives as a git submodule at ``pretix/base/static/bootstrap/``
Font Awesome Font Awesome
Font Awesome lives as a git submodule at ``pretixbase/static/fontawesome/`` Font Awesome lives as a git submodule at ``pretix/base/static/fontawesome/``
jQuery jQuery
jQuery lives as a single JavaScript file in ``pretixbase/static/jquery/js/`` jQuery lives as a single JavaScript file in ``pretix/base/static/jquery/js/``
jQuery plugin: Django formsets jQuery plugin: Django formsets
Our own modified version of `django-formset-js`_ is available as an independent Our own modified version of `django-formset-js`_ is available as an independent

View File

@@ -1,5 +1,5 @@
[run] [run]
source = pretixbase,pretixcontrol,pretixpresale,pretixplugins source = pretix
omit = */migrations/*,*/urls.py,*/tests/*,*/testdummy/*,*/admin.py omit = */migrations/*,*/urls.py,*/tests/*,*/testdummy/*,*/admin.py
[report] [report]

View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class PretixBaseConfig(AppConfig):
name = 'pretix.base'
label = 'pretixbase'
default_app_config = 'pretix.base.PretixBaseConfig'

View File

@@ -3,13 +3,13 @@ from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django import forms from django import forms
from pretixbase.models import ( from pretix.base.models import (
User, Organizer, OrganizerPermission, Event, EventPermission, User, Organizer, OrganizerPermission, Event, EventPermission,
Property, PropertyValue, Item, ItemVariation, ItemCategory Property, PropertyValue, Item, ItemVariation, ItemCategory
) )
class TixlUserCreationForm(forms.ModelForm): class PretixUserCreationForm(forms.ModelForm):
""" """
A form that creates a user, with no privileges, from the given username and A form that creates a user, with no privileges, from the given username and
@@ -39,14 +39,14 @@ class TixlUserCreationForm(forms.ModelForm):
return password2 return password2
def save(self, commit=True): def save(self, commit=True):
user = super(TixlUserCreationForm, self).save(commit=False) user = super(PretixUserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"]) user.set_password(self.cleaned_data["password1"])
if commit: if commit:
user.save() user.save()
return user return user
class TixlUserAdmin(UserAdmin): class PretixUserAdmin(UserAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('identifier', 'event', 'username', 'password')}), (None, {'fields': ('identifier', 'event', 'username', 'password')}),
@@ -59,7 +59,7 @@ class TixlUserAdmin(UserAdmin):
search_fields = ('identifier', 'username', 'givenname', 'familyname', 'email') search_fields = ('identifier', 'username', 'givenname', 'familyname', 'email')
ordering = ('identifier',) ordering = ('identifier',)
list_filter = ('is_staff', 'is_active', 'groups') list_filter = ('is_staff', 'is_active', 'groups')
add_form = TixlUserCreationForm add_form = PretixUserCreationForm
class OrganizerPermissionInline(admin.TabularInline): class OrganizerPermissionInline(admin.TabularInline):
@@ -126,7 +126,7 @@ class ItemAdmin(admin.ModelAdmin):
search_fields = ('name', 'event', 'category', 'short_description') search_fields = ('name', 'event', 'category', 'short_description')
admin.site.register(User, TixlUserAdmin) admin.site.register(User, PretixUserAdmin)
admin.site.register(Organizer, OrganizerAdmin) admin.site.register(Organizer, OrganizerAdmin)
admin.site.register(Event, EventAdmin) admin.site.register(Event, EventAdmin)
admin.site.register(Property, PropertyAdmin) admin.site.register(Property, PropertyAdmin)

View File

@@ -3,7 +3,7 @@ import hashlib
from django.core.cache import caches from django.core.cache import caches
from pretixbase.models import Event from pretix.base.models import Event
class EventRelatedCache: class EventRelatedCache:

View File

@@ -1,7 +1,6 @@
import pytz import pytz
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import resolve
from django.middleware.locale import LocaleMiddleware as BaseLocaleMiddleware from django.middleware.locale import LocaleMiddleware as BaseLocaleMiddleware
from django.utils.translation.trans_real import ( from django.utils.translation.trans_real import (
get_supported_language_variant, get_supported_language_variant,
@@ -14,7 +13,6 @@ from django.utils import translation, timezone
from collections import OrderedDict from collections import OrderedDict
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from pretixbase.models import Event
_supported = None _supported = None

View File

@@ -7,7 +7,7 @@ import django.core.validators
from django.conf import settings from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import pretixbase.models import pretix.base.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -322,7 +322,8 @@ class Migration(migrations.Migration):
('items', versions.models.VersionedManyToManyField(blank=True, related_name='quotas', to='pretixbase.Item', verbose_name='Item')), ('items', versions.models.VersionedManyToManyField(blank=True, related_name='quotas', to='pretixbase.Item', verbose_name='Item')),
('lock_cache', models.ManyToManyField(blank=True, to='pretixbase.CartPosition')), ('lock_cache', models.ManyToManyField(blank=True, to='pretixbase.CartPosition')),
('order_cache', models.ManyToManyField(blank=True, to='pretixbase.OrderPosition')), ('order_cache', models.ManyToManyField(blank=True, to='pretixbase.OrderPosition')),
('variations', pretixbase.models.VariationsField(blank=True, related_name='quotas', to='pretixbase.ItemVariation', verbose_name='Variations')), ('variations', pretix.base.models.VariationsField(blank=True, related_name='quotas',
to='pretixbase.ItemVariation', verbose_name='Variations')),
], ],
options={ options={
'verbose_name_plural': 'Quotas', 'verbose_name_plural': 'Quotas',

View File

@@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import pretixbase.models import pretix.base.models
import versions.models import versions.models

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import versions.models import versions.models
import pretixbase.models import pretix.base.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -5,7 +5,7 @@ from django.db import models, migrations
from django.utils.timezone import utc from django.utils.timezone import utc
import versions.models import versions.models
import datetime import datetime
import pretixbase.models import pretix.base.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import pretixbase.models import pretix.base.models
import versions.models import versions.models

View File

@@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import pretixbase.models import pretix.base.models
import versions.models import versions.models

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import versions.models import versions.models
import pretixbase.models import pretix.base.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import versions.models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0008_quota_locked'),
]
operations = [
migrations.CreateModel(
name='EventSetting',
fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)),
('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
('event', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Event')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='OrganizerSetting',
fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)),
('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
('organizer', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Organizer')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
]

View File

@@ -7,6 +7,7 @@ from django.db import models
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db.models import Q, Count from django.db.models import Q, Count
from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import date as _date from django.template.defaultfilters import date as _date
@@ -227,6 +228,58 @@ class Organizer(Versionable):
def __str__(self): def __str__(self):
return self.name return self.name
class OrganizerSettingsProxy:
"""
This objects allows convenient access to settings stored in the
OrganizerSettings database model. It exposes all settings as properties
and it will do all the nasty defaults stuff for
you. It will return None for non-existing properties.
"""
def __init__(self, organizer):
self._organizer = organizer
self._cached_obj = None
def _cache(self):
if self._cached_obj is None:
self._cached_obj = {}
for setting in self._organizer.setting_objects.current.all():
self._cached_obj[setting.key] = setting
return self._cached_obj
def __getattr__(self, key):
if key in self._cache():
return self._cache()[key].value
if key in OrganizerSetting.DEFAULTS:
return OrganizerSetting.DEFAULTS[key]
return None
def __delattr__(self, key):
if key.startswith('_'):
return super().__delattr__(key)
if key in self._cache():
self._cache()[key].delete()
del self._cache()[key]
def __setattr__(self, key, value):
if key.startswith('_'):
return super().__setattr__(key, value)
if key in self._cache():
s = self._cache()[key]
s = s.clone()
else:
s = OrganizerSetting(organizer=self._organizer, key=key)
s.value = value
s.save()
self._cache()[key] = s
@cached_property
def settings(self):
"""
Returns an object representing this organizer's settings
"""
return Organizer.OrganizerSettingsProxy(self)
class OrganizerPermission(Versionable): class OrganizerPermission(Versionable):
""" """
@@ -382,10 +435,60 @@ class Event(Versionable):
"DATETIME_FORMAT" if self.show_times else "DATE_FORMAT" "DATETIME_FORMAT" if self.show_times else "DATE_FORMAT"
) )
def get_cache(self) -> "pretixbase.cache.EventRelatedCache": def get_cache(self) -> "pretix.base.cache.EventRelatedCache":
from pretixbase.cache import EventRelatedCache from pretix.base.cache import EventRelatedCache
return EventRelatedCache(self) return EventRelatedCache(self)
class EventSettingsProxy:
"""
This objects allows convenient access to settings stored in the
EventSettings database model. It exposes all settings as properties
and it will do all the nasty inheritance and defaults stuff for
you. It will return None for non-existing properties.
"""
def __init__(self, event):
self._event = event
self._cached_obj = None
def _cache(self):
if self._cached_obj is None:
self._cached_obj = {}
for setting in self._event.setting_objects.current.all():
self._cached_obj[setting.key] = setting
return self._cached_obj
def __getattr__(self, key):
if key in self._cache():
return self._cache()[key].value
return getattr(self._event.organizer.settings, key)
def __setattr__(self, key, value):
if key.startswith('_'):
return super().__setattr__(key, value)
if key in self._cache():
s = self._cache()[key]
s = s.clone()
else:
s = EventSetting(event=self._event, key=key)
s.value = value
s.save()
self._cache()[key] = s
def __delattr__(self, key):
if key.startswith('_'):
return super().__delattr__(key)
if key in self._cache():
self._cache()[key].delete()
del self._cache()[key]
@cached_property
def settings(self):
"""
Returns an object representing this event's settings
"""
return Event.EventSettingsProxy(self)
class EventPermission(Versionable): class EventPermission(Versionable):
""" """
@@ -745,14 +848,27 @@ class Item(Versionable):
return self._get_all_available_variations_cache return self._get_all_available_variations_cache
from .signals import determine_availability from .signals import determine_availability
if self.properties.count() == 0:
propids = set([p.identity for p in self.properties.all()])
if len(propids) == 0:
variations = [VariationDict()] variations = [VariationDict()]
else: else:
all_variations = list(self.variations.annotate(qc=Count('quotas')).filter(qc__gt=0)) all_variations = list(
self.variations.annotate(
qc=Count('quotas')
).filter(qc__gt=0).prefetch_related(
"values", "values__prop"
)
)
variations = [] variations = []
for var in all_variations: for var in all_variations:
values = list(var.values.all())
# Make sure we don't expose stale ItemVariation objects which are
# still around altough they have an old set of properties
if set([v.prop.identity for v in values]) != propids:
continue
vardict = VariationDict() vardict = VariationDict()
for v in var.values.all(): for v in values:
vardict[v.prop.identity] = v vardict[v.prop.identity] = v
vardict['variation'] = var vardict['variation'] = var
variations.append(vardict) variations.append(vardict)
@@ -782,7 +898,7 @@ class Item(Versionable):
self._get_all_available_variations_cache = variations self._get_all_available_variations_cache = variations
return variations return variations
def availability(self): def check_quotas(self):
""" """
This method is used to determine whether this Item is currently available This method is used to determine whether this Item is currently available
for sale. It may return any of the return codes of Quota.availability() for sale. It may return any of the return codes of Quota.availability()
@@ -792,7 +908,7 @@ class Item(Versionable):
'but call this on their ItemVariation objects') 'but call this on their ItemVariation objects')
return min([q.availability() for q in self.quotas.all()]) return min([q.availability() for q in self.quotas.all()])
def execute_restrictions(self): def check_restrictions(self):
""" """
This method is used to determine whether this ItemVariation is restricted This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins. in sale by any restriction plugins.
@@ -859,6 +975,9 @@ class ItemVariation(Versionable):
verbose_name = _("Item variation") verbose_name = _("Item variation")
verbose_name_plural = _("Item variations") verbose_name_plural = _("Item variations")
def __str__(self):
return str(self.to_variation_dict())
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
if self.item: if self.item:
@@ -869,7 +988,7 @@ class ItemVariation(Versionable):
if self.item: if self.item:
self.item.event.get_cache().clear() self.item.event.get_cache().clear()
def availability(self): def check_quotas(self):
""" """
This method is used to determine whether this ItemVariation is currently This method is used to determine whether this ItemVariation is currently
available for sale in terms of quotas. It may return any of the return codes available for sale in terms of quotas. It may return any of the return codes
@@ -884,7 +1003,7 @@ class ItemVariation(Versionable):
vd['variation'] = self vd['variation'] = self
return vd return vd
def execute_restrictions(self): def check_restrictions(self):
""" """
This method is used to determine whether this ItemVariation is restricted This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins. in sale by any restriction plugins.
@@ -913,7 +1032,7 @@ class VariationsField(VersionedManyToManyField):
""" """
def formfield(self, **kwargs): def formfield(self, **kwargs):
from pretixcontrol.views.forms import VariationsField as FVariationsField from pretix.control.views.forms import VariationsField as FVariationsField
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
defaults = { defaults = {
'form_class': FVariationsField, 'form_class': FVariationsField,
@@ -1086,14 +1205,14 @@ class Quota(Versionable):
Q(variation__quotas__in=[self]) Q(variation__quotas__in=[self])
) )
) )
paid_orders = OrderPosition.objects.filter( paid_orders = OrderPosition.objects.current.filter(
Q(order__status=Order.STATUS_PAID) Q(order__status=Order.STATUS_PAID)
& quotalookup & quotalookup
).count() ).count()
if paid_orders >= self.size: if paid_orders >= self.size:
return Quota.AVAILABILITY_GONE, 0 return Quota.AVAILABILITY_GONE, 0
pending_valid_orders = OrderPosition.objects.filter( pending_valid_orders = OrderPosition.objects.current.filter(
Q(order__status=Order.STATUS_PENDING) Q(order__status=Order.STATUS_PENDING)
& Q(order__expires__gte=now()) & Q(order__expires__gte=now())
& quotalookup & quotalookup
@@ -1101,7 +1220,7 @@ class Quota(Versionable):
if (paid_orders + pending_valid_orders) >= self.size: if (paid_orders + pending_valid_orders) >= self.size:
return Quota.AVAILABILITY_ORDERED, 0 return Quota.AVAILABILITY_ORDERED, 0
valid_cart_positions = CartPosition.objects.filter( valid_cart_positions = CartPosition.objects.current.filter(
Q(expires__gte=now()) Q(expires__gte=now())
& quotalookup & quotalookup
).count() ).count()
@@ -1307,3 +1426,26 @@ class CartPosition(Versionable):
class Meta: class Meta:
verbose_name = _("Cart position") verbose_name = _("Cart position")
verbose_name_plural = _("Cart positions") verbose_name_plural = _("Cart positions")
class EventSetting(Versionable):
"""
An event settings is a key-value setting which can be set for a
specific event
"""
event = VersionedForeignKey(Event, related_name='setting_objects')
key = models.CharField(max_length=255)
value = models.TextField()
class OrganizerSetting(Versionable):
"""
An event option is a key-value setting which can be set for an
organizer. It will be inherited by the events of this organizer
"""
DEFAULTS = {
}
organizer = VersionedForeignKey(Organizer, related_name='setting_objects')
key = models.CharField(max_length=255)
value = models.TextField()

View File

@@ -12,12 +12,12 @@ class PluginType(Enum):
def get_all_plugins() -> "List[class]": def get_all_plugins() -> "List[class]":
""" """
Returns the TixlPluginMeta classes of all plugins found in the installed Django apps. Returns the PretixPluginMeta classes of all plugins found in the installed Django apps.
""" """
plugins = [] plugins = []
for app in apps.get_app_configs(): for app in apps.get_app_configs():
if hasattr(app, 'TixlPluginMeta'): if hasattr(app, 'PretixPluginMeta'):
meta = app.TixlPluginMeta meta = app.PretixPluginMeta
meta.module = app.name meta.module = app.name
plugins.append(meta) plugins.append(meta)
return plugins return plugins

View File

@@ -17,7 +17,7 @@ class EventPluginSignal(django.dispatch.Signal):
Send signal from sender to all connected receivers that belong to Send signal from sender to all connected receivers that belong to
plugins enabled for the given Event. plugins enabled for the given Event.
sender is required to be an instance of ``pretixbase.models.Event``. sender is required to be an instance of ``pretix.base.models.Event``.
""" """
assert isinstance(sender, Event) assert isinstance(sender, Event)

View File

@@ -2,7 +2,6 @@ import os
import sys import sys
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import LiveServerTestCase
from django.conf import settings from django.conf import settings
from selenium import webdriver from selenium import webdriver

View File

@@ -4,7 +4,7 @@ from django.test import TestCase
from django.core.cache import cache as django_cache from django.core.cache import cache as django_cache
from django.utils.timezone import now from django.utils.timezone import now
from pretixbase.models import Event, Organizer from pretix.base.models import Event, Organizer
class CacheTest(TestCase): class CacheTest(TestCase):

View File

@@ -2,7 +2,7 @@ from django.test import TestCase, Client
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings from django.conf import settings
from pretixbase.models import Event, Organizer, User from pretix.base.models import Event, Organizer, User
class LocaleDeterminationTest(TestCase): class LocaleDeterminationTest(TestCase):

View File

@@ -2,12 +2,12 @@ from datetime import timedelta
from django.test import TestCase from django.test import TestCase
from django.utils.timezone import now from django.utils.timezone import now
from pretixbase.models import ( from pretix.base.models import (
Event, Organizer, Item, ItemVariation, Event, Organizer, Item, ItemVariation,
Property, PropertyValue, User, Quota, Property, PropertyValue, User, Quota,
Order, OrderPosition, CartPosition Order, OrderPosition, CartPosition,
) OrganizerSetting)
from pretixbase.types import VariationDict from pretix.base.types import VariationDict
class ItemVariationsTest(TestCase): class ItemVariationsTest(TestCase):
@@ -202,15 +202,15 @@ class QuotaTestCase(TestCase):
def test_available(self): def test_available(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
try: try:
self.item2.availability() self.item2.check_quotas()
self.assertTrue(False) self.assertTrue(False)
except: except:
pass pass
self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_OK, 2)) self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
def test_sold_out(self): def test_sold_out(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -219,19 +219,19 @@ class QuotaTestCase(TestCase):
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_GONE, 0)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
self.quota.size = 3 self.quota.size = 3
self.quota.save() self.quota.save()
self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID, order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2) OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2)
self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_GONE, 0)) self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
def test_ordered(self): def test_ordered(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -239,17 +239,17 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING, order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_ORDERED, 0)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))
order.expires = now() - timedelta(days=3) order.expires = now() - timedelta(days=3)
order.save() order.save()
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
def test_reserved(self): def test_reserved(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -259,36 +259,100 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING, order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2, cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2,
expires=now() + timedelta(days=3)) expires=now() + timedelta(days=3))
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_RESERVED, 0)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
cp.expires = now() - timedelta(days=3) cp.expires = now() - timedelta(days=3)
cp.save() cp.save()
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1, cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1,
price=2, expires=now() + timedelta(days=3)) price=2, expires=now() + timedelta(days=3))
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_RESERVED, 0)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
def test_multiple(self): def test_multiple(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
quota2 = Quota.objects.create(event=self.event, name="Test 2", size=1) quota2 = Quota.objects.create(event=self.event, name="Test 2", size=1)
quota2.items.add(self.item1) quota2.items.add(self.item1)
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
quota2.size = 0 quota2.size = 0
quota2.save() quota2.save()
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_GONE, 0)) self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
class SettingsTestCase(TestCase):
def setUp(self):
OrganizerSetting.DEFAULTS['test_default'] = 'def'
self.organizer = Organizer.objects.create(name='Dummy', slug='dummy')
self.event = Event.objects.create(
organizer=self.organizer, name='Dummy', slug='dummy',
date_from=now(),
)
def test_event_set_explicit(self):
self.event.settings.test = 'foo'
self.assertEqual(self.event.settings.test, 'foo')
# Reload object
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.event.settings.test, 'foo')
def test_event_set_on_organizer(self):
self.organizer.settings.test = 'foo'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'foo')
# Reload object
self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'foo')
def test_override_organizer(self):
self.organizer.settings.test = 'foo'
self.event.settings.test = 'bar'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')
# Reload object
self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')
def test_default(self):
self.assertEqual(self.organizer.settings.test_default, 'def')
self.assertEqual(self.event.settings.test_default, 'def')
def test_delete(self):
self.organizer.settings.test = 'foo'
self.event.settings.test = 'bar'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')
del self.event.settings.test
self.assertEqual(self.event.settings.test, 'foo')
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.event.settings.test, 'foo')
del self.organizer.settings.test
self.assertIsNone(self.organizer.settings.test)
self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.assertIsNone(self.organizer.settings.test)

View File

@@ -2,9 +2,9 @@ from django.test import TestCase
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings from django.conf import settings
from pretixbase.models import Event, Organizer from pretix.base.models import Event, Organizer
from pretixbase.plugins import get_all_plugins from pretix.base.plugins import get_all_plugins
from pretixbase.signals import determine_availability from pretix.base.signals import determine_availability
class PluginRegistryTest(TestCase): class PluginRegistryTest(TestCase):
@@ -49,9 +49,9 @@ class PluginSignalTest(TestCase):
self.assertEqual(len(responses), 0) self.assertEqual(len(responses), 0)
def test_one_plugin_active(self): def test_one_plugin_active(self):
self.event.plugins = 'pretixplugins.testdummy' self.event.plugins = 'pretix.plugins.testdummy'
self.event.save() self.event.save()
payload = {'foo': 'bar'} payload = {'foo': 'bar'}
responses = determine_availability.send(self.event, **payload) responses = determine_availability.send(self.event, **payload)
self.assertEqual(len(responses), 1) self.assertEqual(len(responses), 1)
self.assertIn('pretixplugins.testdummy.signals', [r[0].__module__ for r in responses]) self.assertIn('pretix.plugins.testdummy.signals', [r[0].__module__ for r in responses])

View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class PretixControlConfig(AppConfig):
name = 'pretix.control'
label = 'pretixcontrol'
default_app_config = 'pretix.control.PretixControlConfig'

View File

@@ -7,7 +7,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseNotFound from django.http import HttpResponseNotFound
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from pretixbase.models import Event from pretix.base.models import Event
class PermissionMiddleware: class PermissionMiddleware:

View File

@@ -1,7 +1,7 @@
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from pretixbase.models import EventPermission from pretix.base.models import EventPermission
def event_permission_required(permission): def event_permission_required(permission):

View File

@@ -1,4 +1,4 @@
from pretixbase.signals import EventPluginSignal from pretix.base.signals import EventPluginSignal
""" """

View File

@@ -1,4 +1,4 @@
@import "../../../../pretixbase/static/bootstrap/less/bootstrap.less"; @import "../../../../base/static/bootstrap/less/bootstrap.less";
body { body {
background: #eee; background: #eee;

View File

@@ -0,0 +1,4 @@
@import "../../../../base/static/bootstrap/less/bootstrap.less";
@import "../../../../base/static/fontawesome/less/font-awesome.less";
@fa-font-path: "../../fontawesome/fonts";
@import "forms.less";

View File

@@ -1,7 +1,7 @@
from django.test import TestCase, Client from django.test import TestCase, Client
from pretixbase.models import User from pretix.base.models import User
from pretixbase.tests import BrowserTest, on_platforms from pretix.base.tests import BrowserTest, on_platforms
@on_platforms() @on_platforms()

View File

@@ -1,6 +1,6 @@
import datetime import datetime
from pretixbase.models import User, Organizer, Event, OrganizerPermission, EventPermission from pretix.base.models import User, Organizer, Event, OrganizerPermission, EventPermission
from pretixbase.tests import BrowserTest, on_platforms from pretix.base.tests import BrowserTest, on_platforms
@on_platforms() @on_platforms()
@@ -54,8 +54,8 @@ class EventsTest(BrowserTest):
self.driver.get('%s/control/event/%s/%s/settings/plugins' % (self.live_server_url, self.orga1.slug, self.driver.get('%s/control/event/%s/%s/settings/plugins' % (self.live_server_url, self.orga1.slug,
self.event1.slug)) self.event1.slug))
self.assertIn("Restriction by time", self.driver.find_element_by_class_name("form-plugins").text) self.assertIn("Restriction by time", self.driver.find_element_by_class_name("form-plugins").text)
self.assertIn("Enable", self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").text) self.assertIn("Enable", self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").text)
self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").click() self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").click()
self.assertIn("Disable", self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").text) self.assertIn("Disable", self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").text)
self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").click() self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").click()
self.assertIn("Enable", self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").text) self.assertIn("Enable", self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").text)

View File

@@ -3,9 +3,9 @@ import time
import datetime import datetime
from django.utils import unittest from django.utils import unittest
from selenium.webdriver.support.select import Select from selenium.webdriver.support.select import Select
from pretixbase.models import User, Organizer, Event, OrganizerPermission, EventPermission, ItemCategory, Property, \ from pretix.base.models import User, Organizer, Event, OrganizerPermission, EventPermission, ItemCategory, Property, \
PropertyValue, Question, Quota, Item PropertyValue, Question, Quota, Item
from pretixbase.tests import BrowserTest, on_platforms from pretix.base.tests import BrowserTest, on_platforms
class ItemFormTest(BrowserTest): class ItemFormTest(BrowserTest):
@@ -18,7 +18,7 @@ class ItemFormTest(BrowserTest):
self.event1 = Event.objects.create( self.event1 = Event.objects.create(
organizer=self.orga1, name='30C3', slug='30c3', organizer=self.orga1, name='30C3', slug='30c3',
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
) )
OrganizerPermission.objects.create(organizer=self.orga1, user=self.user) OrganizerPermission.objects.create(organizer=self.orga1, user=self.user)
EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True, EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True,
can_change_settings=True) can_change_settings=True)

View File

@@ -1,7 +1,7 @@
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils.timezone import now from django.utils.timezone import now
from pretixbase.models import Event, Organizer, User, EventPermission from pretix.base.models import Event, Organizer, User, EventPermission
class PermissionMiddlewareTest(TestCase): class PermissionMiddlewareTest(TestCase):

View File

@@ -1,22 +1,22 @@
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
from pretixcontrol.views import main, event, item from pretix.control.views import main, event, item
urlpatterns = patterns('',) urlpatterns = patterns('',)
urlpatterns += patterns( urlpatterns += patterns(
'pretixcontrol.views.auth', 'pretix.control.views.auth',
url(r'^logout$', 'logout', name='auth.logout'), url(r'^logout$', 'logout', name='auth.logout'),
url(r'^login$', 'login', name='auth.login'), url(r'^login$', 'login', name='auth.login'),
) )
urlpatterns += patterns( urlpatterns += patterns(
'pretixcontrol.views.main', 'pretix.control.views.main',
url(r'^$', 'index', name='index'), url(r'^$', 'index', name='index'),
url(r'^events/$', main.EventList.as_view(), name='events'), url(r'^events/$', main.EventList.as_view(), name='events'),
) )
urlpatterns += patterns( urlpatterns += patterns(
'pretixcontrol.views.event', 'pretix.control.views.event',
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include( url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(
patterns( patterns(
'pretixcontrol.views', 'pretix.control.views',
url(r'^$', 'event.index', name='event.index'), url(r'^$', 'event.index', name='event.index'),
url(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'), url(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
url(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'), url(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
@@ -54,5 +54,5 @@ urlpatterns += patterns(
name='event.items.quotas.delete'), name='event.items.quotas.delete'),
url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
) )
)) ))
) )

View File

@@ -7,10 +7,10 @@ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from pytz import common_timezones from pytz import common_timezones
from pretixbase.forms import VersionedModelForm from pretix.base.forms import VersionedModelForm
from pretixbase.models import Event from pretix.base.models import Event
from pretixcontrol.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
class EventUpdateForm(VersionedModelForm): class EventUpdateForm(VersionedModelForm):
@@ -75,7 +75,7 @@ class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin
return self.request.event return self.request.event
def get_context_data(self, *args, **kwargs) -> dict: def get_context_data(self, *args, **kwargs) -> dict:
from pretixbase.plugins import get_all_plugins from pretix.base.plugins import get_all_plugins
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(*args, **kwargs)
context['plugins'] = [p for p in get_all_plugins() if not p.name.startswith('.')] context['plugins'] = [p for p in get_all_plugins() if not p.name.startswith('.')]
context['plugins_active'] = self.object.get_plugins() context['plugins_active'] = self.object.get_plugins()

View File

@@ -1,15 +1,15 @@
from itertools import product from itertools import product
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction, IntegrityError from django.db import transaction
from django.forms.widgets import flatatt from django.forms.widgets import flatatt
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.forms import VersionedModelForm from pretix.base.forms import VersionedModelForm
from pretixbase.models import ItemVariation, PropertyValue, Item from pretix.base.models import ItemVariation, PropertyValue, Item
class TolerantFormsetModelForm(VersionedModelForm): class TolerantFormsetModelForm(VersionedModelForm):

View File

@@ -12,14 +12,14 @@ from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import redirect from django.shortcuts import redirect
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.forms import VersionedModelForm from pretix.base.forms import VersionedModelForm
from pretixbase.models import ( from pretix.base.models import (
Item, ItemCategory, Property, ItemVariation, PropertyValue, Question, Quota, Item, ItemCategory, Property, ItemVariation, PropertyValue, Question, Quota,
Versionable) Versionable)
from pretixcontrol.permissions import EventPermissionRequiredMixin, event_permission_required from pretix.control.permissions import EventPermissionRequiredMixin, event_permission_required
from pretixcontrol.views.forms import TolerantFormsetModelForm, VariationsField from pretix.control.views.forms import TolerantFormsetModelForm, VariationsField
from pretixcontrol.signals import restriction_formset from pretix.control.signals import restriction_formset
class ItemList(ListView): class ItemList(ListView):
@@ -649,7 +649,7 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
'organizer': self.request.event.organizer.slug, 'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug, 'event': self.request.event.slug,
'item': self.object.identity, 'item': self.object.identity,
}) + '?success=true' }) + '?success=true'
def get_form_kwargs(self): def get_form_kwargs(self):
""" """

View File

@@ -1,7 +1,7 @@
from django.shortcuts import render from django.shortcuts import render
from django.views.generic import ListView from django.views.generic import ListView
from pretixbase.models import Event from pretix.base.models import Event
class EventList(ListView): class EventList(ListView):

View File

@@ -1,12 +1,12 @@
from django.apps import AppConfig from django.apps import AppConfig
from pretixbase.plugins import PluginType from pretix.base.plugins import PluginType
class TestDummyApp(AppConfig): class TestDummyApp(AppConfig):
name = 'pretixplugins.testdummy' name = 'pretix.plugins.testdummy'
verbose_name = '.testdummy' verbose_name = '.testdummy'
class TixlPluginMeta: class PretixPluginMeta:
type = PluginType.RESTRICTION type = PluginType.RESTRICTION
name = '.testdummy' name = '.testdummy'
version = '1.0.0' version = '1.0.0'
@@ -14,4 +14,4 @@ class TestDummyApp(AppConfig):
def ready(self): def ready(self):
from . import signals # NOQA from . import signals # NOQA
default_app_config = 'pretixplugins.testdummy.TestDummyApp' default_app_config = 'pretix.plugins.testdummy.TestDummyApp'

View File

@@ -1,6 +1,6 @@
from django.dispatch import receiver from django.dispatch import receiver
from pretixbase.signals import determine_availability from pretix.base.signals import determine_availability
@receiver(determine_availability) @receiver(determine_availability)

View File

@@ -1,13 +1,13 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.plugins import PluginType from pretix.base.plugins import PluginType
class TimeRestrictionApp(AppConfig): class TimeRestrictionApp(AppConfig):
name = 'pretixplugins.timerestriction' name = 'pretix.plugins.timerestriction'
verbose_name = _("Time restriction") verbose_name = _("Time restriction")
class TixlPluginMeta: class PretixPluginMeta:
type = PluginType.RESTRICTION type = PluginType.RESTRICTION
name = _("Restriction by time") name = _("Restriction by time")
author = _("the pretix team") author = _("the pretix team")
@@ -19,4 +19,4 @@ class TimeRestrictionApp(AppConfig):
def ready(self): def ready(self):
from . import signals # NOQA from . import signals # NOQA
default_app_config = 'pretixplugins.timerestriction.TimeRestrictionApp' default_app_config = 'pretix.plugins.timerestriction.TimeRestrictionApp'

View File

@@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import pretixbase.models import pretix.base.models
import versions.models import versions.models
@@ -26,7 +26,8 @@ class Migration(migrations.Migration):
('price', models.DecimalField(null=True, blank=True, verbose_name='Price in time frame', max_digits=7, decimal_places=2)), ('price', models.DecimalField(null=True, blank=True, verbose_name='Price in time frame', max_digits=7, decimal_places=2)),
('event', versions.models.VersionedForeignKey(to='pretixbase.Event', related_name='restrictions_timerestriction_timerestriction', verbose_name='Event')), ('event', versions.models.VersionedForeignKey(to='pretixbase.Event', related_name='restrictions_timerestriction_timerestriction', verbose_name='Event')),
('item', versions.models.VersionedForeignKey(to='pretixbase.Item', blank=True, null=True, related_name='restrictions_timerestriction_timerestriction', verbose_name='Item')), ('item', versions.models.VersionedForeignKey(to='pretixbase.Item', blank=True, null=True, related_name='restrictions_timerestriction_timerestriction', verbose_name='Item')),
('variations', pretixbase.models.VariationsField(to='pretixbase.ItemVariation', blank=True, verbose_name='Variations', related_name='restrictions_timerestriction_timerestriction')), ('variations', pretix.base.models.VariationsField(to='pretixbase.ItemVariation', blank=True,
verbose_name='Variations', related_name='restrictions_timerestriction_timerestriction')),
], ],
options={ options={
'verbose_name': 'Restriction', 'verbose_name': 'Restriction',

View File

@@ -1,7 +1,7 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretixbase.models import BaseRestriction from pretix.base.models import BaseRestriction
class TimeRestriction(BaseRestriction): class TimeRestriction(BaseRestriction):

View File

@@ -3,10 +3,10 @@ from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from pretixbase.signals import determine_availability from pretix.base.signals import determine_availability
from pretixbase.models import Item from pretix.base.models import Item
from pretixcontrol.views.forms import VariationsField, RestrictionInlineFormset, RestrictionForm from pretix.control.views.forms import RestrictionInlineFormset, RestrictionForm
from pretixcontrol.signals import restriction_formset from pretix.control.signals import restriction_formset
from .models import TimeRestriction from .models import TimeRestriction

View File

@@ -3,13 +3,13 @@ from datetime import timedelta
from django.test import TestCase from django.test import TestCase
from django.utils.timezone import now from django.utils.timezone import now
from pretixbase.models import ( from pretix.base.models import (
Event, Organizer, Item, Property, PropertyValue, ItemVariation Event, Organizer, Item, Property, PropertyValue, ItemVariation
) )
# Do NOT use relative imports here # Do NOT use relative imports here
from pretixplugins.timerestriction import signals from pretix.plugins.timerestriction import signals
from pretixplugins.timerestriction.models import TimeRestriction from pretix.plugins.timerestriction.models import TimeRestriction
class TimeRestrictionTest(TestCase): class TimeRestrictionTest(TestCase):

View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class PretixPresaleConfig(AppConfig):
name = 'pretix.presale'
label = 'pretixpresale'
default_app_config = 'pretix.presale.PretixPresaleConfig'

View File

@@ -1,13 +1,7 @@
from django.conf import settings
from django.core.urlresolvers import resolve from django.core.urlresolvers import resolve
from django.utils.encoding import force_str
from django.utils.six.moves.urllib.parse import urlparse
from django.shortcuts import resolve_url
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseNotFound from django.http import HttpResponseNotFound
from django.utils.translation import ugettext as _
from pretixbase.models import Event from pretix.base.models import Event
class EventMiddleware: class EventMiddleware:

Some files were not shown because too many files have changed in this diff Show More