How to reference an inner class or attribute before it is fully defined?

I have a scenario where a class contains an inner class, and I want to reference that inner class (or its attributes) within the outer class. Here’s a concrete example using Django:

from django.db import models
from django.utils.translation import gettext_lazy as _

class DummyModel(models.Model):

    class StatusChoices(models.TextChoices):
        ACTIVE = "active", _("Active")
        INACTIVE = "inactive", _("Inactive")

    status = models.CharField(
        max_length=15,
        choices=StatusChoices.choices,
        verbose_name=_("Status"),
        help_text=_("Current status of the model."),
        default=StatusChoices.ACTIVE,
        null=False,
        blank=False,
    )

    class Meta:
        verbose_name = _("Dummy Model")
        verbose_name_plural = _("Dummy Models")
        constraints = [
            models.CheckConstraint(
                name="%(app_label)s_%(class)s_status_valid",
                check=models.Q(status__in=[choice.value for choice in DummyModel.StatusChoices]),
            )
        ]

In this case, the constraints list in the Meta class tries to reference DummyModel.StatusChoices. However, at the time this reference is evaluated, DummyModel is not fully defined, leading to an error (neither StatusChoices is accessible in that line).

I would like to solve this without significantly altering the structure of the code—StatusChoices must remain defined inside DummyModel.

How can I resolve this issue while keeping the inner class and its attributes accessible as intended?

Python evaluates the Meta class at the time it is defined, but the DummyModel class is not fully defined yet. Therefore, referencing DummyModel.StatusChoices inside the Meta class leads to an error.

You can probably get away using a classmethod func. Define a helper method to retrieve the choices:

class DummyModel(models.Model):

    class StatusChoices(models.TextChoices):
        ACTIVE = "active", _("Active")
        INACTIVE = "inactive", _("Inactive")

    status = models.CharField(
        max_length=15,
        choices=StatusChoices.choices,
        verbose_name=_("Status"),
        help_text=_("Current status of the model."),
        default=StatusChoices.ACTIVE,
        null=False,
        blank=False,
    )

    @classmethod
    def valid_status_choices(cls):
        return [choice.value for choice in cls.StatusChoices]

    class Meta:
        verbose_name = _("Dummy Model")
        verbose_name_plural = _("Dummy Models")
        constraints = [
            models.CheckConstraint(
                name="%(app_label)s_%(class)s_status_valid",
                # Use the classmethod for valid choices
                check=models.Q(status__in=DummyModel.valid_status_choices()),
            )
        ]

This avoids using lambda and instead evaluates it later so the class is already defined when called.

You can probably do this by defining the choices outside the class first, because the Meta class is actually constructed even before the status is accessible:

#       🖟 outside DummyModel
class StatusChoices(models.TextChoices):
    ACTIVE = 'active', _('Active')
    INACTIVE = 'inactive', _('Inactive')


class DummyModel(models.Model):
    status = models.CharField(
        max_length=15,
        choices=StatusChoices.choices,
        verbose_name=_('Status'),
        help_text=_('Current status of the model.'),
        default=StatusChoices.ACTIVE,
        null=False,
        blank=False,
    )

    class Meta:
        verbose_name = _('Dummy Model')
        verbose_name_plural = _('Dummy Models')
        constraints = [
            models.CheckConstraint(
                name='%(app_label)s_%(class)s_status_valid',
                check=models.Q(
                    status__in=[choice.value for choice in StatusChoices]
                ),
            )
        ]


DummyModel.StatusChoices = StatusChoices

For what it is worth, I made a small Django package named django-enforced-choices [GitHub] that can enforce choices at the database by just looking at the field with choices.

Вернуться на верх