diff --git a/.travis.sh b/.travis.sh index 11a090a55..15691b539 100755 --- a/.travis.sh +++ b/.travis.sh @@ -15,13 +15,13 @@ if [ "$PRETIX_CONFIG_FILE" == "tests/travis_postgres.cfg" ]; then fi if [ "$1" == "style" ]; then - XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt + XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt cd src flake8 . isort -c -rc -df . fi if [ "$1" == "doctests" ]; then - XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt -r src/requirements/py34.txt + XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt cd doc make doctest fi @@ -39,21 +39,21 @@ if [ "$1" == "translation-spelling" ]; then potypo fi if [ "$1" == "tests" ]; then - pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt pytest-xdist + pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt pytest-xdist cd src python manage.py check make all compress py.test --reruns 5 -n 2 tests fi if [ "$1" == "tests-cov" ]; then - pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt + pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt cd src python manage.py check make all compress coverage run -m py.test --reruns 5 tests && codecov fi if [ "$1" == "plugins" ]; then - pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt -r src/requirements/py34.txt + pip3 install -r src/requirements.txt -Ur src/requirements/dev.txt cd src python setup.py develop make all compress diff --git a/.travis.yml b/.travis.yml index f68b87204..e12f21945 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,20 +18,10 @@ matrix: env: JOB=tests-cov PRETIX_CONFIG_FILE=tests/travis_postgres.cfg - python: 3.6 env: JOB=style - - python: 3.4 - env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg - python: 3.5 env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg - - python: 3.4 - env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg - - python: 3.5 - env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg - python: 3.6 env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg - - python: 3.4 - env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg - - python: 3.5 - env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg - python: 3.6 env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg - python: 3.6 diff --git a/doc/admin/installation/manual_smallscale.rst b/doc/admin/installation/manual_smallscale.rst index d301ea999..816ba0fa0 100644 --- a/doc/admin/installation/manual_smallscale.rst +++ b/doc/admin/installation/manual_smallscale.rst @@ -121,8 +121,7 @@ command if you're running PostgreSQL:: (venv)$ pip3 install "pretix[mysql]" gunicorn -If you are running Python 3.4, you also need to ``pip3 install typing``. This is not required on 3.5 or newer. -You can find out your Python version using ``python -V``. +Note that you need Python 3.5 or newer. You can find out your Python version using ``python -V``. We also need to create a data directory:: diff --git a/doc/development/api/customview.rst b/doc/development/api/customview.rst index 710b804b2..fbf1f6b90 100644 --- a/doc/development/api/customview.rst +++ b/doc/development/api/customview.rst @@ -64,7 +64,7 @@ Similarly, there is ``organizer_permission_required`` and ``OrganizerPermissionR event-related views, there is also a signal that allows you to add the view to the event navigation like this:: - from django.core.urlresolvers import resolve, reverse + from django.urls import resolve, reverse from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from pretix.control.signals import nav_event diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 51bc36760..028a10c1f 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -18,7 +18,7 @@ External Dependencies --------------------- Your should install the following on your system: -* Python 3.4 or newer +* Python 3.5 or newer * ``pip`` for Python 3 (Debian package: ``python3-pip``) * ``python-dev`` for Python 3 (Debian package: ``python3-dev``) * ``libffi`` (Debian package: ``libffi-dev``) @@ -54,10 +54,6 @@ The first thing you need are all the main application's dependencies:: cd src/ pip3 install -r requirements.txt -r requirements/dev.txt -If you are working with Python 3.4, you will also need (you can skip this for Python 3.5+):: - - pip3 install -r requirements/py34.txt - Next, you need to copy the SCSS files from the source folder to the STATIC_ROOT directory:: python manage.py collectstatic --noinput diff --git a/src/pretix/api/migrations/0001_initial.py b/src/pretix/api/migrations/0001_initial.py index d56fb19ef..b768aebbf 100644 --- a/src/pretix/api/migrations/0001_initial.py +++ b/src/pretix/api/migrations/0001_initial.py @@ -46,7 +46,7 @@ class Migration(migrations.Migration): ('updated', models.DateTimeField(auto_now=True)), ('name', models.CharField(max_length=255, verbose_name='Application name')), ('redirect_uris', models.TextField(help_text='Allowed URIs list, space separated', - validators=[oauth2_provider.validators.validate_uris], + validators=[oauth2_provider.validators.URIValidator], verbose_name='Redirection URIs')), ('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, diff --git a/src/pretix/api/models.py b/src/pretix/api/models.py index 189d04efb..7a62d0e66 100644 --- a/src/pretix/api/models.py +++ b/src/pretix/api/models.py @@ -11,13 +11,13 @@ from oauth2_provider.models import ( AbstractAccessToken, AbstractApplication, AbstractGrant, AbstractRefreshToken, ) -from oauth2_provider.validators import validate_uris +from oauth2_provider.validators import URIValidator class OAuthApplication(AbstractApplication): name = models.CharField(verbose_name=_("Application name"), max_length=255, blank=False) redirect_uris = models.TextField( - blank=False, validators=[validate_uris], + blank=False, validators=[URIValidator], verbose_name=_("Redirection URIs"), help_text=_("Allowed URIs list, space separated") ) diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 964d56862..f5931b20a 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -32,7 +32,7 @@ class CheckinListViewSet(viewsets.ModelViewSet): serializer_class = CheckinListSerializer queryset = CheckinList.objects.none() filter_backends = (DjangoFilterBackend,) - filter_class = CheckinListFilter + filterset_class = CheckinListFilter permission = 'can_view_orders' write_permission = 'can_change_event_settings' @@ -175,7 +175,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): }, } - filter_class = CheckinOrderPositionFilter + filterset_class = CheckinOrderPositionFilter permission = 'can_view_orders' write_permission = 'can_change_orders' diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index cb92fc110..d68512902 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -129,7 +129,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ReadOnlyModelViewSet): serializer_class = SubEventSerializer queryset = ItemCategory.objects.none() filter_backends = (DjangoFilterBackend, filters.OrderingFilter) - filter_class = SubEventFilter + filterset_class = SubEventFilter def get_queryset(self): return self.request.event.subevents.prefetch_related( diff --git a/src/pretix/api/views/item.py b/src/pretix/api/views/item.py index 27585f724..b89d5c8e3 100644 --- a/src/pretix/api/views/item.py +++ b/src/pretix/api/views/item.py @@ -41,7 +41,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering_fields = ('id', 'position') ordering = ('position', 'id') - filter_class = ItemFilter + filterset_class = ItemFilter permission = 'can_change_items' write_permission = 'can_change_items' @@ -207,7 +207,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet): serializer_class = ItemCategorySerializer queryset = ItemCategory.objects.none() filter_backends = (DjangoFilterBackend, OrderingFilter) - filter_class = ItemCategoryFilter + filterset_class = ItemCategoryFilter ordering_fields = ('id', 'position') ordering = ('position', 'id') permission = 'can_change_items' @@ -261,7 +261,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): serializer_class = QuestionSerializer queryset = Question.objects.none() filter_backends = (DjangoFilterBackend, OrderingFilter) - filter_class = QuestionFilter + filterset_class = QuestionFilter ordering_fields = ('id', 'position') ordering = ('position', 'id') permission = 'can_change_items' @@ -359,7 +359,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): serializer_class = QuotaSerializer queryset = Quota.objects.none() filter_backends = (DjangoFilterBackend, OrderingFilter,) - filter_class = QuotaFilter + filterset_class = QuotaFilter ordering_fields = ('id', 'size') ordering = ('id',) permission = 'can_change_items' diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 90c40061f..7e3112543 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -45,10 +45,10 @@ from pretix.base.signals import order_placed, register_ticket_outputs class OrderFilter(FilterSet): - email = django_filters.CharFilter(name='email', lookup_expr='iexact') - code = django_filters.CharFilter(name='code', lookup_expr='iexact') - status = django_filters.CharFilter(name='status', lookup_expr='iexact') - modified_since = django_filters.IsoDateTimeFilter(name='last_modified', lookup_expr='gte') + email = django_filters.CharFilter(field_name='email', lookup_expr='iexact') + code = django_filters.CharFilter(field_name='code', lookup_expr='iexact') + status = django_filters.CharFilter(field_name='status', lookup_expr='iexact') + modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte') class Meta: model = Order @@ -61,7 +61,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering = ('datetime',) ordering_fields = ('datetime', 'code', 'status') - filter_class = OrderFilter + filterset_class = OrderFilter lookup_field = 'code' permission = 'can_view_orders' write_permission = 'can_change_orders' @@ -307,7 +307,7 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): class OrderPositionFilter(FilterSet): - order = django_filters.CharFilter(name='order', lookup_expr='code__iexact') + order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs') attendee_name = django_filters.CharFilter(method='attendee_name_qs') search = django_filters.CharFilter(method='search_qs') @@ -345,7 +345,7 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering = ('order__datetime', 'positionid') ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',) - filter_class = OrderPositionFilter + filterset_class = OrderPositionFilter permission = 'can_view_orders' def get_queryset(self): @@ -590,7 +590,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): class InvoiceFilter(FilterSet): refers = django_filters.CharFilter(method='refers_qs') number = django_filters.CharFilter(method='nr_qs') - order = django_filters.CharFilter(name='order', lookup_expr='code__iexact') + order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') def refers_qs(self, queryset, name, value): return queryset.annotate( @@ -617,7 +617,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering = ('nr',) ordering_fields = ('nr', 'date') - filter_class = InvoiceFilter + filterset_class = InvoiceFilter permission = 'can_view_orders' lookup_url_kwarg = 'number' lookup_field = 'nr' diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index b2ab942a0..b23f03f7b 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -12,7 +12,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet): lookup_url_kwarg = 'organizer' def get_queryset(self): - if self.request.user.is_authenticated(): + if self.request.user.is_authenticated: if self.request.user.has_active_staff_session(self.request.session.session_key): return Organizer.objects.all() elif isinstance(self.request.auth, OAuthAccessToken): diff --git a/src/pretix/api/views/voucher.py b/src/pretix/api/views/voucher.py index 73e7f8cf1..e31157cc2 100644 --- a/src/pretix/api/views/voucher.py +++ b/src/pretix/api/views/voucher.py @@ -34,7 +34,7 @@ class VoucherViewSet(viewsets.ModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering = ('id',) ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value') - filter_class = VoucherFilter + filterset_class = VoucherFilter permission = 'can_view_vouchers' write_permission = 'can_change_vouchers' diff --git a/src/pretix/api/views/waitinglist.py b/src/pretix/api/views/waitinglist.py index 36f6e8bf7..cf649dc43 100644 --- a/src/pretix/api/views/waitinglist.py +++ b/src/pretix/api/views/waitinglist.py @@ -28,7 +28,7 @@ class WaitingListViewSet(viewsets.ModelViewSet): filter_backends = (DjangoFilterBackend, OrderingFilter) ordering = ('created',) ordering_fields = ('id', 'created', 'email', 'item') - filter_class = WaitingListFilter + filterset_class = WaitingListFilter permission = 'can_view_orders' write_permission = 'can_change_orders' diff --git a/src/pretix/base/forms/auth.py b/src/pretix/base/forms/auth.py index 8162d3173..f4f92d4ce 100644 --- a/src/pretix/base/forms/auth.py +++ b/src/pretix/base/forms/auth.py @@ -39,7 +39,7 @@ class LoginForm(forms.Form): password = self.cleaned_data.get('password') if email and password: - self.user_cache = authenticate(email=email.lower(), password=password) + self.user_cache = authenticate(request=self.request, email=email.lower(), password=password) if self.user_cache is None: raise forms.ValidationError( self.error_messages['invalid_login'], diff --git a/src/pretix/base/middleware.py b/src/pretix/base/middleware.py index ab0324c4d..a2f1fedf2 100644 --- a/src/pretix/base/middleware.py +++ b/src/pretix/base/middleware.py @@ -3,8 +3,8 @@ from urllib.parse import urlsplit import pytz from django.conf import settings -from django.core.urlresolvers import get_script_prefix from django.http import HttpRequest, HttpResponse +from django.urls import get_script_prefix from django.utils import timezone, translation from django.utils.cache import patch_vary_headers from django.utils.deprecation import MiddlewareMixin diff --git a/src/pretix/base/migrations/0098_auto_20180731_1243.py b/src/pretix/base/migrations/0098_auto_20180731_1243.py new file mode 100644 index 000000000..bf04b2306 --- /dev/null +++ b/src/pretix/base/migrations/0098_auto_20180731_1243.py @@ -0,0 +1,56 @@ +# Generated by Django 2.0.7 on 2018-07-31 12:43 + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import pretix.base.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0097_auto_20180722_0804'), + ] + + operations = [ + migrations.AlterModelOptions( + name='logentry', + options={'ordering': ('-datetime', '-id')}, + ), + migrations.AlterField( + model_name='orderpayment', + name='fee', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='payments', to='pretixbase.OrderFee'), + ), + migrations.AlterField( + model_name='organizer', + name='slug', + field=models.SlugField(help_text='Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used once. This is being used in URLs to refer to your organizer accounts and your events.', unique=True, validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$'), pretix.base.validators.OrganizerSlugBlacklistValidator()], verbose_name='Short form'), + ), + migrations.AlterField( + model_name='staffsession', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='staffsessionauditlog', + name='impersonating', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='staffsessionauditlog', + name='session', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='logs', to='pretixbase.StaffSession'), + ), + migrations.AlterField( + model_name='user', + name='locale', + field=models.CharField(choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)'), ('nl', 'Dutch'), ('da', 'Danish'), ('tr', 'Turkish'), ('pt-br', 'Portuguese (Brazil)')], default='en', max_length=50, verbose_name='Language'), + ), + migrations.AlterUniqueTogether( + name='event', + unique_together={('organizer', 'slug')}, + ), + ] diff --git a/src/pretix/base/models/auth.py b/src/pretix/base/models/auth.py index 541e9f129..eaed7e10f 100644 --- a/src/pretix/base/models/auth.py +++ b/src/pretix/base/models/auth.py @@ -340,7 +340,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin): class StaffSession(models.Model): - user = models.ForeignKey('User') + user = models.ForeignKey('User', on_delete=models.PROTECT) date_start = models.DateTimeField(auto_now_add=True) date_end = models.DateTimeField(null=True, blank=True) session_key = models.CharField(max_length=255) @@ -351,11 +351,11 @@ class StaffSession(models.Model): class StaffSessionAuditLog(models.Model): - session = models.ForeignKey('StaffSession', related_name='logs') + session = models.ForeignKey('StaffSession', related_name='logs', on_delete=models.PROTECT) datetime = models.DateTimeField(auto_now_add=True) url = models.CharField(max_length=255) method = models.CharField(max_length=255) - impersonating = models.ForeignKey('User', null=True, blank=True) + impersonating = models.ForeignKey('User', null=True, blank=True, on_delete=models.PROTECT) class Meta: ordering = ('datetime',) diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py index a75d52573..c6bab9e3d 100644 --- a/src/pretix/base/models/checkin.py +++ b/src/pretix/base/models/checkin.py @@ -8,12 +8,12 @@ from pretix.base.models import LoggedModel class CheckinList(LoggedModel): - event = models.ForeignKey('Event', related_name='checkin_lists') + event = models.ForeignKey('Event', related_name='checkin_lists', on_delete=models.CASCADE) name = models.CharField(max_length=190) all_products = models.BooleanField(default=True, verbose_name=_("All products (including newly created ones)")) limit_products = models.ManyToManyField('Item', verbose_name=_("Limit to products"), blank=True) subevent = models.ForeignKey('SubEvent', null=True, blank=True, - verbose_name=pgettext_lazy('subevent', 'Date')) + verbose_name=pgettext_lazy('subevent', 'Date'), on_delete=models.CASCADE) include_pending = models.BooleanField(verbose_name=pgettext_lazy('checkin', 'Include pending orders'), default=False, help_text=_('With this option, people will be able to check in even if the ' @@ -157,7 +157,7 @@ class Checkin(models.Model): """ A check-in object is created when a person enters the event. """ - position = models.ForeignKey('pretixbase.OrderPosition', related_name='checkins') + position = models.ForeignKey('pretixbase.OrderPosition', related_name='checkins', on_delete=models.CASCADE) datetime = models.DateTimeField(default=now) nonce = models.CharField(max_length=190, null=True, blank=True) list = models.ForeignKey( diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 0f058fb45..7568fc9a9 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -265,6 +265,7 @@ class Event(EventMixin, LoggedModel): verbose_name = _("Event") verbose_name_plural = _("Events") ordering = ("date_from", "name") + unique_together = (('organizer', 'slug'),) def __str__(self): return str(self.name) diff --git a/src/pretix/base/models/invoices.py b/src/pretix/base/models/invoices.py index 76feb602f..8b9962dd1 100644 --- a/src/pretix/base/models/invoices.py +++ b/src/pretix/base/models/invoices.py @@ -64,14 +64,14 @@ class Invoice(models.Model): :param file: The filename of the rendered invoice :type file: File """ - order = models.ForeignKey('Order', related_name='invoices', db_index=True) + order = models.ForeignKey('Order', related_name='invoices', db_index=True, on_delete=models.CASCADE) organizer = models.ForeignKey('Organizer', related_name='invoices', db_index=True, on_delete=models.PROTECT) - event = models.ForeignKey('Event', related_name='invoices', db_index=True) + event = models.ForeignKey('Event', related_name='invoices', db_index=True, on_delete=models.CASCADE) prefix = models.CharField(max_length=160, db_index=True) invoice_no = models.CharField(max_length=19, db_index=True) full_invoice_no = models.CharField(max_length=190, db_index=True) is_cancellation = models.BooleanField(default=False) - refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True) + refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True, on_delete=models.CASCADE) invoice_from = models.TextField() invoice_to = models.TextField() date = models.DateField(default=today) @@ -175,7 +175,7 @@ class InvoiceLine(models.Model): :param tax_name: The name of the applied tax rate :type tax_name: str """ - invoice = models.ForeignKey('Invoice', related_name='lines') + invoice = models.ForeignKey('Invoice', related_name='lines', on_delete=models.CASCADE) position = models.PositiveIntegerField(default=0) description = models.TextField() gross_value = models.DecimalField(max_digits=10, decimal_places=2) diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 54087a9e6..38bc7d9ff 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -447,7 +447,8 @@ class ItemVariation(models.Model): """ item = models.ForeignKey( Item, - related_name='variations' + related_name='variations', + on_delete=models.CASCADE ) value = I18nCharField( max_length=255, @@ -562,12 +563,14 @@ class ItemAddOn(models.Model): """ base_item = models.ForeignKey( Item, - related_name='addons' + related_name='addons', + on_delete=models.CASCADE ) addon_category = models.ForeignKey( ItemCategory, related_name='addon_to', - verbose_name=_('Category') + verbose_name=_('Category'), + on_delete=models.CASCADE ) min_count = models.PositiveIntegerField( default=0, @@ -679,7 +682,8 @@ class Question(LoggedModel): event = models.ForeignKey( Event, - related_name="questions" + related_name="questions", + on_delete=models.CASCADE ) question = I18nTextField( verbose_name=_("Question") @@ -831,7 +835,7 @@ class Question(LoggedModel): class QuestionOption(models.Model): - question = models.ForeignKey('Question', related_name='options') + question = models.ForeignKey('Question', related_name='options', on_delete=models.CASCADE) identifier = models.CharField(max_length=190) answer = I18nCharField(verbose_name=_('Answer')) position = models.IntegerField(default=0) diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 4b0fd8923..10557354c 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -119,7 +119,8 @@ class Order(LoggedModel): event = models.ForeignKey( Event, verbose_name=_("Event"), - related_name="orders" + related_name="orders", + on_delete=models.CASCADE ) email = models.EmailField( null=True, blank=True, @@ -226,11 +227,11 @@ class Order(LoggedModel): pending_sum_rc=-1 * F('payment_sum') + Coalesce(F('refund_sum'), 0), ).annotate( is_overpaid=Case( - When(~Q(status__in=[Order.STATUS_REFUNDED, Order.STATUS_CANCELED]) & Q(pending_sum_t__lt=0), + When(~Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_t__lt=0), then=Value('1')), - When(Q(status__in=[Order.STATUS_REFUNDED, Order.STATUS_CANCELED]) & Q(pending_sum_rc__lt=0), + When(Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_rc__lt=0), then=Value('1')), - When(Q(status__in=[Order.STATUS_EXPIRED, Order.STATUS_PENDING]) & Q(pending_sum_t__lte=0), + When(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0), then=Value('1')), default=Value('0'), output_field=models.IntegerField() @@ -544,14 +545,14 @@ class QuestionAnswer(models.Model): """ orderposition = models.ForeignKey( 'OrderPosition', null=True, blank=True, - related_name='answers' + related_name='answers', on_delete=models.CASCADE ) cartposition = models.ForeignKey( 'CartPosition', null=True, blank=True, - related_name='answers' + related_name='answers', on_delete=models.CASCADE ) question = models.ForeignKey( - Question, related_name='answers' + Question, related_name='answers', on_delete=models.CASCADE ) options = models.ManyToManyField( QuestionOption, related_name='answers', blank=True @@ -699,7 +700,7 @@ class AbstractPosition(models.Model): help_text=_("Empty, if this product is not an admission ticket") ) voucher = models.ForeignKey( - 'Voucher', null=True, blank=True + 'Voucher', null=True, blank=True, on_delete=models.CASCADE ) addon_to = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.CASCADE, related_name='addons' @@ -829,7 +830,7 @@ class OrderPayment(models.Model): ) fee = models.ForeignKey( 'OrderFee', - null=True, blank=True, related_name='payments' + null=True, blank=True, related_name='payments', on_delete=models.SET_NULL ) migrated = models.BooleanField(default=False) @@ -1444,7 +1445,8 @@ class CartPosition(AbstractPosition): """ event = models.ForeignKey( Event, - verbose_name=_("Event") + verbose_name=_("Event"), + on_delete=models.CASCADE ) cart_id = models.CharField( max_length=255, null=True, blank=True, db_index=True, @@ -1488,7 +1490,7 @@ class CartPosition(AbstractPosition): class InvoiceAddress(models.Model): last_modified = models.DateTimeField(auto_now=True) - order = models.OneToOneField(Order, null=True, blank=True, related_name='invoice_address') + order = models.OneToOneField(Order, null=True, blank=True, related_name='invoice_address', on_delete=models.CASCADE) is_business = models.BooleanField(default=False, verbose_name=_('Business customer')) company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name')) name = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index bffa2da80..d76c7f4d0 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -42,6 +42,7 @@ class Organizer(LoggedModel): OrganizerSlugBlacklistValidator() ], verbose_name=_("Short form"), + unique=True ) class Meta: diff --git a/src/pretix/base/models/tax.py b/src/pretix/base/models/tax.py index 13a7d9b8e..ca9a91db0 100644 --- a/src/pretix/base/models/tax.py +++ b/src/pretix/base/models/tax.py @@ -60,7 +60,7 @@ EU_CURRENCIES = { class TaxRule(LoggedModel): - event = models.ForeignKey('Event', related_name='tax_rules') + event = models.ForeignKey('Event', related_name='tax_rules', on_delete=models.CASCADE) name = I18nCharField( verbose_name=_('Name'), help_text=_('Should be short, e.g. "VAT"'), diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 34991aa5b..c309c4768 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -137,14 +137,14 @@ class Voucher(LoggedModel): item = models.ForeignKey( Item, related_name='vouchers', verbose_name=_("Product"), - null=True, blank=True, + null=True, blank=True, on_delete=models.CASCADE, help_text=_( "This product is added to the user's cart if the voucher is redeemed." ) ) variation = models.ForeignKey( ItemVariation, related_name='vouchers', - null=True, blank=True, + null=True, blank=True, on_delete=models.CASCADE, verbose_name=_("Product variation"), help_text=_( "This variation of the product select above is being used." @@ -152,7 +152,7 @@ class Voucher(LoggedModel): ) quota = models.ForeignKey( Quota, related_name='quota', - null=True, blank=True, + null=True, blank=True, on_delete=models.CASCADE, verbose_name=_("Quota"), help_text=_( "If enabled, the voucher is valid for any product affected by this quota." diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py index 51be7dbf5..886b97be7 100644 --- a/src/pretix/base/models/waitinglist.py +++ b/src/pretix/base/models/waitinglist.py @@ -43,10 +43,11 @@ class WaitingListEntry(LoggedModel): 'Voucher', verbose_name=_("Assigned voucher"), null=True, blank=True, - related_name='waitinglistentries' + related_name='waitinglistentries', + on_delete=models.CASCADE ) item = models.ForeignKey( - Item, related_name='waitinglistentries', + Item, related_name='waitinglistentries', on_delete=models.CASCADE, verbose_name=_("Product"), help_text=_( "The product the user waits for." @@ -54,7 +55,7 @@ class WaitingListEntry(LoggedModel): ) variation = models.ForeignKey( ItemVariation, related_name='waitinglistentries', - null=True, blank=True, + null=True, blank=True, on_delete=models.CASCADE, verbose_name=_("Product variation"), help_text=_( "The variation of the product selected above." diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py index 416574159..da6ed1b78 100644 --- a/src/pretix/base/services/invoices.py +++ b/src/pretix/base/services/invoices.py @@ -171,7 +171,7 @@ def build_cancellation(invoice: Invoice): def generate_cancellation(invoice: Invoice, trigger_pdf=True): - cancellation = copy.copy(invoice) + cancellation = copy.deepcopy(invoice) cancellation.pk = None cancellation.invoice_no = None cancellation.prefix = None diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index f65083353..cbaa646a8 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -923,7 +923,7 @@ class OrderChangeManager: op.save() try: - ia = copy.copy(self.order.invoice_address) + ia = copy.deepcopy(self.order.invoice_address) ia.pk = None ia.order = split_order ia.save() @@ -947,7 +947,7 @@ class OrderChangeManager: split_order.total += fee.value for fee in self.order.fees.exclude(fee_type=OrderFee.FEE_TYPE_PAYMENT): - new_fee = copy.copy(fee) + new_fee = copy.deepcopy(fee) new_fee.pk = None new_fee.order = split_order split_order.total += new_fee.value diff --git a/src/pretix/base/templates/error.html b/src/pretix/base/templates/error.html index fcb1f223c..ad1e120a7 100644 --- a/src/pretix/base/templates/error.html +++ b/src/pretix/base/templates/error.html @@ -1,6 +1,6 @@ {% load compress %} {% load i18n %} -{% load staticfiles %} +{% load static %}
diff --git a/src/pretix/base/templates/pretixbase/cachedfiles/pending.html b/src/pretix/base/templates/pretixbase/cachedfiles/pending.html index 36d99aff0..20a37feb7 100644 --- a/src/pretix/base/templates/pretixbase/cachedfiles/pending.html +++ b/src/pretix/base/templates/pretixbase/cachedfiles/pending.html @@ -1,6 +1,6 @@ {% load compress %} {% load i18n %} -{% load staticfiles %} +{% load static %} diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py index 98d543577..6dd00262c 100644 --- a/src/pretix/base/templatetags/rich_text.py +++ b/src/pretix/base/templatetags/rich_text.py @@ -63,7 +63,7 @@ ALLOWED_PROTOCOLS = ['http', 'https', 'mailto', 'tel'] def safelink_callback(attrs, new=False): url = attrs.get((None, 'href'), '/') - if not is_safe_url(url) and not url.startswith('mailto:') and not url.startswith('tel:'): + if not is_safe_url(url, allowed_hosts=None) and not url.startswith('mailto:') and not url.startswith('tel:'): signer = signing.Signer(salt='safe-redirect') attrs[None, 'href'] = reverse('redirect') + '?url=' + urllib.parse.quote(signer.sign(url)) attrs[None, 'target'] = '_blank' diff --git a/src/pretix/base/templatetags/urlreplace.py b/src/pretix/base/templatetags/urlreplace.py index 1dc678570..6a5b4717f 100644 --- a/src/pretix/base/templatetags/urlreplace.py +++ b/src/pretix/base/templatetags/urlreplace.py @@ -15,6 +15,6 @@ def url_replace(request, *pairs): if key in dict_: del dict_[key] else: - dict_[key] = p + dict_[key] = str(p) key = None return dict_.urlencode(safe='[]') diff --git a/src/pretix/base/views/js_catalog.py b/src/pretix/base/views/js_catalog.py index b2f475a66..ea315aaec 100644 --- a/src/pretix/base/views/js_catalog.py +++ b/src/pretix/base/views/js_catalog.py @@ -1,7 +1,8 @@ from django.utils import timezone +from django.utils.translation.trans_real import DjangoTranslation from django.views.decorators.cache import cache_page from django.views.decorators.http import etag -from django.views.i18n import get_javascript_catalog, render_javascript_catalog +from django.views.i18n import JavaScriptCatalog, render_javascript_catalog # Yes, we want to regenerate this every time the module has been imported to # refresh the cache at least at every code deployment @@ -18,6 +19,6 @@ js_info_dict = { @etag(lambda *s, **k: import_date) @cache_page(3600, key_prefix='js18n-%s' % import_date) def js_catalog(request, lang): - packages = ['pretix'] - catalog, plural = get_javascript_catalog(lang, 'djangojs', packages) - return render_javascript_catalog(catalog, plural) + c = JavaScriptCatalog() + c.translation = DjangoTranslation(lang, domain='djangojs') + return render_javascript_catalog(c.get_catalog(), c.get_plural()) diff --git a/src/pretix/base/views/redirect.py b/src/pretix/base/views/redirect.py index ad58bec3c..bc17f943c 100644 --- a/src/pretix/base/views/redirect.py +++ b/src/pretix/base/views/redirect.py @@ -1,8 +1,8 @@ import urllib.parse from django.core import signing -from django.core.urlresolvers import reverse from django.http import HttpResponseBadRequest, HttpResponseRedirect +from django.urls import reverse def redir_view(request): diff --git a/src/pretix/control/context.py b/src/pretix/control/context.py index 2a6c2e299..d09a11fd5 100644 --- a/src/pretix/control/context.py +++ b/src/pretix/control/context.py @@ -2,8 +2,8 @@ import sys from importlib import import_module from django.conf import settings -from django.core.urlresolvers import Resolver404, get_script_prefix, resolve from django.db.models import Q +from django.urls import Resolver404, get_script_prefix, resolve from django.utils.translation import get_language from pretix.base.models.auth import StaffSession diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 77a1f0395..f8438aeaf 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -78,7 +78,7 @@ class QuotaForm(I18nModelForm): self.instance = kwargs.get('instance', None) self.event = kwargs.get('event') items = kwargs.pop('items', None) or self.event.items.prefetch_related('variations') - self.original_instance = copy.copy(self.instance) if self.instance else None + self.original_instance = copy.deepcopy(self.instance) if self.instance else None initial = kwargs.get('initial', {}) if self.instance and self.instance.pk: initial['itemvars'] = [str(i.pk) for i in self.instance.items.all()] + [ @@ -370,6 +370,7 @@ class ItemVariationsFormSet(I18nFormSet): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, + use_required_attribute=False, locales=self.locales, event=self.event ) @@ -430,6 +431,7 @@ class ItemAddOnsFormSet(I18nFormSet): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, + use_required_attribute=False, locales=self.locales, event=self.event ) diff --git a/src/pretix/control/forms/subevents.py b/src/pretix/control/forms/subevents.py index 219b2a670..f933e0c4d 100644 --- a/src/pretix/control/forms/subevents.py +++ b/src/pretix/control/forms/subevents.py @@ -150,6 +150,7 @@ class QuotaFormSet(I18nInlineFormSet): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, + use_required_attribute=False, locales=self.locales, event=self.event, items=self.items @@ -196,6 +197,7 @@ class CheckinListFormSet(I18nInlineFormSet): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, + use_required_attribute=False, event=self.event, ) self.add_fields(form, None) diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index 55a4b34dc..e231f5111 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -45,7 +45,7 @@ class VoucherForm(I18nModelForm): instance = kwargs.get('instance') initial = kwargs.get('initial') if instance: - self.initial_instance_data = copy.copy(instance) + self.initial_instance_data = copy.deepcopy(instance) try: if instance.variation: initial['itemvar'] = '%d-%d' % (instance.item.pk, instance.variation.pk) @@ -139,7 +139,7 @@ class VoucherForm(I18nModelForm): if 'codes' in data: data['codes'] = [a.strip() for a in data.get('codes', '').strip().split("\n") if a] - cnt = len(data['codes']) * data['max_usages'] + cnt = len(data['codes']) * data.get('max_usages', 0) else: cnt = data['max_usages'] @@ -217,7 +217,7 @@ class VoucherBulkForm(VoucherForm): def save(self, event, *args, **kwargs): objs = [] for code in self.cleaned_data['codes']: - obj = copy.copy(self.instance) + obj = copy.deepcopy(self.instance) obj.event = event obj.code = code data = dict(self.cleaned_data) diff --git a/src/pretix/control/middleware.py b/src/pretix/control/middleware.py index cd99705ef..45d07d1a3 100644 --- a/src/pretix/control/middleware.py +++ b/src/pretix/control/middleware.py @@ -2,9 +2,9 @@ from urllib.parse import quote, urljoin, urlparse from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME, logout -from django.core.urlresolvers import get_script_prefix, resolve, reverse from django.http import Http404 from django.shortcuts import get_object_or_404, redirect, resolve_url +from django.urls import get_script_prefix, resolve, reverse from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_str from django.utils.translation import ugettext as _ diff --git a/src/pretix/control/templates/pretixcontrol/auth/base.html b/src/pretix/control/templates/pretixcontrol/auth/base.html index 7cf1fa0c8..54e15129d 100644 --- a/src/pretix/control/templates/pretixcontrol/auth/base.html +++ b/src/pretix/control/templates/pretixcontrol/auth/base.html @@ -1,6 +1,6 @@ {% load compress %} {% load i18n %} -{% load staticfiles %} +{% load static %} diff --git a/src/pretix/control/templates/pretixcontrol/auth/forgot.html b/src/pretix/control/templates/pretixcontrol/auth/forgot.html index 3140a8497..7e456284b 100644 --- a/src/pretix/control/templates/pretixcontrol/auth/forgot.html +++ b/src/pretix/control/templates/pretixcontrol/auth/forgot.html @@ -1,6 +1,6 @@ {% extends "pretixcontrol/auth/base.html" %} {% load bootstrap3 %} -{% load staticfiles %} +{% load static %} {% load i18n %} {% block content %}