Fixed #108 -- Removed the restrictions system

This commit is contained in:
Raphael Michel
2015-12-06 17:49:02 +01:00
parent b26eaaa6c9
commit 4a1122a862
25 changed files with 26 additions and 1256 deletions

View File

@@ -2,8 +2,8 @@ from .auth import User
from .base import CachedFile, Versionable, cachedfile_name
from .event import Event, EventLock, EventPermission, EventSetting
from .items import (
BaseRestriction, Item, ItemCategory, ItemVariation, Property,
PropertyValue, Question, Quota, VariationsField, itempicture_upload_to,
Item, ItemCategory, ItemVariation, Property, PropertyValue, Question,
Quota, VariationsField, itempicture_upload_to,
)
from .orders import (
CachedTicket, CartPosition, ObjectWithAnswers, Order, OrderPosition,
@@ -14,7 +14,7 @@ from .organizer import Organizer, OrganizerPermission, OrganizerSetting
__all__ = [
'Versionable', 'User', 'CachedFile', 'Organizer', 'OrganizerPermission', 'Event', 'EventPermission',
'ItemCategory', 'Item', 'Property', 'PropertyValue', 'ItemVariation', 'VariationsField', 'Question',
'BaseRestriction', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'ObjectWithAnswers', 'OrderPosition',
'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'ObjectWithAnswers', 'OrderPosition',
'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock', 'cachedfile_name', 'itempicture_upload_to',
'generate_secret'
]

View File

@@ -80,8 +80,6 @@ class Item(Versionable):
An item is a thing which can be sold. It belongs to an event and may or may not belong to a category.
Items are often also called 'products' but are named 'items' internally due to historic reasons.
It has a default price which might by overriden by restrictions.
:param event: The event this belongs to.
:type event: Event
:param category: The category this belongs to. May be null.
@@ -249,10 +247,9 @@ class Item(Versionable):
def get_all_available_variations(self, use_cache: bool=False):
"""
This method returns a list of all variations which are theoretically
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 never
be available for sale. The only exception is the empty variation
possible for sale. 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
never 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
@@ -268,39 +265,18 @@ class Item(Versionable):
if use_cache and hasattr(self, '_get_all_available_variations_cache'):
return self._get_all_available_variations_cache
from pretix.base.signals import determine_availability
variations = self._get_all_generated_variations()
responses = determine_availability.send(
self.event, item=self,
variations=variations, context=None,
cache=self.event.get_cache()
)
for i, var in enumerate(variations):
var['available'] = var['variation'].active if 'variation' in var else True
if 'variation' in var:
if var['variation'].default_price:
if var['variation'].default_price is not None:
var['price'] = var['variation'].default_price
else:
var['price'] = self.default_price
else:
var['price'] = self.default_price
# It is possible, that *multiple* restriction plugins change the default price.
# In this case, the cheapest one wins. As soon as there is a restriction
# that changes the price, the default price has no effect.
newprice = None
for rec, response in responses:
if 'available' in response[i] and not response[i]['available']:
var['available'] = False
break
if 'price' in response[i] and response[i]['price'] is not None \
and (newprice is None or response[i]['price'] < newprice):
newprice = response[i]['price']
var['price'] = newprice or var['price']
variations = [var for var in variations if var['available']]
self._get_all_available_variations_cache = variations
@@ -322,38 +298,6 @@ class Item(Versionable):
return min([q.availability() for q in self.quotas.all()],
key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
def check_restrictions(self):
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
:returns:
* ``False``, if the item is unavailable
* the item's price, otherwise
:raises ValueError: if you call this on an item which has properties associated with it.
Please use the method on the ItemVariation object you are interested in.
"""
if self.properties.count() > 0: # NOQA
raise ValueError('Do not call this directly on items which have properties '
'but call this on their ItemVariation objects')
from pretix.base.signals import determine_availability
vd = VariationDict()
responses = determine_availability.send(
self.event, item=self,
variations=[vd], context=None,
cache=self.event.get_cache()
)
price = self.default_price
for rec, response in responses:
if 'available' in response[0] and not response[0]['available']:
return False
elif 'price' in response[0] and response[0]['price'] is not None and response[0]['price'] < price:
price = response[0]['price']
return price
class Property(Versionable):
"""
@@ -466,8 +410,6 @@ class ItemVariation(Versionable):
values by creating an ItemVariation object for them with active set to
False.
Restrictions can be not only set to items but also directly to variations.
:param item: The item this variation belongs to
:type item: Item
:param values: A set of ``PropertyValue`` objects defining this variation
@@ -531,31 +473,6 @@ class ItemVariation(Versionable):
vd['variation'] = self
return vd
def check_restrictions(self) -> Union[bool, Decimal]:
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
:returns:
* ``False``, if the item is unavailable
* the item's price, otherwise
"""
from pretix.base.signals import determine_availability
responses = determine_availability.send(
self.item.event, item=self.item,
variations=[self.to_variation_dict()], context=None,
cache=self.item.event.get_cache()
)
price = self.default_price if self.default_price is not None else self.item.default_price
for rec, response in responses:
if 'available' in response[0] and not response[0]['available']:
return False
elif 'price' in response[0] and response[0]['price'] is not None and response[0]['price'] < price:
price = response[0]['price']
return price
def add_values_from_string(self, pk):
"""
Add values to this ItemVariation using a serialized string of the form
@@ -672,47 +589,6 @@ class Question(Versionable):
self.event.get_cache().clear()
class BaseRestriction(Versionable):
"""
A restriction is the abstract concept of a rule that limits the availability
of Items or ItemVariations. This model is just an abstract base class to be
extended by restriction plugins.
"""
event = VersionedForeignKey(
Event,
on_delete=models.CASCADE,
related_name="restrictions_%(app_label)s_%(class)s",
verbose_name=_("Event"),
)
item = VersionedForeignKey(
Item,
blank=True, null=True,
verbose_name=_("Item"),
related_name="restrictions_%(app_label)s_%(class)s",
)
variations = VariationsField(
'pretixbase.ItemVariation',
blank=True,
verbose_name=_("Variations"),
related_name="restrictions_%(app_label)s_%(class)s",
)
class Meta:
abstract = True
verbose_name = _("Restriction")
verbose_name_plural = _("Restrictions")
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
if self.event:
self.event.get_cache().clear()
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.event:
self.event.get_cache().clear()
class Quota(Versionable):
"""
A quota is a "pool of tickets". It is there to limit the number of items

View File

@@ -90,15 +90,10 @@ def _add_items(event: Event, items: List[Tuple[str, Optional[str], int]],
item = items_cache[i[0]]
variation = variations_cache[i[1]] if i[1] is not None else None
# 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()
# 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 price is False or len(quotas) == 0 or not item.active:
if len(quotas) == 0 or not item.active:
err = err or error_messages['unavailable']
continue
@@ -121,11 +116,15 @@ def _add_items(event: Event, items: List[Tuple[str, Optional[str], int]],
# Recreating
cp = i[3].clone()
cp.expires = expiry
cp.price = price
cp.price = item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price)
cp.save()
else:
CartPosition.objects.create(
event=event, item=item, variation=variation, price=price, expires=expiry,
event=event, item=item, variation=variation,
price=item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price),
expires=expiry,
cart_id=cart_id
)
return err

View File

@@ -103,7 +103,8 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
if cp.expires >= dt:
# Other checks are not necessary
continue
price = cp.item.check_restrictions() if cp.variation is None else cp.variation.check_restrictions()
price = cp.item.default_price if cp.variation is None else (
cp.variation.default_price if cp.variation.default_price is not None else cp.item.default_price)
if price is False or len(quotas) == 0:
err = err or error_messages['unavailable']
cp.delete()

View File

@@ -46,15 +46,6 @@ class EventPluginSignal(django.dispatch.Signal):
responses.append((receiver, response))
return responses
"""
This signal is sent out every time some component of pretix wants to know whether a specific
item or variation is available for sell. The item will only be sold, if all (active) receivers
return a positive result (see plugin API documentation for details).
"""
determine_availability = EventPluginSignal(
providing_args=["item", "variations", "context", "cache"]
)
"""
This signal is sent out to get all known payment providers. Receivers should return a
subclass of pretix.base.payment.BasePaymentProvider