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

View File

@@ -1,5 +1,5 @@
[run]
source = pretixbase,pretixcontrol,pretixpresale,pretixplugins
source = pretix
omit = */migrations/*,*/urls.py,*/tests/*,*/testdummy/*,*/admin.py
[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 import forms
from pretixbase.models import (
from pretix.base.models import (
User, Organizer, OrganizerPermission, Event, EventPermission,
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
@@ -39,14 +39,14 @@ class TixlUserCreationForm(forms.ModelForm):
return password2
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"])
if commit:
user.save()
return user
class TixlUserAdmin(UserAdmin):
class PretixUserAdmin(UserAdmin):
fieldsets = (
(None, {'fields': ('identifier', 'event', 'username', 'password')}),
@@ -59,7 +59,7 @@ class TixlUserAdmin(UserAdmin):
search_fields = ('identifier', 'username', 'givenname', 'familyname', 'email')
ordering = ('identifier',)
list_filter = ('is_staff', 'is_active', 'groups')
add_form = TixlUserCreationForm
add_form = PretixUserCreationForm
class OrganizerPermissionInline(admin.TabularInline):
@@ -126,7 +126,7 @@ class ItemAdmin(admin.ModelAdmin):
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(Event, EventAdmin)
admin.site.register(Property, PropertyAdmin)

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import django.core.validators
from django.conf import settings
import django.db.models.deletion
import django.utils.timezone
import pretixbase.models
import pretix.base.models
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')),
('lock_cache', models.ManyToManyField(blank=True, to='pretixbase.CartPosition')),
('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={
'verbose_name_plural': 'Quotas',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import models, migrations
import versions.models
import pretixbase.models
import pretix.base.models
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.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db.models import Q, Count
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import date as _date
@@ -227,6 +228,58 @@ class Organizer(Versionable):
def __str__(self):
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):
"""
@@ -382,10 +435,60 @@ class Event(Versionable):
"DATETIME_FORMAT" if self.show_times else "DATE_FORMAT"
)
def get_cache(self) -> "pretixbase.cache.EventRelatedCache":
from pretixbase.cache import EventRelatedCache
def get_cache(self) -> "pretix.base.cache.EventRelatedCache":
from pretix.base.cache import EventRelatedCache
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):
"""
@@ -745,14 +848,27 @@ class Item(Versionable):
return self._get_all_available_variations_cache
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()]
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 = []
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()
for v in var.values.all():
for v in values:
vardict[v.prop.identity] = v
vardict['variation'] = var
variations.append(vardict)
@@ -782,7 +898,7 @@ class Item(Versionable):
self._get_all_available_variations_cache = variations
return variations
def availability(self):
def check_quotas(self):
"""
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()
@@ -792,7 +908,7 @@ class Item(Versionable):
'but call this on their ItemVariation objects')
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
in sale by any restriction plugins.
@@ -859,6 +975,9 @@ class ItemVariation(Versionable):
verbose_name = _("Item variation")
verbose_name_plural = _("Item variations")
def __str__(self):
return str(self.to_variation_dict())
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
if self.item:
@@ -869,7 +988,7 @@ class ItemVariation(Versionable):
if self.item:
self.item.event.get_cache().clear()
def availability(self):
def check_quotas(self):
"""
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
@@ -884,7 +1003,7 @@ class ItemVariation(Versionable):
vd['variation'] = self
return vd
def execute_restrictions(self):
def check_restrictions(self):
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
@@ -913,7 +1032,7 @@ class VariationsField(VersionedManyToManyField):
"""
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
defaults = {
'form_class': FVariationsField,
@@ -1086,14 +1205,14 @@ class Quota(Versionable):
Q(variation__quotas__in=[self])
)
)
paid_orders = OrderPosition.objects.filter(
paid_orders = OrderPosition.objects.current.filter(
Q(order__status=Order.STATUS_PAID)
& quotalookup
).count()
if paid_orders >= self.size:
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__expires__gte=now())
& quotalookup
@@ -1101,7 +1220,7 @@ class Quota(Versionable):
if (paid_orders + pending_valid_orders) >= self.size:
return Quota.AVAILABILITY_ORDERED, 0
valid_cart_positions = CartPosition.objects.filter(
valid_cart_positions = CartPosition.objects.current.filter(
Q(expires__gte=now())
& quotalookup
).count()
@@ -1307,3 +1426,26 @@ class CartPosition(Versionable):
class Meta:
verbose_name = _("Cart position")
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]":
"""
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 = []
for app in apps.get_app_configs():
if hasattr(app, 'TixlPluginMeta'):
meta = app.TixlPluginMeta
if hasattr(app, 'PretixPluginMeta'):
meta = app.PretixPluginMeta
meta.module = app.name
plugins.append(meta)
return plugins

View File

@@ -17,7 +17,7 @@ class EventPluginSignal(django.dispatch.Signal):
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 ``pretixbase.models.Event``.
sender is required to be an instance of ``pretix.base.models.Event``.
"""
assert isinstance(sender, Event)

View File

@@ -2,7 +2,6 @@ import os
import sys
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import LiveServerTestCase
from django.conf import settings
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.utils.timezone import now
from pretixbase.models import Event, Organizer
from pretix.base.models import Event, Organizer
class CacheTest(TestCase):

View File

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

View File

@@ -2,12 +2,12 @@ from datetime import timedelta
from django.test import TestCase
from django.utils.timezone import now
from pretixbase.models import (
from pretix.base.models import (
Event, Organizer, Item, ItemVariation,
Property, PropertyValue, User, Quota,
Order, OrderPosition, CartPosition
)
from pretixbase.types import VariationDict
Order, OrderPosition, CartPosition,
OrganizerSetting)
from pretix.base.types import VariationDict
class ItemVariationsTest(TestCase):
@@ -202,15 +202,15 @@ class QuotaTestCase(TestCase):
def test_available(self):
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.variations.add(self.var1)
try:
self.item2.availability()
self.item2.check_quotas()
self.assertTrue(False)
except:
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):
self.quota.items.add(self.item1)
@@ -219,19 +219,19 @@ class QuotaTestCase(TestCase):
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_GONE, 0))
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.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,
expires=now() + timedelta(days=3),
total=4)
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):
self.quota.items.add(self.item1)
@@ -239,17 +239,17 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3),
total=4)
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,
expires=now() + timedelta(days=3),
total=4)
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.save()
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
def test_reserved(self):
self.quota.items.add(self.item1)
@@ -259,36 +259,100 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3),
total=4)
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,
expires=now() + timedelta(days=3),
total=4)
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,
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.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.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.availability(), (Quota.AVAILABILITY_RESERVED, 0))
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
def test_multiple(self):
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.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.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.conf import settings
from pretixbase.models import Event, Organizer
from pretixbase.plugins import get_all_plugins
from pretixbase.signals import determine_availability
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):
@@ -49,9 +49,9 @@ class PluginSignalTest(TestCase):
self.assertEqual(len(responses), 0)
def test_one_plugin_active(self):
self.event.plugins = 'pretixplugins.testdummy'
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('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.utils.translation import ugettext as _
from pretixbase.models import Event
from pretix.base.models import Event
class PermissionMiddleware:

View File

@@ -1,7 +1,7 @@
from django.http import HttpResponseForbidden
from django.utils.translation import ugettext as _
from pretixbase.models import EventPermission
from pretix.base.models import EventPermission
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 {
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 pretixbase.models import User
from pretixbase.tests import BrowserTest, on_platforms
from pretix.base.models import User
from pretix.base.tests import BrowserTest, on_platforms
@on_platforms()

View File

@@ -1,6 +1,6 @@
import datetime
from pretixbase.models import User, Organizer, Event, OrganizerPermission, EventPermission
from pretixbase.tests import BrowserTest, on_platforms
from pretix.base.models import User, Organizer, Event, OrganizerPermission, EventPermission
from pretix.base.tests import BrowserTest, 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.event1.slug))
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.driver.find_element_by_name("plugin:pretixplugins.timerestriction").click()
self.assertIn("Disable", self.driver.find_element_by_name("plugin:pretixplugins.timerestriction").text)
self.driver.find_element_by_name("plugin:pretixplugins.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)
self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").click()
self.assertIn("Disable", self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").text)
self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").click()
self.assertIn("Enable", self.driver.find_element_by_name("plugin:pretix.plugins.timerestriction").text)

View File

@@ -3,9 +3,9 @@ import time
import datetime
from django.utils import unittest
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
from pretixbase.tests import BrowserTest, on_platforms
from pretix.base.tests import BrowserTest, on_platforms
class ItemFormTest(BrowserTest):
@@ -18,7 +18,7 @@ class ItemFormTest(BrowserTest):
self.event1 = Event.objects.create(
organizer=self.orga1, name='30C3', slug='30c3',
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
)
)
OrganizerPermission.objects.create(organizer=self.orga1, user=self.user)
EventPermission.objects.create(event=self.event1, user=self.user, can_change_items=True,
can_change_settings=True)

View File

@@ -1,7 +1,7 @@
from django.test import TestCase, Client
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):

View File

@@ -1,22 +1,22 @@
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(
'pretixcontrol.views.auth',
'pretix.control.views.auth',
url(r'^logout$', 'logout', name='auth.logout'),
url(r'^login$', 'login', name='auth.login'),
)
urlpatterns += patterns(
'pretixcontrol.views.main',
'pretix.control.views.main',
url(r'^$', 'index', name='index'),
url(r'^events/$', main.EventList.as_view(), name='events'),
)
urlpatterns += patterns(
'pretixcontrol.views.event',
'pretix.control.views.event',
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(
patterns(
'pretixcontrol.views',
'pretix.control.views',
url(r'^$', 'event.index', name='event.index'),
url(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
url(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
@@ -54,5 +54,5 @@ urlpatterns += patterns(
name='event.items.quotas.delete'),
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 pytz import common_timezones
from pretixbase.forms import VersionedModelForm
from pretix.base.forms import VersionedModelForm
from pretixbase.models import Event
from pretixcontrol.permissions import EventPermissionRequiredMixin
from pretix.base.models import Event
from pretix.control.permissions import EventPermissionRequiredMixin
class EventUpdateForm(VersionedModelForm):
@@ -75,7 +75,7 @@ class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin
return self.request.event
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['plugins'] = [p for p in get_all_plugins() if not p.name.startswith('.')]
context['plugins_active'] = self.object.get_plugins()

View File

@@ -1,15 +1,15 @@
from itertools import product
from django import forms
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.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
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):

View File

@@ -12,14 +12,14 @@ from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import redirect
from django.forms.models import inlineformset_factory
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,
Versionable)
from pretixcontrol.permissions import EventPermissionRequiredMixin, event_permission_required
from pretixcontrol.views.forms import TolerantFormsetModelForm, VariationsField
from pretixcontrol.signals import restriction_formset
from pretix.control.permissions import EventPermissionRequiredMixin, event_permission_required
from pretix.control.views.forms import TolerantFormsetModelForm, VariationsField
from pretix.control.signals import restriction_formset
class ItemList(ListView):
@@ -649,7 +649,7 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
'item': self.object.identity,
}) + '?success=true'
}) + '?success=true'
def get_form_kwargs(self):
"""

View File

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

View File

@@ -1,12 +1,12 @@
from django.apps import AppConfig
from pretixbase.plugins import PluginType
from pretix.base.plugins import PluginType
class TestDummyApp(AppConfig):
name = 'pretixplugins.testdummy'
name = 'pretix.plugins.testdummy'
verbose_name = '.testdummy'
class TixlPluginMeta:
class PretixPluginMeta:
type = PluginType.RESTRICTION
name = '.testdummy'
version = '1.0.0'
@@ -14,4 +14,4 @@ class TestDummyApp(AppConfig):
def ready(self):
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 pretixbase.signals import determine_availability
from pretix.base.signals import determine_availability
@receiver(determine_availability)

View File

@@ -1,13 +1,13 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from pretixbase.plugins import PluginType
from pretix.base.plugins import PluginType
class TimeRestrictionApp(AppConfig):
name = 'pretixplugins.timerestriction'
name = 'pretix.plugins.timerestriction'
verbose_name = _("Time restriction")
class TixlPluginMeta:
class PretixPluginMeta:
type = PluginType.RESTRICTION
name = _("Restriction by time")
author = _("the pretix team")
@@ -19,4 +19,4 @@ class TimeRestrictionApp(AppConfig):
def ready(self):
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 django.db import models, migrations
import pretixbase.models
import pretix.base.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)),
('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')),
('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={
'verbose_name': 'Restriction',

View File

@@ -1,7 +1,7 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from pretixbase.models import BaseRestriction
from pretix.base.models import 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.forms.models import inlineformset_factory
from pretixbase.signals import determine_availability
from pretixbase.models import Item
from pretixcontrol.views.forms import VariationsField, RestrictionInlineFormset, RestrictionForm
from pretixcontrol.signals import restriction_formset
from pretix.base.signals import determine_availability
from pretix.base.models import Item
from pretix.control.views.forms import RestrictionInlineFormset, RestrictionForm
from pretix.control.signals import restriction_formset
from .models import TimeRestriction

View File

@@ -3,13 +3,13 @@ from datetime import timedelta
from django.test import TestCase
from django.utils.timezone import now
from pretixbase.models import (
from pretix.base.models import (
Event, Organizer, Item, Property, PropertyValue, ItemVariation
)
# Do NOT use relative imports here
from pretixplugins.timerestriction import signals
from pretixplugins.timerestriction.models import TimeRestriction
from pretix.plugins.timerestriction import signals
from pretix.plugins.timerestriction.models import TimeRestriction
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.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.utils.translation import ugettext as _
from pretixbase.models import Event
from pretix.base.models import Event
class EventMiddleware:

View File

@@ -1,5 +1,4 @@
.product-row {
padding: 10px 0;
border-top: 1px solid @table-border-color;
&.headline, &.simple {
@@ -25,14 +24,25 @@
color: @alert-warning-text;
}
}
.price {
text-align: center;
}
.cart-row, .product-row {
padding: 10px 0;
.count form {
display: inline;
}
.price, .count {
text-align: right;
}
.price small,
.availability-box small {
display: block;
line-height: 1;
}
&.total {
border-top: 1px solid @table-border-color;
}
}
.checkout-button-row {
padding: 15px 0;

View File

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

View File

@@ -0,0 +1,63 @@
{% load i18n %}
{% for line in cart.positions %}
<div class="row-fluid cart-row">
<div class="col-md-4 col-xs-6">
<strong>{{ line.item }}</strong>
{% if line.variation %}
{{ line.variation }}
{% endif %}
</div>
<div class="col-md-3 col-xs-6 price">
{{ event.currency }} {{ line.price|floatformat:2 }}
</div>
<div class="col-md-2 col-xs-6 count">
{% if editable %}
<form action="{% url "presale:event.cart.remove" event=event.slug organizer=event.organizer.slug %}"
method="post">
{% csrf_token %}
{% if line.variation %}
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
value="1" />
{% else %}
<input type="hidden" name="item_{{ line.item.identity }}"
value="1" />
{% endif %}
<button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button>
</form>
{% endif %}
{{ line.count }}
{% if editable %}
<form action="{% url "presale:event.cart.add" event=event.slug organizer=event.organizer.slug %}"
method="post">
{% csrf_token %}
{% if line.variation %}
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
value="1" />
{% else %}
<input type="hidden" name="item_{{ line.item.identity }}"
value="1" />
{% endif %}
<button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button>
</form>
{% endif %}
</div>
<div class="col-md-3 col-xs-6 price">
<strong>{{ event.currency }} {{ line.total|floatformat:2 }}</strong>
{% if line.item.tax_rate %}
<br /><small>{% blocktrans trimmed with rate=line.item.tax_rate %}
incl. {{ rate }}% taxes
{% endblocktrans %}</small>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
{% endfor %}
<div class="row-fluid cart-row total">
<div class="col-md-4 col-xs-6">
<strong>{% trans "Total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ event.currency }} {{ cart.total|floatformat:2 }}</strong>
</div>
<div class="clearfix"></div>
</div>

View File

@@ -2,6 +2,34 @@
{% load i18n %}
{% block content %}
{% if cart.positions %}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Your cart" %}</h3>
</div>
<div class="panel-body">
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %}
<div class="row-fluid">
<div class="col-md-6 col-xs-12">
{% if cart.minutes_left > 0 %}
<em>{% blocktrans trimmed with minutes=cart.minutes_left %}
The items in your cart are reserved for you for {{ minutes }} minutes.
{% endblocktrans %}</em>
{% else %}
<em>{% trans "The items in your cart are no longer reserved for you." %}</em>
{% endif %}
</div>
<div class="col-md-4 col-md-offset-2 col-xs-12">
<a class="btn btn-block btn-primary btn-lg"
href="{% url "presale:event.checkout.start" organizer=request.event.organizer.slug event=request.event.slug %}">
<i class="fa fa-shopping-cart"></i> {% trans "Proceed with checkout" %}
</a>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
{% endif %}
<form method="post"
action="{% url "presale:event.cart.add" organizer=request.event.organizer.slug event=request.event.slug %}?next={{ request.path_info|urlencode }}">
{% csrf_token %}

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