From 5d2af48732b7e75d468e5f23085f2ce7a9e4f4e0 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 10:52:01 +0100 Subject: [PATCH 01/11] Small refactoring in cart.py and related locations --- src/pretixbase/models.py | 8 +++---- src/pretixbase/tests/test_models.py | 34 ++++++++++++++--------------- src/pretixpresale/views/cart.py | 29 +++++++++++++++--------- src/pretixpresale/views/event.py | 4 ++-- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/pretixbase/models.py b/src/pretixbase/models.py index deb1f36ea4..99481bab30 100644 --- a/src/pretixbase/models.py +++ b/src/pretixbase/models.py @@ -782,7 +782,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 +792,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. @@ -869,7 +869,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 +884,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. diff --git a/src/pretixbase/tests/test_models.py b/src/pretixbase/tests/test_models.py index 68becc1b00..02da787a36 100644 --- a/src/pretixbase/tests/test_models.py +++ b/src/pretixbase/tests/test_models.py @@ -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,36 @@ 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)) diff --git a/src/pretixpresale/views/cart.py b/src/pretixpresale/views/cart.py index bac687a289..7280bb5ebf 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretixpresale/views/cart.py @@ -39,8 +39,11 @@ class CartActionMixin: class CartAdd(EventViewMixin, CartActionMixin, View): - def post(self, *args, **kwargs): - # Parse input + def _items_from_post_data(self): + """ + Parses the POST data and returns a list of tuples in the + form (item id, variation id or None, number) + """ items = [] for key, value in self.request.POST.items(): if value.strip() == '': @@ -49,14 +52,23 @@ class CartAdd(EventViewMixin, CartActionMixin, View): try: items.append((key.split("_")[1], None, int(value))) except ValueError: - messages.error(self.request, _('Please only enter numbers.')) - return redirect(self.get_failure_url()) + messages.error(self.request, _('Please enter numbers only.')) + return False elif key.startswith('variation_'): try: items.append((key.split("_")[1], key.split("_")[2], int(value))) except ValueError: - messages.error(self.request, _('Please only enter numbers.')) - return redirect(self.get_failure_url()) + messages.error(self.request, _('Please enter numbers only.')) + return False + if len(items) == 0: + messages.error(self.request, _('You did not select any items.')) + return False + return items + + def post(self, *args, **kwargs): + items = self._items_from_post_data() + if not items: + return redirect(self.get_failure_url()) if sum(i[2] for i in items) > self.request.event.max_items_per_order: # TODO: Plurals @@ -64,9 +76,6 @@ class CartAdd(EventViewMixin, CartActionMixin, View): _("You cannot select more than %d items per order") % self.event.max_items_per_order) return redirect(self.get_failure_url()) - # items is now a list of tuples of the form - # (item id, variation id or None, number) - # Fetch items from the database items_cache = { i.identity: i for i @@ -91,7 +100,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View): return redirect(self.get_failure_url()) item = items_cache[i[0]] variation = variations_cache[i[1]] if i[1] is not None else None - price = item.execute_restrictions() if variation is None else variation.execute_restrictions() + price = item.check_restrictions() if variation is None else variation.check_restrictions() if price is False: if not msg_some_unavailable: diff --git a/src/pretixpresale/views/event.py b/src/pretixpresale/views/event.py index a1e8bcfff0..2c6a932845 100644 --- a/src/pretixpresale/views/event.py +++ b/src/pretixpresale/views/event.py @@ -30,13 +30,13 @@ class EventIndex(EventViewMixin, TemplateView): item.has_variations = (len(item.available_variations) != 1 or not item.available_variations[0].empty()) if not item.has_variations: - item.cached_availability = list(item.availability()) + item.cached_availability = list(item.check_quotas()) item.cached_availability[1] = min(item.cached_availability[1], self.request.event.max_items_per_order) item.price = item.available_variations[0]['price'] else: for var in item.available_variations: - var.cached_availability = list(var['variation'].availability()) + var.cached_availability = list(var['variation'].check_quotas()) var.cached_availability[1] = min(var.cached_availability[1], self.request.event.max_items_per_order) From 7f84069f4d1abb317756d9b36326cf57c4b3fc21 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 11:01:59 +0100 Subject: [PATCH 02/11] Code documentation --- src/pretixpresale/views/cart.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/pretixpresale/views/cart.py b/src/pretixpresale/views/cart.py index 7280bb5ebf..cbb19bc1da 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretixpresale/views/cart.py @@ -61,7 +61,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View): messages.error(self.request, _('Please enter numbers only.')) return False if len(items) == 0: - messages.error(self.request, _('You did not select any items.')) + messages.warning(self.request, _('You did not select any items.')) return False return items @@ -71,7 +71,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View): return redirect(self.get_failure_url()) if sum(i[2] for i in items) > self.request.event.max_items_per_order: - # TODO: Plurals + # TODO: i18n plurals messages.error(self.request, _("You cannot select more than %d items per order") % self.event.max_items_per_order) return redirect(self.get_failure_url()) @@ -81,27 +81,34 @@ class CartAdd(EventViewMixin, CartActionMixin, View): i.identity: i for i in Item.objects.filter( event=self.request.event, - id__in=[i[0] for i in items] + identity__in=[i[0] for i in items] ).prefetch_related("quotas") } variations_cache = { v.identity: v for v in ItemVariation.objects.filter( item__event=self.request.event, - id__in=[i[1] for i in items if i[1] is not None] + identity__in=[i[1] for i in items if i[1] is not None] ).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop") } # Process the request itself msg_some_unavailable = False for i in items: + # Check whether the specified items are part of what we just fetched from the database + # If they are not, the user supplied item IDs which either do not exist or belong to + # a different event if i[0] not in items_cache or (i[1] is not None and i[1] not in variations_cache): messages.error(self.request, _('You selected an item which is not available for sale.')) return redirect(self.get_failure_url()) + item = items_cache[i[0]] variation = variations_cache[i[1]] if i[1] is not None else None - price = item.check_restrictions() if variation is None else variation.check_restrictions() + # Execute restriction plugins to check whether they (a) change the price or + # (b) make the item/variation unavailable. If neither is the case, check_restriction + # will correctly return the default price + price = item.check_restrictions() if variation is None else variation.check_restrictions() if price is False: if not msg_some_unavailable: msg_some_unavailable = True @@ -110,6 +117,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View): 'Please see below for details.')) continue + # Fetch all quotas. If there are no quotas, this item is not allowed to be sold. quotas = list(item.quotas.all()) if variation is None else list(variation.quotas.all()) if len(quotas) == 0: if not msg_some_unavailable: @@ -119,12 +127,16 @@ class CartAdd(EventViewMixin, CartActionMixin, View): 'Please see below for details.')) continue + # Assume that all quotas allow us to buy i[2] instances of the object quota_ok = i[2] try: for quota in quotas: + # Lock the quota, so no other thread is allowed to perform sales covered by this + # quota while we're doing so. quota.lock() avail = quota.availability() if avail[0] != Quota.AVAILABILITY_OK: + # This quota is sold out/currently unavailable, so do not sell this at all if not msg_some_unavailable: msg_some_unavailable = True messages.error(self.request, @@ -133,6 +145,8 @@ class CartAdd(EventViewMixin, CartActionMixin, View): quota_ok = 0 break elif avail[1] < i[2]: + # This quota is available, but with less than i[2] items left, so we have to + # reduce the number of bought items if not msg_some_unavailable: msg_some_unavailable = True messages.error(self.request, @@ -140,6 +154,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View): 'the quantity you selected. Please see below for details.')) quota_ok = min(quota_ok, avail[1]) + # Create a CartPosition for as much items as we can for k in range(quota_ok): CartPosition.objects.create( event=self.request.event, @@ -151,12 +166,15 @@ class CartAdd(EventViewMixin, CartActionMixin, View): expires=now() + timedelta(minutes=30) ) except Quota.LockTimeoutException: + # Is raised when there are too many threads asking for quota locks and we were + # unaible to get one if not msg_some_unavailable: msg_some_unavailable = True messages.error(self.request, _('We were not able to process your request completely as the ' 'server was too busy. Please try again.')) finally: + # Release the locks. This is important ;) for quota in quotas: quota.release() From cbf94ad4f1bc22d4eb02714e61bdafb4281120e4 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 11:13:13 +0100 Subject: [PATCH 03/11] Move some optimizations and checks around --- src/pretixbase/models.py | 19 ++++++++++++++++--- src/pretixpresale/views/event.py | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/pretixbase/models.py b/src/pretixbase/models.py index 99481bab30..8fe8932d25 100644 --- a/src/pretixbase/models.py +++ b/src/pretixbase/models.py @@ -745,14 +745,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) diff --git a/src/pretixpresale/views/event.py b/src/pretixpresale/views/event.py index 2c6a932845..7aeac158ed 100644 --- a/src/pretixpresale/views/event.py +++ b/src/pretixpresale/views/event.py @@ -18,7 +18,7 @@ class EventIndex(EventViewMixin, TemplateView): items = self.request.event.items.all().select_related( 'category', # for re-grouping ).prefetch_related( - 'properties', 'variations__values__prop', # for .get_all_available_variations() + 'properties', # for .get_all_available_variations() 'quotas', 'variations__quotas' # for .availability() ).annotate(quotac=Count('quotas')).filter( quotac__gt=0 From 294ef76660f9ef2986c0761ae52a7bd366a830da Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 12:23:20 +0100 Subject: [PATCH 04/11] Display and manipulate cart --- src/pretixbase/models.py | 9 ++- .../static/pretixpresale/less/event.less | 10 +++- .../pretixpresale/event/fragment_cart.html | 59 +++++++++++++++++++ .../templates/pretixpresale/event/index.html | 1 + src/pretixpresale/urls.py | 1 + src/pretixpresale/views/__init__.py | 57 ++++++++++++++++++ src/pretixpresale/views/cart.py | 54 ++++++++++++----- src/pretixpresale/views/event.py | 12 ++-- 8 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 src/pretixpresale/templates/pretixpresale/event/fragment_cart.html diff --git a/src/pretixbase/models.py b/src/pretixbase/models.py index 8fe8932d25..d66ca80511 100644 --- a/src/pretixbase/models.py +++ b/src/pretixbase/models.py @@ -872,6 +872,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: @@ -1099,14 +1102,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 @@ -1114,7 +1117,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() diff --git a/src/pretixpresale/static/pretixpresale/less/event.less b/src/pretixpresale/static/pretixpresale/less/event.less index d6304450e5..2f9d3297ff 100644 --- a/src/pretixpresale/static/pretixpresale/less/event.less +++ b/src/pretixpresale/static/pretixpresale/less/event.less @@ -1,5 +1,4 @@ .product-row { - padding: 10px 0; border-top: 1px solid @table-border-color; &.headline, &.simple { @@ -25,7 +24,14 @@ color: @alert-warning-text; } } - .price { +} +.cart-row, .product-row { + padding: 10px 0; + + .count form { + display: inline; + } + .price, .count { text-align: center; } .price small, diff --git a/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html b/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html new file mode 100644 index 0000000000..4d3b80e0ee --- /dev/null +++ b/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html @@ -0,0 +1,59 @@ +{% load i18n %} +{% if cart %} +
+
+

{% trans "Your cart" %}

+
+
+ {% for line in cart %} +
+
+ {{ line.item }} + {% if line.variation %} + – {{ line.variation }} + {% endif %} +
+
+ {{ event.currency }} {{ line.price|floatformat:2 }} +
+
+
+ {% csrf_token %} + {% if line.variation %} + + {% else %} + + {% endif %} + +
+ {{ line.count }} +
+ {% csrf_token %} + {% if line.variation %} + + {% else %} + + {% endif %} + +
+
+
+ {{ event.currency }} {{ line.total|floatformat:2 }} + {% if line.item.tax_rate %} +
{% blocktrans trimmed with rate=line.item.tax_rate %} + incl. {{ rate }}% taxes + {% endblocktrans %} + {% endif %} +
+
+
+ {% endfor %} +
+
+{% endif %} \ No newline at end of file diff --git a/src/pretixpresale/templates/pretixpresale/event/index.html b/src/pretixpresale/templates/pretixpresale/event/index.html index 3119de4d61..d4bbe47b08 100644 --- a/src/pretixpresale/templates/pretixpresale/event/index.html +++ b/src/pretixpresale/templates/pretixpresale/event/index.html @@ -2,6 +2,7 @@ {% load i18n %} {% block content %} + {% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event %}
{% csrf_token %} diff --git a/src/pretixpresale/urls.py b/src/pretixpresale/urls.py index e230cf0953..97929ffdbb 100644 --- a/src/pretixpresale/urls.py +++ b/src/pretixpresale/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns( 'pretixpresale.views.event', url(r'^$', pretixpresale.views.event.EventIndex.as_view(), name='event.index'), url(r'^cart/add$', pretixpresale.views.cart.CartAdd.as_view(), name='event.cart.add'), + url(r'^cart/remove$', pretixpresale.views.cart.CartRemove.as_view(), name='event.cart.remove'), ) )), ) diff --git a/src/pretixpresale/views/__init__.py b/src/pretixpresale/views/__init__.py index e69de29bb2..89b69edff9 100644 --- a/src/pretixpresale/views/__init__.py +++ b/src/pretixpresale/views/__init__.py @@ -0,0 +1,57 @@ +import uuid +from itertools import groupby + +from django.db.models import Q + +from pretixbase.models import CartPosition + + +class CartMixin: + def get_session_key(self): + if 'cart_key' in self.request.session: + return self.request.session.get('cart_key') + key = str(uuid.uuid4()) + self.request.session['cart_key'] = key + return key + + +class CartDisplayMixin(CartMixin): + + def get_cart(self): + qw = Q(session=self.get_session_key()) + if self.request.user.is_authenticated(): + qw |= Q(user=self.request.user) + + cartpos = list(CartPosition.objects.current.filter( + qw & Q(event=self.request.event) + ).order_by( + 'item', 'variation' + ).select_related( + 'item', 'variation' + ).prefetch_related( + 'variation__values', 'variation__values__prop' + )) + + # Group items of the same variation + # We do this by list manipulations instead of a GROUP BY query, as + # Django is unable to join related models in a .values() query + def keyfunc(pos): + return pos.item_id, pos.variation_id, pos.price + + cart = [] + for k, g in groupby(sorted(cartpos, key=keyfunc), key=keyfunc): + g = list(g) + group = g[0] + group.count = len(g) + group.total = group.count * group.price + cart.append(group) + + return cart + + +class EventViewMixin: + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['event'] = self.request.event + return context diff --git a/src/pretixpresale/views/cart.py b/src/pretixpresale/views/cart.py index cbb19bc1da..e75c642bf8 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretixpresale/views/cart.py @@ -1,18 +1,18 @@ from datetime import timedelta -import uuid from django.contrib import messages from django.core.urlresolvers import reverse +from django.db.models import Q from django.shortcuts import redirect from django.utils.timezone import now from django.views.generic import View from django.utils.translation import ugettext_lazy as _ -from .event import EventViewMixin from pretixbase.models import Item, ItemVariation, Quota, CartPosition +from pretixpresale.views import CartMixin, EventViewMixin -class CartActionMixin: +class CartActionMixin(CartMixin): def get_next_url(self): if "next" in self.request.GET and '://' not in self.request.GET: @@ -29,16 +29,6 @@ class CartActionMixin: def get_failure_url(self): return self.get_next_url() - def get_session_key(self): - if 'cart_key' in self.request.session: - return self.request.session.get('cart_key') - key = str(uuid.uuid4()) - self.request.session['cart_key'] = key - return key - - -class CartAdd(EventViewMixin, CartActionMixin, View): - def _items_from_post_data(self): """ Parses the POST data and returns a list of tuples in the @@ -65,6 +55,32 @@ class CartAdd(EventViewMixin, CartActionMixin, View): return False return items + +class CartRemove(EventViewMixin, CartActionMixin, View): + + def post(self, *args, **kwargs): + items = self._items_from_post_data() + if not items: + return redirect(self.get_failure_url()) + qw = Q(session=self.get_session_key()) + if self.request.user.is_authenticated(): + qw |= Q(user=self.request.user) + + for item, variation, cnt in items: + cw = qw & Q(item_id=item) + if variation: + cw &= Q(variation_id=variation) + else: + cw &= Q(variation__isnull=True) + for cp in CartPosition.objects.current.filter(cw).order_by("-price")[:cnt]: + cp.delete() + + messages.success(self.request, _('Your cart has been updated.')) + return redirect(self.get_success_url()) + + +class CartAdd(EventViewMixin, CartActionMixin, View): + def post(self, *args, **kwargs): items = self._items_from_post_data() if not items: @@ -79,19 +95,27 @@ class CartAdd(EventViewMixin, CartActionMixin, View): # Fetch items from the database items_cache = { i.identity: i for i - in Item.objects.filter( + in Item.objects.current.filter( event=self.request.event, identity__in=[i[0] for i in items] ).prefetch_related("quotas") } variations_cache = { v.identity: v for v - in ItemVariation.objects.filter( + in ItemVariation.objects.current.filter( item__event=self.request.event, identity__in=[i[1] for i in items if i[1] is not None] ).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop") } + # Extend this user's cart session to 30 minutes from now to ensure all items in the + # cart expire at the same time + qw = Q(session=self.get_session_key()) + if self.request.user.is_authenticated(): + qw |= Q(user=self.request.user) + CartPosition.objects.current.filter( + qw & Q(event=self.request.event)).update(expires=now() + timedelta(minutes=30)) + # Process the request itself msg_some_unavailable = False for i in items: diff --git a/src/pretixpresale/views/event.py b/src/pretixpresale/views/event.py index 7aeac158ed..fea1c49e93 100644 --- a/src/pretixpresale/views/event.py +++ b/src/pretixpresale/views/event.py @@ -1,15 +1,9 @@ from django.db.models import Count from django.views.generic import TemplateView +from pretixpresale.views import EventViewMixin, CartDisplayMixin -class EventViewMixin: - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['event'] = self.request.event - return context - - -class EventIndex(EventViewMixin, TemplateView): +class EventIndex(EventViewMixin, CartDisplayMixin, TemplateView): template_name = "pretixpresale/event/index.html" def get_context_data(self, **kwargs): @@ -46,4 +40,6 @@ class EventIndex(EventViewMixin, TemplateView): (cat, [i for i in items if i.category_id == cat.identity]) for cat in set([i.category for i in items]) # insert categories into a set for uniqueness ], key=lambda group: (group[0].position, group[0].pk)) # a set is unsorted, so sort again by category + + context['cart'] = self.get_cart() return context From 15896d2f413afce551185472772bcf14071a0926 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 12:29:06 +0100 Subject: [PATCH 05/11] flake8 run --- src/pretixbase/middleware.py | 2 -- src/pretixbase/tests/__init__.py | 1 - src/pretixcontrol/tests/test_items.py | 2 +- src/pretixcontrol/urls.py | 2 +- src/pretixcontrol/views/forms.py | 2 +- src/pretixcontrol/views/item.py | 2 +- src/pretixplugins/timerestriction/signals.py | 2 +- src/pretixpresale/admin.py | 3 --- src/pretixpresale/middleware.py | 6 ------ src/pretixpresale/views/cart.py | 2 +- 10 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 src/pretixpresale/admin.py diff --git a/src/pretixbase/middleware.py b/src/pretixbase/middleware.py index e61af7c011..efebc65e87 100644 --- a/src/pretixbase/middleware.py +++ b/src/pretixbase/middleware.py @@ -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 diff --git a/src/pretixbase/tests/__init__.py b/src/pretixbase/tests/__init__.py index d563d39224..1090761f72 100644 --- a/src/pretixbase/tests/__init__.py +++ b/src/pretixbase/tests/__init__.py @@ -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 diff --git a/src/pretixcontrol/tests/test_items.py b/src/pretixcontrol/tests/test_items.py index 1b4d60aa18..9dc2f8452f 100644 --- a/src/pretixcontrol/tests/test_items.py +++ b/src/pretixcontrol/tests/test_items.py @@ -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) diff --git a/src/pretixcontrol/urls.py b/src/pretixcontrol/urls.py index fe5113b392..73f87503b1 100644 --- a/src/pretixcontrol/urls.py +++ b/src/pretixcontrol/urls.py @@ -54,5 +54,5 @@ urlpatterns += patterns( name='event.items.quotas.delete'), url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), ) - )) + )) ) diff --git a/src/pretixcontrol/views/forms.py b/src/pretixcontrol/views/forms.py index 4d3809955e..261f8d4668 100644 --- a/src/pretixcontrol/views/forms.py +++ b/src/pretixcontrol/views/forms.py @@ -1,7 +1,7 @@ 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 diff --git a/src/pretixcontrol/views/item.py b/src/pretixcontrol/views/item.py index bca14f9ba8..b3ec94a4d2 100644 --- a/src/pretixcontrol/views/item.py +++ b/src/pretixcontrol/views/item.py @@ -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): """ diff --git a/src/pretixplugins/timerestriction/signals.py b/src/pretixplugins/timerestriction/signals.py index 27568ae7f9..0106c8e912 100644 --- a/src/pretixplugins/timerestriction/signals.py +++ b/src/pretixplugins/timerestriction/signals.py @@ -5,7 +5,7 @@ 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.views.forms import RestrictionInlineFormset, RestrictionForm from pretixcontrol.signals import restriction_formset from .models import TimeRestriction diff --git a/src/pretixpresale/admin.py b/src/pretixpresale/admin.py deleted file mode 100644 index 8c38f3f3da..0000000000 --- a/src/pretixpresale/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/src/pretixpresale/middleware.py b/src/pretixpresale/middleware.py index 75b2e63bea..5277bbee66 100644 --- a/src/pretixpresale/middleware.py +++ b/src/pretixpresale/middleware.py @@ -1,11 +1,5 @@ -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 diff --git a/src/pretixpresale/views/cart.py b/src/pretixpresale/views/cart.py index e75c642bf8..cbf561f174 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretixpresale/views/cart.py @@ -21,7 +21,7 @@ class CartActionMixin(CartMixin): return reverse('presale:event.index', kwargs={ 'event': self.request.event.slug, 'organizer': self.request.event.organizer.slug, - }) + }) def get_success_url(self): return self.get_next_url() From cf18f3e20059ed77ca5954a16a665cf672d25991 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 16:32:39 +0100 Subject: [PATCH 06/11] Minor changes to the shopping cart --- .../static/pretixpresale/less/event.less | 6 +- .../pretixpresale/event/fragment_cart.html | 114 +++++++++--------- .../templates/pretixpresale/event/index.html | 11 +- src/pretixpresale/views/__init__.py | 9 +- src/pretixpresale/views/cart.py | 2 + 5 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/pretixpresale/static/pretixpresale/less/event.less b/src/pretixpresale/static/pretixpresale/less/event.less index 2f9d3297ff..a6560c3293 100644 --- a/src/pretixpresale/static/pretixpresale/less/event.less +++ b/src/pretixpresale/static/pretixpresale/less/event.less @@ -32,13 +32,17 @@ display: inline; } .price, .count { - text-align: center; + 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; diff --git a/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html b/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html index 4d3b80e0ee..d894589785 100644 --- a/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html +++ b/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html @@ -1,59 +1,63 @@ {% load i18n %} -{% if cart %} -
-
-

{% trans "Your cart" %}

+{% for line in cart.positions %} +
+
+ {{ line.item }} + {% if line.variation %} + – {{ line.variation }} + {% endif %}
-
- {% for line in cart %} -
-
- {{ line.item }} - {% if line.variation %} - – {{ line.variation }} - {% endif %} -
-
- {{ event.currency }} {{ line.price|floatformat:2 }} -
-
- - {% csrf_token %} - {% if line.variation %} - - {% else %} - - {% endif %} - - - {{ line.count }} -
- {% csrf_token %} - {% if line.variation %} - - {% else %} - - {% endif %} - -
-
-
- {{ event.currency }} {{ line.total|floatformat:2 }} - {% if line.item.tax_rate %} -
{% blocktrans trimmed with rate=line.item.tax_rate %} - incl. {{ rate }}% taxes - {% endblocktrans %} - {% endif %} -
-
-
- {% endfor %} +
+ {{ event.currency }} {{ line.price|floatformat:2 }}
+
+ {% if editable %} +
+ {% csrf_token %} + {% if line.variation %} + + {% else %} + + {% endif %} + +
+ {% endif %} + {{ line.count }} + {% if editable %} +
+ {% csrf_token %} + {% if line.variation %} + + {% else %} + + {% endif %} + +
+ {% endif %} +
+
+ {{ event.currency }} {{ line.total|floatformat:2 }} + {% if line.item.tax_rate %} +
{% blocktrans trimmed with rate=line.item.tax_rate %} + incl. {{ rate }}% taxes + {% endblocktrans %} + {% endif %} +
+
-{% endif %} \ No newline at end of file +{% endfor %} +
+
+ {% trans "Total" %} +
+
+ {{ event.currency }} {{ cart.total|floatformat:2 }} +
+
+
diff --git a/src/pretixpresale/templates/pretixpresale/event/index.html b/src/pretixpresale/templates/pretixpresale/event/index.html index d4bbe47b08..5fe06c58ac 100644 --- a/src/pretixpresale/templates/pretixpresale/event/index.html +++ b/src/pretixpresale/templates/pretixpresale/event/index.html @@ -2,7 +2,16 @@ {% load i18n %} {% block content %} - {% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event %} + {% if cart.positions %} +
+
+

{% trans "Your cart" %}

+
+
+ {% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %} +
+
+ {% endif %}
{% csrf_token %} diff --git a/src/pretixpresale/views/__init__.py b/src/pretixpresale/views/__init__.py index 89b69edff9..2ea8d8fb2a 100644 --- a/src/pretixpresale/views/__init__.py +++ b/src/pretixpresale/views/__init__.py @@ -38,15 +38,18 @@ class CartDisplayMixin(CartMixin): def keyfunc(pos): return pos.item_id, pos.variation_id, pos.price - cart = [] + positions = [] for k, g in groupby(sorted(cartpos, key=keyfunc), key=keyfunc): g = list(g) group = g[0] group.count = len(g) group.total = group.count * group.price - cart.append(group) + positions.append(group) - return cart + return { + 'positions': positions, + 'total': sum(p.total for p in positions), + } class EventViewMixin: diff --git a/src/pretixpresale/views/cart.py b/src/pretixpresale/views/cart.py index cbf561f174..dda833823f 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretixpresale/views/cart.py @@ -17,6 +17,8 @@ class CartActionMixin(CartMixin): def get_next_url(self): if "next" in self.request.GET and '://' not in self.request.GET: return self.request.GET.get('next') + elif "HTTP_REFERER" in self.request.META: + return self.request.META.get('HTTP_REFERER') else: return reverse('presale:event.index', kwargs={ 'event': self.request.event.slug, From 077413f41c5a14551fa9c2a48a022f9092a0fb22 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 17:55:13 +0100 Subject: [PATCH 07/11] Restructure our python module. A lot. --- .gitmodules | 8 ++-- doc/development/api/plugins.rst | 4 +- doc/development/concepts.rst | 16 ++++---- doc/development/goals.rst | 4 +- doc/development/structure.rst | 40 ++++++++++--------- src/.coveragerc | 2 +- src/pretix/base/__init__.py | 8 ++++ src/{pretixbase => pretix/base}/admin.py | 12 +++--- src/{pretixbase => pretix/base}/cache.py | 2 +- src/{pretixbase => pretix/base}/forms.py | 0 src/{pretixbase => pretix/base}/middleware.py | 0 .../base}/migrations/0001_initial.py | 5 ++- .../migrations/0002_auto_20150211_2031.py | 2 +- .../migrations/0003_auto_20150211_2042.py | 2 +- .../migrations/0004_auto_20150211_2330.py | 2 +- .../migrations/0005_auto_20150212_0901.py | 2 +- .../migrations/0006_auto_20150212_0908.py | 2 +- .../migrations/0007_auto_20150212_0939.py | 2 +- .../base}/migrations/0008_quota_locked.py | 0 .../base/migrations}/__init__.py | 0 src/{pretixbase => pretix/base}/models.py | 6 +-- src/{pretixbase => pretix/base}/plugins.py | 6 +-- src/{pretixbase => pretix/base}/signals.py | 2 +- .../base}/static/bootstrap | 0 .../base}/static/fontawesome | 0 .../static/jquery/js/jquery-2.1.1.min.js | 0 .../base}/tests/__init__.py | 0 .../base}/tests/test_cache.py | 2 +- .../base}/tests/test_middleware.py | 2 +- .../base}/tests/test_models.py | 4 +- .../base}/tests/test_plugins.py | 10 ++--- src/{pretixbase => pretix/base}/types.py | 0 src/pretix/control/__init__.py | 8 ++++ .../control}/context.py | 0 .../control}/middleware.py | 2 +- .../control/migrations}/__init__.py | 0 .../control}/permissions.py | 2 +- .../control}/signals.py | 2 +- .../static/pretixcontrol/js/ui/main.js | 0 .../static/pretixcontrol/less/auth.less | 2 +- .../static/pretixcontrol/less/forms.less | 0 .../static/pretixcontrol/less/main.less | 4 ++ .../templates/pretixcontrol/auth/base.html | 0 .../templates/pretixcontrol/auth/login.html | 0 .../templates/pretixcontrol/base.html | 0 .../templates/pretixcontrol/event/base.html | 0 .../templates/pretixcontrol/event/index.html | 0 .../pretixcontrol/event/plugins.html | 0 .../pretixcontrol/event/settings.html | 0 .../pretixcontrol/event/settings_base.html | 0 .../templates/pretixcontrol/events/index.html | 0 .../templates/pretixcontrol/item/base.html | 0 .../templates/pretixcontrol/item/index.html | 0 .../pretixcontrol/item/restrictions.html | 0 .../pretixcontrol/item/variations_0d.html | 0 .../pretixcontrol/item/variations_1d.html | 0 .../pretixcontrol/item/variations_nd.html | 0 .../templates/pretixcontrol/items/base.html | 0 .../pretixcontrol/items/categories.html | 0 .../pretixcontrol/items/category.html | 0 .../pretixcontrol/items/category_delete.html | 0 .../templates/pretixcontrol/items/index.html | 0 .../pretixcontrol/items/properties.html | 0 .../pretixcontrol/items/property.html | 0 .../pretixcontrol/items/property_delete.html | 0 .../pretixcontrol/items/question.html | 0 .../pretixcontrol/items/question_delete.html | 0 .../pretixcontrol/items/questions.html | 0 .../templates/pretixcontrol/items/quota.html | 0 .../pretixcontrol/items/quota_delete.html | 0 .../templates/pretixcontrol/items/quotas.html | 0 .../control/tests}/__init__.py | 0 .../control}/tests/test_auth.py | 4 +- .../control}/tests/test_events.py | 14 +++---- .../control}/tests/test_items.py | 4 +- .../control}/tests/test_permissions.py | 2 +- src/{pretixcontrol => pretix/control}/urls.py | 10 ++--- .../control/views}/__init__.py | 0 .../control}/views/auth.py | 0 .../control}/views/event.py | 8 ++-- .../control}/views/forms.py | 4 +- .../control}/views/item.py | 10 ++--- .../control}/views/main.py | 2 +- .../migrations => pretix/helpers}/__init__.py | 0 .../helpers/lessabsolutefilter.py | 0 .../plugins}/__init__.py | 0 .../plugins}/testdummy/__init__.py | 8 ++-- .../plugins}/testdummy/models.py | 0 .../plugins}/testdummy/signals.py | 2 +- .../plugins}/timerestriction/__init__.py | 8 ++-- .../migrations/0001_initial.py | 5 ++- .../timerestriction/migrations}/__init__.py | 0 .../plugins}/timerestriction/models.py | 2 +- .../plugins}/timerestriction/signals.py | 8 ++-- .../plugins}/timerestriction/tests.py | 6 +-- src/pretix/presale/__init__.py | 8 ++++ .../presale}/middleware.py | 2 +- .../static/pretixpresale/js/ui/main.js | 0 .../static/pretixpresale/less/event.less | 0 .../static/pretixpresale/less/main.less | 5 +++ .../templates/pretixpresale/event/base.html | 0 .../event/fragment_availability.html | 0 .../pretixpresale/event/fragment_cart.html | 0 .../templates/pretixpresale/event/index.html | 0 .../presale}/tests.py | 0 src/pretix/presale/urls.py | 16 ++++++++ .../presale}/views/__init__.py | 2 +- .../presale}/views/cart.py | 4 +- .../presale}/views/event.py | 2 +- src/pretix/settings.py | 22 +++++----- src/pretix/urls.py | 8 ++-- .../static/pretixcontrol/less/main.less | 4 -- src/pretixcontrol/views/__init__.py | 0 .../timerestriction/migrations/__init__.py | 0 src/pretixpresale/__init__.py | 0 .../static/pretixpresale/less/main.less | 5 --- src/pretixpresale/urls.py | 16 -------- 117 files changed, 193 insertions(+), 163 deletions(-) create mode 100644 src/pretix/base/__init__.py rename src/{pretixbase => pretix/base}/admin.py (93%) rename src/{pretixbase => pretix/base}/cache.py (98%) rename src/{pretixbase => pretix/base}/forms.py (100%) rename src/{pretixbase => pretix/base}/middleware.py (100%) rename src/{pretixbase => pretix/base}/migrations/0001_initial.py (99%) rename src/{pretixbase => pretix/base}/migrations/0002_auto_20150211_2031.py (97%) rename src/{pretixbase => pretix/base}/migrations/0003_auto_20150211_2042.py (94%) rename src/{pretixbase => pretix/base}/migrations/0004_auto_20150211_2330.py (99%) rename src/{pretixbase => pretix/base}/migrations/0005_auto_20150212_0901.py (96%) rename src/{pretixbase => pretix/base}/migrations/0006_auto_20150212_0908.py (95%) rename src/{pretixbase => pretix/base}/migrations/0007_auto_20150212_0939.py (95%) rename src/{pretixbase => pretix/base}/migrations/0008_quota_locked.py (100%) rename src/{helpers => pretix/base/migrations}/__init__.py (100%) rename src/{pretixbase => pretix/base}/models.py (99%) rename src/{pretixbase => pretix/base}/plugins.py (71%) rename src/{pretixbase => pretix/base}/signals.py (96%) rename src/{pretixbase => pretix/base}/static/bootstrap (100%) rename src/{pretixbase => pretix/base}/static/fontawesome (100%) rename src/{pretixbase => pretix/base}/static/jquery/js/jquery-2.1.1.min.js (100%) rename src/{pretixbase => pretix/base}/tests/__init__.py (100%) rename src/{pretixbase => pretix/base}/tests/test_cache.py (96%) rename src/{pretixbase => pretix/base}/tests/test_middleware.py (97%) rename src/{pretixbase => pretix/base}/tests/test_models.py (99%) rename src/{pretixbase => pretix/base}/tests/test_plugins.py (83%) rename src/{pretixbase => pretix/base}/types.py (100%) create mode 100644 src/pretix/control/__init__.py rename src/{pretixcontrol => pretix/control}/context.py (100%) rename src/{pretixcontrol => pretix/control}/middleware.py (98%) rename src/{pretixbase => pretix/control/migrations}/__init__.py (100%) rename src/{pretixcontrol => pretix/control}/permissions.py (96%) rename src/{pretixcontrol => pretix/control}/signals.py (80%) rename src/{pretixcontrol => pretix/control}/static/pretixcontrol/js/ui/main.js (100%) rename src/{pretixcontrol => pretix/control}/static/pretixcontrol/less/auth.less (79%) rename src/{pretixcontrol => pretix/control}/static/pretixcontrol/less/forms.less (100%) create mode 100644 src/pretix/control/static/pretixcontrol/less/main.less rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/auth/base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/auth/login.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/event/base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/event/index.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/event/plugins.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/event/settings.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/event/settings_base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/events/index.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/index.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/restrictions.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/variations_0d.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/variations_1d.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/item/variations_nd.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/base.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/categories.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/category.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/category_delete.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/index.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/properties.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/property.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/property_delete.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/question.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/question_delete.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/questions.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/quota.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/quota_delete.html (100%) rename src/{pretixcontrol => pretix/control}/templates/pretixcontrol/items/quotas.html (100%) rename src/{pretixbase/migrations => pretix/control/tests}/__init__.py (100%) rename src/{pretixcontrol => pretix/control}/tests/test_auth.py (97%) rename src/{pretixcontrol => pretix/control}/tests/test_events.py (86%) rename src/{pretixcontrol => pretix/control}/tests/test_items.py (98%) rename src/{pretixcontrol => pretix/control}/tests/test_permissions.py (96%) rename src/{pretixcontrol => pretix/control}/urls.py (94%) rename src/{pretixcontrol => pretix/control/views}/__init__.py (100%) rename src/{pretixcontrol => pretix/control}/views/auth.py (100%) rename src/{pretixcontrol => pretix/control}/views/event.py (94%) rename src/{pretixcontrol => pretix/control}/views/forms.py (99%) rename src/{pretixcontrol => pretix/control}/views/item.py (99%) rename src/{pretixcontrol => pretix/control}/views/main.py (92%) rename src/{pretixcontrol/migrations => pretix/helpers}/__init__.py (100%) rename src/{ => pretix}/helpers/lessabsolutefilter.py (100%) rename src/{pretixplugins => pretix/plugins}/__init__.py (100%) rename src/{pretixplugins => pretix/plugins}/testdummy/__init__.py (59%) rename src/{pretixplugins => pretix/plugins}/testdummy/models.py (100%) rename src/{pretixplugins => pretix/plugins}/testdummy/signals.py (75%) rename src/{pretixplugins => pretix/plugins}/timerestriction/__init__.py (76%) rename src/{pretixplugins => pretix/plugins}/timerestriction/migrations/0001_initial.py (85%) rename src/{pretixcontrol/tests => pretix/plugins/timerestriction/migrations}/__init__.py (100%) rename src/{pretixplugins => pretix/plugins}/timerestriction/models.py (93%) rename src/{pretixplugins => pretix/plugins}/timerestriction/signals.py (95%) rename src/{pretixplugins => pretix/plugins}/timerestriction/tests.py (98%) create mode 100644 src/pretix/presale/__init__.py rename src/{pretixpresale => pretix/presale}/middleware.py (95%) rename src/{pretixpresale => pretix/presale}/static/pretixpresale/js/ui/main.js (100%) rename src/{pretixpresale => pretix/presale}/static/pretixpresale/less/event.less (100%) create mode 100644 src/pretix/presale/static/pretixpresale/less/main.less rename src/{pretixpresale => pretix/presale}/templates/pretixpresale/event/base.html (100%) rename src/{pretixpresale => pretix/presale}/templates/pretixpresale/event/fragment_availability.html (100%) rename src/{pretixpresale => pretix/presale}/templates/pretixpresale/event/fragment_cart.html (100%) rename src/{pretixpresale => pretix/presale}/templates/pretixpresale/event/index.html (100%) rename src/{pretixpresale => pretix/presale}/tests.py (100%) create mode 100644 src/pretix/presale/urls.py rename src/{pretixpresale => pretix/presale}/views/__init__.py (97%) rename src/{pretixpresale => pretix/presale}/views/cart.py (98%) rename src/{pretixpresale => pretix/presale}/views/event.py (97%) delete mode 100644 src/pretixcontrol/static/pretixcontrol/less/main.less delete mode 100644 src/pretixcontrol/views/__init__.py delete mode 100644 src/pretixplugins/timerestriction/migrations/__init__.py delete mode 100644 src/pretixpresale/__init__.py delete mode 100644 src/pretixpresale/static/pretixpresale/less/main.less delete mode 100644 src/pretixpresale/urls.py diff --git a/.gitmodules b/.gitmodules index 3e61b743cf..a88d0df0d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "src/pretixbase/static/bootstrap"] - path = src/pretixbase/static/bootstrap +[submodule "src/pretix/base/static/bootstrap"] + path = src/pretix/base/static/bootstrap url = https://github.com/twbs/bootstrap.git -[submodule "src/pretixbase/static/fontawesome"] - path = src/pretixbase/static/fontawesome +[submodule "src/pretix/base/static/fontawesome"] + path = src/pretix/base/static/fontawesome url = https://github.com/FortAwesome/Font-Awesome.git diff --git a/doc/development/api/plugins.rst b/doc/development/api/plugins.rst index 8a734a817a..7e4080e730 100644 --- a/doc/development/api/plugins.rst +++ b/doc/development/api/plugins.rst @@ -55,7 +55,7 @@ example, taken from the time restriction module (see next chapter) as a template name = 'pretixplugins.timerestriction' verbose_name = _("Time restriction") - class TixlPluginMeta: + class PretixPluginMeta: type = PluginType.RESTRICTION name = _("Restriciton by time") author = _("the pretix team") @@ -70,7 +70,7 @@ example, taken from the time restriction module (see next chapter) as a template default_app_config = 'pretixplugins.timerestriction.TimeRestrictionApp' .. IMPORTANT:: - You have to implement a ``TixlPluginMeta`` class like in the example to make your + You have to implement a ``PretixPluginMeta`` class like in the example to make your plugin available to the users. .. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/ diff --git a/doc/development/concepts.rst b/doc/development/concepts.rst index 74186e7fbf..7fd76046d9 100644 --- a/doc/development/concepts.rst +++ b/doc/development/concepts.rst @@ -9,26 +9,26 @@ The components The project pretix is split into several components. The main three of them are: -**pretixbase** - Tixlbase is the foundation below all other components. It is primarily +**pretix.base** + Pretixbase is the foundation below all other components. It is primarily responsible for the data structures and database communication. It also hosts several utilities which are used by multiple other components. -**pretixcontrol** - Tixlcontrol is the web-based backend software which allows organizers to +**pretix.control** + Pretixcontrol is the web-based backend software which allows organizers to create and manage their events, items, orders and tickets. -**pretixpresale** - Tixlpresale is the ticket-shop itself, containing all the parts visible to the +**pretix.presale** + Pretixpresale is the ticket-shop itself, containing all the parts visible to the end user. Users and events ^^^^^^^^^^^^^^^^ -Tixl is all about **events**, which are defined as something happening somewhere. +Pretix is all about **events**, which are defined as something happening somewhere. Every Event is managed by the **organizer**, an abstract entity running the event. -Tixl is used by **users**. We want to enable global users who can just login into +Pretix is used by **users**. We want to enable global users who can just login into pretix and buy tickets for as many events as they like but at the same time it should be possible to create some kind of local user to have a temporary account just to buy tickets for one single event. diff --git a/doc/development/goals.rst b/doc/development/goals.rst index 8d441ff10e..c1021e450d 100644 --- a/doc/development/goals.rst +++ b/doc/development/goals.rst @@ -1,7 +1,7 @@ Development goals ================= -Tixl is a web software handling presale of event tickets. +Pretix is a web software handling presale of event tickets. Technical goals --------------- @@ -19,7 +19,7 @@ Feature goals * One pretix software installation has to cope with multiple events by multiple organizers * There is no code access necessary to create a new event -* Tixl is abstract in many ways to adopt to as much events as possible. +*Tixe is abstract in many ways to adopt to as much events as possible. * Tickets are only an instance of an abstract model called items, such that the system can also sell e.g. merchandise * An abstract concept of restriction is used to restrict the selling of tickets, for example by date, by number or by user permissions. diff --git a/doc/development/structure.rst b/doc/development/structure.rst index 1cd0aea859..34fce3aa37 100644 --- a/doc/development/structure.rst +++ b/doc/development/structure.rst @@ -7,22 +7,22 @@ Python source code All the source code lives in ``src/``, which has several subdirectories. pretix/ - This directory contains the basic Django settings and URL routing. + This directory contains nearly all source code. -pretixbase/ - This is the django app containing all the models and methods which are - essential to all of pretix's features. + base/ + This is the django app containing all the models and methods which are + essential to all of pretix's features. -pretixcontrol/ - This is the django app containing the frontend for organizers. + control/ + This is the django app containing the frontend for organizers. -pretixpresale/ - This is the django app containing the frontend for users buying tickets. + presale/ + This is the django app containing the frontend for users buying tickets. -helpers/ - Helpers contain a very few modules providing workarounds for low-level flaws in - Django or installed 3rd-party packages, like a filter to combine the ``lessc`` - preprocessor with ``django-compressor``'s URL rewriting. + helpers/ + Helpers contain a very few modules providing workarounds for low-level flaws in + Django or installed 3rd-party packages, like a filter to combine the ``lessc`` + preprocessor with ``django-compressor``'s URL rewriting. Language files -------------- @@ -37,21 +37,25 @@ LESS source code We use less as a preprocessor for CSS. Our own less code is built in the same step as Bootstrap and FontAwesome, so their mixins etc. are fully available. -pretixcontrol - pretixcontrol has two main LESS files, ``pretixcontrol/static/pretixcontrol/less/main.less`` and - ``pretixcontrol/static/pretixcontrol/less/auth.less``, importing everything else. +pretix.control + pretixcontrol has two main LESS files, ``pretix/control/static/pretixcontrol/less/main.less`` and + ``pretix/control/static/pretixcontrol/less/auth.less``, importing everything else. + +pretix.presale + pretixpresale has one main LESS files, ``pretix/control/static/pretix/presale/less/main.less``, + importing everything else. 3rd-party assets ^^^^^^^^^^^^^^^^ Bootstrap - Bootstrap lives as a git submodule at ``pretixbase/static/bootstrap/`` + Bootstrap lives as a git submodule at ``pretix/base/static/bootstrap/`` Font Awesome - Font Awesome lives as a git submodule at ``pretixbase/static/fontawesome/`` + Font Awesome lives as a git submodule at ``pretix/base/static/fontawesome/`` jQuery - jQuery lives as a single JavaScript file in ``pretixbase/static/jquery/js/`` + jQuery lives as a single JavaScript file in ``pretix/base/static/jquery/js/`` jQuery plugin: Django formsets Our own modified version of `django-formset-js`_ is available as an independent diff --git a/src/.coveragerc b/src/.coveragerc index f5ecd52af9..3f57e0209b 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -1,5 +1,5 @@ [run] -source = pretixbase,pretixcontrol,pretixpresale,pretixplugins +source = pretix omit = */migrations/*,*/urls.py,*/tests/*,*/testdummy/*,*/admin.py [report] diff --git a/src/pretix/base/__init__.py b/src/pretix/base/__init__.py new file mode 100644 index 0000000000..2455a458a3 --- /dev/null +++ b/src/pretix/base/__init__.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class PretixBaseConfig(AppConfig): + name = 'pretix.base' + label = 'pretixbase' + +default_app_config = 'pretix.base.PretixBaseConfig' diff --git a/src/pretixbase/admin.py b/src/pretix/base/admin.py similarity index 93% rename from src/pretixbase/admin.py rename to src/pretix/base/admin.py index d89b424f49..862c5afbe3 100644 --- a/src/pretixbase/admin.py +++ b/src/pretix/base/admin.py @@ -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) diff --git a/src/pretixbase/cache.py b/src/pretix/base/cache.py similarity index 98% rename from src/pretixbase/cache.py rename to src/pretix/base/cache.py index 39398e8d10..e896d8b1cd 100644 --- a/src/pretixbase/cache.py +++ b/src/pretix/base/cache.py @@ -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: diff --git a/src/pretixbase/forms.py b/src/pretix/base/forms.py similarity index 100% rename from src/pretixbase/forms.py rename to src/pretix/base/forms.py diff --git a/src/pretixbase/middleware.py b/src/pretix/base/middleware.py similarity index 100% rename from src/pretixbase/middleware.py rename to src/pretix/base/middleware.py diff --git a/src/pretixbase/migrations/0001_initial.py b/src/pretix/base/migrations/0001_initial.py similarity index 99% rename from src/pretixbase/migrations/0001_initial.py rename to src/pretix/base/migrations/0001_initial.py index 034faa3822..0ca49bca30 100644 --- a/src/pretixbase/migrations/0001_initial.py +++ b/src/pretix/base/migrations/0001_initial.py @@ -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', diff --git a/src/pretixbase/migrations/0002_auto_20150211_2031.py b/src/pretix/base/migrations/0002_auto_20150211_2031.py similarity index 97% rename from src/pretixbase/migrations/0002_auto_20150211_2031.py rename to src/pretix/base/migrations/0002_auto_20150211_2031.py index 18d6e6853c..96a20328c8 100644 --- a/src/pretixbase/migrations/0002_auto_20150211_2031.py +++ b/src/pretix/base/migrations/0002_auto_20150211_2031.py @@ -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 diff --git a/src/pretixbase/migrations/0003_auto_20150211_2042.py b/src/pretix/base/migrations/0003_auto_20150211_2042.py similarity index 94% rename from src/pretixbase/migrations/0003_auto_20150211_2042.py rename to src/pretix/base/migrations/0003_auto_20150211_2042.py index a3e6d57dae..e810ad47f4 100644 --- a/src/pretixbase/migrations/0003_auto_20150211_2042.py +++ b/src/pretix/base/migrations/0003_auto_20150211_2042.py @@ -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): diff --git a/src/pretixbase/migrations/0004_auto_20150211_2330.py b/src/pretix/base/migrations/0004_auto_20150211_2330.py similarity index 99% rename from src/pretixbase/migrations/0004_auto_20150211_2330.py rename to src/pretix/base/migrations/0004_auto_20150211_2330.py index 8b0baf1f50..4a80794879 100644 --- a/src/pretixbase/migrations/0004_auto_20150211_2330.py +++ b/src/pretix/base/migrations/0004_auto_20150211_2330.py @@ -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): diff --git a/src/pretixbase/migrations/0005_auto_20150212_0901.py b/src/pretix/base/migrations/0005_auto_20150212_0901.py similarity index 96% rename from src/pretixbase/migrations/0005_auto_20150212_0901.py rename to src/pretix/base/migrations/0005_auto_20150212_0901.py index e7534c55bd..a96e77da52 100644 --- a/src/pretixbase/migrations/0005_auto_20150212_0901.py +++ b/src/pretix/base/migrations/0005_auto_20150212_0901.py @@ -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 diff --git a/src/pretixbase/migrations/0006_auto_20150212_0908.py b/src/pretix/base/migrations/0006_auto_20150212_0908.py similarity index 95% rename from src/pretixbase/migrations/0006_auto_20150212_0908.py rename to src/pretix/base/migrations/0006_auto_20150212_0908.py index ebe9483260..e950628ef4 100644 --- a/src/pretixbase/migrations/0006_auto_20150212_0908.py +++ b/src/pretix/base/migrations/0006_auto_20150212_0908.py @@ -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 diff --git a/src/pretixbase/migrations/0007_auto_20150212_0939.py b/src/pretix/base/migrations/0007_auto_20150212_0939.py similarity index 95% rename from src/pretixbase/migrations/0007_auto_20150212_0939.py rename to src/pretix/base/migrations/0007_auto_20150212_0939.py index 5b8a2505ac..fa1532b255 100644 --- a/src/pretixbase/migrations/0007_auto_20150212_0939.py +++ b/src/pretix/base/migrations/0007_auto_20150212_0939.py @@ -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): diff --git a/src/pretixbase/migrations/0008_quota_locked.py b/src/pretix/base/migrations/0008_quota_locked.py similarity index 100% rename from src/pretixbase/migrations/0008_quota_locked.py rename to src/pretix/base/migrations/0008_quota_locked.py diff --git a/src/helpers/__init__.py b/src/pretix/base/migrations/__init__.py similarity index 100% rename from src/helpers/__init__.py rename to src/pretix/base/migrations/__init__.py diff --git a/src/pretixbase/models.py b/src/pretix/base/models.py similarity index 99% rename from src/pretixbase/models.py rename to src/pretix/base/models.py index d66ca80511..84aefbe1d4 100644 --- a/src/pretixbase/models.py +++ b/src/pretix/base/models.py @@ -382,8 +382,8 @@ 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) @@ -929,7 +929,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, diff --git a/src/pretixbase/plugins.py b/src/pretix/base/plugins.py similarity index 71% rename from src/pretixbase/plugins.py rename to src/pretix/base/plugins.py index a1814fffa3..5be7a2d4a0 100644 --- a/src/pretixbase/plugins.py +++ b/src/pretix/base/plugins.py @@ -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 diff --git a/src/pretixbase/signals.py b/src/pretix/base/signals.py similarity index 96% rename from src/pretixbase/signals.py rename to src/pretix/base/signals.py index 99860e5c8f..d175743018 100644 --- a/src/pretixbase/signals.py +++ b/src/pretix/base/signals.py @@ -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) diff --git a/src/pretixbase/static/bootstrap b/src/pretix/base/static/bootstrap similarity index 100% rename from src/pretixbase/static/bootstrap rename to src/pretix/base/static/bootstrap diff --git a/src/pretixbase/static/fontawesome b/src/pretix/base/static/fontawesome similarity index 100% rename from src/pretixbase/static/fontawesome rename to src/pretix/base/static/fontawesome diff --git a/src/pretixbase/static/jquery/js/jquery-2.1.1.min.js b/src/pretix/base/static/jquery/js/jquery-2.1.1.min.js similarity index 100% rename from src/pretixbase/static/jquery/js/jquery-2.1.1.min.js rename to src/pretix/base/static/jquery/js/jquery-2.1.1.min.js diff --git a/src/pretixbase/tests/__init__.py b/src/pretix/base/tests/__init__.py similarity index 100% rename from src/pretixbase/tests/__init__.py rename to src/pretix/base/tests/__init__.py diff --git a/src/pretixbase/tests/test_cache.py b/src/pretix/base/tests/test_cache.py similarity index 96% rename from src/pretixbase/tests/test_cache.py rename to src/pretix/base/tests/test_cache.py index 52fdae565d..d160801764 100644 --- a/src/pretixbase/tests/test_cache.py +++ b/src/pretix/base/tests/test_cache.py @@ -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): diff --git a/src/pretixbase/tests/test_middleware.py b/src/pretix/base/tests/test_middleware.py similarity index 97% rename from src/pretixbase/tests/test_middleware.py rename to src/pretix/base/tests/test_middleware.py index 9b9fb0e2f8..be4d406396 100644 --- a/src/pretixbase/tests/test_middleware.py +++ b/src/pretix/base/tests/test_middleware.py @@ -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): diff --git a/src/pretixbase/tests/test_models.py b/src/pretix/base/tests/test_models.py similarity index 99% rename from src/pretixbase/tests/test_models.py rename to src/pretix/base/tests/test_models.py index 02da787a36..cf113a6c7c 100644 --- a/src/pretixbase/tests/test_models.py +++ b/src/pretix/base/tests/test_models.py @@ -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 +from pretix.base.types import VariationDict class ItemVariationsTest(TestCase): diff --git a/src/pretixbase/tests/test_plugins.py b/src/pretix/base/tests/test_plugins.py similarity index 83% rename from src/pretixbase/tests/test_plugins.py rename to src/pretix/base/tests/test_plugins.py index 3e8cc6a7fb..8b0570433d 100644 --- a/src/pretixbase/tests/test_plugins.py +++ b/src/pretix/base/tests/test_plugins.py @@ -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]) diff --git a/src/pretixbase/types.py b/src/pretix/base/types.py similarity index 100% rename from src/pretixbase/types.py rename to src/pretix/base/types.py diff --git a/src/pretix/control/__init__.py b/src/pretix/control/__init__.py new file mode 100644 index 0000000000..d53dcf208e --- /dev/null +++ b/src/pretix/control/__init__.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class PretixControlConfig(AppConfig): + name = 'pretix.control' + label = 'pretixcontrol' + +default_app_config = 'pretix.control.PretixControlConfig' diff --git a/src/pretixcontrol/context.py b/src/pretix/control/context.py similarity index 100% rename from src/pretixcontrol/context.py rename to src/pretix/control/context.py diff --git a/src/pretixcontrol/middleware.py b/src/pretix/control/middleware.py similarity index 98% rename from src/pretixcontrol/middleware.py rename to src/pretix/control/middleware.py index d6e5d864ac..e728a7ec4c 100644 --- a/src/pretixcontrol/middleware.py +++ b/src/pretix/control/middleware.py @@ -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: diff --git a/src/pretixbase/__init__.py b/src/pretix/control/migrations/__init__.py similarity index 100% rename from src/pretixbase/__init__.py rename to src/pretix/control/migrations/__init__.py diff --git a/src/pretixcontrol/permissions.py b/src/pretix/control/permissions.py similarity index 96% rename from src/pretixcontrol/permissions.py rename to src/pretix/control/permissions.py index c6eb961fbe..f9df2cd0f9 100644 --- a/src/pretixcontrol/permissions.py +++ b/src/pretix/control/permissions.py @@ -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): diff --git a/src/pretixcontrol/signals.py b/src/pretix/control/signals.py similarity index 80% rename from src/pretixcontrol/signals.py rename to src/pretix/control/signals.py index 3b7269f3f0..fe50902d33 100644 --- a/src/pretixcontrol/signals.py +++ b/src/pretix/control/signals.py @@ -1,4 +1,4 @@ -from pretixbase.signals import EventPluginSignal +from pretix.base.signals import EventPluginSignal """ diff --git a/src/pretixcontrol/static/pretixcontrol/js/ui/main.js b/src/pretix/control/static/pretixcontrol/js/ui/main.js similarity index 100% rename from src/pretixcontrol/static/pretixcontrol/js/ui/main.js rename to src/pretix/control/static/pretixcontrol/js/ui/main.js diff --git a/src/pretixcontrol/static/pretixcontrol/less/auth.less b/src/pretix/control/static/pretixcontrol/less/auth.less similarity index 79% rename from src/pretixcontrol/static/pretixcontrol/less/auth.less rename to src/pretix/control/static/pretixcontrol/less/auth.less index a15ed8e704..232d631fae 100644 --- a/src/pretixcontrol/static/pretixcontrol/less/auth.less +++ b/src/pretix/control/static/pretixcontrol/less/auth.less @@ -1,4 +1,4 @@ -@import "../../../../pretixbase/static/bootstrap/less/bootstrap.less"; +@import "../../../../base/static/bootstrap/less/bootstrap.less"; body { background: #eee; diff --git a/src/pretixcontrol/static/pretixcontrol/less/forms.less b/src/pretix/control/static/pretixcontrol/less/forms.less similarity index 100% rename from src/pretixcontrol/static/pretixcontrol/less/forms.less rename to src/pretix/control/static/pretixcontrol/less/forms.less diff --git a/src/pretix/control/static/pretixcontrol/less/main.less b/src/pretix/control/static/pretixcontrol/less/main.less new file mode 100644 index 0000000000..a7b151e8dd --- /dev/null +++ b/src/pretix/control/static/pretixcontrol/less/main.less @@ -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"; diff --git a/src/pretixcontrol/templates/pretixcontrol/auth/base.html b/src/pretix/control/templates/pretixcontrol/auth/base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/auth/base.html rename to src/pretix/control/templates/pretixcontrol/auth/base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/auth/login.html b/src/pretix/control/templates/pretixcontrol/auth/login.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/auth/login.html rename to src/pretix/control/templates/pretixcontrol/auth/login.html diff --git a/src/pretixcontrol/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/base.html rename to src/pretix/control/templates/pretixcontrol/base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/event/base.html b/src/pretix/control/templates/pretixcontrol/event/base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/event/base.html rename to src/pretix/control/templates/pretixcontrol/event/base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/event/index.html b/src/pretix/control/templates/pretixcontrol/event/index.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/event/index.html rename to src/pretix/control/templates/pretixcontrol/event/index.html diff --git a/src/pretixcontrol/templates/pretixcontrol/event/plugins.html b/src/pretix/control/templates/pretixcontrol/event/plugins.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/event/plugins.html rename to src/pretix/control/templates/pretixcontrol/event/plugins.html diff --git a/src/pretixcontrol/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/event/settings.html rename to src/pretix/control/templates/pretixcontrol/event/settings.html diff --git a/src/pretixcontrol/templates/pretixcontrol/event/settings_base.html b/src/pretix/control/templates/pretixcontrol/event/settings_base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/event/settings_base.html rename to src/pretix/control/templates/pretixcontrol/event/settings_base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/events/index.html b/src/pretix/control/templates/pretixcontrol/events/index.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/events/index.html rename to src/pretix/control/templates/pretixcontrol/events/index.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/base.html b/src/pretix/control/templates/pretixcontrol/item/base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/base.html rename to src/pretix/control/templates/pretixcontrol/item/base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/index.html rename to src/pretix/control/templates/pretixcontrol/item/index.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/restrictions.html b/src/pretix/control/templates/pretixcontrol/item/restrictions.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/restrictions.html rename to src/pretix/control/templates/pretixcontrol/item/restrictions.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/variations_0d.html b/src/pretix/control/templates/pretixcontrol/item/variations_0d.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/variations_0d.html rename to src/pretix/control/templates/pretixcontrol/item/variations_0d.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/variations_1d.html b/src/pretix/control/templates/pretixcontrol/item/variations_1d.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/variations_1d.html rename to src/pretix/control/templates/pretixcontrol/item/variations_1d.html diff --git a/src/pretixcontrol/templates/pretixcontrol/item/variations_nd.html b/src/pretix/control/templates/pretixcontrol/item/variations_nd.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/item/variations_nd.html rename to src/pretix/control/templates/pretixcontrol/item/variations_nd.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/base.html b/src/pretix/control/templates/pretixcontrol/items/base.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/base.html rename to src/pretix/control/templates/pretixcontrol/items/base.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/categories.html b/src/pretix/control/templates/pretixcontrol/items/categories.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/categories.html rename to src/pretix/control/templates/pretixcontrol/items/categories.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/category.html b/src/pretix/control/templates/pretixcontrol/items/category.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/category.html rename to src/pretix/control/templates/pretixcontrol/items/category.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/category_delete.html b/src/pretix/control/templates/pretixcontrol/items/category_delete.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/category_delete.html rename to src/pretix/control/templates/pretixcontrol/items/category_delete.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/index.html b/src/pretix/control/templates/pretixcontrol/items/index.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/index.html rename to src/pretix/control/templates/pretixcontrol/items/index.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/properties.html b/src/pretix/control/templates/pretixcontrol/items/properties.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/properties.html rename to src/pretix/control/templates/pretixcontrol/items/properties.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/property.html b/src/pretix/control/templates/pretixcontrol/items/property.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/property.html rename to src/pretix/control/templates/pretixcontrol/items/property.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/property_delete.html b/src/pretix/control/templates/pretixcontrol/items/property_delete.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/property_delete.html rename to src/pretix/control/templates/pretixcontrol/items/property_delete.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/question.html b/src/pretix/control/templates/pretixcontrol/items/question.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/question.html rename to src/pretix/control/templates/pretixcontrol/items/question.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/question_delete.html b/src/pretix/control/templates/pretixcontrol/items/question_delete.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/question_delete.html rename to src/pretix/control/templates/pretixcontrol/items/question_delete.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/questions.html b/src/pretix/control/templates/pretixcontrol/items/questions.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/questions.html rename to src/pretix/control/templates/pretixcontrol/items/questions.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/quota.html b/src/pretix/control/templates/pretixcontrol/items/quota.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/quota.html rename to src/pretix/control/templates/pretixcontrol/items/quota.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/quota_delete.html b/src/pretix/control/templates/pretixcontrol/items/quota_delete.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/quota_delete.html rename to src/pretix/control/templates/pretixcontrol/items/quota_delete.html diff --git a/src/pretixcontrol/templates/pretixcontrol/items/quotas.html b/src/pretix/control/templates/pretixcontrol/items/quotas.html similarity index 100% rename from src/pretixcontrol/templates/pretixcontrol/items/quotas.html rename to src/pretix/control/templates/pretixcontrol/items/quotas.html diff --git a/src/pretixbase/migrations/__init__.py b/src/pretix/control/tests/__init__.py similarity index 100% rename from src/pretixbase/migrations/__init__.py rename to src/pretix/control/tests/__init__.py diff --git a/src/pretixcontrol/tests/test_auth.py b/src/pretix/control/tests/test_auth.py similarity index 97% rename from src/pretixcontrol/tests/test_auth.py rename to src/pretix/control/tests/test_auth.py index 6a0511d7fc..b870936303 100644 --- a/src/pretixcontrol/tests/test_auth.py +++ b/src/pretix/control/tests/test_auth.py @@ -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() diff --git a/src/pretixcontrol/tests/test_events.py b/src/pretix/control/tests/test_events.py similarity index 86% rename from src/pretixcontrol/tests/test_events.py rename to src/pretix/control/tests/test_events.py index edf592df29..1596cc647e 100644 --- a/src/pretixcontrol/tests/test_events.py +++ b/src/pretix/control/tests/test_events.py @@ -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) diff --git a/src/pretixcontrol/tests/test_items.py b/src/pretix/control/tests/test_items.py similarity index 98% rename from src/pretixcontrol/tests/test_items.py rename to src/pretix/control/tests/test_items.py index 9dc2f8452f..ab253ef2a3 100644 --- a/src/pretixcontrol/tests/test_items.py +++ b/src/pretix/control/tests/test_items.py @@ -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): diff --git a/src/pretixcontrol/tests/test_permissions.py b/src/pretix/control/tests/test_permissions.py similarity index 96% rename from src/pretixcontrol/tests/test_permissions.py rename to src/pretix/control/tests/test_permissions.py index 90fa01ad8e..6b41837bf9 100644 --- a/src/pretixcontrol/tests/test_permissions.py +++ b/src/pretix/control/tests/test_permissions.py @@ -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): diff --git a/src/pretixcontrol/urls.py b/src/pretix/control/urls.py similarity index 94% rename from src/pretixcontrol/urls.py rename to src/pretix/control/urls.py index 73f87503b1..257a830e40 100644 --- a/src/pretixcontrol/urls.py +++ b/src/pretix/control/urls.py @@ -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[^/]+)/(?P[^/]+)/', 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'), diff --git a/src/pretixcontrol/__init__.py b/src/pretix/control/views/__init__.py similarity index 100% rename from src/pretixcontrol/__init__.py rename to src/pretix/control/views/__init__.py diff --git a/src/pretixcontrol/views/auth.py b/src/pretix/control/views/auth.py similarity index 100% rename from src/pretixcontrol/views/auth.py rename to src/pretix/control/views/auth.py diff --git a/src/pretixcontrol/views/event.py b/src/pretix/control/views/event.py similarity index 94% rename from src/pretixcontrol/views/event.py rename to src/pretix/control/views/event.py index 9afb2d3824..1d49f52c64 100644 --- a/src/pretixcontrol/views/event.py +++ b/src/pretix/control/views/event.py @@ -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() diff --git a/src/pretixcontrol/views/forms.py b/src/pretix/control/views/forms.py similarity index 99% rename from src/pretixcontrol/views/forms.py rename to src/pretix/control/views/forms.py index 261f8d4668..ded2d26835 100644 --- a/src/pretixcontrol/views/forms.py +++ b/src/pretix/control/views/forms.py @@ -7,9 +7,9 @@ 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): diff --git a/src/pretixcontrol/views/item.py b/src/pretix/control/views/item.py similarity index 99% rename from src/pretixcontrol/views/item.py rename to src/pretix/control/views/item.py index b3ec94a4d2..e1895a54a9 100644 --- a/src/pretixcontrol/views/item.py +++ b/src/pretix/control/views/item.py @@ -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): diff --git a/src/pretixcontrol/views/main.py b/src/pretix/control/views/main.py similarity index 92% rename from src/pretixcontrol/views/main.py rename to src/pretix/control/views/main.py index ab6f50c888..e545c7c07d 100644 --- a/src/pretixcontrol/views/main.py +++ b/src/pretix/control/views/main.py @@ -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): diff --git a/src/pretixcontrol/migrations/__init__.py b/src/pretix/helpers/__init__.py similarity index 100% rename from src/pretixcontrol/migrations/__init__.py rename to src/pretix/helpers/__init__.py diff --git a/src/helpers/lessabsolutefilter.py b/src/pretix/helpers/lessabsolutefilter.py similarity index 100% rename from src/helpers/lessabsolutefilter.py rename to src/pretix/helpers/lessabsolutefilter.py diff --git a/src/pretixplugins/__init__.py b/src/pretix/plugins/__init__.py similarity index 100% rename from src/pretixplugins/__init__.py rename to src/pretix/plugins/__init__.py diff --git a/src/pretixplugins/testdummy/__init__.py b/src/pretix/plugins/testdummy/__init__.py similarity index 59% rename from src/pretixplugins/testdummy/__init__.py rename to src/pretix/plugins/testdummy/__init__.py index b2d111f0c1..2e8e07261f 100644 --- a/src/pretixplugins/testdummy/__init__.py +++ b/src/pretix/plugins/testdummy/__init__.py @@ -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' diff --git a/src/pretixplugins/testdummy/models.py b/src/pretix/plugins/testdummy/models.py similarity index 100% rename from src/pretixplugins/testdummy/models.py rename to src/pretix/plugins/testdummy/models.py diff --git a/src/pretixplugins/testdummy/signals.py b/src/pretix/plugins/testdummy/signals.py similarity index 75% rename from src/pretixplugins/testdummy/signals.py rename to src/pretix/plugins/testdummy/signals.py index df00b52fa4..3e8248e4f9 100644 --- a/src/pretixplugins/testdummy/signals.py +++ b/src/pretix/plugins/testdummy/signals.py @@ -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) diff --git a/src/pretixplugins/timerestriction/__init__.py b/src/pretix/plugins/timerestriction/__init__.py similarity index 76% rename from src/pretixplugins/timerestriction/__init__.py rename to src/pretix/plugins/timerestriction/__init__.py index 7a258695cd..7a2830d363 100644 --- a/src/pretixplugins/timerestriction/__init__.py +++ b/src/pretix/plugins/timerestriction/__init__.py @@ -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' diff --git a/src/pretixplugins/timerestriction/migrations/0001_initial.py b/src/pretix/plugins/timerestriction/migrations/0001_initial.py similarity index 85% rename from src/pretixplugins/timerestriction/migrations/0001_initial.py rename to src/pretix/plugins/timerestriction/migrations/0001_initial.py index 3f28068437..80a125630c 100644 --- a/src/pretixplugins/timerestriction/migrations/0001_initial.py +++ b/src/pretix/plugins/timerestriction/migrations/0001_initial.py @@ -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', diff --git a/src/pretixcontrol/tests/__init__.py b/src/pretix/plugins/timerestriction/migrations/__init__.py similarity index 100% rename from src/pretixcontrol/tests/__init__.py rename to src/pretix/plugins/timerestriction/migrations/__init__.py diff --git a/src/pretixplugins/timerestriction/models.py b/src/pretix/plugins/timerestriction/models.py similarity index 93% rename from src/pretixplugins/timerestriction/models.py rename to src/pretix/plugins/timerestriction/models.py index c120e21334..e3ba537a4b 100644 --- a/src/pretixplugins/timerestriction/models.py +++ b/src/pretix/plugins/timerestriction/models.py @@ -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): diff --git a/src/pretixplugins/timerestriction/signals.py b/src/pretix/plugins/timerestriction/signals.py similarity index 95% rename from src/pretixplugins/timerestriction/signals.py rename to src/pretix/plugins/timerestriction/signals.py index 0106c8e912..abc6b02de8 100644 --- a/src/pretixplugins/timerestriction/signals.py +++ b/src/pretix/plugins/timerestriction/signals.py @@ -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 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 diff --git a/src/pretixplugins/timerestriction/tests.py b/src/pretix/plugins/timerestriction/tests.py similarity index 98% rename from src/pretixplugins/timerestriction/tests.py rename to src/pretix/plugins/timerestriction/tests.py index dba9f06160..619792778c 100644 --- a/src/pretixplugins/timerestriction/tests.py +++ b/src/pretix/plugins/timerestriction/tests.py @@ -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): diff --git a/src/pretix/presale/__init__.py b/src/pretix/presale/__init__.py new file mode 100644 index 0000000000..23e13910d7 --- /dev/null +++ b/src/pretix/presale/__init__.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class PretixPresaleConfig(AppConfig): + name = 'pretix.presale' + label = 'pretixpresale' + +default_app_config = 'pretix.presale.PretixPresaleConfig' diff --git a/src/pretixpresale/middleware.py b/src/pretix/presale/middleware.py similarity index 95% rename from src/pretixpresale/middleware.py rename to src/pretix/presale/middleware.py index 5277bbee66..96f73d4b61 100644 --- a/src/pretixpresale/middleware.py +++ b/src/pretix/presale/middleware.py @@ -1,7 +1,7 @@ from django.core.urlresolvers import resolve from django.http import HttpResponseNotFound -from pretixbase.models import Event +from pretix.base.models import Event class EventMiddleware: diff --git a/src/pretixpresale/static/pretixpresale/js/ui/main.js b/src/pretix/presale/static/pretixpresale/js/ui/main.js similarity index 100% rename from src/pretixpresale/static/pretixpresale/js/ui/main.js rename to src/pretix/presale/static/pretixpresale/js/ui/main.js diff --git a/src/pretixpresale/static/pretixpresale/less/event.less b/src/pretix/presale/static/pretixpresale/less/event.less similarity index 100% rename from src/pretixpresale/static/pretixpresale/less/event.less rename to src/pretix/presale/static/pretixpresale/less/event.less diff --git a/src/pretix/presale/static/pretixpresale/less/main.less b/src/pretix/presale/static/pretixpresale/less/main.less new file mode 100644 index 0000000000..87def66894 --- /dev/null +++ b/src/pretix/presale/static/pretixpresale/less/main.less @@ -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"; diff --git a/src/pretixpresale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html similarity index 100% rename from src/pretixpresale/templates/pretixpresale/event/base.html rename to src/pretix/presale/templates/pretixpresale/event/base.html diff --git a/src/pretixpresale/templates/pretixpresale/event/fragment_availability.html b/src/pretix/presale/templates/pretixpresale/event/fragment_availability.html similarity index 100% rename from src/pretixpresale/templates/pretixpresale/event/fragment_availability.html rename to src/pretix/presale/templates/pretixpresale/event/fragment_availability.html diff --git a/src/pretixpresale/templates/pretixpresale/event/fragment_cart.html b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html similarity index 100% rename from src/pretixpresale/templates/pretixpresale/event/fragment_cart.html rename to src/pretix/presale/templates/pretixpresale/event/fragment_cart.html diff --git a/src/pretixpresale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html similarity index 100% rename from src/pretixpresale/templates/pretixpresale/event/index.html rename to src/pretix/presale/templates/pretixpresale/event/index.html diff --git a/src/pretixpresale/tests.py b/src/pretix/presale/tests.py similarity index 100% rename from src/pretixpresale/tests.py rename to src/pretix/presale/tests.py diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py new file mode 100644 index 0000000000..f93c631cfc --- /dev/null +++ b/src/pretix/presale/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import patterns, url, include + +import pretix.presale.views.event +import pretix.presale.views.cart + +urlpatterns = patterns( + '', + url(r'^(?P[^/]+)/(?P[^/]+)/', include( + patterns( + 'pretix.presale.views.event', + url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), + url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), + url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'), + ) + )), +) diff --git a/src/pretixpresale/views/__init__.py b/src/pretix/presale/views/__init__.py similarity index 97% rename from src/pretixpresale/views/__init__.py rename to src/pretix/presale/views/__init__.py index 2ea8d8fb2a..962a2df79c 100644 --- a/src/pretixpresale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -3,7 +3,7 @@ from itertools import groupby from django.db.models import Q -from pretixbase.models import CartPosition +from pretix.base.models import CartPosition class CartMixin: diff --git a/src/pretixpresale/views/cart.py b/src/pretix/presale/views/cart.py similarity index 98% rename from src/pretixpresale/views/cart.py rename to src/pretix/presale/views/cart.py index dda833823f..35d1e6b357 100644 --- a/src/pretixpresale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -8,8 +8,8 @@ from django.utils.timezone import now from django.views.generic import View from django.utils.translation import ugettext_lazy as _ -from pretixbase.models import Item, ItemVariation, Quota, CartPosition -from pretixpresale.views import CartMixin, EventViewMixin +from pretix.base.models import Item, ItemVariation, Quota, CartPosition +from pretix.presale.views import CartMixin, EventViewMixin class CartActionMixin(CartMixin): diff --git a/src/pretixpresale/views/event.py b/src/pretix/presale/views/event.py similarity index 97% rename from src/pretixpresale/views/event.py rename to src/pretix/presale/views/event.py index fea1c49e93..7318c1fed0 100644 --- a/src/pretixpresale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -1,6 +1,6 @@ from django.db.models import Count from django.views.generic import TemplateView -from pretixpresale.views import EventViewMixin, CartDisplayMixin +from pretix.presale.views import EventViewMixin, CartDisplayMixin class EventIndex(EventViewMixin, CartDisplayMixin, TemplateView): diff --git a/src/pretix/settings.py b/src/pretix/settings.py index c5f1fb1396..27d332b125 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -37,15 +37,15 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'pretixbase', - 'pretixcontrol', - 'pretixpresale', + 'pretix.base', + 'pretix.control', + 'pretix.presale', 'compressor', 'bootstrap3', 'debug_toolbar.apps.DebugToolbarConfig', 'djangoformsetjs', - 'pretixplugins.testdummy', - 'pretixplugins.timerestriction', + 'pretix.plugins.testdummy', + 'pretix.plugins.timerestriction', ) MIDDLEWARE_CLASSES = ( @@ -57,9 +57,9 @@ MIDDLEWARE_CLASSES = ( 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'pretixcontrol.middleware.PermissionMiddleware', - 'pretixpresale.middleware.EventMiddleware', - 'pretixbase.middleware.LocaleMiddleware', + 'pretix.control.middleware.PermissionMiddleware', + 'pretix.presale.middleware.EventMiddleware', + 'pretix.base.middleware.LocaleMiddleware', ) TEMPLATE_CONTEXT_PROCESSORS = ( @@ -71,7 +71,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( "django.core.context_processors.static", "django.core.context_processors.tz", "django.contrib.messages.context_processors.messages", - 'pretixcontrol.context.contextprocessor', + 'pretix.control.context.contextprocessor', ) ROOT_URLCONF = 'pretix.urls' @@ -133,7 +133,7 @@ STATICFILES_FINDERS = ( ) COMPRESS_PRECOMPILERS = ( - ('text/less', 'helpers.lessabsolutefilter.LessFilter'), + ('text/less', 'pretix.helpers.lessabsolutefilter.LessFilter'), ) COMPRESS_CSS_FILTERS = ( @@ -148,7 +148,7 @@ DEBUG_TOOLBAR_CONFIG = { } -# Tixl specific settings +# Pretix specific settings PRETIX_INSTANCE_NAME = 'pretix.de' DEFAULT_CURRENCY = 'EUR' diff --git a/src/pretix/urls.py b/src/pretix/urls.py index 0179255219..5a67db51d7 100644 --- a/src/pretix/urls.py +++ b/src/pretix/urls.py @@ -2,12 +2,12 @@ from django.conf.urls import patterns, include, url from django.contrib import admin from django.conf import settings -import pretixcontrol.urls -import pretixpresale.urls +import pretix.control.urls +import pretix.presale.urls urlpatterns = patterns('', - url(r'^control/', include(pretixcontrol.urls, namespace='control')), + url(r'^control/', include(pretix.control.urls, namespace='control')), url(r'^admin/', include(admin.site.urls)), # The pretixpresale namespace is configured at the bottom of this file, because it # contains a wildcard-style URL which has to be configured _after_ debug settings. @@ -20,5 +20,5 @@ if settings.DEBUG: ) urlpatterns += patterns('', - url(r'', include(pretixpresale.urls, namespace='presale')) + url(r'', include(pretix.presale.urls, namespace='presale')) ) diff --git a/src/pretixcontrol/static/pretixcontrol/less/main.less b/src/pretixcontrol/static/pretixcontrol/less/main.less deleted file mode 100644 index 4044528912..0000000000 --- a/src/pretixcontrol/static/pretixcontrol/less/main.less +++ /dev/null @@ -1,4 +0,0 @@ -@import "../../../../pretixbase/static/bootstrap/less/bootstrap.less"; -@import "../../../../pretixbase/static/fontawesome/less/font-awesome.less"; -@fa-font-path: "../../fontawesome/fonts"; -@import "forms.less"; diff --git a/src/pretixcontrol/views/__init__.py b/src/pretixcontrol/views/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/pretixplugins/timerestriction/migrations/__init__.py b/src/pretixplugins/timerestriction/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/pretixpresale/__init__.py b/src/pretixpresale/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/pretixpresale/static/pretixpresale/less/main.less b/src/pretixpresale/static/pretixpresale/less/main.less deleted file mode 100644 index 2d71208018..0000000000 --- a/src/pretixpresale/static/pretixpresale/less/main.less +++ /dev/null @@ -1,5 +0,0 @@ -@import "../../../../pretixbase/static/bootstrap/less/bootstrap.less"; -@import "../../../../pretixbase/static/fontawesome/less/font-awesome.less"; -@fa-font-path: "../../fontawesome/fonts"; - -@import "event.less"; diff --git a/src/pretixpresale/urls.py b/src/pretixpresale/urls.py deleted file mode 100644 index 97929ffdbb..0000000000 --- a/src/pretixpresale/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.conf.urls import patterns, url, include - -import pretixpresale.views.event -import pretixpresale.views.cart - -urlpatterns = patterns( - '', - url(r'^(?P[^/]+)/(?P[^/]+)/', include( - patterns( - 'pretixpresale.views.event', - url(r'^$', pretixpresale.views.event.EventIndex.as_view(), name='event.index'), - url(r'^cart/add$', pretixpresale.views.cart.CartAdd.as_view(), name='event.cart.add'), - url(r'^cart/remove$', pretixpresale.views.cart.CartRemove.as_view(), name='event.cart.remove'), - ) - )), -) From 1a7b9d0744f66f7d756b27785bb4d926806b9220 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 17:57:31 +0100 Subject: [PATCH 08/11] There shouldn't have been an __init__.py in src/ --- src/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/__init__.py diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 93aeda0bdbf2d7b04d36005dbd31c7585ebfcfb1 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 18:00:10 +0100 Subject: [PATCH 09/11] Update plugin docs --- doc/development/api/plugins.rst | 24 ++++-------------------- doc/development/api/restriction.rst | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/doc/development/api/plugins.rst b/doc/development/api/plugins.rst index 7e4080e730..22d13e3d14 100644 --- a/doc/development/api/plugins.rst +++ b/doc/development/api/plugins.rst @@ -23,23 +23,7 @@ on the next pages. Creating a plugin ----------------- -To create a new plugin, create a new python package as a subpackage to ``pretixplugins``. -In order to do so, you can place your module into pretix's :file:`pretixplugins` folder *or -anywhere else in your python import path* inside a folder called ``pretixplugins``. - -.. IMPORTANT:: - This makes use of a design pattern called `namespace packages`_ which is only - implicitly available as of Python 3.4. As we aim to support Python 3.2 for a bit - longer, you **MUST** put **EXACLTY** the following content into ``pretixplugins/__init__.py`` - if you create a new ``pretixplugins`` folder somewhere in your path:: - - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) - - Otherwise it **will break** on Python 3.2 systems *depending on the python path's order*, - which is not tolerable behaviour. Also, even on Python 3.4 the test runner seems to have - problems without this workaround. - +To create a new plugin, create a new python package. Inside your newly created folder, you'll probably need the three python modules ``__init__.py``, ``models.py`` and ``signals.py``, although this is up to you. You can take the following @@ -48,11 +32,11 @@ example, taken from the time restriction module (see next chapter) as a template 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 PretixPluginMeta: @@ -67,7 +51,7 @@ example, taken from the time restriction module (see next chapter) as a template def ready(self): from . import signals # NOQA - default_app_config = 'pretixplugins.timerestriction.TimeRestrictionApp' + default_app_config = 'pretix.plugins.timerestriction.TimeRestrictionApp' .. IMPORTANT:: You have to implement a ``PretixPluginMeta`` class like in the example to make your diff --git a/doc/development/api/restriction.rst b/doc/development/api/restriction.rst index 8d39733174..5f69e8f57f 100644 --- a/doc/development/api/restriction.rst +++ b/doc/development/api/restriction.rst @@ -15,14 +15,14 @@ The restriction model It is very likely that your new restriction plugin needs to store data. In order to do so, it should define its own model with a name related to what your restriction does, -e.g. ``TimeRestriction``. This model should be a child class of ``pretixbase.models.BaseRestriction``. +e.g. ``TimeRestriction``. This model should be a child class of ``pretix.base.models.BaseRestriction``. You do not need to define custom fields, but you should create at least an empty model. -In our example, we put the following into :file:`pretixplugins/timerestriction/models.py`:: +In our example, we put the following into :file:`pretix/plugins/timerestriction/models.py`:: from django.db import models from django.utils.translation import ugettext_lazy as _ - from pretixbase.models import BaseRestriction + from pretix.base.models import BaseRestriction class TimeRestriction(BaseRestriction): @@ -52,14 +52,14 @@ Availability determination ^^^^^^^^^^^^^^^^^^^^^^^^^^ This is the one signal *every* restriction plugin has to listen for, as your plugin does not -restrict anything without doing so. It is available as ``pretixbase.signals.determine_availability`` +restrict anything without doing so. It is available as ``pretix.base.signals.determine_availability`` and is sent out every time some component of pretix wants to know whether a specific item or variation is available for sell. It is sent out with several keyword arguments: ``item`` - The instance of ``pretixbase.models.Item`` in question. + The instance of ``pretix.base.models.Item`` in question. ``variations`` A list of dictionaries in the same format as ``Item.get_all_variations``: The list contains one dictionary per variation, where the ``Property`` IDs are @@ -68,7 +68,7 @@ It is sent out with several keyword arguments: the item does not have any properties, the list will contain exactly one empty dictionary. Please note: this is *not* the list of all possible variations, this is only the list of all variations the frontend likes to determine the status for. - Technically, you won't get ``dict`` objects but ``pretixbase.types.VariationDict`` + Technically, you won't get ``dict`` objects but ``pretix.base.types.VariationDict`` objects, which behave exactly the same but add some extra methods. ``context`` A yet-to-be-defined context object containing information about the user and the order @@ -103,7 +103,7 @@ In our example, the implementation could look like this:: from django.dispatch import receiver from django.utils.timezone import now - from pretixbase.signals import determine_availability + from pretix.base.signals import determine_availability from .models import TimeRestriction @@ -217,12 +217,12 @@ Control interface formsets To make it possible for the event organizer to configure your restriction, there is a 'Restrictions' page in the item configuration. This page is able to show a formset for each restriction plugin, but *you* are required to create this formset. This is why you -should listen to the the ``pretixcontrol.signals.restriction_formset`` signal. +should listen to the the ``pretix.control.signals.restriction_formset`` signal. Currently, the signal comes with only one keyword argument: ``item`` - The instance of ``pretixbase.models.Item`` we want a formset for. + The instance of ``pretix.base.models.Item`` we want a formset for. You are expected to return a dict containing the following items: @@ -245,9 +245,9 @@ Our time restriction example looks like this:: from django.dispatch import receiver from django.forms.models import inlineformset_factory - from pretixcontrol.signals import restriction_formset - from pretixbase.models import Item - from pretixcontrol.views.forms import ( + from pretix.control.signals import restriction_formset + from pretix.base.models import Item + from pretix.control.views.forms import ( VariationsField, RestrictionInlineFormset, RestrictionForm ) From 38e313c886b5e1227d5343b8f91062fcb85f9893 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 14 Feb 2015 23:53:39 +0100 Subject: [PATCH 10/11] Cart expiry handling, checkout link --- .../templates/pretixpresale/event/index.html | 18 ++++++++++ src/pretix/presale/urls.py | 3 ++ src/pretix/presale/views/__init__.py | 6 ++++ src/pretix/presale/views/cart.py | 35 ++++++++++++++----- src/pretix/presale/views/checkout.py | 23 ++++++++++++ 5 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/pretix/presale/views/checkout.py diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index 5fe06c58ac..583cff30f8 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -9,6 +9,24 @@
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %} +
+
+ {% if cart.minutes_left > 0 %} + {% blocktrans trimmed with minutes=cart.minutes_left %} + The items in your cart are reserved for you for {{ minutes }} minutes. + {% endblocktrans %} + {% else %} + {% trans "The items in your cart are no longer reserved for you." %} + {% endif %} +
+ +
+
{% endif %} diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index f93c631cfc..9491ed5780 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import patterns, url, include import pretix.presale.views.event import pretix.presale.views.cart +import pretix.presale.views.checkout + urlpatterns = patterns( '', @@ -11,6 +13,7 @@ urlpatterns = patterns( url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'), + url(r'^checkout$', pretix.presale.views.checkout.CheckoutStart.as_view(), name='event.checkout.start'), ) )), ) diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py index 962a2df79c..7a0285c1d3 100644 --- a/src/pretix/presale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -1,7 +1,9 @@ import uuid from itertools import groupby +from datetime import timedelta from django.db.models import Q +from django.utils.timezone import now from pretix.base.models import CartPosition @@ -49,6 +51,10 @@ class CartDisplayMixin(CartMixin): return { 'positions': positions, 'total': sum(p.total for p in positions), + 'minutes_left': ( + max(min(p.expires for p in positions) - now(), timedelta()).seconds // 60 + if positions else 0 + ), } diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index 35d1e6b357..89cffc76bd 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -57,6 +57,14 @@ class CartActionMixin(CartMixin): return False return items + def _re_add_position(self, items, position): + for i, tup in enumerate(items): + if tup[0] == position.item_id and tup[1] == position.variation_id: + items[i] = (tup[0], tup[1], tup[2] + 1) + return items + items.append((position.item_id, position.variation_id, 1)) + return items + class CartRemove(EventViewMixin, CartActionMixin, View): @@ -94,6 +102,25 @@ class CartAdd(EventViewMixin, CartActionMixin, View): _("You cannot select more than %d items per order") % self.event.max_items_per_order) return redirect(self.get_failure_url()) + # Extend this user's cart session to 30 minutes from now to ensure all items in the + # cart expire at the same time + qw = Q(session=self.get_session_key()) + if self.request.user.is_authenticated(): + qw |= Q(user=self.request.user) + + # We can extend the reservation of items which are not yet expired without + # risk + CartPosition.objects.current.filter( + qw & Q(event=self.request.event) & Q(expires__gt=now()) + ).update(expires=now() + timedelta(minutes=30)) + + # For items that are already expired, we have to delete and re-add them, as they might + # be no longer available. Sorry! + for cp in CartPosition.objects.current.filter( + qw & Q(event=self.request.event) & Q(expires__lte=now())): + items = self._re_add_position(items, cp) + cp.delete() + # Fetch items from the database items_cache = { i.identity: i for i @@ -110,14 +137,6 @@ class CartAdd(EventViewMixin, CartActionMixin, View): ).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop") } - # Extend this user's cart session to 30 minutes from now to ensure all items in the - # cart expire at the same time - qw = Q(session=self.get_session_key()) - if self.request.user.is_authenticated(): - qw |= Q(user=self.request.user) - CartPosition.objects.current.filter( - qw & Q(event=self.request.event)).update(expires=now() + timedelta(minutes=30)) - # Process the request itself msg_some_unavailable = False for i in items: diff --git a/src/pretix/presale/views/checkout.py b/src/pretix/presale/views/checkout.py new file mode 100644 index 0000000000..ed494e6266 --- /dev/null +++ b/src/pretix/presale/views/checkout.py @@ -0,0 +1,23 @@ +from django.contrib import messages +from django.core.urlresolvers import reverse +from django.shortcuts import redirect +from django.views.generic import View +from django.utils.translation import ugettext_lazy as _ + +from pretix.presale.views import EventViewMixin, CartDisplayMixin + + +class CheckoutStart(EventViewMixin, CartDisplayMixin, View): + + def get_failure_url(self): + return reverse('presale:event.index', kwargs={ + 'event': self.request.event.slug, + 'organizer': self.request.event.organizer.slug, + }) + + def get(self, *args, **kwargs): + cart = self.get_cart() + if not cart['positions']: + messages.error(self.request, + _("Your cart is empty") % self.event.max_items_per_order) + return redirect(self.get_failure_url()) From 73aa3fb4bcfca0d27f29807693099bec049cae6b Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 15 Feb 2015 00:40:02 +0100 Subject: [PATCH 11/11] Add a simple framework for event- and organizer-related settings --- .../0009_eventsetting_organizersetting.py | 49 +++++++ src/pretix/base/models.py | 126 ++++++++++++++++++ src/pretix/base/tests/test_models.py | 68 +++++++++- 3 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/pretix/base/migrations/0009_eventsetting_organizersetting.py diff --git a/src/pretix/base/migrations/0009_eventsetting_organizersetting.py b/src/pretix/base/migrations/0009_eventsetting_organizersetting.py new file mode 100644 index 0000000000..a735eb3fd1 --- /dev/null +++ b/src/pretix/base/migrations/0009_eventsetting_organizersetting.py @@ -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,), + ), + ] diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index 84aefbe1d4..e14e659960 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -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): """ @@ -386,6 +439,56 @@ class Event(Versionable): 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): """ @@ -1320,3 +1423,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() diff --git a/src/pretix/base/tests/test_models.py b/src/pretix/base/tests/test_models.py index cf113a6c7c..f8499d7b8d 100644 --- a/src/pretix/base/tests/test_models.py +++ b/src/pretix/base/tests/test_models.py @@ -5,8 +5,8 @@ from django.utils.timezone import now from pretix.base.models import ( Event, Organizer, Item, ItemVariation, Property, PropertyValue, User, Quota, - Order, OrderPosition, CartPosition -) + Order, OrderPosition, CartPosition, + OrganizerSetting) from pretix.base.types import VariationDict @@ -292,3 +292,67 @@ class QuotaTestCase(TestCase): 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)