class SubscriptionSerializer(serializers.HyperlinkedModelSerializer): trial_end = serializers.DateField(required=False) start_date = serializers.DateField(required=False) ended_at = serializers.DateField(read_only=True) url = SubscriptionUrl(view_name='subscription-detail', source='*', queryset=Subscription.objects.all(), required=False) updateable_buckets = serializers.ReadOnlyField() meta = JSONField(required=False) customer = CustomerUrl(view_name='customer-detail', queryset=Customer.objects.all()) class Meta: model = Subscription fields = ('id', 'url', 'plan', 'customer', 'trial_end', 'start_date', 'ended_at', 'state', 'reference', 'updateable_buckets', 'meta', 'description') read_only_fields = ('state', 'updateable_buckets') def validate(self, attrs): attrs = super(SubscriptionSerializer, self).validate(attrs) instance = Subscription(**attrs) instance.clean() return attrs
class TransactionSerializer(AutoCleanSerializerMixin, serializers.HyperlinkedModelSerializer): payment_method = PaymentMethodUrl(view_name='payment-method-detail', lookup_field='payment_method', queryset=PaymentMethod.objects.all()) url = TransactionUrl( view_name='transaction-detail', lookup_field='uuid', ) pay_url = TransactionPaymentUrl(lookup_url_kwarg='token', view_name='payment') customer = CustomerUrl(view_name='customer-detail', read_only=True) provider = ProviderUrl(view_name='provider-detail', read_only=True) id = serializers.CharField(source='uuid', read_only=True) amount = serializers.DecimalField(required=False, decimal_places=2, max_digits=12, min_value=0) class Meta: model = Transaction fields = ('id', 'url', 'customer', 'provider', 'amount', 'currency', 'state', 'proforma', 'invoice', 'can_be_consumed', 'payment_processor', 'payment_method', 'pay_url', 'valid_until', 'updated_at', 'created_at', 'fail_code', 'refund_code', 'cancel_code') read_only_fields = ('customer', 'provider', 'can_be_consumed', 'pay_url', 'id', 'url', 'state', 'updated_at', 'created_at', 'payment_processor', 'fail_code', 'refund_code', 'cancel_code') updateable_fields = ('valid_until', 'success_url', 'failed_url') extra_kwargs = { 'amount': { 'required': False }, 'currency': { 'required': False }, 'invoice': { 'view_name': 'invoice-detail' }, 'proforma': { 'view_name': 'proforma-detail' } } def validate(self, attrs): attrs = super(TransactionSerializer, self).validate(attrs) if not attrs: return attrs if self.instance: if self.instance.state != Transaction.States.Initial: message = "The transaction cannot be modified once it is in {}"\ " state.".format(self.instance.state) raise serializers.ValidationError(message) return attrs
class DocumentSerializer(serializers.HyperlinkedModelSerializer): """ A read-only serializers for Proformas and Invoices """ customer = CustomerUrl(view_name='customer-detail', queryset=Customer.objects.all()) pdf_url = PDFUrl(view_name='pdf', source='*', read_only=True) url = DocumentUrl( proforma_view_name='proforma-detail', invoice_view_name='invoice-detail', ) transactions = serializers.SerializerMethodField() def get_transactions(self, document): if document.kind == 'invoice': transactions = document.invoice_transactions.all() elif document.kind == 'proforma': transactions = document.proforma_transactions.all() else: return [] for transaction in transactions: # This is done to avoid prefetching already prefetched resources transaction.payment_method.customer = document.customer transaction.provider = document.provider return TransactionSerializer(transactions, many=True, context=self.context).data class Meta: model = BillingDocumentBase fields = ('id', 'url', 'kind', 'series', 'number', 'provider', 'customer', 'due_date', 'issue_date', 'paid_date', 'cancel_date', 'sales_tax_name', 'sales_tax_percent', 'transaction_currency', 'currency', 'state', 'total', 'total_in_transaction_currency', 'pdf_url', 'transactions') read_only_fields = fields
class CustomerSerializer(serializers.HyperlinkedModelSerializer): subscriptions = SubscriptionUrl(view_name='subscription-detail', many=True, read_only=True) payment_methods = serializers.HyperlinkedIdentityField( view_name='payment-method-list', source='*', lookup_url_kwarg='customer_pk' ) transactions = serializers.HyperlinkedIdentityField( view_name='transaction-list', source='*', lookup_url_kwarg='customer_pk' ) meta = JSONField(required=False) url = CustomerUrl(view_name='customer-detail', read_only=True, source='*') class Meta: model = Customer fields = ('id', 'url', 'customer_reference', 'first_name', 'last_name', 'company', 'email', 'address_1', 'address_2', 'city', 'state', 'zip_code', 'country', 'currency', 'phone', 'extra', 'sales_tax_number', 'sales_tax_name', 'sales_tax_percent', 'consolidated_billing', 'subscriptions', 'payment_methods', 'transactions', 'meta')
class ProformaSerializer(AutoCleanSerializerMixin, serializers.HyperlinkedModelSerializer): proforma_entries = DocumentEntrySerializer(many=True, required=False) pdf_url = PDFUrl(view_name='pdf', source='*', read_only=True) customer = CustomerUrl(view_name='customer-detail', queryset=Customer.objects.all()) transactions = TransactionSerializer(many=True, read_only=True) class Meta: model = Proforma fields = ('id', 'series', 'number', 'provider', 'customer', 'archived_provider', 'archived_customer', 'due_date', 'issue_date', 'paid_date', 'cancel_date', 'sales_tax_name', 'sales_tax_percent', 'currency', 'transaction_currency', 'transaction_xe_rate', 'transaction_xe_date', 'state', 'invoice', 'proforma_entries', 'total', 'total_in_transaction_currency', 'pdf_url', 'transactions') read_only_fields = ('archived_provider', 'archived_customer', 'total', 'total_in_transaction_currency') extra_kwargs = { 'transaction_currency': { 'required': False }, 'number': { 'required': False }, 'series': { 'required': False }, 'invoice': { 'source': 'related_document', 'view_name': 'invoice-detail' }, } def create(self, validated_data): entries = validated_data.pop('proforma_entries', []) proforma = Proforma.objects.create(**validated_data) for entry in entries: entry_dict = dict() entry_dict['proforma'] = proforma for field in entry.items(): entry_dict[field[0]] = field[1] DocumentEntry.objects.create(**entry_dict) return proforma def update(self, instance, validated_data): # The provider has changed => force the generation of the correct number # corresponding to the count of the new provider current_provider = instance.provider new_provider = validated_data.get('provider') if new_provider and new_provider != current_provider: instance.number = None updateable_fields = instance.updateable_fields for field_name in updateable_fields: field_value = validated_data.get(field_name, getattr(instance, field_name)) setattr(instance, field_name, field_value) instance.save() return instance def instantiate_object(self, data): proforma = super(ProformaSerializer, self).instantiate_object(data) # after clean_defaults is moved into full_clean this call won't be needed proforma.clean_defaults() return proforma def validate(self, data): data = super(ProformaSerializer, self).validate(data) if self.instance and 'state' in data and data[ 'state'] != self.instance.state: msg = "Direct state modification is not allowed." \ " Use the corresponding endpoint to update the state." raise serializers.ValidationError(msg) return data
class TransactionSerializer(serializers.HyperlinkedModelSerializer): payment_method = PaymentMethodUrl(view_name='payment-method-detail', lookup_field='payment_method', queryset=PaymentMethod.objects.all()) url = TransactionUrl( view_name='transaction-detail', lookup_field='uuid', ) pay_url = TransactionPaymentUrl(lookup_url_kwarg='token', view_name='payment') customer = CustomerUrl(view_name='customer-detail', read_only=True) provider = ProviderUrl(view_name='provider-detail', read_only=True) id = serializers.CharField(source='uuid', read_only=True) amount = serializers.DecimalField(required=False, decimal_places=2, max_digits=12, min_value=0) class Meta: model = Transaction fields = ('id', 'url', 'customer', 'provider', 'amount', 'currency', 'state', 'proforma', 'invoice', 'can_be_consumed', 'payment_processor', 'payment_method', 'pay_url', 'valid_until', 'updated_at', 'created_at', 'fail_code', 'refund_code', 'cancel_code') read_only_fields = ('customer', 'provider', 'can_be_consumed', 'pay_url', 'id', 'url', 'state', 'updated_at', 'created_at', 'payment_processor', 'fail_code', 'refund_code', 'cancel_code') updateable_fields = ('valid_until', 'success_url', 'failed_url') extra_kwargs = { 'amount': { 'required': False }, 'currency': { 'required': False }, 'invoice': { 'view_name': 'invoice-detail' }, 'proforma': { 'view_name': 'proforma-detail' } } def validate(self, attrs): attrs = super(TransactionSerializer, self).validate(attrs) if not attrs: return attrs if self.instance: if self.instance.state != Transaction.States.Initial: message = "The transaction cannot be modified once it is in {}"\ " state.".format(self.instance.state) raise serializers.ValidationError(message) # Run model clean and handle ValidationErrors try: # Use the existing instance to avoid unique field errors if self.instance: transaction = self.instance transaction_dict = transaction.__dict__.copy() errors = {} for attribute, value in list(attrs.items()): if attribute in self.Meta.updateable_fields: continue if getattr(transaction, attribute) != value: errors[attribute] = "This field may not be modified." setattr(transaction, attribute, value) if errors: raise serializers.ValidationError(errors) transaction.full_clean() # Revert changes to existing instance transaction.__dict__ = transaction_dict else: transaction = Transaction(**attrs) transaction.full_clean() except ValidationError as e: errors = e.error_dict non_field_errors = errors.pop(NON_FIELD_ERRORS, None) if non_field_errors: errors['non_field_errors'] = [ error for sublist in non_field_errors for error in sublist ] raise serializers.ValidationError(errors) return attrs
class PaymentMethodSerializer(serializers.HyperlinkedModelSerializer): url = PaymentMethodUrl(view_name='payment-method-detail', source="*", read_only=True) transactions = PaymentMethodTransactionsUrl( view_name='payment-method-transaction-list', source='*') customer = CustomerUrl(view_name='customer-detail', read_only=True) payment_processor_name = serializers.ModelField( model_field=PaymentMethod()._meta.get_field('payment_processor'), source="payment_processor", label="Payment Processor") payment_processor = serializers.SerializerMethodField() def get_payment_processor(self, obj): return PaymentProcessorSerializer(obj.get_payment_processor(), context=self.context).data class Meta: model = PaymentMethod fields = ('url', 'transactions', 'customer', 'payment_processor_name', 'payment_processor', 'added_at', 'verified', 'canceled', 'valid_until', 'display_info') extra_kwargs = { 'added_at': { 'read_only': True }, } def validate(self, attrs): attrs = super(PaymentMethodSerializer, self).validate(attrs) if self.instance: if self.instance.canceled: raise ValidationError( 'You cannot update a canceled payment method.') # Run model clean and handle ValidationErrors try: # Use the existing instance to avoid unique field errors payment_method = self.instance payment_method_dict = payment_method.__dict__.copy() for attribute, value in attrs.items(): setattr(payment_method, attribute, value) payment_method.full_clean() # Revert changes to existing instance payment_method.__dict__ = payment_method_dict except ValidationError as e: errors = e.error_dict non_field_errors = errors.pop(NON_FIELD_ERRORS, None) if non_field_errors: errors['non_field_errors'] = [ error for sublist in non_field_errors for error in sublist ] raise serializers.ValidationError(errors) return attrs def validate_payment_processor_name(self, value): if value not in PaymentMethod.PaymentProcessors.as_list(): raise serializers.ValidationError('"{}" is not a valid ' 'choice'.format(value)) if self.instance and value != self.instance.payment_processor: message = "This field may not be modified." raise serializers.ValidationError(message) return value def validate_verified(self, value): if self.instance and not value and self.instance.verified: message = "You cannot unverify a payment method." raise serializers.ValidationError(message) return value