forked from CGM_Public/pretix_original
Customer accounts & Memberships (#2024)
This commit is contained in:
@@ -19,7 +19,8 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class FullAccessSecurityProfile:
|
||||
|
||||
@@ -160,9 +160,17 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
|
||||
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data')
|
||||
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
|
||||
'require_membership', 'require_membership_types', 'grant_membership_type',
|
||||
'grant_membership_duration_like_event', 'grant_membership_duration_days',
|
||||
'grant_membership_duration_months')
|
||||
read_only_fields = ('has_variations',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['require_membership_types'].queryset = self.context['event'].organizer.membership_types.all()
|
||||
self.fields['grant_membership_type'].queryset = self.context['event'].organizer.membership_types.all()
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
|
||||
|
||||
@@ -44,7 +44,7 @@ from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedFile, Checkin, Invoice, InvoiceAddress, InvoiceLine, Item,
|
||||
CachedFile, Checkin, Customer, Invoice, InvoiceAddress, InvoiceLine, Item,
|
||||
ItemVariation, Order, OrderPosition, Question, QuestionAnswer, Seat,
|
||||
SubEvent, TaxRule, Voucher,
|
||||
)
|
||||
@@ -537,7 +537,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
|
||||
self.fields['subevent'] = SubEventSerializer(read_only=True)
|
||||
|
||||
if 'item' in self.context['request'].query_params.getlist('expand'):
|
||||
self.fields['item'] = ItemSerializer(read_only=True)
|
||||
self.fields['item'] = ItemSerializer(read_only=True, context=self.context)
|
||||
|
||||
if 'variation' in self.context['request'].query_params.getlist('expand'):
|
||||
self.fields['variation'] = InlineItemVariationSerializer(read_only=True)
|
||||
@@ -624,6 +624,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
payment_date = OrderPaymentDateField(source='*', read_only=True)
|
||||
payment_provider = OrderPaymentTypeField(source='*', read_only=True)
|
||||
url = OrderURLField(source='*', read_only=True)
|
||||
customer = serializers.SlugRelatedField(slug_field='identifier', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
@@ -631,11 +632,11 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
|
||||
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||
'url'
|
||||
'url', 'customer'
|
||||
)
|
||||
read_only_fields = (
|
||||
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'positions', 'downloads',
|
||||
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
|
||||
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
|
||||
)
|
||||
|
||||
@@ -907,16 +908,18 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
payment_date = serializers.DateTimeField(required=False, allow_null=True)
|
||||
send_email = serializers.BooleanField(default=False, required=False)
|
||||
simulate = serializers.BooleanField(default=False, required=False)
|
||||
customer = serializers.SlugRelatedField(slug_field='identifier', queryset=Customer.objects.none(), required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all()
|
||||
self.fields['customer'].queryset = self.context['event'].organizer.customers.all()
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
|
||||
'force', 'send_email', 'simulate')
|
||||
'force', 'send_email', 'simulate', 'customer')
|
||||
|
||||
def validate_payment_provider(self, pp):
|
||||
if pp is None:
|
||||
|
||||
@@ -34,8 +34,9 @@ from pretix.api.serializers.settings import SettingsSerializer
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.models import (
|
||||
Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, Team,
|
||||
TeamAPIToken, TeamInvite, User,
|
||||
Customer, Device, GiftCard, GiftCardTransaction, Membership,
|
||||
MembershipType, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite,
|
||||
User,
|
||||
)
|
||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
@@ -61,6 +62,43 @@ class SeatingPlanSerializer(I18nAwareModelSerializer):
|
||||
fields = ('id', 'name', 'layout')
|
||||
|
||||
|
||||
class CustomerSerializer(I18nAwareModelSerializer):
|
||||
identifier = serializers.CharField(read_only=True)
|
||||
name = serializers.CharField(read_only=True)
|
||||
last_login = serializers.DateTimeField(read_only=True)
|
||||
date_joined = serializers.DateTimeField(read_only=True)
|
||||
last_modified = serializers.DateTimeField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ('identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined',
|
||||
'locale', 'last_modified')
|
||||
|
||||
|
||||
class MembershipTypeSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = MembershipType
|
||||
fields = ('id', 'name', 'transferable', 'allow_parallel_usage', 'max_usages')
|
||||
|
||||
|
||||
class MembershipSerializer(I18nAwareModelSerializer):
|
||||
customer = serializers.SlugRelatedField(slug_field='identifier', queryset=Customer.objects.none())
|
||||
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ('id', 'customer', 'membership_type', 'date_start', 'date_end', 'attendee_name_parts')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['customer'].queryset = self.context['organizer'].customers.all()
|
||||
self.fields['membership_type'].queryset = self.context['organizer'].membership_types.all()
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
validated_data['customer'] = instance.customer # no modifying
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class GiftCardSerializer(I18nAwareModelSerializer):
|
||||
value = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.00'))
|
||||
|
||||
@@ -116,7 +154,7 @@ class TeamSerializer(serializers.ModelSerializer):
|
||||
'id', 'name', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams',
|
||||
'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings',
|
||||
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers',
|
||||
'can_change_vouchers', 'can_checkin_orders'
|
||||
'can_change_vouchers', 'can_checkin_orders', 'can_manage_customers'
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
@@ -234,6 +272,7 @@ class TeamMemberSerializer(serializers.ModelSerializer):
|
||||
|
||||
class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
default_fields = [
|
||||
'customer_accounts',
|
||||
'contact_mail',
|
||||
'imprint_url',
|
||||
'organizer_info_text',
|
||||
|
||||
@@ -54,6 +54,9 @@ orga_router.register(r'subevents', event.SubEventViewSet)
|
||||
orga_router.register(r'webhooks', webhooks.WebHookViewSet)
|
||||
orga_router.register(r'seatingplans', organizer.SeatingPlanViewSet)
|
||||
orga_router.register(r'giftcards', organizer.GiftCardViewSet)
|
||||
orga_router.register(r'customers', organizer.CustomerViewSet)
|
||||
orga_router.register(r'memberships', organizer.MembershipViewSet)
|
||||
orga_router.register(r'membershiptypes', organizer.MembershipTypeViewSet)
|
||||
orga_router.register(r'teams', organizer.TeamViewSet)
|
||||
orga_router.register(r'devices', organizer.DeviceViewSet)
|
||||
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
||||
|
||||
@@ -38,14 +38,16 @@ from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from pretix.api.models import OAuthAccessToken
|
||||
from pretix.api.serializers.organizer import (
|
||||
DeviceSerializer, GiftCardSerializer, GiftCardTransactionSerializer,
|
||||
OrganizerSerializer, OrganizerSettingsSerializer, SeatingPlanSerializer,
|
||||
TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer,
|
||||
TeamSerializer,
|
||||
CustomerSerializer, DeviceSerializer, GiftCardSerializer,
|
||||
GiftCardTransactionSerializer, MembershipSerializer,
|
||||
MembershipTypeSerializer, OrganizerSerializer, OrganizerSettingsSerializer,
|
||||
SeatingPlanSerializer, TeamAPITokenSerializer, TeamInviteSerializer,
|
||||
TeamMemberSerializer, TeamSerializer,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, Team,
|
||||
TeamAPIToken, TeamInvite, User,
|
||||
Customer, Device, GiftCard, GiftCardTransaction, Membership,
|
||||
MembershipType, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite,
|
||||
User,
|
||||
)
|
||||
from pretix.base.settings import SETTINGS_AFFECTING_CSS
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
@@ -480,3 +482,163 @@ class OrganizerSettingsView(views.APIView):
|
||||
'request': request
|
||||
})
|
||||
return Response(s.data)
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
class CustomerFilter(FilterSet):
|
||||
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
|
||||
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ['email']
|
||||
|
||||
|
||||
class CustomerViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = CustomerSerializer
|
||||
queryset = Customer.objects.none()
|
||||
permission = 'can_manage_customers'
|
||||
lookup_field = 'identifier'
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filterset_class = CustomerFilter
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.customers.all()
|
||||
return qs
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
raise MethodNotAllowed("Customers cannot be deleted.")
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
serializer.instance.log_action(
|
||||
'pretix.customer.created',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
serializer.instance.log_action(
|
||||
'pretix.customer.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@action(detail=True, methods=["POST"])
|
||||
@transaction.atomic()
|
||||
def anonymize(self, request, **kwargs):
|
||||
o = self.get_object()
|
||||
o.anonymize()
|
||||
o.log_action('pretix.customer.anonymized', user=self.request.user, auth=self.request.auth)
|
||||
return Response(CustomerSerializer(o).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class MembershipTypeViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = MembershipTypeSerializer
|
||||
queryset = MembershipType.objects.none()
|
||||
permission = 'can_change_organizer_settings'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.membership_types.all()
|
||||
return qs
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied("Can only be deleted if unused.")
|
||||
instance.log_action(
|
||||
'pretix.membershiptype.deleted',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'id': instance.pk}
|
||||
)
|
||||
instance.delete()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
serializer.instance.log_action(
|
||||
'pretix.membershiptype.created',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
serializer.instance.log_action(
|
||||
'pretix.membershiptype.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
class MembershipFilter(FilterSet):
|
||||
customer = django_filters.CharFilter(field_name='customer__identifier', lookup_expr='iexact')
|
||||
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ['customer', 'membership_type']
|
||||
|
||||
|
||||
class MembershipViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = MembershipSerializer
|
||||
queryset = Membership.objects.none()
|
||||
permission = 'can_manage_customers'
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filterset_class = MembershipFilter
|
||||
|
||||
def get_queryset(self):
|
||||
return Membership.objects.filter(
|
||||
customer__organizer=self.request.organizer
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
raise MethodNotAllowed("Memberships cannot be deleted. You can change the date instead.")
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
inst = serializer.save()
|
||||
serializer.instance.customer.log_action(
|
||||
'pretix.customer.membership.created',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
inst = serializer.save()
|
||||
serializer.instance.customer.log_action(
|
||||
'pretix.customer.membership.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
return inst
|
||||
|
||||
Reference in New Issue
Block a user