diff --git a/doc/development/concepts.rst b/doc/development/concepts.rst index 0b8166573..c321d3461 100644 --- a/doc/development/concepts.rst +++ b/doc/development/concepts.rst @@ -19,26 +19,26 @@ Tixl is used by **users**, of which it knows two types: For more information about this user concept and reasons behind it, see the docstring of the ``tixlbase.models.User`` class. -Items and flavors -^^^^^^^^^^^^^^^^^ +Items and variations +^^^^^^^^^^^^^^^^^^^^ The purpose of tixl 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 **flavor**. Using the examples from above, a possible **flavor** would be 'T-Shirt S blue'. +Any combination of those **values** is called a **variation**. Using the examples from above, a possible **variation** would be 'T-Shirt S blue'. Restrictions ^^^^^^^^^^^^ -The probably most powerful concepts of tixl is the very abstract concept of **restricitons**. We already know that **items** can come in very different **flavors**, but a **restriction** decides whether an item is available for sale and assign **prices** to **flavors**. There are **restriction types**, which are pieces of code implementing the restrictions and **restriction instances**, which are configurations made by the **organzier**. Although **restrictions** are a very abstract concept which can be used to do nearly anything, there are a few obvious examples: +The probably most powerful concepts of tixl is the very abstract concept of **restricitons**. We already know that **items** can come in very different **variations**, but a **restriction** decides whether an item is available for sale and assign **prices** to **variations**. There are **restriction types**, which are pieces of code implementing the restrictions and **restriction instances**, which are configurations made by the **organzier**. Although **restrictions** are a very abstract concept which can be used to do nearly anything, there are a few obvious examples: -* One easy example is the time restriction, which allows the sale of certain item flavors only within a certain time frame. As restrictions can also assign a price to a flavor, this can also be used to implement something like 'early-bird prices' for your tickets by using multiple time restrictions with different prices. +* One easy example is the time restriction, which allows the sale of certain item variations only within a certain time frame. As restrictions can also assign a price to a variation, this can also be used to implement something like 'early-bird prices' for your tickets by using multiple time restrictions with different prices. * The most obvious example is the number restriction, which limits the sale of the tickets to a maximum number. You can use this either to stop selling tickets completely when your house is full or for creating limited 'VIP tickets'. * A more advanced example is a restriction by user, for example reduced ticket prices for members who are members of a special group. * Arbitrary sophisticated features like coupon codes are also possible to be implemented using this feature. -Any number of **restrictions** can be applied to the whole of a **item** or to a specific **flavor**. The processing of the restriction follows the following set of rules: +Any number of **restrictions** can be applied to the whole of a **item** or to a specific **variation**. The processing of the restriction follows the following set of rules: -* **Flavor**-specific rules have precedence over **item**-specific rules. +* **Variation**-specific rules have precedence over **item**-specific rules. * The restrictions are being processed in random order (there may not be any assumptions about the evaluation order). * Multiple restriction instances of **different restriction types** are linked with *and*, so if both a time frame and a number restriction are applied to an item, the item is only avaliable for sale within the given time frame *and* only as long as items are available. * Multiple restriction instances of the **same restriction type** are linked with *or*, so if two time frames are applied to an item, the item is available for sale in both of the time frames. (This behaviour is actually a decision of the restriction type itself, so this rule is not enforced but rather a general rule of thumb). diff --git a/src/tixlbase/admin.py b/src/tixlbase/admin.py index cb84edd13..9ca6fe7e9 100644 --- a/src/tixlbase/admin.py +++ b/src/tixlbase/admin.py @@ -5,7 +5,7 @@ from django import forms from tixlbase.models import ( User, Organizer, OrganizerPermission, Event, EventPermission, - Property, PropertyValue, Item, ItemFlavor + Property, PropertyValue, Item, ItemVariation ) @@ -105,16 +105,16 @@ class PropertyAdmin(admin.ModelAdmin): search_fields = ('name', 'event') -class ItemFlavorInline(admin.TabularInline): +class ItemVariationInline(admin.TabularInline): - model = ItemFlavor + model = ItemVariation extra = 4 class ItemAdmin(admin.ModelAdmin): model = Item - inlines = [ItemFlavorInline] + inlines = [ItemVariationInline] list_display = ('name', 'event', 'category') search_fields = ('name', 'event', 'category', 'short_description') diff --git a/src/tixlbase/migrations/0010_auto_20140927_1006.py b/src/tixlbase/migrations/0010_auto_20140927_1006.py new file mode 100644 index 000000000..e119fbdc3 --- /dev/null +++ b/src/tixlbase/migrations/0010_auto_20140927_1006.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0009_auto_20140916_2120'), + ] + + operations = [ + migrations.RemoveField( + model_name='itemflavor', + name='prop', + ), + migrations.AddField( + model_name='eventpermission', + name='can_change_items', + field=models.BooleanField(verbose_name='Can change item settings', default=True), + preserve_default=True, + ), + migrations.AddField( + model_name='itemflavor', + name='values', + field=models.ManyToManyField(to='tixlbase.PropertyValue', related_name='flavors'), + preserve_default=True, + ), + ] diff --git a/src/tixlbase/migrations/0011_auto_20140927_1013.py b/src/tixlbase/migrations/0011_auto_20140927_1013.py new file mode 100644 index 000000000..96b2a0d5b --- /dev/null +++ b/src/tixlbase/migrations/0011_auto_20140927_1013.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0010_auto_20140927_1006'), + ] + + operations = [ + migrations.CreateModel( + name='ItemVariation', + fields=[ + ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), + ('active', models.BooleanField(default=True)), + ('default_price', models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Default price')), + ('item', models.ForeignKey(related_name='variations', to='tixlbase.Item')), + ('values', models.ManyToManyField(related_name='variations', to='tixlbase.PropertyValue')), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.RemoveField( + model_name='itemflavor', + name='item', + ), + migrations.RemoveField( + model_name='itemflavor', + name='values', + ), + migrations.DeleteModel( + name='ItemFlavor', + ), + migrations.AlterField( + model_name='item', + name='category', + field=models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='items', to='tixlbase.ItemCategory'), + ), + migrations.AlterField( + model_name='item', + name='event', + field=models.ForeignKey(to='tixlbase.Event', on_delete=django.db.models.deletion.PROTECT, related_name='items'), + ), + ] diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index 8ee903d59..1b2d614fc 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -309,6 +309,10 @@ class EventPermission(models.Model): default=True, verbose_name=_("Can change event settings") ) + can_change_items = models.BooleanField( + default=True, + verbose_name=_("Can change item settings") + ) def __str__(self): return _("%(name)s on %(object)s") % { @@ -400,11 +404,13 @@ class Item(models.Model): """ event = models.ForeignKey( Event, - on_delete=models.PROTECT + on_delete=models.PROTECT, + related_name="items", ) category = models.ForeignKey( ItemCategory, on_delete=models.PROTECT, + related_name="items", blank=True, null=True ) name = models.CharField( @@ -450,32 +456,32 @@ class Item(models.Model): verbose_name_plural = _("Items") -class ItemFlavor(models.Model): +class ItemVariation(models.Model): """ - A flavor is an item combined with values for all properties + 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 - flavor would be 'T-Shirt XL read'. + variation would be 'T-Shirt XL read'. Attention: _ALL_ combinations of PropertyValues _ALWAYS_ exist, - even if there is no ItemFlavor object for them! ItemFlavor objects + 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. They also allow to explicitly EXCLUDE certain combinations of property - values by creating an ItemFlavor object for them with active set to + 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 flavors. + Restrictions can be not only set to items but also directly to variation. """ item = models.ForeignKey( Item, - related_name='flavors' + related_name='variations' ) values = models.ManyToManyField( PropertyValue, - related_name='flavors', + related_name='variations', ) active = models.BooleanField( default=True,