diff --git a/src/pretix/base/exporters/__init__.py b/src/pretix/base/exporters/__init__.py index 43cf23a37..b0d1a778c 100644 --- a/src/pretix/base/exporters/__init__.py +++ b/src/pretix/base/exporters/__init__.py @@ -20,6 +20,7 @@ # . # from .answers import * # noqa +from .customers import * # noqa from .dekodi import * # noqa from .events import * # noqa from .invoices import * # noqa diff --git a/src/pretix/base/exporters/customers.py b/src/pretix/base/exporters/customers.py new file mode 100644 index 000000000..3d3e39bdc --- /dev/null +++ b/src/pretix/base/exporters/customers.py @@ -0,0 +1,113 @@ +# +# 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 . +# +# 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 +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Benjamin Hättasch, Tobias Kunze +# +# 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. + +from collections import OrderedDict + +from django.dispatch import receiver +from django.utils.timezone import get_current_timezone +from django.utils.translation import gettext as _, gettext_lazy + +from pretix.base.settings import PERSON_NAME_SCHEMES + +from ..exporter import ListExporter, OrganizerLevelExportMixin +from ..signals import register_multievent_data_exporters + + +class CustomerListExporter(OrganizerLevelExportMixin, ListExporter): + identifier = 'customerlist' + verbose_name = gettext_lazy('Customer accounts') + organizer_required_permission = 'can_manage_customers' + + @property + def additional_form_fields(self): + return OrderedDict( + [] + ) + + def iterate_list(self, form_data): + qs = self.organizer.customers.prefetch_related('provider') + + headers = [ + _('Customer ID'), + _('SSO provider'), + _('External identifier'), + _('E-mail'), + _('Phone number'), + _('Full name'), + ] + name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme] + if name_scheme and len(name_scheme['fields']) > 1: + for k, label, w in name_scheme['fields']: + headers.append(_('Name') + ': ' + str(label)) + + headers += [ + _('Account active'), + _('Verified email address'), + _('Last login'), + _('Registration date'), + _('Language'), + _('Notes'), + ] + yield headers + + tz = get_current_timezone() + for obj in qs: + row = [ + obj.identifier, + obj.provider.name if obj.provider else None, + obj.external_identifier, + obj.email or '', + obj.phone or '', + obj.name, + ] + if name_scheme and len(name_scheme['fields']) > 1: + for k, label, w in name_scheme['fields']: + row.append(obj.name_parts.get(k, '')) + row += [ + _('Yes') if obj.is_active else _('No'), + _('Yes') if obj.is_verified else _('No'), + obj.last_login.astimezone(tz).date().strftime('%Y-%m-%d') if obj.last_login else '', + obj.date_joined.astimezone(tz).date().strftime('%Y-%m-%d') if obj.date_joined else '', + obj.get_locale_display(), + obj.notes or '', + ] + yield row + + def get_filename(self): + return '{}_customers'.format(self.organizer.slug) + + +@receiver(register_multievent_data_exporters, dispatch_uid="multiexporter_customerlist") +def register_multievent_i_customerlist_exporter(sender, **kwargs): + return CustomerListExporter diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 4df92010a..fc8dbaf1b 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -78,6 +78,7 @@ class Customer(LoggedModel): organizer = models.ForeignKey(Organizer, related_name='customers', on_delete=models.CASCADE) provider = models.ForeignKey(CustomerSSOProvider, related_name='customers', on_delete=models.PROTECT, null=True, blank=True) identifier = models.CharField( + verbose_name=_('Customer ID'), max_length=190, db_index=True, help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do ' diff --git a/src/pretix/base/services/export.py b/src/pretix/base/services/export.py index 9adac010e..763de8ca6 100644 --- a/src/pretix/base/services/export.py +++ b/src/pretix/base/services/export.py @@ -102,9 +102,9 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int, timezone = e.settings.timezone region = e.settings.region else: - locale = settings.LANGUAGE_CODE - timezone = settings.TIME_ZONE - region = None + locale = organizer.settings.locale or settings.LANGUAGE_CODE + timezone = organizer.settings.timezone or settings.TIME_ZONE + region = organizer.settings.region with language(locale, region), override(timezone): if form_data.get('events') is not None: if isinstance(form_data['events'][0], str): @@ -123,9 +123,7 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int, if ( isinstance(ex, OrganizerLevelExportMixin) and not staff_session and - not (device or token or user).has_organizer_permission(self.request.organizer, - ex.organizer_required_permission, - self.request) + not (device or token or user).has_organizer_permission(organizer, ex.organizer_required_permission) ): raise ExportError( gettext('You do not have sufficient permission to perform this export.')