Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
f78e54f2a1 Fix #575 -- Do not extend card expiry on failed card operations 2020-03-09 21:21:28 +01:00
324 changed files with 53682 additions and 76924 deletions

View File

@@ -13,24 +13,24 @@ services:
- postgresql
matrix:
include:
- python: 3.8
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
- python: 3.8
- python: 3.7
env: JOB=tests-cov PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.8
- python: 3.7
env: JOB=style
- python: 3.8
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
- python: 3.8
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.7
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.8
- python: 3.5
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
- python: 3.7
env: JOB=doc-spelling
- python: 3.8
- python: 3.7
env: JOB=translation-spelling
addons:
postgresql: "10"
postgresql: "9.4"
mariadb: '10.3'
apt:
packages:

View File

@@ -182,7 +182,7 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
-v /var/pretix-data:/data \
-v /etc/pretix:/etc/pretix \
-v /var/run/redis:/var/run/redis \
--sysctl net.core.somaxconn=4096 \
--sysctl net.core.somaxconn=4096
pretix/standalone:stable all
ExecStop=/usr/bin/docker stop %n

View File

@@ -12,7 +12,7 @@ solution with many things readily set-up, look at :ref:`dockersmallscale`.
get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
offers at `pretix.eu`_.
We tested this guide on the Linux distribution **Debian 10.0** but it should work very similar on other
We tested this guide on the Linux distribution **Debian 8.0** but it should work very similar on other
modern distributions, especially on all systemd-based ones.
Requirements
@@ -133,7 +133,7 @@ command if you're running MySQL::
(venv)$ pip3 install "pretix[postgres]" gunicorn
Note that you need Python 3.6 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::

View File

@@ -151,10 +151,6 @@ last_modified datetime Last modificati
The ``order.fees.canceled`` attribute has been added.
.. versionchanged:: 3.8
The ``reactivate`` operation has been added.
.. _order-position-resource:
@@ -177,13 +173,6 @@ price money (string) Price of this p
attendee_name string Specified attendee name for this position (or ``null``)
attendee_name_parts object of strings Decomposition of attendee name (i.e. given name, family name)
attendee_email string Specified attendee email address for this position (or ``null``)
company string Attendee company name (or ``null``)
street string Attendee street (or ``null``)
zipcode string Attendee ZIP code (or ``null``)
city string Attendee city (or ``null``)
country string Attendee country code (or ``null``)
state string Attendee state (ISO 3166-2 code). Only supported in
AU, BR, CA, CN, MY, MX, and US, otherwise ``null``.
voucher integer Internal ID of the voucher used for this position (or ``null``)
tax_rate decimal (string) VAT rate applied for this position
tax_value money (string) VAT included in this position
@@ -247,10 +236,6 @@ pdf_data object Data object req
The attribute ``canceled`` has been added.
.. versionchanged:: 3.8
The attributes ``company``, ``street``, ``zipcode``, ``city``, ``country``, and ``state`` have been added.
.. _order-payment-resource:
Order payment resource
@@ -395,12 +380,6 @@ List of all orders
"full_name": "Peter",
},
"attendee_email": null,
"company": "Sample company",
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "DE",
"state": null,
"voucher": null,
"tax_rate": "0.00",
"tax_value": "0.00",
@@ -557,12 +536,6 @@ Fetching individual orders
"full_name": "Peter",
},
"attendee_email": null,
"company": "Sample company",
"street": "Test street 12",
"zipcode": "12345",
"city": "Testington",
"country": "DE",
"state": null,
"voucher": null,
"tax_rate": "0.00",
"tax_rule": null,
@@ -843,9 +816,9 @@ Creating orders
* ``consume_carts`` (optional) A list of cart IDs. All cart positions with these IDs will be deleted if the
order creation is successful. Any quotas or seats that become free by this operation will be credited to your order
creation.
* ``email`` (optional)
* ``email``
* ``locale``
* ``sales_channel`` (optional)
* ``sales_channel``
* ``payment_provider`` (optional) The identifier of the payment provider set for this order. This needs to be an
existing payment provider. You should use ``"free"`` for free orders, and we strongly advise to use ``"manual"``
for all orders you create as paid. This field is optional when the order status is ``"n"`` or the order total is
@@ -878,21 +851,15 @@ Creating orders
* ``positionid`` (optional, see below)
* ``item``
* ``variation`` (optional)
* ``variation``
* ``price`` (optional, if set to ``null`` or missing the price will be computed from the given product)
* ``seat`` (The ``seat_guid`` attribute of a seat. Required when the specified ``item`` requires a seat, otherwise must be ``null``.)
* ``attendee_name`` **or** ``attendee_name_parts`` (optional)
* ``attendee_name`` **or** ``attendee_name_parts``
* ``voucher`` (optional, the ``code`` attribute of a valid voucher)
* ``attendee_email`` (optional)
* ``company`` (optional)
* ``street`` (optional)
* ``zipcode`` (optional)
* ``city`` (optional)
* ``country`` (optional)
* ``state`` (optional)
* ``attendee_email``
* ``secret`` (optional)
* ``addon_to`` (optional, see below)
* ``subevent`` (optional)
* ``subevent``
* ``answers``
* ``question``
@@ -1090,42 +1057,6 @@ Order state operations
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order does not exist.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/reactivate/
Reactivates a canceled order. This will set the order to pending or paid state. Only possible if all products are
still available.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/reactivate/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"code": "ABC12",
"status": "n",
...
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param code: The ``code`` field of the order to modify
:statuscode 200: no error
:statuscode 400: The order cannot be reactivated
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested order does not exist.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/mark_pending/
Marks a paid order as unpaid.

View File

@@ -66,7 +66,7 @@ event-related views, there is also a signal that allows you to add the view to t
from django.urls import resolve, reverse
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.control.signals import nav_event

View File

@@ -20,7 +20,7 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins
"""""""""
@@ -33,11 +33,11 @@ Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, item_description
.. automodule:: pretix.presale.signals
:members: order_info, order_info_top, order_meta_from_request
:members: order_info, order_meta_from_request
Request flow
""""""""""""

View File

@@ -61,7 +61,7 @@ A working example would be::
from pretix.base.plugins import PluginConfig
except ImportError:
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class PaypalApp(PluginConfig):

View File

@@ -69,7 +69,7 @@ We now need a way to translate the action codes like ``pretix.event.changed`` in
strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple
implementation could look like::
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from pretix.base.signals import logentry_display
@receiver(signal=logentry_display)

View File

@@ -1,277 +0,0 @@
Digital content
===============
The digital content plugin provides a HTTP API that allows you to create new digital content for your ticket holders,
such as live streams, videos, or material downloads.
Resource description
--------------------
The digital content resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal content ID
title multi-lingual string The content title (required)
content_type string The type of content, valid values are ``webinar``, ``video``, ``livestream``, ``link``, ``file``
url string The location of the digital content
description multi-lingual string A public description of the item. May contain Markdown
syntax and is not required.
available_from datetime The first date time at which this content will be shown
(or ``null``).
available_until datetime The last date time at which this content will b e shown
(or ``null``).
all_products boolean If ``true``, the content is available to all buyers of tickets for this event. The ``limit_products`` field is ignored in this case.
limit_products list of integers List of product/item IDs. This content is only shown to buyers of these ticket types.
position integer An integer, used for sorting
subevent integer Date in an event series this content should be shown for. Should be ``null`` if this is not an event series or if this should be shown to all customers.
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/digitalcontents/
Returns a list of all digital content configured for an event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/digitalcontents/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"subevent": null,
"title": {
"en": "Concert livestream"
},
"content_type": "link",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"description": {
"en": "Watch our event live here on YouTube!"
},
"all_products": true,
"limit_products": [],
"available_from": "2020-03-22T23:00:00Z",
"available_until": null,
"position": 1
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/digitalcontents/(id)/
Returns information on one content item, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/digitalcontents/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"subevent": null,
"title": {
"en": "Concert livestream"
},
"content_type": "link",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"description": {
"en": "Watch our event live here on YouTube!"
},
"all_products": true,
"limit_products": [],
"available_from": "2020-03-22T23:00:00Z",
"available_until": null,
"position": 1
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the content to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/content does not exist **or** you have no permission to view it.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/digitalcontents/
Create a new digital content.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/digitalcontents/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 166
{
"subevent": null,
"title": {
"en": "Concert livestream"
},
"content_type": "link",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"description": {
"en": "Watch our event live here on YouTube!"
},
"all_products": true,
"limit_products": [],
"available_from": "2020-03-22T23:00:00Z",
"available_until": null,
"position": 1
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 2,
"subevent": null,
"title": {
"en": "Concert livestream"
},
"content_type": "link",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"description": {
"en": "Watch our event live here on YouTube!"
},
"all_products": true,
"limit_products": [],
"available_from": "2020-03-22T23:00:00Z",
"available_until": null,
"position": 1
}
:param organizer: The ``slug`` field of the organizer to create new content for
:param event: The ``slug`` field of the event to create new content for
:statuscode 201: no error
:statuscode 400: The content could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create digital contents.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/digitalcontents/(id)/
Update a content. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/digitalcontents/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 34
{
"url": "https://mywebsite.com"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"id": 2,
"subevent": null,
"title": {
"en": "Concert livestream"
},
"content_type": "link",
"url": "https://mywebsite.com",
"description": {
"en": "Watch our event live here on YouTube!"
},
"all_products": true,
"limit_products": [],
"available_from": "2020-03-22T23:00:00Z",
"available_until": null,
"position": 1
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the content to modify
:statuscode 200: no error
:statuscode 400: The content could not be modified due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/content does not exist **or** you have no permission to change it.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/digitalcontents/(id)/
Delete a digital content.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/digitalcontents/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the content to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/content does not exist **or** you have no permission to change it

View File

@@ -15,4 +15,3 @@ If you want to **create** a plugin, please go to the
ticketoutputpdf
badges
campaigns
digital

View File

@@ -14,23 +14,30 @@ and with pretix, you can do this. On this page, you find out the necessary steps
With the pretix.eu hosted service
---------------------------------
Go to "Organizers" in the backend and select your organizer account. Then, go to "Settings" and "Custom Domain".
This page will show you instructions on how to set up your own domain. Basically, it works like this:
Step 1: DNS Configuration
#########################
Go to the website of the provider you registered your domain name with. Look for the "DNS" settings page in their
interface. Unfortunately, we can't tell you exactly how that is named and how it looks, since it is different for every
domain provider.
Use this interface to add a new subdomain record, e.g. ``tickets`` of the type ``CNAME`` (might also be called "alias").
The value of the record should be the one shown on the "Custom Domain" page in pretix' backend.
The value of the record should be ``www.pretix.eu``.
Step 2: Wait for the DNS entry to propagate
###########################################
Submit your changes and wait a bit, it can regularly take up to three hours for DNS changes to propagate to the caches
of all DNS servers. You can try checking by accessing your new subdomain, ``http://tickets.awesomepartycorp.com``.
If DNS was changed successfully, you should see a SSL certificate error. If you ignore the error and access the page
anyways, you should get a pretix-themed error page with the headline "Unknown domain".
Now, tell us about your domain on the "Custom Domain" page to get started.
Step 3: Tell us
###############
Write an email to support@pretix.eu, naming your new domain and your organizer account. We will then generate a SSL
certificate for you (for free!) and configure the domain.
With a custom pretix installation
---------------------------------

View File

@@ -1 +1 @@
__version__ = "3.8.0"
__version__ = "3.7.0.dev0"

View File

@@ -3,7 +3,7 @@ from datetime import timedelta
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from oauth2_provider.generators import (
generate_client_id, generate_client_secret,
)

View File

@@ -2,7 +2,7 @@ from datetime import timedelta
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy
from django.utils.translation import ugettext_lazy
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -56,7 +56,7 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
else validated_data.get('item').quotas.filter(subevent=validated_data.get('subevent')))
if len(new_quotas) == 0:
raise ValidationError(
gettext_lazy('The product "{}" is not assigned to a quota.').format(
ugettext_lazy('The product "{}" is not assigned to a quota.').format(
str(validated_data.get('item'))
)
)
@@ -64,8 +64,8 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < 1):
raise ValidationError(
gettext_lazy('There is not enough quota available on quota "{}" to perform '
'the operation.').format(
ugettext_lazy('There is not enough quota available on quota "{}" to perform '
'the operation.').format(
quota.name
)
)
@@ -88,7 +88,7 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
else:
validated_data['seat'] = seat
if not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web')):
raise ValidationError(gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
raise ValidationError(ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')

View File

@@ -1,4 +1,4 @@
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

View File

@@ -2,7 +2,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django_countries.serializers import CountryFieldMixin
from hierarkey.proxy import HierarkeyProxy
from pytz import common_timezones
@@ -532,9 +532,6 @@ class EventSettingsSerializer(serializers.Serializer):
'checkout_email_helptext',
'presale_has_ended_text',
'voucher_explanation_text',
'banner_text',
'banner_text_bottom',
'show_dates_on_frontpage',
'show_date_to',
'show_times',
'show_items_outside_presale_period',
@@ -560,10 +557,6 @@ class EventSettingsSerializer(serializers.Serializer):
'attendee_names_required',
'attendee_emails_asked',
'attendee_emails_required',
'attendee_addresses_asked',
'attendee_addresses_required',
'attendee_company_asked',
'attendee_company_required',
'confirm_text',
'order_email_asked_twice',
'payment_term_days',
@@ -617,10 +610,6 @@ class EventSettingsSerializer(serializers.Serializer):
'cancel_allow_user_paid_keep',
'cancel_allow_user_paid_keep_fees',
'cancel_allow_user_paid_keep_percentage',
'cancel_allow_user_paid_adjust_fees',
'cancel_allow_user_paid_adjust_fees_explanation',
'cancel_allow_user_paid_refund_as_giftcard',
'cancel_allow_user_paid_require_approval',
]
def __init__(self, *args, **kwargs):

View File

@@ -1,29 +0,0 @@
from collections import OrderedDict
from rest_framework import serializers
def remove_duplicates_from_list(data):
return list(OrderedDict.fromkeys(data))
class ListMultipleChoiceField(serializers.MultipleChoiceField):
def to_internal_value(self, data):
if isinstance(data, str) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
internal_value_data = [
super(serializers.MultipleChoiceField, self).to_internal_value(item)
for item in data
]
return remove_duplicates_from_list(internal_value_data)
def to_representation(self, value):
representation_data = [
self.choice_strings_to_values.get(str(item), item) for item in value
]
return remove_duplicates_from_list(representation_data)

View File

@@ -3,7 +3,7 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from pretix.api.serializers.event import MetaDataField
@@ -287,8 +287,8 @@ class QuestionSerializer(I18nAwareModelSerializer):
if value:
if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
raise ValidationError('Question dependencies can only be set to boolean or choice questions.')
if value == self.instance:
raise ValidationError('A question cannot depend on itself.')
if value == self.instance:
raise ValidationError('A question cannot depend on itself.')
return value
def validate(self, data):

View File

@@ -5,7 +5,7 @@ from decimal import Decimal
import pycountry
from django.db.models import F, Q
from django.utils.timezone import now
from django.utils.translation import gettext_lazy
from django.utils.translation import ugettext_lazy
from django_countries.fields import Country
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -39,7 +39,7 @@ class CompatibleCountryField(serializers.Field):
def to_representation(self, instance: InvoiceAddress):
if instance.country:
return str(instance.country)
elif hasattr(instance, 'country_old'):
else:
return instance.country_old
@@ -211,12 +211,10 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
pdf_data = PdfDataSerializer(source='*')
seat = InlineSeatSerializer(read_only=True)
country = CompatibleCountryField(source='*')
class Meta:
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
@@ -518,22 +516,12 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
max_digits=10)
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
required=False, allow_null=True)
country = CompatibleCountryField(source='*')
class Meta:
model = OrderPosition
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for k, v in self.fields.items():
if k in ('company', 'street', 'zipcode', 'city', 'country', 'state'):
v.required = False
v.allow_blank = True
v.allow_null = True
def validate_secret(self, secret):
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
raise ValidationError(
@@ -588,24 +576,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
)
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
if data.get('country'):
if not pycountry.countries.get(alpha_2=data.get('country')):
raise ValidationError(
{'country': ['Invalid country code.']}
)
if data.get('state'):
cc = str(data.get('country') or self.instance.country or '')
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(
{'state': ['States are not supported in country "{}".'.format(cc)]}
)
if not pycountry.subdivisions.get(code=cc + '-' + data.get('state')):
raise ValidationError(
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
return data
@@ -892,7 +862,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else:
pos_data['seat'] = seat
if (seat not in free_seats and not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web'))) or seat in seats_seen:
errs[i]['seat'] = [gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
errs[i]['seat'] = [ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
seats_seen.add(seat)
elif seated:
errs[i]['seat'] = ['The specified product requires to choose a seat.']
@@ -907,7 +877,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if pos_data.get('variation')
else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
if len(new_quotas) == 0:
errs[i]['item'] = [gettext_lazy('The product "{}" is not assigned to a quota.').format(
errs[i]['item'] = [ugettext_lazy('The product "{}" is not assigned to a quota.').format(
str(pos_data.get('item'))
)]
else:
@@ -919,7 +889,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
quota_avail_cache[quota][1] -= 1
if quota_avail_cache[quota][1] < 0:
errs[i]['item'] = [
gettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
ugettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
quota.name
)
]

View File

@@ -1,7 +1,7 @@
from decimal import Decimal
from django.db.models import Q
from django.utils.translation import get_language, gettext_lazy as _
from django.utils.translation import get_language, ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

View File

@@ -2,7 +2,7 @@ import logging
from django import forms
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from oauth2_provider.exceptions import OAuthToolkitError
from oauth2_provider.forms import AllowForm
from oauth2_provider.views import (

View File

@@ -9,7 +9,7 @@ from django.db.models.functions import Coalesce, Concat
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import mixins, serializers, status, viewsets
@@ -44,7 +44,7 @@ from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderChangeManager, OrderError, _order_placed_email,
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded, reactivate_order,
extend_order, mark_order_expired, mark_order_refunded,
)
from pretix.base.services.pricing import get_price
from pretix.base.services.tickets import generate
@@ -261,29 +261,6 @@ class OrderViewSet(viewsets.ModelViewSet):
)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def reactivate(self, request, **kwargs):
order = self.get_object()
if order.status != Order.STATUS_CANCELED:
return Response(
{'detail': 'The order is not allowed to be reactivated.'},
status=status.HTTP_400_BAD_REQUEST
)
try:
reactivate_order(
order,
user=request.user if request.user.is_authenticated else None,
auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None,
)
except OrderError as e:
return Response(
{'detail': str(e)},
status=status.HTTP_400_BAD_REQUEST
)
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])
def approve(self, request, **kwargs):
send_mail = request.data.get('send_email', True)

View File

@@ -58,7 +58,7 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
write_permission = 'can_change_organizer_settings'
def get_queryset(self):
return self.request.organizer.seating_plans.order_by('name')
return self.request.organizer.seating_plans.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -195,7 +195,7 @@ class TeamViewSet(viewsets.ModelViewSet):
write_permission = 'can_change_teams'
def get_queryset(self):
return self.request.organizer.teams.order_by('pk')
return self.request.organizer.teams.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -268,7 +268,7 @@ class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyMo
return get_object_or_404(self.request.organizer.teams, pk=self.kwargs.get('team'))
def get_queryset(self):
return self.team.invites.order_by('email')
return self.team.invites.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -305,7 +305,7 @@ class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnly
return get_object_or_404(self.request.organizer.teams, pk=self.kwargs.get('team'))
def get_queryset(self):
return self.team.tokens.order_by('name')
return self.team.tokens.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()

View File

@@ -7,7 +7,7 @@ import requests
from celery.exceptions import MaxRetriesExceededError
from django.db.models import Exists, OuterRef, Q
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_scopes import scope, scopes_disabled
from requests import RequestException
@@ -125,10 +125,6 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.event.order.canceled',
_('Order canceled'),
),
ParametrizedOrderWebhookEvent(
'pretix.event.order.reactivated',
_('Order reactivated'),
),
ParametrizedOrderWebhookEvent(
'pretix.event.order.expired',
_('Order expired'),

View File

@@ -85,16 +85,6 @@ class BaseAuthBackend:
"""
return
def get_next_url(self, request):
"""
This method will be called after a successful login to determine the next URL. Pretix in general uses the
``'next'`` query parameter. However, external authentication methods could use custom attributes with hardcoded
names for security purposes. For example, OAuth uses ``'state'`` for keeping track of application state.
"""
if "next" in request.GET:
return request.GET.get("next")
return None
class NativeAuthBackend(BaseAuthBackend):
identifier = 'native'

View File

@@ -2,7 +2,7 @@ import logging
from collections import OrderedDict
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.signals import register_sales_channels

View File

@@ -9,7 +9,7 @@ from django.core.mail.backends.smtp import EmailBackend
from django.dispatch import receiver
from django.template.loader import get_template
from django.utils.timezone import now
from django.utils.translation import get_language, gettext_lazy as _
from django.utils.translation import get_language, ugettext_lazy as _
from inlinestyler.utils import inline_css
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber

View File

@@ -7,7 +7,7 @@ from typing import Tuple
from defusedcsv import csv
from django import forms
from django.utils.formats import localize
from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import ugettext, ugettext_lazy as _
from openpyxl import Workbook
from openpyxl.cell.cell import KNOWN_TYPES
@@ -180,9 +180,9 @@ class MultiSheetListExporter(ListExporter):
]
for s, l in self.sheets:
choices += [
(s + ':default', str(l) + ' ' + gettext('CSV (with commas)')),
(s + ':excel', str(l) + ' ' + gettext('CSV (Excel-style)')),
(s + ':semicolon', str(l) + ' ' + gettext('CSV (with semicolons)')),
(s + ':default', str(l) + ' ' + ugettext('CSV (with commas)')),
(s + ':excel', str(l) + ' ' + ugettext('CSV (Excel-style)')),
(s + ':semicolon', str(l) + ' ' + ugettext('CSV (with semicolons)')),
]
ff = OrderedDict(
[

View File

@@ -5,7 +5,7 @@ from zipfile import ZipFile
from django import forms
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import QuestionAnswer

View File

@@ -6,7 +6,7 @@ import dateutil
from django import forms
from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver
from django.utils.translation import gettext, gettext_lazy
from django.utils.translation import ugettext, ugettext_lazy
from pretix.base.i18n import language
from pretix.base.models import Invoice, OrderPayment
@@ -79,7 +79,7 @@ class DekodiNREIExporter(BaseExporter):
payments.append({
'PTID': '5',
'PTN': 'Lastschrift',
'PTNo4': gettext('Event ticket {event}-{code}').format(
'PTNo4': ugettext('Event ticket {event}-{code}').format(
event=self.event.slug.upper(),
code=invoice.order.code
),
@@ -199,19 +199,19 @@ class DekodiNREIExporter(BaseExporter):
[
('date_from',
forms.DateField(
label=gettext_lazy('Start date'),
label=ugettext_lazy('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=gettext_lazy('Only include invoices issued on or after this date. Note that the invoice date does '
'not always correspond to the order or payment date.')
help_text=ugettext_lazy('Only include invoices issued on or after this date. Note that the invoice date does '
'not always correspond to the order or payment date.')
)),
('date_to',
forms.DateField(
label=gettext_lazy('End date'),
label=ugettext_lazy('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=gettext_lazy('Only include invoices issued on or before this date. Note that the invoice date '
'does not always correspond to the order or payment date.')
help_text=ugettext_lazy('Only include invoices issued on or before this date. Note that the invoice date '
'does not always correspond to the order or payment date.')
)),
]
)

View File

@@ -7,7 +7,7 @@ import dateutil.parser
from django import forms
from django.db.models import Exists, OuterRef, Q
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import OrderPayment

View File

@@ -2,7 +2,7 @@ from collections import OrderedDict
from django import forms
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import OrderPosition

View File

@@ -8,10 +8,10 @@ from django.db.models import (
)
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext as _, gettext_lazy, pgettext
from django.utils.translation import pgettext, ugettext as _, ugettext_lazy
from pretix.base.models import (
GiftCard, InvoiceAddress, InvoiceLine, Order, OrderPosition, Question,
InvoiceAddress, InvoiceLine, Order, OrderPosition, Question,
)
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -22,7 +22,7 @@ from ..signals import register_data_exporters
class OrderListExporter(MultiSheetListExporter):
identifier = 'orderlist'
verbose_name = gettext_lazy('Order data')
verbose_name = ugettext_lazy('Order data')
@property
def sheets(self):
@@ -305,12 +305,6 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Attendee name') + ': ' + str(label))
headers += [
_('Attendee email'),
_('Company'),
_('Address'),
_('ZIP code'),
_('City'),
_('Country'),
pgettext('address', 'State'),
_('Voucher'),
_('Pseudonymization ID'),
]
@@ -370,12 +364,6 @@ class OrderListExporter(MultiSheetListExporter):
)
row += [
op.attendee_email,
op.company or '',
op.street or '',
op.zipcode or '',
op.city or '',
op.country if op.country else '',
op.state or '',
op.voucher.code if op.voucher else '',
op.pseudonymization_id,
]
@@ -426,7 +414,7 @@ class OrderListExporter(MultiSheetListExporter):
class PaymentListExporter(ListExporter):
identifier = 'paymentlist'
verbose_name = gettext_lazy('Order payments and refunds')
verbose_name = ugettext_lazy('Order payments and refunds')
@property
def additional_form_fields(self):
@@ -497,7 +485,7 @@ class PaymentListExporter(ListExporter):
class QuotaListExporter(ListExporter):
identifier = 'quotalist'
verbose_name = gettext_lazy('Quota availabilities')
verbose_name = ugettext_lazy('Quota availabilities')
def iterate_list(self, form_data):
headers = [
@@ -526,7 +514,7 @@ class QuotaListExporter(ListExporter):
class InvoiceDataExporter(MultiSheetListExporter):
identifier = 'invoicedata'
verbose_name = gettext_lazy('Invoice data')
verbose_name = ugettext_lazy('Invoice data')
@property
def sheets(self):
@@ -710,45 +698,6 @@ class InvoiceDataExporter(MultiSheetListExporter):
return '{}_invoices'.format(self.event.slug)
class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Giftcard Redemptions')
def iterate_list(self, form_data):
tz = pytz.timezone(self.event.settings.timezone)
payments = OrderPayment.objects.filter(
order__event=self.event,
provider='giftcard'
).order_by('created')
refunds = OrderRefund.objects.filter(
order__event=self.event,
provider='giftcard'
).order_by('created')
objs = sorted(list(payments) + list(refunds), key=lambda o: (o.order.code, o.created))
headers = [
_('Order'), _('Payment ID'), _('Date'), _('Gift card code'), _('Amount'), _('Issuer')
]
yield headers
for obj in objs:
gc = GiftCard.objects.get(pk=obj.info_data.get('gift_card'))
row = [
obj.order.code,
obj.full_id,
obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
gc.secret,
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
gc.issuer
]
yield row
def get_filename(self):
return '{}_giftcardredemptions'.format(self.event.slug)
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
def register_orderlist_exporter(sender, **kwargs):
return OrderListExporter
@@ -767,8 +716,3 @@ def register_quotalist_exporter(sender, **kwargs):
@receiver(register_data_exporters, dispatch_uid="exporter_invoicedata")
def register_invoicedata_exporter(sender, **kwargs):
return InvoiceDataExporter
@receiver(register_data_exporters, dispatch_uid="exporter_giftcardredemptionlist")
def register_giftcardredemptionlist_exporter(sender, **kwargs):
return GiftcardRedemptionListExporter

View File

@@ -3,6 +3,7 @@ import logging
import i18nfield.forms
from django import forms
from django.forms.models import ModelFormMetaclass
from django.utils import six
from django.utils.crypto import get_random_string
from formtools.wizard.views import SessionWizardView
from hierarkey.forms import HierarkeyForm
@@ -24,7 +25,7 @@ class BaseI18nModelForm(i18nfield.forms.BaseI18nModelForm):
super().__init__(*args, **kwargs)
class I18nModelForm(BaseI18nModelForm, metaclass=ModelFormMetaclass):
class I18nModelForm(six.with_metaclass(ModelFormMetaclass, BaseI18nModelForm)):
pass

View File

@@ -3,10 +3,9 @@ from django.conf import settings
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import User
from pretix.helpers.dicts import move_to_end
class LoginForm(forms.Form):
@@ -37,7 +36,7 @@ class LoginForm(forms.Form):
if not settings.PRETIX_LONG_SESSIONS or backend.url:
del self.fields['keep_logged_in']
else:
move_to_end(self.fields, 'keep_logged_in')
self.fields.move_to_end('keep_logged_in')
def clean(self):
if all(k in self.cleaned_data for k, f in self.fields.items() if f.required):

View File

@@ -18,7 +18,7 @@ from django.forms import Select
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
get_language, pgettext_lazy, ugettext_lazy as _,
)
from django_countries import countries
from django_countries.fields import Country, CountryField
@@ -41,7 +41,6 @@ from pretix.base.settings import (
)
from pretix.base.templatetags.rich_text import rich_text
from pretix.control.forms import SplitDateTimeField
from pretix.helpers.countries import CachedCountries
from pretix.helpers.escapejson import escapejson_attr
from pretix.helpers.i18n import get_format_without_seconds
from pretix.presale.signals import question_form_fields
@@ -215,10 +214,6 @@ def guess_country(event):
return country
class QuestionCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'pretixbase/forms/widgets/checkbox_option_with_links.html'
class BaseQuestionsForm(forms.Form):
"""
This form class is responsible for asking order-related questions. This includes
@@ -246,7 +241,7 @@ class BaseQuestionsForm(forms.Form):
if item.admission and event.settings.attendee_names_asked:
self.fields['attendee_name_parts'] = NamePartsFormField(
max_length=255,
required=event.settings.attendee_names_required and not self.all_optional,
required=event.settings.attendee_names_required,
scheme=event.settings.name_scheme,
titles=event.settings.name_scheme_titles,
label=_('Attendee name'),
@@ -254,7 +249,7 @@ class BaseQuestionsForm(forms.Form):
)
if item.admission and event.settings.attendee_emails_asked:
self.fields['attendee_email'] = forms.EmailField(
required=event.settings.attendee_emails_required and not self.all_optional,
required=event.settings.attendee_emails_required,
label=_('Attendee email'),
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email),
widget=forms.EmailInput(
@@ -263,75 +258,6 @@ class BaseQuestionsForm(forms.Form):
}
)
)
if item.admission and event.settings.attendee_company_asked:
self.fields['company'] = forms.CharField(
required=event.settings.attendee_company_required and not self.all_optional,
label=_('Company'),
initial=(cartpos.company if cartpos else orderpos.company),
)
if item.admission and event.settings.attendee_addresses_asked:
self.fields['street'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
label=_('Address'),
widget=forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
'autocomplete': 'street-address'
}),
initial=(cartpos.street if cartpos else orderpos.street),
)
self.fields['zipcode'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
label=_('ZIP code'),
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
widget=forms.TextInput(attrs={
'autocomplete': 'postal-code',
}),
)
self.fields['city'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
label=_('City'),
initial=(cartpos.city if cartpos else orderpos.city),
widget=forms.TextInput(attrs={
'autocomplete': 'address-level2',
}),
)
country = (cartpos.country if cartpos else orderpos.country) or guess_country(event)
self.fields['country'] = CountryField(
countries=CachedCountries
).formfield(
required=event.settings.attendee_addresses_required and not self.all_optional,
label=_('Country'),
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
}),
)
c = [('', pgettext_lazy('address', 'Select state'))]
fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else ''
cc = None
if fprefix + 'country' in self.data:
cc = str(self.data[fprefix + 'country'])
elif country:
cc = str(country)
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
elif fprefix + 'state' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'state']
self.fields['state'] = forms.ChoiceField(
label=pgettext_lazy('address', 'State'),
required=False,
choices=c,
widget=forms.Select(attrs={
'autocomplete': 'address-level1',
}),
)
self.fields['state'].widget.is_required = True
for q in questions:
# Do we already have an answer? Provide it as the initial value
@@ -383,14 +309,12 @@ class BaseQuestionsForm(forms.Form):
initial=initial.answer if initial else None,
)
elif q.type == Question.TYPE_COUNTRYCODE:
field = CountryField(
countries=CachedCountries
).formfield(
field = CountryField().formfield(
label=label, required=required,
help_text=help_text,
widget=forms.Select,
empty_label='',
initial=initial.answer if initial else guess_country(event),
initial=initial.answer if initial else None,
)
elif q.type == Question.TYPE_CHOICE:
field = forms.ModelChoiceField(
@@ -408,7 +332,7 @@ class BaseQuestionsForm(forms.Form):
label=label, required=required,
help_text=help_text,
to_field_name='identifier',
widget=QuestionCheckboxSelectMultiple,
widget=forms.CheckboxSelectMultiple,
initial=initial.options.all() if initial else None,
)
elif q.type == Question.TYPE_FILE:
@@ -495,10 +419,6 @@ class BaseQuestionsForm(forms.Form):
def clean(self):
d = super().clean()
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not d.get('state'):
self.add_error('state', _('This field is required.'))
question_cache = {f.question.pk: f.question for f in self.fields.values() if getattr(f, 'question', None)}
def question_is_visible(parentid, qvals):
@@ -580,8 +500,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if not event.settings.invoice_address_vatid:
del self.fields['vat_id']
self.fields['country'].choices = CachedCountries()
c = [('', pgettext_lazy('address', 'Select state'))]
fprefix = self.prefix + '-' if self.prefix else ''
cc = None

View File

@@ -4,7 +4,7 @@ from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pytz import common_timezones
from pretix.base.models import User

View File

@@ -2,7 +2,7 @@ import re
from django.core.exceptions import ValidationError
from django.core.validators import BaseValidator
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from i18nfield.strings import LazyI18nString

View File

@@ -4,7 +4,7 @@ from django import forms
from django.utils.formats import get_format
from django.utils.functional import lazy
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class DatePickerWidget(forms.DateInput):

View File

@@ -3,7 +3,7 @@ from contextlib import contextmanager
from django.conf import settings
from django.utils import translation
from django.utils.formats import date_format, number_format
from django.utils.translation import gettext
from django.utils.translation import ugettext
from i18nfield.fields import ( # noqa
I18nCharField, I18nTextarea, I18nTextField, I18nTextInput,
)
@@ -69,6 +69,6 @@ class LazyLocaleException(Exception):
def __str__(self):
if self.msgargs:
return gettext(self.msg) % self.msgargs
return ugettext(self.msg) % self.msgargs
else:
return gettext(self.msg)
return ugettext(self.msg)

View File

@@ -10,7 +10,7 @@ from django.contrib.staticfiles import finders
from django.dispatch import receiver
from django.utils.formats import date_format, localize
from django.utils.translation import (
get_language, gettext, gettext_lazy, pgettext,
get_language, pgettext, ugettext, ugettext_lazy,
)
from PIL.Image import BICUBIC
from reportlab.lib import pagesizes
@@ -264,8 +264,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
invoice_to_top = 52 * mm
def _draw_invoice_to(self, canvas):
p = Paragraph(bleach.clean(self.invoice.address_invoice_to, tags=[]).strip().replace('\n', '<br />\n'),
style=self.stylesheet['Normal'])
p = Paragraph(self.invoice.address_invoice_to.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
@@ -423,7 +422,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
canvas.saveState()
canvas.setFont('OpenSansBd', 30)
canvas.setFillColorRGB(32, 0, 0)
canvas.drawRightString(self.pagesize[0] - 20 * mm, (297 - 100) * mm, gettext('TEST MODE'))
canvas.drawRightString(self.pagesize[0] - 20 * mm, (297 - 100) * mm, ugettext('TEST MODE'))
canvas.restoreState()
def _on_first_page(self, canvas: Canvas, doc):
@@ -688,7 +687,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
class Modern1Renderer(ClassicInvoiceRenderer):
identifier = 'modern1'
verbose_name = gettext_lazy('Modern Invoice Renderer (pretix 2.7)')
verbose_name = ugettext_lazy('Modern Invoice Renderer (pretix 2.7)')
bottom_margin = 16.9 * mm
top_margin = 16.9 * mm
right_margin = 20 * mm

View File

@@ -15,9 +15,7 @@ from django.utils.translation.trans_real import (
)
from pretix.base.settings import GlobalSettingsObject
from pretix.multidomain.urlreverse import (
get_event_domain, get_organizer_domain,
)
from pretix.multidomain.urlreverse import get_domain
_supported = None
@@ -233,10 +231,7 @@ class SecurityMiddleware(MiddlewareMixin):
dynamicdomain += " " + settings.SITE_URL
if hasattr(request, 'organizer') and request.organizer:
if hasattr(request, 'event') and request.event:
domain = get_event_domain(request.event, fallback=True)
else:
domain = get_organizer_domain(request.organizer)
domain = get_domain(request.organizer)
if domain:
siteurlsplit = urlsplit(settings.SITE_URL)
if siteurlsplit.port and siteurlsplit.port not in (80, 443):

View File

@@ -6,7 +6,7 @@ import django.core.validators
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
import pretix.base.validators
from pretix.base.i18n import language

View File

@@ -7,7 +7,7 @@ from django.db import migrations, models
from django.db.models import F
from django.db.models.functions import Concat
from django.utils.crypto import get_random_string
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
import pretix.base.models.auth
import pretix.base.validators

View File

@@ -1,20 +0,0 @@
# Generated by Django 2.2.9 on 2020-03-21 15:12
from django.db import migrations, models
import pretix.base.models.auth
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0146_giftcardtransaction_text'),
]
operations = [
migrations.AddField(
model_name='user',
name='session_token',
field=models.CharField(default=pretix.base.models.auth.generate_session_token, max_length=32),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 3.0.4 on 2020-03-25 10:05
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0147_user_session_token'),
]
operations = [
migrations.CreateModel(
name='CancellationRequest',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True)),
('cancellation_fee', models.DecimalField(decimal_places=2, max_digits=10)),
('refund_as_giftcard', models.BooleanField(default=False)),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cancellation_requests', to='pretixbase.Order')),
],
),
]

View File

@@ -1,46 +0,0 @@
# Generated by Django 3.0.4 on 2020-03-25 14:40
from django.db import migrations, models
from django.db.models import Count, OuterRef, Q, Subquery
from django.utils.timezone import now
def fill_cancellation_date(apps, schema_editor):
Order = apps.get_model('pretixbase', 'Order')
LogEntry = apps.get_model('pretixbase', 'LogEntry')
OrderPosition = apps.get_model('pretixbase', 'OrderPosition')
s = OrderPosition.all.filter(
order=OuterRef('pk'),
canceled=False,
).order_by().values('order').annotate(k=Count('id')).values('k')
for o in Order.objects.annotate(
pcnt=Subquery(s)
).filter(
Q(pcnt=0) | Q(pcnt__isnull=True) | Q(status="c")
).values('id').iterator():
le = LogEntry.objects.filter(
content_type__model="order",
object_id=o['id'],
action_type='pretix.event.order.canceled'
).order_by('-datetime').only('datetime').first()
if le:
Order.objects.filter(pk=o['id']).update(
cancellation_date=le.datetime,
last_modified=now()
)
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0148_cancellationrequest'),
]
operations = [
migrations.AddField(
model_name='order',
name='cancellation_date',
field=models.DateTimeField(blank=True, null=True),
),
migrations.RunPython(fill_cancellation_date, migrations.RunPython.noop)
]

View File

@@ -1,74 +0,0 @@
# Generated by Django 3.0.4 on 2020-04-01 11:24
import django_countries.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0149_order_cancellation_date'),
]
operations = [
migrations.AddField(
model_name='cartposition',
name='city',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='cartposition',
name='company',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='cartposition',
name='country',
field=django_countries.fields.CountryField(max_length=2, null=True),
),
migrations.AddField(
model_name='cartposition',
name='state',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='cartposition',
name='street',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='cartposition',
name='zipcode',
field=models.CharField(max_length=30, null=True),
),
migrations.AddField(
model_name='orderposition',
name='city',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='orderposition',
name='company',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='orderposition',
name='country',
field=django_countries.fields.CountryField(max_length=2, null=True),
),
migrations.AddField(
model_name='orderposition',
name='state',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='orderposition',
name='street',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='orderposition',
name='zipcode',
field=models.CharField(max_length=30, null=True),
),
]

View File

@@ -12,9 +12,9 @@ from django.contrib.auth.tokens import default_token_generator
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_otp.models import Device
from django_scopes import scopes_disabled
from u2flib_server.utils import (
@@ -54,10 +54,6 @@ def generate_notifications_token():
return get_random_string(length=32)
def generate_session_token():
return get_random_string(length=32)
class SuperuserPermissionSet:
def __contains__(self, item):
return True
@@ -114,7 +110,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
)
notifications_token = models.CharField(max_length=255, default=generate_notifications_token)
auth_backend = models.CharField(max_length=255, default='native')
session_token = models.CharField(max_length=32, default=generate_session_token)
objects = UserManager()
@@ -387,20 +382,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
self._staff_session_cache[session_key] = sess
return self._staff_session_cache[session_key]
def get_session_auth_hash(self):
"""
Return an HMAC that needs to
"""
key_salt = "pretix.base.models.User.get_session_auth_hash"
payload = self.password
payload += self.email
payload += self.session_token
return salted_hmac(key_salt, payload).hexdigest()
def update_session_token(self):
self.session_token = generate_session_token()
self.save(update_fields=['session_token'])
class StaffSession(models.Model):
user = models.ForeignKey('User', on_delete=models.PROTECT)

View File

@@ -1,7 +1,7 @@
from django.db import models
from django.db.models import Exists, OuterRef
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.models import LoggedModel

View File

@@ -3,7 +3,7 @@ import string
from django.db import models
from django.db.models import Max
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from pretix.base.models import LoggedModel

View File

@@ -15,10 +15,9 @@ from django.db import models
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
from django.template.defaultfilters import date as _date
from django.utils.crypto import get_random_string
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from i18nfield.fields import I18nCharField, I18nTextField
@@ -294,7 +293,7 @@ class Event(EventMixin, LoggedModel):
"This will be used in URLs, order codes, invoice numbers, and bank transfer references."),
validators=[
RegexValidator(
regex="^[a-zA-Z0-9][a-zA-Z0-9.-]*$",
regex="^[a-zA-Z0-9][a-zA-Z0-9.-]+$",
message=_("The slug may only contain letters, numbers, dots and dashes."),
),
EventSlugBanlistValidator()
@@ -371,8 +370,6 @@ class Event(EventMixin, LoggedModel):
"""
self.settings.invoice_renderer = 'modern1'
self.settings.invoice_include_expire_date = True
self.settings.ticketoutput_pdf__enabled = True
self.settings.ticketoutput_passbook__enabled = True
@property
def social_image(self):
@@ -388,7 +385,7 @@ class Event(EventMixin, LoggedModel):
if img:
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
def free_seats(self, ignore_voucher=None, sales_channel='web', include_blocked=False):
def free_seats(self, ignore_voucher=None, sales_channel='web'):
from .orders import CartPosition, Order, OrderPosition
from .vouchers import Voucher
vqs = Voucher.objects.filter(
@@ -419,7 +416,7 @@ class Event(EventMixin, LoggedModel):
vqs
)
).filter(has_order=False, has_cart=False, has_voucher=False)
if not (sales_channel in self.settings.seating_allow_blocked_seats_for_channel or include_blocked):
if sales_channel not in self.settings.seating_allow_blocked_seats_for_channel:
qs = qs.filter(blocked=False)
return qs
@@ -625,10 +622,8 @@ class Event(EventMixin, LoggedModel):
q.dependency_question = question_map[q.dependency_question_id]
q.save(update_fields=['dependency_question'])
checkin_list_map = {}
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related('limit_products'):
items = list(cl.limit_products.all())
checkin_list_map[cl.pk] = cl
cl.pk = None
cl.event = self
cl.save()
@@ -683,7 +678,7 @@ class Event(EventMixin, LoggedModel):
event_copy_data.send(
sender=self, other=other,
tax_map=tax_map, category_map=category_map, item_map=item_map, variation_map=variation_map,
question_map=question_map, checkin_list_map=checkin_list_map
question_map=question_map
)
def get_payment_providers(self, cached=False) -> dict:
@@ -1035,13 +1030,9 @@ class SubEvent(EventMixin, LoggedModel):
ordering = ("date_from", "name")
def __str__(self):
return '{} - {} {}'.format(
self.name,
self.get_date_range_display(),
date_format(self.date_from.astimezone(self.timezone), "TIME_FORMAT") if self.settings.show_times else ""
).strip()
return '{} - {}'.format(self.name, self.get_date_range_display())
def free_seats(self, ignore_voucher=None, sales_channel='web', include_blocked=False):
def free_seats(self, ignore_voucher=None, sales_channel='web'):
from .orders import CartPosition, Order, OrderPosition
from .vouchers import Voucher
vqs = Voucher.objects.filter(
@@ -1075,7 +1066,7 @@ class SubEvent(EventMixin, LoggedModel):
vqs
)
).filter(has_order=False, has_cart=False, has_voucher=False)
if not (sales_channel in self.settings.seating_allow_blocked_seats_for_channel or include_blocked):
if sales_channel not in self.settings.seating_allow_blocked_seats_for_channel:
qs = qs.filter(blocked=False)
return qs

View File

@@ -5,7 +5,7 @@ from django.core.validators import RegexValidator
from django.db import models
from django.db.models import Sum
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.banlist import banned
from pretix.base.models import LoggedModel
@@ -83,7 +83,6 @@ class GiftCard(LoggedModel):
class Meta:
unique_together = (('secret', 'issuer'),)
ordering = ("issuance",)
class GiftCardTransaction(models.Model):

View File

@@ -157,12 +157,9 @@ class Invoice(models.Model):
state_name = self.invoice_to_state
if str(self.invoice_to_country) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if COUNTRIES_WITH_STATE_IN_ADDRESS[str(self.invoice_to_country)][1] == 'long':
try:
state_name = pycountry.subdivisions.get(
code='{}-{}'.format(self.invoice_to_country, self.invoice_to_state)
).name
except:
pass
state_name = pycountry.subdivisions.get(
code='{}-{}'.format(self.invoice_to_country, self.invoice_to_state)
).name
parts = [
self.invoice_to_company,

View File

@@ -16,7 +16,7 @@ from django.utils import formats
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.timezone import is_naive, make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_countries.fields import Country
from django_scopes import ScopedManager
from i18nfield.fields import I18nCharField, I18nTextField
@@ -455,8 +455,7 @@ class Item(LoggedModel):
t = TaxedPrice(gross=price, net=price, tax=Decimal('0.00'),
rate=Decimal('0.00'), name='')
else:
t = self.tax_rule.tax(price, base_price_is=base_price_is,
currency=currency or self.event.currency)
t = self.tax_rule.tax(price, base_price_is=base_price_is, currency=currency)
if include_bundled:
for b in self.bundles.all():
@@ -1117,13 +1116,10 @@ class Question(LoggedModel):
return None
if self.type == Question.TYPE_CHOICE:
q = Q(identifier=answer)
if isinstance(answer, int) or answer.isdigit():
q |= Q(pk=answer)
o = self.options.filter(q).first()
if not o:
try:
return self.options.get(Q(pk=answer) | Q(identifier=answer))
except:
raise ValidationError(_('Invalid option selected.'))
return o
elif self.type == Question.TYPE_CHOICE_MULTIPLE:
if isinstance(answer, str):
l_ = list(self.options.filter(

View File

@@ -6,7 +6,7 @@ from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.signals import logentry_object_link

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class NotificationSetting(models.Model):

View File

@@ -4,7 +4,6 @@ import json
import logging
import os
import string
from collections import Counter
from datetime import datetime, time, timedelta
from decimal import Decimal
from typing import Any, Dict, List, Union
@@ -26,7 +25,7 @@ from django.utils.encoding import escape_uri_path
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_countries.fields import Country, CountryField
from django_scopes import ScopedManager, scopes_disabled
from i18nfield.strings import LazyI18nString
@@ -44,7 +43,6 @@ from pretix.base.services.locking import NoLockManager
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import order_gracefully_delete
from ...helpers.countries import CachedCountries
from .base import LockModel, LoggedModel
from .event import Event, SubEvent
from .items import Item, ItemVariation, Question, QuestionOption, Quota
@@ -153,9 +151,6 @@ class Order(LockModel, LoggedModel):
datetime = models.DateTimeField(
verbose_name=_("Date"), db_index=True
)
cancellation_date = models.DateTimeField(
null=True, blank=True
)
expires = models.DateTimeField(
verbose_name=_("Expiration date")
)
@@ -474,8 +469,6 @@ class Order(LockModel, LoggedModel):
"""
from .checkin import Checkin
if self.cancellation_requests.exists():
return False
positions = list(
self.positions.all().annotate(
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
@@ -701,19 +694,16 @@ class Order(LockModel, LoggedModel):
return self._is_still_available(count_waitinglist=count_waitinglist, force=force)
def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True, force=False,
check_voucher_usage=False) -> Union[bool, str]:
def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True, force=False) -> Union[bool, str]:
error_messages = {
'unavailable': _('The ordered product "{item}" is no longer available.'),
'seat_unavailable': _('The seat "{seat}" is no longer available.'),
'voucher_budget': _('The voucher "{voucher}" no longer has sufficient budget.'),
'voucher_usages': _('The voucher "{voucher}" has been used in the meantime.'),
}
now_dt = now_dt or now()
positions = self.positions.all().select_related('item', 'variation', 'seat', 'voucher')
quota_cache = {}
v_budget = {}
v_usage = Counter()
try:
for i, op in enumerate(positions):
if op.seat:
@@ -732,13 +722,6 @@ class Order(LockModel, LoggedModel):
))
v_budget[op.voucher] -= disc
if op.voucher and check_voucher_usage:
v_usage[op.voucher.pk] += 1
if v_usage[op.voucher.pk] + op.voucher.redeemed > op.voucher.max_usages:
raise Quota.QuotaExceededException(error_messages['voucher_usages'].format(
voucher=op.voucher.code
))
quotas = list(op.quotas)
if len(quotas) == 0:
raise Quota.QuotaExceededException(error_messages['unavailable'].format(
@@ -1066,13 +1049,6 @@ class AbstractPosition(models.Model):
'Seat', null=True, blank=True, on_delete=models.PROTECT
)
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'), null=True)
street = models.TextField(verbose_name=_('Address'), blank=True, null=True)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True, null=True)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True, null=True)
country = CountryField(verbose_name=_('Country'), blank=True, blank_label=_('Select country'), null=True)
state = models.CharField(max_length=255, verbose_name=pgettext_lazy('address', 'State'), blank=True, null=True)
class Meta:
abstract = True
@@ -2114,8 +2090,7 @@ class InvoiceAddress(models.Model):
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
country = CountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
countries=CachedCountries)
country = CountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'))
state = models.CharField(max_length=255, verbose_name=pgettext_lazy('address', 'State'), blank=True)
vat_id = models.CharField(max_length=255, blank=True, verbose_name=_('VAT ID'),
help_text=_('Only for business customers within the EU.'))
@@ -2222,13 +2197,6 @@ class CachedCombinedTicket(models.Model):
created = models.DateTimeField(auto_now_add=True)
class CancellationRequest(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='cancellation_requests')
created = models.DateTimeField(auto_now_add=True)
cancellation_fee = models.DecimalField(max_digits=10, decimal_places=2)
refund_as_giftcard = models.BooleanField(default=False)
@receiver(post_delete, sender=CachedTicket)
def cachedticket_delete(sender, instance, **kwargs):
if instance.file:

View File

@@ -5,7 +5,7 @@ from django.db import models
from django.db.models import Exists, OuterRef, Q
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models.base import LoggedModel
from pretix.base.validators import OrganizerSlugBanlistValidator

View File

@@ -8,7 +8,7 @@ from django.db import models
from django.db.models import F, Q
from django.utils.deconstruct import deconstructible
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import gettext, ugettext_lazy as _
from pretix.base.models import Event, Item, LoggedModel, Organizer, SubEvent

View File

@@ -4,7 +4,7 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.formats import localize
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_countries.fields import CountryField
from i18nfield.fields import I18nCharField

View File

@@ -8,7 +8,7 @@ from django.db.models import F, OuterRef, Q, Subquery, Sum
from django.db.models.functions import Coalesce
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from pretix.base.banlist import banned

View File

@@ -3,7 +3,7 @@ from datetime import timedelta
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.email import get_email_context

View File

@@ -3,7 +3,7 @@ from collections import OrderedDict, namedtuple
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.models import Event, LogEntry
from pretix.base.signals import register_notification_types
@@ -223,12 +223,6 @@ def register_default_notification_types(sender, **kwargs):
_('Order canceled'),
_('Order {order.code} has been canceled.')
),
ParametrizedOrderNotificationType(
sender,
'pretix.event.order.reactivated',
_('Order reactivated'),
_('Order {order.code} has been reactivated.')
),
ParametrizedOrderNotificationType(
sender,
'pretix.event.order.expired',

View File

@@ -325,7 +325,7 @@ class InvoiceAddressState(ImportColumn):
@property
def verbose_name(self):
return _('Invoice address') + ': ' + pgettext('address', 'State')
return _('Invoice address') + ': ' + _('State')
def clean(self, value, previous_values):
if value:
@@ -398,99 +398,6 @@ class AttendeeEmail(ImportColumn):
position.attendee_email = value
class AttendeeCompany(ImportColumn):
identifier = 'attendee_company'
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('Company')
def assign(self, value, order, position, invoice_address, **kwargs):
position.company = value or ''
class AttendeeStreet(ImportColumn):
identifier = 'attendee_street'
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('Address')
def assign(self, value, order, position, invoice_address, **kwargs):
position.address = value or ''
class AttendeeZip(ImportColumn):
identifier = 'attendee_zipcode'
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('ZIP code')
def assign(self, value, order, position, invoice_address, **kwargs):
position.zipcode = value or ''
class AttendeeCity(ImportColumn):
identifier = 'attendee_city'
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('City')
def assign(self, value, order, position, invoice_address, **kwargs):
position.city = value or ''
class AttendeeCountry(ImportColumn):
identifier = 'attendee_country'
default_value = None
@property
def initial(self):
return 'static:' + str(guess_country(self.event))
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('Country')
def static_choices(self):
return list(countries)
def clean(self, value, previous_values):
if value and not Country(value).numeric:
raise ValidationError(_("Please enter a valid country code."))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
position.country = value or ''
class AttendeeState(ImportColumn):
identifier = 'attendee_state'
@property
def verbose_name(self):
return _('Attendee address') + ': ' + _('State')
def clean(self, value, previous_values):
if value:
if previous_values.get('attendee_country') not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(_("States are not supported for this country."))
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[previous_values.get('attendee_country')]
match = [
s for s in pycountry.subdivisions.get(country_code=previous_values.get('attendee_country'))
if s.type in types and (s.code[3:] == value or s.name == value)
]
if len(match) == 0:
raise ValidationError(_("Please enter a valid state."))
return match[0].code[3:]
def assign(self, value, order, position, invoice_address, **kwargs):
position.state = value or ''
class Price(ImportColumn):
identifier = 'price'
verbose_name = gettext_lazy('Price')
@@ -689,12 +596,6 @@ def get_all_columns(event):
default.append(AttendeeNamePart(event, n, l))
default += [
AttendeeEmail(event),
AttendeeCompany(event),
AttendeeStreet(event),
AttendeeZip(event),
AttendeeCity(event),
AttendeeCountry(event),
AttendeeState(event),
Price(event),
Secret(event),
Locale(event),

View File

@@ -17,7 +17,7 @@ from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_countries import Countries
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
@@ -29,7 +29,6 @@ from pretix.base.models import (
OrderRefund, Quota,
)
from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
from pretix.base.services.cart import get_fees
from pretix.base.settings import SettingsSandbox
from pretix.base.signals import register_payment_providers
from pretix.base.templatetags.money import money_filter
@@ -1107,16 +1106,8 @@ class GiftCardPayment(BasePaymentProvider):
return
cs['gift_cards'] = cs['gift_cards'] + [gc.pk]
total = sum(p.total for p in cart['positions'])
# Recompute fees. Some plugins, e.g. pretix-servicefees, change their fee schedule if a gift card is
# applied.
fees = get_fees(
self.event, request, total, cart['invoice_address'], cs.get('payment'),
cart['raw']
)
total += sum([f.value for f in fees])
remainder = total - gc.value
if remainder > Decimal('0.00'):
remainder = cart['total'] - gc.value
if remainder >= Decimal('0.00'):
del cs['payment']
messages.success(request, _("Your gift card has been applied, but {} still need to be paid. Please select a payment method.").format(
money_filter(remainder, self.event.currency)
@@ -1219,7 +1210,6 @@ class GiftCardPayment(BasePaymentProvider):
)
refund.info_data = {
'gift_card': gc.pk,
'gift_card_code': gc.secret,
'transaction_id': trans.pk,
}
refund.done()

View File

@@ -17,7 +17,7 @@ from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.html import conditional_escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from PyPDF2 import PdfFileReader
from pytz import timezone
from reportlab.graphics import renderPDF
@@ -205,11 +205,6 @@ DEFAULT_VARIABLES = OrderedDict((
"editor_sample": _("Sample city"),
"evaluate": lambda op, order, ev: order.invoice_address.city if getattr(order, 'invoice_address', None) else ''
}),
("attendee_company", {
"label": _("Attendee company"),
"editor_sample": _("Sample company"),
"evaluate": lambda op, order, ev: op.company or (op.addon_to.company if op.addon_to else '')
}),
("addons", {
"label": _("List of Add-Ons"),
"editor_sample": _("Addon 1\nAddon 2"),

View File

@@ -7,7 +7,7 @@ from dateutil import parser
from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
BASE_CHOICES = (
@@ -324,7 +324,7 @@ class ModelRelativeDateTimeField(models.CharField):
return value.to_string()
return value
def from_db_value(self, value, expression, connection):
def from_db_value(self, value, expression, connection, context):
if value is None:
return None
return RelativeDateWrapper.from_string(value)

View File

@@ -2,15 +2,17 @@ import logging
from decimal import Decimal
from django.db import transaction
from django.db.models import Count, Exists, IntegerField, OuterRef, Subquery
from django.db.models import (
Count, Exists, IntegerField, OuterRef, Subquery, Sum,
)
from i18nfield.strings import LazyI18nString
from pretix.base.decimal import round_decimal
from pretix.base.email import get_email_context
from pretix.base.i18n import language
from pretix.base.models import (
Event, InvoiceAddress, Order, OrderFee, OrderPosition, OrderRefund,
SubEvent, User, WaitingListEntry,
Event, InvoiceAddress, Order, OrderFee, OrderPosition, SubEvent, User,
WaitingListEntry,
)
from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import SendMailException, TolerantDict, mail
@@ -83,10 +85,10 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
def cancel_event(self, event: Event, subevent: int, auto_refund: bool, keep_fee_fixed: str,
keep_fee_percentage: str, keep_fees: list=None, manual_refund: bool=False,
send: bool=False, send_subject: dict=None, send_message: dict=None,
keep_fee_percentage: str, keep_fees: bool,
send: bool, send_subject: dict, send_message: dict,
send_waitinglist: bool=False, send_waitinglist_subject: dict={}, send_waitinglist_message: dict={},
user: int=None, refund_as_giftcard: bool=False):
user: int=None):
send_subject = LazyI18nString(send_subject)
send_message = LazyI18nString(send_message)
send_waitinglist_subject = LazyI18nString(send_waitinglist_subject)
@@ -149,30 +151,27 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool, keep_fee_
for o in orders_to_cancel.only('id', 'total'):
try:
fee = Decimal('0.00')
fee_sum = Decimal('0.00')
keep_fee_objects = []
if keep_fees:
for f in o.fees.all():
if f.fee_type in keep_fees:
fee += f.value
keep_fee_objects.append(f)
fee_sum += f.value
fee += o.fees.filter(
fee_type__in=(OrderFee.FEE_TYPE_PAYMENT, OrderFee.FEE_TYPE_SHIPPING, OrderFee.FEE_TYPE_SERVICE,
OrderFee.FEE_TYPE_CANCELLATION)
).aggregate(
s=Sum('value')
)['s'] or 0
if keep_fee_percentage:
fee += Decimal(keep_fee_percentage) / Decimal('100.00') * (o.total - fee_sum)
fee += Decimal(keep_fee_percentage) / Decimal('100.00') * (o.total - fee)
if keep_fee_fixed:
fee += Decimal(keep_fee_fixed)
fee = round_decimal(min(fee, o.payment_refund_sum), event.currency)
_cancel_order(o.pk, user, send_mail=False, cancellation_fee=fee, keep_fees=keep_fee_objects)
_cancel_order(o.pk, user, send_mail=False, cancellation_fee=fee)
refund_amount = o.payment_refund_sum
try:
if auto_refund:
_try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True,
source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard)
finally:
if send:
_send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all())
if auto_refund:
_try_auto_refund(o.pk)
if send:
_send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all())
except LockTimeoutException:
logger.exception("Could not cancel order")
failed += 1
@@ -213,7 +212,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool, keep_fee_
refund_amount = o.payment_refund_sum - o.total
if auto_refund:
_try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True, source=OrderRefund.REFUND_SOURCE_ADMIN)
_try_auto_refund(o.pk)
if send:
_send_mail(o, send_subject, send_message, subevent, refund_amount, user, positions)

View File

@@ -9,7 +9,7 @@ from django.db import DatabaseError, transaction
from django.db.models import Count, Exists, OuterRef, Q
from django.dispatch import receiver
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext as _
from django_scopes import scopes_disabled
from pretix.base.channels import get_all_sales_channels
@@ -213,7 +213,7 @@ class CartManager:
has_variations=Count('variations'),
).filter(
id__in=[i for i in item_ids if i and i not in self._items_cache]
).order_by()
)
})
self._variations_cache.update({
v.pk: v
@@ -221,7 +221,7 @@ class CartManager:
'quotas'
).select_related('item', 'item__event').filter(
id__in=[i for i in variation_ids if i and i not in self._variations_cache]
).order_by()
)
})
def _check_max_cart_size(self):
@@ -1005,8 +1005,9 @@ class CartManager:
with lockfn() as now_dt:
with transaction.atomic():
self.now_dt = now_dt
self._extend_expiry_of_valid_existing_positions()
err = self._perform_operations() or err
if not err:
self._extend_expiry_of_valid_existing_positions()
if err:
raise CartError(err)

View File

@@ -2,7 +2,7 @@ from django.db import transaction
from django.db.models import Prefetch
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from pretix.base.models import (
Checkin, CheckinList, Order, OrderPosition, Question, QuestionOption,

View File

@@ -2,7 +2,7 @@ from typing import Any, Dict
from django.core.files.base import ContentFile
from django.utils.timezone import override
from django.utils.translation import gettext
from django.utils.translation import ugettext
from pretix.base.i18n import LazyLocaleException, language
from pretix.base.models import CachedFile, Event, cachedfile_name
@@ -26,7 +26,7 @@ def export(event: Event, fileid: str, provider: str, form_data: Dict[str, Any])
d = ex.render(form_data)
if d is None:
raise ExportError(
gettext('Your export did not contain any data.')
ugettext('Your export did not contain any data.')
)
file.filename, file.type, data = d
file.file.save(cachedfile_name(file, file.filename), ContentFile(data))

View File

@@ -15,7 +15,7 @@ from django.dispatch import receiver
from django.utils import timezone
from django.utils.formats import date_format
from django.utils.timezone import now
from django.utils.translation import gettext as _, pgettext
from django.utils.translation import pgettext, ugettext as _
from django_countries.fields import Country
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString

View File

@@ -20,7 +20,7 @@ from django.core.mail import (
)
from django.core.mail.message import SafeMIMEText
from django.template.loader import get_template
from django.utils.translation import gettext as _, pgettext
from django.utils.translation import pgettext, ugettext as _
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
@@ -276,6 +276,20 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
cm = lambda: scopes_disabled() # noqa
with cm():
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
if inv.file:
try:
with language(inv.order.locale):
email.attach(
pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf',
inv.file.file.read(),
'application/pdf'
)
except:
logger.exception('Could not attach invoice to email')
pass
if event:
if order:
try:
@@ -330,21 +344,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
if inv.file:
try:
with language(inv.order.locale):
email.attach(
pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf',
inv.file.file.read(),
'application/pdf'
)
except:
logger.exception('Could not attach invoice to email')
pass
email = global_email_filter.send_chained(event, 'message', message=email, user=user, order=order)
try:

View File

@@ -15,7 +15,7 @@ from django.db.transaction import get_connection
from django.dispatch import receiver
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django_scopes import scopes_disabled
from pretix.api.models import OAuthApplication
@@ -94,54 +94,6 @@ def mark_order_paid(*args, **kwargs):
raise NotImplementedError("This method is no longer supported since pretix 1.17.")
def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None):
"""
Reactivates a canceled order. If ``force`` is not set to ``True``, this will fail if there is not
enough quota.
"""
if order.status != Order.STATUS_CANCELED:
raise OrderError('The order was not canceled.')
with order.event.lock() as now_dt:
is_available = force or order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True)
if is_available is True:
if order.payment_refund_sum >= order.total:
order.status = Order.STATUS_PAID
else:
order.status = Order.STATUS_PENDING
order.cancellation_date = None
order.set_expires(now(),
order.event.subevents.filter(id__in=[p.subevent_id for p in order.positions.all()]))
with transaction.atomic():
order.save(update_fields=['expires', 'status', 'cancellation_date'])
order.log_action(
'pretix.event.order.reactivated',
user=user,
auth=auth,
data={
'expires': order.expires,
}
)
for position in order.positions.all():
if position.voucher:
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') + 1))
for gc in position.issued_gift_cards.all():
gc = GiftCard.objects.select_for_update().get(pk=gc.pk)
gc.transactions.create(value=position.price, order=order)
break
else:
raise OrderError(is_available)
order_approved.send(order.event, order=order)
if order.status == Order.STATUS_PAID:
order_paid.send(order.event, order=order)
num_invoices = order.invoices.filter(is_cancellation=False).count()
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
generate_invoice(order)
def extend_order(order: Order, new_date: datetime, force: bool=False, user: User=None, auth=None):
"""
Extends the deadline of an order. If the order is already expired, the quota will be checked to
@@ -165,10 +117,9 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
'state_change': was_expired
}
)
if was_expired:
num_invoices = order.invoices.filter(is_cancellation=False).count()
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices:
generate_invoice(order)
if order.status == Order.STATUS_PENDING:
@@ -320,13 +271,12 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device=None, oauth_application=None,
cancellation_fee=None, keep_fees=None):
cancellation_fee=None):
"""
Mark this order as canceled
:param order: The order to change
:param user: The user that performed the change
"""
# If new actions are added to this function, make sure to add the reverse operation to reactivate_order()
with transaction.atomic():
if isinstance(order, int):
order = Order.objects.select_for_update().get(pk=order)
@@ -368,38 +318,31 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
position.canceled = True
position.save(update_fields=['canceled'])
new_fee = cancellation_fee
for fee in order.fees.all():
if keep_fees and fee in keep_fees:
new_fee -= fee.value
else:
fee.canceled = True
fee.save(update_fields=['canceled'])
fee.canceled = True
fee.save(update_fields=['canceled'])
if new_fee:
f = OrderFee(
fee_type=OrderFee.FEE_TYPE_CANCELLATION,
value=new_fee,
tax_rule=order.event.settings.tax_rate_default,
order=order,
)
f._calculate_tax()
f.save()
f = OrderFee(
fee_type=OrderFee.FEE_TYPE_CANCELLATION,
value=cancellation_fee,
tax_rule=order.event.settings.tax_rate_default,
order=order,
)
f._calculate_tax()
f.save()
if order.payment_refund_sum < cancellation_fee:
raise OrderError(_('The cancellation fee cannot be higher than the payment credit of this order.'))
order.status = Order.STATUS_PAID
order.total = cancellation_fee
order.cancellation_date = now()
order.save(update_fields=['status', 'cancellation_date', 'total'])
order.total = f.value
order.save(update_fields=['status', 'total'])
if i:
invoices.append(generate_invoice(order))
else:
with order.event.lock():
order.status = Order.STATUS_CANCELED
order.cancellation_date = now()
order.save(update_fields=['status', 'cancellation_date'])
order.save(update_fields=['status'])
for position in order.positions.all():
if position.voucher:
@@ -407,7 +350,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
order.log_action('pretix.event.order.canceled', user=user, auth=api_token or oauth_application or device,
data={'cancellation_fee': cancellation_fee})
order.cancellation_requests.all().delete()
if send_mail:
email_template = order.event.settings.mail_text_order_canceled
@@ -691,7 +633,7 @@ def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvid
total = sum([c.price for c in positions])
for recv, resp in order_fee_calculation.send(sender=event, invoice_address=address, total=total,
meta_info=meta_info, positions=positions, gift_cards=gift_cards):
meta_info=meta_info, positions=positions):
if resp:
fees += resp
total += sum(f.value for f in fees)
@@ -1028,6 +970,7 @@ def send_download_reminders(sender, **kwargs):
F('event__date_from')
)
).filter(
status=Order.STATUS_PAID,
download_reminder_sent=False,
datetime__lte=now() - timedelta(hours=2),
first_date__gte=today,
@@ -1057,22 +1000,6 @@ def send_download_reminders(sender, **kwargs):
if not all([r for rr, r in allow_ticket_download.send(event, order=o)]):
continue
if not o.ticket_download_available:
continue
positions = o.positions.select_related('item')
if o.status != Order.STATUS_PAID:
if o.status != Order.STATUS_PENDING or o.require_approval or not \
o.event.settings.ticket_download_pending:
continue
send = False
for p in positions:
if p.generate_ticket:
send = True
break
if not send:
continue
with language(o.locale):
o.download_reminder_sent = True
o.save(update_fields=['download_reminder_sent'])
@@ -1090,9 +1017,6 @@ def send_download_reminders(sender, **kwargs):
if event.settings.mail_send_download_reminder_attendee:
for p in o.positions.all():
if not p.generate_ticket:
continue
if p.subevent_id:
reminder_date = (p.subevent.date_from - timedelta(days=days)).replace(
hour=0, minute=0, second=0, microsecond=0
@@ -1224,27 +1148,6 @@ class OrderChangeManager:
self._quotadiff.subtract(position.quotas)
self._operations.append(self.SubeventOperation(position, subevent))
def change_item_and_subevent(self, position: OrderPosition, item: Item, variation: Optional[ItemVariation],
subevent: SubEvent):
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
raise OrderError(self.error_messages['product_without_variation'])
price = get_price(item, variation, voucher=position.voucher, subevent=subevent,
invoice_address=self._invoice_address)
if price is None: # NOQA
raise OrderError(self.error_messages['product_invalid'])
new_quotas = (variation.quotas.filter(subevent=subevent)
if variation else item.quotas.filter(subevent=subevent))
if not new_quotas:
raise OrderError(self.error_messages['quota_missing'])
self._quotadiff.update(new_quotas)
self._quotadiff.subtract(position.quotas)
self._operations.append(self.ItemOperation(position, item, variation))
self._operations.append(self.SubeventOperation(position, subevent))
def regenerate_secret(self, position: OrderPosition):
self._operations.append(self.RegenerateSecretOperation(position))
@@ -1913,8 +1816,7 @@ def perform_order(self, event: Event, payment_provider: str, positions: List[str
raise OrderError(str(error_messages['busy']))
def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=OrderRefund.REFUND_SOURCE_BUYER,
refund_as_giftcard=False):
def _try_auto_refund(order):
notify_admin = False
error = False
if isinstance(order, int):
@@ -1923,55 +1825,14 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
if refund_amount <= Decimal('0.00'):
return
if refund_as_giftcard:
proposals = {}
can_auto_refund = True
can_auto_refund_sum = refund_amount
with transaction.atomic():
giftcard = order.event.organizer.issued_gift_cards.create(
currency=order.event.currency,
testmode=order.testmode
)
giftcard.log_action('pretix.giftcards.created', data={})
r = order.refunds.create(
order=order,
payment=None,
source=source,
state=OrderRefund.REFUND_STATE_CREATED,
execution_date=now(),
amount=can_auto_refund_sum,
provider='giftcard',
info=json.dumps({
'gift_card': giftcard.pk
})
)
try:
r.payment_provider.execute_refund(r)
except PaymentException as e:
with transaction.atomic():
r.state = OrderRefund.REFUND_STATE_FAILED
r.save()
order.log_action('pretix.event.order.refund.failed', {
'local_id': r.local_id,
'provider': r.provider,
'error': str(e)
})
error = True
notify_admin = True
else:
if r.state != OrderRefund.REFUND_STATE_DONE:
notify_admin = True
else:
proposals = order.propose_auto_refunds(refund_amount)
can_auto_refund_sum = sum(proposals.values())
can_auto_refund = (allow_partial and can_auto_refund_sum) or can_auto_refund_sum == refund_amount
proposals = order.propose_auto_refunds(refund_amount)
can_auto_refund = sum(proposals.values()) == refund_amount
if can_auto_refund:
for p, value in proposals.items():
with transaction.atomic():
r = order.refunds.create(
payment=p,
source=source,
source=OrderRefund.REFUND_SOURCE_BUYER,
state=OrderRefund.REFUND_STATE_CREATED,
amount=value,
provider=p.provider
@@ -1995,24 +1856,10 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
error = True
notify_admin = True
else:
if r.state not in (OrderRefund.REFUND_STATE_TRANSIT, OrderRefund.REFUND_STATE_DONE):
if r.state != OrderRefund.REFUND_STATE_DONE:
notify_admin = True
if refund_amount - can_auto_refund_sum > Decimal('0.00'):
if manual_refund:
with transaction.atomic():
r = order.refunds.create(
source=source,
state=OrderRefund.REFUND_STATE_CREATED,
amount=refund_amount - can_auto_refund_sum,
provider='manual'
)
order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id,
'provider': r.provider,
})
else:
notify_admin = True
elif refund_amount != Decimal('0.00'):
notify_admin = True
if notify_admin:
order.log_action('pretix.event.order.refund.requested')
@@ -2027,13 +1874,13 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
@scopes_disabled()
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None,
device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False):
device=None, cancellation_fee=None, try_auto_refund=False):
try:
try:
ret = _cancel_order(order, user, send_mail, api_token, device, oauth_application,
cancellation_fee)
if try_auto_refund:
_try_auto_refund(order, refund_as_giftcard=refund_as_giftcard)
_try_auto_refund(order)
return ret
except LockTimeoutException:
self.retry()

View File

@@ -1,5 +1,5 @@
from django.db.models import Count, Q
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.i18n import LazyLocaleException
from pretix.base.models import CartPosition, Seat
@@ -25,7 +25,7 @@ def validate_plan_change(event, subevent, plan):
subevent=subevent,
).filter(
Q(has_v=True) | Q(has_op=True)
).values_list('seat_guid', flat=True).order_by()
).values_list('seat_guid', flat=True)
)
new_seats = {
ss.guid for ss in plan.iter_all_seats()
@@ -40,7 +40,7 @@ def generate_seats(event, subevent, plan, mapping):
current_seats = {}
for s in event.seats.select_related('product').annotate(
has_op=Count('orderposition'), has_v=Count('vouchers')
).filter(subevent=subevent).order_by():
).filter(subevent=subevent):
if s.seat_guid in current_seats:
s.delete() # Duplicates should not exist
else:

View File

@@ -8,7 +8,7 @@ from dateutil.parser import parse
from django.conf import settings
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import CachedFile, Event, cachedfile_name
from pretix.base.services.tasks import ProfiledEventTask

View File

@@ -6,7 +6,7 @@ from django.db.models import (
Case, Count, DateTimeField, F, Max, OuterRef, Subquery, Sum, Value, When,
)
from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
from pretix.base.models.event import SubEvent

View File

@@ -3,7 +3,7 @@ import os
from django.core.files.base import ContentFile
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django_scopes import scopes_disabled
from pretix.base.i18n import language

View File

@@ -5,7 +5,7 @@ from datetime import timedelta
import requests
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, gettext_noop
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django_scopes import scopes_disabled
from i18nfield.strings import LazyI18nString
@@ -91,7 +91,7 @@ def send_update_notification_email():
gs.settings.update_check_email,
_('pretix update available'),
LazyI18nString.from_gettext(
gettext_noop(
ugettext_noop(
'Hi!\n\nAn update is available for pretix or for one of the plugins you installed in your '
'pretix installation. Please click on the following link for more information:\n\n {url} \n\n'
'You can always find information on the latest updates on the pretix.eu blog:\n\n'

View File

@@ -11,7 +11,7 @@ from django.core.files import File
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import Model
from django.utils.translation import (
gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy,
pgettext, pgettext_lazy, ugettext_lazy as _, ugettext_noop,
)
from django_countries import countries
from hierarkey.models import GlobalSettingsBase, Hierarkey
@@ -19,7 +19,6 @@ from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
from rest_framework import serializers
from pretix.api.serializers.fields import ListMultipleChoiceField
from pretix.api.serializers.i18n import I18nField
from pretix.base.models.tax import TaxRule
from pretix.base.reldate import (
@@ -105,44 +104,6 @@ DEFAULTS = {
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_emails_asked'}),
)
},
'attendee_company_asked': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Ask for company per ticket"),
)
},
'attendee_company_required': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Require company per ticket"),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_company_asked'}),
)
},
'attendee_addresses_asked': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Ask for postal addresses per ticket"),
)
},
'attendee_addresses_required': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Require postal addresses per ticket"),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_addresses_asked'}),
)
},
'order_email_asked_twice': {
'default': 'False',
'type': bool,
@@ -404,7 +365,7 @@ DEFAULTS = {
'type': RelativeDateWrapper,
'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField,
'form_kwargs': dict(
'form_kawrgs': dict(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the number of "
"days configured above. If you use the event series feature and an order contains tickets for "
@@ -678,7 +639,7 @@ DEFAULTS = {
'locales': {
'default': json.dumps([settings.LANGUAGE_CODE]),
'type': list,
'serializer_class': ListMultipleChoiceField,
'serializer_class': serializers.MultipleChoiceField,
'serializer_kwargs': dict(
choices=settings.LANGUAGES,
required=True,
@@ -707,17 +668,6 @@ DEFAULTS = {
label=_("Default language"),
)
},
'show_dates_on_frontpage': {
'default': 'True',
'type': bool,
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Show event times and dates on the ticket shop"),
help_text=_("If disabled, no date or time will be shown on the ticket shop's front page. This settings "
"does however not affect the display in other locations."),
)
},
'show_date_to': {
'default': 'True',
'type': bool,
@@ -820,8 +770,8 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Allow users to download tickets"),
help_text=_("If this is off, nobody can download a ticket."),
label=_("Use feature"),
help_text=_("Use pretix to generate tickets for the user to download and print out."),
)
},
'ticket_download_date': {
@@ -842,11 +792,7 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Generate tickets for add-on products"),
help_text=_('By default, tickets are only issued for products selected individually, not for add-on '
'products. With this option, a separate ticket is issued for every add-on product as well.'),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download',
'data-checkbox-dependency-visual': 'on'}),
label=_("Offer to download tickets separately for add-on products"),
)
},
'ticket_download_nonadm': {
@@ -855,11 +801,7 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Generate tickets for all products"),
help_text=_('If turned off, tickets are only issued for products that are marked as an "admission ticket"'
'in the product settings. You can also turn off tickt issuing in every product separately.'),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download',
'data-checkbox-dependency-visual': 'on'}),
label=_("Generate tickets for non-admission products"),
)
},
'ticket_download_pending': {
@@ -868,10 +810,7 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Generate tickets for pending orders"),
help_text=_('If turned off, ticket downloads are only possible after an order has been marked as paid.'),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download',
'data-checkbox-dependency-visual': 'on'}),
label=_("Offer to download tickets even before an order is paid"),
)
},
'event_list_availability': {
@@ -956,66 +895,6 @@ DEFAULTS = {
label=_("Keep a percentual cancellation fee"),
)
},
'cancel_allow_user_paid_adjust_fees': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Allow customers to voluntarily choose a lower refund"),
help_text=_("With this option enabled, your customers can choose to get a smaller refund to support you.")
)
},
'cancel_allow_user_paid_adjust_fees_explanation': {
'default': LazyI18nString.from_gettext(gettext_noop(
'However, if you want us to help keep the lights on here, please consider using the slider below to '
'request a smaller refund. Thank you!'
)),
'type': LazyI18nString,
'serializer_class': I18nField,
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Voluntary lower refund explanation"),
widget=I18nTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown in between the explanation of how the refunds work and the slider "
"which your customers can use to choose the amount they would like to receive. You can use it "
"e.g. to explain choosing a lower refund will help your organisation.")
)
},
'cancel_allow_user_paid_require_approval': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Customers can only request a cancellation that needs to be approved by the event organizer "
"before the order is canceled and a refund is issued."),
)
},
'cancel_allow_user_paid_refund_as_giftcard': {
'default': 'off',
'type': str,
'serializer_class': serializers.ChoiceField,
'serializer_kwargs': dict(
choices=[
('off', _('All refunds are issued to the original payment method')),
('option', _('Customers can choose between a gift card and a refund to their payment method')),
('force', _('All refunds are issued as gift cards')),
],
),
'form_class': forms.ChoiceField,
'form_kwargs': dict(
label=_('Refund method'),
choices=[
('off', _('All refunds are issued to the original payment method')),
('option', _('Customers can choose between a gift card and a refund to their payment method')),
('force', _('All refunds are issued as gift cards')),
],
widget=forms.RadioSelect,
# When adding a new ordering, remember to also define it in the event model
)
},
'cancel_allow_user_paid_until': {
'default': None,
'type': RelativeDateWrapper,
@@ -1115,7 +994,7 @@ DEFAULTS = {
},
'mail_text_resend_link': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
you receive this message because you asked us to send you the link
to your order for {event}.
@@ -1128,7 +1007,7 @@ Your {event} team"""))
},
'mail_text_resend_all_links': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
somebody requested a list of your orders for {event}.
The list is as follows:
@@ -1140,7 +1019,7 @@ Your {event} team"""))
},
'mail_text_order_free_attendee': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name},
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello {attendee_name},
you have been registered for {event} successfully.
@@ -1152,7 +1031,7 @@ Your {event} team"""))
},
'mail_text_order_free': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
your order for {event} was successful. As you only ordered free products,
no payment is required.
@@ -1169,7 +1048,7 @@ Your {event} team"""))
},
'mail_text_order_placed_require_approval': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your order for {event}. Since you ordered
a product that requires approval by the event organizer, we ask you to
@@ -1183,7 +1062,7 @@ Your {event} team"""))
},
'mail_text_order_placed': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your order for {event} with a total value
of {total_with_currency}. Please complete your payment before {expire_date}.
@@ -1202,7 +1081,7 @@ Your {event} team"""))
},
'mail_text_order_placed_attendee': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name},
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello {attendee_name},
a ticket for {event} has been ordered for you.
@@ -1214,7 +1093,7 @@ Your {event} team"""))
},
'mail_text_order_changed': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
your order for {event} has been changed.
@@ -1226,7 +1105,7 @@ Your {event} team"""))
},
'mail_text_order_paid': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your payment for {event}. Thank you!
@@ -1244,7 +1123,7 @@ Your {event} team"""))
},
'mail_text_order_paid_attendee': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name},
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello {attendee_name},
a ticket for {event} that has been ordered for you is now paid.
@@ -1260,7 +1139,7 @@ Your {event} team"""))
},
'mail_text_order_expire_warning': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we did not yet receive a full payment for your order for {event}.
Please keep in mind that we only guarantee your order if we receive
@@ -1274,7 +1153,7 @@ Your {event} team"""))
},
'mail_text_waiting_list': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
you submitted yourself to the waiting list for {event},
for the product {product}.
@@ -1297,7 +1176,7 @@ Your {event} team"""))
},
'mail_text_order_canceled': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
your order {code} for {event} has been canceled.
@@ -1309,7 +1188,7 @@ Your {event} team"""))
},
'mail_text_order_approved': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we approved your order for {event} and will be happy to welcome you
at our event.
@@ -1325,7 +1204,7 @@ Your {event} team"""))
},
'mail_text_order_denied': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
unfortunately, we denied your order request for {event}.
@@ -1340,7 +1219,7 @@ Your {event} team"""))
},
'mail_text_order_custom_mail': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
You can change your order details and view the status of your order at
{url}
@@ -1358,7 +1237,7 @@ Your {event} team"""))
},
'mail_text_download_reminder_attendee': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name},
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello {attendee_name},
you are registered for {event}.
@@ -1370,7 +1249,7 @@ Your {event} team"""))
},
'mail_text_download_reminder': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
you bought a ticket for {event}.
@@ -1486,32 +1365,6 @@ Your {event} team"""))
widget=I18nTextarea
)
},
'banner_text': {
'default': '',
'type': LazyI18nString,
'serializer_class': I18nField,
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Banner text (top)"),
widget=I18nTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown above every page of your shop. Please only use this for "
"very important messages.")
)
},
'banner_text_bottom': {
'default': '',
'type': LazyI18nString,
'serializer_class': I18nField,
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Banner text (bottom)"),
widget=I18nTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown below every page of your shop. Please only use this for "
"very important messages.")
)
},
'voucher_explanation_text': {
'default': '',
'type': LazyI18nString,
@@ -1526,7 +1379,7 @@ Your {event} team"""))
)
},
'checkout_email_helptext': {
'default': LazyI18nString.from_gettext(gettext_noop(
'default': LazyI18nString.from_gettext(ugettext_noop(
'Make sure to enter a valid email address. We will send you an order '
'confirmation including a link that you need to access your order later.'
)),

View File

@@ -7,7 +7,7 @@ from django.db.models import Max, Q
from django.db.models.functions import Greatest
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.api.serializers.order import (
AnswerSerializer, InvoiceAddressSerializer,
@@ -194,23 +194,15 @@ class WaitingListShredder(BaseDataShredder):
le.save(update_fields=['data', 'shredded'])
class AttendeeInfoShredder(BaseDataShredder):
verbose_name = _('Attendee info')
identifier = 'attendee_info'
description = _('This will remove all attendee names and postal addresses from order positions, as well as logged '
'changes to them.')
class AttendeeNameShredder(BaseDataShredder):
verbose_name = _('Attendee names')
identifier = 'attendee_names'
description = _('This will remove all attendee names from order positions, as well as logged changes to them.')
def generate_files(self) -> List[Tuple[str, str, str]]:
yield 'attendee-info.json', 'application/json', json.dumps({
'{}-{}'.format(op.order.code, op.positionid): {
'name': op.attendee_name,
'company': op.company,
'street': op.street,
'zipcode': op.zipcode,
'city': op.city,
'country': str(op.country) if op.country else None,
'state': op.state
} for op in OrderPosition.all.filter(
yield 'attendee-names.json', 'application/json', json.dumps({
'{}-{}'.format(op.order.code, op.positionid): op.attendee_name
for op in OrderPosition.all.filter(
order__event=self.event
).filter(
Q(Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False))
@@ -222,10 +214,8 @@ class AttendeeInfoShredder(BaseDataShredder):
OrderPosition.all.filter(
order__event=self.event
).filter(
Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False) |
Q(company__isnull=False) | Q(street__isnull=False) | Q(zipcode__isnull=False) | Q(city__isnull=False)
).update(attendee_name_cached=None, attendee_name_parts={'_shredded': True}, company=None, street=None,
zipcode=None, city=None)
Q(Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False))
).update(attendee_name_cached=None, attendee_name_parts={'_shredded': True})
for le in self.event.logentry_set.filter(action_type="pretix.event.order.modified").exclude(data=""):
d = le.parsed_data
@@ -237,14 +227,6 @@ class AttendeeInfoShredder(BaseDataShredder):
d['data'][i]['attendee_name_parts'] = {
'_legacy': ''
}
if 'company' in row:
d['data'][i]['company'] = ''
if 'street' in row:
d['data'][i]['street'] = ''
if 'zipcode' in row:
d['data'][i]['zipcode'] = ''
if 'city' in row:
d['data'][i]['city'] = ''
le.data = json.dumps(d)
le.shredded = True
le.save(update_fields=['data', 'shredded'])
@@ -375,7 +357,7 @@ class PaymentInfoShredder(BaseDataShredder):
def register_payment_provider(sender, **kwargs):
return [
EmailAddressShredder,
AttendeeInfoShredder,
AttendeeNameShredder,
InvoiceAddressShredder,
QuestionAnswerShredder,
InvoiceShredder,

View File

@@ -341,16 +341,6 @@ as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_reactivated = EventPluginSignal(
providing_args=["order"]
)
"""
This signal is sent out every time a canceled order is reactivated. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_expired = EventPluginSignal(
providing_args=["order"]
)
@@ -482,7 +472,7 @@ As with all event-plugin signals, the ``sender`` keyword argument will contain t
"""
event_copy_data = EventPluginSignal(
providing_args=["other", "tax_map", "category_map", "item_map", "question_map", "variation_map", "checkin_list_map"]
providing_args=["other", "tax_map", "category_map", "item_map", "question_map", "variation_map"]
)
"""
This signal is sent out when a new event is created as a clone of an existing event, i.e.
@@ -494,9 +484,9 @@ but you might need to modify that data.
The ``sender`` keyword argument will contain the event of the **new** event. The ``other``
keyword argument will contain the event to **copy from**. The keyword arguments
``tax_map``, ``category_map``, ``item_map``, ``question_map``, ``variation_map`` and
``checkin_list_map`` contain mappings from object IDs in the original event to objects
in the new event of the respective types.
``tax_map``, ``category_map``, ``item_map``, ``question_map``, and ``variation_map`` contain
mappings from object IDs in the original event to objects in the new event of the respective
types.
"""
item_copy_data = EventPluginSignal(
@@ -527,7 +517,7 @@ an OrderedDict of (setting name, form field).
"""
order_fee_calculation = EventPluginSignal(
providing_args=['positions', 'invoice_address', 'meta_info', 'total', 'gift_cards']
providing_args=['positions', 'invoice_address', 'meta_info', 'total']
)
"""
This signals allows you to add fees to an order while it is being created. You are expected to
@@ -538,8 +528,7 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
argument will contain the cart positions and ``invoice_address`` the invoice address (useful for
tax calculation). The argument ``meta_info`` contains the order's meta dictionary. The ``total``
keyword argument will contain the total cart sum without any fees. You should not rely on this
``total`` value for fee calculations as other fees might interfere. The ``gift_cards`` argument lists
the gift cards in use.
``total`` value for fee calculations as other fees might interfere.
"""
order_fee_type_name = EventPluginSignal(

View File

@@ -1,2 +0,0 @@
{% load rich_text %}
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label|rich_text_snippet }}</label>{% endif %}

View File

@@ -7,7 +7,7 @@ from django import template
from django.conf import settings
from django.core import signing
from django.urls import reverse
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.http import is_safe_url
from django.utils.safestring import mark_safe
register = template.Library()
@@ -66,7 +66,7 @@ ALLOWED_PROTOCOLS = ['http', 'https', 'mailto', 'tel']
def safelink_callback(attrs, new=False):
url = attrs.get((None, 'href'), '/')
if not url_has_allowed_host_and_scheme(url, allowed_hosts=None) 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'

View File

@@ -6,7 +6,7 @@ from zipfile import ZipFile
from django import forms
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Event, Order, OrderPosition
from pretix.base.settings import SettingsSandbox
@@ -142,7 +142,7 @@ class BaseTicketOutput:
return OrderedDict([
('_enabled',
forms.BooleanField(
label=_('Enable ticket format'),
label=_('Enable output'),
required=False,
)),
])

View File

@@ -133,7 +133,7 @@ def timeline_for_event(event, subevent=None):
if not event.has_subevents:
days = event.settings.get('mail_days_download_reminder', as_type=int)
if days is not None and event.settings.ticket_download:
if days is not None:
reminder_date = (ev.date_from - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
tl.append(TimelineEvent(
event=event, subevent=subevent,

View File

@@ -1,7 +1,7 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class BanlistValidator:

View File

@@ -5,7 +5,7 @@ from django.middleware.csrf import REASON_NO_CSRF_COOKIE, REASON_NO_REFERER
from django.template import TemplateDoesNotExist, loader
from django.template.loader import get_template
from django.utils.functional import Promise
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import requires_csrf_token
from sentry_sdk import last_event_id

View File

@@ -85,20 +85,10 @@ class BaseQuestionsViewMixin:
for k, v in form.cleaned_data.items():
if k == 'attendee_name_parts':
form.pos.attendee_name_parts = v if v else None
form.pos.save()
elif k == 'attendee_email':
form.pos.attendee_email = v if v != '' else None
elif k == 'company':
form.pos.company = v if v != '' else None
elif k == 'street':
form.pos.street = v if v != '' else None
elif k == 'zipcode':
form.pos.zipcode = v if v != '' else None
elif k == 'city':
form.pos.city = v if v != '' else None
elif k == 'country':
form.pos.country = v if v != '' else None
elif k == 'state':
form.pos.state = v if v != '' else None
form.pos.save()
elif k.startswith('question_'):
field = form.fields[k]
if hasattr(field, 'answer'):
@@ -129,7 +119,7 @@ class BaseQuestionsViewMixin:
meta_info['question_form_data'][k] = v
form.pos.meta_info = json.dumps(meta_info)
form.pos.save()
form.pos.save(update_fields=['meta_info'])
return not failed
def _save_to_answer(self, field, answer, value):

View File

@@ -6,7 +6,7 @@ from django.conf import settings
from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from pretix.celery_app import app

View File

@@ -16,7 +16,6 @@ from pretix.control.navigation import (
from ..helpers.i18n import (
get_javascript_format, get_javascript_output_format, get_moment_locale,
)
from ..multidomain.urlreverse import get_event_domain
from .signals import html_head, nav_topbar
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
@@ -26,12 +25,6 @@ def contextprocessor(request):
"""
Adds data to all template contexts
"""
if not hasattr(request, '_pretix_control_default_context'):
request._pretix_control_default_context = _default_context(request)
return request._pretix_control_default_context
def _default_context(request):
try:
url = resolve(request.path_info)
except Resolver404:
@@ -58,7 +51,7 @@ def _default_context(request):
if request.event.settings.get('payment_term_weekdays'):
_js_payment_weekdays_disabled = '[0,6]'
ctx['has_domain'] = get_event_domain(request.event, fallback=True) is not None
ctx['has_domain'] = request.event.organizer.domains.exists()
if not request.event.testmode:
with scope(organizer=request.organizer):

View File

@@ -6,7 +6,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files import File
from django.forms.utils import from_current_timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from ...base.forms import I18nModelForm
# Import for backwards compatibility with okd import paths

View File

@@ -1,4 +1,4 @@
from urllib.parse import urlencode, urlparse
from urllib.parse import urlencode
from django import forms
from django.conf import settings
@@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_countries import Countries
from django_countries.fields import LazyTypedChoiceField
from i18nfield.forms import (
@@ -32,7 +32,6 @@ from pretix.control.forms import (
SplitDateTimeField, SplitDateTimePickerWidget,
)
from pretix.control.forms.widgets import Select2
from pretix.multidomain.models import KnownDomain
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.plugins.banktransfer.payment import BankTransfer
from pretix.presale.style import get_fonts
@@ -292,15 +291,6 @@ class EventUpdateForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.change_slug = kwargs.pop('change_slug', False)
self.domain = kwargs.pop('domain', False)
kwargs.setdefault('initial', {})
self.instance = kwargs['instance']
if self.domain and self.instance:
initial_domain = self.instance.domains.first()
if initial_domain:
kwargs['initial'].setdefault('domain', initial_domain.domainname)
super().__init__(*args, **kwargs)
if not self.change_slug:
self.fields['slug'].widget.attrs['readonly'] = 'readonly'
@@ -308,47 +298,6 @@ class EventUpdateForm(I18nModelForm):
self.fields['location'].widget.attrs['placeholder'] = _(
'Sample Conference Center\nHeidelberg, Germany'
)
if self.domain:
self.fields['domain'] = forms.CharField(
max_length=255,
label=_('Custom domain'),
required=False,
help_text=_('You need to configure the custom domain in the webserver beforehand.')
)
def clean_domain(self):
d = self.cleaned_data['domain']
if d:
if d == urlparse(settings.SITE_URL).hostname:
raise ValidationError(
_('You cannot choose the base domain of this installation.')
)
if KnownDomain.objects.filter(domainname=d).exclude(event=self.instance.pk).exists():
raise ValidationError(
_('This domain is already in use for a different event or organizer.')
)
return d
def save(self, commit=True):
instance = super().save(commit)
if self.domain:
current_domain = instance.domains.first()
if self.cleaned_data['domain']:
if current_domain and current_domain.domainname != self.cleaned_data['domain']:
current_domain.delete()
KnownDomain.objects.create(
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
)
elif not current_domain:
KnownDomain.objects.create(
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
)
elif current_domain:
current_domain.delete()
instance.cache.clear()
return instance
def clean_slug(self):
if self.change_slug:
@@ -490,7 +439,6 @@ class EventSettingsForm(SettingsForm):
'checkout_email_helptext',
'presale_has_ended_text',
'voucher_explanation_text',
'show_dates_on_frontpage',
'show_date_to',
'show_times',
'show_items_outside_presale_period',
@@ -515,13 +463,7 @@ class EventSettingsForm(SettingsForm):
'attendee_names_required',
'attendee_emails_asked',
'attendee_emails_required',
'attendee_company_asked',
'attendee_company_required',
'attendee_addresses_asked',
'attendee_addresses_required',
'confirm_text',
'banner_text',
'banner_text_bottom',
'order_email_asked_twice',
'last_order_modification_date',
]
@@ -571,10 +513,6 @@ class CancelSettingsForm(SettingsForm):
'cancel_allow_user_paid_keep',
'cancel_allow_user_paid_keep_fees',
'cancel_allow_user_paid_keep_percentage',
'cancel_allow_user_paid_adjust_fees',
'cancel_allow_user_paid_adjust_fees_explanation',
'cancel_allow_user_paid_refund_as_giftcard',
'cancel_allow_user_paid_require_approval',
]
@@ -1250,7 +1188,7 @@ class QuickSetupForm(I18nForm):
class QuickSetupProductForm(I18nForm):
name = I18nFormField(
max_length=200, # Max length of Quota.name
max_length=255,
label=_("Product name"),
widget=I18nTextInput
)

View File

@@ -8,7 +8,7 @@ from django.db.models.functions import Coalesce, ExtractWeekDay
from django.urls import reverse, reverse_lazy
from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import (
@@ -220,7 +220,6 @@ class EventOrderFilterForm(OrderFilterForm):
('underpaid', _('Underpaid')),
('pendingpaid', _('Pending (but fully paid)')),
('testmode', _('Test mode')),
('rc', _('Cancellation requested')),
),
required=False,
)
@@ -306,10 +305,6 @@ class EventOrderFilterForm(OrderFilterForm):
Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
| Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
)
elif fdata.get('status') == 'rc':
qs = qs.filter(
cancellation_requests__isnull=False
)
elif fdata.get('status') == 'pendingpaid':
qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
qs = qs.filter(

View File

@@ -1,7 +1,7 @@
from collections import OrderedDict
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from pretix.base.forms import SettingsForm

View File

@@ -7,7 +7,7 @@ from django.db.models import Max
from django.forms.formsets import DELETION_FIELD_NAME
from django.urls import reverse
from django.utils.translation import (
gettext as __, gettext_lazy as _, pgettext_lazy,
pgettext_lazy, ugettext as __, ugettext_lazy as _,
)
from django_scopes.forms import (
SafeModelChoiceField, SafeModelMultipleChoiceField,
@@ -303,7 +303,6 @@ class ItemCreateForm(I18nModelForm):
self.instance.allow_cancel = self.cleaned_data['copy_from'].allow_cancel
self.instance.min_per_order = self.cleaned_data['copy_from'].min_per_order
self.instance.max_per_order = self.cleaned_data['copy_from'].max_per_order
self.instance.generate_tickets = self.cleaned_data['copy_from'].generate_tickets
self.instance.checkin_attention = self.cleaned_data['copy_from'].checkin_attention
self.instance.free_price = self.cleaned_data['copy_from'].free_price
self.instance.original_price = self.cleaned_data['copy_from'].original_price

View File

@@ -8,7 +8,7 @@ from django.db import models
from django.urls import reverse
from django.utils.timezone import make_aware, now
from django.utils.translation import (
gettext_lazy as _, gettext_noop, pgettext_lazy,
gettext_noop, pgettext_lazy, ugettext_lazy as _,
)
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
@@ -16,9 +16,7 @@ from i18nfield.strings import LazyI18nString
from pretix.base.email import get_available_placeholders
from pretix.base.forms import I18nModelForm, PlaceholderValidator
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import (
InvoiceAddress, ItemAddOn, Order, OrderFee, OrderPosition,
)
from pretix.base.models import InvoiceAddress, ItemAddOn, Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
from pretix.control.forms.widgets import Select2
@@ -241,7 +239,6 @@ class OrderPositionAddForm(forms.Form):
)
def __init__(self, *args, **kwargs):
self.items = kwargs.pop('items')
order = kwargs.pop('order')
super().__init__(*args, **kwargs)
@@ -251,13 +248,11 @@ class OrderPositionAddForm(forms.Form):
ia = None
choices = []
for i in self.items:
for i in order.event.items.prefetch_related('variations').all():
pname = str(i)
if not i.is_available():
pname += ' ({})'.format(_('inactive'))
variations = list(i.variations.all())
if i.tax_rule: # performance optimization
i.tax_rule.event = order.event
if variations:
for v in variations:
p = get_price(i, v, invoice_address=ia)
@@ -267,11 +262,7 @@ class OrderPositionAddForm(forms.Form):
p = get_price(i, invoice_address=ia)
choices.append((str(i.pk), '%s (%s)' % (pname, p.print(order.event.currency))))
self.fields['itemvar'].choices = choices
if order.event.cache.get_or_set(
'has_addon_products',
default=lambda: ItemAddOn.objects.filter(base_item__event=order.event).exists(),
timeout=300
):
if ItemAddOn.objects.filter(base_item__event=order.event).exists():
self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related(
'item', 'variation'
)
@@ -300,12 +291,10 @@ class OrderPositionAddForm(forms.Form):
class OrderPositionAddFormset(forms.BaseFormSet):
def __init__(self, *args, **kwargs):
self.order = kwargs.pop('order', None)
self.items = kwargs.pop('items')
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['order'] = self.order
kwargs['items'] = self.items
return super()._construct_form(i, **kwargs)
@property
@@ -316,7 +305,6 @@ class OrderPositionAddFormset(forms.BaseFormSet):
empty_permitted=True,
use_required_attribute=False,
order=self.order,
items=self.items,
)
self.add_fields(form, None)
return form
@@ -356,7 +344,6 @@ class OrderPositionChangeForm(forms.Form):
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance')
items = kwargs.pop('items')
initial = kwargs.get('initial', {})
initial['price'] = instance.price
@@ -385,7 +372,7 @@ class OrderPositionChangeForm(forms.Form):
choices = [
('', _('(Unchanged)'))
]
for i in items:
for i in instance.order.event.items.prefetch_related('variations').all():
pname = str(i)
if not i.is_available():
pname += ' ({})'.format(_('inactive'))
@@ -537,29 +524,14 @@ class EventCancelForm(forms.Form):
subevent = forms.ModelChoiceField(
SubEvent.objects.none(),
label=pgettext_lazy('subevent', 'Date'),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
required=True,
empty_label=None
)
auto_refund = forms.BooleanField(
label=_('Automatically refund money if possible'),
initial=True,
required=False
)
manual_refund = forms.BooleanField(
label=_('Create manual refund if the payment method odes not support automatic refunds'),
widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_auto_refund'}),
initial=True,
required=False,
help_text=_('If checked, all payments with a payment method not supporting automatic refunds will be on your '
'manual refund to-do list. Do not check if you want to refund some of the orders by offsetting '
'with different orders or issuing gift cards.')
)
refund_as_giftcard = forms.BooleanField(
label=_('Refund order value to a gift card instead instead of the original payment method'),
widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_auto_refund'}),
initial=False,
required=False,
)
keep_fee_fixed = forms.DecimalField(
label=_("Keep a fixed cancellation fee"),
max_digits=10, decimal_places=2,
@@ -570,13 +542,8 @@ class EventCancelForm(forms.Form):
max_digits=10, decimal_places=2,
required=False
)
keep_fees = forms.MultipleChoiceField(
label=_("Keep fees"),
widget=forms.CheckboxSelectMultiple,
choices=[(k, v) for k, v in OrderFee.FEE_TYPES if k != OrderFee.FEE_TYPE_GIFTCARD],
help_text=_('The selected types of fees will not be refunded but instead added to the cancellation fee. Fees '
'are never refunded in when an order in an event series is only partially canceled since it '
'consists of tickets for multiple dates.'),
keep_fees = forms.BooleanField(
label=_("Keep payment, shipping and service fees"),
required=False,
)
send = forms.BooleanField(
@@ -614,7 +581,6 @@ class EventCancelForm(forms.Form):
self.fields['send_subject'] = I18nFormField(
label=_("Subject"),
required=True,
widget_kwargs={'attrs': {'data-display-dependency': '#id_send'}},
initial=_('Canceled: {event}'),
widget=I18nTextInput,
locales=self.event.settings.get('locales'),
@@ -623,7 +589,6 @@ class EventCancelForm(forms.Form):
label=_('Message'),
widget=I18nTextarea,
required=True,
widget_kwargs={'attrs': {'data-display-dependency': '#id_send'}},
locales=self.event.settings.get('locales'),
initial=LazyI18nString.from_gettext(gettext_noop(
'Hello,\n\n'
@@ -633,7 +598,6 @@ class EventCancelForm(forms.Form):
'Your {event} team'
))
)
self._set_field_placeholders('send_subject', ['event_or_subevent', 'refund_amount', 'position_or_address',
'order', 'event'])
self._set_field_placeholders('send_message', ['event_or_subevent', 'refund_amount', 'position_or_address',
@@ -643,7 +607,6 @@ class EventCancelForm(forms.Form):
required=True,
initial=_('Canceled: {event}'),
widget=I18nTextInput,
widget_kwargs={'attrs': {'data-display-dependency': '#id_send_waitinglist'}},
locales=self.event.settings.get('locales'),
)
self.fields['send_waitinglist_message'] = I18nFormField(
@@ -651,7 +614,6 @@ class EventCancelForm(forms.Form):
widget=I18nTextarea,
required=True,
locales=self.event.settings.get('locales'),
widget_kwargs={'attrs': {'data-display-dependency': '#id_send_waitinglist'}},
initial=LazyI18nString.from_gettext(gettext_noop(
'Hello,\n\n'
'with this email, we regret to inform you that {event} has been canceled.\n\n'
@@ -672,10 +634,11 @@ class EventCancelForm(forms.Form):
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
'data-placeholder': pgettext_lazy('subevent', 'Date')
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
self.fields['subevent'].required = True
else:
del self.fields['subevent']
change_decimal_field(self.fields['keep_fee_fixed'], self.event.currency)

View File

@@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes.forms import SafeModelMultipleChoiceField
from i18nfield.forms import I18nFormField, I18nTextarea
@@ -95,10 +95,9 @@ class OrganizerUpdateForm(OrganizerForm):
raise ValidationError(
_('You cannot choose the base domain of this installation.')
)
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.pk,
event__isnull=True).exists():
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.pk).exists():
raise ValidationError(
_('This domain is already in use for a different event or organizer.')
_('This domain is already in use for a different organizer.')
)
return d

View File

@@ -7,7 +7,7 @@ from django.urls import reverse
from django.utils.dates import MONTHS, WEEKDAYS
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.forms import I18nInlineFormSet
from pretix.base.forms import I18nModelForm

Some files were not shown because too many files have changed in this diff Show More