Start documenting the models

This commit is contained in:
Raphael Michel
2015-03-19 12:17:19 +01:00
parent db6077eeb2
commit 43c44e917a
3 changed files with 321 additions and 41 deletions

View File

@@ -123,11 +123,11 @@ class User(AbstractBaseUser, PermissionsMixin):
This is the user model used by pretix for authentication.
Handling users is somehow complicated, as we try to have two
classes of users in one system:
(1) We want global users who can just login into pretix and
(1) We want *global* users who can just login into pretix and
buy tickets for multiple events -- we also need those
global users for event organizers who should not need
multiple users for managing multiple events.
(2) We want local users who exist only in the scope of a
(2) We want *local* users who exist only in the scope of a
certain event
The hard part is to find a primary key to identify all of these
users. Letting the users choose usernames is a bad idea, as
@@ -148,7 +148,30 @@ class User(AbstractBaseUser, PermissionsMixin):
according to this scheme when it is empty. The __str__() method
returns the identifier.
The is_staff field is only True for system operators.
:param identifier: The identifier of the user, as described above
:type identifier: str
:param username: The username, null for global users.
:type username: str
:param event: The event the user belongs to, null for global users
:type event: Event
:param email: The user's e-mail address. May be empty or null for local users
:type email: str
:param givenname: The user's given name. May be empty or null.
:type givenname: str
:param familyname: The user's given name. May be empty or null.
:type familyname: str
:param givenname: The user's given name. May be empty or null.
:type givenname: str
:param is_active: Whether this user account is activated.
:type is_active: bool
:param is_staff: ``True`` for system operators.
:type is_staff: bool
:param date_joined: The datetime of the user's registration.
:type date_joined: datetime
:param locale: The user's preferred locale code.
:type locale: str
:param timezone: The user's preferred timezone.
:type timezone: str
"""
USERNAME_FIELD = 'identifier'
@@ -195,6 +218,10 @@ class User(AbstractBaseUser, PermissionsMixin):
return self.identifier
def save(self, *args, **kwargs):
"""
Before passing the call to the default ``save()`` method, this will fill the ``identifier``
field if it is empty, according to the scheme descriped in the model docstring.
"""
if not self.identifier:
if self.event is None:
self.identifier = self.email.lower()
@@ -205,6 +232,13 @@ class User(AbstractBaseUser, PermissionsMixin):
super().save(*args, **kwargs)
def get_short_name(self) -> str:
"""
Returns the first of the following user properties that is found to exist:
* Given name
* Family name
* User name
"""
if self.givenname:
return self.givenname
elif self.familyname:
@@ -213,6 +247,14 @@ class User(AbstractBaseUser, PermissionsMixin):
return self.username
def get_full_name(self) -> str:
"""
Returns the first of the following user properties that is found to exist:
* A combination of given name and family name, depending on the locale
* Given name
* Family name
* User name
"""
if self.givenname and not self.familyname:
return self.givenname
elif not self.givenname and self.familyname:
@@ -232,8 +274,10 @@ class Organizer(Versionable):
charity, person, …
:param name: The organizer's name
:type name: str
:param slug: A globally unique, short name for this organizer, to be used
in URLs and similar places.
:type slug: str
"""
name = models.CharField(max_length=200,
@@ -253,7 +297,7 @@ class Organizer(Versionable):
return self.name
@cached_property
def settings(self):
def settings(self) -> SettingsProxy:
"""
Returns an object representing this organizer's settings
"""
@@ -264,6 +308,14 @@ class OrganizerPermission(Versionable):
"""
The relation between an Organizer and an User who has permissions to
access an organizer profile.
:param organizer: The organizer this relation refers to
:type organizer: Organizer
:param user: The user this set of permissions is valid for
:type user: User
:param can_create_events: Whether or not this user can create new events with this
organizer account.
:type can_create_events: bool
"""
organizer = VersionedForeignKey(Organizer)
@@ -290,26 +342,41 @@ class Event(Versionable):
tickets for.
:param organizer: The organizer this event belongs to
:type organizer: Organizer
:param name: This events full title
:type name: str
:param slug: A short, alphanumeric, all-lowercase name for use in URLs. The slug has to
be unique among the events of the same organizer.
:type slug: str
:param locale: This events default locale
:type locale: str
:param timezone: The timezone this event takes place in (or is being described in)
:type timezone: str
:param currency: The currency of all prices and payments of this event
:type currency: str
:param date_from: The datetime this event starts
:type date_from: datetime
:param date_to: The datetime this event ends
:type date_to: datetime
:param show_date_to: If ``False``, the ``date_to`` value will not be shown to the
user and the event will be listed only with it's start date.
:type show_date_to: bool
:param show_times: If ``False``, ``date_from`` and ``date_to`` will only be displayed
as dates, without a time of day.
:type show_times: bool
:param presale_start: No tickets will be sold before this date.
:type presale_start: datetime
:param presale_end: No tickets will be sold before this date.
:type presale_end: datetime
:param payment_term_days: The number of days in which an order has to be
paid after it has been submitted.
:type payment_term_days: int
:param payment_term_last: The day all orders must be paid by, no matter when
the order was placed.
:type payment_term_last: datetime
:param plugins: A comma-separated list of plugin names that are active for this
event.
:type plugins: str
"""
organizer = VersionedForeignKey(Organizer, related_name="events",
@@ -394,17 +461,29 @@ class Event(Versionable):
return obj
def get_plugins(self) -> "list[str]":
"""
Get the names of the plugins activated for this event as a list.
"""
if self.plugins is None:
return []
return self.plugins.split(",")
def get_date_from_display(self) -> str:
"""
Returns a formatted string containing the start date of the event with respect
to the current locale and to the ``show_times`` setting.
"""
return _date(
self.date_from,
"DATETIME_FORMAT" if self.show_times else "DATE_FORMAT"
)
def get_date_to_display(self) -> str:
"""
Returns a formatted string containing the start date of the event with respect
to the current locale and to the ``show_times`` setting. Returns an empty string
if ``show_date_to`` is ``False``.
"""
if not self.show_date_to:
return ""
return _date(
@@ -413,6 +492,12 @@ class Event(Versionable):
)
def get_cache(self) -> "pretix.base.cache.EventRelatedCache":
"""
Returns an :py:class:`EventRelatedCache` object. This behaves equivalent to
Django's built-in cache backends, but puts you into an isolated environment for
this event, so you don't have to prefix your cache keys. In addition, the cache
is being cleared every time the event or one of its related objects change.
"""
from pretix.base.cache import EventRelatedCache
return EventRelatedCache(self)
@@ -428,6 +513,15 @@ class EventPermission(Versionable):
"""
The relation between an Event and an User who has permissions to
access an event.
:param event: The event this refers to
:type event: Event
:param user: The user these permission set applies to
:type user: User
:param can_change_settings: If ``True``, the user can change all basic settings for this event.
:type can_change_settings: bool
:param can_change_items: If ``True``, the user can change and add items and related objects for this event.
:type can_change_items: bool
"""
event = VersionedForeignKey(Event)
@@ -454,8 +548,14 @@ class EventPermission(Versionable):
class ItemCategory(Versionable):
"""
Items can be sorted into categories, which only have a name and a
configurable order
Items can be sorted into these categories.
:param event: The event this belongs to
:type event: Event
:param name: The name of this category
:type name: str
:param position: An integer, used for sorting
:type position: int
"""
event = VersionedForeignKey(
Event,
@@ -500,6 +600,11 @@ class Property(Versionable):
"""
A property is a modifier which can be applied to an Item. For example
'Size' would be a property associated with the item 'T-Shirt'.
:param event: The event this belongs to
:type event: Event
:param name: The name of this property.
:type name: str
"""
event = VersionedForeignKey(
@@ -532,7 +637,14 @@ class Property(Versionable):
class PropertyValue(Versionable):
"""
A value of a property. If the property would be 'T-Shirt size',
this could be 'M' or 'L'
this could be 'M' or 'L'.
:param prop: The property this value is a valid option for.
:type prop: Property
:param value: The value, as a human-readable string
:type value: str
:param position: An integer, used for sorting
:type position: int
"""
prop = VersionedForeignKey(
@@ -577,7 +689,22 @@ class PropertyValue(Versionable):
class Question(Versionable):
"""
A question is an input field that can be used to extend a ticket
by custom information, e.g. "Attendee name" or "Attendee age".
by custom information, e.g. "Attendee age". A question can allow one o several
input types, currently:
* a number (``TYPE_NUMBER``)
* a one-line string (``TYPE_STRING``)
* a multi-line string (``TYPE_TEXT``)
* a boolean (``TYPE_BOOLEAN``)
:param event: The event this question belongs to
:type event: Event
:param question: The question text. This will be displayed next to the input field.
:type question: str
:param type: One of the above types
:param required: Whether answering this question is required for submiting an order including
items associated with this question.
:type required: bool
"""
TYPE_NUMBER = "N"
TYPE_STRING = "S"
@@ -627,17 +754,32 @@ class Question(Versionable):
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.
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.
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.
:type category: ItemCategory
:param name: The name of this item:
:type name: str
:param active: Whether this item is being sold
:type active: bool
:param short_description: A short description
:type short_description: str
:param long_description: A long description
:type long_description: str
:param default_price: The item's default price
:type default_price: decimal.Decimal
:param tax_rate: The VAT tax that is included in this item's price (in %)
:type tax_rate: decimal.Decimal
:param properties: A set of ``Property`` objects that should be applied to this item
:param questions: A set of ``Question`` objects that should be applied to this item
:param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise)
:type admission: bool
Items can not be deleted, as this would cause database
inconsistencies. Instead, they have an attribute "deleted".
Deleted items will not be shown anywhere.
"""
event = VersionedForeignKey(
Event,
@@ -736,6 +878,11 @@ class Item(Versionable):
VariationDicts differ from dicts only by specifying some extra
methods.
:param use_cache: If this parameter is set to ``True``, a second call to this method
on the same model instance won't query the database again but return
the previous result again.
:type use_cache: bool
"""
if use_cache and hasattr(self, '_get_all_variations_cache'):
return self._get_all_variations_cache
@@ -773,16 +920,18 @@ class Item(Versionable):
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 NOT
ever be available for sale. The only exception is the empty variation
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
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')
It is recommended to call::
.prefetch_related('properties', 'variations__values__prop')
when retrieving Item objects you are going to use this method on.
"""
if use_cache and hasattr(self, '_get_all_available_variations_cache'):
@@ -842,7 +991,12 @@ class Item(Versionable):
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()
for sale.
:returns: any of the return codes of :py:meth:`Quota.availability()`.
: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 '
@@ -853,8 +1007,14 @@ class Item(Versionable):
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
It returns False, if the item is unavailable or the item's price, if it is
available.
: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 '
@@ -879,20 +1039,29 @@ class ItemVariation(Versionable):
"""
A variation is an item combined with values for all properties
associated with the item. For example, if your item is 'T-Shirt'
and your properties are 'Size' and 'Color', then an example for a
and your properties are 'Size' and 'Color', then an example for an
variation would be 'T-Shirt XL read'.
Attention: _ALL_ combinations of PropertyValues _ALWAYS_ exist,
even if there is no ItemVariation object for them! ItemVariation objects
do NOT prove existance, they are only available to make it possible
to override default values (like the price) for certain combinations
of property values.
of property values. However, appropriate ItemVariation objects will be
created as soon as you add your variations to a quota.
They also allow to explicitly EXCLUDE certain combinations of property
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
:param active: Whether this value is to be sold.
:type active: bool
:param default_price: This variation's default price
:type default_price: decimal.Decimal
"""
item = VersionedForeignKey(
Item,
@@ -932,12 +1101,16 @@ class ItemVariation(Versionable):
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
of Quota.availability()
available for sale in terms of quotas.
:returns: any of the return codes of :py:meth:`Quota.availability()`.
"""
return min([q.availability() for q in self.quotas.all()])
def to_variation_dict(self):
"""
:return: a :py:class:`VariationDict` representing this variation.
"""
vd = VariationDict()
for v in self.values.all():
vd[v.prop.identity] = v
@@ -948,8 +1121,11 @@ class ItemVariation(Versionable):
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
It returns False, if the item is unavailable or the item's price, if it is
available.
:returns:
* ``False``, if the item is unavailable
* the item's price, otherwise
"""
from .signals import determine_availability
responses = determine_availability.send(
@@ -1058,9 +1234,7 @@ class Quota(Versionable):
VIP tickets will be fine).
As always, a quota can not only be tied to an item, but also to specific
variations. We follow the general rule here: If there are no variations
speficied, the quota applies to all of them, and if there are variations
specified, the quota applies to those.
variations.
Please read the documentation section on quotas carefully before doing
anything with quotas. This might confuse you otherwise.
@@ -1084,6 +1258,15 @@ class Quota(Versionable):
AVAILABILITY_GONE
This item is completely sold out.
:param event: The event this belongs to
:type event: Event
:param name: This quota's name
:type str:
:param size: The number of items in this quota
:type size: int
:param items: The set of :py:class:`Item` objects this quota applies to
:param variations: The set of :py:class:`ItemVariation` objects this quota applies to
"""
AVAILABILITY_GONE = 0
@@ -1141,9 +1324,10 @@ class Quota(Versionable):
def availability(self):
"""
This method is used to determine whether Items or ItemVariations belonging
to this quota should currently be available for sale. It returns a tuple where
the first entry is one of the Quota.AVAILABILITY_ constants and the second
is the number of available tickets.
to this quota should currently be available for sale.
:returns: a tuple where the first entry is one of the Quota.AVAILABILITY_ constants and the second
is the number of available tickets.
"""
# TODO: These lookups are highly inefficient. However, we'll wait with optimizing
# until Django 1.8 is released, as the following feature might make it a
@@ -1192,10 +1376,9 @@ class Quota(Versionable):
def lock(self):
"""
Issue a lock on this quota so nobody can take tickets from this quota until
you release the lock.
you release the lock. Will retry 5 times on failure.
Raises an Quota.LockTimeoutException if the quota is locked every time we
try to obtain a lock.
:raises Quota.LockTimeoutException: if the quota is locked every time we try to obtain the lock
"""
retries = 5
for i in range(retries):
@@ -1215,7 +1398,7 @@ class Quota(Versionable):
def release(self, force=False):
"""
Release a lock placed by lock(). If the parameter force is not set,
Release a lock placed by :py:meth:`lock()`. If the parameter force is not set to ``True``,
the lock will only be released if it was issued in _this_ python
representation of the database object.
"""