diff --git a/src/pretixbase/migrations/0004_auto_20150211_2330.py b/src/pretixbase/migrations/0004_auto_20150211_2330.py new file mode 100644 index 0000000000..8b0baf1f50 --- /dev/null +++ b/src/pretixbase/migrations/0004_auto_20150211_2330.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.utils.timezone import utc +import versions.models +import datetime +import pretixbase.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0003_auto_20150211_2042'), + ] + + operations = [ + migrations.AddField( + model_name='cartposition', + name='identity', + field=models.CharField(default='LEGACY', max_length=36), + preserve_default=False, + ), + migrations.AddField( + model_name='cartposition', + name='version_birth_date', + field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 3, 234665, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='cartposition', + name='version_end_date', + field=models.DateTimeField(blank=True, null=True, default=None), + preserve_default=True, + ), + migrations.AddField( + model_name='cartposition', + name='version_start_date', + field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 3, 234665, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='orderposition', + name='identity', + field=models.CharField(default='LEGACY', max_length=36), + preserve_default=False, + ), + migrations.AddField( + model_name='orderposition', + name='version_birth_date', + field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 15, 115790, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='orderposition', + name='version_end_date', + field=models.DateTimeField(blank=True, null=True, default=None), + preserve_default=True, + ), + migrations.AddField( + model_name='orderposition', + name='version_start_date', + field=models.DateTimeField(default=datetime.datetime(2015, 2, 11, 23, 30, 21, 726769, tzinfo=utc)), + preserve_default=False, + ), + migrations.AlterField( + model_name='cartposition', + name='id', + field=models.CharField(primary_key=True, serialize=False, max_length=36), + preserve_default=True, + ), + migrations.AlterField( + model_name='orderposition', + name='id', + field=models.CharField(primary_key=True, serialize=False, max_length=36), + preserve_default=True, + ), + ] diff --git a/src/pretixbase/models.py b/src/pretixbase/models.py index 13ce7f63f1..e7d09c1298 100644 --- a/src/pretixbase/models.py +++ b/src/pretixbase/models.py @@ -446,6 +446,13 @@ class ItemCategory(Versionable): if self.event: self.event.get_cache().clear() + def __lt__(self, other): + if self.position < other.position: + return True + if self.position == other.position: + return self.pk < other.pk + return False + class Property(Versionable): """ @@ -517,6 +524,13 @@ class PropertyValue(Versionable): if self.prop: self.prop.event.get_cache().clear() + def __lt__(self, other): + if self.position < other.position: + return True + if self.position == other.position: + return self.pk < other.pk + return False + class Question(Versionable): """ @@ -708,14 +722,32 @@ class Item(Versionable): def get_all_available_variations(self): """ This method returns a list of all variations which are theoretically - possible for sale. It DOES call all activated restriction plugins, but it - DOES NOT take into account quotas. Use is_available on the ItemVariation - objects (or the Item it self, if it does not have variations) to determine - availability by the terms of quotas. + possible for sale. It DOES call all activated restriction plugins, and it + DOES only return variations which DO have an ItemVariation object, as all + variations without one CAN NOT be part of a Quota and therefore CAN NOT + ever be available for sale. The only exception is the empty variation + for items without properties, which never has an ItemVariation object. + + This DOES NOT take into account quotas itself. Use is_available on the + ItemVariation objects (or the Item it self, if it does not have variations) to + determine availability by the terms of quotas. + + It is recommended to call + prefetch_related('properties', 'variations__values__prop') + when retrieving Item objects you are going to use this method on. """ from .signals import determine_availability - - variations = self.get_all_variations() + if self.properties.count() == 0: + variations = [VariationDict()] + else: + all_variations = list(self.variations.all()) + variations = [] + for var in all_variations: + vardict = VariationDict() + for v in var.values.all(): + vardict[v.prop.identity] = v + vardict['variation'] = var + variations.append(vardict) responses = determine_availability.send( self.event, item=self, variations=variations, context=None, @@ -746,7 +778,7 @@ class Item(Versionable): 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() """ - if self.properties.exist(): + if self.properties.count() > 0: raise ValueError('Do not call this directly on items which have properties ' 'but call this on their ItemVariation objects') return max([q.availability() for q in self.quotas.all()]) @@ -987,25 +1019,25 @@ class Quota(Versionable): Q(variation__quotas__in=[self]) ) ) - paid_orders = OrderPosition.objects.current.filter( + paid_orders = OrderPosition.objects.filter( Q(order__status=Order.STATUS_PAID) & quotalookup - ) + ).count() if paid_orders >= self.size: return Quota.AVAILABILITY_GONE - pending_valid_orders = OrderPosition.objects.current.filter( + pending_valid_orders = OrderPosition.objects.filter( Q(order__status=Order.STATUS_PENDING) & Q(order__expires__gte=now()) & quotalookup - ) + ).count() if (paid_orders + pending_valid_orders) >= self.size: return Quota.AVAILABILITY_ORDERED - valid_cart_positions = CartPosition.objects.current.filter( - Q(order__expires__lt=now()) + valid_cart_positions = CartPosition.objects.filter( + Q(expires__gte=now()) & quotalookup - ) + ).count() if (paid_orders + pending_valid_orders + valid_cart_positions) >= self.size: return Quota.AVAILABILITY_RESERVED @@ -1080,7 +1112,7 @@ class QuestionAnswer(Versionable): answer = models.TextField() -class OrderPosition(models.Model): +class OrderPosition(Versionable): """ An OrderPosition is one line of an order, representing one ordered items of a specified type (or variation). @@ -1116,7 +1148,7 @@ class OrderPosition(models.Model): verbose_name_plural = _("Order positions") -class CartPosition(models.Model): +class CartPosition(Versionable): """ A cart position is similar to a order line, except that it is not yet part of a binding order but just placed by some user in his or diff --git a/src/pretixbase/types.py b/src/pretixbase/types.py index 34e2b6a3e9..605ac69a40 100644 --- a/src/pretixbase/types.py +++ b/src/pretixbase/types.py @@ -35,7 +35,7 @@ class VariationDict(dict): unique among one item. """ def order_key(i): - i[0] + return i[0] return ",".join(( str(v[1].pk) for v in sorted(self.relevant_items(), key=order_key) )) diff --git a/src/pretixpresale/static/pretixpresale/less/event.less b/src/pretixpresale/static/pretixpresale/less/event.less new file mode 100644 index 0000000000..d6304450e5 --- /dev/null +++ b/src/pretixpresale/static/pretixpresale/less/event.less @@ -0,0 +1,39 @@ +.product-row { + padding: 10px 0; + border-top: 1px solid @table-border-color; + + &.headline, &.simple { + border-top: 2px solid @table-border-color; + } + &:last-child { + border-bottom: 2px solid @table-border-color; + } + p:last-child { + margin-bottom: 0; + } + + .input-item-count { + text-align: center; + } + .availability-box { + text-align: center; + + &.gone { + color: @alert-danger-text; + } + &.unavailable { + color: @alert-warning-text; + } + } + .price { + text-align: center; + } + .price small, + .availability-box small { + display: block; + line-height: 1; + } +} +.checkout-button-row { + padding: 15px 0; +} diff --git a/src/pretixpresale/static/pretixpresale/less/main.less b/src/pretixpresale/static/pretixpresale/less/main.less index 037d8d3fcf..2d71208018 100644 --- a/src/pretixpresale/static/pretixpresale/less/main.less +++ b/src/pretixpresale/static/pretixpresale/less/main.less @@ -2,9 +2,4 @@ @import "../../../../pretixbase/static/fontawesome/less/font-awesome.less"; @fa-font-path: "../../fontawesome/fonts"; -.input-item-count { - text-align: center; -} -.availabilitybox.available { - text-align: center; -} +@import "event.less"; diff --git a/src/pretixpresale/templates/pretixpresale/event/fragment_availability.html b/src/pretixpresale/templates/pretixpresale/event/fragment_availability.html new file mode 100644 index 0000000000..bf8792446b --- /dev/null +++ b/src/pretixpresale/templates/pretixpresale/event/fragment_availability.html @@ -0,0 +1,13 @@ +{% load i18n %} +{% if avail == 30 %} +
{{ item.short_description }}
-{{ item.short_description }}
{% endif %}{{ item.short_description }}
+ {% for var in item.available_variations %} +{{ item.short_description }}
{% endif %} +