diff --git a/doc/development/models.rst b/doc/development/models.rst index d4c1ed9f19..4105490d89 100644 --- a/doc/development/models.rst +++ b/doc/development/models.rst @@ -8,26 +8,6 @@ Pretix provides the following data(base) models. Every model and every model met documented here is considered private and should not be used by third-party plugins, as it may change without advance notice. -.. IMPORTANT:: - pretix's models are built with `cleanerversion`_, which extends the default Django ORM by adding versioning - information to the database. There are basically three things you absolutely need to know about cleanerversion: - - * When querying the database, make sure you only get the current versions:: - - queryset = Model.objects.current.filter(…) - - * Before you modify an object, clone it:: - - obj = Model.objects.current.get(identity=1) # Prefer identities over primary keys - obj = obj.clone() # Saves the old version to the database and creates the new one - obj.foo = 'bar' - obj.save() - - * Beware of batch operations, use ``queryset.update()``, ``queryset.delete()`` etc. only if - you know what you're doing. - - There is one exception: The ``User`` model is a classic Django model! - User model ---------- diff --git a/src/pretix/base/exporter.py b/src/pretix/base/exporter.py index 2be7444d34..b66a447b24 100644 --- a/src/pretix/base/exporter.py +++ b/src/pretix/base/exporter.py @@ -87,13 +87,13 @@ class JSONExporter(BaseExporter): }, 'categories': [ { - 'id': category.identity, + 'id': category.id, 'name': str(category.name) - } for category in self.event.categories.current.all() + } for category in self.event.categories.all() ], 'items': [ { - 'id': item.identity, + 'id': item.id, 'name': str(item.name), 'category': item.category_id, 'price': item.default_price, @@ -101,20 +101,20 @@ class JSONExporter(BaseExporter): 'active': item.active, 'variations': [ { - 'id': variation.identity, + 'id': variation.id, 'active': variation.active, 'price': variation.default_price if variation.default_price is not None else item.default_price, 'name': str(variation) - } for variation in item.variations.current.all() + } for variation in item.variations.all() ] - } for item in self.event.items.current.all().prefetch_related('variations') + } for item in self.event.items.all().prefetch_related('variations') ], 'questions': [ { - 'id': question.identity, + 'id': question.id, 'question': str(question.question), 'type': question.type - } for question in self.event.questions.current.all() + } for question in self.event.questions.all() ], 'orders': [ { @@ -126,7 +126,7 @@ class JSONExporter(BaseExporter): 'total': order.total, 'positions': [ { - 'id': position.identity, + 'id': position.id, 'item': position.item_id, 'variation': position.variation_id, 'price': position.price, @@ -137,19 +137,19 @@ class JSONExporter(BaseExporter): 'answer': answer.answer } for answer in position.answers.all() ] - } for position in order.positions.current.all() + } for position in order.positions.all() ] } for order in - self.event.orders.current.all().prefetch_related('positions', 'positions__answers').select_related( + self.event.orders.all().prefetch_related('positions', 'positions__answers').select_related( 'user') ], 'quotas': [ { - 'id': quota.identity, + 'id': quota.id, 'size': quota.size, 'items': [item.id for item in quota.items.all()], 'variations': [variation.id for variation in quota.variations.all()], - } for quota in self.event.quotas.current.all().prefetch_related('items', 'variations') + } for quota in self.event.quotas.all().prefetch_related('items', 'variations') ] } } diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py index e8dd49ec8d..339b3fe379 100644 --- a/src/pretix/base/forms/__init__.py +++ b/src/pretix/base/forms/__init__.py @@ -5,11 +5,9 @@ from django import forms from django.core.files import File from django.core.files.storage import default_storage from django.core.files.uploadedfile import UploadedFile -from django.db import models from django.forms.models import BaseModelForm, ModelFormMetaclass from django.utils import six from django.utils.translation import ugettext_lazy as _ -from versions.models import Versionable from pretix.base.i18n import I18nFormField from pretix.base.models import Event @@ -30,44 +28,6 @@ class BaseI18nModelForm(BaseModelForm): field.widget.enabled_langcodes = event.settings.get('locales') -class VersionedBaseModelForm(BaseI18nModelForm): - """ - This is a helperclass to construct VersionedModelForm - """ - def __init__(self, *args, **kwargs): - instance = kwargs.get('instance', None) - self.original_instance = copy.copy(instance) if instance else None - super().__init__(*args, **kwargs) - - def save(self, commit=True): - if self.instance.pk is not None and isinstance(self.instance, Versionable): - if self.has_changed() and self.original_instance: - new = self.instance - old = self.original_instance - clone = old.clone() - for f in type(self.instance)._meta.get_fields(): - if f.name not in ( - 'id', 'identity', 'version_start_date', 'version_end_date', - 'version_birth_date' - ) and not isinstance(f, ( - models.ManyToOneRel, models.ManyToManyRel, models.ManyToManyField - )): - setattr(clone, f.name, getattr(new, f.name)) - self.instance = clone - return super().save(commit) - - -class VersionedModelForm(six.with_metaclass(ModelFormMetaclass, VersionedBaseModelForm)): - """ - This is a modified version of I18nModelForm which differs from I18nModelForm in - only one way: It executes the .clone() method of an object before saving it back to - the database, if the model is a sub-class of versions.models.Versionable. You can - safely use this as a base class for all your model forms, it will work out correctly - with both versioned and non-versioned models. - """ - pass - - class I18nModelForm(six.with_metaclass(ModelFormMetaclass, BaseI18nModelForm)): """ This is a modified version of Django's ModelForm which differs from ModelForm in diff --git a/src/pretix/base/migrations/0001_initial.py b/src/pretix/base/migrations/0001_initial.py index 79df631421..91e65ce6c5 100644 --- a/src/pretix/base/migrations/0001_initial.py +++ b/src/pretix/base/migrations/0001_initial.py @@ -5,12 +5,23 @@ import uuid import django.core.validators import django.db.models.deletion -import versions.models from django.conf import settings +from django.contrib.auth.hashers import make_password from django.db import migrations, models import pretix.base.i18n -import pretix.base.models +import pretix.base.models.base +import pretix.base.models.items +import pretix.base.models.orders + + +def initial_user(apps, schema_editor): + User = apps.get_model("pretixbase", "User") + user = User(email='admin@localhost') + user.is_staff = True + user.is_superuser = True + user.password = make_password('admin') + user.save() class Migration(migrations.Migration): @@ -23,20 +34,20 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('password', models.CharField(verbose_name='password', max_length=128)), - ('last_login', models.DateTimeField(verbose_name='last login', null=True, blank=True)), - ('is_superuser', models.BooleanField(default=False, verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.')), - ('email', models.EmailField(unique=True, verbose_name='E-mail', blank=True, db_index=True, max_length=254, null=True)), - ('givenname', models.CharField(verbose_name='Given name', null=True, max_length=255, blank=True)), - ('familyname', models.CharField(verbose_name='Family name', null=True, max_length=255, blank=True)), - ('is_active', models.BooleanField(default=True, verbose_name='Is active')), - ('is_staff', models.BooleanField(default=False, verbose_name='Is site admin')), + ('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)), + ('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')), + ('email', models.EmailField(max_length=254, blank=True, unique=True, verbose_name='E-mail', null=True, db_index=True)), + ('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)), + ('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)), + ('is_active', models.BooleanField(verbose_name='Is active', default=True)), + ('is_staff', models.BooleanField(verbose_name='Is site admin', default=False)), ('date_joined', models.DateTimeField(verbose_name='Date joined', auto_now_add=True)), - ('locale', models.CharField(default='en', verbose_name='Language', choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)')], max_length=50)), - ('timezone', models.CharField(default='UTC', verbose_name='Timezone', max_length=100)), - ('groups', models.ManyToManyField(related_query_name='user', verbose_name='groups', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', blank=True, related_name='user_set', to='auth.Group')), - ('user_permissions', models.ManyToManyField(related_query_name='user', verbose_name='user permissions', help_text='Specific permissions for this user.', blank=True, related_name='user_set', to='auth.Permission')), + ('locale', models.CharField(verbose_name='Language', default='en', choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)')], max_length=50)), + ('timezone', models.CharField(verbose_name='Timezone', default='UTC', max_length=100)), + ('groups', models.ManyToManyField(to='auth.Group', blank=True, related_query_name='user', verbose_name='groups', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set')), + ('user_permissions', models.ManyToManyField(to='auth.Permission', blank=True, related_query_name='user', verbose_name='user permissions', help_text='Specific permissions for this user.', related_name='user_set')), ], options={ 'verbose_name': 'User', @@ -47,17 +58,17 @@ class Migration(migrations.Migration): name='CachedFile', fields=[ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('expires', models.DateTimeField(null=True, blank=True)), - ('date', models.DateTimeField(null=True, blank=True)), + ('expires', models.DateTimeField(blank=True, null=True)), + ('date', models.DateTimeField(blank=True, null=True)), ('filename', models.CharField(max_length=255)), ('type', models.CharField(max_length=255)), - ('file', models.FileField(upload_to=pretix.base.models.cachedfile_name, null=True, blank=True)), + ('file', models.FileField(blank=True, null=True, upload_to=pretix.base.models.base.cachedfile_name)), ], ), migrations.CreateModel( name='CachedTicket', fields=[ - ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('provider', models.CharField(max_length=255)), ('cachedfile', models.ForeignKey(to='pretixbase.CachedFile')), ], @@ -65,39 +76,32 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CartPosition', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), - ('session', models.CharField(verbose_name='Session', null=True, max_length=255, blank=True)), - ('price', models.DecimalField(decimal_places=2, verbose_name='Price', max_digits=10)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('cart_id', models.CharField(verbose_name='Cart ID (e.g. session key)', max_length=255, blank=True, null=True)), + ('price', models.DecimalField(verbose_name='Price', max_digits=10, decimal_places=2)), ('datetime', models.DateTimeField(verbose_name='Date', auto_now_add=True)), ('expires', models.DateTimeField(verbose_name='Expiration date')), - ('attendee_name', models.CharField(verbose_name='Attendee name', null=True, help_text='Empty, if this product is not an admission ticket', max_length=255, blank=True)), + ('attendee_name', models.CharField(verbose_name='Attendee name', max_length=255, blank=True, null=True, help_text='Empty, if this product is not an admission ticket')), ], options={ 'verbose_name': 'Cart position', 'verbose_name_plural': 'Cart positions', }, - bases=(pretix.base.models.ObjectWithAnswers, models.Model), + bases=(pretix.base.models.orders.ObjectWithAnswers, models.Model), ), migrations.CreateModel( name='Event', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Name', max_length=200)), - ('slug', models.SlugField(verbose_name='Slug', help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z0-9.-]+$', message='The slug may only contain letters, numbers, dots and dashes.')])), - ('currency', models.CharField(default='EUR', verbose_name='Default currency', max_length=10)), + ('slug', models.SlugField(verbose_name='Slug', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.')), + ('currency', models.CharField(verbose_name='Default currency', default='EUR', max_length=10)), ('date_from', models.DateTimeField(verbose_name='Event start time')), - ('date_to', models.DateTimeField(verbose_name='Event end time', null=True, blank=True)), - ('presale_end', models.DateTimeField(verbose_name='End of presale', help_text='No products will be sold after this date.', null=True, blank=True)), - ('presale_start', models.DateTimeField(verbose_name='Start of presale', help_text='No products will be sold before this date.', null=True, blank=True)), - ('plugins', models.TextField(verbose_name='Plugins', null=True, blank=True)), + ('date_to', models.DateTimeField(verbose_name='Event end time', blank=True, null=True)), + ('is_public', models.BooleanField(verbose_name='Visible in public lists', default=False, help_text="If selected, this event may show up on the ticket system's start page or an organization profile.")), + ('presale_end', models.DateTimeField(verbose_name='End of presale', help_text='No products will be sold after this date.', blank=True, null=True)), + ('presale_start', models.DateTimeField(verbose_name='Start of presale', help_text='No products will be sold before this date.', blank=True, null=True)), + ('plugins', models.TextField(verbose_name='Plugins', blank=True, null=True)), ], options={ 'verbose_name': 'Event', @@ -108,7 +112,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='EventLock', fields=[ - ('event', models.CharField(serialize=False, max_length=36, primary_key=True)), + ('event', models.CharField(max_length=36, primary_key=True, serialize=False)), ('date', models.DateTimeField(auto_now=True)), ('token', models.UUIDField(default=uuid.uuid4)), ], @@ -116,18 +120,14 @@ class Migration(migrations.Migration): migrations.CreateModel( name='EventPermission', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), - ('can_change_settings', models.BooleanField(default=True, verbose_name='Can change event settings')), - ('can_change_items', models.BooleanField(default=True, verbose_name='Can change product settings')), - ('can_view_orders', models.BooleanField(default=True, verbose_name='Can view orders')), - ('can_change_permissions', models.BooleanField(default=True, verbose_name='Can change permissions')), - ('can_change_orders', models.BooleanField(default=True, verbose_name='Can change orders')), - ('event', versions.models.VersionedForeignKey(to='pretixbase.Event')), - ('user', models.ForeignKey(related_name='event_perms', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('can_change_settings', models.BooleanField(verbose_name='Can change event settings', default=True)), + ('can_change_items', models.BooleanField(verbose_name='Can change product settings', default=True)), + ('can_view_orders', models.BooleanField(verbose_name='Can view orders', default=True)), + ('can_change_permissions', models.BooleanField(verbose_name='Can change permissions', default=True)), + ('can_change_orders', models.BooleanField(verbose_name='Can change orders', default=True)), + ('event', models.ForeignKey(to='pretixbase.Event', related_name='user_perms')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_perms')), ], options={ 'verbose_name': 'Event permission', @@ -137,35 +137,26 @@ class Migration(migrations.Migration): migrations.CreateModel( name='EventSetting', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('key', models.CharField(max_length=255)), ('value', models.TextField()), - ('object', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Event')), + ('object', models.ForeignKey(to='pretixbase.Event', related_name='setting_objects')), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='Item', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Item name', max_length=255)), - ('active', models.BooleanField(default=True, verbose_name='Active')), - ('description', pretix.base.i18n.I18nTextField(verbose_name='Description', help_text='This is shown below the product name in lists.', null=True, blank=True)), - ('default_price', models.DecimalField(decimal_places=2, verbose_name='Default price', max_digits=7, null=True)), - ('tax_rate', models.DecimalField(decimal_places=2, verbose_name='Taxes included in percent', max_digits=7, null=True, blank=True)), - ('admission', models.BooleanField(default=False, verbose_name='Is an admission ticket', help_text='Whether or not buying this product allows a person to enter your event')), + ('active', models.BooleanField(verbose_name='Active', default=True)), + ('description', pretix.base.i18n.I18nTextField(verbose_name='Description', help_text='This is shown below the product name in lists.', blank=True, null=True)), + ('default_price', models.DecimalField(verbose_name='Default price', max_digits=7, decimal_places=2, null=True)), + ('tax_rate', models.DecimalField(verbose_name='Taxes included in percent', max_digits=7, blank=True, null=True, decimal_places=2)), + ('admission', models.BooleanField(verbose_name='Is an admission ticket', default=False, help_text='Whether or not buying this product allows a person to enter your event')), ('position', models.IntegerField(default=0)), - ('picture', models.ImageField(upload_to=pretix.base.models.itempicture_upload_to, verbose_name='Product picture', null=True, blank=True)), + ('picture', models.ImageField(verbose_name='Product picture', blank=True, null=True, upload_to=pretix.base.models.items.itempicture_upload_to)), + ('available_from', models.DateTimeField(verbose_name='Available from', help_text='This product will not be sold before the given date.', blank=True, null=True)), + ('available_until', models.DateTimeField(verbose_name='Available until', help_text='This product will not be sold after the given date.', blank=True, null=True)), ], options={ 'verbose_name': 'Product', @@ -176,32 +167,24 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ItemCategory', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Category name', max_length=255)), ('position', models.IntegerField(default=0)), - ('event', versions.models.VersionedForeignKey(related_name='categories', to='pretixbase.Event')), + ('event', models.ForeignKey(to='pretixbase.Event', related_name='categories')), ], options={ 'verbose_name': 'Product category', 'verbose_name_plural': 'Product categories', - 'ordering': ('position', 'version_birth_date'), + 'ordering': ('position', 'id'), }, ), migrations.CreateModel( name='ItemVariation', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), - ('active', models.BooleanField(default=True, verbose_name='Active')), - ('default_price', models.DecimalField(decimal_places=2, verbose_name='Default price', max_digits=7, null=True, blank=True)), - ('item', versions.models.VersionedForeignKey(related_name='variations', to='pretixbase.Item')), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('active', models.BooleanField(verbose_name='Active', default=True)), + ('default_price', models.DecimalField(verbose_name='Default price', max_digits=7, blank=True, null=True, decimal_places=2)), + ('item', models.ForeignKey(to='pretixbase.Item', related_name='variations')), ], options={ 'verbose_name': 'Product variation', @@ -211,25 +194,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Order', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('code', models.CharField(verbose_name='Order code', max_length=16)), - ('status', models.CharField(verbose_name='Status', choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3)), - ('email', models.EmailField(verbose_name='E-mail', null=True, max_length=254, blank=True)), - ('locale', models.CharField(verbose_name='Locale', null=True, max_length=32, blank=True)), - ('secret', models.CharField(default=pretix.base.models.generate_secret, max_length=32)), + ('status', models.CharField(verbose_name='Status', max_length=3, choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')])), + ('email', models.EmailField(verbose_name='E-mail', max_length=254, blank=True, null=True)), + ('locale', models.CharField(verbose_name='Locale', max_length=32, blank=True, null=True)), + ('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)), ('datetime', models.DateTimeField(verbose_name='Date')), ('expires', models.DateTimeField(verbose_name='Expiration date')), - ('payment_date', models.DateTimeField(verbose_name='Payment date', null=True, blank=True)), - ('payment_provider', models.CharField(verbose_name='Payment provider', null=True, max_length=255, blank=True)), - ('payment_fee', models.DecimalField(decimal_places=2, default=0, verbose_name='Payment method fee', max_digits=10)), - ('payment_info', models.TextField(verbose_name='Payment information', null=True, blank=True)), - ('payment_manual', models.BooleanField(default=False, verbose_name='Payment state was manually modified')), - ('total', models.DecimalField(decimal_places=2, verbose_name='Total amount', max_digits=10)), - ('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='orders', to='pretixbase.Event')), + ('payment_date', models.DateTimeField(verbose_name='Payment date', blank=True, null=True)), + ('payment_provider', models.CharField(verbose_name='Payment provider', max_length=255, blank=True, null=True)), + ('payment_fee', models.DecimalField(verbose_name='Payment method fee', default=0, max_digits=10, decimal_places=2)), + ('payment_info', models.TextField(verbose_name='Payment information', blank=True, null=True)), + ('payment_manual', models.BooleanField(verbose_name='Payment state was manually modified', default=False)), + ('total', models.DecimalField(verbose_name='Total amount', max_digits=10, decimal_places=2)), + ('event', models.ForeignKey(to='pretixbase.Event', verbose_name='Event', related_name='orders')), ], options={ 'verbose_name': 'Order', @@ -240,33 +219,25 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OrderPosition', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), - ('price', models.DecimalField(decimal_places=2, verbose_name='Price', max_digits=10)), - ('attendee_name', models.CharField(verbose_name='Attendee name', null=True, help_text='Empty, if this product is not an admission ticket', max_length=255, blank=True)), - ('item', versions.models.VersionedForeignKey(verbose_name='Item', related_name='positions', to='pretixbase.Item')), - ('order', versions.models.VersionedForeignKey(verbose_name='Order', related_name='positions', to='pretixbase.Order')), - ('variation', versions.models.VersionedForeignKey(verbose_name='Variation', blank=True, null=True, to='pretixbase.ItemVariation')), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('price', models.DecimalField(verbose_name='Price', max_digits=10, decimal_places=2)), + ('attendee_name', models.CharField(verbose_name='Attendee name', max_length=255, blank=True, null=True, help_text='Empty, if this product is not an admission ticket')), + ('item', models.ForeignKey(to='pretixbase.Item', on_delete=django.db.models.deletion.PROTECT, verbose_name='Item', related_name='positions')), + ('order', models.ForeignKey(to='pretixbase.Order', on_delete=django.db.models.deletion.PROTECT, verbose_name='Order', related_name='positions')), + ('variation', models.ForeignKey(blank=True, to='pretixbase.ItemVariation', on_delete=django.db.models.deletion.PROTECT, verbose_name='Variation', null=True)), ], options={ 'verbose_name': 'Order position', 'verbose_name_plural': 'Order positions', }, - bases=(pretix.base.models.ObjectWithAnswers, models.Model), + bases=(pretix.base.models.orders.ObjectWithAnswers, models.Model), ), migrations.CreateModel( name='Organizer', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', models.CharField(verbose_name='Name', max_length=200)), - ('slug', models.SlugField(verbose_name='Slug', help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z0-9.-]+$', message='The slug may only contain letters, numbers, dots and dashes.')])), + ('slug', models.SlugField(verbose_name='Slug', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.')), ], options={ 'verbose_name': 'Organizer', @@ -277,14 +248,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OrganizerPermission', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), - ('can_create_events', models.BooleanField(default=True, verbose_name='Can create events')), - ('organizer', versions.models.VersionedForeignKey(to='pretixbase.Organizer')), - ('user', models.ForeignKey(related_name='organizer_perms', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('can_create_events', models.BooleanField(verbose_name='Can create events', default=True)), + ('organizer', models.ForeignKey(to='pretixbase.Organizer', related_name='user_perms')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='organizer_perms')), ], options={ 'verbose_name': 'Organizer permission', @@ -294,30 +261,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OrganizerSetting', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('key', models.CharField(max_length=255)), ('value', models.TextField()), - ('object', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Organizer')), + ('object', models.ForeignKey(to='pretixbase.Organizer', related_name='setting_objects')), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='Property', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Property name', max_length=250)), - ('event', versions.models.VersionedForeignKey(related_name='properties', to='pretixbase.Event')), - ('item', versions.models.VersionedForeignKey(blank=True, related_name='properties', null=True, to='pretixbase.Item')), + ('event', models.ForeignKey(to='pretixbase.Event', related_name='properties')), + ('item', models.ForeignKey(blank=True, to='pretixbase.Item', null=True, related_name='properties')), ], options={ 'verbose_name': 'Product property', @@ -327,34 +283,26 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PropertyValue', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('value', pretix.base.i18n.I18nCharField(verbose_name='Value', max_length=250)), ('position', models.IntegerField(default=0)), - ('prop', versions.models.VersionedForeignKey(related_name='values', to='pretixbase.Property')), + ('prop', models.ForeignKey(to='pretixbase.Property', related_name='values')), ], options={ 'verbose_name': 'Property value', 'verbose_name_plural': 'Property values', - 'ordering': ('position', 'version_birth_date'), + 'ordering': ('position', 'id'), }, ), migrations.CreateModel( name='Question', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('question', pretix.base.i18n.I18nTextField(verbose_name='Question')), - ('type', models.CharField(verbose_name='Question type', choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], max_length=5)), - ('required', models.BooleanField(default=False, verbose_name='Required question')), - ('event', versions.models.VersionedForeignKey(related_name='questions', to='pretixbase.Event')), - ('items', versions.models.VersionedManyToManyField(to='pretixbase.Item', verbose_name='Products', help_text='This question will be asked to buyers of the selected products', related_name='questions', blank=True)), + ('type', models.CharField(verbose_name='Question type', max_length=5, choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')])), + ('required', models.BooleanField(verbose_name='Required question', default=False)), + ('event', models.ForeignKey(to='pretixbase.Event', related_name='questions')), + ('items', models.ManyToManyField(verbose_name='Products', help_text='This question will be asked to buyers of the selected products', blank=True, to='pretixbase.Item', related_name='questions')), ], options={ 'verbose_name': 'Question', @@ -364,33 +312,22 @@ class Migration(migrations.Migration): migrations.CreateModel( name='QuestionAnswer', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('answer', models.TextField()), - ('cartposition', models.ForeignKey(blank=True, related_name='answers', null=True, to='pretixbase.CartPosition')), - ('orderposition', models.ForeignKey(blank=True, related_name='answers', null=True, to='pretixbase.OrderPosition')), - ('question', versions.models.VersionedForeignKey(related_name='answers', to='pretixbase.Question')), + ('cartposition', models.ForeignKey(blank=True, to='pretixbase.CartPosition', null=True, related_name='answers')), + ('orderposition', models.ForeignKey(blank=True, to='pretixbase.OrderPosition', null=True, related_name='answers')), + ('question', models.ForeignKey(to='pretixbase.Question', related_name='answers')), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='Quota', fields=[ - ('id', models.CharField(serialize=False, max_length=36, primary_key=True)), - ('identity', models.CharField(max_length=36)), - ('version_start_date', models.DateTimeField()), - ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)), - ('version_birth_date', models.DateTimeField()), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), ('name', models.CharField(verbose_name='Name', max_length=200)), - ('size', models.PositiveIntegerField(verbose_name='Total capacity')), - ('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='quotas', to='pretixbase.Event')), - ('items', versions.models.VersionedManyToManyField(to='pretixbase.Item', verbose_name='Item', related_name='quotas', blank=True)), - ('variations', pretix.base.models.VariationsField(to='pretixbase.ItemVariation', verbose_name='Variations', related_name='quotas', blank=True)), + ('size', models.PositiveIntegerField(verbose_name='Total capacity', help_text='Leave empty for an unlimited number of tickets.', blank=True, null=True)), + ('event', models.ForeignKey(to='pretixbase.Event', verbose_name='Event', related_name='quotas')), + ('items', models.ManyToManyField(verbose_name='Item', to='pretixbase.Item', blank=True, related_name='quotas')), + ('variations', pretix.base.models.items.VariationsField(verbose_name='Variations', to='pretixbase.ItemVariation', blank=True, related_name='quotas')), ], options={ 'verbose_name': 'Quota', @@ -400,51 +337,52 @@ class Migration(migrations.Migration): migrations.AddField( model_name='organizer', name='permitted', - field=models.ManyToManyField(related_name='organizers', to=settings.AUTH_USER_MODEL, through='pretixbase.OrganizerPermission'), + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='pretixbase.OrganizerPermission', related_name='organizers'), ), migrations.AddField( model_name='itemvariation', name='values', - field=versions.models.VersionedManyToManyField(related_name='variations', to='pretixbase.PropertyValue'), + field=models.ForeignKey(to='pretixbase.PropertyValue', related_name='variations'), ), migrations.AddField( model_name='item', name='category', - field=versions.models.VersionedForeignKey(verbose_name='Category', blank=True, to='pretixbase.ItemCategory', related_name='items', null=True, on_delete=django.db.models.deletion.PROTECT), + field=models.ForeignKey(blank=True, to='pretixbase.ItemCategory', on_delete=django.db.models.deletion.PROTECT, verbose_name='Category', null=True, related_name='items'), ), migrations.AddField( model_name='item', name='event', - field=versions.models.VersionedForeignKey(verbose_name='Event', related_name='items', to='pretixbase.Event', on_delete=django.db.models.deletion.PROTECT), + field=models.ForeignKey(to='pretixbase.Event', on_delete=django.db.models.deletion.PROTECT, verbose_name='Event', related_name='items'), ), migrations.AddField( model_name='event', name='organizer', - field=versions.models.VersionedForeignKey(related_name='events', to='pretixbase.Organizer', on_delete=django.db.models.deletion.PROTECT), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='pretixbase.Organizer', related_name='events'), ), migrations.AddField( model_name='event', name='permitted', - field=models.ManyToManyField(related_name='events', to=settings.AUTH_USER_MODEL, through='pretixbase.EventPermission'), + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='pretixbase.EventPermission', related_name='events'), ), migrations.AddField( model_name='cartposition', name='event', - field=versions.models.VersionedForeignKey(verbose_name='Event', to='pretixbase.Event'), + field=models.ForeignKey(verbose_name='Event', to='pretixbase.Event'), ), migrations.AddField( model_name='cartposition', name='item', - field=versions.models.VersionedForeignKey(verbose_name='Item', to='pretixbase.Item'), + field=models.ForeignKey(verbose_name='Item', to='pretixbase.Item'), ), migrations.AddField( model_name='cartposition', name='variation', - field=versions.models.VersionedForeignKey(verbose_name='Variation', blank=True, null=True, to='pretixbase.ItemVariation'), + field=models.ForeignKey(blank=True, to='pretixbase.ItemVariation', verbose_name='Variation', null=True), ), migrations.AddField( model_name='cachedticket', name='order', - field=versions.models.VersionedForeignKey(to='pretixbase.Order'), + field=models.ForeignKey(to='pretixbase.Order'), ), + migrations.RunPython(initial_user), ] diff --git a/src/pretix/base/migrations/0002_auto_20151021_1412.py b/src/pretix/base/migrations/0002_auto_20151021_1412.py deleted file mode 100644 index 69380c7e0f..0000000000 --- a/src/pretix/base/migrations/0002_auto_20151021_1412.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pretixbase', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='quota', - name='size', - field=models.PositiveIntegerField(help_text='Leave empty for an unlimited number of tickets.', verbose_name='Total capacity', blank=True, null=True), - ), - ] diff --git a/src/pretix/base/migrations/0002_auto_20151212_1123.py b/src/pretix/base/migrations/0002_auto_20151212_1123.py new file mode 100644 index 0000000000..45708bd5cf --- /dev/null +++ b/src/pretix/base/migrations/0002_auto_20151212_1123.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='itemvariation', + name='values', + ), + migrations.AddField( + model_name='itemvariation', + name='values', + field=models.ManyToManyField(related_name='variations', to='pretixbase.PropertyValue'), + ), + ] diff --git a/src/pretix/base/migrations/0003_event_is_public.py b/src/pretix/base/migrations/0003_event_is_public.py deleted file mode 100644 index 3383f86723..0000000000 --- a/src/pretix/base/migrations/0003_event_is_public.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pretixbase', '0002_auto_20151021_1412'), - ] - - operations = [ - migrations.AddField( - model_name='event', - name='is_public', - field=models.BooleanField(help_text="If selected, this event may show up on the ticket system's start page or an organization profile.", default=False, verbose_name='Visible in public lists'), - ), - ] diff --git a/src/pretix/base/migrations/0004_auto_20151024_0848.py b/src/pretix/base/migrations/0004_auto_20151024_0848.py deleted file mode 100644 index 50d566d44b..0000000000 --- a/src/pretix/base/migrations/0004_auto_20151024_0848.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pretixbase', '0003_event_is_public'), - ] - - operations = [ - migrations.RenameField( - model_name='cartposition', - old_name='session', - new_name='cart_id' - ), - migrations.AlterField( - model_name='cartposition', - name='cart_id', - field=models.CharField(blank=True, verbose_name='Cart ID (e.g. session key)', max_length=255, null=True), - ), - ] diff --git a/src/pretix/base/migrations/0005_auto_20151206_1652.py b/src/pretix/base/migrations/0005_auto_20151206_1652.py deleted file mode 100644 index 9992623452..0000000000 --- a/src/pretix/base/migrations/0005_auto_20151206_1652.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pretixbase', '0004_auto_20151024_0848'), - ] - - operations = [ - migrations.AddField( - model_name='item', - name='available_from', - field=models.DateTimeField(null=True, help_text='This product will not be sold before the given date.', blank=True, verbose_name='Available from'), - ), - migrations.AddField( - model_name='item', - name='available_until', - field=models.DateTimeField(null=True, help_text='This product will not be sold after the given date.', blank=True, verbose_name='Available to'), - ), - ] diff --git a/src/pretix/base/models/__init__.py b/src/pretix/base/models/__init__.py index b9fedaa166..de32244020 100644 --- a/src/pretix/base/models/__init__.py +++ b/src/pretix/base/models/__init__.py @@ -1,5 +1,5 @@ from .auth import User -from .base import CachedFile, Versionable, cachedfile_name +from .base import CachedFile, cachedfile_name from .event import Event, EventLock, EventPermission, EventSetting from .items import ( Item, ItemCategory, ItemVariation, Property, PropertyValue, Question, @@ -12,7 +12,7 @@ from .orders import ( from .organizer import Organizer, OrganizerPermission, OrganizerSetting __all__ = [ - 'Versionable', 'User', 'CachedFile', 'Organizer', 'OrganizerPermission', 'Event', 'EventPermission', + 'User', 'CachedFile', 'Organizer', 'OrganizerPermission', 'Event', 'EventPermission', 'ItemCategory', 'Item', 'Property', 'PropertyValue', 'ItemVariation', 'VariationsField', 'Question', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'ObjectWithAnswers', 'OrderPosition', 'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock', 'cachedfile_name', 'itempicture_upload_to', diff --git a/src/pretix/base/models/base.py b/src/pretix/base/models/base.py index 9dfc0fd4ef..3d822a2f91 100644 --- a/src/pretix/base/models/base.py +++ b/src/pretix/base/models/base.py @@ -1,71 +1,12 @@ -import copy import uuid -from datetime import datetime -import six from django.db import models from django.db.models.signals import post_delete from django.dispatch import receiver -from versions.models import Versionable as BaseVersionable, get_utc_now - - -class Versionable(BaseVersionable): - class Meta: - abstract = True - - def clone_shallow(self, forced_version_date: datetime=None): - """ - This behaves like clone(), but misses all the Many2Many-relation-handling. This is - a performance optimization for cases in which we have to handle the Many2Many relations - by hand anyways. - """ - if not self.pk: # NOQA - raise ValueError('Instance must be saved before it can be cloned') - - if self.version_end_date: # NOQA - raise ValueError('This is a historical item and can not be cloned.') - - if forced_version_date: # NOQA - if not self.version_start_date <= forced_version_date <= get_utc_now(): - raise ValueError('The clone date must be between the version start date and now.') - else: - forced_version_date = get_utc_now() - - earlier_version = self - - later_version = copy.copy(earlier_version) - later_version.version_end_date = None - later_version.version_start_date = forced_version_date - - # set earlier_version's ID to a new UUID so the clone (later_version) can - # get the old one -- this allows 'head' to always have the original - # id allowing us to get at all historic foreign key relationships - earlier_version.id = six.u(str(uuid.uuid4())) - earlier_version.version_end_date = forced_version_date - earlier_version.save() - - for field in earlier_version._meta.many_to_many: - earlier_version.clone_relations_shallow(later_version, field.attname, forced_version_date) - - if hasattr(earlier_version._meta, 'many_to_many_related'): - for rel in earlier_version._meta.many_to_many_related: - earlier_version.clone_relations_shallow(later_version, rel.via_field_name, forced_version_date) - - later_version.save() - - return later_version - - def clone_relations_shallow(self, clone, manager_field_name, forced_version_date): - # Source: the original object, where relations are currently pointing to - source = getattr(self, manager_field_name) # returns a VersionedRelatedManager instance - # Destination: the clone, where the cloned relations should point to - source.through.objects.filter(**{source.source_field.attname: clone.id}).update(**{ - source.source_field.attname: self.id, 'version_end_date': forced_version_date - }) def cachedfile_name(instance, filename: str) -> str: - return 'cachedfiles/%s.%s' % (instance.id, filename.split('.')[-1]) + return 'cachedfiles/%012d.%s' % (instance.id, filename.split('.')[-1]) class CachedFile(models.Model): diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index d9069835f9..248ae8ed0b 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -9,17 +9,15 @@ from django.template.defaultfilters import date as _date from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ -from versions.models import VersionedForeignKey from pretix.base.i18n import I18nCharField from pretix.base.settings import SettingsProxy from .auth import User -from .base import Versionable from .organizer import Organizer -class Event(Versionable): +class Event(models.Model): """ This model represents an event. An event is anything you can buy tickets for. @@ -46,8 +44,7 @@ class Event(Versionable): :type plugins: str """ - organizer = VersionedForeignKey(Organizer, related_name="events", - on_delete=models.PROTECT) + organizer = models.ForeignKey(Organizer, related_name="events", on_delete=models.PROTECT) name = I18nCharField( max_length=200, verbose_name=_("Name"), @@ -184,7 +181,7 @@ class Event(Versionable): return locking.LockManager(self) -class EventPermission(Versionable): +class EventPermission(models.Model): """ The relation between an Event and an User who has permissions to access an event. @@ -203,8 +200,8 @@ class EventPermission(Versionable): :type can_change_orders: bool """ - event = VersionedForeignKey(Event) - user = models.ForeignKey(User, related_name="event_perms") + event = models.ForeignKey(Event, related_name="user_perms", on_delete=models.CASCADE) + user = models.ForeignKey(User, related_name="event_perms", on_delete=models.CASCADE) can_change_settings = models.BooleanField( default=True, verbose_name=_("Can change event settings") @@ -237,12 +234,12 @@ class EventPermission(Versionable): } -class EventSetting(Versionable): +class EventSetting(models.Model): """ An event settings is a key-value setting which can be set for a specific event """ - object = VersionedForeignKey(Event, related_name='setting_objects') + object = models.ForeignKey(Event, related_name='setting_objects', on_delete=models.CASCADE) key = models.CharField(max_length=255) value = models.TextField() diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index ec707375a5..9a9b4835a0 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -8,16 +8,14 @@ from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from typing import List, Tuple -from versions.models import VersionedForeignKey, VersionedManyToManyField from pretix.base.i18n import I18nCharField, I18nTextField from ..types import VariationDict -from .base import Versionable from .event import Event -class ItemCategory(Versionable): +class ItemCategory(models.Model): """ Items can be sorted into these categories. @@ -28,7 +26,7 @@ class ItemCategory(Versionable): :param position: An integer, used for sorting :type position: int """ - event = VersionedForeignKey( + event = models.ForeignKey( Event, on_delete=models.CASCADE, related_name='categories', @@ -44,7 +42,7 @@ class ItemCategory(Versionable): class Meta: verbose_name = _("Product category") verbose_name_plural = _("Product categories") - ordering = ('position', 'version_birth_date') + ordering = ('position', 'id') def __str__(self): return str(self.name) @@ -61,7 +59,7 @@ class ItemCategory(Versionable): @property def sortkey(self): - return self.position, self.version_birth_date + return self.position, self.id def __lt__(self, other) -> bool: return self.sortkey < other.sortkey @@ -69,12 +67,12 @@ class ItemCategory(Versionable): def itempicture_upload_to(instance, filename: str) -> str: return '%s/%s/item-%s.%s' % ( - instance.event.organizer.slug, instance.event.slug, instance.identity, + instance.event.organizer.slug, instance.event.slug, instance.id, filename.split('.')[-1] ) -class Item(Versionable): +class Item(models.Model): """ An item is a thing which can be sold. It belongs to an event and may or may not belong to a category. Items are often also called 'products' but are named 'items' internally due to historic reasons. @@ -104,13 +102,13 @@ class Item(Versionable): """ - event = VersionedForeignKey( + event = models.ForeignKey( Event, on_delete=models.PROTECT, related_name="items", verbose_name=_("Event"), ) - category = VersionedForeignKey( + category = models.ForeignKey( ItemCategory, on_delete=models.PROTECT, related_name="items", @@ -222,7 +220,7 @@ class Item(Versionable): for var in all_variations: key = [] for v in var.values.all(): - key.append((v.prop_id, v.identity)) + key.append((v.prop_id, v.id)) key = tuple(sorted(key)) variations_cache[key] = var @@ -234,8 +232,8 @@ class Item(Versionable): key = [] var = VariationDict() for v in comb: - key.append((v.prop.identity, v.identity)) - var[v.prop.identity] = v + key.append((v.prop.id, v.id)) + var[v.prop.id] = v key = tuple(sorted(key)) if key in variations_cache: var['variation'] = variations_cache[key] @@ -245,7 +243,7 @@ class Item(Versionable): return result def _get_all_generated_variations(self): - propids = set([p.identity for p in self.properties.all()]) + propids = set([p.id for p in self.properties.all()]) if len(propids) == 0: variations = [VariationDict()] else: @@ -261,11 +259,11 @@ class Item(Versionable): values = list(var.values.all()) # Make sure we don't expose stale ItemVariation objects which are # still around altough they have an old set of properties - if set([v.prop.identity for v in values]) != propids: + if set([v.prop.id for v in values]) != propids: continue vardict = VariationDict() for v in values: - vardict[v.prop.identity] = v + vardict[v.prop.id] = v vardict['variation'] = var variations.append(vardict) return variations @@ -325,7 +323,7 @@ class Item(Versionable): key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize)) -class Property(Versionable): +class Property(models.Model): """ A property is a modifier which can be applied to an Item. For example 'Size' would be a property associated with the item 'T-Shirt'. @@ -336,11 +334,11 @@ class Property(Versionable): :type name: str """ - event = VersionedForeignKey( + event = models.ForeignKey( Event, related_name="properties" ) - item = VersionedForeignKey( + item = models.ForeignKey( Item, related_name='properties', null=True, blank=True ) name = I18nCharField( @@ -366,7 +364,7 @@ class Property(Versionable): self.event.get_cache().clear() -class PropertyValue(Versionable): +class PropertyValue(models.Model): """ A value of a property. If the property would be 'T-Shirt size', this could be 'M' or 'L'. @@ -379,7 +377,7 @@ class PropertyValue(Versionable): :type position: int """ - prop = VersionedForeignKey( + prop = models.ForeignKey( Property, on_delete=models.CASCADE, related_name="values" @@ -395,7 +393,7 @@ class PropertyValue(Versionable): class Meta: verbose_name = _("Property value") verbose_name_plural = _("Property values") - ordering = ("position", "version_birth_date") + ordering = ("position", "id") def __str__(self): return "%s: %s" % (self.prop.name, self.value) @@ -412,13 +410,13 @@ class PropertyValue(Versionable): @property def sortkey(self) -> Tuple[int, datetime]: - return self.position, self.version_birth_date + return self.position, self.id def __lt__(self, other) -> bool: return self.sortkey < other.sortkey -class ItemVariation(Versionable): +class ItemVariation(models.Model): """ A variation is an item combined with values for all properties associated with the item. For example, if your item is 'T-Shirt' @@ -444,11 +442,11 @@ class ItemVariation(Versionable): :param default_price: This variation's default price :type default_price: decimal.Decimal """ - item = VersionedForeignKey( + item = models.ForeignKey( Item, related_name='variations' ) - values = VersionedManyToManyField( + values = models.ManyToManyField( PropertyValue, related_name='variations', ) @@ -495,7 +493,7 @@ class ItemVariation(Versionable): """ vd = VariationDict() for v in self.values.all(): - vd[v.prop.identity] = v + vd[v.prop.id] = v vd['variation'] = self return vd @@ -507,14 +505,14 @@ class ItemVariation(Versionable): for pair in pk.split(","): prop, value = pair.split(":") self.values.add( - PropertyValue.objects.current.get( - identity=value, + PropertyValue.objects.get( + id=value, prop_id=prop ) ) -class VariationsField(VersionedManyToManyField): +class VariationsField(models.ManyToManyField): """ This is a ManyToManyField using the pretixcontrol.views.forms.VariationsField form field by default. @@ -536,12 +534,12 @@ class VariationsField(VersionedManyToManyField): initial = defaults['initial'] if callable(initial): initial = initial() - defaults['initial'] = [i.identity for i in initial] + defaults['initial'] = [i.id for i in initial] # Skip ManyToManyField in dependency chain return super(RelatedField, self).formfield(**defaults) -class Question(Versionable): +class Question(models.Model): """ A question is an input field that can be used to extend a ticket by custom information, e.g. "Attendee age". A question can allow one o several @@ -573,7 +571,7 @@ class Question(Versionable): (TYPE_BOOLEAN, _("Yes/No")), ) - event = VersionedForeignKey( + event = models.ForeignKey( Event, related_name="questions" ) @@ -589,7 +587,7 @@ class Question(Versionable): default=False, verbose_name=_("Required question") ) - items = VersionedManyToManyField( + items = models.ManyToManyField( Item, related_name='questions', verbose_name=_("Products"), @@ -615,7 +613,7 @@ class Question(Versionable): self.event.get_cache().clear() -class Quota(Versionable): +class Quota(models.Model): """ A quota is a "pool of tickets". It is there to limit the number of items of a certain type to be sold. For example, you could have a quota of 500 @@ -666,7 +664,7 @@ class Quota(Versionable): AVAILABILITY_RESERVED = 20 AVAILABILITY_OK = 100 - event = VersionedForeignKey( + event = models.ForeignKey( Event, on_delete=models.CASCADE, related_name="quotas", @@ -681,7 +679,7 @@ class Quota(Versionable): null=True, blank=True, help_text=_("Leave empty for an unlimited number of tickets.") ) - items = VersionedManyToManyField( + items = models.ManyToManyField( Item, verbose_name=_("Item"), related_name="quotas", @@ -745,7 +743,7 @@ class Quota(Versionable): def count_in_cart(self) -> int: from pretix.base.models import CartPosition - return CartPosition.objects.current.filter( + return CartPosition.objects.filter( Q(expires__gte=now()) & self._position_lookup ).count() @@ -753,7 +751,7 @@ class Quota(Versionable): def count_orders(self) -> dict: from pretix.base.models import Order, OrderPosition - o = OrderPosition.objects.current.filter(self._position_lookup).aggregate( + o = OrderPosition.objects.filter(self._position_lookup).aggregate( paid=Sum( Case(When(order__status=Order.STATUS_PAID, then=1), output_field=models.IntegerField()) diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index b9ff5c5030..bb017e57b1 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -6,9 +6,8 @@ from django.db import models from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from typing import List, Union -from versions.models import VersionedForeignKey -from .base import CachedFile, Versionable +from .base import CachedFile from .event import Event from .items import Item, ItemVariation, Question, Quota @@ -17,7 +16,7 @@ def generate_secret(): return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16)) -class Order(Versionable): +class Order(models.Model): """ An order is created when a user clicks 'buy' on his cart. It holds several OrderPositions and is connected to an user. It has an @@ -83,7 +82,7 @@ class Order(Versionable): choices=STATUS_CHOICE, verbose_name=_("Status") ) - event = VersionedForeignKey( + event = models.ForeignKey( Event, verbose_name=_("Event"), related_name="orders" @@ -180,13 +179,11 @@ class Order(Versionable): def mark_refunded(self): """ - Mark this order as refunded. This clones the order object, sets the payment status and - returns the cloned order object. + Mark this order as refunded. This sets the payment status and returns the order object. """ - order = self.clone() - order.status = Order.STATUS_REFUNDED - order.save() - return order + self.status = Order.STATUS_REFUNDED + self.save() + return self def _can_be_paid(self) -> Union[bool, str]: error_messages = { @@ -223,12 +220,12 @@ class Order(Versionable): for quota in quotas: # Lock the quota, so no other thread is allowed to perform sales covered by this # quota while we're doing so. - if quota.identity not in quota_cache: - quota_cache[quota.identity] = quota + if quota.id not in quota_cache: + quota_cache[quota.id] = quota quota.cached_availability = quota.availability()[1] else: # Use cached version - quota = quota_cache[quota.identity] + quota = quota_cache[quota.id] if quota.cached_availability is not None: quota.cached_availability -= 1 if quota.cached_availability < 0: @@ -240,12 +237,12 @@ class Order(Versionable): class CachedTicket(models.Model): - order = VersionedForeignKey(Order, on_delete=models.CASCADE) + order = models.ForeignKey(Order, on_delete=models.CASCADE) cachedfile = models.ForeignKey(CachedFile, on_delete=models.CASCADE) provider = models.CharField(max_length=255) -class QuestionAnswer(Versionable): +class QuestionAnswer(models.Model): """ The answer to a Question, connected to an OrderPosition or CartPosition. @@ -268,7 +265,7 @@ class QuestionAnswer(Versionable): 'CartPosition', null=True, blank=True, related_name='answers' ) - question = VersionedForeignKey( + question = models.ForeignKey( Question, related_name='answers' ) answer = models.TextField() @@ -286,14 +283,14 @@ class ObjectWithAnswers: self.answ[a.question_id] = a.answer self.questions = [] for q in self.item.questions.all(): - if q.identity in self.answ: - q.answer = self.answ[q.identity] + if q.id in self.answ: + q.answer = self.answ[q.id] else: q.answer = "" self.questions.append(q) -class OrderPosition(ObjectWithAnswers, Versionable): +class OrderPosition(ObjectWithAnswers, models.Model): """ An OrderPosition is one line of an order, representing one ordered items of a specified type (or variation). @@ -309,20 +306,23 @@ class OrderPosition(ObjectWithAnswers, Versionable): :param attendee_name: The attendee's name, if entered. :type attendee_name: str """ - order = VersionedForeignKey( + order = models.ForeignKey( Order, verbose_name=_("Order"), - related_name='positions' + related_name='positions', + on_delete=models.PROTECT ) - item = VersionedForeignKey( + item = models.ForeignKey( Item, verbose_name=_("Item"), - related_name='positions' + related_name='positions', + on_delete=models.PROTECT ) - variation = VersionedForeignKey( + variation = models.ForeignKey( ItemVariation, null=True, blank=True, - verbose_name=_("Variation") + verbose_name=_("Variation"), + on_delete=models.PROTECT ) price = models.DecimalField( decimal_places=2, max_digits=10, @@ -347,17 +347,16 @@ class OrderPosition(ObjectWithAnswers, Versionable): order=order, item=cartpos.item, variation=cartpos.variation, price=cartpos.price, attendee_name=cartpos.attendee_name ) + op.save() for answ in cartpos.answers.all(): - answ = answ.clone() answ.orderposition = op answ.cartposition = None answ.save() - op.save() cartpos.delete() ops.append(op) -class CartPosition(ObjectWithAnswers, Versionable): +class CartPosition(ObjectWithAnswers, models.Model): """ 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 @@ -383,7 +382,7 @@ class CartPosition(ObjectWithAnswers, Versionable): :param attendee_name: The attendee's name, if entered. :type attendee_name: str """ - event = VersionedForeignKey( + event = models.ForeignKey( Event, verbose_name=_("Event") ) @@ -391,14 +390,16 @@ class CartPosition(ObjectWithAnswers, Versionable): max_length=255, null=True, blank=True, verbose_name=_("Cart ID (e.g. session key)") ) - item = VersionedForeignKey( + item = models.ForeignKey( Item, - verbose_name=_("Item") + verbose_name=_("Item"), + on_delete=models.CASCADE ) - variation = VersionedForeignKey( + variation = models.ForeignKey( ItemVariation, null=True, blank=True, - verbose_name=_("Variation") + verbose_name=_("Variation"), + on_delete=models.CASCADE ) price = models.DecimalField( decimal_places=2, max_digits=10, @@ -421,3 +422,8 @@ class CartPosition(ObjectWithAnswers, Versionable): class Meta: verbose_name = _("Cart position") verbose_name_plural = _("Cart positions") + + def __str__(self): + return '' % ( + self.item.id, self.variation.id if self.variation else 0, self.cart_id + ) diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index e038093cb2..3cb7433cbe 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -2,15 +2,13 @@ from django.core.validators import RegexValidator from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from versions.models import VersionedForeignKey from pretix.base.settings import SettingsProxy from .auth import User -from .base import Versionable -class Organizer(Versionable): +class Organizer(models.Model): """ This model represents an entity organizing events, e.g. a company, institution, charity, person, … @@ -72,7 +70,7 @@ class Organizer(Versionable): return ObjectRelatedCache(self) -class OrganizerPermission(Versionable): +class OrganizerPermission(models.Model): """ The relation between an Organizer and an User who has permissions to access an organizer profile. @@ -86,7 +84,7 @@ class OrganizerPermission(Versionable): :type can_create_events: bool """ - organizer = VersionedForeignKey(Organizer) + organizer = models.ForeignKey(Organizer, related_name="user_perms", on_delete=models.CASCADE) user = models.ForeignKey(User, related_name="organizer_perms") can_create_events = models.BooleanField( default=True, @@ -104,11 +102,11 @@ class OrganizerPermission(Versionable): } -class OrganizerSetting(Versionable): +class OrganizerSetting(models.Model): """ An event option is a key-value setting which can be set for an organizer. It will be inherited by the events of this organizer """ - object = VersionedForeignKey(Organizer, related_name='setting_objects') + object = models.ForeignKey(Organizer, related_name='setting_objects', on_delete=models.CASCADE) key = models.CharField(max_length=255) value = models.TextField() diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 42af2f0a8e..d94d6eee71 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -258,9 +258,7 @@ class BasePaymentProvider: If the payment is completed, you should call ``pretix.base.services.orders.mark_order_paid(order, provider, info)`` with ``provider`` being your :py:attr:`identifier` and ``info`` being any string - you might want to store for later usage. Please note, that if you want to store - something inside ``order.payment_info``, please do it after the ``mark_order_paid`` call, - as this call does a object clone for you. Please also note that ``mark_order_paid`` might + you might want to store for later usage. Please note that ``mark_order_paid`` might raise a ``Quota.QuotaExceededException`` if (and only if) the payment term of this order is over and some of the items are sold out. You should use the exception message to display a meaningful error to the user. @@ -403,7 +401,7 @@ class FreeOrderProvider(BasePaymentProvider): pass def payment_is_valid_session(self, request: HttpRequest) -> bool: - return CartPosition.objects.current.filter( + return CartPosition.objects.filter( Q(cart_id=request.session.session_key) & Q(event=request.event) ).aggregate(sum=Sum('price'))['sum'] == 0 @@ -446,7 +444,7 @@ class FreeOrderProvider(BasePaymentProvider): messages.success(request, _('The order has been marked as refunded.')) def is_allowed(self, request: HttpRequest) -> bool: - return CartPosition.objects.current.filter( + return CartPosition.objects.filter( cart_id=request.session.session_key, event=request.event ).aggregate(sum=Sum('price'))['sum'] == 0 diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 5c059ba476..4908597ea1 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -35,7 +35,7 @@ def _extend_existing(event: Event, cart_id: str, expiry: datetime) -> None: # Extend this user's cart session to 30 minutes from now to ensure all items in the # cart expire at the same time # We can extend the reservation of items which are not yet expired without risk - CartPosition.objects.current.filter( + CartPosition.objects.filter( Q(cart_id=cart_id) & Q(event=event) & Q(expires__gt=now()) ).update(expires=expiry) @@ -44,7 +44,7 @@ def _re_add_expired_positions(items: List[CartPosition], event: Event, cart_id: positions = set() # For items that are already expired, we have to delete and re-add them, as they might # be no longer available or prices might have changed. Sorry! - expired = CartPosition.objects.current.filter( + expired = CartPosition.objects.filter( Q(cart_id=cart_id) & Q(event=event) & Q(expires__lte=now()) ) for cp in expired: @@ -55,7 +55,7 @@ def _re_add_expired_positions(items: List[CartPosition], event: Event, cart_id: def _delete_expired(expired: List[CartPosition]) -> None: for cp in expired: - if cp.version_end_date is None: + if cp.expires <= now(): cp.delete() @@ -66,19 +66,19 @@ def _check_date(event: Event) -> None: raise CartError(error_messages['ended']) -def _add_new_items(event: Event, items: List[Tuple[str, Optional[str], int]], +def _add_new_items(event: Event, items: List[Tuple[int, Optional[int], int]], cart_id: str, expiry: datetime) -> Optional[str]: err = None # Fetch items from the database - items_query = Item.objects.current.filter(event=event, identity__in=[i[0] for i in items]).prefetch_related( + items_query = Item.objects.filter(event=event, id__in=[i[0] for i in items]).prefetch_related( "quotas") - items_cache = {i.identity: i for i in items_query} - variations_query = ItemVariation.objects.current.filter( + items_cache = {i.id: i for i in items_query} + variations_query = ItemVariation.objects.filter( item__event=event, - identity__in=[i[1] for i in items if i[1] is not None] + id__in=[i[1] for i in items if i[1] is not None] ).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop") - variations_cache = {v.identity: v for v in variations_query} + variations_cache = {v.id: v for v in variations_query} for i in items: # Check whether the specified items are part of what we just fetched from the database @@ -115,7 +115,7 @@ def _add_new_items(event: Event, items: List[Tuple[str, Optional[str], int]], for k in range(quota_ok): if len(i) > 3 and i[2] == 1: # Recreating - cp = i[3].clone() + cp = i[3] cp.expires = expiry cp.price = item.default_price if variation is None else ( variation.default_price if variation.default_price is not None else item.default_price) @@ -131,10 +131,10 @@ def _add_new_items(event: Event, items: List[Tuple[str, Optional[str], int]], return err -def _add_items_to_cart(event: Event, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None: +def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int]], cart_id: str=None) -> None: with event.lock(): _check_date(event) - existing = CartPosition.objects.current.filter(Q(cart_id=cart_id) & Q(event=event)).count() + existing = CartPosition.objects.filter(Q(cart_id=cart_id) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int(event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'] % event.settings.max_items_per_order) @@ -152,7 +152,7 @@ def _add_items_to_cart(event: Event, items: List[Tuple[str, Optional[str], int]] raise CartError(err) -def add_items_to_cart(event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None: +def add_items_to_cart(event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str=None) -> None: """ Adds a list of items to a user's cart. :param event: The event ID in question @@ -160,21 +160,21 @@ def add_items_to_cart(event: str, items: List[Tuple[str, Optional[str], int]], c :param session: Session ID of a guest :raises CartError: On any error that occured """ - event = Event.objects.current.get(identity=event) + event = Event.objects.get(id=event) try: _add_items_to_cart(event, items, cart_id) except EventLock.LockTimeoutException: raise CartError(error_messages['busy']) -def remove_items_from_cart(event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None: +def remove_items_from_cart(event: int, items: List[Tuple[int, Optional[int], int]], cart_id: int=None) -> None: """ Removes a list of items from a user's cart. :param event: The event ID in question :param items: A list of tuple of the form (item id, variation id or None, number) :param session: Session ID of a guest """ - event = Event.objects.current.get(identity=event) + event = Event.objects.get(id=event) for item, variation, cnt in items: cw = Q(cart_id=cart_id) & Q(item_id=item) & Q(event=event) @@ -182,7 +182,7 @@ def remove_items_from_cart(event: str, items: List[Tuple[str, Optional[str], int cw &= Q(variation_id=variation) else: cw &= Q(variation__isnull=True) - for cp in CartPosition.objects.current.filter(cw).order_by("-price")[:cnt]: + for cp in CartPosition.objects.filter(cw).order_by("-price")[:cnt]: cp.delete() @@ -190,8 +190,8 @@ if settings.HAS_CELERY: from pretix.celery import app @app.task(bind=True, max_retries=5, default_retry_delay=2) - def add_items_to_cart_task(self, event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str): - event = Event.objects.current.get(identity=event) + def add_items_to_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str): + event = Event.objects.get(id=event) try: _add_items_to_cart(event, items, cart_id) except EventLock.LockTimeoutException: diff --git a/src/pretix/base/services/export.py b/src/pretix/base/services/export.py index cd846c410d..bcb8e661bc 100644 --- a/src/pretix/base/services/export.py +++ b/src/pretix/base/services/export.py @@ -7,7 +7,7 @@ from pretix.base.signals import register_data_exporters def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) -> None: - event = Event.objects.current.get(identity=event) + event = Event.objects.get(id=event) file = CachedFile.objects.get(id=fileid) responses = register_data_exporters.send(event) for receiver, response in responses: diff --git a/src/pretix/base/services/locking.py b/src/pretix/base/services/locking.py index bdc8c5a0db..aa4bc02b6d 100644 --- a/src/pretix/base/services/locking.py +++ b/src/pretix/base/services/locking.py @@ -64,13 +64,13 @@ def lock_event_db(event): for i in range(retries): with transaction.atomic(): dt = now() - l, created = EventLock.objects.get_or_create(event=event.identity) + l, created = EventLock.objects.get_or_create(event=event.id) if created: event._lock = l return True elif l.date < now() - timedelta(seconds=LOCK_TIMEOUT): newtoken = uuid.uuid4() - updated = EventLock.objects.filter(event=event.identity, token=l.token).update(date=dt, token=newtoken) + updated = EventLock.objects.filter(event=event.id, token=l.token).update(date=dt, token=newtoken) if updated: l.token = newtoken event._lock = l @@ -84,7 +84,7 @@ def release_event_db(event): if not hasattr(event, '_lock') or not event._lock: raise EventLock.LockReleaseException('Lock is not owned by this thread') try: - lock = EventLock.objects.get(event=event.identity, token=event._lock.token) + lock = EventLock.objects.get(event=event.id, token=event._lock.token) lock.delete() event._lock = None except EventLock.DoesNotExist: @@ -97,7 +97,7 @@ def redis_lock_from_event(event): if not hasattr(event, '_lock') or not event._lock: rc = get_redis_connection("redis") - event._lock = Lock(redis=rc, name='pretix_event_%s' % event.identity, timeout=LOCK_TIMEOUT) + event._lock = Lock(redis=rc, name='pretix_event_%s' % event.id, timeout=LOCK_TIMEOUT) return event._lock diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 45326cbe3b..12abd79cde 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -33,8 +33,8 @@ error_messages = { def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None, force: bool=False) -> Order: """ - Marks an order as paid. This clones the order object, sets the payment provider, - info and date and returns the cloned order object. + Marks an order as paid. This sets the payment provider, info and date and returns + the order object. :param provider: The payment provider that marked this as paid :type provider: str @@ -52,7 +52,6 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date can_be_paid = order._can_be_paid() if not force and can_be_paid is not True: raise Quota.QuotaExceededException(can_be_paid) - order = order.clone() order.payment_provider = provider or order.payment_provider order.payment_info = info or order.payment_info order.payment_date = date or now() @@ -110,7 +109,6 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]): cp.delete() continue if price != cp.price: - cp = cp.clone() positions[i] = cp cp.price = price cp.save() @@ -125,7 +123,6 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]): quota_ok = False break if quota_ok: - cp = cp.clone() positions[i] = cp cp.expires = now() + timedelta( minutes=event.settings.get('reservation_time', as_type=int)) @@ -163,7 +160,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], dt: d def _perform_order(event: str, payment_provider: str, position_ids: List[str], email: str, locale: str): - event = Event.objects.current.get(identity=event) + event = Event.objects.get(id=event) responses = register_payment_providers.send(event) pprov = None for receiver, response in responses: @@ -175,8 +172,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str], dt = now() with event.lock(): - positions = list(CartPosition.objects.current.filter( - identity__in=position_ids).select_related('item', 'variation')) + positions = list(CartPosition.objects.filter( + id__in=position_ids).select_related('item', 'variation')) if len(position_ids) != len(positions): raise OrderError(error_messages['internal']) _check_positions(event, dt, positions) @@ -197,7 +194,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str], }, event, locale=order.locale ) - return order.identity + return order.id def perform_order(event: str, payment_provider: str, positions: List[str], diff --git a/src/pretix/base/services/stats.py b/src/pretix/base/services/stats.py index b91c3d6d00..b042481e8a 100644 --- a/src/pretix/base/services/stats.py +++ b/src/pretix/base/services/stats.py @@ -37,28 +37,28 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], num_total = { (p['item'], p['variation']): (p['cnt'], p['price']) - for p in (OrderPosition.objects.current + for p in (OrderPosition.objects .filter(order__event=event) .values('item', 'variation') .annotate(cnt=Count('id'), price=Sum('price')).order_by()) } num_cancelled = { (p['item'], p['variation']): (p['cnt'], p['price']) - for p in (OrderPosition.objects.current + for p in (OrderPosition.objects .filter(order__event=event, order__status=Order.STATUS_CANCELLED) .values('item', 'variation') .annotate(cnt=Count('id'), price=Sum('price')).order_by()) } num_refunded = { (p['item'], p['variation']): (p['cnt'], p['price']) - for p in (OrderPosition.objects.current + for p in (OrderPosition.objects .filter(order__event=event, order__status=Order.STATUS_REFUNDED) .values('item', 'variation') .annotate(cnt=Count('id'), price=Sum('price')).order_by()) } num_pending = { (p['item'], p['variation']): (p['cnt'], p['price']) - for p in (OrderPosition.objects.current + for p in (OrderPosition.objects .filter(order__event=event, order__status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED)) .values('item', 'variation') @@ -66,7 +66,7 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], } num_paid = { (p['item'], p['variation']): (p['cnt'], p['price']) - for p in (OrderPosition.objects.current + for p in (OrderPosition.objects .filter(order__event=event, order__status=Order.STATUS_PAID) .values('item', 'variation') .annotate(cnt=Count('id'), price=Sum('price')).order_by()) @@ -76,12 +76,12 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], item.all_variations = sorted(item.get_all_variations(), key=lambda vd: vd.ordered_values()) for var in item.all_variations: - variid = var['variation'].identity if 'variation' in var else None - var.num_total = num_total.get((item.identity, variid), (0, 0)) - var.num_pending = num_pending.get((item.identity, variid), (0, 0)) - var.num_cancelled = num_cancelled.get((item.identity, variid), (0, 0)) - var.num_refunded = num_refunded.get((item.identity, variid), (0, 0)) - var.num_paid = num_paid.get((item.identity, variid), (0, 0)) + variid = var['variation'].id if 'variation' in var else None + var.num_total = num_total.get((item.id, variid), (0, 0)) + var.num_pending = num_pending.get((item.id, variid), (0, 0)) + var.num_cancelled = num_cancelled.get((item.id, variid), (0, 0)) + var.num_refunded = num_refunded.get((item.id, variid), (0, 0)) + var.num_paid = num_paid.get((item.id, variid), (0, 0)) item.has_variations = (len(item.all_variations) != 1 or not item.all_variations[0].empty()) item.num_total = tuplesum(var.num_total for var in item.all_variations) @@ -100,7 +100,8 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], # insert categories into a set for uniqueness # a set is unsorted, so sort again by category ], - key=lambda group: (group[0].position, group[0].identity) if group[0] is not None else (0, "") + key=lambda group: (group[0].position, group[0].id) if ( + group[0] is not None and group[0].id is not None) else (0, 0) ) for c in items_by_category: @@ -116,35 +117,35 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], payment_items = [] num_total = { o['payment_provider']: (o['cnt'], o['payment_fee']) - for o in (Order.objects.current + for o in (Order.objects .filter(event=event) .values('payment_provider') .annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by()) } num_cancelled = { o['payment_provider']: (o['cnt'], o['payment_fee']) - for o in (Order.objects.current + for o in (Order.objects .filter(event=event, status=Order.STATUS_CANCELLED) .values('payment_provider') .annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by()) } num_refunded = { o['payment_provider']: (o['cnt'], o['payment_fee']) - for o in (Order.objects.current + for o in (Order.objects .filter(event=event, status=Order.STATUS_REFUNDED) .values('payment_provider') .annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by()) } num_pending = { o['payment_provider']: (o['cnt'], o['payment_fee']) - for o in (Order.objects.current + for o in (Order.objects .filter(event=event, status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED)) .values('payment_provider') .annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by()) } num_paid = { o['payment_provider']: (o['cnt'], o['payment_fee']) - for o in (Order.objects.current + for o in (Order.objects .filter(event=event, status=Order.STATUS_PAID) .values('payment_provider') .annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by()) diff --git a/src/pretix/base/services/tickets.py b/src/pretix/base/services/tickets.py index aefd0dbd76..469b1cbbf6 100644 --- a/src/pretix/base/services/tickets.py +++ b/src/pretix/base/services/tickets.py @@ -9,7 +9,7 @@ from pretix.base.signals import register_ticket_outputs def generate(order: str, provider: str): - order = Order.objects.current.select_related('event').get(identity=order) + order = Order.objects.select_related('event').get(id=order) ct = CachedTicket.objects.get_or_create(order=order, provider=provider)[0] if not ct.cachedfile: cf = CachedFile() diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index b5b77213ab..83f4299166 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -8,7 +8,6 @@ from django.core.files import File from django.core.files.storage import default_storage from django.db.models import Model from typing import Any, Callable, Dict, Optional, TypeVar, Union -from versions.models import Versionable DEFAULTS = { 'max_items_per_order': { @@ -115,7 +114,7 @@ class SettingsProxy: def _cache(self) -> Dict[str, Any]: if self._cached_obj is None: self._cached_obj = {} - for setting in self._obj.setting_objects.current.all(): + for setting in self._obj.setting_objects.all(): self._cached_obj[setting.key] = setting return self._cached_obj @@ -146,8 +145,6 @@ class SettingsProxy: return dateutil.parser.parse(value).date() elif as_type == time: return dateutil.parser.parse(value).time() - elif as_type is not None and issubclass(as_type, Versionable): - return as_type.objects.current.get(identity=value) elif as_type is not None and issubclass(as_type, Model): return as_type.objects.get(pk=value) return value @@ -162,8 +159,6 @@ class SettingsProxy: return json.dumps(value) elif isinstance(value, datetime) or isinstance(value, date) or isinstance(value, time): return value.isoformat() - elif isinstance(value, Versionable): - return value.identity elif isinstance(value, Model): return value.pk elif isinstance(value, File): @@ -213,7 +208,6 @@ class SettingsProxy: def set(self, key: str, value: Any) -> None: if key in self._cache(): s = self._cache()[key] - s = s.clone() else: s = self._type(object=self._obj, key=key) s.value = self._serialize(value) @@ -237,7 +231,7 @@ class SettingsSandbox: prefixes for you. """ - def __init__(self, type: str, key: str, event: Versionable): + def __init__(self, type: str, key: str, event: Model): self._event = event self._type = type self._key = key diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py index 1966f65a23..b4e63396e2 100644 --- a/src/pretix/control/forms/__init__.py +++ b/src/pretix/control/forms/__init__.py @@ -14,7 +14,7 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ -from pretix.base.forms import VersionedModelForm +from pretix.base.forms import I18nModelForm from pretix.base.models import Item, ItemVariation @@ -59,9 +59,9 @@ class I18nFormSet(BaseModelFormSet): return form -class TolerantFormsetModelForm(VersionedModelForm): +class TolerantFormsetModelForm(I18nModelForm): """ - This is equivalent to a normal VersionedModelForm, but works around a problem that + This is equivalent to a normal I18nModelForm, but works around a problem that arises when the form is used inside a FormSet with can_order=True and django-formset-js enabled. In this configuration, even empty "extra" forms might have an ORDER value sent and Django marks the form as empty and raises validation errors because the other @@ -105,15 +105,15 @@ def selector(values, prop): # properties they belong to EXCEPT the value for the property prop2. # We'll see later why we need this. return [ - v.identity for v in sorted(values, key=lambda v: v.prop.identity) - if v.prop.identity != prop.identity + v.id for v in sorted(values, key=lambda v: v.prop.id) + if v.prop.id != prop.id ] def sort(v, prop): # Given a list of variations, this will sort them by their position # on the x-axis - return v[prop.identity].sortkey + return v[prop.id].sortkey class VariationsFieldRenderer(forms.widgets.CheckboxFieldRenderer): @@ -175,7 +175,7 @@ class VariationsFieldRenderer(forms.widgets.CheckboxFieldRenderer): final_attrs['checked'] = 'checked' w = self.choice_input_class( self.name, self.value, self.attrs.copy(), - (variation['key'], variation[properties[0].identity].value), + (variation['key'], variation[properties[0].id].value), i ) output.append(format_html('
  • {0}
  • ', force_text(w))) @@ -185,15 +185,15 @@ class VariationsFieldRenderer(forms.widgets.CheckboxFieldRenderer): def render_nd(self, output, variations, properties): # prop1 is the property on all the grid's y-axes prop1 = properties[0] - prop1v = list(prop1.values.current.all()) + prop1v = list(prop1.values.all()) # prop2 is the property on all the grid's x-axes prop2 = properties[1] - prop2v = list(prop2.values.current.all()) + prop2v = list(prop2.values.all()) # We now iterate over the cartesian product of all the other # properties which are NOT on the axes of the grid because we # create one grid for any combination of them. - for gridrow in product(*[prop.values.current.all() for prop in properties[2:]]): + for gridrow in product(*[prop.values.all() for prop in properties[2:]]): if len(gridrow) > 0: output.append('') output.append(", ".join([str(value.value) for value in gridrow])) @@ -281,7 +281,7 @@ class VariationsField(forms.ModelMultipleChoiceField): variations = self.item.get_all_variations(use_cache=True) return ( ( - v['variation'].identity if 'variation' in v else v.key(), + v['variation'].id if 'variation' in v else v.key(), v ) for v in variations ) @@ -312,10 +312,10 @@ class VariationsField(forms.ModelMultipleChoiceField): cleaned_value = self._clean_value(value) - qs = self.item.variations.current.filter(identity__in=cleaned_value) + qs = self.item.variations.filter(id__in=cleaned_value) # Re-check for consistency - pks = set(force_text(getattr(o, "identity")) for o in qs) + pks = set(force_text(getattr(o, "id")) for o in qs) for val in cleaned_value: if force_text(val) not in pks: raise ValidationError( @@ -335,7 +335,7 @@ class VariationsField(forms.ModelMultipleChoiceField): # which uses a very similar method all_variations = self.item.variations.all().prefetch_related("values") variations_cache = { - var.to_variation_dict().identify(): var.identity for var in all_variations + var.to_variation_dict().identify(): var.id for var in all_variations } cleaned_value = [] @@ -360,7 +360,7 @@ class VariationsField(forms.ModelMultipleChoiceField): # No ItemVariation present, create one! var = ItemVariation() - var.item_id = self.item.identity + var.item_id = self.item.id var.save() # Add the values to the ItemVariation object try: @@ -371,8 +371,8 @@ class VariationsField(forms.ModelMultipleChoiceField): code='invalid_pk_value', params={'pk': value}, ) - variations_cache[key] = var.identity - cleaned_value.append(str(var.identity)) + variations_cache[key] = var.id + cleaned_value.append(str(var.id)) else: # An ItemVariation id was given cleaned_value.append(pk) diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 7caa943637..31cb2c7816 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -4,11 +4,11 @@ from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from pytz import common_timezones -from pretix.base.forms import SettingsForm, VersionedModelForm +from pretix.base.forms import I18nModelForm, SettingsForm from pretix.base.models import Event -class EventCreateForm(VersionedModelForm): +class EventCreateForm(I18nModelForm): error_messages = { 'duplicate_slug': _("You already used this slug for a different event. Please choose a new one."), } @@ -39,7 +39,7 @@ class EventCreateForm(VersionedModelForm): return slug -class EventUpdateForm(VersionedModelForm): +class EventUpdateForm(I18nModelForm): def clean_slug(self): return self.instance.slug diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index df2c762c2f..0b40c810c4 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -1,19 +1,18 @@ import copy from django import forms -from django.db import models from django.forms import BooleanField from django.utils.translation import ugettext_lazy as _ -from pretix.base.forms import I18nModelForm, VersionedModelForm +from pretix.base.forms import I18nModelForm from pretix.base.models import ( Item, ItemCategory, ItemVariation, Property, PropertyValue, Question, - Quota, Versionable, + Quota, ) from pretix.control.forms import TolerantFormsetModelForm, VariationsField -class CategoryForm(VersionedModelForm): +class CategoryForm(I18nModelForm): class Meta: model = ItemCategory localized_fields = '__all__' @@ -22,7 +21,7 @@ class CategoryForm(VersionedModelForm): ] -class PropertyForm(VersionedModelForm): +class PropertyForm(I18nModelForm): class Meta: model = Property localized_fields = '__all__' @@ -40,10 +39,10 @@ class PropertyValueForm(TolerantFormsetModelForm): ] -class QuestionForm(VersionedModelForm): +class QuestionForm(I18nModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['items'].queryset = self.instance.event.items.current.all() + self.fields['items'].queryset = self.instance.event.items.all() class Meta: model = Question @@ -60,10 +59,6 @@ class QuestionForm(VersionedModelForm): class QuotaForm(I18nModelForm): - """ - The form for quotas does not derive from VersionedModelForm as it does not - perform a 'full clone' as part of a performance optimization - """ def __init__(self, **kwargs): items = kwargs['items'] @@ -72,7 +67,7 @@ class QuotaForm(I18nModelForm): self.original_instance = copy.copy(instance) if instance else None super().__init__(**kwargs) - if hasattr(self, 'instance'): + if hasattr(self, 'instance') and self.instance.pk: active_items = set(self.instance.items.all()) active_variations = set(self.instance.variations.all()) else: @@ -81,36 +76,19 @@ class QuotaForm(I18nModelForm): for item in items: if len(item.properties.all()) > 0: - self.fields['item_%s' % item.identity] = VariationsField( + self.fields['item_%s' % item.id] = VariationsField( item, label=_("Activate for"), required=False, initial=active_variations ) - self.fields['item_%s' % item.identity].set_item(item) + self.fields['item_%s' % item.id].set_item(item) else: - self.fields['item_%s' % item.identity] = BooleanField( + self.fields['item_%s' % item.id] = BooleanField( label=_("Activate"), required=False, initial=(item in active_items) ) - def save(self, commit=True): - if self.instance.pk is not None and isinstance(self.instance, Versionable): - if self.has_changed() and self.original_instance: - new = self.instance - old = self.original_instance - clone = old.clone_shallow() - for f in type(self.instance)._meta.get_fields(): - if f.name not in ( - 'id', 'identity', 'version_start_date', 'version_end_date', - 'version_birth_date' - ) and not isinstance(f, ( - models.ManyToOneRel, models.ManyToManyRel, models.ManyToManyField - )): - setattr(clone, f.name, getattr(new, f.name)) - self.instance = clone - return super().save(commit) - class Meta: model = Quota localized_fields = '__all__' @@ -120,10 +98,10 @@ class QuotaForm(I18nModelForm): ] -class ItemFormGeneral(VersionedModelForm): +class ItemFormGeneral(I18nModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['category'].queryset = self.instance.event.categories.current.all() + self.fields['category'].queryset = self.instance.event.categories.all() class Meta: model = Item @@ -142,7 +120,7 @@ class ItemFormGeneral(VersionedModelForm): ] -class ItemVariationForm(VersionedModelForm): +class ItemVariationForm(I18nModelForm): class Meta: model = ItemVariation localized_fields = '__all__' diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py index f8f2e23ae8..0706f74572 100644 --- a/src/pretix/control/forms/orders.py +++ b/src/pretix/control/forms/orders.py @@ -1,8 +1,8 @@ -from pretix.base.forms import VersionedModelForm +from pretix.base.forms import I18nModelForm from pretix.base.models import Order -class ExtendForm(VersionedModelForm): +class ExtendForm(I18nModelForm): class Meta: model = Order fields = ['expires'] diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index b35742c7a2..a80ddc3e2c 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -1,11 +1,11 @@ from django import forms from django.utils.translation import ugettext_lazy as _ -from pretix.base.forms import VersionedModelForm +from pretix.base.forms import I18nModelForm from pretix.base.models import Organizer -class OrganizerForm(VersionedModelForm): +class OrganizerForm(I18nModelForm): error_messages = { 'duplicate_slug': _("This slug is already in use. Please choose a different one."), } diff --git a/src/pretix/control/middleware.py b/src/pretix/control/middleware.py index 35030d2e25..593f69c040 100644 --- a/src/pretix/control/middleware.py +++ b/src/pretix/control/middleware.py @@ -54,16 +54,16 @@ class PermissionMiddleware: return redirect_to_login( path, resolved_login_url, REDIRECT_FIELD_NAME) - request.user.events_cache = request.user.events.current.order_by( + request.user.events_cache = request.user.events.order_by( "organizer", "date_from").prefetch_related("organizer") if 'event' in url.kwargs and 'organizer' in url.kwargs: try: - request.event = Event.objects.current.filter( + request.event = Event.objects.filter( slug=url.kwargs['event'], permitted__id__exact=request.user.id, organizer__slug=url.kwargs['organizer'], ).select_related('organizer')[0] - request.eventperm = EventPermission.objects.current.get( + request.eventperm = EventPermission.objects.get( event=request.event, user=request.user ) @@ -73,7 +73,7 @@ class PermissionMiddleware: "have no permission to administrate it.")) elif 'organizer' in url.kwargs: try: - request.organizer = Organizer.objects.current.filter( + request.organizer = Organizer.objects.filter( slug=url.kwargs['organizer'], permitted__id__exact=request.user.id, )[0] diff --git a/src/pretix/control/permissions.py b/src/pretix/control/permissions.py index 1b31daa72f..a38d3ffa94 100644 --- a/src/pretix/control/permissions.py +++ b/src/pretix/control/permissions.py @@ -15,7 +15,7 @@ def event_permission_required(permission): # just a double check, should not ever happen raise PermissionDenied() try: - perm = EventPermission.objects.current.get( + perm = EventPermission.objects.get( event=request.event, user=request.user ) @@ -59,7 +59,7 @@ def organizer_permission_required(permission): # just a double check, should not ever happen raise PermissionDenied() try: - perm = OrganizerPermission.objects.current.get( + perm = OrganizerPermission.objects.get( organizer=request.organizer, user=request.user ) diff --git a/src/pretix/control/templates/pretixcontrol/item/base.html b/src/pretix/control/templates/pretixcontrol/item/base.html index 43eaadc09e..f19785ed1f 100644 --- a/src/pretix/control/templates/pretixcontrol/item/base.html +++ b/src/pretix/control/templates/pretixcontrol/item/base.html @@ -2,12 +2,12 @@ {% load i18n %} {% block title %}{{ object.name }} :: {% trans "Product" %}{% endblock %} {% block content %} - {% if object.identity %} + {% if object.id %}

    {% trans "Modify product:" %} {{ object.name }}

    {% else %}

    {% trans "Create product" %}

    @@ -15,7 +15,7 @@ You will be able to adjust further settings in the next step. {% endblocktrans %}

    {% endif %} - {% if object.identity and not object.quotas.exists %} + {% if object.id and not object.quotas.exists %}
    {% blocktrans trimmed %} Please note, that your product will not be available for sale until you added your diff --git a/src/pretix/control/templates/pretixcontrol/items/categories.html b/src/pretix/control/templates/pretixcontrol/items/categories.html index 143107b4c6..d195c1306f 100644 --- a/src/pretix/control/templates/pretixcontrol/items/categories.html +++ b/src/pretix/control/templates/pretixcontrol/items/categories.html @@ -18,12 +18,12 @@ {% for c in categories %} - {{ c.name }} + {{ c.name }} - - + + - + {% endfor %} diff --git a/src/pretix/control/templates/pretixcontrol/items/index.html b/src/pretix/control/templates/pretixcontrol/items/index.html index 32605674e8..8438040b5b 100644 --- a/src/pretix/control/templates/pretixcontrol/items/index.html +++ b/src/pretix/control/templates/pretixcontrol/items/index.html @@ -32,15 +32,15 @@ {% if not i.active %}{% endif %} {{ i.name }} + {% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i.name }} {% if not i.active %}{% endif %} {% if i.category %}{{ i.category.name }}{% endif %} - - + + - + {% endfor %} {% endfor %} diff --git a/src/pretix/control/templates/pretixcontrol/items/questions.html b/src/pretix/control/templates/pretixcontrol/items/questions.html index 38085446b2..34e0b6dd47 100644 --- a/src/pretix/control/templates/pretixcontrol/items/questions.html +++ b/src/pretix/control/templates/pretixcontrol/items/questions.html @@ -19,10 +19,10 @@ {% for q in questions %} {{ q.question }} + {% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}">{{ q.question }} {{ q.get_type_display }} + {% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"> {% endfor %} diff --git a/src/pretix/control/templates/pretixcontrol/items/quota.html b/src/pretix/control/templates/pretixcontrol/items/quota.html index d66ce72d8b..6f1b08038f 100644 --- a/src/pretix/control/templates/pretixcontrol/items/quota.html +++ b/src/pretix/control/templates/pretixcontrol/items/quota.html @@ -25,12 +25,12 @@ -
    +
    {% bootstrap_field item.field layout="horizontal" %} diff --git a/src/pretix/control/templates/pretixcontrol/items/quotas.html b/src/pretix/control/templates/pretixcontrol/items/quotas.html index a7d58540b7..de58d20dcf 100644 --- a/src/pretix/control/templates/pretixcontrol/items/quotas.html +++ b/src/pretix/control/templates/pretixcontrol/items/quotas.html @@ -20,19 +20,19 @@ {% for q in quotas %} - {{ q.name }} + {{ q.name }} {{ q.size }} {% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability %} - + {% endfor %} diff --git a/src/pretix/control/templates/pretixcontrol/orders/index.html b/src/pretix/control/templates/pretixcontrol/orders/index.html index f619bbbe4d..941e00774e 100644 --- a/src/pretix/control/templates/pretixcontrol/orders/index.html +++ b/src/pretix/control/templates/pretixcontrol/orders/index.html @@ -25,8 +25,8 @@ {% else %} - {% endif %} @@ -41,10 +41,10 @@ method="post" data-asynctask> {% csrf_token %} {% if line.variation %} - {% else %} - {% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index ebaec7c043..7eb1899525 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -65,7 +65,7 @@ {% if item.picture %} + data-lightbox="{{ item.id }}"> {{ item.name }} @@ -109,7 +109,7 @@
    + name="variation_{{ item.id }}_{{ var.variation.id }}">
    {% else %} {% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %} @@ -125,7 +125,7 @@ {% if item.picture %} + data-lightbox="{{ item.id }}"> {{ item.name }} @@ -144,7 +144,7 @@ {% if item.cached_availability.0 == 100 %}
    + max="{{ item.cached_availability.1 }}" name="item_{{ item.id }}">
    {% else %} {% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_modify.html b/src/pretix/presale/templates/pretixpresale/event/order_modify.html index 166b1d7e58..439b2c7a10 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order_modify.html +++ b/src/pretix/presale/templates/pretixpresale/event/order_modify.html @@ -15,7 +15,7 @@
    -