Allow to save invoice addresses and attendee profiles to customer account (#2084)

Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
Raphael Michel
2021-09-06 20:50:25 +02:00
committed by GitHub
parent 89554a82eb
commit 28d78e40f9
21 changed files with 1208 additions and 148 deletions

View File

@@ -31,7 +31,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import copy
import inspect
from collections import defaultdict
from decimal import Decimal
@@ -59,6 +59,7 @@ from pretix.base.services.cart import (
)
from pretix.base.services.memberships import validate_memberships_in_order
from pretix.base.services.orders import perform_order
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import validate_cart_addons
from pretix.base.templatetags.phone_format import phone_format
from pretix.base.templatetags.rich_text import rich_text_snippet
@@ -227,7 +228,7 @@ class TemplateFlowStep(TemplateResponseMixin, BaseCheckoutFlowStep):
raise NotImplementedError()
class CustomerStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
class CustomerStep(CartMixin, TemplateFlowStep):
priority = 45
identifier = "customer"
template_name = "pretixpresale/event/checkout_customer.html"
@@ -352,7 +353,7 @@ class CustomerStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
return ctx
class MembershipStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
class MembershipStep(CartMixin, TemplateFlowStep):
priority = 47
identifier = "membership"
template_name = "pretixpresale/event/checkout_membership.html"
@@ -772,6 +773,9 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
'name_parts': self.cart_customer.name_parts
})
if 'saved_invoice_address' in self.cart_session:
initial['saved_id'] = self.cart_session['saved_invoice_address']
override_sets = self._contact_override_sets
for overrides in override_sets:
initial.update({
@@ -791,6 +795,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
request=self.request,
initial=initial,
instance=self.invoice_address,
allow_save=bool(self.cart_customer),
validate_vat_id=self.eu_reverse_charge_relevant, all_optional=self.all_optional)
for name, field in f.fields.items():
if wd_initial.get(name) and wd.get('fix', '') == 'true':
@@ -828,6 +833,23 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
self.cart_session['contact_form_data'] = d
if self.address_asked or self.request.event.settings.invoice_name_required:
addr = self.invoice_form.save()
if self.cart_customer and self.invoice_form.cleaned_data.get('save'):
if self.invoice_form.cleaned_data.get('saved_id'):
saved = InvoiceAddress.profiles.filter(
customer=self.cart_customer, pk=self.invoice_form.cleaned_data.get('saved_id')
).first() or InvoiceAddress(customer=self.cart_customer)
else:
saved = InvoiceAddress(customer=self.cart_customer)
for f in InvoiceAddress._meta.fields:
if f.name not in ('order', 'customer', 'last_modified', 'pk', 'id'):
val = getattr(addr, f.name)
setattr(saved, f.name, copy.deepcopy(val))
saved.save()
self.cart_session['saved_invoice_address'] = saved.pk
try:
diff = update_tax_rates(
event=request.event,
@@ -946,6 +968,93 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
ctx['cart'] = self.get_cart()
ctx['cart_session'] = self.cart_session
ctx['invoice_address_asked'] = self.address_asked
if self.cart_customer:
if self.address_asked:
addresses = self.cart_customer.stored_addresses.all()
addresses_list = []
for a in addresses:
data = {
"_pk": a.pk,
"_country_for_address": a.country.name,
"_state_for_address": a.state_for_address,
"_name": a.name,
"is_business": "business" if a.is_business else "individual",
}
if a.name_parts:
name_parts = a.name_parts
# map full_name to name_parts and vice versa
scheme = PERSON_NAME_SCHEMES[self.request.event.settings.name_scheme]
available_keys = name_parts.keys()
asked_keys = [k for (k, l, w) in scheme["fields"]]
if not set(available_keys).intersection(asked_keys):
if "full_name" in available_keys:
name_keys = ("given_name", "family_name")
name_split = name_parts.get("full_name").rsplit(" ", 1)
name_parts = dict(zip(name_keys, name_split))
elif "full_name" in asked_keys:
name_parts = {
"full_name": a.name
}
for i, k in enumerate(asked_keys):
data[f"name_parts_{i}"] = name_parts.get(k) or ""
for k in (
"company", "street", "zipcode", "city", "country", "state",
"state_for_address", "vat_id", "custom_field", "internal_reference", "beneficiary"
):
v = getattr(a, k) or ""
# always add all values of an address even when empty,
# so an address always gets fully overwritten client-side
data[k] = str(v)
addresses_list.append(data)
ctx['addresses_data'] = addresses_list
profiles = list(self.cart_customer.attendee_profiles.all())
profiles_list = []
for p in profiles:
data = {
"_pk": p.pk,
"_country_for_address": p.country.name,
"_state_for_address": p.state_for_address,
"_attendee_name": p.attendee_name,
}
if p.attendee_name_parts:
name_parts = p.attendee_name_parts
# map full_name to name_parts and vice versa
scheme = PERSON_NAME_SCHEMES[self.request.event.settings.name_scheme]
available_keys = name_parts.keys()
asked_keys = [k for (k, l, w) in scheme["fields"]]
if not set(available_keys).intersection(asked_keys):
if "full_name" in available_keys:
name_keys = ("given_name", "family_name")
name_split = name_parts.get("full_name").rsplit(" ", 1)
name_parts = dict(zip(name_keys, name_split))
elif "full_name" in asked_keys:
name_parts = {
"full_name": p.attendee_name
}
for i, k in enumerate(asked_keys):
data[f"attendee_name_parts_{i}"] = name_parts.get(k) or ""
for k in ("attendee_email", "company", "street", "zipcode", "city", "country", "state"):
v = getattr(p, k) or ""
# always add all values of an address even when empty,
# so an address always gets fully overwritten client-side
data[k] = str(v)
for a in p.answers:
data[a["field_name"]] = {
"label": a["field_label"],
"value": a["value"],
"identifier": a["question_identifier"],
"type": a["question_type"],
}
profiles_list.append(data)
ctx['profiles_data'] = profiles_list
return ctx

View File

@@ -124,6 +124,22 @@ class InvoiceAddressForm(BaseInvoiceAddressForm):
required_css_class = 'required'
vat_warning = True
def __init__(self, *args, **kwargs):
allow_save = kwargs.pop('allow_save', False)
super().__init__(*args, **kwargs)
if allow_save:
self.fields['saved_id'] = forms.IntegerField(
required=False,
help_text=" ", # non-breaking-space, will be overwritten by JavaScript, needed here for HTML-output
label=_("Save to address"),
widget=forms.Select(choices=(("", _("Create new address")),))
)
self.fields['save'] = forms.BooleanField(
label=_('Save address in my customer account for future purchases'),
required=False,
initial=False,
)
class InvoiceNameForm(InvoiceAddressForm):
@@ -142,6 +158,22 @@ class QuestionsForm(BaseQuestionsForm):
"""
required_css_class = 'required'
def __init__(self, *args, **kwargs):
allow_save = kwargs.pop('allow_save', False)
super().__init__(*args, **kwargs)
if allow_save and self.fields:
self.fields['save'] = forms.BooleanField(
label=_('Save answers to my customer profiles for future purchases'),
required=False,
initial=False,
)
self.fields['saved_id'] = forms.IntegerField(
required=False,
help_text=" ", # non-breaking-space, will be overwritten by JavaScript, needed here for HTML-output
label=_("Save to profile"),
widget=forms.Select(choices=(("", _("Create new profile")),))
)
class AddOnRadioSelect(forms.RadioSelect):
option_template_name = 'pretixpresale/forms/addon_choice_option.html'

View File

@@ -29,11 +29,11 @@ from django.utils.safestring import mark_safe
from django.utils.translation import pgettext
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None):
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None, attrs=None):
"""
Render a label with content
"""
attrs = {}
attrs = attrs or {}
if label_for:
attrs['for'] = label_for
if label_class:
@@ -118,6 +118,7 @@ class CheckoutFieldRenderer(FieldRenderer):
widget.attrs["aria-describedby"] = " ".join(help_ids)
def add_label(self, html):
attrs = {}
label = self.get_label()
if hasattr(self.field.field, '_show_required'):
@@ -141,11 +142,15 @@ class CheckoutFieldRenderer(FieldRenderer):
label_for = self.field.id_for_label
label_id = ""
if hasattr(self.field.field, 'question') and self.field.field.question.identifier:
attrs["data-identifier"] = self.field.field.question.identifier
html = render_label(
label,
label_for=label_for,
label_class=self.get_label_class(),
label_id=label_id,
attrs=attrs,
optional=not required and not isinstance(self.widget, CheckboxInput),
is_valid=is_valid
) + html

View File

@@ -2,6 +2,8 @@
{% load i18n %}
{% load bootstrap3 %}
{% load rich_text %}
{% load lists %}
{% load escapejson %}
{% block inner %}
<p>{% trans "Before we continue, we need you to answer some questions." %}</p>
<p class="required-legend" aria-hidden="true">
@@ -9,6 +11,9 @@
You need to fill all fields that are marked with <span>*</span> to continue.
{% endblocktrans %}
</p>
{% if profiles_data %}
{{ profiles_data|json_script:"profiles_json" }}
{% endif %}
<form class="form-horizontal" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="panel-group" id="questions_group">
@@ -39,8 +44,25 @@
<i class="fa fa-angle-down collapse-indicator" aria-hidden="true"></i>
</h4>
</summary>
<div id="invoice">
{% if addresses_data %}
{{ addresses_data|json_script:"addresses_json" }}
{% endif %}
<div id="invoice" class="profile-scope" data-profiles-id="addresses_json">
<div class="panel-body">
{% if addresses_data %}
<div class="form-group profile-select-container">
<label class="col-md-3 control-label" for="address-list-select">{% trans "Auto-fill with address" %}</label>
<div class="col-md-9">
<p class="profile-select-control">
<select class="profile-select form-control" id="address-list-select"></select>
</p>
<p class="help-block profile-desc" id="selected-address-desc"></p>
<p><button type="button" class="profile-apply btn btn-default" aria-describedby="selected-address-desc"
><i class="fa fa-address-card-o fa-lg" aria-hidden="true"></i>&nbsp; {% trans "Fill form" %}</button>
</p>
</div>
</div>
{% endif %}
{% if event.settings.invoice_address_explanation_text %}
<div>
{{ event.settings.invoice_address_explanation_text|rich_text }}
@@ -127,7 +149,7 @@
{% for form in forms %}
{% if form.pos.item != pos.item %}
{# Add-Ons #}
<legend>
<legend{% if profiles_data %} class="profile-add-on-legend"{% endif %}>
{% if form.show_copy_answers_to_addon_button and event.settings.checkout_show_copy_answers_button %}
<span class="pull-right flip">
<button type="button" data-id="{{ forloop.parentloop.counter0 }}" data-addonid="{{ forloop.counter0 }}" name="copy" class="js-copy-answers-addon btn btn-default btn-xs">{% trans "Copy answers" %}</button>
@@ -136,7 +158,24 @@
+ {{ form.pos.item.name }}{% if form.pos.variation %} {{ form.pos.variation.value }}{% endif %}
</legend>
{% endif %}
<div data-idx="{{ forloop.parentloop.counter0 }}" data-addonidx="{{ forloop.counter0 }}">
<div data-idx="{{ forloop.parentloop.counter0 }}" data-addonidx="{{ forloop.counter0 }}" class="profile-scope{% if form.pos.item != pos.item %}{% if profiles_data %} profile-add-on{% endif %}{% endif %}">
{% if profiles_data %}
<div class="form-group profile-select-container">
<label class="col-md-3 control-label" for="profile-select-{{ forloop.parentloop.counter0 }}-{{ forloop.counter0 }}">{% trans "Auto-fill with profile" %}</label>
<div class="col-md-9">
<p class="profile-select-control">
<select class="profile-select form-control" id="profile-select-{{ forloop.parentloop.counter0 }}-{{ forloop.counter0 }}"></select>
</p>
<p class="help-block profile-desc" id="selected-profile-desc-{{ forloop.parentloop.counter0 }}-{{ forloop.counter0 }}"></p>
<p>
<button type="button" class="profile-apply btn btn-default"
aria-describedby="selected-profile-desc-{{ forloop.parentloop.counter0 }}-{{ forloop.counter0 }}"
><i class="fa fa-address-card-o fa-lg" aria-hidden="true"></i>&nbsp; {% trans "Fill form" %}</button>
</p>
</div>
</div>
{% endif %}
{% bootstrap_form form layout="checkout" %}
</div>
{% endfor %}

View File

@@ -0,0 +1,33 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load eventurl %}
{% block title %}{% trans "Delete address" %}{% endblock %}
{% block content %}
<h2>
{% trans "Delete address" %}
</h2>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following address from your account?" %}
</p>
<address>
{{ address.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}

View File

@@ -37,125 +37,203 @@
</div>
</div>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Memberships" %}
</h3>
</div>
<table class="panel-body table table-hover">
<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>
<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 }}
</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">
<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>
</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>
{% endfor %}
</tbody>
</table>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Orders" %}
</h3>
</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>
<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 }}
</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>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load eventurl %}
{% block title %}{% trans "Delete profile" %}{% endblock %}
{% block content %}
<h2>
{% trans "Delete profile" %}
</h2>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following profile from your account?" %}
</p>
<address>
{{ profile.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}

View File

@@ -179,6 +179,8 @@ organizer_patterns = [
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/membership/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
re_path(r'^account/addresses/(?P<id>\d+)/delete$', pretix.presale.views.customer.AddressDeleteView.as_view(), name='organizer.customer.address.delete'),
re_path(r'^account/profiles/(?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'),
]

View File

@@ -34,9 +34,9 @@ from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, ListView, View
from django.views.generic import DeleteView, FormView, ListView, View
from pretix.base.models import Customer, Order, OrderPosition
from pretix.base.models import Customer, InvoiceAddress, Order, OrderPosition
from pretix.base.services.mail import mail
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.presale.forms.customer import (
@@ -299,6 +299,7 @@ class ProfileView(CustomerRequiredMixin, ListView):
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']:
@@ -353,6 +354,28 @@ class MembershipUsageView(CustomerRequiredMixin, ListView):
return ctx
class AddressDeleteView(CustomerRequiredMixin, DeleteView):
template_name = 'pretixpresale/organizers/customer_address_delete.html'
context_object_name = 'address'
def get_object(self, **kwargs):
return get_object_or_404(InvoiceAddress.profiles, customer=self.request.customer, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
class ProfileDeleteView(CustomerRequiredMixin, DeleteView):
template_name = 'pretixpresale/organizers/customer_profile_delete.html'
context_object_name = 'profile'
def get_object(self, **kwargs):
return get_object_or_404(self.request.customer.attendee_profiles, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
class ChangePasswordView(CustomerRequiredMixin, FormView):
template_name = 'pretixpresale/organizers/customer_password.html'
form_class = ChangePasswordForm

View File

@@ -47,3 +47,14 @@ class QuestionsViewMixin(BaseQuestionsViewMixin):
def _positions_for_questions(self):
cart = get_cart(self.request)
return sorted(list(cart), key=self._keyfunc)
def question_form_kwargs(self, cr):
d = {
'allow_save': bool(self.cart_customer),
'initial': {},
}
if f'saved_attendee_profile_{cr.pk}' in self.cart_session:
d['initial']['saved_id'] = self.cart_session[f'saved_attendee_profile_{cr.pk}']
return d