How to Use Model Fields

django-stratagem provides model fields for storing references to registry implementations. Each field stores a fully qualified class name (FQN) like "myapp.notifications.EmailNotification" as a CharField value in the database.

Field Types Summary

Field

Stores

Returns on Access

RegistryClassField

Single FQN string

The class itself

RegistryField

Single FQN string

An instance (via factory)

MultipleRegistryClassField

Comma-separated FQNs

List of classes

MultipleRegistryField

Comma-separated FQNs

List of instances

HierarchicalRegistryField

Single FQN string

Instance with parent validation

MultipleHierarchicalRegistryField

Comma-separated FQNs

List of instances with parent validation

Common Parameters

All registry fields accept these parameters:

registry

The Registry subclass this field is tied to. Required.

import_error

Value or callable to use when a stored class can’t be imported. If callable, receives (original_value, exception). Default: None.

max_length

Maximum CharField length. Default: 200.

Plus all standard Django Field kwargs (blank, null, default, verbose_name, etc.).

RegistryClassField

Stores a reference to an implementation class. Accessing the field returns the class.

from django_stratagem import RegistryClassField

class MyModel(models.Model):
    strategy = RegistryClassField(registry=NotificationRegistry)
obj = MyModel()
obj.strategy = EmailNotification       # Set by class
obj.strategy = "email"                  # Set by slug
obj.strategy = "myapp.notifications.EmailNotification"  # Set by FQN
obj.save()

obj.strategy  # Returns <class 'myapp.notifications.EmailNotification'>

Tip

The convenience method Registry.choices_field() creates a RegistryClassField tied to the registry:

strategy = NotificationRegistry.choices_field()
# Equivalent to: RegistryClassField(registry=NotificationRegistry)

RegistryField

Like RegistryClassField, but accessing the field returns an instance created by the factory callable.

from django_stratagem import RegistryField

class MyModel(models.Model):
    strategy = RegistryField(
        registry=NotificationRegistry,
        factory=lambda klass, obj: klass(),  # default
    )
factory

A callable (klass, obj) -> instance where klass is the implementation class and obj is the model instance. Default: lambda klass, obj: klass().

obj.strategy.send("Hello!", "user@example.com")  # Already an instance

Tip

The convenience method Registry.instance_field() creates a RegistryField:

strategy = NotificationRegistry.instance_field()

MultipleRegistryClassField

Stores references to multiple implementation classes as comma-separated FQNs.

from django_stratagem import MultipleRegistryClassField

class MyModel(models.Model):
    strategies = MultipleRegistryClassField(registry=NotificationRegistry)
obj.strategies = [EmailNotification, SMSNotification]
obj.save()

obj.strategies  # [<class 'EmailNotification'>, <class 'SMSNotification'>]

MultipleRegistryField

Like MultipleRegistryClassField, but returns instances.

from django_stratagem import MultipleRegistryField

class MyModel(models.Model):
    strategies = MultipleRegistryField(
        registry=NotificationRegistry,
        factory=lambda klass, obj: klass(),
    )
for strategy in obj.strategies:
    strategy.send("Hello!", "user@example.com")

HierarchicalRegistryField

A registry field that depends on a parent registry field selection. Used with HierarchicalRegistry.

from django_stratagem import HierarchicalRegistryField

class MyModel(models.Model):
    category = NotificationRegistry.choices_field()
    subcategory = HierarchicalRegistryField(
        registry=SubcategoryRegistry,
        parent_field="category",
    )
parent_field

Name of the model field that holds the parent selection. Validation ensures the child selection is valid for the chosen parent.

MultipleHierarchicalRegistryField

Multiple selection version of HierarchicalRegistryField.

from django_stratagem import MultipleHierarchicalRegistryField

class MyModel(models.Model):
    category = NotificationRegistry.choices_field()
    subcategories = MultipleHierarchicalRegistryField(
        registry=SubcategoryRegistry,
        parent_field="category",
    )

Slug Resolution

All field descriptors resolve values using this order:

  1. Check if the value is a slug in registry.implementations

  2. Fall back to importing as a fully qualified name via import_by_name()

This means you can set fields using either slugs or FQNs.

Querying with Lookups

Registry fields register custom lookups that automatically convert classes and instances to their FQN strings for database queries.

# Filter by class
MyModel.objects.filter(strategy=EmailNotification)

# Filter by slug string
MyModel.objects.filter(strategy="email")

# Filter with __in lookup (accepts classes)
MyModel.objects.filter(strategy__in=[EmailNotification, SMSNotification])

# Contains lookup (for multiple fields)
MyModel.objects.filter(strategies__contains=EmailNotification)

Supported lookups: exact, iexact, contains, icontains, in.

Advanced Factory Patterns

The factory parameter on RegistryField controls how instances are created when accessing the field. The default is lambda klass, obj: klass(), but you can inject model data, dependencies, or configuration.

Injecting Model Instance Data

class Notification(models.Model):
    channel = RegistryField(
        registry=NotificationRegistry,
        factory=lambda klass, obj: klass(
            sender=obj.sender_email,
            template=obj.template_name,
        ),
    )
    sender_email = models.EmailField()
    template_name = models.CharField(max_length=100)

Dependency Injection

def create_with_dependencies(klass, obj):
    """Inject services from a container."""
    from myapp.services import get_service_container
    container = get_service_container()
    return klass(
        http_client=container.http_client,
        cache=container.cache,
    )

class MyModel(models.Model):
    strategy = RegistryField(
        registry=MyRegistry,
        factory=create_with_dependencies,
    )

Singleton Pattern

_instance_cache = {}

def singleton_factory(klass, obj):
    if klass not in _instance_cache:
        _instance_cache[klass] = klass()
    return _instance_cache[klass]

class MyModel(models.Model):
    strategy = RegistryField(
        registry=MyRegistry,
        factory=singleton_factory,
    )

Validators

ClassnameValidator

Validates that a string value is a valid, importable Python class name.

from django_stratagem import ClassnameValidator

validator = ClassnameValidator(None)
validator("myapp.notifications.EmailNotification")  # OK
validator("invalid")  # Raises ValidationError

RegistryValidator

Validates that a value is registered in a specific registry.

from django_stratagem import RegistryValidator

validator = RegistryValidator(NotificationRegistry)
validator("email")  # OK if registered
validator("unknown")  # Raises ValidationError

Supports both single values and lists. Both validators are automatically added to registry model fields.

System Checks

django-stratagem registers system checks under the django_stratagem tag:

ID

Level

Description

E001

Error

Registry has invalid implementations_module

E002

Error

Model field has invalid registry (not a Registry subclass)

W001

Warning

Hierarchical registry references parent not in global registry

W002

Warning

Model field references registry not in global registry

Run checks with:

python manage.py check --tag django_stratagem