mirror of
https://github.com/pretix/pretix.git
synced 2026-08-28 13:34:40 +00:00
Add note field and external identifier to customers (#2605)
This commit is contained in:
@@ -14,6 +14,7 @@ The customer resource contains the following public fields:
|
|||||||
Field Type Description
|
Field Type Description
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
identifier string Internal ID of the customer
|
identifier string Internal ID of the customer
|
||||||
|
external_identifier string External ID of the customer (or ``null``)
|
||||||
email string Customer email address
|
email string Customer email address
|
||||||
name string Name of this customer (or ``null``)
|
name string Name of this customer (or ``null``)
|
||||||
name_parts object of strings Decomposition of name (i.e. given name, family name)
|
name_parts object of strings Decomposition of name (i.e. given name, family name)
|
||||||
@@ -24,6 +25,7 @@ last_login datetime Date and time o
|
|||||||
date_joined datetime Date and time of registration
|
date_joined datetime Date and time of registration
|
||||||
locale string Preferred language of the customer
|
locale string Preferred language of the customer
|
||||||
last_modified datetime Date and time of modification of the record
|
last_modified datetime Date and time of modification of the record
|
||||||
|
notes string Internal notes and comments (or ``null``)
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
@@ -58,6 +60,7 @@ Endpoints
|
|||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": null,
|
||||||
"email": "customer@example.org",
|
"email": "customer@example.org",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
"name_parts": {
|
"name_parts": {
|
||||||
@@ -69,7 +72,8 @@ Endpoints
|
|||||||
"last_login": null,
|
"last_login": null,
|
||||||
"date_joined": "2021-04-06T13:44:22.809216Z",
|
"date_joined": "2021-04-06T13:44:22.809216Z",
|
||||||
"locale": "de",
|
"locale": "de",
|
||||||
"last_modified": "2021-04-06T13:44:22.809377Z"
|
"last_modified": "2021-04-06T13:44:22.809377Z",
|
||||||
|
"notes": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -103,6 +107,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": null,
|
||||||
"email": "customer@example.org",
|
"email": "customer@example.org",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
"name_parts": {
|
"name_parts": {
|
||||||
@@ -114,7 +119,8 @@ Endpoints
|
|||||||
"last_login": null,
|
"last_login": null,
|
||||||
"date_joined": "2021-04-06T13:44:22.809216Z",
|
"date_joined": "2021-04-06T13:44:22.809216Z",
|
||||||
"locale": "de",
|
"locale": "de",
|
||||||
"last_modified": "2021-04-06T13:44:22.809377Z"
|
"last_modified": "2021-04-06T13:44:22.809377Z",
|
||||||
|
"notes": null
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
@@ -150,6 +156,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": null,
|
||||||
"email": "test@example.org",
|
"email": "test@example.org",
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -193,6 +200,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": null,
|
||||||
"email": "test@example.org",
|
"email": "test@example.org",
|
||||||
…
|
…
|
||||||
}
|
}
|
||||||
@@ -226,6 +234,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": null,
|
||||||
"email": null,
|
"email": null,
|
||||||
…
|
…
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ class CustomerSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ('identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined',
|
fields = ('identifier', 'external_identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined',
|
||||||
'locale', 'last_modified')
|
'locale', 'last_modified', 'notes')
|
||||||
|
|
||||||
|
|
||||||
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
payment_providers=Subquery(p_providers, output_field=CharField()),
|
payment_providers=Subquery(p_providers, output_field=CharField()),
|
||||||
invoice_numbers=Subquery(i_numbers, output_field=CharField()),
|
invoice_numbers=Subquery(i_numbers, output_field=CharField()),
|
||||||
pcnt=Subquery(s, output_field=IntegerField())
|
pcnt=Subquery(s, output_field=IntegerField())
|
||||||
).select_related('invoice_address')
|
).select_related('invoice_address', 'customer')
|
||||||
|
|
||||||
qs = self._date_filter(qs, form_data, rel='')
|
qs = self._date_filter(qs, form_data, rel='')
|
||||||
|
|
||||||
@@ -268,8 +268,8 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
tax_rates = self._get_all_tax_rates(qs)
|
tax_rates = self._get_all_tax_rates(qs)
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
_('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'), _('Order date'),
|
_('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'),
|
||||||
_('Order time'), _('Company'), _('Name'),
|
_('Order date'), _('Order time'), _('Company'), _('Name'),
|
||||||
]
|
]
|
||||||
name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None
|
name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None
|
||||||
if name_scheme and len(name_scheme['fields']) > 1:
|
if name_scheme and len(name_scheme['fields']) > 1:
|
||||||
@@ -294,6 +294,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
headers.append(_('Follow-up date'))
|
headers.append(_('Follow-up date'))
|
||||||
headers.append(_('Positions'))
|
headers.append(_('Positions'))
|
||||||
headers.append(_('E-mail address verified'))
|
headers.append(_('E-mail address verified'))
|
||||||
|
headers.append(_('External customer ID'))
|
||||||
headers.append(_('Payment providers'))
|
headers.append(_('Payment providers'))
|
||||||
if form_data.get('include_payment_amounts'):
|
if form_data.get('include_payment_amounts'):
|
||||||
payment_methods = self._get_all_payment_methods(qs)
|
payment_methods = self._get_all_payment_methods(qs)
|
||||||
@@ -400,6 +401,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
|
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
|
||||||
row.append(order.pcnt)
|
row.append(order.pcnt)
|
||||||
row.append(_('Yes') if order.email_known_to_work else _('No'))
|
row.append(_('Yes') if order.email_known_to_work else _('No'))
|
||||||
|
row.append(str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '')
|
||||||
row.append(', '.join([
|
row.append(', '.join([
|
||||||
str(self.providers.get(p, p)) for p in sorted(set((order.payment_providers or '').split(',')))
|
str(self.providers.get(p, p)) for p in sorted(set((order.payment_providers or '').split(',')))
|
||||||
if p and p != 'free'
|
if p and p != 'free'
|
||||||
@@ -428,7 +430,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order__event__in=self.events,
|
order__event__in=self.events,
|
||||||
).annotate(
|
).annotate(
|
||||||
payment_providers=Subquery(p_providers, output_field=CharField()),
|
payment_providers=Subquery(p_providers, output_field=CharField()),
|
||||||
).select_related('order', 'order__invoice_address', 'tax_rule')
|
).select_related('order', 'order__invoice_address', 'order__customer', 'tax_rule')
|
||||||
if form_data['paid_only']:
|
if form_data['paid_only']:
|
||||||
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
||||||
|
|
||||||
@@ -459,6 +461,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
|
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
headers.append(_('External customer ID'))
|
||||||
headers.append(_('Payment providers'))
|
headers.append(_('Payment providers'))
|
||||||
yield headers
|
yield headers
|
||||||
|
|
||||||
@@ -502,6 +505,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
]
|
]
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
row += [''] * (8 + (len(name_scheme['fields']) if name_scheme and len(name_scheme['fields']) > 1 else 0))
|
row += [''] * (8 + (len(name_scheme['fields']) if name_scheme and len(name_scheme['fields']) > 1 else 0))
|
||||||
|
row.append(str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '')
|
||||||
row.append(', '.join([
|
row.append(', '.join([
|
||||||
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
|
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
|
||||||
if p and p != 'free'
|
if p and p != 'free'
|
||||||
@@ -524,7 +528,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
qs = base_qs.annotate(
|
qs = base_qs.annotate(
|
||||||
payment_providers=Subquery(p_providers, output_field=CharField()),
|
payment_providers=Subquery(p_providers, output_field=CharField()),
|
||||||
).select_related(
|
).select_related(
|
||||||
'order', 'order__invoice_address', 'item', 'variation',
|
'order', 'order__invoice_address', 'order__customer', 'item', 'variation',
|
||||||
'voucher', 'tax_rule'
|
'voucher', 'tax_rule'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'answers', 'answers__question', 'answers__options'
|
'answers', 'answers__question', 'answers__options'
|
||||||
@@ -611,6 +615,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
headers += [
|
headers += [
|
||||||
_('Sales channel'), _('Order locale'),
|
_('Sales channel'), _('Order locale'),
|
||||||
_('E-mail address verified'),
|
_('E-mail address verified'),
|
||||||
|
_('External customer ID'),
|
||||||
_('Payment providers'),
|
_('Payment providers'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -730,7 +735,8 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
row += [
|
row += [
|
||||||
order.sales_channel,
|
order.sales_channel,
|
||||||
order.locale,
|
order.locale,
|
||||||
_('Yes') if order.email_known_to_work else _('No')
|
_('Yes') if order.email_known_to_work else _('No'),
|
||||||
|
str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '',
|
||||||
]
|
]
|
||||||
row.append(', '.join([
|
row.append(', '.join([
|
||||||
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
|
str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(',')))
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-04-28 08:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0213_discount_condition_ignore_voucher_discounted'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='external_identifier',
|
||||||
|
field=models.CharField(max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -59,6 +59,8 @@ class Customer(LoggedModel):
|
|||||||
default=settings.LANGUAGE_CODE,
|
default=settings.LANGUAGE_CODE,
|
||||||
verbose_name=_('Language'))
|
verbose_name=_('Language'))
|
||||||
last_modified = models.DateTimeField(auto_now=True)
|
last_modified = models.DateTimeField(auto_now=True)
|
||||||
|
external_identifier = models.CharField(max_length=255, verbose_name=_('External identifier'), null=True, blank=True)
|
||||||
|
notes = models.TextField(verbose_name=_('Notes'), null=True, blank=True)
|
||||||
|
|
||||||
objects = ScopedManager(organizer='organizer')
|
objects = ScopedManager(organizer='organizer')
|
||||||
|
|
||||||
@@ -90,6 +92,8 @@ class Customer(LoggedModel):
|
|||||||
self.name_cached = ''
|
self.name_cached = ''
|
||||||
self.email = None
|
self.email = None
|
||||||
self.phone = None
|
self.phone = None
|
||||||
|
self.external_identifier = None
|
||||||
|
self.notes = None
|
||||||
self.save()
|
self.save()
|
||||||
self.all_logentries().update(data={}, shredded=True)
|
self.all_logentries().update(data={}, shredded=True)
|
||||||
self.orders.all().update(customer=None)
|
self.orders.all().update(customer=None)
|
||||||
|
|||||||
@@ -1265,7 +1265,8 @@ class CustomerFilterForm(FilterForm):
|
|||||||
orders = {
|
orders = {
|
||||||
'email': 'email',
|
'email': 'email',
|
||||||
'identifier': 'identifier',
|
'identifier': 'identifier',
|
||||||
'name_cached': 'name_cached',
|
'name': 'name_cached',
|
||||||
|
'external_identifier': 'external_identifier',
|
||||||
}
|
}
|
||||||
query = forms.CharField(
|
query = forms.CharField(
|
||||||
label=_('Search query'),
|
label=_('Search query'),
|
||||||
|
|||||||
@@ -607,7 +607,7 @@ class CustomerUpdateForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['is_active', 'name_parts', 'email', 'is_verified', 'phone', 'locale']
|
fields = ['is_active', 'external_identifier', 'name_parts', 'email', 'is_verified', 'phone', 'locale', 'notes']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -651,7 +651,7 @@ class CustomerCreateForm(CustomerUpdateForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['identifier', 'is_active', 'name_parts', 'email', 'is_verified', 'locale']
|
fields = ['is_active', 'identifier', 'external_identifier', 'name_parts', 'email', 'is_verified', 'phone', 'locale', 'notes']
|
||||||
|
|
||||||
|
|
||||||
class MembershipUpdateForm(forms.ModelForm):
|
class MembershipUpdateForm(forms.ModelForm):
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
<dl class="dl-horizontal">
|
<dl class="dl-horizontal">
|
||||||
<dt>{% trans "Customer ID" %}</dt>
|
<dt>{% trans "Customer ID" %}</dt>
|
||||||
<dd>#{{ customer.identifier }}</dd>
|
<dd>#{{ customer.identifier }}</dd>
|
||||||
|
{% if customer.external_identifier %}
|
||||||
|
<dt>{% trans "External identifier" %}</dt>
|
||||||
|
<dd>{{ customer.external_identifier }}</dd>
|
||||||
|
{% endif %}
|
||||||
<dt>{% trans "Status" %}</dt>
|
<dt>{% trans "Status" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{% if not customer.is_active %}
|
{% if not customer.is_active %}
|
||||||
@@ -59,6 +63,10 @@
|
|||||||
<dt>{% trans "Last login" %}</dt>
|
<dt>{% trans "Last login" %}</dt>
|
||||||
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
|
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
|
||||||
–{% endif %}</dd>
|
–{% endif %}</dd>
|
||||||
|
{% if customer.notes %}
|
||||||
|
<dt>{% trans "Notes" %}</dt>
|
||||||
|
<dd>{{ customer.notes }}</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</form>
|
</form>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
|
|||||||
@@ -62,6 +62,9 @@
|
|||||||
<th>{% trans "Name" %}
|
<th>{% trans "Name" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a></th>
|
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||||
|
<th>{% trans "External ID" %}
|
||||||
|
<a href="?{% url_replace request 'ordering' '-external_identifier' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
|
<a href="?{% url_replace request 'ordering' 'external_identifier' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -81,6 +84,7 @@
|
|||||||
{% if not c.is_verified %}</strike>{% endif %}
|
{% if not c.is_verified %}</strike>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ c.name }}</td>
|
<td>{{ c.name }}</td>
|
||||||
|
<td>{% if c.external_identifier %}{{ c.external_identifier }}{% endif %}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=c.identifier %}"
|
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=c.identifier %}"
|
||||||
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
|
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ def customer(organizer, event):
|
|||||||
|
|
||||||
TEST_CUSTOMER_RES = {
|
TEST_CUSTOMER_RES = {
|
||||||
"identifier": "8WSAJCJ",
|
"identifier": "8WSAJCJ",
|
||||||
|
"external_identifier": None,
|
||||||
"email": "foo@example.org",
|
"email": "foo@example.org",
|
||||||
"name": "Foo",
|
"name": "Foo",
|
||||||
"name_parts": {
|
"name_parts": {
|
||||||
@@ -46,7 +47,8 @@ TEST_CUSTOMER_RES = {
|
|||||||
"last_login": None,
|
"last_login": None,
|
||||||
"date_joined": "2021-04-06T13:44:22.809216Z",
|
"date_joined": "2021-04-06T13:44:22.809216Z",
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"last_modified": "2021-04-06T13:44:22.809377Z"
|
"last_modified": "2021-04-06T13:44:22.809377Z",
|
||||||
|
"notes": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user