diff --git a/.travis.sh b/.travis.sh index 11a090a557..15691b5395 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 f68b87204b..e12f219453 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 d301ea9995..816ba0fa08 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 710b804b2b..fbf1f6b90f 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 51bc36760c..028a10c1f4 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 d56fb19ef7..b768aebbfc 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 189d04efb2..7a62d0e66a 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 964d568629..f5931b20ab 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 cb92fc1106..d685129025 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 27585f7248..b89d5c8e3b 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 90c40061ff..7e3112543f 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 b2ab942a06..b23f03f7b8 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 73e7f8cf1c..e31157cc20 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 36f6e8bf7a..cf649dc435 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 8162d3173f..f4f92d4ce6 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 ab0324c4d2..a2f1fedf28 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 0000000000..bf04b2306f --- /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 541e9f1298..eaed7e10f4 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 a75d52573d..c6bab9e3d3 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 0f058fb45d..7568fc9a9e 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 76feb602fa..8b9962dd12 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 54087a9e68..38bc7d9ffb 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 4b0fd8923f..10557354c6 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 bffa2da803..d76c7f4d0b 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 13a7d9b8ee..ca9a91db0c 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 34991aa5b3..c309c4768a 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 51be7dbf5b..886b97be79 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 416574159b..da6ed1b780 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 f650833530..cbaa646a89 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 fcb1f223cc..ad1e120a77 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 36d99aff02..20a37feb7e 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 98d5435776..6dd00262ca 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 1dc6785701..6a5b4717f4 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 b2f475a661..ea315aaec6 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 ad58bec3c6..bc17f943c6 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 2a6c2e2990..d09a11fd5d 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 77a1f03954..f8438aeaf7 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 219b2a670a..f933e0c4dd 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 55a4b34dc8..e231f51115 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 cd99705efd..45d07d1a30 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 7cf1fa0c88..54e15129d7 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 3140a8497c..7e456284b6 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 %}