DRF: Programmatically get default choice from TextChoices field

Viewed 299

Our site is a Vue frontend / DRF backend. In a serializer.validate() method, I need to programmatically determine which option from a TextChoices class has been specified as the default value for a model field.

TextChoices class (abbreviated example):

class PaymentMethod(models.TextChoices):
    BANK_TRANSFER = 'Bank Transfer'
    CREDIT_CARD = 'Credit Card'
    PAYPAL = 'PayPal'
    OTHER = 'Other'

The model:

class InvoicePayment(CommonFieldsModelMixin,
                     StatusFieldModelMixin,
                     models.Model):

    ...other fields omitted...
    payment_method = models.TextField(
        verbose_name=_('Method'),
        choices=PaymentMethod.choices,
        default=PaymentMethod.OTHER,
    )
    payment_method_other = models.TextField(
        verbose_name=_('Payment Method Other'),
        default='',
    )

Our users are able to bypass the frontend and post directly to the API, which means they may omit fields from the POST data - either from negligence or because the fields have default values. For the above model, though, payment_method_other is required only if payment_method is "Other". That check is done in the serializer.validate() method.

If "Other" is selected on a form in the frontend, there's no problem because that value is present in validated_data passed to the validate() method. But if a user posts directly to the API and omits payment_method, the default value process is done at the database level (more or less), after the validate() method has executed.

To keep it DRY, and to avoid having mismatched code if the default is changed in the future, I don't want to hard-code the default of "Other" in the validate() method. Instead, I want to access the field definition info (meta data?) and programmatically determine the default that was defined on the model.

1 Answers

One way to do this without much hacking around is to define the default as a property of the model like this:

class InvoicePayment(CommonFieldsModelMixin,
                     StatusFieldModelMixin,
                     models.Model):

    DEFAULT_PAYMENT_METHOD = PaymentMethod.OTHER

    ...other fields omitted...
    payment_method = models.TextField(
        verbose_name=_('Method'),
        choices=PaymentMethod.choices,
        default=DEFAULT_PAYMENT_METHOD,
    )
    payment_method_other = models.TextField(
        verbose_name=_('Payment Method Other'),
        default='',
    )

Then you can just access the default easily through the model:

InvoicePayment.DEFAULT_PAYMENT_METHOD
Related