forked from CGM_Public/pretix_original
Compare commits
10 Commits
v2024.11.0
...
addressfor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0279ca7d94 | ||
|
|
d1989c3cd3 | ||
|
|
61cb2e15cf | ||
|
|
f2ee1d00b3 | ||
|
|
e8e9698a31 | ||
|
|
a1bf7be244 | ||
|
|
f4ca9a5681 | ||
|
|
e6d984538f | ||
|
|
9f1ee9157f | ||
|
|
242e5af4b5 |
@@ -53,7 +53,7 @@ dependencies = [
|
|||||||
"django-phonenumber-field==7.3.*",
|
"django-phonenumber-field==7.3.*",
|
||||||
"django-redis==5.4.*",
|
"django-redis==5.4.*",
|
||||||
"django-scopes==2.0.*",
|
"django-scopes==2.0.*",
|
||||||
"django-statici18n==2.5.*",
|
"django-statici18n==2.6.*",
|
||||||
"djangorestframework==3.15.*",
|
"djangorestframework==3.15.*",
|
||||||
"dnspython==2.7.*",
|
"dnspython==2.7.*",
|
||||||
"drf_ujson2==1.7.*",
|
"drf_ujson2==1.7.*",
|
||||||
@@ -76,7 +76,7 @@ dependencies = [
|
|||||||
"phonenumberslite==8.13.*",
|
"phonenumberslite==8.13.*",
|
||||||
"Pillow==11.0.*",
|
"Pillow==11.0.*",
|
||||||
"pretix-plugin-build",
|
"pretix-plugin-build",
|
||||||
"protobuf==5.28.*",
|
"protobuf==5.29.*",
|
||||||
"psycopg2-binary",
|
"psycopg2-binary",
|
||||||
"pycountry",
|
"pycountry",
|
||||||
"pycparser==2.22",
|
"pycparser==2.22",
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# 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/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2024.11.0"
|
__version__ = "2024.12.0.dev0"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from django.core.validators import (
|
|||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.forms import Select, widgets
|
from django.forms import Select, widgets
|
||||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@@ -77,7 +78,7 @@ from pretix.base.i18n import (
|
|||||||
get_babel_locale, get_language_without_region, language,
|
get_babel_locale, get_language_without_region, language,
|
||||||
)
|
)
|
||||||
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
|
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
|
||||||
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
|
from pretix.base.models.tax import ask_for_vat_id
|
||||||
from pretix.base.services.tax import (
|
from pretix.base.services.tax import (
|
||||||
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
||||||
)
|
)
|
||||||
@@ -602,6 +603,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
questions = pos.item.questions_to_ask
|
questions = pos.item.questions_to_ask
|
||||||
event = kwargs.pop('event')
|
event = kwargs.pop('event')
|
||||||
self.all_optional = kwargs.pop('all_optional', False)
|
self.all_optional = kwargs.pop('all_optional', False)
|
||||||
|
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@@ -676,7 +678,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
|
|
||||||
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
||||||
add_fields['street'] = forms.CharField(
|
add_fields['street'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=self.attendee_addresses_required,
|
||||||
label=_('Address'),
|
label=_('Address'),
|
||||||
widget=forms.Textarea(attrs={
|
widget=forms.Textarea(attrs={
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
@@ -686,7 +688,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
initial=(cartpos.street if cartpos else orderpos.street),
|
initial=(cartpos.street if cartpos else orderpos.street),
|
||||||
)
|
)
|
||||||
add_fields['zipcode'] = forms.CharField(
|
add_fields['zipcode'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=False,
|
||||||
max_length=30,
|
max_length=30,
|
||||||
label=_('ZIP code'),
|
label=_('ZIP code'),
|
||||||
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
||||||
@@ -695,7 +697,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
add_fields['city'] = forms.CharField(
|
add_fields['city'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=False,
|
||||||
label=_('City'),
|
label=_('City'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
initial=(cartpos.city if cartpos else orderpos.city),
|
initial=(cartpos.city if cartpos else orderpos.city),
|
||||||
@@ -707,11 +709,12 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
add_fields['country'] = CountryField(
|
add_fields['country'] = CountryField(
|
||||||
countries=CachedCountries
|
countries=CachedCountries
|
||||||
).formfield(
|
).formfield(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=self.attendee_addresses_required,
|
||||||
label=_('Country'),
|
label=_('Country'),
|
||||||
initial=country,
|
initial=country,
|
||||||
widget=forms.Select(attrs={
|
widget=forms.Select(attrs={
|
||||||
'autocomplete': 'country',
|
'autocomplete': 'country',
|
||||||
|
'data-country-information-url': reverse('js_helpers.states'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||||
@@ -946,9 +949,9 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
d = super().clean()
|
d = super().clean()
|
||||||
|
|
||||||
if self.address_validation:
|
if self.address_validation:
|
||||||
self.cleaned_data = d = validate_address(d, True)
|
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
|
||||||
|
|
||||||
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||||
if not d.get('state'):
|
if not d.get('state'):
|
||||||
self.add_error('state', _('This field is required.'))
|
self.add_error('state', _('This field is required.'))
|
||||||
|
|
||||||
@@ -1005,7 +1008,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
'street': forms.Textarea(attrs={
|
'street': forms.Textarea(attrs={
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
'placeholder': _('Street and Number'),
|
'placeholder': _('Street and Number'),
|
||||||
'autocomplete': 'street-address'
|
'autocomplete': 'street-address',
|
||||||
}),
|
}),
|
||||||
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
||||||
'country': forms.Select(attrs={
|
'country': forms.Select(attrs={
|
||||||
@@ -1021,7 +1024,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
'data-display-dependency': '#id_is_business_1',
|
'data-display-dependency': '#id_is_business_1',
|
||||||
'autocomplete': 'organization',
|
'autocomplete': 'organization',
|
||||||
}),
|
}),
|
||||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
|
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
||||||
'internal_reference': forms.TextInput,
|
'internal_reference': forms.TextInput,
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
@@ -1055,6 +1058,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
])
|
])
|
||||||
|
|
||||||
self.fields['country'].choices = CachedCountries()
|
self.fields['country'].choices = CachedCountries()
|
||||||
|
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
|
||||||
|
|
||||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||||
fprefix = self.prefix + '-' if self.prefix else ''
|
fprefix = self.prefix + '-' if self.prefix else ''
|
||||||
@@ -1083,6 +1087,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
self.fields['state'].widget.is_required = True
|
self.fields['state'].widget.is_required = True
|
||||||
|
|
||||||
|
self.fields['street'].required = False
|
||||||
|
self.fields['zipcode'].required = False
|
||||||
|
self.fields['city'].required = False
|
||||||
|
|
||||||
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
|
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
|
||||||
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
|
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
|
||||||
self.data = self.data.copy()
|
self.data = self.data.copy()
|
||||||
@@ -1135,6 +1143,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
validate_address # local import to prevent impact on startup time
|
validate_address # local import to prevent impact on startup time
|
||||||
|
|
||||||
data = self.cleaned_data
|
data = self.cleaned_data
|
||||||
|
|
||||||
if not data.get('is_business'):
|
if not data.get('is_business'):
|
||||||
data['company'] = ''
|
data['company'] = ''
|
||||||
data['vat_id'] = ''
|
data['vat_id'] = ''
|
||||||
@@ -1142,9 +1151,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
data['vat_id'] = ''
|
data['vat_id'] = ''
|
||||||
if self.event.settings.invoice_address_required:
|
if self.event.settings.invoice_address_required:
|
||||||
if data.get('is_business') and not data.get('company'):
|
if data.get('is_business') and not data.get('company'):
|
||||||
raise ValidationError(_('You need to provide a company name.'))
|
raise ValidationError({"company": _('You need to provide a company name.')})
|
||||||
if not data.get('is_business') and not data.get('name_parts'):
|
if not data.get('is_business') and not data.get('name_parts'):
|
||||||
raise ValidationError(_('You need to provide your name.'))
|
raise ValidationError(_('You need to provide your name.'))
|
||||||
|
if not self.all_optional and 'street' in self.fields and not data.get('street') and not data.get('zipcode') and not data.get('city'):
|
||||||
|
raise ValidationError({"street": _('This field is required.')})
|
||||||
|
|
||||||
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
||||||
self.instance.vat_id_validated = False
|
self.instance.vat_id_validated = False
|
||||||
|
|||||||
@@ -159,10 +159,24 @@ class Membership(models.Model):
|
|||||||
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
|
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
|
||||||
return f'{self.membership_type.name}: {self.attendee_name} ({ds} – {de})'
|
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
|
@property
|
||||||
def attendee_name(self):
|
def attendee_name(self):
|
||||||
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
|
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):
|
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
|
||||||
if valid_from_not_chosen:
|
if valid_from_not_chosen:
|
||||||
return not self.canceled and self.date_end >= time_machine_now()
|
return not self.canceled and self.date_end >= time_machine_now()
|
||||||
|
|||||||
@@ -3204,9 +3204,9 @@ class InvoiceAddress(models.Model):
|
|||||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
|
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
|
||||||
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
||||||
name_parts = models.JSONField(default=dict)
|
name_parts = models.JSONField(default=dict)
|
||||||
street = models.TextField(verbose_name=_('Address'), blank=False)
|
street = models.TextField(verbose_name=_('Address'), blank=True)
|
||||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
|
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
|
||||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
|
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
|
||||||
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
||||||
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
||||||
countries=CachedCountries)
|
countries=CachedCountries)
|
||||||
|
|||||||
34
src/pretix/base/templatetags/icon.py
Normal file
34
src/pretix/base/templatetags/icon.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# 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 "",
|
||||||
|
)
|
||||||
42
src/pretix/base/templatetags/textbubble.py
Normal file
42
src/pretix/base/templatetags/textbubble.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#
|
||||||
|
# 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>')
|
||||||
@@ -22,16 +22,30 @@
|
|||||||
import pycountry
|
import pycountry
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
from pretix.base.addressvalidation import (
|
||||||
|
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
|
||||||
|
)
|
||||||
|
from pretix.base.models.tax import VAT_ID_COUNTRIES
|
||||||
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
def states(request):
|
def states(request):
|
||||||
cc = request.GET.get("country", "DE")
|
cc = request.GET.get("country", "DE")
|
||||||
|
info = {
|
||||||
|
'street': {'required': True},
|
||||||
|
'zipcode': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||||
|
'city': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||||
|
'state': {'visible': cc in COUNTRIES_WITH_STATE_IN_ADDRESS, 'required': cc in COUNTRIES_WITH_STATE_IN_ADDRESS},
|
||||||
|
'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False},
|
||||||
|
}
|
||||||
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||||
return JsonResponse({'data': []})
|
return JsonResponse({'data': [], **info, })
|
||||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||||
return JsonResponse({'data': [
|
return JsonResponse({
|
||||||
|
'data': [
|
||||||
{'name': s.name, 'code': s.code[3:]}
|
{'name': s.name, 'code': s.code[3:]}
|
||||||
for s in sorted(statelist, key=lambda s: s.name)
|
for s in sorted(statelist, key=lambda s: s.name)
|
||||||
]})
|
],
|
||||||
|
**info,
|
||||||
|
})
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{{ html_head|safe }}
|
{{ html_head|safe }}
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,13 @@
|
|||||||
<div id="cp{{ pos.id }}">
|
<div id="cp{{ pos.id }}">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for form in forms %}
|
{% for form in forms %}
|
||||||
|
<div class="profile-scope">
|
||||||
{% if form.pos.item != pos.item %}
|
{% if form.pos.item != pos.item %}
|
||||||
{# Add-Ons #}
|
{# Add-Ons #}
|
||||||
<legend>+ {{ form.pos.item }}</legend>
|
<legend>+ {{ form.pos.item }}</legend>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_form form layout="control" %}
|
{% bootstrap_form form layout="control" %}
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-01 22:52+0000\n"
|
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
|
||||||
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
|
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
|
||||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/"
|
||||||
"js/hu/>\n"
|
"pretix-js/hu/>\n"
|
||||||
"Language: hu\n"
|
"Language: hu\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.7.2\n"
|
"X-Generator: Weblate 5.8.3\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: 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:588
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||||
msgid "Time zone:"
|
msgid "Time zone:"
|
||||||
msgstr ""
|
msgstr "Időzona:"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||||
msgid "Your local time:"
|
msgid "Your local time:"
|
||||||
|
|||||||
@@ -1076,8 +1076,8 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
if warn:
|
if warn:
|
||||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||||
return False
|
return False
|
||||||
if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_attendees_required', as_type=bool) \
|
if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_addresses_required', as_type=bool) \
|
||||||
and (cp.street is None or cp.city is None or cp.country is None):
|
and (cp.street is None and cp.city is None and cp.country is None):
|
||||||
if warn:
|
if warn:
|
||||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class InvoiceAddressForm(BaseInvoiceAddressForm):
|
|||||||
|
|
||||||
|
|
||||||
class InvoiceNameForm(InvoiceAddressForm):
|
class InvoiceNameForm(InvoiceAddressForm):
|
||||||
|
address_validation = False
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load textbubble %}
|
||||||
{# Changes should be replicated in pretixcontrol/orders/fragment_order_status.html and in pretix/base/models/orders.py #}
|
{# Changes should be replicated in pretixcontrol/orders/fragment_order_status.html and in pretix/base/models/orders.py #}
|
||||||
{% if order.status == "n" %}
|
{% if order.status == "n" %}
|
||||||
{% if order.require_approval %}
|
{% if order.require_approval %}
|
||||||
{% trans "Approval pending" %}
|
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Approval pending" %}{% endtextbubble %}
|
||||||
{% elif order.total == 0 %}
|
{% elif order.total == 0 %}
|
||||||
{% trans "Confirmation pending" context "order state" %}
|
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Confirmation pending" context "order state" %}{% endtextbubble %}
|
||||||
{% elif event.settings.payment_pending_hidden %}
|
{% elif event.settings.payment_pending_hidden %}
|
||||||
{# intentionally left blank #}
|
{# intentionally left blank #}
|
||||||
{% elif order.valid_if_pending %}
|
{% elif order.valid_if_pending %}
|
||||||
{% trans "Confirmed" context "order state" %}
|
{% textbubble "info" icon="info-circle" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Payment pending" %}
|
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Payment pending" %}{% endtextbubble %}
|
||||||
{% 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 %}
|
{% endif %}
|
||||||
{% elif order.status == "p" %}
|
{% elif order.status == "p" %}
|
||||||
{% if order.count_positions == 0 %}
|
{% if order.count_positions == 0 %}
|
||||||
{% trans "Canceled (paid fee)" %} <i class="status-dot fa fa-info-circle text-info" aria-hidden="true"></i>
|
{% textbubble "info" icon="info-circle" %}{% trans "Canceled (paid fee)" %}{% endtextbubble %}
|
||||||
{% elif order.total == 0 %}
|
{% elif order.total == 0 %}
|
||||||
{% trans "Confirmed" context "order state" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
|
{% textbubble "success" icon="check" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Paid" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
|
{% textbubble "success" icon="check" %}{% trans "Paid" %}{% endtextbubble %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif order.status == "e" %}
|
{% elif order.status == "e" %}
|
||||||
{% trans "Expired" %} <i class="status-dot fa fa-minus-circle text-danger" aria-hidden="true"></i>
|
{% textbubble "danger" icon="minus" %}{% trans "Expired" %}{% endtextbubble %}
|
||||||
{% elif order.status == "c" %}
|
{% elif order.status == "c" %}
|
||||||
{% trans "Canceled" %} <i class="status-dot fa fa-times-circle text-danger" aria-hidden="true"></i>
|
{% textbubble "danger" icon="times" %}{% trans "Canceled" %}{% endtextbubble %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -20,4 +20,5 @@
|
|||||||
<script type="text/javascript" src="{% static "pretixpresale/js/ui/cart.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixpresale/js/ui/cart.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixpresale/js/ui/iframe.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixpresale/js/ui/iframe.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
{% if request.organizer.settings.customer_accounts %}
|
{% if request.organizer.settings.customer_accounts %}
|
||||||
<nav class="loginstatus" aria-label="{% trans "customer account" %}">
|
<nav class="loginstatus" aria-label="{% trans "customer account" %}">
|
||||||
{% if request.customer %}
|
{% if request.customer %}
|
||||||
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}"
|
<a href="{% abseventurl request.organizer "presale:organizer.customer.index" %}"
|
||||||
aria-label="{% trans "View customer account" %}" data-placement="bottom"
|
aria-label="{% trans "View customer account" %}" data-placement="bottom"
|
||||||
title="{% trans "View user profile" %}" data-toggle="tooltip">
|
title="{% trans "View customer account" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-user" aria-hidden="true"></span>
|
<span class="fa fa-user" aria-hidden="true"></span>
|
||||||
{{ request.customer.name|default:request.customer.email }}</a>
|
{{ 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 }}"
|
<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,33 +1,39 @@
|
|||||||
{% extends "pretixpresale/organizers/base.html" %}
|
{% extends "pretixpresale/organizers/customer_base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load icon %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% block title %}{% trans "Delete address" %}{% endblock %}
|
{% block title %}{% trans "Delete address" %}{% endblock %}
|
||||||
{% block content %}
|
{% block inner %}
|
||||||
<h2>
|
<div class="panel panel-default">
|
||||||
{% trans "Delete address" %}
|
<div class="panel-heading">
|
||||||
</h2>
|
<h3 class="panel-title">
|
||||||
<form method="post">
|
{% icon "address-card-o" %} <b>{% trans "Delete address" %}</b>
|
||||||
{% csrf_token %}
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body account-addresses">
|
||||||
<p>
|
<p>
|
||||||
{% trans "Do you really want to delete the following address from your account?" %}
|
{% trans "Do you really want to delete the following address from your account?" %}
|
||||||
</p>
|
</p>
|
||||||
<address>
|
<address>
|
||||||
{{ address.describe|linebreaksbr }}
|
{{ address.describe|linebreaksbr }}
|
||||||
</address>
|
</address>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 col-sm-6">
|
<div class="col-md-4 col-sm-6">
|
||||||
<a class="btn btn-block btn-default btn-lg"
|
<a class="btn btn-block btn-default btn-lg"
|
||||||
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
|
href="{% abseventurl request.organizer "presale:organizer.customer.addresses" %}">
|
||||||
{% trans "Go back" %}
|
{% trans "Go back" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-md-offset-4 col-sm-6">
|
<div class="col-md-4 col-md-offset-4 col-sm-6">
|
||||||
<button class="btn btn-block btn-danger btn-lg" type="submit">
|
<button class="btn btn-block btn-danger btn-lg" type="submit">
|
||||||
|
{% icon "trash" %}
|
||||||
{% trans "Delete" %}
|
{% trans "Delete" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{% 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 don’t have any addresses in your account yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{% 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" %}
|
{% extends "pretixpresale/organizers/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load icon %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% load urlreplace %}
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% block title %}{% trans "Log in" %}{% endblock %}
|
{% block title %}{% trans "Log in" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
{% bootstrap_form form %}
|
{% bootstrap_form form %}
|
||||||
<div class="form-group buttons">
|
<div class="form-group buttons">
|
||||||
<button type="submit" class="btn btn-primary btn-lg btn-block">
|
<button type="submit" class="btn btn-primary btn-lg btn-block">
|
||||||
|
{% icon "sign-in" %}
|
||||||
{% trans "Log in" %}
|
{% trans "Log in" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a class="btn btn-link btn-block"
|
<a class="btn btn-link btn-block"
|
||||||
href="{% eventurl request.organizer "presale:organizer.customer.register" %}">
|
href="{% eventurl request.organizer "presale:organizer.customer.register" %}">
|
||||||
|
{% icon "address-book-o" %}
|
||||||
{% trans "Create account" %}
|
{% trans "Create account" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,96 +1,127 @@
|
|||||||
{% extends "pretixpresale/organizers/base.html" %}
|
{% extends "pretixpresale/organizers/customer_base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load icon %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% load urlreplace %}
|
{% load textbubble %}
|
||||||
{% load money %}
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% block title %}{% trans "Your membership" %}{% endblock %}
|
{% block title %}{% trans "Your membership" %}{% endblock %}
|
||||||
{% block content %}
|
{% block inner %}
|
||||||
<h2>
|
<div class="panel panel-default">
|
||||||
{% trans "Your membership" %}
|
|
||||||
</h2>
|
|
||||||
<div class="panel panel-primary items">
|
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{% trans "Details" %}
|
{% 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 %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<dl class="dl-horizontal">
|
<dl class="dl-horizontal">
|
||||||
<dt>{% trans "Membership type" %}</dt>
|
<dt>{% trans "Membership type" %}</dt>
|
||||||
<dd>{{ membership.membership_type.name }}</dd>
|
<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>
|
||||||
<dt>{% trans "Valid from" %}</dt>
|
<dt>{% trans "Valid from" %}</dt>
|
||||||
<dd>{{ membership.date_start|date:"SHORT_DATETIME_FORMAT" }}
|
<dd>{{ membership.date_start|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
<dt>{% trans "Valid until" %}</dt>
|
<dt>{% trans "Valid until" %}</dt>
|
||||||
<dd>{{ membership.date_end|date:"SHORT_DATETIME_FORMAT" }}
|
<dd>{{ membership.date_end|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
<dt>{% trans "Attendee name" %}</dt>
|
<dt>{% trans "Attendee name" %}</dt>
|
||||||
<dd>{{ membership.attendee_name }}
|
<dd>{{ membership.attendee_name|default_if_none:"–" }}
|
||||||
<dt>{% trans "Maximum usages" %}</dt>
|
<dt>{% trans "Maximum usages" %}</dt>
|
||||||
<dd>{{ membership.membership_type.max_usages|default_if_none:"–" }}</dd>
|
<dd>{{ membership.membership_type.max_usages|default_if_none:"–" }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default items">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{% trans "Usages" %}
|
{% trans "Usages" %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<table class="panel-body table table-hover">
|
<div class="panel-body">
|
||||||
<caption class="sr-only">{% trans "Usages" %}</caption>
|
{% if usages %}
|
||||||
<thead>
|
<ul class="full-width-list alternating-rows">
|
||||||
<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 %}
|
{% for op in usages %}
|
||||||
<tr>
|
<li class="row">
|
||||||
<td>
|
<dl>
|
||||||
<strong>
|
<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 }}
|
{{ op.order.code }}-{{ op.positionid }}
|
||||||
|
</a>
|
||||||
</strong>
|
</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 %}
|
{% if op.order.testmode %}
|
||||||
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
<dd>
|
||||||
|
<small>
|
||||||
|
{% textbubble "warning" %}
|
||||||
|
{% trans "TEST MODE" %}
|
||||||
|
{% endtextbubble %}
|
||||||
|
</small>
|
||||||
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<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 }}
|
{{ op.order.event }}
|
||||||
{% if op.subevent %}
|
{% if op.subevent %}
|
||||||
<br>
|
<br>{{ op.subevent }}
|
||||||
{{ op.subevent|default:"" }}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
{% if not op.order.event.has_subevents and op.order.event.settings.show_dates_on_frontpage %}
|
||||||
<td>
|
<br>{{ op.order.event.get_date_range_display }}
|
||||||
{{ 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 %}
|
{% endif %}
|
||||||
</td>
|
</small>
|
||||||
<td class="text-right flip">
|
</dd>
|
||||||
<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>
|
</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 haven’t used this membership yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{% 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 don’t have any memberships in your account yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{% 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 don’t have any orders in your account yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
{% 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,33 +1,39 @@
|
|||||||
{% extends "pretixpresale/organizers/base.html" %}
|
{% extends "pretixpresale/organizers/customer_base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load icon %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% block title %}{% trans "Delete profile" %}{% endblock %}
|
{% block title %}{% trans "Delete profile" %}{% endblock %}
|
||||||
{% block content %}
|
{% block inner %}
|
||||||
<h2>
|
<div class="panel panel-default">
|
||||||
{% trans "Delete profile" %}
|
<div class="panel-heading">
|
||||||
</h2>
|
<h3 class="panel-title">
|
||||||
<form method="post">
|
{% icon "user" %} <b>{% trans "Delete profile" %}</b>
|
||||||
{% csrf_token %}
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
{% trans "Do you really want to delete the following profile from your account?" %}
|
{% trans "Do you really want to delete the following profile from your account?" %}
|
||||||
</p>
|
</p>
|
||||||
<address>
|
<p>
|
||||||
{{ profile.describe|linebreaksbr }}
|
{{ profile.describe|linebreaksbr }}
|
||||||
</address>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 col-sm-6">
|
<div class="col-md-4 col-sm-6">
|
||||||
<a class="btn btn-block btn-default btn-lg"
|
<a class="btn btn-block btn-default btn-lg"
|
||||||
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
|
href="{% abseventurl request.organizer "presale:organizer.customer.profiles" %}">
|
||||||
{% trans "Go back" %}
|
{% trans "Go back" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-md-offset-4 col-sm-6">
|
<div class="col-md-4 col-md-offset-4 col-sm-6">
|
||||||
<button class="btn btn-block btn-danger btn-lg" type="submit">
|
<button class="btn btn-block btn-danger btn-lg" type="submit">
|
||||||
|
{% icon "trash" %}
|
||||||
{% trans "Delete" %}
|
{% trans "Delete" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{% 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 don’t have any attendee profiles in your account yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -210,10 +210,13 @@ organizer_patterns = [
|
|||||||
re_path(r'^account/password$', pretix.presale.views.customer.ChangePasswordView.as_view(), name='organizer.customer.password'),
|
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/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/confirmchange$', pretix.presale.views.customer.ConfirmChangeView.as_view(), name='organizer.customer.change.confirm'),
|
||||||
re_path(r'^account/membership/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
|
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/addresses/(?P<id>\d+)/delete$', pretix.presale.views.customer.AddressDeleteView.as_view(), name='organizer.customer.address.delete'),
|
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/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.ProfileView.as_view(), name='organizer.customer.profile'),
|
re_path(r'^account/$', pretix.presale.views.customer.OrderView.as_view(), name='organizer.customer.index'),
|
||||||
|
|
||||||
re_path(r'^oauth2/v1/authorize$', pretix.presale.views.oidc_op.AuthorizeView.as_view(),
|
re_path(r'^oauth2/v1/authorize$', pretix.presale.views.oidc_op.AuthorizeView.as_view(),
|
||||||
name='organizer.oauth2.v1.authorize'),
|
name='organizer.oauth2.v1.authorize'),
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class LoginView(RedirectBackMixin, FormView):
|
|||||||
url = self.get_redirect_url()
|
url = self.get_redirect_url()
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
|
|
||||||
if self.request.GET.get("request_cross_domain_customer_auth") == "true":
|
if self.request.GET.get("request_cross_domain_customer_auth") == "true":
|
||||||
otpstore = SessionStore()
|
otpstore = SessionStore()
|
||||||
@@ -350,8 +350,42 @@ class CustomerRequiredMixin:
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ProfileView(CustomerRequiredMixin, ListView):
|
class CustomerAccountBaseMixin(CustomerRequiredMixin):
|
||||||
template_name = 'pretixpresale/organizers/customer_profile.html'
|
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'
|
||||||
context_object_name = 'orders'
|
context_object_name = 'orders'
|
||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
|
|
||||||
@@ -369,18 +403,6 @@ class ProfileView(CustomerRequiredMixin, ListView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**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(
|
s = OrderPosition.objects.filter(
|
||||||
order=OuterRef('pk')
|
order=OuterRef('pk')
|
||||||
@@ -404,7 +426,18 @@ class ProfileView(CustomerRequiredMixin, ListView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class MembershipUsageView(CustomerRequiredMixin, ListView):
|
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):
|
||||||
template_name = 'pretixpresale/organizers/customer_membership.html'
|
template_name = 'pretixpresale/organizers/customer_membership.html'
|
||||||
context_object_name = 'usages'
|
context_object_name = 'usages'
|
||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
@@ -428,7 +461,16 @@ class MembershipUsageView(CustomerRequiredMixin, ListView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class AddressDeleteView(CustomerRequiredMixin, CompatDeleteView):
|
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):
|
||||||
template_name = 'pretixpresale/organizers/customer_address_delete.html'
|
template_name = 'pretixpresale/organizers/customer_address_delete.html'
|
||||||
context_object_name = 'address'
|
context_object_name = 'address'
|
||||||
|
|
||||||
@@ -436,10 +478,19 @@ class AddressDeleteView(CustomerRequiredMixin, CompatDeleteView):
|
|||||||
return get_object_or_404(InvoiceAddress.profiles, customer=self.request.customer, pk=self.kwargs.get('id'))
|
return get_object_or_404(InvoiceAddress.profiles, customer=self.request.customer, pk=self.kwargs.get('id'))
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.addresses', kwargs={})
|
||||||
|
|
||||||
|
|
||||||
class ProfileDeleteView(CustomerRequiredMixin, CompatDeleteView):
|
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):
|
||||||
template_name = 'pretixpresale/organizers/customer_profile_delete.html'
|
template_name = 'pretixpresale/organizers/customer_profile_delete.html'
|
||||||
context_object_name = 'profile'
|
context_object_name = 'profile'
|
||||||
|
|
||||||
@@ -447,10 +498,10 @@ class ProfileDeleteView(CustomerRequiredMixin, CompatDeleteView):
|
|||||||
return get_object_or_404(self.request.customer.attendee_profiles, pk=self.kwargs.get('id'))
|
return get_object_or_404(self.request.customer.attendee_profiles, pk=self.kwargs.get('id'))
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.profiles', kwargs={})
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordView(CustomerRequiredMixin, FormView):
|
class ChangePasswordView(CustomerAccountBaseMixin, FormView):
|
||||||
template_name = 'pretixpresale/organizers/customer_password.html'
|
template_name = 'pretixpresale/organizers/customer_password.html'
|
||||||
form_class = ChangePasswordForm
|
form_class = ChangePasswordForm
|
||||||
|
|
||||||
@@ -465,7 +516,7 @@ class ChangePasswordView(CustomerRequiredMixin, FormView):
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -483,7 +534,7 @@ class ChangePasswordView(CustomerRequiredMixin, FormView):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class ChangeInformationView(CustomerRequiredMixin, FormView):
|
class ChangeInformationView(CustomerAccountBaseMixin, FormView):
|
||||||
template_name = 'pretixpresale/organizers/customer_info.html'
|
template_name = 'pretixpresale/organizers/customer_info.html'
|
||||||
form_class = ChangeInfoForm
|
form_class = ChangeInfoForm
|
||||||
|
|
||||||
@@ -498,7 +549,7 @@ class ChangeInformationView(CustomerRequiredMixin, FormView):
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if form.cleaned_data['email'] != self.initial_email and not self.request.customer.provider:
|
if form.cleaned_data['email'] != self.initial_email and not self.request.customer.provider:
|
||||||
@@ -581,7 +632,7 @@ class ConfirmChangeView(View):
|
|||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
|
|
||||||
|
|
||||||
class SSOLoginView(RedirectBackMixin, View):
|
class SSOLoginView(RedirectBackMixin, View):
|
||||||
@@ -641,7 +692,7 @@ class SSOLoginView(RedirectBackMixin, View):
|
|||||||
url = self.get_redirect_url()
|
url = self.get_redirect_url()
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
@@ -864,7 +915,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
|
|||||||
url = self.get_redirect_url(redirect_to)
|
url = self.get_redirect_url(redirect_to)
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
|
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
|
||||||
else:
|
else:
|
||||||
if self.request.session.get(f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'):
|
if self.request.session.get(f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'):
|
||||||
otpstore = SessionStore()
|
otpstore = SessionStore()
|
||||||
|
|||||||
64
src/pretix/static/pretixbase/js/addressform.js
Normal file
64
src/pretix/static/pretixbase/js/addressform.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
$(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
$("select[data-country-information-url]").each(function () {
|
||||||
|
let xhr;
|
||||||
|
const dependency = $(this),
|
||||||
|
loader = $("<span class='fa fa-cog fa-spin'></span>").hide().prependTo(dependency.closest(".form-group").find("label")),
|
||||||
|
url = this.getAttribute('data-country-information-url'),
|
||||||
|
form = dependency.closest(".panel-body, form, .profile-scope"),
|
||||||
|
isRequired = dependency.closest(".form-group").is(".required"),
|
||||||
|
dependents = {
|
||||||
|
'city': form.find("input[name$=city]"),
|
||||||
|
'zipcode': form.find("input[name$=zipcode]"),
|
||||||
|
'street': form.find("textarea[name$=street]"),
|
||||||
|
'state': form.find("select[name$=state]"),
|
||||||
|
'vat_id': form.find("input[name$=vat_id]"),
|
||||||
|
},
|
||||||
|
update = function (ev) {
|
||||||
|
if (xhr) {
|
||||||
|
xhr.abort();
|
||||||
|
}
|
||||||
|
for (var k in dependents) dependents[k].prop("disabled", true);
|
||||||
|
loader.show();
|
||||||
|
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", "");
|
||||||
|
dependents.state.find("option:not([value=''])").remove();
|
||||||
|
if (data.data.length > 0) {
|
||||||
|
$.each(data.data, function (k, s) {
|
||||||
|
var o = $("<option>").attr("value", s.code).text(s.name);
|
||||||
|
if (selected_value == s.code) o.prop("selected", true);
|
||||||
|
dependents.state.append(o);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for(var k in dependents) {
|
||||||
|
const options = data[k],
|
||||||
|
dependent = dependents[k],
|
||||||
|
visible = 'visible' in options ? options.visible : true,
|
||||||
|
required = 'required' in options && options.required && isRequired && visible;
|
||||||
|
|
||||||
|
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||||
|
dependent.prop("required", required);
|
||||||
|
}
|
||||||
|
for (var k in dependents) dependents[k].prop("disabled", false);
|
||||||
|
}).always(function() {
|
||||||
|
loader.hide();
|
||||||
|
}).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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
dependents.state.prop("data-selected-value", dependents.state.val());
|
||||||
|
update();
|
||||||
|
dependency.on("change", update);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -8,7 +8,7 @@ $gray-lightest: lighten(#000, 97.25%);
|
|||||||
|
|
||||||
$font-family-sans-serif: var(--pretix-font-family-sans-serif);
|
$font-family-sans-serif: var(--pretix-font-family-sans-serif);
|
||||||
$text-color: #222222 !default;
|
$text-color: #222222 !default;
|
||||||
$text-muted: #767676 !default;
|
$text-muted: #737373 !default;
|
||||||
$input-color-placeholder: lighten(#000, 70%) !default;
|
$input-color-placeholder: lighten(#000, 70%) !default;
|
||||||
|
|
||||||
$border-radius-base: var(--pretix-border-radius-base);
|
$border-radius-base: var(--pretix-border-radius-base);
|
||||||
|
|||||||
@@ -434,60 +434,6 @@ var form_handlers = function (el) {
|
|||||||
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("input[name$=vat_id][data-countries-with-vat-id]").each(function () {
|
|
||||||
var dependent = $(this),
|
|
||||||
dependency_country = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
|
||||||
dependency_id_is_business_1 = $(this).closest(".panel-body, form").find('input[id$=id_is_business_1]'),
|
|
||||||
update = function (ev) {
|
|
||||||
if (dependency_id_is_business_1.length && !dependency_id_is_business_1.prop("checked")) {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
} else if (dependent.attr('data-countries-with-vat-id').split(',').includes(dependency_country.val())) {
|
|
||||||
dependent.closest(".form-group").show();
|
|
||||||
} else {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
update();
|
|
||||||
dependency_country.on("change", update);
|
|
||||||
dependency_id_is_business_1.on("change", update);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("select[name$=state]:not([data-static])").each(function () {
|
|
||||||
var dependent = $(this),
|
|
||||||
counter = 0,
|
|
||||||
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
|
||||||
update = function (ev) {
|
|
||||||
counter++;
|
|
||||||
var curCounter = counter;
|
|
||||||
dependent.prop("disabled", true);
|
|
||||||
dependency.closest(".form-group").find("label").prepend("<span class='fa fa-cog fa-spin'></span> ");
|
|
||||||
$.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) {
|
|
||||||
if (counter > curCounter) {
|
|
||||||
return; // Lost race
|
|
||||||
}
|
|
||||||
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
|
|
||||||
if (data.data.length > 0) {
|
|
||||||
$.each(data.data, function (k, s) {
|
|
||||||
dependent.append($("<option>").attr("value", s.code).text(s.name));
|
|
||||||
});
|
|
||||||
dependent.closest(".form-group").show();
|
|
||||||
dependent.prop('required', dependency.prop("required"));
|
|
||||||
} else {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
dependent.prop("required", false);
|
|
||||||
}
|
|
||||||
dependent.prop("disabled", false);
|
|
||||||
dependency.closest(".form-group").find("label .fa-spin").remove();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (dependent.find("option").length === 1) {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
} else {
|
|
||||||
dependent.prop('required', dependency.prop("required"));
|
|
||||||
}
|
|
||||||
dependency.on("change", update);
|
|
||||||
});
|
|
||||||
|
|
||||||
el.find("div.scrolling-choice:not(.no-search)").each(function () {
|
el.find("div.scrolling-choice:not(.no-search)").each(function () {
|
||||||
if ($(this).find("input[type=text]").length > 0) {
|
if ($(this).find("input[type=text]").length > 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -517,65 +517,6 @@ $(function () {
|
|||||||
dependency.closest('.form-group, form').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
dependency.closest('.form-group, form').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("input[name$=vat_id][data-countries-with-vat-id]").each(function () {
|
|
||||||
var dependent = $(this),
|
|
||||||
dependency_country = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
|
||||||
dependency_id_is_business_1 = $(this).closest(".panel-body, form").find('input[id$=id_is_business_1]'),
|
|
||||||
update = function (ev) {
|
|
||||||
if (dependency_id_is_business_1.length && !dependency_id_is_business_1.prop("checked")) {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
} else if (dependent.attr('data-countries-with-vat-id').split(',').includes(dependency_country.val())) {
|
|
||||||
dependent.closest(".form-group").show();
|
|
||||||
} else {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
update();
|
|
||||||
dependency_country.on("change", update);
|
|
||||||
dependency_id_is_business_1.on("change", update);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("select[name$=state]").each(function () {
|
|
||||||
var dependent = $(this),
|
|
||||||
counter = 0,
|
|
||||||
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
|
||||||
update = function (ev) {
|
|
||||||
counter++;
|
|
||||||
var curCounter = counter;
|
|
||||||
dependent.prop("disabled", true);
|
|
||||||
dependency.closest(".form-group").find("label").prepend("<span class='fa fa-cog fa-spin'></span> ");
|
|
||||||
$.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) {
|
|
||||||
if (counter > curCounter) {
|
|
||||||
return; // Lost race
|
|
||||||
}
|
|
||||||
var selected_value = dependent.prop("data-selected-value");
|
|
||||||
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
|
|
||||||
if (data.data.length > 0) {
|
|
||||||
$.each(data.data, function (k, s) {
|
|
||||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
|
||||||
if (s.code == selected_value || (selected_value && selected_value.indexOf && selected_value.indexOf(s.code) > -1)) {
|
|
||||||
o.prop("selected", true);
|
|
||||||
}
|
|
||||||
dependent.append(o);
|
|
||||||
});
|
|
||||||
dependent.closest(".form-group").show();
|
|
||||||
dependent.prop('required', dependency.prop("required"));
|
|
||||||
} else {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
dependent.prop("required", false);
|
|
||||||
}
|
|
||||||
dependent.prop("disabled", false);
|
|
||||||
dependency.closest(".form-group").find("label .fa-spin").remove();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (dependent.find("option").length === 1) {
|
|
||||||
dependent.closest(".form-group").hide();
|
|
||||||
} else {
|
|
||||||
dependent.prop('required', dependency.prop("required"));
|
|
||||||
}
|
|
||||||
dependency.on("change", update);
|
|
||||||
});
|
|
||||||
|
|
||||||
form_handlers($("body"));
|
form_handlers($("body"));
|
||||||
|
|
||||||
var local_tz = moment.tz.guess()
|
var local_tz = moment.tz.guess()
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ $body-bg: #f5f5f5 !default;
|
|||||||
float: left;
|
float: left;
|
||||||
margin-right: .25em;
|
margin-right: .25em;
|
||||||
}
|
}
|
||||||
.status-dot {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
.text-warning { color: $brand-warning; }
|
.text-warning { color: $brand-warning; }
|
||||||
.text-info { color: $brand-info; }
|
.text-info { color: $brand-info; }
|
||||||
.text-success { color: $brand-success; }
|
.text-success { color: $brand-success; }
|
||||||
@@ -98,6 +95,9 @@ footer nav .btn-link {
|
|||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.subnav a.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.locales ul {
|
.locales ul {
|
||||||
display: inline;
|
display: inline;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -499,6 +499,9 @@ h2 .label {
|
|||||||
&.availability .progress-bar-success {
|
&.availability .progress-bar-success {
|
||||||
background: var(--pretix-brand-success-lighten-20);
|
background: var(--pretix-brand-success-lighten-20);
|
||||||
}
|
}
|
||||||
|
&.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
@@ -527,6 +530,76 @@ h2 .label {
|
|||||||
font-size: 30px;
|
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 "_iframe.scss";
|
||||||
@import "_a11y.scss";
|
@import "_a11y.scss";
|
||||||
@import "_print.scss";
|
@import "_print.scss";
|
||||||
|
|||||||
@@ -1207,6 +1207,65 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
}
|
}
|
||||||
assert ia.name_cached == 'Mr John Kennedy'
|
assert ia.name_cached == 'Mr John Kennedy'
|
||||||
|
|
||||||
|
def test_invoice_address_required_no_zipcode_country(self):
|
||||||
|
self.event.settings.invoice_address_asked = True
|
||||||
|
self.event.settings.invoice_address_required = True
|
||||||
|
self.event.settings.invoice_address_not_asked_free = True
|
||||||
|
self.event.settings.set('name_scheme', 'title_given_middle_family')
|
||||||
|
|
||||||
|
with scopes_disabled():
|
||||||
|
CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
|
self.assertEqual(len(doc.select('input[name="city"]')), 1)
|
||||||
|
|
||||||
|
# Not all required fields filled out, expect failure
|
||||||
|
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'is_business': 'business',
|
||||||
|
'company': 'Foo',
|
||||||
|
'name_parts_0': 'Mr',
|
||||||
|
'name_parts_1': 'John',
|
||||||
|
'name_parts_2': '',
|
||||||
|
'name_parts_3': 'Kennedy',
|
||||||
|
'street': '',
|
||||||
|
'zipcode': '',
|
||||||
|
'city': '',
|
||||||
|
'country': 'BI',
|
||||||
|
'email': 'admin@localhost'
|
||||||
|
}, follow=True)
|
||||||
|
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||||
|
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||||
|
|
||||||
|
# Correct request for a country where zip code is not required in address
|
||||||
|
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'is_business': 'business',
|
||||||
|
'company': 'Foo',
|
||||||
|
'name_parts_0': 'Mr',
|
||||||
|
'name_parts_1': 'John',
|
||||||
|
'name_parts_2': '',
|
||||||
|
'name_parts_3': 'Kennedy',
|
||||||
|
'street': 'BP 12345',
|
||||||
|
'zipcode': '',
|
||||||
|
'city': 'Bujumbura',
|
||||||
|
'country': 'BI',
|
||||||
|
'email': 'admin@localhost'
|
||||||
|
}, follow=True)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
with scopes_disabled():
|
||||||
|
ia = InvoiceAddress.objects.last()
|
||||||
|
assert ia.name_parts == {
|
||||||
|
'title': 'Mr',
|
||||||
|
'given_name': 'John',
|
||||||
|
'middle_name': '',
|
||||||
|
'family_name': 'Kennedy',
|
||||||
|
"_scheme": "title_given_middle_family"
|
||||||
|
}
|
||||||
|
assert ia.name_cached == 'Mr John Kennedy'
|
||||||
|
|
||||||
def test_invoice_address_validated(self):
|
def test_invoice_address_validated(self):
|
||||||
self.event.settings.invoice_address_asked = True
|
self.event.settings.invoice_address_asked = True
|
||||||
self.event.settings.invoice_address_required = True
|
self.event.settings.invoice_address_required = True
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ def test_org_sso_login_new_customer_email_conflict(env, client, provider):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize("url", [
|
@pytest.mark.parametrize("url", [
|
||||||
"account/change",
|
"account/change",
|
||||||
"account/membership/1/",
|
"account/memberships/1/",
|
||||||
"account/",
|
"account/",
|
||||||
])
|
])
|
||||||
def test_login_required(client, env, url):
|
def test_login_required(client, env, url):
|
||||||
|
|||||||
Reference in New Issue
Block a user