Add meta_data for items (#1576)

* PoC for ItemMetaProperties/Values

* Missing is_valid

* ItemMetaProperties/Values in editable via API, cloneable

* Tests

* Add Docs

* Fix import order

* Fix another import sorting...

* Typeahead for ItemMetaValues

* Test for editing event-objects

* Fix typeahead permission checks

* Further access restriction

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
This commit is contained in:
Martin Gross
2020-02-26 15:06:25 +01:00
committed by GitHub
parent dd1e5fa929
commit 76aaf61e19
22 changed files with 573 additions and 25 deletions

View File

@@ -10,9 +10,9 @@ from .event import (
from .giftcards import GiftCard, GiftCardAcceptance, GiftCardTransaction
from .invoices import Invoice, InvoiceLine, invoice_filename
from .items import (
Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation, Question,
QuestionOption, Quota, SubEventItem, SubEventItemVariation,
itempicture_upload_to,
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaProperty, ItemMetaValue,
ItemVariation, Question, QuestionOption, Quota, SubEventItem,
SubEventItemVariation, itempicture_upload_to,
)
from .log import LogEntry
from .notifications import NotificationSetting

View File

@@ -515,7 +515,7 @@ class Event(EventMixin, LoggedModel):
), tz)
def copy_data_from(self, other):
from . import ItemAddOn, ItemCategory, Item, Question, Quota
from . import ItemAddOn, ItemCategory, Item, Question, Quota, ItemMetaValue
from ..signals import event_copy_data
self.plugins = other.plugins
@@ -540,6 +540,14 @@ class Event(EventMixin, LoggedModel):
c.save()
c.log_action('pretix.object.cloned')
item_meta_properties_map = {}
for imp in other.item_meta_properties.all():
item_meta_properties_map[imp.pk] = imp
imp.pk = None
imp.event = self
imp.save()
imp.log_action('pretix.object.cloned')
item_map = {}
variation_map = {}
for i in Item.objects.filter(event=other).prefetch_related('variations'):
@@ -561,6 +569,12 @@ class Event(EventMixin, LoggedModel):
v.item = i
v.save()
for imv in ItemMetaValue.objects.filter(item__event=other).prefetch_related('item', 'property'):
imv.pk = None
imv.property = item_meta_properties_map[imv.property.pk]
imv.item = item_map[imv.item.pk]
imv.save()
for ia in ItemAddOn.objects.filter(base_item__event=other).prefetch_related('base_item', 'addon_category'):
ia.pk = None
ia.base_item = item_map[ia.base_item.pk]

View File

@@ -1,6 +1,6 @@
import sys
import uuid
from collections import Counter
from collections import Counter, OrderedDict
from datetime import date, datetime, time
from decimal import Decimal, DecimalException
from typing import Tuple
@@ -9,6 +9,7 @@ import dateutil.parser
import pytz
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import F, Func, Q, Sum
from django.utils import formats
@@ -591,6 +592,16 @@ class Item(LoggedModel):
if from_date > until_date:
raise ValidationError(_('The item\'s availability cannot end before it starts.'))
@property
def meta_data(self):
data = {p.name: p.default for p in self.event.item_meta_properties.all()}
if hasattr(self, 'meta_values_cached'):
data.update({v.property.name: v.value for v in self.meta_values_cached})
else:
data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()})
return OrderedDict((k, v) for k, v in sorted(data.items(), key=lambda k: k[0]))
class ItemVariation(models.Model):
"""
@@ -1541,3 +1552,57 @@ class Quota(LoggedModel):
else:
if subevent:
raise ValidationError(_('The subevent does not belong to this event.'))
class ItemMetaProperty(LoggedModel):
"""
An event can have ItemMetaProperty objects attached to define meta information fields
for its items. This information can be re-used for example in ticket layouts.
:param event: The event this property is defined for.
:type event: Event
:param name: Name
:type name: Name of the property, used in various places
:param default: Default value
:type default: str
"""
event = models.ForeignKey(Event, related_name="item_meta_properties", on_delete=models.CASCADE)
name = models.CharField(
max_length=50, db_index=True,
help_text=_(
"Can not contain spaces or special characters except underscores"
),
validators=[
RegexValidator(
regex="^[a-zA-Z0-9_]+$",
message=_("The property name may only contain letters, numbers and underscores."),
),
],
verbose_name=_("Name"),
)
default = models.TextField(blank=True)
class ItemMetaValue(LoggedModel):
"""
A meta-data value assigned to an item.
:param item: The item this metadata is valid for
:type item: Item
:param property: The property this value belongs to
:type property: ItemMetaProperty
:param value: The actual value
:type value: str
"""
item = models.ForeignKey('Item', on_delete=models.CASCADE, related_name='meta_values')
property = models.ForeignKey('ItemMetaProperty', on_delete=models.CASCADE, related_name='item_values')
value = models.TextField()
class Meta:
unique_together = ('item', 'property')
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)