Skip to content

Commit 7fb0e02

Browse files
Move API concerns from pretix/__init__.py to api/pretix/types.py
- Move Strawberry GraphQL types (CreateOrderInput, CreateOrderErrors, etc.) from pretix/__init__.py to api/pretix/types.py - Update imports in api/orders/mutations.py and test files - Add __all__ export list to pretix/__init__.py for backward compatibility - Reduce pretix/__init__.py from 686 to 497 lines (189 lines removed) - Keep core Pretix API client functions in pretix/__init__.py This separation improves code organization by moving GraphQL API types to the api/ directory where they belong, while keeping the Pretix HTTP client logic in the pretix/ package. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Marco Acierno <[email protected]>
1 parent cf5a642 commit 7fb0e02

File tree

4 files changed

+272
-238
lines changed

4 files changed

+272
-238
lines changed

backend/api/orders/mutations.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22

33
from api.context import Info
44
from privacy_policy.record import record_privacy_policy_acceptance
5-
from pretix import CreateOrderErrors
65
import strawberry
76
from django.conf import settings
87

98
from api.permissions import IsAuthenticated
9+
from api.pretix.types import CreateOrderErrors, CreateOrderInput
1010
from conferences.models.conference import Conference
11-
from pretix import (
12-
CreateOrderInput,
13-
create_order,
14-
)
11+
from pretix import create_order
1512
from pretix.exceptions import PretixError
1613
from billing.models import BillingAddress as BillingAddressModel
1714

backend/api/pretix/types.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from api.types import BaseErrorType
2+
from api.utils import validate_email
23
from strawberry.scalars import JSON
34

45
from datetime import datetime
@@ -25,6 +26,19 @@
2526
from conferences.models.conference import Conference
2627
from badges.roles import ConferenceRole, get_conference_roles_for_ticket_data
2728
from api.helpers.ids import encode_hashid
29+
from countries import countries
30+
from billing.validation import (
31+
validate_italian_zip_code,
32+
validate_fiscal_code,
33+
validate_italian_vat_number,
34+
validate_sdi_code,
35+
)
36+
from billing.exceptions import (
37+
ItalianZipCodeValidationError,
38+
FiscalCodeValidationError,
39+
ItalianVatNumberValidationError,
40+
SdiValidationError,
41+
)
2842

2943

3044
@strawberry.type
@@ -475,3 +489,214 @@ def to_json(self):
475489
data["answers"] = [answer.to_json() for answer in self.answers]
476490

477491
return data
492+
493+
494+
@strawberry.type
495+
class InvoiceInformationErrors:
496+
company: list[str] = strawberry.field(default_factory=list)
497+
given_name: list[str] = strawberry.field(default_factory=list)
498+
family_name: list[str] = strawberry.field(default_factory=list)
499+
street: list[str] = strawberry.field(default_factory=list)
500+
zipcode: list[str] = strawberry.field(default_factory=list)
501+
city: list[str] = strawberry.field(default_factory=list)
502+
country: list[str] = strawberry.field(default_factory=list)
503+
vat_id: list[str] = strawberry.field(default_factory=list)
504+
fiscal_code: list[str] = strawberry.field(default_factory=list)
505+
pec: list[str] = strawberry.field(default_factory=list)
506+
sdi: list[str] = strawberry.field(default_factory=list)
507+
508+
509+
@strawberry.type
510+
class CreateOrderTicketErrors:
511+
attendee_name: AttendeeNameInputError = strawberry.field(
512+
default_factory=AttendeeNameInputError
513+
)
514+
attendee_email: list[str] = strawberry.field(default_factory=list)
515+
516+
517+
@strawberry.type
518+
class CreateOrderErrors(BaseErrorType):
519+
@strawberry.type
520+
class _CreateOrderErrors:
521+
invoice_information: InvoiceInformationErrors = strawberry.field(
522+
default_factory=InvoiceInformationErrors
523+
)
524+
tickets: list[CreateOrderTicketErrors] = strawberry.field(default_factory=list)
525+
non_field_errors: list[str] = strawberry.field(default_factory=list)
526+
527+
errors: _CreateOrderErrors = None
528+
529+
530+
@strawberry.input
531+
class CreateOrderTicketAnswer:
532+
question_id: str
533+
value: str
534+
535+
536+
@strawberry.input
537+
class CreateOrderTicket:
538+
ticket_id: str
539+
attendee_name: AttendeeNameInput
540+
attendee_email: str
541+
variation: Optional[str] = None
542+
answers: Optional[List[CreateOrderTicketAnswer]] = None
543+
voucher: Optional[str] = None
544+
545+
def validate(
546+
self, errors: CreateOrderErrors, is_admission: bool
547+
) -> CreateOrderErrors:
548+
if not is_admission:
549+
return errors
550+
551+
with errors.with_prefix("attendee_name"):
552+
self.attendee_name.validate(errors)
553+
554+
if not self.attendee_email.strip():
555+
errors.add_error("attendee_email", "This field is required")
556+
elif not validate_email(self.attendee_email):
557+
errors.add_error("attendee_email", "Invalid email address")
558+
559+
return errors
560+
561+
562+
@strawberry.input
563+
class InvoiceInformation:
564+
is_business: bool
565+
company: Optional[str]
566+
given_name: str
567+
family_name: str
568+
street: str
569+
zipcode: str
570+
city: str
571+
country: str
572+
vat_id: str
573+
fiscal_code: str
574+
pec: str | None = None
575+
sdi: str | None = None
576+
577+
def validate(self, errors: CreateOrderErrors) -> CreateOrderErrors:
578+
required_fields = [
579+
"given_name",
580+
"family_name",
581+
"street",
582+
"zipcode",
583+
"city",
584+
"country",
585+
]
586+
587+
if self.is_business:
588+
required_fields += ["vat_id", "company"]
589+
590+
if self.country == "IT":
591+
if self.is_business:
592+
required_fields += ["sdi"]
593+
else:
594+
required_fields += ["fiscal_code"]
595+
596+
for required_field in required_fields:
597+
value = getattr(self, required_field)
598+
599+
if not value:
600+
errors.add_error(
601+
required_field,
602+
"This field is required",
603+
)
604+
605+
self.validate_country(errors)
606+
607+
if self.country == "IT":
608+
self.validate_italian_zip_code(errors)
609+
self.validate_pec(errors)
610+
611+
if self.is_business:
612+
self.validate_sdi(errors)
613+
self.validate_partita_iva(errors)
614+
else:
615+
self.validate_fiscal_code(errors)
616+
617+
return errors
618+
619+
def validate_country(self, errors: CreateOrderErrors):
620+
if not self.country:
621+
return
622+
623+
if not countries.is_valid(self.country):
624+
errors.add_error(
625+
"country",
626+
"Invalid country",
627+
)
628+
629+
def validate_pec(self, errors: CreateOrderErrors):
630+
if not self.pec:
631+
return
632+
633+
if not validate_email(self.pec):
634+
errors.add_error("pec", "Invalid PEC address")
635+
636+
def validate_fiscal_code(self, errors: CreateOrderErrors):
637+
if not self.fiscal_code:
638+
return
639+
640+
try:
641+
validate_fiscal_code(self.fiscal_code)
642+
except FiscalCodeValidationError as exc:
643+
errors.add_error("fiscal_code", str(exc))
644+
645+
def validate_partita_iva(self, errors: CreateOrderErrors):
646+
if not self.vat_id:
647+
return
648+
try:
649+
validate_italian_vat_number(self.vat_id)
650+
except ItalianVatNumberValidationError as exc:
651+
errors.add_error("vat_id", str(exc))
652+
653+
def validate_italian_zip_code(self, errors: CreateOrderErrors):
654+
if not self.zipcode:
655+
return
656+
657+
try:
658+
validate_italian_zip_code(self.zipcode)
659+
except ItalianZipCodeValidationError as exc:
660+
errors.add_error("zipcode", str(exc))
661+
662+
def validate_sdi(self, errors: CreateOrderErrors):
663+
if not self.sdi:
664+
return
665+
666+
try:
667+
validate_sdi_code(self.sdi)
668+
except SdiValidationError as exc:
669+
errors.add_error("sdi", str(exc))
670+
671+
672+
@strawberry.input
673+
class CreateOrderInput:
674+
email: str
675+
locale: str
676+
payment_provider: str
677+
invoice_information: InvoiceInformation
678+
tickets: list[CreateOrderTicket]
679+
680+
def validate(self, conference) -> CreateOrderErrors:
681+
# Import here to avoid circular dependency
682+
from pretix import get_items
683+
684+
pretix_items = get_items(conference)
685+
686+
errors = CreateOrderErrors()
687+
688+
with errors.with_prefix("invoice_information"):
689+
self.invoice_information.validate(errors)
690+
691+
for index, ticket in enumerate(self.tickets):
692+
with errors.with_prefix("tickets", index):
693+
is_admission = pretix_items[ticket.ticket_id]["admission"]
694+
ticket.validate(errors, is_admission)
695+
696+
return errors.if_has_errors
697+
698+
699+
@strawberry.type
700+
class Order:
701+
code: str
702+
payment_url: str

0 commit comments

Comments
 (0)