diff --git a/doc/api/resources/customers.rst b/doc/api/resources/customers.rst index 667b6acda..17d5d9279 100644 --- a/doc/api/resources/customers.rst +++ b/doc/api/resources/customers.rst @@ -131,7 +131,9 @@ Endpoints .. http:post:: /api/v1/organizers/(organizer)/customers/ - Creates a new customer + Creates a new customer. In addition to the fields defined on the resource, you can pass the field ``send_email`` + to control whether the system should send an account activation email with a password reset link (defaults to + ``false``). **Example request**: @@ -143,7 +145,8 @@ Endpoints Content-Type: application/json { - "email": "test@example.org" + "email": "test@example.org", + "send_email": true } **Example response**: diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index dd81ed9ba..a4e5c7593 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -75,6 +75,14 @@ class CustomerSerializer(I18nAwareModelSerializer): 'locale', 'last_modified', 'notes') +class CustomerCreateSerializer(CustomerSerializer): + send_email = serializers.BooleanField(default=False, required=False, allow_null=True) + + class Meta: + model = Customer + fields = CustomerSerializer.Meta.fields + ('send_email',) + + class MembershipTypeSerializer(I18nAwareModelSerializer): class Meta: diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index eae5fccf5..65b899e94 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -22,6 +22,7 @@ from decimal import Decimal import django_filters +from django.contrib.auth.hashers import make_password from django.db import transaction from django.shortcuts import get_object_or_404 from django.utils.functional import cached_property @@ -38,8 +39,8 @@ from rest_framework.viewsets import GenericViewSet from pretix.api.models import OAuthAccessToken from pretix.api.serializers.organizer import ( - CustomerSerializer, DeviceSerializer, GiftCardSerializer, - GiftCardTransactionSerializer, MembershipSerializer, + CustomerCreateSerializer, CustomerSerializer, DeviceSerializer, + GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer, MembershipTypeSerializer, OrganizerSerializer, OrganizerSettingsSerializer, SeatingPlanSerializer, TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer, TeamSerializer, @@ -514,15 +515,24 @@ class CustomerViewSet(viewsets.ModelViewSet): raise MethodNotAllowed("Customers cannot be deleted.") @transaction.atomic() - def perform_create(self, serializer): - inst = serializer.save(organizer=self.request.organizer) + def perform_create(self, serializer, send_email=False): + customer = serializer.save(organizer=self.request.organizer, password=make_password(None)) serializer.instance.log_action( 'pretix.customer.created', user=self.request.user, auth=self.request.auth, data=self.request.data, ) - return inst + if send_email: + customer.send_activation_mail() + return customer + + def create(self, request, *args, **kwargs): + serializer = CustomerCreateSerializer(data=request.data, context=self.get_serializer_context()) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer, send_email=serializer.validated_data.pop('send_email', False)) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) @transaction.atomic() def perform_update(self, serializer): diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 488f7c8fa..cb496c284 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -216,6 +216,27 @@ class Customer(LoggedModel): testmode=testmode, ) + def send_activation_mail(self): + from pretix.base.services.mail import mail + from pretix.multidomain.urlreverse import build_absolute_uri + from pretix.presale.forms.customer import TokenGenerator + + ctx = self.get_email_context() + token = TokenGenerator().make_token(self) + ctx['url'] = build_absolute_uri( + self.organizer, + 'presale:organizer.customer.activate' + ) + '?id=' + self.identifier + '&token=' + token + mail( + self.email, + _('Activate your account at {organizer}').format(organizer=self.organizer.name), + self.organizer.settings.mail_text_customer_registration, + ctx, + locale=self.locale, + customer=self, + organizer=self.organizer, + ) + class AttendeeProfile(models.Model): customer = models.ForeignKey( diff --git a/src/pretix/presale/forms/customer.py b/src/pretix/presale/forms/customer.py index 92bcfb505..cdb83ea3f 100644 --- a/src/pretix/presale/forms/customer.py +++ b/src/pretix/presale/forms/customer.py @@ -41,9 +41,7 @@ from pretix.base.forms.questions import ( ) from pretix.base.i18n import get_language_without_region from pretix.base.models import Customer -from pretix.base.services.mail import mail from pretix.helpers.http import get_client_ip -from pretix.multidomain.urlreverse import build_absolute_uri class TokenGenerator(PasswordResetTokenGenerator): @@ -268,19 +266,7 @@ class RegistrationForm(forms.Form): customer.set_unusable_password() customer.save() customer.log_action('pretix.customer.created', {}) - ctx = customer.get_email_context() - token = TokenGenerator().make_token(customer) - ctx['url'] = build_absolute_uri(self.request.organizer, - 'presale:organizer.customer.activate') + '?id=' + customer.identifier + '&token=' + token - mail( - customer.email, - _('Activate your account at {organizer}').format(organizer=self.request.organizer.name), - self.request.organizer.settings.mail_text_customer_registration, - ctx, - locale=customer.locale, - customer=customer, - organizer=self.request.organizer, - ) + customer.send_activation_mail() return customer diff --git a/src/tests/api/test_customers.py b/src/tests/api/test_customers.py index 58a7ca7f8..233b6eebf 100644 --- a/src/tests/api/test_customers.py +++ b/src/tests/api/test_customers.py @@ -20,6 +20,7 @@ # . # import pytest +from django.core import mail as djmail from django_scopes import scopes_disabled @@ -98,6 +99,29 @@ def test_customer_create(token_client, organizer): assert customer.is_active assert customer.name == 'John Doe' assert customer.is_verified + assert len(djmail.outbox) == 0 + + +@pytest.mark.django_db +def test_customer_create_send_email(token_client, organizer): + resp = token_client.post( + '/api/v1/organizers/{}/customers/'.format(organizer.slug), + format='json', + data={ + 'identifier': 'IGNORED', + 'email': 'bar@example.com', + 'name_parts': { + "_scheme": "given_family", + 'given_name': 'John', + 'family_name': 'Doe', + }, + 'is_active': True, + 'is_verified': True, + 'send_email': True, + } + ) + assert resp.status_code == 201 + assert len(djmail.outbox) == 1 @pytest.mark.django_db