mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Removed multi-dimensional item variations [backwards-incompatible]
This commit is contained in:
@@ -120,7 +120,7 @@ class JSONExporter(BaseExporter):
|
||||
{
|
||||
'code': order.code,
|
||||
'status': order.status,
|
||||
'user': order.user.email,
|
||||
'user': order.email,
|
||||
'datetime': order.datetime,
|
||||
'payment_fee': order.payment_fee,
|
||||
'total': order.total,
|
||||
@@ -140,8 +140,7 @@ class JSONExporter(BaseExporter):
|
||||
} for position in order.positions.all()
|
||||
]
|
||||
} for order in
|
||||
self.event.orders.all().prefetch_related('positions', 'positions__answers').select_related(
|
||||
'user')
|
||||
self.event.orders.all().prefetch_related('positions', 'positions__answers')
|
||||
],
|
||||
'quotas': [
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models import Model, QuerySet, TextField
|
||||
from django.forms import BaseModelFormSet
|
||||
from django.utils import translation
|
||||
from django.utils.safestring import mark_safe
|
||||
from typing import Dict, List
|
||||
@@ -251,3 +252,29 @@ class I18nJSONEncoder(DjangoJSONEncoder):
|
||||
return {'type': obj.__class__.__name__, 'id': obj.id}
|
||||
else:
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class I18nFormSet(BaseModelFormSet):
|
||||
"""
|
||||
This is equivalent to a normal BaseModelFormset, but cares for the special needs
|
||||
of I18nForms (see there for more information).
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['event'] = self.event
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
event=self.event
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
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.base
|
||||
import pretix.base.models.items
|
||||
import pretix.base.models.orders
|
||||
|
||||
|
||||
def initial_user(apps, schema_editor):
|
||||
User = apps.get_model("pretixbase", "User")
|
||||
@@ -54,335 +44,5 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Users',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CachedFile',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('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(blank=True, null=True, upload_to=pretix.base.models.base.cachedfile_name)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CachedTicket',
|
||||
fields=[
|
||||
('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')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CartPosition',
|
||||
fields=[
|
||||
('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', 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.orders.ObjectWithAnswers, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('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', 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', 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',
|
||||
'verbose_name_plural': 'Events',
|
||||
'ordering': ('date_from', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventLock',
|
||||
fields=[
|
||||
('event', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('date', models.DateTimeField(auto_now=True)),
|
||||
('token', models.UUIDField(default=uuid.uuid4)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventPermission',
|
||||
fields=[
|
||||
('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',
|
||||
'verbose_name_plural': 'Event permissions',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('value', models.TextField()),
|
||||
('object', models.ForeignKey(to='pretixbase.Event', related_name='setting_objects')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('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(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(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',
|
||||
'verbose_name_plural': 'Products',
|
||||
'ordering': ('category__position', 'category', 'position'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemCategory',
|
||||
fields=[
|
||||
('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', models.ForeignKey(to='pretixbase.Event', related_name='categories')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Product category',
|
||||
'verbose_name_plural': 'Product categories',
|
||||
'ordering': ('position', 'id'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemVariation',
|
||||
fields=[
|
||||
('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',
|
||||
'verbose_name_plural': 'Product variations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('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', 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', 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',
|
||||
'verbose_name_plural': 'Orders',
|
||||
'ordering': ('-datetime',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderPosition',
|
||||
fields=[
|
||||
('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.orders.ObjectWithAnswers, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organizer',
|
||||
fields=[
|
||||
('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', 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',
|
||||
'verbose_name_plural': 'Organizers',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizerPermission',
|
||||
fields=[
|
||||
('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',
|
||||
'verbose_name_plural': 'Organizer permissions',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizerSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('value', models.TextField()),
|
||||
('object', models.ForeignKey(to='pretixbase.Organizer', related_name='setting_objects')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Property',
|
||||
fields=[
|
||||
('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', 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',
|
||||
'verbose_name_plural': 'Product properties',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PropertyValue',
|
||||
fields=[
|
||||
('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', models.ForeignKey(to='pretixbase.Property', related_name='values')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Property value',
|
||||
'verbose_name_plural': 'Property values',
|
||||
'ordering': ('position', 'id'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('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', 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',
|
||||
'verbose_name_plural': 'Questions',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionAnswer',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||
('answer', models.TextField()),
|
||||
('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')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quota',
|
||||
fields=[
|
||||
('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', 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',
|
||||
'verbose_name_plural': 'Quotas',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizer',
|
||||
name='permitted',
|
||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='pretixbase.OrganizerPermission', related_name='organizers'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='itemvariation',
|
||||
name='values',
|
||||
field=models.ForeignKey(to='pretixbase.PropertyValue', related_name='variations'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='category',
|
||||
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=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=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(to=settings.AUTH_USER_MODEL, through='pretixbase.EventPermission', related_name='events'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='event',
|
||||
field=models.ForeignKey(verbose_name='Event', to='pretixbase.Event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='item',
|
||||
field=models.ForeignKey(verbose_name='Item', to='pretixbase.Item'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='variation',
|
||||
field=models.ForeignKey(blank=True, to='pretixbase.ItemVariation', verbose_name='Variation', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cachedticket',
|
||||
name='order',
|
||||
field=models.ForeignKey(to='pretixbase.Order'),
|
||||
),
|
||||
migrations.RunPython(initial_user),
|
||||
]
|
||||
|
||||
@@ -1,23 +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.RemoveField(
|
||||
model_name='itemvariation',
|
||||
name='values',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='itemvariation',
|
||||
name='values',
|
||||
field=models.ManyToManyField(related_name='variations', to='pretixbase.PropertyValue'),
|
||||
),
|
||||
]
|
||||
347
src/pretix/base/migrations/0002_auto_20151213_1144.py
Normal file
347
src/pretix/base/migrations/0002_auto_20151213_1144.py
Normal file
@@ -0,0 +1,347 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-13 11:44
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.i18n
|
||||
import pretix.base.models.base
|
||||
import pretix.base.models.items
|
||||
import pretix.base.models.orders
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('pretixbase', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CachedFile',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('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(blank=True, null=True, upload_to=pretix.base.models.base.cachedfile_name)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CachedTicket',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('provider', models.CharField(max_length=255)),
|
||||
('cachedfile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.CachedFile')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CartPosition',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('cart_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Cart ID (e.g. session key)')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
||||
('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
|
||||
('expires', models.DateTimeField(verbose_name='Expiration date')),
|
||||
('attendee_name', models.CharField(blank=True, help_text='Empty, if this product is not an admission ticket', max_length=255, null=True, verbose_name='Attendee name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Cart positions',
|
||||
'verbose_name': 'Cart position',
|
||||
},
|
||||
bases=(pretix.base.models.orders.ObjectWithAnswers, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', pretix.base.i18n.I18nCharField(max_length=200, verbose_name='Name')),
|
||||
('slug', models.SlugField(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(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], verbose_name='Slug')),
|
||||
('currency', models.CharField(default='EUR', max_length=10, verbose_name='Default currency')),
|
||||
('date_from', models.DateTimeField(verbose_name='Event start time')),
|
||||
('date_to', models.DateTimeField(blank=True, null=True, verbose_name='Event end time')),
|
||||
('is_public', models.BooleanField(default=False, help_text="If selected, this event may show up on the ticket system's start page or an organization profile.", verbose_name='Visible in public lists')),
|
||||
('presale_end', models.DateTimeField(blank=True, help_text='No products will be sold after this date.', null=True, verbose_name='End of presale')),
|
||||
('presale_start', models.DateTimeField(blank=True, help_text='No products will be sold before this date.', null=True, verbose_name='Start of presale')),
|
||||
('plugins', models.TextField(blank=True, null=True, verbose_name='Plugins')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('date_from', 'name'),
|
||||
'verbose_name_plural': 'Events',
|
||||
'verbose_name': 'Event',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventLock',
|
||||
fields=[
|
||||
('event', models.CharField(max_length=36, primary_key=True, serialize=False)),
|
||||
('date', models.DateTimeField(auto_now=True)),
|
||||
('token', models.UUIDField(default=uuid.uuid4)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventPermission',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('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', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_perms', to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_perms', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Event permissions',
|
||||
'verbose_name': 'Event permission',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('value', models.TextField()),
|
||||
('object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='setting_objects', to='pretixbase.Event')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', pretix.base.i18n.I18nCharField(max_length=255, verbose_name='Item name')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('description', pretix.base.i18n.I18nTextField(blank=True, help_text='This is shown below the product name in lists.', null=True, verbose_name='Description')),
|
||||
('default_price', models.DecimalField(decimal_places=2, max_digits=7, null=True, verbose_name='Default price')),
|
||||
('tax_rate', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='Taxes included in percent')),
|
||||
('admission', models.BooleanField(default=False, help_text='Whether or not buying this product allows a person to enter your event', verbose_name='Is an admission ticket')),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('picture', models.ImageField(blank=True, null=True, upload_to=pretix.base.models.items.itempicture_upload_to, verbose_name='Product picture')),
|
||||
('available_from', models.DateTimeField(blank=True, help_text='This product will not be sold before the given date.', null=True, verbose_name='Available from')),
|
||||
('available_until', models.DateTimeField(blank=True, help_text='This product will not be sold after the given date.', null=True, verbose_name='Available until')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('category__position', 'category', 'position'),
|
||||
'verbose_name_plural': 'Products',
|
||||
'verbose_name': 'Product',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', pretix.base.i18n.I18nCharField(max_length=255, verbose_name='Category name')),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='categories', to='pretixbase.Event')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('position', 'id'),
|
||||
'verbose_name_plural': 'Product categories',
|
||||
'verbose_name': 'Product category',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemVariation',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', pretix.base.i18n.I18nCharField(max_length=255, verbose_name='Description')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('position', models.PositiveIntegerField(default=0, verbose_name='Position')),
|
||||
('default_price', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='Default price')),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variations', to='pretixbase.Item')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('position',),
|
||||
'verbose_name_plural': 'Product variations',
|
||||
'verbose_name': 'Product variation',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LogEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('datetime', models.DateTimeField(auto_now_add=True)),
|
||||
('action_type', models.CharField(max_length=255)),
|
||||
('data', models.TextField(default='{}')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.CharField(max_length=16, verbose_name='Order code')),
|
||||
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')),
|
||||
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
|
||||
('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(blank=True, null=True, verbose_name='Payment date')),
|
||||
('payment_provider', models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment provider')),
|
||||
('payment_fee', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Payment method fee')),
|
||||
('payment_info', models.TextField(blank=True, null=True, verbose_name='Payment information')),
|
||||
('payment_manual', models.BooleanField(default=False, verbose_name='Payment state was manually modified')),
|
||||
('total', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Total amount')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='pretixbase.Event', verbose_name='Event')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-datetime',),
|
||||
'verbose_name_plural': 'Orders',
|
||||
'verbose_name': 'Order',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderPosition',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
||||
('attendee_name', models.CharField(blank=True, help_text='Empty, if this product is not an admission ticket', max_length=255, null=True, verbose_name='Attendee name')),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='positions', to='pretixbase.Item', verbose_name='Item')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='positions', to='pretixbase.Order', verbose_name='Order')),
|
||||
('variation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.ItemVariation', verbose_name='Variation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Order positions',
|
||||
'verbose_name': 'Order position',
|
||||
},
|
||||
bases=(pretix.base.models.orders.ObjectWithAnswers, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organizer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('slug', models.SlugField(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(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], verbose_name='Slug')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
'verbose_name_plural': 'Organizers',
|
||||
'verbose_name': 'Organizer',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizerPermission',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('can_create_events', models.BooleanField(default=True, verbose_name='Can create events')),
|
||||
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_perms', to='pretixbase.Organizer')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizer_perms', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Organizer permissions',
|
||||
'verbose_name': 'Organizer permission',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizerSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('value', models.TextField()),
|
||||
('object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='setting_objects', to='pretixbase.Organizer')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('question', pretix.base.i18n.I18nTextField(verbose_name='Question')),
|
||||
('type', models.CharField(choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], max_length=5, verbose_name='Question type')),
|
||||
('required', models.BooleanField(default=False, verbose_name='Required question')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='pretixbase.Event')),
|
||||
('items', models.ManyToManyField(blank=True, help_text='This question will be asked to buyers of the selected products', related_name='questions', to='pretixbase.Item', verbose_name='Products')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Questions',
|
||||
'verbose_name': 'Question',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionAnswer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('answer', models.TextField()),
|
||||
('cartposition', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.CartPosition')),
|
||||
('orderposition', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.OrderPosition')),
|
||||
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.Question')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quota',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('size', models.PositiveIntegerField(blank=True, help_text='Leave empty for an unlimited number of tickets.', null=True, verbose_name='Total capacity')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotas', to='pretixbase.Event', verbose_name='Event')),
|
||||
('items', models.ManyToManyField(blank=True, related_name='quotas', to='pretixbase.Item', verbose_name='Item')),
|
||||
('variations', models.ManyToManyField(blank=True, related_name='quotas', to='pretixbase.ItemVariation', verbose_name='Variations')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Quotas',
|
||||
'verbose_name': 'Quota',
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizer',
|
||||
name='permitted',
|
||||
field=models.ManyToManyField(related_name='organizers', through='pretixbase.OrganizerPermission', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='items', to='pretixbase.ItemCategory', verbose_name='Category'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='items', to='pretixbase.Event', verbose_name='Event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='organizer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='events', to='pretixbase.Organizer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='permitted',
|
||||
field=models.ManyToManyField(related_name='events', through='pretixbase.EventPermission', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event', verbose_name='Event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='item',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Item', verbose_name='Item'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='variation',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.ItemVariation', verbose_name='Variation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cachedticket',
|
||||
name='order',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Order'),
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-12 13:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('pretixbase', '0002_auto_20151212_1123'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LogEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('datetime', models.DateTimeField(auto_now_add=True)),
|
||||
('action_type', models.CharField(max_length=255)),
|
||||
('data', models.TextField(default='{}')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -2,8 +2,7 @@ from .auth import User
|
||||
from .base import CachedFile, cachedfile_name
|
||||
from .event import Event, EventLock, EventPermission, EventSetting
|
||||
from .items import (
|
||||
Item, ItemCategory, ItemVariation, Property, PropertyValue, Question,
|
||||
Quota, VariationsField, itempicture_upload_to,
|
||||
Item, ItemCategory, ItemVariation, Question, Quota, itempicture_upload_to,
|
||||
)
|
||||
from .log import LogEntry
|
||||
from .orders import (
|
||||
@@ -14,8 +13,7 @@ from .organizer import Organizer, OrganizerPermission, OrganizerSetting
|
||||
|
||||
__all__ = [
|
||||
'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',
|
||||
'generate_secret', 'LogEntry'
|
||||
'ItemCategory', 'Item', 'ItemVariation', 'Question', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer',
|
||||
'ObjectWithAnswers', 'OrderPosition', 'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock',
|
||||
'cachedfile_name', 'itempicture_upload_to', 'generate_secret', 'LogEntry'
|
||||
]
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from itertools import product
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q, Case, Count, Sum, When
|
||||
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 typing import Tuple
|
||||
|
||||
from pretix.base.i18n import I18nCharField, I18nTextField
|
||||
from pretix.base.models.base import LoggedModel
|
||||
|
||||
from ..types import VariationDict
|
||||
from .event import Event
|
||||
|
||||
|
||||
@@ -196,117 +194,6 @@ class Item(LoggedModel):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_all_variations(self, use_cache: bool=False) -> List[VariationDict]:
|
||||
"""
|
||||
This method returns a list containing all variations of this
|
||||
item. The list contains one VariationDict per variation, where
|
||||
the Proprty IDs are keys and the PropertyValue objects are
|
||||
values. If an ItemVariation object exists, it is available in
|
||||
the dictionary via the special key 'variation'.
|
||||
|
||||
VariationDicts differ from dicts only by specifying some extra
|
||||
methods.
|
||||
|
||||
:param use_cache: If this parameter is set to ``True``, a second call to this method
|
||||
on the same model instance won't query the database again but return
|
||||
the previous result again.
|
||||
:type use_cache: bool
|
||||
"""
|
||||
if use_cache and hasattr(self, '_get_all_variations_cache'):
|
||||
return self._get_all_variations_cache
|
||||
|
||||
all_variations = self.variations.all().prefetch_related("values")
|
||||
all_properties = self.properties.all().prefetch_related("values")
|
||||
variations_cache = {}
|
||||
for var in all_variations:
|
||||
key = []
|
||||
for v in var.values.all():
|
||||
key.append((v.prop_id, v.id))
|
||||
key = tuple(sorted(key))
|
||||
variations_cache[key] = var
|
||||
|
||||
result = []
|
||||
for comb in product(*[prop.values.all() for prop in all_properties]):
|
||||
if len(comb) == 0:
|
||||
result.append(VariationDict())
|
||||
continue
|
||||
key = []
|
||||
var = VariationDict()
|
||||
for v in comb:
|
||||
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]
|
||||
result.append(var)
|
||||
|
||||
self._get_all_variations_cache = result
|
||||
return result
|
||||
|
||||
def _get_all_generated_variations(self):
|
||||
propids = set([p.id for p in self.properties.all()])
|
||||
if len(propids) == 0:
|
||||
variations = [VariationDict()]
|
||||
else:
|
||||
all_variations = list(
|
||||
self.variations.annotate(
|
||||
qc=Count('quotas')
|
||||
).filter(qc__gt=0).prefetch_related(
|
||||
"values", "values__prop", "quotas__event"
|
||||
)
|
||||
)
|
||||
variations = []
|
||||
for var in all_variations:
|
||||
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.id for v in values]) != propids:
|
||||
continue
|
||||
vardict = VariationDict()
|
||||
for v in values:
|
||||
vardict[v.prop.id] = v
|
||||
vardict['variation'] = var
|
||||
variations.append(vardict)
|
||||
return variations
|
||||
|
||||
def get_all_available_variations(self, use_cache: bool=False):
|
||||
"""
|
||||
This method returns a list of all variations which are theoretically
|
||||
possible for sale. It DOES only return variations which DO have an ItemVariation
|
||||
object, as all variations without one CAN NOT be part of a Quota and therefore can
|
||||
never be available for sale. The only exception is the empty variation
|
||||
for items without properties, which never has an ItemVariation object.
|
||||
|
||||
This DOES NOT take into account quotas itself. Use ``is_available`` on the
|
||||
ItemVariation objects (or the Item it self, if it does not have variations) to
|
||||
determine availability by the terms of quotas.
|
||||
|
||||
It is recommended to call::
|
||||
|
||||
.prefetch_related('properties', 'variations__values__prop')
|
||||
|
||||
when retrieving Item objects you are going to use this method on.
|
||||
"""
|
||||
if use_cache and hasattr(self, '_get_all_available_variations_cache'):
|
||||
return self._get_all_available_variations_cache
|
||||
|
||||
variations = self._get_all_generated_variations()
|
||||
|
||||
for i, var in enumerate(variations):
|
||||
var['available'] = var['variation'].active if 'variation' in var else True
|
||||
if 'variation' in var:
|
||||
if var['variation'].default_price is not None:
|
||||
var['price'] = var['variation'].default_price
|
||||
else:
|
||||
var['price'] = self.default_price
|
||||
else:
|
||||
var['price'] = self.default_price
|
||||
|
||||
variations = [var for var in variations if var['available']]
|
||||
|
||||
self._get_all_available_variations_cache = variations
|
||||
return variations
|
||||
|
||||
def check_quotas(self):
|
||||
"""
|
||||
This method is used to determine whether this Item is currently available
|
||||
@@ -314,130 +201,24 @@ class Item(LoggedModel):
|
||||
|
||||
:returns: any of the return codes of :py:meth:`Quota.availability()`.
|
||||
|
||||
:raises ValueError: if you call this on an item which has properties associated with it.
|
||||
:raises ValueError: if you call this on an item which has variations associated with it.
|
||||
Please use the method on the ItemVariation object you are interested in.
|
||||
"""
|
||||
if self.properties.count() > 0: # NOQA
|
||||
raise ValueError('Do not call this directly on items which have properties '
|
||||
if self.variations.count() > 0: # NOQA
|
||||
raise ValueError('Do not call this directly on items which have variations '
|
||||
'but call this on their ItemVariation objects')
|
||||
return min([q.availability() for q in self.quotas.all()],
|
||||
key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
|
||||
|
||||
|
||||
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'.
|
||||
|
||||
:param event: The event this belongs to
|
||||
:type event: Event
|
||||
:param name: The name of this property.
|
||||
:type name: str
|
||||
"""
|
||||
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
related_name="properties"
|
||||
)
|
||||
item = models.ForeignKey(
|
||||
Item, related_name='properties', null=True, blank=True
|
||||
)
|
||||
name = I18nCharField(
|
||||
max_length=250,
|
||||
verbose_name=_("Property name")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Product property")
|
||||
verbose_name_plural = _("Product properties")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.get_cache().clear()
|
||||
|
||||
|
||||
class PropertyValue(models.Model):
|
||||
"""
|
||||
A value of a property. If the property would be 'T-Shirt size',
|
||||
this could be 'M' or 'L'.
|
||||
|
||||
:param prop: The property this value is a valid option for.
|
||||
:type prop: Property
|
||||
:param value: The value, as a human-readable string
|
||||
:type value: str
|
||||
:param position: An integer, used for sorting
|
||||
:type position: int
|
||||
"""
|
||||
|
||||
prop = models.ForeignKey(
|
||||
Property,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="values"
|
||||
)
|
||||
value = I18nCharField(
|
||||
max_length=250,
|
||||
verbose_name=_("Value"),
|
||||
)
|
||||
position = models.IntegerField(
|
||||
default=0
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Property value")
|
||||
verbose_name_plural = _("Property values")
|
||||
ordering = ("position", "id")
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s" % (self.prop.name, self.value)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
if self.prop:
|
||||
self.prop.event.get_cache().clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
if self.prop:
|
||||
self.prop.event.get_cache().clear()
|
||||
|
||||
@property
|
||||
def sortkey(self) -> Tuple[int, datetime]:
|
||||
return self.position, self.id
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return self.sortkey < other.sortkey
|
||||
|
||||
|
||||
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'
|
||||
and your properties are 'Size' and 'Color', then an example for an
|
||||
variation would be 'T-Shirt XL read'.
|
||||
|
||||
Attention: _ALL_ combinations of PropertyValues _ALWAYS_ exist,
|
||||
even if there is no ItemVariation object for them! ItemVariation objects
|
||||
do NOT prove existance, they are only available to make it possible
|
||||
to override default values (like the price) for certain combinations
|
||||
of property values. However, appropriate ItemVariation objects will be
|
||||
created as soon as you add your variations to a quota.
|
||||
|
||||
They also allow to explicitly EXCLUDE certain combinations of property
|
||||
values by creating an ItemVariation object for them with active set to
|
||||
False.
|
||||
A variation of a product. For example, if your item is 'T-Shirt'
|
||||
then an example for a variation would be 'T-Shirt XL'.
|
||||
|
||||
:param item: The item this variation belongs to
|
||||
:type item: Item
|
||||
:param values: A set of ``PropertyValue`` objects defining this variation
|
||||
:param value: A string defining this variation
|
||||
:param active: Whether this value is to be sold.
|
||||
:type active: bool
|
||||
:param default_price: This variation's default price
|
||||
@@ -447,14 +228,18 @@ class ItemVariation(models.Model):
|
||||
Item,
|
||||
related_name='variations'
|
||||
)
|
||||
values = models.ManyToManyField(
|
||||
PropertyValue,
|
||||
related_name='variations',
|
||||
value = I18nCharField(
|
||||
max_length=255,
|
||||
verbose_name=_('Description')
|
||||
)
|
||||
active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Active"),
|
||||
)
|
||||
position = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name=_("Position")
|
||||
)
|
||||
default_price = models.DecimalField(
|
||||
decimal_places=2, max_digits=7,
|
||||
null=True, blank=True,
|
||||
@@ -464,9 +249,10 @@ class ItemVariation(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Product variation")
|
||||
verbose_name_plural = _("Product variations")
|
||||
ordering = ("position", "id")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.to_variation_dict())
|
||||
return str(self.value)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
@@ -488,56 +274,10 @@ class ItemVariation(models.Model):
|
||||
return min([q.availability() for q in self.quotas.all()],
|
||||
key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
|
||||
|
||||
def to_variation_dict(self) -> VariationDict:
|
||||
"""
|
||||
:return: a :py:class:`VariationDict` representing this variation.
|
||||
"""
|
||||
vd = VariationDict()
|
||||
for v in self.values.all():
|
||||
vd[v.prop.id] = v
|
||||
vd['variation'] = self
|
||||
return vd
|
||||
|
||||
def add_values_from_string(self, pk):
|
||||
"""
|
||||
Add values to this ItemVariation using a serialized string of the form
|
||||
``property-id:value-id,ṗroperty-id:value-id``
|
||||
"""
|
||||
for pair in pk.split(","):
|
||||
prop, value = pair.split(":")
|
||||
self.values.add(
|
||||
PropertyValue.objects.get(
|
||||
id=value,
|
||||
prop_id=prop
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VariationsField(models.ManyToManyField):
|
||||
"""
|
||||
This is a ManyToManyField using the pretixcontrol.views.forms.VariationsField
|
||||
form field by default.
|
||||
"""
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
from pretix.control.forms import VariationsField as FVariationsField
|
||||
from django.db.models.fields.related import RelatedField
|
||||
|
||||
defaults = {
|
||||
'form_class': FVariationsField,
|
||||
# We don't need a queryset
|
||||
'queryset': ItemVariation.objects.none(),
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
# If initial is passed in, it's a list of related objects, but the
|
||||
# MultipleChoiceField takes a list of IDs.
|
||||
if defaults.get('initial') is not None:
|
||||
initial = defaults['initial']
|
||||
if callable(initial):
|
||||
initial = initial()
|
||||
defaults['initial'] = [i.id for i in initial]
|
||||
# Skip ManyToManyField in dependency chain
|
||||
return super(RelatedField, self).formfield(**defaults)
|
||||
def __lt__(self, other):
|
||||
if self.position == other.position:
|
||||
return self.id < other.id
|
||||
return self.position < other.position
|
||||
|
||||
|
||||
class Question(LoggedModel):
|
||||
@@ -686,7 +426,7 @@ class Quota(LoggedModel):
|
||||
related_name="quotas",
|
||||
blank=True
|
||||
)
|
||||
variations = VariationsField(
|
||||
variations = models.ManyToManyField(
|
||||
ItemVariation,
|
||||
related_name="quotas",
|
||||
blank=True,
|
||||
|
||||
@@ -77,7 +77,7 @@ def _add_new_items(event: Event, items: List[Tuple[int, Optional[int], int]],
|
||||
variations_query = ItemVariation.objects.filter(
|
||||
item__event=event,
|
||||
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")
|
||||
).select_related("item", "item__event").prefetch_related("quotas")
|
||||
variations_cache = {v.id: v for v in variations_query}
|
||||
|
||||
for i in items:
|
||||
|
||||
@@ -31,8 +31,6 @@ def tuplesum(tuples: Iterable[Tuple]) -> Tuple:
|
||||
def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
||||
items = event.items.all().select_related(
|
||||
'category', # for re-grouping
|
||||
).prefetch_related(
|
||||
'properties', # for .get_all_available_variations()
|
||||
).order_by('category__position', 'category_id', 'name')
|
||||
|
||||
num_total = {
|
||||
@@ -73,22 +71,27 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
|
||||
}
|
||||
|
||||
for item in items:
|
||||
item.all_variations = sorted(item.get_all_variations(),
|
||||
key=lambda vd: vd.ordered_values())
|
||||
for var in item.all_variations:
|
||||
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)
|
||||
item.num_pending = tuplesum(var.num_pending for var in item.all_variations)
|
||||
item.num_cancelled = tuplesum(var.num_cancelled for var in item.all_variations)
|
||||
item.num_refunded = tuplesum(var.num_refunded for var in item.all_variations)
|
||||
item.num_paid = tuplesum(var.num_paid for var in item.all_variations)
|
||||
item.all_variations = list(item.variations.all())
|
||||
item.has_variations = (len(item.all_variations) > 0)
|
||||
if item.has_variations:
|
||||
for var in item.all_variations:
|
||||
variid = var.id
|
||||
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.num_total = tuplesum(var.num_total for var in item.all_variations)
|
||||
item.num_pending = tuplesum(var.num_pending for var in item.all_variations)
|
||||
item.num_cancelled = tuplesum(var.num_cancelled for var in item.all_variations)
|
||||
item.num_refunded = tuplesum(var.num_refunded for var in item.all_variations)
|
||||
item.num_paid = tuplesum(var.num_paid for var in item.all_variations)
|
||||
else:
|
||||
item.num_total = num_total.get((item.id, None), (0, 0))
|
||||
item.num_pending = num_pending.get((item.id, None), (0, 0))
|
||||
item.num_cancelled = num_cancelled.get((item.id, None), (0, 0))
|
||||
item.num_refunded = num_refunded.get((item.id, None), (0, 0))
|
||||
item.num_paid = num_paid.get((item.id, None), (0, 0))
|
||||
|
||||
nonecat = ItemCategory(name=_('Uncategorized'))
|
||||
# Regroup those by category
|
||||
@@ -106,6 +109,7 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
|
||||
|
||||
for c in items_by_category:
|
||||
c[0].num_total = tuplesum(item.num_total for item in c[1])
|
||||
print(c[1], c[0].num_total, [item.num_total for item in c[1]])
|
||||
c[0].num_pending = tuplesum(item.num_pending for item in c[1])
|
||||
c[0].num_cancelled = tuplesum(item.num_cancelled for item in c[1])
|
||||
c[0].num_refunded = tuplesum(item.num_refunded for item in c[1])
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
|
||||
class VariationDict(dict):
|
||||
"""
|
||||
A VariationDict object behaves exactle the same as the Python built-in
|
||||
``dict`` does, but adds some special methods. It is used for the dicts
|
||||
returned by ``Item.get_all_variations()`` to avoid duplicate code in the
|
||||
code calling this method.
|
||||
"""
|
||||
IGNORE_KEYS = ('variation', 'key', 'available', 'price')
|
||||
|
||||
def relevant_items(self) -> Iterable[Tuple]:
|
||||
"""
|
||||
Iterate over all items with numeric keys.
|
||||
|
||||
This is in use because the variation dictionaries use property ids
|
||||
as key and have some special keys like 'variation'.
|
||||
"""
|
||||
return (i for i in self.items() if i[0] not in self.IGNORE_KEYS)
|
||||
|
||||
def relevant_values(self) -> Iterable["PropertyValue"]:
|
||||
"""
|
||||
Iterate over all values with numeric keys.
|
||||
|
||||
This is in use because the variation dictionaries use property ids
|
||||
as key and have some special keys like 'variation'.
|
||||
"""
|
||||
return (i[1] for i in self.items() if i[0] not in self.IGNORE_KEYS)
|
||||
|
||||
def identify(self) -> str:
|
||||
"""
|
||||
Build a simple and unique identifier for this dict. This can be any string
|
||||
used to compare one VariationDict to others.
|
||||
|
||||
In the current implementation, it is a string containing a list of
|
||||
the PropertyValue id's, sorted by the Property id's and is therefore
|
||||
unique among one item.
|
||||
"""
|
||||
def order_key(i):
|
||||
return i[0]
|
||||
return ",".join((
|
||||
str(v[1].pk) for v in sorted(self.relevant_items(), key=order_key)
|
||||
))
|
||||
|
||||
def key(self) -> str:
|
||||
"""
|
||||
Build an identifier for this dict which exactly specifies the combination
|
||||
for this variation without any doubt. This can be used to "talk" about a
|
||||
variation in network communication.
|
||||
|
||||
In the current implementation, it is a string containing a list of
|
||||
the propertyid:valueid tuples without any specific order and is therefore
|
||||
not useful to compare two VariationDicts for equality.
|
||||
"""
|
||||
return ",".join((
|
||||
str(v[0]) + ":" + str(v[1].pk) for v in self.relevant_items()
|
||||
))
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is type(self):
|
||||
return self.identify() == other.identify()
|
||||
else:
|
||||
return super().__eq__(other)
|
||||
|
||||
def empty(self) -> bool:
|
||||
"""
|
||||
Returns true, if this VariationDict does not contain any "real" data like
|
||||
references to PropertyValues, but only "metadata".
|
||||
"""
|
||||
return not next(self.relevant_items(), False)
|
||||
|
||||
def ordered_values(self) -> List["ItemVariation"]:
|
||||
"""
|
||||
Returns a list of values ordered by their keys
|
||||
"""
|
||||
return [
|
||||
i[1] for i
|
||||
in sorted(
|
||||
[it for it in self.relevant_items()],
|
||||
key=lambda i: i[0]
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return " – ".join([str(v.value) for v in self.ordered_values()])
|
||||
|
||||
def copy(self) -> "VariationDict":
|
||||
"""
|
||||
Return a one-level deep copy of this object (create a new
|
||||
VariationDict but make a shallow copy of the dict inside it).
|
||||
"""
|
||||
new = VariationDict()
|
||||
for k, v in self.items():
|
||||
new[k] = v
|
||||
return new
|
||||
Reference in New Issue
Block a user