Compare commits

..
Author SHA1 Message Date
Mira Weller eff815056b Fix test case 2024-11-29 14:37:37 +01:00
Mira Weller 45391b104a Require at least one of street, zipcode, city if invoice_address_required 2024-11-29 14:15:36 +01:00
Mira Weller 54d3d20d70 Fix typo, require at least one of the address parts 2024-11-29 14:12:22 +01:00
Mira Weller be1cb12286 Add test case for country without zip code 2024-11-29 13:49:47 +01:00
Mira Weller c65b7f711d Simplify value comparison (state multiselect is not supported anyway) 2024-11-29 13:34:06 +01:00
Mira Weller 8e3d5511e2 Make the test case happy 2024-11-29 13:18:09 +01:00
9acfcf2526 Apply suggestions from code review
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-11-27 14:42:21 +01:00
Mira Weller 32fd9f2f4d Distinguish between addon product address forms in pretixcontrol 2024-11-25 21:53:16 +01:00
Mira Weller 47ec03baac use street instead of city as indicator whether address is filled 2024-11-25 21:46:48 +01:00
Mira Weller 04884ce874 lint 2024-11-25 21:38:56 +01:00
Mira Weller 3337783d1f Optional zipcodes/cities for attendee addresses as well 2024-11-25 21:30:54 +01:00
Mira Weller 391bbc25f8 Keep selected state on load 2024-11-25 21:30:15 +01:00
Mira Weller 68017cc8cb Always mark street address as required 2024-11-25 21:00:54 +01:00
Mira Weller 5fee16035f Distinguish between addon product address forms 2024-11-25 21:00:25 +01:00
Mira Weller 6d32aa5220 Load address format information when selecting country 2024-11-25 20:52:10 +01:00
Mira Weller 3461c59cfe Move countries-with-vat-id logic to addressform.js 2024-11-25 16:08:20 +01:00
Mira Weller 21b4dacee9 Correctly apply "required" attribute to address state field
(we try to use it from the country field, which is a <select> tag with default value and therefore never has a "required" attribute - we need to take the information from the .required class of the container)
2024-11-25 13:37:54 +01:00
Mira Weller 2663e9ff32 Move country-dependent state field JS logic to separate file
(avoids code duplication for presale and control)
2024-11-25 13:35:00 +01:00
91 changed files with 14274 additions and 15026 deletions
-5
View File
@@ -288,7 +288,6 @@ Example::
[django]
secret=j1kjps5a5&4ilpn912s7a1!e2h!duz^i3&idu@_907s$wrz@x-
debug=off
passwords_argon2=on
``secret``
The secret to be used by Django for signing and verification purposes. If this
@@ -304,10 +303,6 @@ Example::
.. WARNING:: Never set this to ``True`` in production!
``passwords_argon``
Use the ``argon2`` algorithm for password hashing. Disable on systems with a small number of CPU cores (currently
less than 8).
``profile``
Enable code profiling for a random subset of requests. Disabled by default, see
:ref:`perf-monitoring` for details.
+3 -2
View File
@@ -231,10 +231,11 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;
+3 -2
View File
@@ -216,10 +216,11 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;
+3 -3
View File
@@ -53,7 +53,7 @@ dependencies = [
"django-phonenumber-field==7.3.*",
"django-redis==5.4.*",
"django-scopes==2.0.*",
"django-statici18n==2.6.*",
"django-statici18n==2.5.*",
"djangorestframework==3.15.*",
"dnspython==2.7.*",
"drf_ujson2==1.7.*",
@@ -76,7 +76,7 @@ dependencies = [
"phonenumberslite==8.13.*",
"Pillow==11.0.*",
"pretix-plugin-build",
"protobuf==5.29.*",
"protobuf==5.28.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.22",
@@ -107,7 +107,7 @@ dependencies = [
[project.optional-dependencies]
memcached = ["pylibmc"]
dev = [
"aiohttp==3.11.*",
"aiohttp==3.10.*",
"coverage",
"coveralls",
"fakeredis==2.26.*",
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2024.12.0.dev0"
__version__ = "2024.11.0.dev0"
+3 -8
View File
@@ -277,10 +277,6 @@ class NamePartsFormField(forms.MultiValueField):
return value
def name_parts_is_empty(name_parts_dict):
return not any(k != "_scheme" and v for k, v in name_parts_dict.items())
class WrappedPhonePrefixSelect(Select):
initial = None
@@ -1147,16 +1143,15 @@ class BaseInvoiceAddressForm(forms.ModelForm):
validate_address # local import to prevent impact on startup time
data = self.cleaned_data
if not data.get('is_business'):
data['company'] = ''
data['vat_id'] = ''
if data.get('is_business') and not ask_for_vat_id(data.get('country')):
data['vat_id'] = ''
if self.address_validation and self.event.settings.invoice_address_required and not self.all_optional:
if self.event.settings.invoice_address_required:
if data.get('is_business') and not data.get('company'):
raise ValidationError({"company": _('You need to provide a company name.')})
if not data.get('is_business') and name_parts_is_empty(data.get('name_parts', {})):
if not data.get('is_business') and not data.get('name_parts'):
raise ValidationError(_('You need to provide your name.'))
if not data.get('street') and not data.get('zipcode') and not data.get('city'):
raise ValidationError({"street": _('This field is required.')})
@@ -1171,7 +1166,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if all(
not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts')
) and name_parts_is_empty(data.get('name_parts', {})):
) and len(data.get('name_parts', {})) == 1:
# Do not save the country if it is the only field set -- we don't know the user even checked it!
self.cleaned_data['country'] = ''
@@ -9,7 +9,6 @@ from decimal import Decimal
import django.core.validators
import django.db.models.deletion
import i18nfield.fields
from argon2.exceptions import HashingError
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.db import migrations, models
@@ -26,14 +25,7 @@ def initial_user(apps, schema_editor):
user = User(email='admin@localhost')
user.is_staff = True
user.is_superuser = True
try:
user.password = make_password('admin')
except HashingError:
raise Exception(
"Could not hash password of initial user with argon2id. If this is a system with less than 8 CPU cores, "
"you might need to disable argon2id by setting `passwords_argon2=off` in the `[django]` section of the "
"pretix.cfg configuration file."
)
user.password = make_password('admin')
user.save()
-14
View File
@@ -159,24 +159,10 @@ class Membership(models.Model):
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
return f'{self.membership_type.name}: {self.attendee_name} ({ds} {de})'
@property
def percentage_used(self):
if self.membership_type.max_usages and self.usages:
return int(self.usages / self.membership_type.max_usages * 100)
return 0
@property
def attendee_name(self):
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
@property
def expired(self):
return time_machine_now() > self.date_end
@property
def not_yet_valid(self):
return time_machine_now() < self.date_start
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
if valid_from_not_chosen:
return not self.canceled and self.date_end >= time_machine_now()
+1 -12
View File
@@ -209,24 +209,13 @@ def get_best_name(position_or_address, parts=False):
def base_placeholders(sender, **kwargs):
from pretix.multidomain.urlreverse import build_absolute_uri
def _event_sample(event):
if event.has_subevents:
se = event.subevents.first()
if se:
return se.name
return event.name
ph = [
SimpleFunctionalTextPlaceholder(
'event', ['event'], lambda event: event.name, lambda event: event.name
),
SimpleFunctionalTextPlaceholder(
'event', ['event_or_subevent'], lambda event_or_subevent: event_or_subevent.name,
_event_sample,
),
SimpleFunctionalTextPlaceholder(
'event_series_name', ['event', 'event_or_subevent'], lambda event, event_or_subevent: event.name,
lambda event: event.name
lambda event_or_subevent: event_or_subevent.name
),
SimpleFunctionalTextPlaceholder(
'event_slug', ['event'], lambda event: event.slug, lambda event: event.slug
-34
View File
@@ -1,34 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django import template
from django.utils.html import format_html
register = template.Library()
@register.simple_tag
def icon(key, *args, **kwargs):
return format_html(
'<span class="fa fa-{} {}" aria-hidden="true"></span>',
key,
kwargs["class"] if "class" in kwargs else "",
)
@@ -1,42 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django import template
from django.utils.html import format_html, mark_safe
register = template.Library()
@register.simple_tag
def textbubble(type, *args, **kwargs):
return format_html(
'<span class="textbubble-{}">{}',
type or "info",
"" if "icon" not in kwargs else format_html(
'<i class="fa fa-{}" aria-hidden="true"></i> ',
kwargs["icon"]
)
)
@register.simple_tag
def endtextbubble():
return mark_safe('</span>')
-10
View File
@@ -136,11 +136,6 @@ class EventWizardBasicsForm(I18nModelForm):
choices=settings.LANGUAGES,
label=_("Default language"),
)
no_taxes = forms.BooleanField(
label=_("I don't want to specify taxes now"),
help_text=_("You can always configure tax rates later."),
required=False,
)
tax_rate = forms.DecimalField(
label=_("Sales tax rate"),
help_text=_("Do you need to pay sales tax on your tickets? In this case, please enter the applicable tax rate "
@@ -228,11 +223,6 @@ class EventWizardBasicsForm(I18nModelForm):
raise ValidationError({
'timezone': _('Your default locale must be specified.')
})
if not data.get("no_taxes") and not data.get("tax_rate"):
raise ValidationError({
'tax_rate': _('You have not specified a tax rate. If you do not want us to compute sales taxes, please '
'check "{field}" above.').format(field=self.fields["no_taxes"].label)
})
# change timezone
zone = ZoneInfo(data.get('timezone'))
@@ -41,10 +41,7 @@
{% endif %}
{% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.no_taxes layout="control" %}
<div data-display-dependency="#{{ form.no_taxes.id_for_label }}" data-inverse>
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
</div>
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Display settings" %}</legend>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
"POT-Creation-Date: 2024-11-18 15:30+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
"PO-Revision-Date: 2024-10-01 22:52+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/"
"pretix-js/hu/>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/hu/>\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.3\n"
"X-Generator: Weblate 5.7.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -779,7 +779,7 @@ msgstr "A kosár lejárt"
#: pretix/static/pretixpresale/js/ui/main.js:588
#: pretix/static/pretixpresale/js/ui/main.js:607
msgid "Time zone:"
msgstr "Időzona:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:598
msgid "Your local time:"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -381,8 +381,8 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
]
)
self._set_field_placeholders('subject', ['event', 'order', 'event_or_subevent'])
self._set_field_placeholders('template', ['event', 'order', 'event_or_subevent'])
self._set_field_placeholders('subject', ['event', 'order'])
self._set_field_placeholders('template', ['event', 'order'])
choices = [(e, l) for e, l in Order.STATUS_CHOICE if e != 'n']
choices.insert(0, ('n__valid_if_pending', _('payment pending but already confirmed')))
@@ -1,33 +0,0 @@
# Generated by Django 4.2.16 on 2024-11-13 13:43
from django.db import migrations
from django.db.models import Q
from i18nfield.strings import LazyI18nString
def migrate_placeholders(apps, schema_editor):
Rule = apps.get_model("sendmail", "Rule")
for r in Rule.objects.filter(
Q(template__contains="{event}") | Q(subject__contains="{event}"),
event__has_subevents=True
):
r.template = LazyI18nString({
k: v.replace("{event}", "{event_series_name}") for k, v in r.template.data.items()
})
r.subject = LazyI18nString({
k: v.replace("{event}", "{event_series_name}") for k, v in r.subject.data.items()
})
r.save(update_fields=["template", "subject"])
class Migration(migrations.Migration):
dependencies = [
("sendmail", "008_remove_scheduled_mails"),
]
operations = [
migrations.RunPython(
migrate_placeholders,
migrations.RunPython.noop,
)
]
+3 -19
View File
@@ -174,12 +174,7 @@ class ScheduledMail(models.Model):
ia = InvoiceAddress(order=o)
if send_to_orders and o.email:
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
try:
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
@@ -197,23 +192,12 @@ class ScheduledMail(models.Model):
for p in positions:
try:
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
position=p,
event_or_subevent=self.subevent or e,
)
email_ctx = get_email_context(event=e, order=o, invoice_address=ia, position=p)
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
elif not o_sent and o.email:
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
+1 -7
View File
@@ -492,18 +492,12 @@ class StripeSettingsHolder(BasePaymentProvider):
# label=_('PayPal'),
# disabled=self.event.currency not in [
# 'EUR', 'GBP', 'USD', 'CHF', 'CZK', 'DKK', 'NOK', 'PLN', 'SEK', 'AUD', 'CAD', 'HKD', 'NZD', 'SGD'
# ],
# help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
# 'before they work properly.'),
# required=False,
# ]
# )),
('method_mobilepay',
forms.BooleanField(
label=_('MobilePay'),
disabled=self.event.currency not in ['DKK', 'EUR', 'NOK', 'SEK'],
help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
'before they work properly.'),
required=False,
)),
] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
)
+2 -2
View File
@@ -160,7 +160,7 @@ class BaseCheckoutFlowStep:
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
return eventreverse(self.request.event, 'presale:event.index', kwargs=kwargs)
else:
return prev.get_step_url(request) + '?dir=prev'
return prev.get_step_url(request)
def get_next_url(self, request):
n = self.get_next_applicable(request)
@@ -662,7 +662,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
if len(self.forms) == 0 and len(self.cross_selling_data) == 0 and self.is_completed(request):
return redirect(self.get_prev_url(request) if request.GET.get('dir') == 'prev' else self.get_next_url(request))
return redirect(self.get_next_url(request))
return TemplateFlowStep.get(self, request)
def _clean_category(self, form, category):
-1
View File
@@ -133,7 +133,6 @@ class InvoiceAddressForm(BaseInvoiceAddressForm):
class InvoiceNameForm(InvoiceAddressForm):
address_validation = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1,29 +1,31 @@
{% load i18n %}
{% load bootstrap3 %}
{% load textbubble %}
{# Changes should be replicated in pretixcontrol/orders/fragment_order_status.html and in pretix/base/models/orders.py #}
{% if order.status == "n" %}
{% if order.require_approval %}
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Approval pending" %}{% endtextbubble %}
{% trans "Approval pending" %}
{% elif order.total == 0 %}
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Confirmation pending" context "order state" %}{% endtextbubble %}
{% trans "Confirmation pending" context "order state" %}
{% elif event.settings.payment_pending_hidden %}
{# intentionally left blank #}
{% elif order.valid_if_pending %}
{% textbubble "info" icon="info-circle" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
{% trans "Confirmed" context "order state" %}
{% else %}
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Payment pending" %}{% endtextbubble %}
{% trans "Payment pending" %}
{% endif %}
{% if not event.settings.payment_pending_hidden %}
<i class="status-dot fa fa-circle {% if order.valid_if_pending %}text-info{% else %}text-warning{% endif %}" aria-hidden="true"></i>
{% endif %}
{% elif order.status == "p" %}
{% if order.count_positions == 0 %}
{% textbubble "info" icon="info-circle" %}{% trans "Canceled (paid fee)" %}{% endtextbubble %}
{% trans "Canceled (paid fee)" %} <i class="status-dot fa fa-info-circle text-info" aria-hidden="true"></i>
{% elif order.total == 0 %}
{% textbubble "success" icon="check" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
{% trans "Confirmed" context "order state" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
{% else %}
{% textbubble "success" icon="check" %}{% trans "Paid" %}{% endtextbubble %}
{% trans "Paid" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
{% endif %}
{% elif order.status == "e" %}
{% textbubble "danger" icon="minus" %}{% trans "Expired" %}{% endtextbubble %}
{% trans "Expired" %} <i class="status-dot fa fa-minus-circle text-danger" aria-hidden="true"></i>
{% elif order.status == "c" %}
{% textbubble "danger" icon="times" %}{% trans "Canceled" %}{% endtextbubble %}
{% trans "Canceled" %} <i class="status-dot fa fa-times-circle text-danger" aria-hidden="true"></i>
{% endif %}
@@ -4,9 +4,9 @@
{% if request.organizer.settings.customer_accounts %}
<nav class="loginstatus" aria-label="{% trans "customer account" %}">
{% if request.customer %}
<a href="{% abseventurl request.organizer "presale:organizer.customer.index" %}"
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}"
aria-label="{% trans "View customer account" %}" data-placement="bottom"
title="{% trans "View customer account" %}" data-toggle="tooltip">
title="{% trans "View user profile" %}" data-toggle="tooltip">
<span class="fa fa-user" aria-hidden="true"></span>
{{ request.customer.name|default:request.customer.email }}</a>
<a href="{% if request.event_domain %}{% abseventurl request.event "presale:organizer.customer.logout" %}{% else %}{% abseventurl request.organizer "presale:organizer.customer.logout" %}{% endif %}?next={{ request.path|urlencode }}%3F{{ request.META.QUERY_STRING|urlencode }}"
@@ -1,39 +1,33 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Delete address" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "address-card-o" %} <b>{% trans "Delete address" %}</b>
</h3>
</div>
<div class="panel-body account-addresses">
<p>
{% trans "Do you really want to delete the following address from your account?" %}
</p>
<address>
{{ address.describe|linebreaksbr }}
</address>
</div>
</div>
{% block content %}
<h2>
{% trans "Delete address" %}
</h2>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following address from your account?" %}
</p>
<address>
{{ address.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.addresses" %}">
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% icon "trash" %}
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}
@@ -1,42 +0,0 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Addresses" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "address-card-o" %}
<b>{% trans "Addresses" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if invoice_addresses %}
<ul class="full-width-list alternating-rows account-addresses">
<li class="row">
{% for ia in invoice_addresses %}
{% if forloop.counter0 and forloop.counter0|divisibleby:4 %}
</li>
<li class="row">
{% endif %}
<div class="col-md-3 col-xs-12">
<address>{{ ia.describe|linebreaksbr }}</address>
<p class="blank-after">
<a href="{% abseventurl request.organizer "presale:organizer.customer.address.delete" id=ia.id %}"
class="btn btn-danger btn-sm">
{% icon "trash" %}
{% trans "Delete" %}
</a>
</p>
</div>
{% endfor %}
</li>
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any addresses in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -1,55 +0,0 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block content %}
<h2>
{% trans "Your account" %}
</h2>
<div class="blank-after">
<dl class="row">
<div class="col-sm-6">
<dt>{{ customer.name }}</dt>
<dd>{{ customer.email }}</dd>
{% if customer.phone %}
<dd>{% icon "phone" %} {{ customer.phone }}</dd>
{% endif %}
<dd>
<ul class="list-inline">
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.change" %}">
{% icon "edit" %}
{% trans "Change account information" %}
</a>
</li>
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.password" %}">
{% icon "key" %}
{% trans "Change password" %}
</a>
</li>
</ul>
</dd>
<dd>
</dd>
</div>
<div class="col-sm-6 text-right">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
</div>
</dl>
<nav class="subnav row" aria-label="{% trans "customer account information" %}">
<ul class="list-inline blank-after col-xs-12">
{% for nav in sub_nav %}
<li>
<a href="{{ nav.url }}"{% if nav.active %} class="active"{% endif %}>
{% icon nav.icon %}{{ nav.label }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
{% block inner %}
{% endblock %}
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load urlreplace %}
{% load bootstrap3 %}
{% block title %}{% trans "Log in" %}{% endblock %}
{% block content %}
@@ -18,7 +18,6 @@
{% bootstrap_form form %}
<div class="form-group buttons">
<button type="submit" class="btn btn-primary btn-lg btn-block">
{% icon "sign-in" %}
{% trans "Log in" %}
</button>
</div>
@@ -36,7 +35,6 @@
<div class="col-md-6">
<a class="btn btn-link btn-block"
href="{% eventurl request.organizer "presale:organizer.customer.register" %}">
{% icon "address-book-o" %}
{% trans "Create account" %}
</a>
</div>
@@ -1,127 +1,96 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load textbubble %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Your membership" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
{% block content %}
<h2>
{% trans "Your membership" %}
</h2>
<div class="panel panel-primary items">
<div class="panel-heading">
<h3 class="panel-title">
{% if membership.membership_type.transferable %}
{% icon "users" %}
{% else %}
{% icon "id-badge" %}
{% endif %}
<b>{% trans "Your membership" %}</b>
{% if membership.testmode %}
<span class="h6">
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</span>
{% endif %}
{% trans "Details" %}
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Membership type" %}</dt>
<dd>{% if membership.canceled %}<del>{% endif %}
{{ membership.membership_type.name }}
{% if membership.canceled %}</del>
<small>
{% textbubble "danger" icon="times" %}
{% trans "Canceled" %}
{% endtextbubble %}
</small>
{% endif %}
<br><small class="text-muted">
{% if membership.membership_type.transferable %}
({% trans "transferable" %})
{% else %}
({% trans "not transferable" %})
{% endif %}
</small>
</dd>
<dd>{{ membership.membership_type.name }}</dd>
<dt>{% trans "Valid from" %}</dt>
<dd>{{ membership.date_start|date:"SHORT_DATETIME_FORMAT" }}
<dt>{% trans "Valid until" %}</dt>
<dd>{{ membership.date_end|date:"SHORT_DATETIME_FORMAT" }}
<dt>{% trans "Attendee name" %}</dt>
<dd>{{ membership.attendee_name|default_if_none:"" }}
<dd>{{ membership.attendee_name }}
<dt>{% trans "Maximum usages" %}</dt>
<dd>{{ membership.membership_type.max_usages|default_if_none:"" }}</dd>
</dl>
</div>
</div>
<div class="panel panel-default">
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Usages" %}
</h3>
</div>
<div class="panel-body">
{% if usages %}
<ul class="full-width-list alternating-rows">
<table class="panel-body table table-hover">
<caption class="sr-only">{% trans "Usages" %}</caption>
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "Event" %}</th>
<th>{% trans "Product" %}</th>
<th>{% trans "Order date" %}</th>
<th class="text-right">{% trans "Status" %}</th>
<th class="text-right"></th>
</tr>
</thead>
<tbody>
{% for op in usages %}
<li class="row">
<dl>
<div class="col-md-4 col-sm-5 col-xs-12">
<dt class="sr-only">{% trans "Order" %}</dt>
<dd><strong>
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}" target="_blank">
{% icon "shopping-cart" %}
{{ op.order.code }}-{{ op.positionid }}
</a>
</strong>
<small>{% include "pretixpresale/event/fragment_order_status.html" with order=op.order event=op.order.event %}</small>
</dd>
<dd><time datetime="{{ op.order.datetime|date:"Y-m-d H:i" }}" class="text-muted small">{{ op.order.datetime|date:"SHORT_DATETIME_FORMAT" }}</time></dd>
{% if op.order.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-md-6 col-sm-5 col-xs-8">
<dt class="sr-only">{% trans "Product" %}</dt>
<dd>{{ op.item.name }}
{% if op.variation %} - {{ op.variation }}{% endif %}
</dd>
<dt class="sr-only">{% trans "Event" %}</dt>
<dd>
<small class="text-muted">
{{ op.order.event }}
{% if op.subevent %}
<br>{{ op.subevent }}
{% endif %}
{% if not op.order.event.has_subevents and op.order.event.settings.show_dates_on_frontpage %}
<br>{{ op.order.event.get_date_range_display }}
{% endif %}
</small>
</dd>
</div>
<div class="col-sm-2 col-xs-4">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}"
target="_blank">
{% icon "list-ul" %}
{% trans "Details" %}
</a></dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You havent used this membership yet." %}</p>
{% endif %}
</div>
<tr>
<td>
<strong>
{{ op.order.code }}-{{ op.positionid }}
</strong>
{% if op.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ op.order.event }}
{% if op.subevent %}
<br>
{{ op.subevent|default:"" }}
{% endif %}
</td>
<td>
{{ op.item.name }}
{% if op.variation %} {{ op.variation }}{% endif %}
</td>
<td>
{{ op.order.datetime|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td class="text-right flip">
{% if op.canceled %}
{% trans "Canceled" %} <i class="{{ class }} fa fa-times-circle text-danger" aria-hidden="true"></i>
{% else %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=op.order %}
{% endif %}
</td>
<td class="text-right flip">
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}"
target="_blank"
class="btn btn-default">
{% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -1,111 +0,0 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load textbubble %}
{% block title %}{% trans "Memberships" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "id-badge" %}
<b>{% trans "Memberships" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if memberships %}
<ul class="full-width-list alternating-rows">
{% for m in memberships %}
<li class="row">
<dl>
<div class="col-xs-5">
<dt>
{% if m.canceled %}<del>{% endif %}
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}">
{{ m.membership_type.name }}
</a>
{% if m.canceled %}</del>{% endif %}
{% if m.membership_type.transferable %}
<span class="text-muted" data-toggle="tooltip" title="{% trans "Membership is transferable" %}">
{% icon "users" %}
</span>
{% endif %}
</dt>
{% if m.attendee_name %}
<dd class="text-muted">
{% icon "id-badge" %}
<span class="sr-only">{% trans "Attendee name" %}:</span>
{{ m.attendee_name }}
</dd>
{% endif %}
<dd class="text-muted">
<small>
{% if m.canceled %}
{% textbubble "danger" icon="times" %}
{% trans "Canceled" %}
{% endtextbubble %}
{% elif m.expired %}
{% icon "minus-square-o" %}
{% trans "Expired since" %}
<time datetime="{{ m.date_end|date:"Y-m-d H:i" }}">
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% elif m.not_yet_valid %}
{% icon "clock-o" %}
{% trans "Valid from" %}
<time datetime="{{ m.date_start|date:"Y-m-d H:i" }}">
{{ m.date_start|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% else %}
{% icon "check" %}
{% trans "Valid until" %}
<time datetime="{{ m.date_end|date:"Y-m-d H:i" }}">
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% endif %}
</small>
</dd>
{% if m.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-xs-5">
<dd>
<div class="quotabox full-width">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ m.percentage_used }}">
</div>
</div>
<div class="numbers">
{{ m.usages }} /
{{ m.membership_type.max_usages|default_if_none:"∞" }}
</div>
</div>
</dd>
</div>
<div class="col-xs-2">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}">
{% icon "list-ul" %}
{% trans "Details" %}
</a>
</dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any memberships in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -1,83 +0,0 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load money %}
{% load textbubble %}
{% block title %}{% trans "Your account" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "shopping-cart" %}
<b>{% trans "Orders" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if orders %}
<ul class="full-width-list alternating-rows">
{% for o in orders %}
<li class="row">
<dl>
<div class="col-md-4 col-sm-5 col-xs-8">
<dt class="sr-only">{% trans "Order" %}</dt>
<dd><strong>
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}" target="_blank">
{% icon "shopping-cart" %}
{{ o.code }}</a>
</strong>
{% if o.customer_id != customer.pk %}
<span class="text-muted" data-toggle="tooltip"
title="{% trans "Matched to the account based on the email address." %}">
{% icon "compress" %}
</span>
{% endif %}
<small>{% include "pretixpresale/event/fragment_order_status.html" with order=o event=o.event %}</small>
</dd>
<dd><time datetime="{{ o.datetime|date:"Y-m-d H:i" }}" class="text-muted small">{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</time></dd>
{% if o.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-md-2 col-sm-2 col-xs-4 text-right">
<dt class="sr-only">{% trans "Order total" %}</dt>
<dd>{{ o.total|money:o.event.currency }}</dd>
<dt class="sr-only">{% trans "Positions" %}</dt>
<dd class="text-muted"><small>{% blocktranslate count counter=o.count_positions|default_if_none:0 %}{{ counter }} item{% plural %}{{ counter }} items{% endblocktranslate %}</small>
</dd>
</div>
<div class="col-md-4 col-sm-3 col-xs-8">
<dt class="sr-only">{% trans "Event" %}</dt>
<dd>
{{ o.event }}
{% if not o.event.has_subevents and o.event.settings.show_dates_on_frontpage %}
<br><small class="text-muted">{{ o.event.get_date_range_display }}</small>
{% endif %}
</dd>
</div>
<div class="col-sm-2 col-xs-4">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}"
target="_blank">
{% icon "list-ul" %}
{% trans "Details" %}
</a></dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any orders in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -0,0 +1,253 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Your account" %}{% endblock %}
{% block content %}
<h2>
{% trans "Your account" %}
</h2>
<div class="panel panel-primary items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Account information" %}
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
{% if customer.provider %}
<dt>{% trans "Login method" %}</dt>
<dd>{{ customer.provider.name }}</dd>
{% endif %}
<dt>{% trans "Email" %}</dt>
<dd>{{ customer.email }}
</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
{% if customer.phone %}
<dt>{% trans "Phone" %}</dt>
<dd>{{ customer.phone }}</dd>
{% endif %}
</dl>
<div class="text-right">
<a href="{% eventurl request.organizer "presale:organizer.customer.change" %}"
class="btn btn-default">
{% trans "Change account information" %}
</a>
{% if not customer.provider %}
<a href="{% eventurl request.organizer "presale:organizer.customer.password" %}"
class="btn btn-default">
{% trans "Change password" %}
</a>
{% endif %}
</div>
</div>
</div>
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#orders" aria-controls="orders" role="tab" data-toggle="tab">{% trans "Orders" %}</a>
</li>
<li role="presentation">
<a href="#memberships" aria-controls="memberships" role="tab" data-toggle="tab">{% trans "Memberships" %}</a>
</li>
<li role="presentation">
<a href="#addresses" aria-controls="addresses" role="tab" data-toggle="tab">{% trans "Addresses" %}</a>
</li>
<li role="presentation">
<a href="#profiles" aria-controls="profiles" role="tab" data-toggle="tab">{% trans "Attendee profiles" %}</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="orders">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "Event" %}</th>
<th>{% trans "Order date" %}</th>
<th class="text-right">{% trans "Order total" %}</th>
<th class="text-right">{% trans "Positions" %}</th>
<th class="text-right">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for o in orders %}
<tr>
<td>
<strong>
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}" target="_blank">
{{ o.code }}
</a>
</strong>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ o.event }}
{% if not o.event.has_subevents and o.event.settings.show_dates_on_frontpage %}
<br><small class="text-muted">{{ o.event.get_date_range_display }}</small>
{% endif %}
</td>
<td>
{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if o.customer_id != customer.pk %}
<span class="fa fa-link text-muted"
data-toggle="tooltip"
title="{% trans "Matched to the account based on the email address." %}"
></span>
{% endif %}
</td>
<td class="text-right flip">
{{ o.total|money:o.event.currency }}
</td>
<td class="text-right flip">{{ o.count_positions|default_if_none:"0" }}</td>
<td class="text-right flip">{% include "pretixpresale/event/fragment_order_status.html" with order=o event=o.event %}</td>
<td class="text-right flip">
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}"
target="_blank"
class="btn btn-default">
{% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}
</div>
<div role="tabpanel" class="tab-pane" id="memberships">
<table class="panel-body table table-hover">
<caption class="sr-only">{% trans "Memberships" %}</caption>
<thead>
<tr>
<th>{% trans "Membership type" %}</th>
<th>{% trans "Valid from" %}</th>
<th>{% trans "Valid until" %}</th>
<th>{% trans "Attendee name" %}</th>
<th>{% trans "Usages" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for m in memberships %}
<tr>
<td>
{% if m.canceled %}<del>{% endif %}
{{ m.membership_type.name }}
{% if m.canceled %}</del>{% endif %}
{% if m.testmode %}<span class="label label-warning">{% trans "TEST MODE" %}</span>{% endif %}
</td>
<td>
{{ m.date_start|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td>
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td>
{{ m.attendee_name }}
</td>
<td>
<div class="quotabox">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ m.percent }}">
</div>
</div>
<div class="numbers">
{{ m.usages }} /
{{ m.membership_type.max_usages|default_if_none:"∞" }}
</div>
</div>
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}"
data-toggle="tooltip"
title="{% trans "Details" %}"
class="btn btn-default">
<i class="fa fa-list"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No memberships are stored in your account." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="addresses">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Address" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ia in invoice_addresses %}
<tr>
<td>
{{ ia.describe|linebreaksbr }}
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.address.delete" id=ia.id %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center">
{% trans "No addresses are stored in your account." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="profiles">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Profile" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ap in customer.attendee_profiles.all %}
<tr>
<td>
{{ ap.describe|linebreaksbr }}
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile.delete" id=ap.id %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center">
{% trans "No attendee profiles are stored in your account." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
@@ -1,39 +1,33 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Delete profile" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "user" %} <b>{% trans "Delete profile" %}</b>
</h3>
</div>
<div class="panel-body">
<p>
{% trans "Do you really want to delete the following profile from your account?" %}
</p>
<p>
{{ profile.describe|linebreaksbr }}
</p>
</div>
</div>
{% block content %}
<h2>
{% trans "Delete profile" %}
</h2>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following profile from your account?" %}
</p>
<address>
{{ profile.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.profiles" %}">
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% icon "trash" %}
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}
@@ -1,42 +0,0 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Attendee profiles" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "users" %}
<b>{% trans "Attendee profiles" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if attendee_profiles %}
<ol class="full-width-list alternating-rows">
<li class="row">
{% for ap in attendee_profiles %}
{% if forloop.counter0 and forloop.counter0|divisibleby:4 %}
</li>
<li class="row">
{% endif %}
<div class="col-md-3 col-xs-12">
<p>{{ ap.describe|linebreaksbr }}</p>
<p class="blank-after">
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile.delete" id=ap.id %}"
class="btn btn-danger btn-sm">
{% icon "trash" %}
{% trans "Delete" %}
</a>
</p>
</div>
{% endfor %}
</li>
</ol>
{% else %}
<p class="text-center">{% trans "You dont have any attendee profiles in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
+2 -5
View File
@@ -210,13 +210,10 @@ organizer_patterns = [
re_path(r'^account/password$', pretix.presale.views.customer.ChangePasswordView.as_view(), name='organizer.customer.password'),
re_path(r'^account/change$', pretix.presale.views.customer.ChangeInformationView.as_view(), name='organizer.customer.change'),
re_path(r'^account/confirmchange$', pretix.presale.views.customer.ConfirmChangeView.as_view(), name='organizer.customer.change.confirm'),
re_path(r'^account/memberships$', pretix.presale.views.customer.MembershipView.as_view(), name='organizer.customer.memberships'),
re_path(r'^account/memberships/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
re_path(r'^account/addresses$', pretix.presale.views.customer.AddressView.as_view(), name='organizer.customer.addresses'),
re_path(r'^account/membership/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
re_path(r'^account/addresses/(?P<id>\d+)/delete$', pretix.presale.views.customer.AddressDeleteView.as_view(), name='organizer.customer.address.delete'),
re_path(r'^account/profiles$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profiles'),
re_path(r'^account/profiles/(?P<id>\d+)/delete$', pretix.presale.views.customer.ProfileDeleteView.as_view(), name='organizer.customer.profile.delete'),
re_path(r'^account/$', pretix.presale.views.customer.OrderView.as_view(), name='organizer.customer.index'),
re_path(r'^account/$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profile'),
re_path(r'^oauth2/v1/authorize$', pretix.presale.views.oidc_op.AuthorizeView.as_view(),
name='organizer.oauth2.v1.authorize'),
+1 -1
View File
@@ -89,7 +89,7 @@ class CheckoutView(View):
else:
previous_step = step
step.c_is_before = True
step.c_resolved_url = step.get_step_url(request) + '?dir=prev'
step.c_resolved_url = step.get_step_url(request)
raise Http404()
def redirect(self, url):
+27 -78
View File
@@ -134,7 +134,7 @@ class LoginView(RedirectBackMixin, FormView):
url = self.get_redirect_url()
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
if self.request.GET.get("request_cross_domain_customer_auth") == "true":
otpstore = SessionStore()
@@ -350,42 +350,8 @@ class CustomerRequiredMixin:
return super().dispatch(request, *args, **kwargs)
class CustomerAccountBaseMixin(CustomerRequiredMixin):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['customer'] = self.request.customer
url_name = self.request.resolver_match.url_name
ctx['sub_nav'] = [
{
'label': _('Orders'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={}),
'active': url_name == 'organizer.customer.index',
'icon': 'shopping-cart',
},
{
'label': _('Memberships'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.memberships', kwargs={}),
'active': url_name.startswith('organizer.customer.membership'),
'icon': 'id-badge',
},
{
'label': _('Addresses'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.addresses', kwargs={}),
'active': url_name.startswith('organizer.customer.address'),
'icon': 'address-card-o',
},
{
'label': _('Attendee profiles'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.profiles', kwargs={}),
'active': url_name.startswith('organizer.customer.profile'),
'icon': 'user',
},
]
return ctx
class OrderView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_orders.html'
class ProfileView(CustomerRequiredMixin, ListView):
template_name = 'pretixpresale/organizers/customer_profile.html'
context_object_name = 'orders'
paginate_by = 20
@@ -403,6 +369,18 @@ class OrderView(CustomerAccountBaseMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['customer'] = self.request.customer
ctx['memberships'] = self.request.customer.memberships.with_usages().select_related(
'membership_type', 'granted_in', 'granted_in__order', 'granted_in__order__event'
)
ctx['invoice_addresses'] = InvoiceAddress.profiles.filter(customer=self.request.customer)
ctx['is_paginated'] = True
for m in ctx['memberships']:
if m.membership_type.max_usages:
m.percent = int(m.usages / m.membership_type.max_usages * 100)
else:
m.percent = 0
s = OrderPosition.objects.filter(
order=OuterRef('pk')
@@ -426,18 +404,7 @@ class OrderView(CustomerAccountBaseMixin, ListView):
return ctx
class MembershipView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_memberships.html'
context_object_name = 'memberships'
paginate_by = 20
def get_queryset(self):
return self.request.customer.memberships.with_usages().select_related(
'membership_type', 'granted_in', 'granted_in__order', 'granted_in__order__event'
)
class MembershipUsageView(CustomerAccountBaseMixin, ListView):
class MembershipUsageView(CustomerRequiredMixin, ListView):
template_name = 'pretixpresale/organizers/customer_membership.html'
context_object_name = 'usages'
paginate_by = 20
@@ -461,16 +428,7 @@ class MembershipUsageView(CustomerAccountBaseMixin, ListView):
return ctx
class AddressView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_addresses.html'
context_object_name = 'invoice_addresses'
paginate_by = 20
def get_queryset(self):
return InvoiceAddress.profiles.filter(customer=self.request.customer)
class AddressDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
class AddressDeleteView(CustomerRequiredMixin, CompatDeleteView):
template_name = 'pretixpresale/organizers/customer_address_delete.html'
context_object_name = 'address'
@@ -478,19 +436,10 @@ class AddressDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
return get_object_or_404(InvoiceAddress.profiles, customer=self.request.customer, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.addresses', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
class ProfileView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_profiles.html'
context_object_name = 'attendee_profiles'
paginate_by = 20
def get_queryset(self):
return self.request.customer.attendee_profiles.all()
class ProfileDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
class ProfileDeleteView(CustomerRequiredMixin, CompatDeleteView):
template_name = 'pretixpresale/organizers/customer_profile_delete.html'
context_object_name = 'profile'
@@ -498,10 +447,10 @@ class ProfileDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
return get_object_or_404(self.request.customer.attendee_profiles, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profiles', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
class ChangePasswordView(CustomerAccountBaseMixin, FormView):
class ChangePasswordView(CustomerRequiredMixin, FormView):
template_name = 'pretixpresale/organizers/customer_password.html'
form_class = ChangePasswordForm
@@ -516,7 +465,7 @@ class ChangePasswordView(CustomerAccountBaseMixin, FormView):
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
@transaction.atomic()
def form_valid(self, form):
@@ -534,7 +483,7 @@ class ChangePasswordView(CustomerAccountBaseMixin, FormView):
return kwargs
class ChangeInformationView(CustomerAccountBaseMixin, FormView):
class ChangeInformationView(CustomerRequiredMixin, FormView):
template_name = 'pretixpresale/organizers/customer_info.html'
form_class = ChangeInfoForm
@@ -549,7 +498,7 @@ class ChangeInformationView(CustomerAccountBaseMixin, FormView):
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
def form_valid(self, form):
if form.cleaned_data['email'] != self.initial_email and not self.request.customer.provider:
@@ -632,7 +581,7 @@ class ConfirmChangeView(View):
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
class SSOLoginView(RedirectBackMixin, View):
@@ -692,7 +641,7 @@ class SSOLoginView(RedirectBackMixin, View):
url = self.get_redirect_url()
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return url
@@ -915,7 +864,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
url = self.get_redirect_url(redirect_to)
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
else:
if self.request.session.get(f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'):
otpstore = SessionStore()
+1 -5
View File
@@ -726,11 +726,7 @@ PASSWORD_HASHERS = [
# the HistoricPassword model will not be changed automatically. In case a serious issue with a hasher
# comes to light, dropping the contents of the HistoricPassword table might be the more risk-adequate
# decision.
*(
["django.contrib.auth.hashers.Argon2PasswordHasher"]
if config.getboolean('django', 'passwords_argon2', fallback=True)
else []
),
"django.contrib.auth.hashers.Argon2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+3 -11
View File
@@ -20,7 +20,7 @@ $(function () {
xhr.abort();
}
for (var k in dependents) dependents[k].prop("disabled", true);
loader.show();
loader.fadeIn();
xhr = $.getJSON(url + '?country=' + dependency.val(), function (data) {
var selected_value = dependents.state.prop("data-selected-value");
if (selected_value) dependents.state.prop("data-selected-value", "");
@@ -43,17 +43,9 @@ $(function () {
}
for (var k in dependents) dependents[k].prop("disabled", false);
}).always(function() {
loader.hide();
loader.fadeOut();
}).fail(function(){
// In case of errors, show everything and require nothing, we can still handle errors in backend
for(var k in dependents) {
const dependent = dependents[k],
visible = true,
required = false;
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
dependent.prop("required", required);
}
// TODO: handle failed request
});
};
dependents.state.prop("data-selected-value", dependents.state.val());
@@ -8,7 +8,7 @@ $gray-lightest: lighten(#000, 97.25%);
$font-family-sans-serif: var(--pretix-font-family-sans-serif);
$text-color: #222222 !default;
$text-muted: #737373 !default;
$text-muted: #767676 !default;
$input-color-placeholder: lighten(#000, 70%) !default;
$border-radius-base: var(--pretix-border-radius-base);
@@ -122,29 +122,29 @@ $label-warning-bg-hover: var(--pretix-brand-warning-darken-10);
$label-danger-bg: var(--pretix-brand-danger);
$label-danger-bg-hover: var(--pretix-brand-danger-darken-10);
$alert-success-bg: var(--pretix-brand-success-tint-85);
$alert-success-text: var(--pretix-brand-success-shade-42);
$alert-success-bg: var(--pretix-brand-success-lighten-48);
$alert-success-text: var(--pretix-brand-success-darken-10);
$alert-success-border: var(--pretix-brand-success);
$alert-success-hr: var(--pretix-brand-success-darken-5);
$alert-success-link: var(--pretix-brand-success-shade-42);
$alert-success-link: var(--pretix-brand-success-darken-20);
$alert-info-bg: var(--pretix-brand-info-tint-85);
$alert-info-text: var(--pretix-brand-info-shade-42);
$alert-info-bg: var(--pretix-brand-info-lighten-33);
$alert-info-text: var(--pretix-brand-info-darken-20);
$alert-info-border: var(--pretix-brand-info);
$alert-info-hr: var(--pretix-brand-info-darken-5);
$alert-info-link: var(--pretix-brand-info-shade-42);
$alert-info-link: var(--pretix-brand-info-darken-30);
$alert-warning-bg: var(--pretix-brand-warning-tint-85);
$alert-warning-text: var(--pretix-brand-warning-shade-42);
$alert-warning-bg: var(--pretix-brand-warning-lighten-41);
$alert-warning-text: var(--pretix-brand-warning-darken-25);
$alert-warning-border: var(--pretix-brand-warning);
$alert-warning-hr: var(--pretix-brand-warning-darken-5);
$alert-warning-link: var(--pretix-brand-warning-shade-42);
$alert-warning-link: var(--pretix-brand-warning-darken-35);
$alert-danger-bg: var(--pretix-brand-danger-tint-85);
$alert-danger-text: var(--pretix-brand-danger-shade-42);
$alert-danger-bg: var(--pretix-brand-danger-lighten-43);
$alert-danger-text: var(--pretix-brand-danger-darken-5);
$alert-danger-border: var(--pretix-brand-danger);
$alert-danger-hr: var(--pretix-brand-danger-darken-5);
$alert-danger-link: var(--pretix-brand-danger-shade-42);
$alert-danger-link: var(--pretix-brand-danger-darken-15);
$main-box-bg: #FFFFFF;
@@ -156,4 +156,4 @@ $slider-secondary-bottom: var(--pretix-brand-primary-lighten-23-saturate-2);
/* The following vars default to $body-bg in bootstrap, which we don't want */
$nav-tabs-active-link-hover-bg: #FFFFFF;
$nav-tabs-justified-active-link-border-color: #FFFFFF;
$thumbnail-bg: #FFFFFF;
$thumbnail-bg: #FFFFFF;
@@ -73,9 +73,7 @@ $in-border-radius-small: 2px !default;
--pretix-brand-success-darken-20: #{darken($in-brand-success, 20%)};
--pretix-brand-success-darken-30: #{darken($in-brand-success, 30%)};
--pretix-brand-success-tint-50: #{tint($in-brand-success, 50%)};
--pretix-brand-success-tint-85: #{tint($in-brand-success, 85%)};
--pretix-brand-success-shade-25: #{shade($in-brand-success, 25%)};
--pretix-brand-success-shade-42: #{shade($in-brand-success, 42%)};
--pretix-brand-info-lighten-23: #{lighten($in-brand-info, 23%)};
--pretix-brand-info-lighten-25: #{lighten($in-brand-info, 25%)};
@@ -86,9 +84,7 @@ $in-border-radius-small: 2px !default;
--pretix-brand-info-darken-17: #{darken($in-brand-info, 17%)};
--pretix-brand-info-darken-20: #{darken($in-brand-info, 20%)};
--pretix-brand-info-darken-30: #{darken($in-brand-info, 30%)};
--pretix-brand-info-tint-85: #{tint($in-brand-info, 85%)};
--pretix-brand-info-shade-25: #{shade($in-brand-info, 25%)};
--pretix-brand-info-shade-42: #{shade($in-brand-info, 42%)};
--pretix-brand-warning-lighten-12: #{lighten($in-brand-warning, 12%)};
--pretix-brand-warning-lighten-31: #{lighten($in-brand-warning, 31%)};
@@ -105,9 +101,7 @@ $in-border-radius-small: 2px !default;
--pretix-brand-warning-darken-30: #{darken($in-brand-warning, 30%)};
--pretix-brand-warning-darken-35: #{darken($in-brand-warning, 35%)};
--pretix-brand-warning-tint-50: #{tint($in-brand-warning, 50%)};
--pretix-brand-warning-tint-85: #{tint($in-brand-warning, 85%)};
--pretix-brand-warning-shade-25: #{shade($in-brand-warning, 25%)};
--pretix-brand-warning-shade-42: #{shade($in-brand-warning, 42%)};
--pretix-brand-warning-transparent-60: #{transparentize($in-brand-warning, 0.6)};
--pretix-brand-danger-lighten-5: #{lighten($in-brand-danger, 5%)};
@@ -124,9 +118,7 @@ $in-border-radius-small: 2px !default;
--pretix-brand-danger-darken-20: #{darken($in-brand-danger, 20%)};
--pretix-brand-danger-darken-30: #{darken($in-brand-danger, 30%)};
--pretix-brand-danger-tint-50: #{tint($in-brand-danger, 50%)};
--pretix-brand-danger-tint-85: #{tint($in-brand-danger, 85%)};
--pretix-brand-danger-shade-25: #{shade($in-brand-danger, 25%)};
--pretix-brand-danger-shade-42: #{shade($in-brand-danger, 42%)};
--pretix-border-radius-base: #{$in-border-radius-base};
--pretix-border-radius-large: #{$in-border-radius-large};
@@ -148,4 +140,4 @@ $in-border-radius-small: 2px !default;
--pretix-body-bg-white-0: 1;
}
}
}
}
+3 -76
View File
@@ -30,6 +30,9 @@ $body-bg: #f5f5f5 !default;
float: left;
margin-right: .25em;
}
.status-dot {
font-size: 1.2em;
}
.text-warning { color: $brand-warning; }
.text-info { color: $brand-info; }
.text-success { color: $brand-success; }
@@ -95,9 +98,6 @@ footer nav .btn-link {
vertical-align: baseline;
}
}
.subnav a.active {
font-weight: bold;
}
.locales ul {
display: inline;
list-style: none;
@@ -499,9 +499,6 @@ h2 .label {
&.availability .progress-bar-success {
background: var(--pretix-brand-success-lighten-20);
}
&.full-width {
width: 100%;
}
}
.nav-tabs {
@@ -530,76 +527,6 @@ h2 .label {
font-size: 30px;
}
.textbubble-success, .textbubble-info, .textbubble-warning, .textbubble-danger {
padding: 0 .4em;
border-radius: $border-radius-base;
font-weight: bold;
white-space: nowrap;
&:has(>.fa:first-child) {
padding-left: .25em;
}
}
.textbubble-success {
color: var(--pretix-brand-success-shade-42);
background: var(--pretix-brand-success-tint-85);
.fa {
color: var(--pretix-brand-success);
}
}
.textbubble-info {
color: var(--pretix-brand-info-shade-42);
background: var(--pretix-brand-info-tint-85);
.fa {
color: var(--pretix-brand-info);
}
}
.textbubble-warning {
color: var(--pretix-brand-warning-shade-42);
background: var(--pretix-brand-warning-tint-85);
.fa {
color: var(--pretix-brand-warning);
}
}
.textbubble-danger {
color: var(--pretix-brand-danger-shade-42);
background: var(--pretix-brand-danger-tint-85);
.fa {
color: var(--pretix-brand-danger);
}
}
.full-width-list {
list-style: none;
margin: 0;
padding: 0;
li {
padding: 15px 0;
}
li+li {
border-top: 1px solid $table-border-color;
}
}
.panel-body:has(>.full-width-list:first-child) {
padding-top: 0;
}
.panel-body:has(>.full-width-list:last-child) {
padding-bottom: 0;
}
.alternating-rows .row:nth-child(even) {
background-color: $table-bg-accent;
}
.account-addresses address::first-line {
font-weight: bold;
}
@import "_iframe.scss";
@import "_a11y.scss";
@import "_print.scss";
-32
View File
@@ -1,32 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from pretix.base.forms.questions import name_parts_is_empty
def test_name_parts_is_empty():
assert name_parts_is_empty({}) is True
assert name_parts_is_empty({"_scheme": "foo"}) is True
assert name_parts_is_empty({"_scheme": "foo", "full_name": ""}) is True
assert name_parts_is_empty({"full_name": None}) is True
assert name_parts_is_empty({"full_name": "Flora Nord"}) is False
assert name_parts_is_empty({"_scheme": "foo", "given_name": "Alice"}) is False
assert name_parts_is_empty({"_legacy": "Alice"}) is False
-8
View File
@@ -763,7 +763,6 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start': '2016-11-01 10:00:00',
@@ -793,7 +792,6 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -890,7 +888,6 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1076,7 +1073,6 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'UTC',
'basics-presale_start_0': '',
@@ -1125,7 +1121,6 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'UTC',
'basics-presale_start_0': '',
@@ -1176,7 +1171,6 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1206,7 +1200,6 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': '$',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1236,7 +1229,6 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'ASD',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
+1 -1
View File
@@ -433,7 +433,7 @@ def test_org_sso_login_new_customer_email_conflict(env, client, provider):
@pytest.mark.django_db
@pytest.mark.parametrize("url", [
"account/change",
"account/memberships/1/",
"account/membership/1/",
"account/",
])
def test_login_required(client, env, url):