diff --git a/doc/development/api/restriction.rst b/doc/development/api/restriction.rst index ffb9f2d0ac..ec80e027de 100644 --- a/doc/development/api/restriction.rst +++ b/doc/development/api/restriction.rst @@ -68,6 +68,8 @@ It is sent out with several arguments: the item does not have any properties, the list will contain exactly one empty dictionary. Please not: this is *not* the list of all possible variations, this is only the list of all variations the frontend likes to determine the status for. + Technically, you won't get ``dict`` objects but ``tixlbase.types.VariationDict`` + objects, which behave exactly the same but add some extra methods. context A yet-to-defined context object containing information about the user and the order process. This is required to implement coupon-systems or similar restrictions. @@ -158,9 +160,7 @@ In our example, the implementation could look like this:: # Make up some unique key for this variation cachekey = 'timerestriction:%d:%s' % ( item.pk, - ",".join(sorted( - [str(v[1].pk) for v in v.items() if v[0] != 'variation'] - )) + v.identify(), ) # Fetch from cache, if available diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index 411c4cb25e..a2eb14a11c 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -7,6 +7,8 @@ from django.utils.translation import ugettext_lazy as _ from django.template.defaultfilters import date as _date from django.core.validators import RegexValidator +from tixlbase.types import VariationDict + class UserManager(BaseUserManager): """ @@ -571,10 +573,13 @@ class Item(models.Model): def get_all_variations(self): """ This method returns a list containing all variations of this - item. The list contains one dictionary per variation, where + item. The list contains one VariationDict per variation, where the Proprty IDs are keys and the PropertyValue objects are values. If an ItemVariation object exists, it is available in the dictionary via the special key 'variation'. + + VariationDicts differ from dicts only by specifying some extra + methods. """ all_variations = self.variations.all().prefetch_related("values") all_properties = self.properties.all().prefetch_related("values") @@ -583,20 +588,20 @@ class Item(models.Model): key = [] for v in var.values.all(): key.append((v.prop_id, v.pk)) - key = hash(tuple(sorted(key))) + key = tuple(sorted(key)) variations_cache[key] = var result = [] for comb in product(*[prop.values.all() for prop in all_properties]): if len(comb) == 0: - result.append({}) + result.append(VariationDict()) continue key = [] - var = {} + var = VariationDict() for v in comb: key.append((v.prop.pk, v.pk)) var[v.prop.pk] = v - key = hash(tuple(sorted(key))) + key = tuple(sorted(key)) if key in variations_cache: var['variation'] = variations_cache[key] result.append(var) diff --git a/src/tixlbase/tests/test_models.py b/src/tixlbase/tests/test_models.py index 9929a98913..1217476bba 100644 --- a/src/tixlbase/tests/test_models.py +++ b/src/tixlbase/tests/test_models.py @@ -5,6 +5,7 @@ from tixlbase.models import ( Event, Organizer, Item, ItemVariation, Property, PropertyValue ) +from tixlbase.types import VariationDict class ItemVariationsTest(TestCase): @@ -44,7 +45,7 @@ class ItemVariationsTest(TestCase): self.assertEqual(len(v), 3) values = [] for var in v: - self.assertIs(type(var), dict) + self.assertIs(type(var), VariationDict) self.assertIn(p.pk, var) self.assertIs(type(var[p.pk]), PropertyValue) values.append(var[p.pk].value) @@ -59,7 +60,7 @@ class ItemVariationsTest(TestCase): values = [] num_variations = 0 for var in v: - self.assertIs(type(var), dict) + self.assertIs(type(var), VariationDict) if 'variation' in var and type(var['variation']) is ItemVariation: self.assertEqual(iv.pk, var['variation'].pk) values.append(var['variation'].values.all()[0].value) @@ -80,7 +81,7 @@ class ItemVariationsTest(TestCase): values = [] num_variations = 0 for var in v: - self.assertIs(type(var), dict) + self.assertIs(type(var), VariationDict) if 'variation' in var: self.assertEqual(iv.pk, var['variation'].pk) values.append(sorted([ivv.value for ivv in iv.values.all()])) diff --git a/src/tixlbase/types.py b/src/tixlbase/types.py new file mode 100644 index 0000000000..4f222f5b55 --- /dev/null +++ b/src/tixlbase/types.py @@ -0,0 +1,71 @@ +class VariationDict(dict): + """ + A VariationDict object behaves exactle the same as the Python built-in + ``dict`` does, but adds some special methods. It is used for the dicts + returned by ``Item.get_all_variations()`` to avoid duplicate code in the + code calling this method. + """ + + def relevant_items(self): + """ + Iterate over all items with numeric keys. + + This is in use because the variation dictionaries use property ids + as key and have some special keys like 'variation'. + """ + for i in self.items(): + if type(i[0]) is int: + yield i + + def relevant_values(self): + """ + Iterate over all values with numeric keys. + + This is in use because the variation dictionaries use property ids + as key and have some special keys like 'variation'. + """ + for i in self.items(): + if type(i[0]) is int: + yield i[1] + + def identify(self): + """ + Build an identifier for this dict. This can be any string used to + compare one VariationDict to others. + + In the current implementation, it is a string containing a list of + the PropertyValue id's, sorted by the Property id's and is therefore + unique among one item. + """ + order_key = lambda i: i[0] + return ",".join([ + str(v[1].pk) for v in sorted(self.relevant_items(), key=order_key) + ]) + + def __eq__(self, other): + if type(other) is type(self): + return self.identify() == other.identify() + else: + return super().__eq__(other) + + def ordered_values(self): + """ + Returns a list of values ordered by their keys + """ + return [ + i[1] for i + in sorted( + [it for it in self.relevant_items()], + key=lambda i: i[0] + ) + ] + + def copy(self): + """ + Return a one-level deep copy of this object (create a new + VariationDict but make a shallow copy of the dict inside it). + """ + new = VariationDict() + for k, v in self.items(): + new[k] = v + return new diff --git a/src/tixlcontrol/views/item.py b/src/tixlcontrol/views/item.py index 69ed2f150f..431a511c7a 100644 --- a/src/tixlcontrol/views/item.py +++ b/src/tixlcontrol/views/item.py @@ -561,16 +561,6 @@ class ItemVariations(TemplateView, SingleObjectMixin): if v.prop.pk != prop2.pk ] - # Given an dictionary like the ones returned by - # Item.get_all_variation() this will return a list of PropertyValue - # objects sorted by the primary keys of the properties they belong - # to. - values = lambda variation: [ - i[1] for i in sorted( - [it for it in variation.items() if it[0] != 'variation'] - ) - ] - # Given a list of variations, this will sort them by their position # on the x-axis sort = lambda v: v[prop2.pk].pk @@ -592,7 +582,7 @@ class ItemVariations(TemplateView, SingleObjectMixin): selection = selector(gridrow + (val1,)) # We now iterate over all variations who generate the same # selector as 'selection'. - filtered = [v for v in variations if selector(values(v)) == selection] + filtered = [v for v in variations if selector(v.relevant_values()) == selection] for variation in sorted(filtered, key=sort): form = self.get_form(variation, data) formrow.append(form) diff --git a/src/tixlplugins/timerestriction/signals.py b/src/tixlplugins/timerestriction/signals.py index 4ef032d9e8..2a77f087bd 100644 --- a/src/tixlplugins/timerestriction/signals.py +++ b/src/tixlplugins/timerestriction/signals.py @@ -59,9 +59,7 @@ def availability_handler(sender, **kwargs): # Make up some unique key for this variation cachekey = 'timerestriction:%d:%s' % ( item.pk, - ",".join(sorted( - [str(v[1].pk) for v in v.items() if v[0] != 'variation'] - )) + v.identify(), ) # Fetch from cache, if available