Updated models and signals documentation

This commit is contained in:
Raphael Michel
2016-05-08 12:35:04 +02:00
parent f40efc0304
commit eea90a9066
8 changed files with 118 additions and 18 deletions

View File

@@ -31,3 +31,36 @@ will have an attribute ``event``.
``pretix.control.signals.nav_event``: ``pretix.control.signals.nav_event``:
The sidebar navigation when the admin has selected an event. The sidebar navigation when the admin has selected an event.
Order events
------------
There are multiple signals that will be sent out in the ordering cycle:
``pretix.base.signals.order_placed``:
Sent out every time an order has been created. Provides the ``order`` as the only
keyword argument.
``pretix.base.signals.order_paid``:
Sent out every time an order has been paid. Provides the ``order`` as the only
keyword argument.
Displaying of log entries
-------------------------
To display an instance of the ``LogEntry`` model to a human user,
``pretix.base.signals.logentry_display`` will be sent out with a ``logentry`` argument.
The first received response that is not ``None`` will be used to display the log entry
to the user.
Periodic tasks
--------------
The ``pretix.base.signals.periodic_task`` is a regular django signal (no pretix event
signal) that we send out every time the periodic task cronjob runs. This interval
is not sharply defined, it can be everything between a minute and a day. The actions
you perform should be idempotent, i.e. it should not make a difference if this is send
out more often than expected.

View File

@@ -38,13 +38,8 @@ Items and variations
The purpose of pretix is to sell **items** (which belong to **events**) to **users**. The purpose of pretix is to sell **items** (which belong to **events**) to **users**.
An **item** is a abstract thing, popular examples being event tickets or a piece of An **item** is a abstract thing, popular examples being event tickets or a piece of
merchandise, like 'T-shirt'. An **item** can have multiple **properties** with multiple merchandise, like 'T-shirt'. An **item** can have multiple **variations**. For example,
**values** each. For example, the **item** 'T-Shirt' could have the **property** 'Size' the **item** 'T-Shirt' could have the **variations** S', 'M' and 'L'.
with **values** 'S', 'M' and 'L' and the **property** 'Color' with **values** 'black'
and 'blue'.
Any combination of those **values** is called a **variation**. Using the examples from
above, a possible **variation** would be 'T-Shirt, S, blue'.
Questions Questions
^^^^^^^^^ ^^^^^^^^^
@@ -61,22 +56,22 @@ special care in the implementation to never sell more tickets than allowed, even
* There is a concept of **quotas**. A quota is basically a number of items combined with information * There is a concept of **quotas**. A quota is basically a number of items combined with information
about how many of them are still available. about how many of them are still available.
* Every time a user places a item in the cart, a **cart lock** is created, reducing the number of * Every time a user places a item in the cart, a **cart position** is created, reducing the number of
available items in the pool by one. The lock is valid for a fixed time (e.g. 30 minutes), but not available items in the pool by one. The position is valid for a fixed time (e.g. 30 minutes), but not
instantly deleted after those 30 minutes (we'll get to that). instantly deleted after those 30 minutes (we'll get to that).
* Every time a user places a binding order, the lock object is replaced by an **order** which behaves * Every time a user places a binding order, the position object is replaced by an **order position** which behaves
much the same as the lock. It reduces the number of available item and is valid for a fixed time, this much the same as the cart position. It reduces the number of available item and is valid for a fixed time, this
time for the configured payment term (e.g. 14 days). time for the configured payment term (e.g. 14 days).
* If the order is being paid, the **order** becomes permanent. * If the order is being paid, the **order** becomes permanent.
* Once there are no available tickets left and user A wants to buy a ticket, he can do so, as long as * Once there are no available tickets left and user A wants to buy a ticket, he can do so, as long as
there are *expired* cart locks in the system. In this case, user A gets a new cart lock, so that there there are *expired* cart position in the system. In this case, user A gets a new cart position, so that there
are more cart locks than available tickets and therefore have to remove one of the expired cart locks. are more cart position than available tickets and therefore have to remove one of the expired cart positions.
However, we do not choose one by random, but keep the surplus in a way that leads to the deletion However, we do not choose one by random, but keep the surplus in a way that leads to the deletion
of the cart lock of the user who tries *last* to use his lock. of the cart position f the user who tries *last* to use his cart position.
* The same goes for orders which are not paid within the specified timeframe. This policy allows the organizer to * The same goes for orders which are not paid within the specified timeframe. This policy allows the organizer to
sell as much items as possible. Moreover, it guarantees the users to get their items if they check out within the validity sell as much items as possible. Moreover, it guarantees the users to get their items if they check out within the validity
period of their locks and pay within the validity period of their orders. It does not guarantee them anything period of their positions and pay within the validity period of their orders. It does not guarantee them anything
any longer, but it tries to be *as tolerant as possible* to users who are paying after their payment any longer, but it tries to be *as tolerant as possible* to users who are paying after their payment
period or click checkout after the expiry of their lock. period or click checkout after the expiry of their position.
* The same quota can apply to multiple items and one item can be affected by multiple quotas. * The same quota can apply to multiple items and one item can be affected by multiple quotas.

View File

@@ -6,7 +6,6 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
goals
concepts concepts
setup setup
style style

View File

@@ -55,7 +55,7 @@ Carts and Orders
:members: :members:
.. autoclass:: pretix.base.models.AbstractPosition .. autoclass:: pretix.base.models.AbstractPosition
:members: :members:
.. autoclass:: pretix.base.models.OrderPosition .. autoclass:: pretix.base.models.OrderPosition
:members: :members:
@@ -72,6 +72,15 @@ Logging
.. autoclass:: pretix.base.models.LogEntry .. autoclass:: pretix.base.models.LogEntry
:members: :members:
Invoicing
---------
.. autoclass:: pretix.base.models.Invoice
:members:
.. autoclass:: pretix.base.models.InvoiceLine
:members:
Vouchers Vouchers
-------- --------

View File

@@ -17,6 +17,22 @@ def invoice_filename(instance, filename: str) -> str:
class Invoice(models.Model): class Invoice(models.Model):
"""
Represents an invoice that is issued because of an order. Because invoices are legally required
not to change, this object duplicates a log of data (e.g. the invoice address).
:param order: The associated order
:param event: The event this belongs to (for convenience)
:param invoice_no: The human-readable, event-unique invoice number
:param is_cancellation: Whether or not this is a cancellation instead of an invoice
:param refers: A link to another invoice this invoice referse to, e.g. the cancelled invoice in an cancellation
:param invoice_from: The sender address
:param invoice_to: The receiver address
:param date: The invoice date
:param locale: The locale in which the invoice should be printed
:param additional_text: Additional text for the invoice
:param file: The filename of the rendered invoice
"""
order = models.ForeignKey('Order', related_name='invoices', db_index=True) order = models.ForeignKey('Order', related_name='invoices', db_index=True)
event = models.ForeignKey('Event', related_name='invoices', db_index=True) event = models.ForeignKey('Event', related_name='invoices', db_index=True)
invoice_no = models.PositiveIntegerField(db_index=True) invoice_no = models.PositiveIntegerField(db_index=True)
@@ -48,6 +64,9 @@ class Invoice(models.Model):
@property @property
def number(self): def number(self):
"""
Returns the invoice number in a human-readable way with the event slug prepended.
"""
return '%s-%05d' % (self.event.slug.upper(), self.invoice_no) return '%s-%05d' % (self.event.slug.upper(), self.invoice_no)
class Meta: class Meta:
@@ -55,6 +74,15 @@ class Invoice(models.Model):
class InvoiceLine(models.Model): class InvoiceLine(models.Model):
"""
One position listed on an invoice.
:param invoice: The invoice this belongs to
:param description: The item description
:param gross_value: The gross value
:param tax_value: The included tax (as an absolute value)
:param tax_rate: The applied tax rate in percent
"""
invoice = models.ForeignKey('Invoice', related_name='lines') invoice = models.ForeignKey('Invoice', related_name='lines')
description = models.TextField() description = models.TextField()
gross_value = models.DecimalField(max_digits=10, decimal_places=2) gross_value = models.DecimalField(max_digits=10, decimal_places=2)

View File

@@ -9,6 +9,7 @@ class LogEntry(models.Model):
in the database. This uses django.contrib.contenttypes to allow a in the database. This uses django.contrib.contenttypes to allow a
relation to an arbitrary database object. relation to an arbitrary database object.
:param datatime: The timestamp of the logged action
:param user: The user that performed the action :param user: The user that performed the action
:type user: User :type user: User
:param action_type: The type of action that has been performed. This is :param action_type: The type of action that has been performed. This is

View File

@@ -52,6 +52,8 @@ class Order(LoggedModel):
:type email: str :type email: str
:param locale: The locale of this order :param locale: The locale of this order
:type locale: str :type locale: str
:param secret: A secret string that is required to modify the order
:type secret: str
:param datetime: The datetime of the order placement :param datetime: The datetime of the order placement
:type datetime: datetime :type datetime: datetime
:param expires: The date until this order has to be paid to guarantee the :param expires: The date until this order has to be paid to guarantee the
@@ -62,6 +64,10 @@ class Order(LoggedModel):
:type payment_provider: str :type payment_provider: str
:param payment_fee: The payment fee calculated at checkout time :param payment_fee: The payment fee calculated at checkout time
:type payment_fee: decimal.Decimal :type payment_fee: decimal.Decimal
:param payment_fee_tax_value: The absolute amound of tax included in the payment fee
:type payment_fee_tax_value: decimal.Decimal
:param payment_fee_tax_rate: The tax rate applied to the payment fee (in percent)
:type payment_fee_tax_rate: decimal.Decimal
:param payment_info: Arbitrary information stored by the payment provider :param payment_info: Arbitrary information stored by the payment provider
:type payment_info: str :type payment_info: str
:param total: The total amount of the order, including the payment fee :param total: The total amount of the order, including the payment fee
@@ -170,6 +176,10 @@ class Order(LoggedModel):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def _calculate_tax(self): def _calculate_tax(self):
"""
Calculates the taxes on the payment fees and sets the parameters payment_fee_tax_rate
and payment_fee_tax_value accordingly.
"""
self.payment_fee_tax_rate = self.event.settings.get('tax_rate_default') self.payment_fee_tax_rate = self.event.settings.get('tax_rate_default')
if self.payment_fee_tax_rate: if self.payment_fee_tax_rate:
self.payment_fee_tax_value = round_decimal( self.payment_fee_tax_value = round_decimal(

View File

@@ -20,6 +20,25 @@ def generate_code():
class Voucher(LoggedModel): class Voucher(LoggedModel):
"""
Represents a voucher. A voucher can reserve ticket quota or allow special prices.
:param event: The event this voucher is valid for
:param code: The secret voucher code
:param redeemed: Whether or not this voucher has already been redeemed
:param valid_until: The expiration date of this voucher (optional)
:param block_quota: If set to true, this voucher will reserve quota for its holder
:param allow_ignore_quota: If set to true, this voucher can be redeemed even if the event is sold out
:param price: If set, the voucher will allow the sale of associated items for this price
:param item: If set, the item to sell
:param variation: If set, the variation to sell
:param quota: If set, the quota to choose an item from
Various constraints apply:
* You can either select a quota or an item and you need to select one of those
* If you select an item that as variations but not select a variation, you cannot set block_quota
"""
event = models.ForeignKey( event = models.ForeignKey(
Event, Event,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -116,11 +135,17 @@ class Voucher(LoggedModel):
self.event.get_cache().delete('vouchers_exist') self.event.get_cache().delete('vouchers_exist')
def is_ordered(self) -> int: def is_ordered(self) -> int:
"""
Returns whether an order position exists that uses this voucher.
"""
return OrderPosition.objects.filter( return OrderPosition.objects.filter(
voucher=self voucher=self
).exists() ).exists()
def is_in_cart(self) -> int: def is_in_cart(self) -> int:
"""
Returns whether a cart position exists that uses this voucher.
"""
return CartPosition.objects.filter( return CartPosition.objects.filter(
voucher=self voucher=self
).count() ).count()