From eea90a90663045bf583094b90c7a85cdbf3bcb11 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 8 May 2016 12:35:04 +0200 Subject: [PATCH] Updated models and signals documentation --- doc/development/api/general.rst | 33 ++++++++++++++++++++++++++++++ doc/development/concepts.rst | 27 ++++++++++-------------- doc/development/index.rst | 1 - doc/development/models.rst | 11 +++++++++- src/pretix/base/models/invoices.py | 28 +++++++++++++++++++++++++ src/pretix/base/models/log.py | 1 + src/pretix/base/models/orders.py | 10 +++++++++ src/pretix/base/models/vouchers.py | 25 ++++++++++++++++++++++ 8 files changed, 118 insertions(+), 18 deletions(-) diff --git a/doc/development/api/general.rst b/doc/development/api/general.rst index 27f7615fec..637f0d4443 100644 --- a/doc/development/api/general.rst +++ b/doc/development/api/general.rst @@ -31,3 +31,36 @@ will have an attribute ``event``. ``pretix.control.signals.nav_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. diff --git a/doc/development/concepts.rst b/doc/development/concepts.rst index 32de84a733..dfb011c585 100644 --- a/doc/development/concepts.rst +++ b/doc/development/concepts.rst @@ -38,13 +38,8 @@ Items and variations 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 -merchandise, like 'T-shirt'. An **item** can have multiple **properties** with multiple -**values** each. For example, the **item** 'T-Shirt' could have the **property** 'Size' -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'. +merchandise, like 'T-shirt'. An **item** can have multiple **variations**. For example, +the **item** 'T-Shirt' could have the **variations** S', 'M' and 'L'. 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 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 - available items in the pool by one. The lock is valid for a fixed time (e.g. 30 minutes), but not +* 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 position is valid for a fixed time (e.g. 30 minutes), but not 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 - much the same as the lock. It reduces the number of available item and is valid for a fixed time, this +* Every time a user places a binding order, the position object is replaced by an **order position** which behaves + 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). * 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 - there are *expired* cart locks in the system. In this case, user A gets a new cart lock, so that there - are more cart locks than available tickets and therefore have to remove one of the expired cart locks. + there are *expired* cart position in the system. In this case, user A gets a new cart position, so that there + 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 - 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 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 - 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. diff --git a/doc/development/index.rst b/doc/development/index.rst index c4e3d62c17..97984d8224 100644 --- a/doc/development/index.rst +++ b/doc/development/index.rst @@ -6,7 +6,6 @@ Contents: .. toctree:: :maxdepth: 2 - goals concepts setup style diff --git a/doc/development/models.rst b/doc/development/models.rst index 255b99e231..7dcb52f38b 100644 --- a/doc/development/models.rst +++ b/doc/development/models.rst @@ -55,7 +55,7 @@ Carts and Orders :members: .. autoclass:: pretix.base.models.AbstractPosition -:members: + :members: .. autoclass:: pretix.base.models.OrderPosition :members: @@ -72,6 +72,15 @@ Logging .. autoclass:: pretix.base.models.LogEntry :members: +Invoicing +--------- + +.. autoclass:: pretix.base.models.Invoice + :members: + +.. autoclass:: pretix.base.models.InvoiceLine + :members: + Vouchers -------- diff --git a/src/pretix/base/models/invoices.py b/src/pretix/base/models/invoices.py index 9bb6ea52f8..e2f02d96c6 100644 --- a/src/pretix/base/models/invoices.py +++ b/src/pretix/base/models/invoices.py @@ -17,6 +17,22 @@ def invoice_filename(instance, filename: str) -> str: 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) event = models.ForeignKey('Event', related_name='invoices', db_index=True) invoice_no = models.PositiveIntegerField(db_index=True) @@ -48,6 +64,9 @@ class Invoice(models.Model): @property 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) class Meta: @@ -55,6 +74,15 @@ class Invoice(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') description = models.TextField() gross_value = models.DecimalField(max_digits=10, decimal_places=2) diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index 5e342cde7f..7a76d29227 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -9,6 +9,7 @@ class LogEntry(models.Model): in the database. This uses django.contrib.contenttypes to allow a relation to an arbitrary database object. + :param datatime: The timestamp of the logged action :param user: The user that performed the action :type user: User :param action_type: The type of action that has been performed. This is diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 2d9aa1bd18..2958deeb04 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -52,6 +52,8 @@ class Order(LoggedModel): :type email: str :param locale: The locale of this order :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 :type datetime: datetime :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 :param payment_fee: The payment fee calculated at checkout time :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 :type payment_info: str :param total: The total amount of the order, including the payment fee @@ -170,6 +176,10 @@ class Order(LoggedModel): super().save(*args, **kwargs) 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') if self.payment_fee_tax_rate: self.payment_fee_tax_value = round_decimal( diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 160b8e73db..4688792bb4 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -20,6 +20,25 @@ def generate_code(): 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, on_delete=models.CASCADE, @@ -116,11 +135,17 @@ class Voucher(LoggedModel): self.event.get_cache().delete('vouchers_exist') def is_ordered(self) -> int: + """ + Returns whether an order position exists that uses this voucher. + """ return OrderPosition.objects.filter( voucher=self ).exists() def is_in_cart(self) -> int: + """ + Returns whether a cart position exists that uses this voucher. + """ return CartPosition.objects.filter( voucher=self ).count()