Registry System Architecture#
This page is for people changing the registry itself. How a portal uses it is in the developers’ section: What registering a type produces, Model configuration and The registry.
The FairDM registry system is a core component that enables automatic generation of Django components (forms, tables, filters, etc.) from model definitions. This document explains the fundamental architecture, design decisions, and implementation details.
Core Architecture#
Key Components#
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ @register │───▶│ ModelConfiguration│───▶│ Components │
│ Decorator │ │ │ │ │
└─────────────────┘ └──────────────────┘ │ • Forms │
▲ │ • Tables │
│ │ • Filters │
▼ │ • Serializers │
┌─────────────────┐ ┌──────────────────┐ │ • Resources │
│ Registry │ │ Factories │ │ • Admin │
│ │ │ │ └─────────────────┘
│ • Model Storage │ │ Component │
│ • Introspection │ │ Generation │
│ • Validation │ └──────────────────┘
└─────────────────┘
Registration Flow#
Model definition: a portal writes a concrete
SampleorMeasurementsubclass.Configuration: a
ModelConfigurationsubclass declares the model and the fields that matter.Validation: the whole configuration is checked while it is built, and the registry checks that the model may be registered at all. A mistake stops the process here.
Registration:
@registerstores the configuration, and registers the model’s admin class unless the portal already registered one of its own.Component access:
get_<component>_class()resolves the field list and builds the class, on every call.
Component Generation#
# User defines model and configuration
@register
class MySampleConfig(ModelConfiguration):
model = MySample
fields = ["name", "location", "temperature"]
# Framework builds components from the configuration
config = registry.get_for_model(MySample)
form_class = config.get_form_class() # ModelForm
table_class = config.get_table_class() # django-tables2 Table
filter_class = config.get_filterset_class() # django-filter FilterSet
# ... and get_serializer_class(), get_resource_class(), get_admin_class()
Always call the accessor. It is the only public way to reach a component, so a configuration that overrides one is honoured everywhere.
Implementation Details#
Registry Storage#
The registry uses a simple dictionary mapping models to configurations:
class FairDMRegistry:
def __init__(self):
self._registry: dict[type[Model], ModelConfiguration] = {}
This provides O(1) lookup performance and simple introspection.
Field Resolution#
resolve_fields() is the one place a field list is worked out, and every component uses it. A
component’s own list wins, then the shared fields, then the framework’s own choice:
def resolve_fields(self, component: str) -> list[str]:
spec = COMPONENTS[component]
declared = getattr(self, spec.fields_attr)
chosen = (
declared
if declared is not None
else (self.fields or self.get_default_fields(self.model))
)
return [n for n in flatten_fields(chosen) if n not in set(self.exclude)]
COMPONENTS is the table that describes all six: which attribute holds each one’s field list, which
holds a supplied class, which base class that must subclass, and which factory builds it. Adding a
component is a row, not six near-identical methods.
The framework’s own choice of fields lives on FieldInspector in fairdm/utils/inspection.py, and
only there. It includes the model’s editable fields and leaves out the primary key, polymorphic type
columns, inheritance pointers, automatic timestamps, non-editable fields, reverse relations, and
many-to-many fields with an explicit through model, which Django’s admin rejects.
Component Factories#
Each component type has a dedicated factory:
class FormFactory(ComponentFactory):
def generate(self) -> type[ModelForm]:
return modelform_factory(
model=self.model,
fields=self.get_fields(),
widgets=self._get_smart_widgets(),
)
A factory receives a resolved field list and builds one class from it. It does no resolution of its own, so there is one answer to “which fields does this model use” per component.
Validation System#
Registration-time validation prevents common errors:
Validation happens once, while the model is being registered, and nowhere else. Registration runs at import, so a mistake stops the process on every start, including under a WSGI or ASGI server. There are no Django system checks for the registry: a check only runs from a management command, which makes it a weaker guarantee wearing the same clothes.
What is refused:
a field name that does not exist, with a close match suggested where there is one
a related path whose later segments do not resolve, not just its first
a component’s own field list declared beside its own class
a supplied class that does not subclass the base its component requires
a custom admin class that is not the polymorphic child admin for its hierarchy
a model that is abstract, or is one of the polymorphic base classes, or is outside both hierarchies
a model registered twice, with the module and qualified name of the first registration
An error names the model, the attribute that declared the offending value, the value itself, and either a suggestion or the reason a path stopped resolving:
Invalid field 'rock_typ' in RockSample.table_fields: no such field on RockSample.
Did you mean: rock_type?
Performance Characteristics#
Registration#
Validating a configuration costs about 0.007 ms, so 100 registered models add 0.69 ms to startup and
250 add 1.7 ms. Validation is _meta lookup only and never touches the database, which matters
because registration runs while Django is still populating the app registry.
Generating components#
Producing all six components for one model takes under 1 ms, scaling at roughly 0.1 ms per field per component. Nothing is cached, so a view pays that cost per request rather than once.
The tests pin the deterministic property rather than the timings: registration and all six accessors run with a query count of zero. Wall-clock assertions are not used, because they are flaky on shared CI in the direction that blocks merges.
Memory#
The registry holds one configuration per model and nothing else.
Testing Strategy#
Unit Tests#
Component generation: Test each factory independently
Field resolution: Test 3-tier fallback algorithm
Validation: Test error cases and edge conditions
Introspection: Test registry API methods
Integration Tests#
End-to-end registration: Model → config → components
Django integration: Forms work with views, tables render correctly
Performance: Benchmark against requirements
Contract Tests#
Protocol compliance: Verify implementations match Protocol definitions
API stability: Ensure public API remains consistent
Contributing Guidelines#
When working on the registry system:
Add comprehensive tests - both unit and integration tests for new features
Update type hints - maintain 100% mypy compliance
Document performance impact - benchmark any changes affecting registration/generation
Follow naming conventions - use consistent naming for new components/methods
Validate with demo app - ensure changes work in demo
The registry system is the foundation of FairDM’s ease of use - changes here impact every user, so maintain high quality standards and thorough testing.