From 7fb0e02d37bbc4813887855227147357833ecf8a Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:37:23 +0000 Subject: [PATCH] Move API concerns from pretix/__init__.py to api/pretix/types.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- backend/api/orders/mutations.py | 7 +- backend/api/pretix/types.py | 225 ++++++++++++++++++ backend/pretix/__init__.py | 264 ++++------------------ backend/pretix/tests/test_create_order.py | 14 +- 4 files changed, 272 insertions(+), 238 deletions(-) diff --git a/backend/api/orders/mutations.py b/backend/api/orders/mutations.py index a0e61b491c..c8bbdf6891 100644 --- a/backend/api/orders/mutations.py +++ b/backend/api/orders/mutations.py @@ -2,16 +2,13 @@ from api.context import Info from privacy_policy.record import record_privacy_policy_acceptance -from pretix import CreateOrderErrors import strawberry from django.conf import settings from api.permissions import IsAuthenticated +from api.pretix.types import CreateOrderErrors, CreateOrderInput from conferences.models.conference import Conference -from pretix import ( - CreateOrderInput, - create_order, -) +from pretix import create_order from pretix.exceptions import PretixError from billing.models import BillingAddress as BillingAddressModel diff --git a/backend/api/pretix/types.py b/backend/api/pretix/types.py index 1d52355d6e..fcaaae68b7 100644 --- a/backend/api/pretix/types.py +++ b/backend/api/pretix/types.py @@ -1,4 +1,5 @@ from api.types import BaseErrorType +from api.utils import validate_email from strawberry.scalars import JSON from datetime import datetime @@ -25,6 +26,19 @@ from conferences.models.conference import Conference from badges.roles import ConferenceRole, get_conference_roles_for_ticket_data from api.helpers.ids import encode_hashid +from countries import countries +from billing.validation import ( + validate_italian_zip_code, + validate_fiscal_code, + validate_italian_vat_number, + validate_sdi_code, +) +from billing.exceptions import ( + ItalianZipCodeValidationError, + FiscalCodeValidationError, + ItalianVatNumberValidationError, + SdiValidationError, +) @strawberry.type @@ -475,3 +489,214 @@ def to_json(self): data["answers"] = [answer.to_json() for answer in self.answers] return data + + +@strawberry.type +class InvoiceInformationErrors: + company: list[str] = strawberry.field(default_factory=list) + given_name: list[str] = strawberry.field(default_factory=list) + family_name: list[str] = strawberry.field(default_factory=list) + street: list[str] = strawberry.field(default_factory=list) + zipcode: list[str] = strawberry.field(default_factory=list) + city: list[str] = strawberry.field(default_factory=list) + country: list[str] = strawberry.field(default_factory=list) + vat_id: list[str] = strawberry.field(default_factory=list) + fiscal_code: list[str] = strawberry.field(default_factory=list) + pec: list[str] = strawberry.field(default_factory=list) + sdi: list[str] = strawberry.field(default_factory=list) + + +@strawberry.type +class CreateOrderTicketErrors: + attendee_name: AttendeeNameInputError = strawberry.field( + default_factory=AttendeeNameInputError + ) + attendee_email: list[str] = strawberry.field(default_factory=list) + + +@strawberry.type +class CreateOrderErrors(BaseErrorType): + @strawberry.type + class _CreateOrderErrors: + invoice_information: InvoiceInformationErrors = strawberry.field( + default_factory=InvoiceInformationErrors + ) + tickets: list[CreateOrderTicketErrors] = strawberry.field(default_factory=list) + non_field_errors: list[str] = strawberry.field(default_factory=list) + + errors: _CreateOrderErrors = None + + +@strawberry.input +class CreateOrderTicketAnswer: + question_id: str + value: str + + +@strawberry.input +class CreateOrderTicket: + ticket_id: str + attendee_name: AttendeeNameInput + attendee_email: str + variation: Optional[str] = None + answers: Optional[List[CreateOrderTicketAnswer]] = None + voucher: Optional[str] = None + + def validate( + self, errors: CreateOrderErrors, is_admission: bool + ) -> CreateOrderErrors: + if not is_admission: + return errors + + with errors.with_prefix("attendee_name"): + self.attendee_name.validate(errors) + + if not self.attendee_email.strip(): + errors.add_error("attendee_email", "This field is required") + elif not validate_email(self.attendee_email): + errors.add_error("attendee_email", "Invalid email address") + + return errors + + +@strawberry.input +class InvoiceInformation: + is_business: bool + company: Optional[str] + given_name: str + family_name: str + street: str + zipcode: str + city: str + country: str + vat_id: str + fiscal_code: str + pec: str | None = None + sdi: str | None = None + + def validate(self, errors: CreateOrderErrors) -> CreateOrderErrors: + required_fields = [ + "given_name", + "family_name", + "street", + "zipcode", + "city", + "country", + ] + + if self.is_business: + required_fields += ["vat_id", "company"] + + if self.country == "IT": + if self.is_business: + required_fields += ["sdi"] + else: + required_fields += ["fiscal_code"] + + for required_field in required_fields: + value = getattr(self, required_field) + + if not value: + errors.add_error( + required_field, + "This field is required", + ) + + self.validate_country(errors) + + if self.country == "IT": + self.validate_italian_zip_code(errors) + self.validate_pec(errors) + + if self.is_business: + self.validate_sdi(errors) + self.validate_partita_iva(errors) + else: + self.validate_fiscal_code(errors) + + return errors + + def validate_country(self, errors: CreateOrderErrors): + if not self.country: + return + + if not countries.is_valid(self.country): + errors.add_error( + "country", + "Invalid country", + ) + + def validate_pec(self, errors: CreateOrderErrors): + if not self.pec: + return + + if not validate_email(self.pec): + errors.add_error("pec", "Invalid PEC address") + + def validate_fiscal_code(self, errors: CreateOrderErrors): + if not self.fiscal_code: + return + + try: + validate_fiscal_code(self.fiscal_code) + except FiscalCodeValidationError as exc: + errors.add_error("fiscal_code", str(exc)) + + def validate_partita_iva(self, errors: CreateOrderErrors): + if not self.vat_id: + return + try: + validate_italian_vat_number(self.vat_id) + except ItalianVatNumberValidationError as exc: + errors.add_error("vat_id", str(exc)) + + def validate_italian_zip_code(self, errors: CreateOrderErrors): + if not self.zipcode: + return + + try: + validate_italian_zip_code(self.zipcode) + except ItalianZipCodeValidationError as exc: + errors.add_error("zipcode", str(exc)) + + def validate_sdi(self, errors: CreateOrderErrors): + if not self.sdi: + return + + try: + validate_sdi_code(self.sdi) + except SdiValidationError as exc: + errors.add_error("sdi", str(exc)) + + +@strawberry.input +class CreateOrderInput: + email: str + locale: str + payment_provider: str + invoice_information: InvoiceInformation + tickets: list[CreateOrderTicket] + + def validate(self, conference) -> CreateOrderErrors: + # Import here to avoid circular dependency + from pretix import get_items + + pretix_items = get_items(conference) + + errors = CreateOrderErrors() + + with errors.with_prefix("invoice_information"): + self.invoice_information.validate(errors) + + for index, ticket in enumerate(self.tickets): + with errors.with_prefix("tickets", index): + is_admission = pretix_items[ticket.ticket_id]["admission"] + ticket.validate(errors, is_admission) + + return errors.if_has_errors + + +@strawberry.type +class Order: + code: str + payment_url: str diff --git a/backend/pretix/__init__.py b/backend/pretix/__init__.py index 49395208c2..3eeaefb653 100644 --- a/backend/pretix/__init__.py +++ b/backend/pretix/__init__.py @@ -1,40 +1,60 @@ -from api.utils import validate_email import logging from typing import Any, Dict, List, Optional from urllib.parse import urljoin from django.utils.dateparse import parse_datetime import requests -from api.types import BaseErrorType -from countries import countries -import strawberry from django.conf import settings from django.core.cache import cache from api.pretix.types import ( - AttendeeNameInput, - AttendeeNameInputError, + CreateOrderInput, + CreateOrderTicket, + CreateOrderTicketAnswer, + InvoiceInformation, + CreateOrderErrors, UpdateAttendeeTicketInput, Voucher, + Order, ) from conferences.models.conference import Conference from pretix.types import Category, Question, Quota import sentry_sdk -from billing.validation import ( - validate_italian_zip_code, - validate_fiscal_code, - validate_italian_vat_number, - validate_sdi_code, -) -from billing.exceptions import ( - ItalianZipCodeValidationError, - FiscalCodeValidationError, - ItalianVatNumberValidationError, - SdiValidationError, -) from .exceptions import PretixError logger = logging.getLogger(__file__) +# Re-export API types for backward compatibility +__all__ = [ + "CreateOrderInput", + "CreateOrderTicket", + "CreateOrderTicketAnswer", + "InvoiceInformation", + "CreateOrderErrors", + "UpdateAttendeeTicketInput", + "Voucher", + "Order", + "get_voucher", + "create_voucher", + "get_order", + "get_user_orders", + "get_orders", + "get_all_order_positions", + "get_invoices", + "get_items", + "get_questions", + "get_categories", + "get_quotas", + "create_order", + "user_has_admission_ticket", + "get_user_tickets", + "get_user_ticket", + "is_ticket_owner", + "update_ticket", + "get_order_position", + "get_all_vouchers", + "PretixError", +] + def get_api_url(conference: Conference, endpoint: str) -> str: return urljoin( @@ -245,214 +265,6 @@ def get_quotas(conference: Conference) -> Dict[str, Quota]: return {str(result["id"]): result for result in data["results"]} -@strawberry.type -class InvoiceInformationErrors: - company: list[str] = strawberry.field(default_factory=list) - given_name: list[str] = strawberry.field(default_factory=list) - family_name: list[str] = strawberry.field(default_factory=list) - street: list[str] = strawberry.field(default_factory=list) - zipcode: list[str] = strawberry.field(default_factory=list) - city: list[str] = strawberry.field(default_factory=list) - country: list[str] = strawberry.field(default_factory=list) - vat_id: list[str] = strawberry.field(default_factory=list) - fiscal_code: list[str] = strawberry.field(default_factory=list) - pec: list[str] = strawberry.field(default_factory=list) - sdi: list[str] = strawberry.field(default_factory=list) - - -@strawberry.type -class CreateOrderTicketErrors: - attendee_name: AttendeeNameInputError = strawberry.field( - default_factory=AttendeeNameInputError - ) - attendee_email: list[str] = strawberry.field(default_factory=list) - - -@strawberry.type -class CreateOrderErrors(BaseErrorType): - @strawberry.type - class _CreateOrderErrors: - invoice_information: InvoiceInformationErrors = strawberry.field( - default_factory=InvoiceInformationErrors - ) - tickets: list[CreateOrderTicketErrors] = strawberry.field(default_factory=list) - non_field_errors: list[str] = strawberry.field(default_factory=list) - - errors: _CreateOrderErrors = None - - -@strawberry.input -class CreateOrderTicketAnswer: - question_id: str - value: str - - -@strawberry.input -class CreateOrderTicket: - ticket_id: str - attendee_name: AttendeeNameInput - attendee_email: str - variation: Optional[str] = None - answers: Optional[List[CreateOrderTicketAnswer]] = None - voucher: Optional[str] = None - - def validate( - self, errors: CreateOrderErrors, is_admission: bool - ) -> CreateOrderErrors: - if not is_admission: - return errors - - with errors.with_prefix("attendee_name"): - self.attendee_name.validate(errors) - - if not self.attendee_email.strip(): - errors.add_error("attendee_email", "This field is required") - elif not validate_email(self.attendee_email): - errors.add_error("attendee_email", "Invalid email address") - - return errors - - -@strawberry.input -class InvoiceInformation: - is_business: bool - company: Optional[str] - given_name: str - family_name: str - street: str - zipcode: str - city: str - country: str - vat_id: str - fiscal_code: str - pec: str | None = None - sdi: str | None = None - - def validate(self, errors: CreateOrderErrors) -> CreateOrderErrors: - required_fields = [ - "given_name", - "family_name", - "street", - "zipcode", - "city", - "country", - ] - - if self.is_business: - required_fields += ["vat_id", "company"] - - if self.country == "IT": - if self.is_business: - required_fields += ["sdi"] - else: - required_fields += ["fiscal_code"] - - for required_field in required_fields: - value = getattr(self, required_field) - - if not value: - errors.add_error( - required_field, - "This field is required", - ) - - self.validate_country(errors) - - if self.country == "IT": - self.validate_italian_zip_code(errors) - self.validate_pec(errors) - - if self.is_business: - self.validate_sdi(errors) - self.validate_partita_iva(errors) - else: - self.validate_fiscal_code(errors) - - return errors - - def validate_country(self, errors: CreateOrderErrors): - if not self.country: - return - - if not countries.is_valid(self.country): - errors.add_error( - "country", - "Invalid country", - ) - - def validate_pec(self, errors: CreateOrderErrors): - if not self.pec: - return - - if not validate_email(self.pec): - errors.add_error("pec", "Invalid PEC address") - - def validate_fiscal_code(self, errors: CreateOrderErrors): - if not self.fiscal_code: - return - - try: - validate_fiscal_code(self.fiscal_code) - except FiscalCodeValidationError as exc: - errors.add_error("fiscal_code", str(exc)) - - def validate_partita_iva(self, errors: CreateOrderErrors): - if not self.vat_id: - return - try: - validate_italian_vat_number(self.vat_id) - except ItalianVatNumberValidationError as exc: - errors.add_error("vat_id", str(exc)) - - def validate_italian_zip_code(self, errors: CreateOrderErrors): - if not self.zipcode: - return - - try: - validate_italian_zip_code(self.zipcode) - except ItalianZipCodeValidationError as exc: - errors.add_error("zipcode", str(exc)) - - def validate_sdi(self, errors: CreateOrderErrors): - if not self.sdi: - return - - try: - validate_sdi_code(self.sdi) - except SdiValidationError as exc: - errors.add_error("sdi", str(exc)) - - -@strawberry.input -class CreateOrderInput: - email: str - locale: str - payment_provider: str - invoice_information: InvoiceInformation - tickets: list[CreateOrderTicket] - - def validate(self, conference) -> CreateOrderErrors: - pretix_items = get_items(conference) - - errors = CreateOrderErrors() - - with errors.with_prefix("invoice_information"): - self.invoice_information.validate(errors) - - for index, ticket in enumerate(self.tickets): - with errors.with_prefix("tickets", index): - is_admission = pretix_items[ticket.ticket_id]["admission"] - ticket.validate(errors, is_admission) - - return errors.if_has_errors - - -@strawberry.type -class Order: - code: str - payment_url: str - - def normalize_answers(ticket: CreateOrderTicket, questions: dict): answers = [] diff --git a/backend/pretix/tests/test_create_order.py b/backend/pretix/tests/test_create_order.py index aad515ebb0..e09ffd6ccd 100644 --- a/backend/pretix/tests/test_create_order.py +++ b/backend/pretix/tests/test_create_order.py @@ -1,15 +1,15 @@ -from api.pretix.types import AttendeeNameInput -from conferences.tests.factories import ConferenceFactory -import pytest -from django.test import override_settings - -from pretix import ( +from api.pretix.types import ( + AttendeeNameInput, CreateOrderInput, CreateOrderTicket, CreateOrderTicketAnswer, InvoiceInformation, - create_order, ) +from conferences.tests.factories import ConferenceFactory +import pytest +from django.test import override_settings + +from pretix import create_order from pretix.exceptions import PretixError