mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Merge branch 'master' of https://github.com/pretix/pretix
This commit is contained in:
8
src/pretix/base/__init__.py
Normal file
8
src/pretix/base/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PretixBaseConfig(AppConfig):
|
||||
name = 'pretix.base'
|
||||
label = 'pretixbase'
|
||||
|
||||
default_app_config = 'pretix.base.PretixBaseConfig'
|
||||
134
src/pretix/base/admin.py
Normal file
134
src/pretix/base/admin.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import ugettext as _
|
||||
from django import forms
|
||||
|
||||
from pretix.base.models import (
|
||||
User, Organizer, OrganizerPermission, Event, EventPermission,
|
||||
Property, PropertyValue, Item, ItemVariation, ItemCategory
|
||||
)
|
||||
|
||||
|
||||
class PretixUserCreationForm(forms.ModelForm):
|
||||
|
||||
"""
|
||||
A form that creates a user, with no privileges, from the given username and
|
||||
password.
|
||||
"""
|
||||
error_messages = {
|
||||
'password_mismatch': _("The two password fields didn't match."),
|
||||
}
|
||||
password1 = forms.CharField(label=_("Password"),
|
||||
widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_("Password confirmation"),
|
||||
widget=forms.PasswordInput,
|
||||
help_text=_("Enter the same password as above, for verification."))
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("email", "username", "event")
|
||||
|
||||
def clean_password2(self):
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
if password1 and password2 and password1 != password2:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['password_mismatch'],
|
||||
code='password_mismatch',
|
||||
)
|
||||
return password2
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super(PretixUserCreationForm, self).save(commit=False)
|
||||
user.set_password(self.cleaned_data["password1"])
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class PretixUserAdmin(UserAdmin):
|
||||
|
||||
fieldsets = (
|
||||
(None, {'fields': ('identifier', 'event', 'username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('familyname', 'givenname', 'email')}),
|
||||
(_('Locale'), {'fields': ('locale', 'timezone')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff',
|
||||
'groups', 'user_permissions')}),
|
||||
)
|
||||
list_display = ('identifier', 'event', 'username', 'email', 'givenname', 'familyname', 'is_staff')
|
||||
search_fields = ('identifier', 'username', 'givenname', 'familyname', 'email')
|
||||
ordering = ('identifier',)
|
||||
list_filter = ('is_staff', 'is_active', 'groups')
|
||||
add_form = PretixUserCreationForm
|
||||
|
||||
|
||||
class OrganizerPermissionInline(admin.TabularInline):
|
||||
|
||||
model = OrganizerPermission
|
||||
extra = 2
|
||||
|
||||
|
||||
class OrganizerAdmin(admin.ModelAdmin):
|
||||
|
||||
model = Organizer
|
||||
inlines = [OrganizerPermissionInline]
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name', 'slug')
|
||||
|
||||
|
||||
class EventPermissionInline(admin.TabularInline):
|
||||
|
||||
model = EventPermission
|
||||
extra = 2
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
|
||||
model = Event
|
||||
inlines = [EventPermissionInline]
|
||||
list_display = ('name', 'slug', 'organizer', 'date_from')
|
||||
search_fields = ('name', 'slug')
|
||||
list_filter = ('date_from', 'locale', 'currency')
|
||||
|
||||
|
||||
class PropertyValueInline(admin.StackedInline):
|
||||
|
||||
model = PropertyValue
|
||||
extra = 4
|
||||
|
||||
|
||||
class PropertyAdmin(admin.ModelAdmin):
|
||||
|
||||
model = Property
|
||||
inlines = [PropertyValueInline]
|
||||
list_display = ('name', 'event')
|
||||
search_fields = ('name', 'event')
|
||||
|
||||
|
||||
class ItemCategoryAdmin(admin.ModelAdmin):
|
||||
|
||||
model = ItemCategory
|
||||
list_display = ('name', 'event')
|
||||
search_fields = ('name', 'event')
|
||||
|
||||
|
||||
class ItemVariationInline(admin.TabularInline):
|
||||
|
||||
model = ItemVariation
|
||||
extra = 4
|
||||
|
||||
|
||||
class ItemAdmin(admin.ModelAdmin):
|
||||
|
||||
model = Item
|
||||
inlines = [ItemVariationInline]
|
||||
list_display = ('name', 'event', 'category')
|
||||
search_fields = ('name', 'event', 'category', 'short_description')
|
||||
|
||||
|
||||
admin.site.register(User, PretixUserAdmin)
|
||||
admin.site.register(Organizer, OrganizerAdmin)
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(Property, PropertyAdmin)
|
||||
admin.site.register(Item, ItemAdmin)
|
||||
admin.site.register(ItemCategory, ItemCategoryAdmin)
|
||||
88
src/pretix/base/cache.py
Normal file
88
src/pretix/base/cache.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
from django.core.cache import caches
|
||||
|
||||
from pretix.base.models import Event
|
||||
|
||||
|
||||
class EventRelatedCache:
|
||||
"""
|
||||
This object behaves exactly like the cache implementations by Django
|
||||
but with one important difference: It stores all keys related to a
|
||||
certain event, so you pass an event when creating this object and if
|
||||
you store data in this cache, it is only stored for this event. The
|
||||
main purpose of this is to be able to flush all cached data related
|
||||
to this event at once.
|
||||
|
||||
The EventRelatedCache instance itself is stateless, all state is
|
||||
stored in the cache backend, so you can instantiate this class as many
|
||||
times as you want.
|
||||
"""
|
||||
|
||||
def __init__(self, event: Event, cache: str='default'):
|
||||
assert isinstance(event, Event)
|
||||
self.cache = caches[cache]
|
||||
self.event = event
|
||||
self.prefixkey = 'event:%s' % self.event.pk
|
||||
|
||||
def _prefix_key(self, original_key: str) -> str:
|
||||
# Race conditions can happen here, but should be very very rare.
|
||||
# We could only handle this by going _really_ lowlevel using
|
||||
# memcached's `add` keyword instead of `set`.
|
||||
# See also:
|
||||
# https://code.google.com/p/memcached/wiki/NewProgrammingTricks#Namespacing
|
||||
prefix = self.cache.get(self.prefixkey)
|
||||
if prefix is None:
|
||||
prefix = int(time.time())
|
||||
self.cache.set(self.prefixkey, prefix)
|
||||
key = 'event:%s:%d:%s' % (self.event.pk, prefix, original_key)
|
||||
if len(key) > 200: # Hash long keys, as memcached has a length limit
|
||||
# TODO: Use a more efficient, non-cryptographic hash algorithm
|
||||
key = hashlib.sha256(key.encode("UTF-8")).hexdigest()
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def _strip_prefix(key: str) -> str:
|
||||
return key.split(":", 3)[-1] if 'event:' in key else key
|
||||
|
||||
def clear(self):
|
||||
try:
|
||||
prefix = self.cache.incr(self.prefixkey, 1)
|
||||
except ValueError:
|
||||
prefix = int(time.time())
|
||||
self.cache.set(self.prefixkey, prefix)
|
||||
|
||||
def set(self, key: str, value: str, timeout: int=3600):
|
||||
return self.cache.set(self._prefix_key(key), value, timeout)
|
||||
|
||||
def get(self, key: str) -> str:
|
||||
return self.cache.get(self._prefix_key(key))
|
||||
|
||||
def get_many(self, keys: "list[str]") -> "dict[str, str]":
|
||||
values = self.cache.get_many([self._prefix_key(key) for key in keys])
|
||||
newvalues = {}
|
||||
for k, v in values.items():
|
||||
newvalues[self._strip_prefix(k)] = v
|
||||
return newvalues
|
||||
|
||||
def set_many(self, values: "dict[str, str]", timeout=3600):
|
||||
newvalues = {}
|
||||
for k, v in values.items():
|
||||
newvalues[self._prefix_key(k)] = v
|
||||
return self.cache.set_many(newvalues, timeout)
|
||||
|
||||
def delete(self, key: str): # NOQA
|
||||
return self.cache.delete(self._prefix_key(key))
|
||||
|
||||
def delete_many(self, keys: "list[str]"): # NOQA
|
||||
return self.cache.delete_many([self._prefix_key(key) for key in keys])
|
||||
|
||||
def incr(self, key: str, by: int=1): # NOQA
|
||||
return self.cache.incr(self._prefix_key(key), by)
|
||||
|
||||
def decr(self, key: str, by: int=1): # NOQA
|
||||
return self.cache.decr(self._prefix_key(key), by)
|
||||
|
||||
def close(self): # NOQA
|
||||
pass
|
||||
25
src/pretix/base/forms.py
Normal file
25
src/pretix/base/forms.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django.forms.models import ModelFormMetaclass, BaseModelForm
|
||||
from django.utils import six
|
||||
from versions.models import Versionable
|
||||
|
||||
|
||||
class VersionedBaseModelForm(BaseModelForm):
|
||||
"""
|
||||
This is a helperclass to construct VersionedModelForm
|
||||
"""
|
||||
def save(self, commit=True):
|
||||
if self.instance.pk is not None and isinstance(self.instance, Versionable):
|
||||
if self.has_changed():
|
||||
self.instance = self.instance.clone()
|
||||
return super().save(commit)
|
||||
|
||||
|
||||
class VersionedModelForm(six.with_metaclass(ModelFormMetaclass, VersionedBaseModelForm)):
|
||||
"""
|
||||
This is a modified version of Django's ModelForm which differs from ModelForm in
|
||||
only one way: It executes the .clone() method of an object before saving it back to
|
||||
the database, if the model is a sub-class of versions.models.Versionable. You can
|
||||
safely use this as a base class for all your model forms, it will work out correctly
|
||||
with both versioned and non-versioned models.
|
||||
"""
|
||||
pass
|
||||
107
src/pretix/base/middleware.py
Normal file
107
src/pretix/base/middleware.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.middleware.locale import LocaleMiddleware as BaseLocaleMiddleware
|
||||
from django.utils.translation.trans_real import (
|
||||
get_supported_language_variant,
|
||||
parse_accept_lang_header,
|
||||
language_code_re,
|
||||
check_for_language
|
||||
)
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from django.utils import translation, timezone
|
||||
from collections import OrderedDict
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
||||
|
||||
_supported = None
|
||||
|
||||
|
||||
class LocaleMiddleware(BaseLocaleMiddleware):
|
||||
|
||||
"""
|
||||
This middleware sets the correct locale and timezone
|
||||
for a request.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
language = get_language_from_request(request)
|
||||
translation.activate(language)
|
||||
request.LANGUAGE_CODE = translation.get_language()
|
||||
|
||||
tzname = None
|
||||
if request.user.is_authenticated():
|
||||
tzname = request.user.timezone
|
||||
if hasattr(request, 'event'):
|
||||
tzname = request.event.timezone
|
||||
if tzname:
|
||||
try:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
except pytz.UnknownTimeZoneError:
|
||||
pass
|
||||
else:
|
||||
timezone.deactivate()
|
||||
|
||||
def process_response(self, request, response):
|
||||
language = translation.get_language()
|
||||
patch_vary_headers(response, ('Accept-Language',))
|
||||
if 'Content-Language' not in response:
|
||||
response['Content-Language'] = language
|
||||
return response
|
||||
|
||||
|
||||
def get_language_from_request(request) -> str:
|
||||
"""
|
||||
Analyzes the request to find what language the user wants the system to
|
||||
show. Only languages listed in settings.LANGUAGES are taken into account.
|
||||
If the user requests a sublanguage where we have a main language, we send
|
||||
out the main language.
|
||||
"""
|
||||
global _supported
|
||||
if _supported is None:
|
||||
_supported = OrderedDict(settings.LANGUAGES)
|
||||
|
||||
# Priority 1: User settings
|
||||
if request.user.is_authenticated():
|
||||
lang_code = request.user.locale
|
||||
if lang_code in _supported and lang_code is not None and check_for_language(lang_code):
|
||||
return lang_code
|
||||
|
||||
# Priority 2: Anonymous user settings (session, cookie)
|
||||
if hasattr(request, 'session'):
|
||||
lang_code = request.session.get(LANGUAGE_SESSION_KEY)
|
||||
if lang_code in _supported and lang_code is not None and check_for_language(lang_code):
|
||||
return lang_code
|
||||
|
||||
lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
|
||||
try:
|
||||
return get_supported_language_variant(lang_code)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
# Priority 3: Event default
|
||||
if hasattr(request, 'event'):
|
||||
lang_code = request.event.locale
|
||||
try:
|
||||
return get_supported_language_variant(lang_code)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
# Priority 4: Browser default
|
||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||
for accept_lang, unused in parse_accept_lang_header(accept):
|
||||
if accept_lang == '*':
|
||||
break
|
||||
|
||||
if not language_code_re.search(accept_lang):
|
||||
continue
|
||||
|
||||
try:
|
||||
return get_supported_language_variant(accept_lang)
|
||||
except LookupError:
|
||||
continue
|
||||
|
||||
try:
|
||||
return get_supported_language_variant(settings.LANGUAGE_CODE)
|
||||
except LookupError:
|
||||
return settings.LANGUAGE_CODE
|
||||
456
src/pretix/base/migrations/0001_initial.py
Normal file
456
src/pretix/base/migrations/0001_initial.py
Normal file
@@ -0,0 +1,456 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import versions.models
|
||||
import django.core.validators
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import pretix.base.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('identifier', models.CharField(unique=True, max_length=255)),
|
||||
('username', models.CharField(max_length=120, blank=True, null=True, help_text='Letters, digits and @/./+/-/_ only.')),
|
||||
('email', models.EmailField(null=True, max_length=75, blank=True, db_index=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')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('is_staff', models.BooleanField(default=False, verbose_name='Is site admin')),
|
||||
('date_joined', models.DateTimeField(verbose_name='Date joined', auto_now_add=True)),
|
||||
('locale', models.CharField(max_length=50, choices=[('de', 'German'), ('en', 'English')], default='en', verbose_name='Language')),
|
||||
('timezone', models.CharField(max_length=100, default='UTC', verbose_name='Timezone')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Users',
|
||||
'verbose_name': 'User',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CartPosition',
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
|
||||
('session', models.CharField(max_length=255, blank=True, null=True, verbose_name='Session key')),
|
||||
('total', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
||||
('datetime', models.DateTimeField(verbose_name='Date')),
|
||||
('expires', models.DateTimeField(verbose_name='Expiration date')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Cart positions',
|
||||
'verbose_name': 'Cart position',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('slug', models.CharField(max_length=50, db_index=True, validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z0-9.-]+$', message='The slug may only contain letters, numbers, dots and dashes.')], verbose_name='Slug', 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.')),
|
||||
('locale', models.CharField(max_length=10, choices=[('de', 'German'), ('en', 'English')], verbose_name='Default locale')),
|
||||
('timezone', models.CharField(max_length=100, default='UTC', verbose_name='Default timezone')),
|
||||
('currency', models.CharField(max_length=10, verbose_name='Default currency')),
|
||||
('date_from', models.DateTimeField(verbose_name='Event start time')),
|
||||
('date_to', models.DateTimeField(blank=True, null=True, verbose_name='Event end time')),
|
||||
('show_date_to', models.BooleanField(default=True, help_text="If disabled, only event's start date will be displayed to the public.", verbose_name='Show event end date')),
|
||||
('show_times', models.BooleanField(default=True, help_text="If disabled, the event's start and end date will be displayed without the time of day.", verbose_name='Show dates with time')),
|
||||
('presale_end', models.DateTimeField(blank=True, null=True, help_text='No items will be sold after this date.', verbose_name='End of presale')),
|
||||
('presale_start', models.DateTimeField(blank=True, null=True, help_text='No items will be sold before this date.', verbose_name='Start of presale')),
|
||||
('payment_term_days', models.PositiveIntegerField(default=14, help_text='The number of days after placing an order the user has to pay to preserve his reservation.', verbose_name='Payment term in days')),
|
||||
('payment_term_last', models.DateTimeField(blank=True, null=True, help_text='The last date any payments are accepted. This has precedence over the number of days configured above.', verbose_name='Last date of payments')),
|
||||
('plugins', models.TextField(blank=True, null=True, verbose_name='Plugins')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Events',
|
||||
'ordering': ('date_from', 'name'),
|
||||
'verbose_name': 'Event',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventPermission',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('can_change_settings', models.BooleanField(default=True, verbose_name='Can change event settings')),
|
||||
('can_change_items', models.BooleanField(default=True, verbose_name='Can change item settings')),
|
||||
('event', versions.models.VersionedForeignKey(to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(related_name='event_perms', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Event permissions',
|
||||
'verbose_name': 'Event permission',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=255, verbose_name='Item name')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('deleted', models.BooleanField(default=False)),
|
||||
('short_description', models.TextField(blank=True, null=True, help_text='This is shown below the item name in lists.', verbose_name='Short description')),
|
||||
('long_description', models.TextField(blank=True, null=True, verbose_name='Long description')),
|
||||
('default_price', models.DecimalField(decimal_places=2, max_digits=7, blank=True, null=True, verbose_name='Default price')),
|
||||
('tax_rate', models.DecimalField(decimal_places=2, max_digits=7, blank=True, null=True, verbose_name='Taxes included in percent')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Items',
|
||||
'verbose_name': 'Item',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemCategory',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=255, verbose_name='Category name')),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('event', versions.models.VersionedForeignKey(related_name='categories', to='pretixbase.Event')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Item categories',
|
||||
'ordering': ('position',),
|
||||
'verbose_name': 'Item category',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemVariation',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('default_price', models.DecimalField(decimal_places=2, max_digits=7, blank=True, null=True, verbose_name='Default price')),
|
||||
('item', versions.models.VersionedForeignKey(related_name='variations', to='pretixbase.Item')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Item variations',
|
||||
'verbose_name': 'Item variation',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('status', models.CharField(max_length=3, choices=[('p', 'pending'), ('n', 'paid'), ('e', 'expired'), ('c', 'cancelled')], verbose_name='Status')),
|
||||
('datetime', models.DateTimeField(verbose_name='Date', auto_now_add=True)),
|
||||
('expires', models.DateTimeField(verbose_name='Expiration date')),
|
||||
('payment_date', models.DateTimeField(verbose_name='Payment date')),
|
||||
('payment_info', models.TextField(verbose_name='Payment information')),
|
||||
('total', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Total amount')),
|
||||
('event', versions.models.VersionedForeignKey(verbose_name='Event', to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(null=True, verbose_name='User', blank=True, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Orders',
|
||||
'verbose_name': 'Order',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderPosition',
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Order positions',
|
||||
'verbose_name': 'Order position',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organizer',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('slug', models.CharField(unique=True, max_length=50, db_index=True, verbose_name='Slug')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Organizers',
|
||||
'ordering': ('name',),
|
||||
'verbose_name': 'Organizer',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizerPermission',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('can_create_events', models.BooleanField(default=True, verbose_name='Can create events')),
|
||||
('organizer', versions.models.VersionedForeignKey(to='pretixbase.Organizer')),
|
||||
('user', models.ForeignKey(related_name='organizer_perms', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Organizer permissions',
|
||||
'verbose_name': 'Organizer permission',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Property',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=250, verbose_name='Property name')),
|
||||
('event', versions.models.VersionedForeignKey(related_name='properties', to='pretixbase.Event')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Item properties',
|
||||
'verbose_name': 'Item property',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PropertyValue',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('value', models.CharField(max_length=250, verbose_name='Value')),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('prop', versions.models.VersionedForeignKey(related_name='values', to='pretixbase.Property')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Property values',
|
||||
'ordering': ('position',),
|
||||
'verbose_name': 'Property value',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('question', models.TextField(verbose_name='Question')),
|
||||
('type', models.CharField(max_length=5, choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], verbose_name='Question type')),
|
||||
('required', models.BooleanField(default=False, verbose_name='Required question')),
|
||||
('event', versions.models.VersionedForeignKey(related_name='questions', to='pretixbase.Event')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Questions',
|
||||
'verbose_name': 'Question',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionAnswer',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('answer', models.TextField()),
|
||||
('cartposition', models.ForeignKey(null=True, to='pretixbase.CartPosition', blank=True)),
|
||||
('orderposition', models.ForeignKey(null=True, to='pretixbase.OrderPosition', blank=True)),
|
||||
('question', versions.models.VersionedForeignKey(to='pretixbase.Question')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quota',
|
||||
fields=[
|
||||
('id', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('identity', models.CharField(max_length=36)),
|
||||
('version_start_date', models.DateTimeField()),
|
||||
('version_end_date', models.DateTimeField(blank=True, null=True, default=None)),
|
||||
('version_birth_date', models.DateTimeField()),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('size', models.PositiveIntegerField(verbose_name='Total capacity')),
|
||||
('event', versions.models.VersionedForeignKey(related_name='quotas', to='pretixbase.Event', verbose_name='Event')),
|
||||
('items', versions.models.VersionedManyToManyField(blank=True, related_name='quotas', to='pretixbase.Item', verbose_name='Item')),
|
||||
('lock_cache', models.ManyToManyField(blank=True, to='pretixbase.CartPosition')),
|
||||
('order_cache', models.ManyToManyField(blank=True, to='pretixbase.OrderPosition')),
|
||||
('variations', pretix.base.models.VariationsField(blank=True, related_name='quotas',
|
||||
to='pretixbase.ItemVariation', verbose_name='Variations')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Quotas',
|
||||
'verbose_name': 'Quota',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='questionanswer',
|
||||
unique_together=set([('id', 'identity')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizer',
|
||||
name='permitted',
|
||||
field=models.ManyToManyField(through='pretixbase.OrganizerPermission', related_name='organizers', to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='answers',
|
||||
field=versions.models.VersionedManyToManyField(through='pretixbase.QuestionAnswer', to='pretixbase.Question', verbose_name='Answers'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='item',
|
||||
field=versions.models.VersionedForeignKey(verbose_name='Item', to='pretixbase.Item'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='order',
|
||||
field=versions.models.VersionedForeignKey(verbose_name='Order', to='pretixbase.Order'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='variation',
|
||||
field=versions.models.VersionedForeignKey(null=True, verbose_name='Variation', blank=True, to='pretixbase.ItemVariation'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='itemvariation',
|
||||
name='values',
|
||||
field=versions.models.VersionedManyToManyField(related_name='variations', to='pretixbase.PropertyValue'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='category',
|
||||
field=versions.models.VersionedForeignKey(on_delete=django.db.models.deletion.PROTECT, null=True, related_name='items', verbose_name='Category', blank=True, to='pretixbase.ItemCategory'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='event',
|
||||
field=versions.models.VersionedForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='items', to='pretixbase.Event', verbose_name='Event'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='properties',
|
||||
field=versions.models.VersionedManyToManyField(blank=True, related_name='items', help_text="The selected properties will be available for the user to select. After saving this field, move to the 'Variations' tab to configure the details.", verbose_name='Properties', to='pretixbase.Property'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='questions',
|
||||
field=versions.models.VersionedManyToManyField(blank=True, related_name='items', help_text='The user will be asked to fill in answers for the selected questions', verbose_name='Questions', to='pretixbase.Question'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='organizer',
|
||||
field=versions.models.VersionedForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='events', to='pretixbase.Organizer'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='permitted',
|
||||
field=models.ManyToManyField(through='pretixbase.EventPermission', related_name='events', to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='event',
|
||||
field=versions.models.VersionedForeignKey(verbose_name='Event', to='pretixbase.Event'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='item',
|
||||
field=versions.models.VersionedForeignKey(verbose_name='Item', to='pretixbase.Item'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='user',
|
||||
field=models.ForeignKey(null=True, verbose_name='User', blank=True, to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='variation',
|
||||
field=versions.models.VersionedForeignKey(null=True, verbose_name='Variation', blank=True, to='pretixbase.ItemVariation'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, null=True, related_name='users', to='pretixbase.Event', blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(related_name='user_set', help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups', related_query_name='user', blank=True, to='auth.Group'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(related_name='user_set', help_text='Specific permissions for this user.', verbose_name='user permissions', related_query_name='user', blank=True, to='auth.Permission'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='user',
|
||||
unique_together=set([('event', 'username')]),
|
||||
),
|
||||
]
|
||||
41
src/pretix/base/migrations/0002_auto_20150211_2031.py
Normal file
41
src/pretix/base/migrations/0002_auto_20150211_2031.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import pretix.base.models
|
||||
import versions.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='itemcategory',
|
||||
options={'verbose_name_plural': 'Item categories', 'verbose_name': 'Item category', 'ordering': ('position', 'id')},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='cartposition',
|
||||
old_name='total',
|
||||
new_name='price',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='currency',
|
||||
field=models.CharField(verbose_name='Default currency', default='EUR', max_length=10),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='locale',
|
||||
field=models.CharField(verbose_name='Default locale', choices=[('de', 'German'), ('en', 'English')], default='en', max_length=10),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='questionanswer',
|
||||
unique_together=set([]),
|
||||
),
|
||||
]
|
||||
24
src/pretix/base/migrations/0003_auto_20150211_2042.py
Normal file
24
src/pretix/base/migrations/0003_auto_20150211_2042.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import versions.models
|
||||
import pretix.base.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0002_auto_20150211_2031'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='quota',
|
||||
name='lock_cache',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='quota',
|
||||
name='order_cache',
|
||||
),
|
||||
]
|
||||
78
src/pretix/base/migrations/0004_auto_20150211_2330.py
Normal file
78
src/pretix/base/migrations/0004_auto_20150211_2330.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.utils.timezone import utc
|
||||
import versions.models
|
||||
import datetime
|
||||
import pretix.base.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0003_auto_20150211_2042'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='identity',
|
||||
field=models.CharField(default='LEGACY', max_length=36),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='version_birth_date',
|
||||
field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 3, 234665, tzinfo=utc)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='version_end_date',
|
||||
field=models.DateTimeField(blank=True, null=True, default=None),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='version_start_date',
|
||||
field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 3, 234665, tzinfo=utc)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='identity',
|
||||
field=models.CharField(default='LEGACY', max_length=36),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='version_birth_date',
|
||||
field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 15, 115790, tzinfo=utc)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='version_end_date',
|
||||
field=models.DateTimeField(blank=True, null=True, default=None),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='version_start_date',
|
||||
field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 21, 726769, tzinfo=utc)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cartposition',
|
||||
name='id',
|
||||
field=models.CharField(primary_key=True, serialize=False, max_length=36),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderposition',
|
||||
name='id',
|
||||
field=models.CharField(primary_key=True, serialize=False, max_length=36),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
28
src/pretix/base/migrations/0005_auto_20150212_0901.py
Normal file
28
src/pretix/base/migrations/0005_auto_20150212_0901.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import pretix.base.models
|
||||
import versions.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0004_auto_20150211_2330'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='payment_date',
|
||||
field=models.DateTimeField(null=True, blank=True, verbose_name='Payment date'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='payment_info',
|
||||
field=models.TextField(null=True, blank=True, verbose_name='Payment information'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
22
src/pretix/base/migrations/0006_auto_20150212_0908.py
Normal file
22
src/pretix/base/migrations/0006_auto_20150212_0908.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import pretix.base.models
|
||||
import versions.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0005_auto_20150212_0901'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cartposition',
|
||||
name='datetime',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
22
src/pretix/base/migrations/0007_auto_20150212_0939.py
Normal file
22
src/pretix/base/migrations/0007_auto_20150212_0939.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import versions.models
|
||||
import pretix.base.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0006_auto_20150212_0908'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='max_items_per_order',
|
||||
field=models.IntegerField(verbose_name='Maximum number of items per order', default=10),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
20
src/pretix/base/migrations/0008_quota_locked.py
Normal file
20
src/pretix/base/migrations/0008_quota_locked.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0007_auto_20150212_0939'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='locked',
|
||||
field=models.DateTimeField(null=True, blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
@@ -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,),
|
||||
),
|
||||
]
|
||||
0
src/pretix/base/migrations/__init__.py
Normal file
0
src/pretix/base/migrations/__init__.py
Normal file
1451
src/pretix/base/models.py
Normal file
1451
src/pretix/base/models.py
Normal file
File diff suppressed because it is too large
Load Diff
23
src/pretix/base/plugins.py
Normal file
23
src/pretix/base/plugins.py
Normal file
@@ -0,0 +1,23 @@
|
||||
try: # NOQA
|
||||
from enum import Enum
|
||||
except ImportError: # NOQA
|
||||
from flufl.enum import Enum # remove this dependency when support for python <=3.3 is dropped
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
class PluginType(Enum):
|
||||
RESTRICTION = 1
|
||||
|
||||
|
||||
def get_all_plugins() -> "List[class]":
|
||||
"""
|
||||
Returns the PretixPluginMeta classes of all plugins found in the installed Django apps.
|
||||
"""
|
||||
plugins = []
|
||||
for app in apps.get_app_configs():
|
||||
if hasattr(app, 'PretixPluginMeta'):
|
||||
meta = app.PretixPluginMeta
|
||||
meta.module = app.name
|
||||
plugins.append(meta)
|
||||
return plugins
|
||||
53
src/pretix/base/signals.py
Normal file
53
src/pretix/base/signals.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import django.dispatch
|
||||
from django.apps import apps
|
||||
from django.dispatch.dispatcher import NO_RECEIVERS
|
||||
|
||||
from .models import Event
|
||||
|
||||
|
||||
class EventPluginSignal(django.dispatch.Signal):
|
||||
"""
|
||||
This is an extension to Django's built-in signals which differs in a way that it sends
|
||||
out it's events only to receivers which belong to plugins that are enabled for the given
|
||||
Event.
|
||||
"""
|
||||
|
||||
def send(self, sender, **named):
|
||||
"""
|
||||
Send signal from sender to all connected receivers that belong to
|
||||
plugins enabled for the given Event.
|
||||
|
||||
sender is required to be an instance of ``pretix.base.models.Event``.
|
||||
"""
|
||||
assert isinstance(sender, Event)
|
||||
|
||||
responses = []
|
||||
if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
|
||||
return responses
|
||||
|
||||
for receiver in self._live_receivers(sender):
|
||||
# Find the Django application this belongs to
|
||||
searchpath = receiver.__module__
|
||||
app = None
|
||||
while "." in searchpath:
|
||||
try:
|
||||
if apps.is_installed(searchpath):
|
||||
app = apps.get_app_config(searchpath.split(".")[-1])
|
||||
except LookupError:
|
||||
pass
|
||||
searchpath, mod = searchpath.rsplit(".", 1)
|
||||
|
||||
# Only fire receivers from active plugins
|
||||
if app.name in sender.get_plugins():
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
"""
|
||||
This signal is sent out every time some component of pretix wants to know whether a specific
|
||||
item or variation is available for sell. The item will only be sold, if all (active) receivers
|
||||
return a positive result (see plugin API documentation for details).
|
||||
"""
|
||||
determine_availability = EventPluginSignal(
|
||||
providing_args=["item", "variations", "context", "cache"]
|
||||
)
|
||||
1
src/pretix/base/static/bootstrap
Submodule
1
src/pretix/base/static/bootstrap
Submodule
Submodule src/pretix/base/static/bootstrap added at 0d7b85ece7
1
src/pretix/base/static/fontawesome
Submodule
1
src/pretix/base/static/fontawesome
Submodule
Submodule src/pretix/base/static/fontawesome added at c79474df99
4
src/pretix/base/static/jquery/js/jquery-2.1.1.min.js
vendored
Normal file
4
src/pretix/base/static/jquery/js/jquery-2.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
110
src/pretix/base/tests/__init__.py
Normal file
110
src/pretix/base/tests/__init__.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
import sys
|
||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||
|
||||
from django.conf import settings
|
||||
from selenium import webdriver
|
||||
|
||||
RUN_LOCAL = ('SAUCE_USERNAME' not in os.environ)
|
||||
"""
|
||||
For a long time, we used SauceLabs for CI testing, because they provide free
|
||||
browser VMs for Open Source projects. However, more tests failed because of
|
||||
connection timeouts to SauceLabs than for real reasons, so we're using
|
||||
PhantomJS now. However, we'll keep the SauceClient code here as it might prove
|
||||
useful some day.
|
||||
"""
|
||||
|
||||
if RUN_LOCAL:
|
||||
# could add Chrome, Firefox, etc... here
|
||||
BROWSERS = [os.environ.get('TEST_BROWSER', 'PhantomJS')]
|
||||
else:
|
||||
from sauceclient import SauceClient
|
||||
USERNAME = os.environ.get('SAUCE_USERNAME')
|
||||
ACCESS_KEY = os.environ.get('SAUCE_ACCESS_KEY')
|
||||
sauce = SauceClient(USERNAME, ACCESS_KEY)
|
||||
|
||||
BROWSERS = [
|
||||
{"platform": "Mac OS X 10.9",
|
||||
"browserName": "chrome",
|
||||
"version": "35"},
|
||||
{"platform": "Windows 8.1",
|
||||
"browserName": "internet explorer",
|
||||
"version": "11"},
|
||||
{"platform": "Linux",
|
||||
"browserName": "firefox",
|
||||
"version": "29"}]
|
||||
|
||||
|
||||
def on_platforms():
|
||||
if RUN_LOCAL:
|
||||
def decorator(base_class):
|
||||
module = sys.modules[base_class.__module__].__dict__
|
||||
for i, platform in enumerate(BROWSERS):
|
||||
d = dict(base_class.__dict__)
|
||||
d['browser'] = platform
|
||||
name = "%s_%s" % (base_class.__name__, i + 1)
|
||||
module[name] = type(name, (base_class,), d)
|
||||
pass
|
||||
return decorator
|
||||
|
||||
def decorator(base_class):
|
||||
module = sys.modules[base_class.__module__].__dict__
|
||||
for i, platform in enumerate(BROWSERS):
|
||||
d = dict(base_class.__dict__)
|
||||
d['desired_capabilities'] = platform
|
||||
name = "%s_%s" % (base_class.__name__, i + 1)
|
||||
module[name] = type(name, (base_class,), d)
|
||||
return decorator
|
||||
|
||||
|
||||
class BrowserTest(StaticLiveServerTestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
settings.DEBUG = ('--debug' in sys.argv)
|
||||
|
||||
def setUp(self):
|
||||
if RUN_LOCAL:
|
||||
self.setUpLocal()
|
||||
else:
|
||||
self.setUpSauce()
|
||||
|
||||
def tearDown(self):
|
||||
if RUN_LOCAL:
|
||||
self.tearDownLocal()
|
||||
else:
|
||||
self.tearDownSauce()
|
||||
|
||||
def setUpSauce(self):
|
||||
if 'TRAVIS_JOB_NUMBER' in os.environ:
|
||||
self.desired_capabilities['tunnel-identifier'] = \
|
||||
os.environ['TRAVIS_JOB_NUMBER']
|
||||
self.desired_capabilities['build'] = os.environ['TRAVIS_BUILD_NUMBER']
|
||||
self.desired_capabilities['tags'] = \
|
||||
[os.environ['TRAVIS_PYTHON_VERSION'], 'CI']
|
||||
self.desired_capabilities['name'] = self.id()
|
||||
|
||||
sauce_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub"
|
||||
self.driver = webdriver.Remote(
|
||||
desired_capabilities=self.desired_capabilities,
|
||||
command_executor=sauce_url % (USERNAME, ACCESS_KEY)
|
||||
)
|
||||
self.driver.implicitly_wait(5)
|
||||
|
||||
def setUpLocal(self):
|
||||
self.driver = getattr(webdriver, self.browser)()
|
||||
self.driver.implicitly_wait(3)
|
||||
|
||||
def tearDownLocal(self):
|
||||
self.driver.quit()
|
||||
|
||||
def tearDownSauce(self):
|
||||
print("\nLink to your job: \n "
|
||||
"https://saucelabs.com/jobs/%s \n" % self.driver.session_id)
|
||||
try:
|
||||
if sys.exc_info() == (None, None, None):
|
||||
sauce.jobs.update_job(self.driver.session_id, passed=True)
|
||||
else:
|
||||
sauce.jobs.update_job(self.driver.session_id, passed=False)
|
||||
finally:
|
||||
self.driver.quit()
|
||||
47
src/pretix/base/tests/test_cache.py
Normal file
47
src/pretix/base/tests/test_cache.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import random
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.cache import cache as django_cache
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
|
||||
|
||||
class CacheTest(TestCase):
|
||||
"""
|
||||
This test case tests the invalidation of the event related
|
||||
cache.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
self.cache = self.event.get_cache()
|
||||
randint = random.random()
|
||||
self.testkey = "test" + str(randint)
|
||||
|
||||
def test_interference(self):
|
||||
django_cache.clear()
|
||||
self.cache.set(self.testkey, "foo")
|
||||
self.assertIsNone(django_cache.get(self.testkey))
|
||||
self.assertIn(self.cache.get(self.testkey), (None, "foo"))
|
||||
|
||||
def test_longkey(self):
|
||||
self.cache.set(self.testkey * 100, "foo")
|
||||
self.assertEquals(self.cache.get(self.testkey * 100), "foo")
|
||||
|
||||
def test_invalidation(self):
|
||||
self.cache.set(self.testkey, "foo")
|
||||
self.cache.clear()
|
||||
self.assertIsNone(self.cache.get(self.testkey))
|
||||
|
||||
def test_many(self):
|
||||
inp = {
|
||||
'a': 'foo',
|
||||
'b': 'bar',
|
||||
}
|
||||
self.cache.set_many(inp)
|
||||
self.assertEquals(inp, self.cache.get_many(inp.keys()))
|
||||
72
src/pretix/base/tests/test_middleware.py
Normal file
72
src/pretix/base/tests/test_middleware.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.utils.timezone import now
|
||||
from django.conf import settings
|
||||
|
||||
from pretix.base.models import Event, Organizer, User
|
||||
|
||||
|
||||
class LocaleDeterminationTest(TestCase):
|
||||
"""
|
||||
This test case tests various methods around the properties /
|
||||
variations concept.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
self.TEST_LOCALE = 'de' if settings.LANGUAGE_CODE == 'en' else 'en'
|
||||
self.TEST_LOCALE_LONG = 'de-AT' if settings.LANGUAGE_CODE == 'en' else 'en-NZ'
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy')
|
||||
|
||||
def test_global_default(self):
|
||||
c = Client()
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, settings.LANGUAGE_CODE)
|
||||
|
||||
def test_browser_default(self):
|
||||
c = Client(HTTP_ACCEPT_LANGUAGE=self.TEST_LOCALE)
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, self.TEST_LOCALE)
|
||||
|
||||
c = Client(HTTP_ACCEPT_LANGUAGE=self.TEST_LOCALE_LONG)
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, self.TEST_LOCALE)
|
||||
|
||||
def test_unknown_browser_default(self):
|
||||
c = Client(HTTP_ACCEPT_LANGUAGE='sjn')
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, settings.LANGUAGE_CODE)
|
||||
|
||||
def test_cookie_settings(self):
|
||||
c = Client()
|
||||
cookies = c.cookies
|
||||
cookies[settings.LANGUAGE_COOKIE_NAME] = self.TEST_LOCALE
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, self.TEST_LOCALE)
|
||||
|
||||
cookies[settings.LANGUAGE_COOKIE_NAME] = self.TEST_LOCALE_LONG
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, self.TEST_LOCALE)
|
||||
|
||||
def test_user_settings(self):
|
||||
c = Client()
|
||||
self.user.locale = self.TEST_LOCALE
|
||||
self.user.save()
|
||||
response = c.post('/control/login', {
|
||||
'email': 'dummy@dummy.dummy',
|
||||
'password': 'dummy',
|
||||
})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = c.get('/control/login')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, self.TEST_LOCALE)
|
||||
358
src/pretix/base/tests/test_models.py
Normal file
358
src/pretix/base/tests/test_models.py
Normal file
@@ -0,0 +1,358 @@
|
||||
from datetime import timedelta
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Organizer, Item, ItemVariation,
|
||||
Property, PropertyValue, User, Quota,
|
||||
Order, OrderPosition, CartPosition,
|
||||
OrganizerSetting)
|
||||
from pretix.base.types import VariationDict
|
||||
|
||||
|
||||
class ItemVariationsTest(TestCase):
|
||||
"""
|
||||
This test case tests various methods around the properties /
|
||||
variations concept.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
p = Property.objects.create(event=self.event, name='Size')
|
||||
PropertyValue.objects.create(prop=p, value='S')
|
||||
PropertyValue.objects.create(prop=p, value='M')
|
||||
PropertyValue.objects.create(prop=p, value='L')
|
||||
p = Property.objects.create(event=self.event, name='Color')
|
||||
PropertyValue.objects.create(prop=p, value='black')
|
||||
PropertyValue.objects.create(prop=p, value='blue')
|
||||
|
||||
def test_variationdict(self):
|
||||
i = Item.objects.create(event=self.event, name='Dummy')
|
||||
p = Property.objects.get(event=self.event, name='Size')
|
||||
i.properties.add(p)
|
||||
iv = ItemVariation.objects.create(item=i)
|
||||
pv = PropertyValue.objects.get(prop=p, value='S')
|
||||
iv.values.add(pv)
|
||||
|
||||
variations = i.get_all_variations()
|
||||
|
||||
for vd in variations:
|
||||
for i, v in vd.relevant_items():
|
||||
self.assertIs(type(v), PropertyValue)
|
||||
|
||||
for v in vd.relevant_values():
|
||||
self.assertIs(type(v), PropertyValue)
|
||||
|
||||
if vd[p.pk] == pv:
|
||||
vd1 = vd
|
||||
|
||||
vd2 = VariationDict()
|
||||
vd2[p.pk] = pv
|
||||
|
||||
self.assertEqual(vd2.identify(), vd1.identify())
|
||||
self.assertEqual(vd2, vd1)
|
||||
|
||||
vd2[p.pk] = PropertyValue.objects.get(prop=p, value='M')
|
||||
|
||||
self.assertNotEqual(vd2.identify(), vd.identify())
|
||||
self.assertNotEqual(vd2, vd1)
|
||||
|
||||
vd3 = vd2.copy()
|
||||
self.assertEqual(vd3, vd2)
|
||||
|
||||
vd2[p.pk] = pv
|
||||
self.assertNotEqual(vd3, vd2)
|
||||
|
||||
vd4 = VariationDict()
|
||||
vd4[4] = 'b'
|
||||
vd4[2] = 'a'
|
||||
self.assertEqual(vd4.ordered_values(), ['a', 'b'])
|
||||
|
||||
def test_get_all_variations(self):
|
||||
i = Item.objects.create(event=self.event, name='Dummy')
|
||||
|
||||
# No properties available
|
||||
v = i.get_all_variations()
|
||||
self.assertEqual(len(v), 1)
|
||||
self.assertEqual(v[0], {})
|
||||
|
||||
# One property, no variations
|
||||
p = Property.objects.get(event=self.event, name='Size')
|
||||
i.properties.add(p)
|
||||
v = i.get_all_variations()
|
||||
self.assertIs(type(v), list)
|
||||
self.assertEqual(len(v), 3)
|
||||
values = []
|
||||
for var in v:
|
||||
self.assertIs(type(var), VariationDict)
|
||||
self.assertIn(p.pk, var)
|
||||
self.assertIs(type(var[p.pk]), PropertyValue)
|
||||
values.append(var[p.pk].value)
|
||||
self.assertEqual(sorted(values), sorted(['S', 'M', 'L']))
|
||||
|
||||
# One property, one variation
|
||||
iv = ItemVariation.objects.create(item=i)
|
||||
iv.values.add(PropertyValue.objects.get(prop=p, value='S'))
|
||||
v = i.get_all_variations()
|
||||
self.assertIs(type(v), list)
|
||||
self.assertEqual(len(v), 3)
|
||||
values = []
|
||||
num_variations = 0
|
||||
for var in v:
|
||||
self.assertIs(type(var), VariationDict)
|
||||
if 'variation' in var and type(var['variation']) is ItemVariation:
|
||||
self.assertEqual(iv.pk, var['variation'].pk)
|
||||
values.append(var['variation'].values.all()[0].value)
|
||||
num_variations += 1
|
||||
elif p.pk in var:
|
||||
self.assertIs(type(var[p.pk]), PropertyValue)
|
||||
values.append(var[p.pk].value)
|
||||
self.assertEqual(sorted(values), sorted(['S', 'M', 'L']))
|
||||
self.assertEqual(num_variations, 1)
|
||||
|
||||
# Two properties, one variation
|
||||
p2 = Property.objects.get(event=self.event, name='Color')
|
||||
i.properties.add(p2)
|
||||
iv.values.add(PropertyValue.objects.get(prop=p2, value='black'))
|
||||
v = i.get_all_variations()
|
||||
self.assertIs(type(v), list)
|
||||
self.assertEqual(len(v), 6)
|
||||
values = []
|
||||
num_variations = 0
|
||||
for var in v:
|
||||
self.assertIs(type(var), VariationDict)
|
||||
if 'variation' in var:
|
||||
self.assertEqual(iv.pk, var['variation'].pk)
|
||||
values.append(sorted([ivv.value for ivv in iv.values.all()]))
|
||||
self.assertEqual(sorted([ivv.value for ivv in iv.values.all()]), sorted(['S', 'black']))
|
||||
num_variations += 1
|
||||
else:
|
||||
values.append(sorted([pv.value for pv in var.values()]))
|
||||
self.assertEqual(sorted(values), sorted([
|
||||
['S', 'black'],
|
||||
['S', 'blue'],
|
||||
['M', 'black'],
|
||||
['M', 'blue'],
|
||||
['L', 'black'],
|
||||
['L', 'blue'],
|
||||
]))
|
||||
self.assertEqual(num_variations, 1)
|
||||
|
||||
|
||||
class VersionableTestCase(TestCase):
|
||||
|
||||
def test_shallow_cone(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
old = Item.objects.create(event=event, name='Dummy', default_price=14)
|
||||
prop = Property.objects.create(event=event, name='Size')
|
||||
old.properties.add(prop)
|
||||
new = old.clone_shallow()
|
||||
self.assertIsNone(new.version_end_date)
|
||||
self.assertIsNotNone(old.version_end_date)
|
||||
self.assertEqual(new.properties.count(), 0)
|
||||
self.assertEqual(old.properties.count(), 1)
|
||||
|
||||
|
||||
class UserTestCase(TestCase):
|
||||
|
||||
def test_identifier_local(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
u = User(event=event, username='tester')
|
||||
u.set_password("test")
|
||||
u.save()
|
||||
self.assertEqual(u.identifier, "%s@%s.event.pretix" % (u.username.lower(), event.id))
|
||||
|
||||
def test_identifier_global(self):
|
||||
u = User(email='test@example.com')
|
||||
u.set_password("test")
|
||||
u.save()
|
||||
self.assertEqual(u.identifier, "test@example.com")
|
||||
|
||||
|
||||
class QuotaTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
self.quota = Quota.objects.create(name="Test", size=2, event=self.event)
|
||||
self.item1 = Item.objects.create(event=self.event, name="Ticket")
|
||||
self.item2 = Item.objects.create(event=self.event, name="T-Shirt")
|
||||
p = Property.objects.create(event=self.event, name='Size')
|
||||
pv1 = PropertyValue.objects.create(prop=p, value='S')
|
||||
PropertyValue.objects.create(prop=p, value='M')
|
||||
PropertyValue.objects.create(prop=p, value='L')
|
||||
self.var1 = ItemVariation.objects.create(item=self.item2)
|
||||
self.var1.values.add(pv1)
|
||||
self.item2.properties.add(p)
|
||||
|
||||
def test_available(self):
|
||||
self.quota.items.add(self.item1)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
|
||||
self.quota.items.add(self.item2)
|
||||
self.quota.variations.add(self.var1)
|
||||
try:
|
||||
self.item2.check_quotas()
|
||||
self.assertTrue(False)
|
||||
except:
|
||||
pass
|
||||
self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
|
||||
|
||||
def test_sold_out(self):
|
||||
self.quota.items.add(self.item1)
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
|
||||
expires=now() + timedelta(days=3),
|
||||
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.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
|
||||
|
||||
self.quota.items.add(self.item2)
|
||||
self.quota.variations.add(self.var1)
|
||||
self.quota.size = 3
|
||||
self.quota.save()
|
||||
self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2)
|
||||
self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
|
||||
|
||||
def test_ordered(self):
|
||||
self.quota.items.add(self.item1)
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
OrderPosition.objects.create(order=order, item=self.item1, price=2)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
OrderPosition.objects.create(order=order, item=self.item1, price=2)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))
|
||||
|
||||
order.expires = now() - timedelta(days=3)
|
||||
order.save()
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_reserved(self):
|
||||
self.quota.items.add(self.item1)
|
||||
self.quota.size = 3
|
||||
self.quota.save()
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
OrderPosition.objects.create(order=order, item=self.item1, price=2)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
|
||||
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
OrderPosition.objects.create(order=order, item=self.item1, price=2)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2,
|
||||
expires=now() + timedelta(days=3))
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
|
||||
|
||||
cp.expires = now() - timedelta(days=3)
|
||||
cp.save()
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
self.quota.items.add(self.item2)
|
||||
self.quota.variations.add(self.var1)
|
||||
cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1,
|
||||
price=2, expires=now() + timedelta(days=3))
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
|
||||
|
||||
def test_multiple(self):
|
||||
self.quota.items.add(self.item1)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
|
||||
|
||||
quota2 = Quota.objects.create(event=self.event, name="Test 2", size=1)
|
||||
quota2.items.add(self.item1)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
quota2.size = 0
|
||||
quota2.save()
|
||||
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)
|
||||
57
src/pretix/base/tests/test_plugins.py
Normal file
57
src/pretix/base/tests/test_plugins.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from django.conf import settings
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
from pretix.base.signals import determine_availability
|
||||
|
||||
|
||||
class PluginRegistryTest(TestCase):
|
||||
"""
|
||||
This test case performs tests for the plugin registry.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
|
||||
def test_plugin_names(self):
|
||||
for mod in get_all_plugins():
|
||||
self.assertIn(mod.module, settings.INSTALLED_APPS)
|
||||
|
||||
def test_metadata(self):
|
||||
for mod in get_all_plugins():
|
||||
self.assertTrue(hasattr(mod, 'name'))
|
||||
self.assertTrue(hasattr(mod, 'version'))
|
||||
self.assertTrue(hasattr(mod, 'type'))
|
||||
|
||||
|
||||
class PluginSignalTest(TestCase):
|
||||
"""
|
||||
This test case tests the EventPluginSignal handler
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
self.event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
)
|
||||
|
||||
def test_no_plugins_active(self):
|
||||
self.event.plugins = ''
|
||||
self.event.save()
|
||||
responses = determine_availability.send(self.event)
|
||||
self.assertEqual(len(responses), 0)
|
||||
|
||||
def test_one_plugin_active(self):
|
||||
self.event.plugins = 'pretix.plugins.testdummy'
|
||||
self.event.save()
|
||||
payload = {'foo': 'bar'}
|
||||
responses = determine_availability.send(self.event, **payload)
|
||||
self.assertEqual(len(responses), 1)
|
||||
self.assertIn('pretix.plugins.testdummy.signals', [r[0].__module__ for r in responses])
|
||||
93
src/pretix/base/types.py
Normal file
93
src/pretix/base/types.py
Normal file
@@ -0,0 +1,93 @@
|
||||
class VariationDict(dict):
|
||||
"""
|
||||
A VariationDict object behaves exactle the same as the Python built-in
|
||||
``dict`` does, but adds some special methods. It is used for the dicts
|
||||
returned by ``Item.get_all_variations()`` to avoid duplicate code in the
|
||||
code calling this method.
|
||||
"""
|
||||
IGNORE_KEYS = ('variation', 'key', 'available', 'price')
|
||||
|
||||
def relevant_items(self) -> "list[(str, PropertyValue)]":
|
||||
"""
|
||||
Iterate over all items with numeric keys.
|
||||
|
||||
This is in use because the variation dictionaries use property ids
|
||||
as key and have some special keys like 'variation'.
|
||||
"""
|
||||
return (i for i in self.items() if i[0] not in self.IGNORE_KEYS)
|
||||
|
||||
def relevant_values(self) -> "list[PropertyValue]":
|
||||
"""
|
||||
Iterate over all values with numeric keys.
|
||||
|
||||
This is in use because the variation dictionaries use property ids
|
||||
as key and have some special keys like 'variation'.
|
||||
"""
|
||||
return (i[1] for i in self.items() if i[0] not in self.IGNORE_KEYS)
|
||||
|
||||
def identify(self) -> str:
|
||||
"""
|
||||
Build a simple and unique identifier for this dict. This can be any string
|
||||
used to compare one VariationDict to others.
|
||||
|
||||
In the current implementation, it is a string containing a list of
|
||||
the PropertyValue id's, sorted by the Property id's and is therefore
|
||||
unique among one item.
|
||||
"""
|
||||
def order_key(i):
|
||||
return i[0]
|
||||
return ",".join((
|
||||
str(v[1].pk) for v in sorted(self.relevant_items(), key=order_key)
|
||||
))
|
||||
|
||||
def key(self) -> str:
|
||||
"""
|
||||
Build an identifier for this dict which exactly specifies the combination
|
||||
for this variation without any doubt. This can be used to "talk" about a
|
||||
variation in network communication.
|
||||
|
||||
In the current implementation, it is a string containing a list of
|
||||
the propertyid:valueid tuples without any specific order and is therefore
|
||||
not useful to compare two VariationDicts for equality.
|
||||
"""
|
||||
return ",".join((
|
||||
str(v[0]) + ":" + str(v[1].pk) for v in self.relevant_items()
|
||||
))
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is type(self):
|
||||
return self.identify() == other.identify()
|
||||
else:
|
||||
return super().__eq__(other)
|
||||
|
||||
def empty(self):
|
||||
"""
|
||||
Returns true, if this VariationDict does not contain any "real" data like
|
||||
references to PropertyValues, but only "metadata".
|
||||
"""
|
||||
return not next(self.relevant_items(), False)
|
||||
|
||||
def ordered_values(self) -> "list[ItemVariation]":
|
||||
"""
|
||||
Returns a list of values ordered by their keys
|
||||
"""
|
||||
return [
|
||||
i[1] for i
|
||||
in sorted(
|
||||
[it for it in self.relevant_items()],
|
||||
key=lambda i: i[0]
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return " – ".join([v.value for v in self.ordered_values()])
|
||||
|
||||
def copy(self) -> "VariationDict":
|
||||
"""
|
||||
Return a one-level deep copy of this object (create a new
|
||||
VariationDict but make a shallow copy of the dict inside it).
|
||||
"""
|
||||
new = VariationDict()
|
||||
for k, v in self.items():
|
||||
new[k] = v
|
||||
return new
|
||||
Reference in New Issue
Block a user