Skip to content

Commit

Permalink
Added vouchers
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelm committed Nov 22, 2015
1 parent 34bdf71 commit 9259956
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 73 deletions.
9 changes: 9 additions & 0 deletions doc/development/models.rst
Expand Up @@ -81,6 +81,9 @@ Carts and Orders
.. autoclass:: pretix.base.models.Order
:members:

.. autoclass:: pretix.base.models.AbstractPosition
:members:

.. autoclass:: pretix.base.models.OrderPosition
:members:

Expand All @@ -90,4 +93,10 @@ Carts and Orders
.. autoclass:: pretix.base.models.QuestionAnswer
:members:

Vouchers
--------

.. autoclass:: pretix.base.models.Voucher
:members:

.. _cleanerversion: https://github.com/swisscom/cleanerversion
2 changes: 0 additions & 2 deletions src/pretix/base/migrations/0001_initial.py
Expand Up @@ -80,7 +80,6 @@ class Migration(migrations.Migration):
'verbose_name': 'Cart position',
'verbose_name_plural': 'Cart positions',
},
bases=(pretix.base.models.ObjectWithAnswers, models.Model),
),
migrations.CreateModel(
name='Event',
Expand Down Expand Up @@ -255,7 +254,6 @@ class Migration(migrations.Migration):
'verbose_name': 'Order position',
'verbose_name_plural': 'Order positions',
},
bases=(pretix.base.models.ObjectWithAnswers, models.Model),
),
migrations.CreateModel(
name='Organizer',
Expand Down
78 changes: 78 additions & 0 deletions src/pretix/base/migrations/0005_auto_20151024_0959.py
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from decimal import Decimal

import versions.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('pretixbase', '0004_auto_20151024_0848'),
]

operations = [
migrations.CreateModel(
name='Voucher',
fields=[
('id', models.CharField(serialize=False, primary_key=True, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(null=True, blank=True, default=None)),
('version_birth_date', models.DateTimeField()),
('code', models.CharField(verbose_name='Voucher code', max_length=255)),
('max_usages', models.PositiveIntegerField(null=True, blank=True, verbose_name='How often can this voucher code be used?', help_text='One product sold counts as one usage. Leave empty for unlimited usages.')),
('valid_until', models.DateTimeField(null=True, blank=True, verbose_name='Valid until')),
('block_quota', models.BooleanField(default=False, verbose_name='Reserve tickets from quota', help_text="If activated, the maximum number of usages of this voucher will be substracted from the affected product's quotas, such that it is guaranteed that anyone with this voucher code does receive a ticket.")),
('benefit', models.CharField(choices=[('product_fix_off', 'Fixed discount'), ('product_percent_off', 'Percent discount'), ('product_fix', 'Fixed price for product')], verbose_name='Type of voucher', max_length=255)),
('value', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10)),
('event', versions.models.VersionedForeignKey(related_name='vouchers', verbose_name='Event', to='pretixbase.Event')),
('items', versions.models.VersionedManyToManyField(related_name='vouchers', verbose_name='Applicable to products', to='pretixbase.Item')),
],
options={
'verbose_name': 'Voucher',
'verbose_name_plural': 'Vouchers',
},
),
migrations.AddField(
model_name='cartposition',
name='base_price',
field=models.DecimalField(null=True, blank=True, decimal_places=2, max_digits=10),
),
migrations.AddField(
model_name='cartposition',
name='voucher_discount',
field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10),
),
migrations.AddField(
model_name='orderposition',
name='base_price',
field=models.DecimalField(null=True, blank=True, decimal_places=2, max_digits=10),
),
migrations.AddField(
model_name='orderposition',
name='voucher_discount',
field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10),
),
migrations.AlterField(
model_name='orderposition',
name='item',
field=versions.models.VersionedForeignKey(to='pretixbase.Item', verbose_name='Item'),
),
migrations.AddField(
model_name='cartposition',
name='voucher',
field=models.ForeignKey(null=True, blank=True, to='pretixbase.Voucher'),
),
migrations.AddField(
model_name='orderposition',
name='voucher',
field=models.ForeignKey(null=True, blank=True, to='pretixbase.Voucher'),
),
migrations.AlterUniqueTogether(
name='voucher',
unique_together=set([('event', 'code')]),
),
]
7 changes: 4 additions & 3 deletions src/pretix/base/models/__init__.py
Expand Up @@ -6,15 +6,16 @@
PropertyValue, Question, Quota, VariationsField, itempicture_upload_to,
)
from .orders import (
CachedTicket, CartPosition, ObjectWithAnswers, Order, OrderPosition,
AbstractPosition, CachedTicket, CartPosition, Order, OrderPosition,
QuestionAnswer, generate_secret,
)
from .organizer import Organizer, OrganizerPermission, OrganizerSetting
from .vouchers import Voucher

__all__ = [
'Versionable', 'User', 'CachedFile', 'Organizer', 'OrganizerPermission', 'Event', 'EventPermission',
'ItemCategory', 'Item', 'Property', 'PropertyValue', 'ItemVariation', 'VariationsField', 'Question',
'BaseRestriction', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'ObjectWithAnswers', 'OrderPosition',
'BaseRestriction', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'AbstractPosition', 'OrderPosition',
'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock', 'cachedfile_name', 'itempicture_upload_to',
'generate_secret'
'generate_secret', 'Voucher'
]
139 changes: 71 additions & 68 deletions src/pretix/base/models/orders.py
@@ -1,6 +1,7 @@
import random
import string
from datetime import datetime
from decimal import Decimal

from django.db import models
from django.utils.timezone import now
Expand Down Expand Up @@ -274,7 +275,61 @@ class QuestionAnswer(Versionable):
answer = models.TextField()


class ObjectWithAnswers:
class AbstractPosition(Versionable):
"""
A position can either be one line of an order or an item placed in a cart.
:param item: The selected item
:type item: Item
:param variation: The selected ItemVariation or null, if the item has no properties
:type variation: ItemVariation
:param datetime: The datetime this item was put into the cart
:type datetime: datetime
:param expires: The date until this item is guarenteed to be reserved
:type expires: datetime
:param price: The price of this item
:type price: decimal.Decimal
:param attendee_name: The attendee's name, if entered.
:type attendee_name: str
:param voucher: A voucher that has been applied to this sale
:type voucher: Voucher
:param voucher_discount: The absolute discount granted by the applied voucher
:type voucher_discount: decimal.Decimal
:param base_price: The base price without any discounts applied
:type base_price: decimal.Decimal
"""
item = VersionedForeignKey(
Item,
verbose_name=_("Item"),
)
variation = VersionedForeignKey(
ItemVariation,
null=True, blank=True,
verbose_name=_("Variation")
)
price = models.DecimalField(
decimal_places=2, max_digits=10,
verbose_name=_("Price")
)
attendee_name = models.CharField(
max_length=255,
verbose_name=_("Attendee name"),
blank=True, null=True,
help_text=_("Empty, if this product is not an admission ticket")
)
voucher = models.ForeignKey(
'Voucher', null=True, blank=True
)
voucher_discount = models.DecimalField(
default=Decimal('0.00'), decimal_places=2, max_digits=10
)
base_price = models.DecimalField(
decimal_places=2, max_digits=10, null=True, blank=True
)

class Meta:
abstract = True

def cache_answers(self):
"""
Creates two properties on the object.
Expand All @@ -292,48 +347,28 @@ def cache_answers(self):
q.answer = ""
self.questions.append(q)

def save(self, *args, **kwargs):
if self.voucher is None and self.base_price is None:
self.base_price = self.price
if self.voucher_discount != Decimal('0.00') and self.base_price is not None:
self.price = self.base_price - self.voucher_discount
return super().save(*args, **kwargs)


class OrderPosition(ObjectWithAnswers, Versionable):
class OrderPosition(AbstractPosition):
"""
An OrderPosition is one line of an order, representing one ordered items
of a specified type (or variation).
of a specified type (or variation). This has all properties of
AbstractPosition.
:param order: The order this is a part of
:type order: Order
:param item: The ordered item
:type item: Item
:param variation: The ordered ItemVariation or null, if the item has no properties
:type variation: ItemVariation
:param price: The price of this item
:type price: decimal.Decimal
:param attendee_name: The attendee's name, if entered.
:type attendee_name: str
"""
order = VersionedForeignKey(
Order,
verbose_name=_("Order"),
related_name='positions'
)
item = VersionedForeignKey(
Item,
verbose_name=_("Item"),
related_name='positions'
)
variation = VersionedForeignKey(
ItemVariation,
null=True, blank=True,
verbose_name=_("Variation")
)
price = models.DecimalField(
decimal_places=2, max_digits=10,
verbose_name=_("Price")
)
attendee_name = models.CharField(
max_length=255,
verbose_name=_("Attendee name"),
blank=True, null=True,
help_text=_("Empty, if this product is not an admission ticket")
)

class Meta:
verbose_name = _("Order position")
Expand All @@ -343,10 +378,9 @@ class Meta:
def transform_cart_positions(cls, cp: List, order) -> list:
ops = []
for cartpos in cp:
op = OrderPosition(
order=order, item=cartpos.item, variation=cartpos.variation,
price=cartpos.price, attendee_name=cartpos.attendee_name
)
op = OrderPosition(order=order)
for f in AbstractPosition._meta.fields:
setattr(op, f.name, getattr(cartpos, f.name))
for answ in cartpos.answers.all():
answ = answ.clone()
answ.orderposition = op
Expand All @@ -357,31 +391,19 @@ def transform_cart_positions(cls, cp: List, order) -> list:
ops.append(op)


class CartPosition(ObjectWithAnswers, Versionable):
class CartPosition(AbstractPosition):
"""
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
her cart. It therefore normally has a much shorter expiration time
than an ordered position, but still blocks an item in the quota pool
as we do not want to throw out users while they're clicking through
the checkout process.
the checkout process. This has all properties of AbstractPosition.
:param event: The event this belongs to
:type event: Evnt
:param item: The selected item
:type item: Item
:param cart_id: The user session that contains this cart position
:type cart_id: str
:param variation: The selected ItemVariation or null, if the item has no properties
:type variation: ItemVariation
:param datetime: The datetime this item was put into the cart
:type datetime: datetime
:param expires: The date until this item is guarenteed to be reserved
:type expires: datetime
:param price: The price of this item
:type price: decimal.Decimal
:param attendee_name: The attendee's name, if entered.
:type attendee_name: str
"""
event = VersionedForeignKey(
Event,
Expand All @@ -391,32 +413,13 @@ class CartPosition(ObjectWithAnswers, Versionable):
max_length=255, null=True, blank=True,
verbose_name=_("Cart ID (e.g. session key)")
)
item = VersionedForeignKey(
Item,
verbose_name=_("Item")
)
variation = VersionedForeignKey(
ItemVariation,
null=True, blank=True,
verbose_name=_("Variation")
)
price = models.DecimalField(
decimal_places=2, max_digits=10,
verbose_name=_("Price")
)
datetime = models.DateTimeField(
verbose_name=_("Date"),
auto_now_add=True
)
expires = models.DateTimeField(
verbose_name=_("Expiration date")
)
attendee_name = models.CharField(
max_length=255,
verbose_name=_("Attendee name"),
blank=True, null=True,
help_text=_("Empty, if this product is not an admission ticket")
)

class Meta:
verbose_name = _("Cart position")
Expand Down

0 comments on commit 9259956

Please sign in to comment.