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 |
|---|---|---|
|
Single FQN string |
The class itself |
|
Single FQN string |
An instance (via factory) |
|
Comma-separated FQNs |
List of classes |
|
Comma-separated FQNs |
List of instances |
|
Single FQN string |
Instance with parent validation |
|
Comma-separated FQNs |
List of instances with parent validation |
Common Parameters¶
All registry fields accept these parameters:
registryThe
Registrysubclass this field is tied to. Required.import_errorValue or callable to use when a stored class can’t be imported. If callable, receives
(original_value, exception). Default:None.max_lengthMaximum 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
)
factoryA callable
(klass, obj) -> instancewhereklassis the implementation class andobjis 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_fieldName 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:
Check if the value is a slug in
registry.implementationsFall 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 |
|---|---|---|
|
Error |
Registry has invalid |
|
Error |
Model field has invalid |
|
Warning |
Hierarchical registry references parent not in global registry |
|
Warning |
Model field references registry not in global registry |
Run checks with:
python manage.py check --tag django_stratagem