fairdm.registry.factories#

Factories that generate Django components from a ModelConfiguration.

Each factory builds one component (form, table, filter set, admin, serializer or resource), choosing widgets, columns and filters from the model’s field types.

Module Contents#

Classes#

ComponentFactory

Base factory for generating Django components from configuration.

FormFactory

Factory for generating ModelForm classes with smart widgets.

TableFactory

Factory for generating Table classes with smart column types.

PublishedChoicesMixin

Narrow every related-record filter to published records, last.

FilterFactory

Factory for generating FilterSet classes with smart filter types.

AdminFactory

Factory for generating Django Admin ModelAdmin classes.

SerializerFactory

Factory for generating DRF ModelSerializer classes.

ResourceFactory

Factory for generating import/export Resource classes.

Functions#

published_related_queryset

Return the published records of a model, or None when publication is moot.

API#

class ComponentFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Base factory for generating Django components from configuration.

This abstract base class provides common functionality for all component factories. Subclasses implement specific generation logic for Forms, Tables, Filters, etc.

Args: model: The Django model class to generate components for. fields: Field names to use. None uses the model’s safe fields.

Initialization

get_fields() → list[str]#

Return the fields to use.

Explicitly provided fields win, then the model’s safe fields as found by inspector.get_safe_fields().

Returns: The field names to use.

abstractmethod generate() → Any#

Generate the component. Subclasses must implement it.

Returns: The generated component class.

Raises: NotImplementedError: Always, on the base class.

class FormFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating ModelForm classes with smart widgets.

Dates, large text, email and URL fields get a matching widget, and the form carries a crispy-forms helper with a submit button.

Initialization

generate() → type[django.forms.ModelForm]#

Generate a ModelForm class with smart widgets.

Returns: A ModelForm subclass with smart widgets and a crispy-forms helper.

get_base_form_class() → type[django.forms.ModelForm]#

Return the base ModelForm to build the form on.

Mirrors TableFactory.get_base_table_class(): a sample or measurement type supplying no form of its own still gets SampleFormMixin’s or MeasurementFormMixin’s widget configuration, dataset scoping and guidance text rather than a bare ModelForm.

Returns: A form class carrying the mixin for the model’s hierarchy, or ModelForm for any other model.

class TableFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating Table classes with smart column types.

Dates, emails, URLs and booleans get a matching column. Large text fields are left out of the table.

Initialization

generate() → type[django_tables2.Table]#

Generate a django-tables2 Table class with smart columns.

Returns: A Table subclass with smart columns.

get_base_table_class() → type[django_tables2.Table]#

Return the base table class for the model.

Returns: The sample or measurement table for those hierarchies, otherwise Table.

Return the published records of a model, or None when publication is moot.

The dataset branch goes through Dataset.all_objects, never Dataset.objects: the default manager excludes PRIVATE datasets, and a published-but-private dataset is the ordinary state, so Dataset.objects.published() would leave a filter offering nothing while the table shows that dataset’s rows. Publication is the only test a listing applies.

None rather than an unscoped queryset, so a caller can tell “every published one” from “not this model’s question” - a content-type filter already scoped to the registered types must not be widened back out.

Args: related_model: The model whose records a filter offers.

Returns: A queryset of the published records for a Dataset, Sample or Measurement model, otherwise None.

class PublishedChoicesMixin(*args, **kwargs)#

Narrow every related-record filter to published records, last.

Scoping filters as the factory builds them is not enough, in two ways that both leaked.

A generated filter set inherits SampleFilterMixin or MeasurementFilterMixin, and both assign their own hand-declared dataset (and, for measurements, sample) choice lists in __init__ - from Dataset.all_objects and Sample.objects respectively, neither of which applies publication. Those assignments happen at instantiation, after any class-level override, so a class-level fix does not survive them.

And a registration may supply its own filterset_class, or override get_filterset_class() outright, in which case the factory never runs at all. That is a documented tier of the configuration API, not an edge case.

So this is applied by the listing view to whatever filter set it is handed, and it is placed first in the bases, so its __init__ runs the rest of the chain and then re-scopes what that chain assigned. It reads each filter’s own queryset rather than the model field, so a many-to-many or reverse relation django-filter generated for itself is covered by the same pass, and a relation publication says nothing about is left alone.

It applies to the listings and to nothing else. Every other page that builds a filter on those two core mixins keeps their behaviour.

Initialization

classmethod applied_to(filterset_class: type) → type#

Put this mixin in front of a filter set, unless it is already there.

Idempotent, because a listing resolves its filter set on every request and a fresh subclass per request would leak classes.

Args: filterset_class: The filter set to scope.

Returns: filterset_class itself if it already has the mixin, otherwise a subclass with the mixin first.

class FilterFactory(model: type[django.db.models.Model], fields: list[str] | None = None, search_fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating FilterSet classes with smart filter types.

Dates get range filters, choice fields a choice filter, foreign keys a model choice filter, numbers a range filter and text a case-insensitive contains filter. The set also carries a q filter over the type’s search fields, and its range filters use the range inputs from mvp_forms.

Args: model: The Django model class to generate the filter set for. fields: Field names to use. None uses the model’s safe fields. search_fields: ORM paths the q filter matches. Empty means the filter set has no q filter of its own.

Initialization

generate() → type[django_filters.FilterSet]#

Generate a django-filter FilterSet class with smart filters.

Fields django-filter cannot resolve are dropped one at a time until it accepts the rest.

Returns: A FilterSet subclass with smart filters.

Raises: AssertionError: django-filter rejected the fields for a reason other than an unresolved field name.

get_base_filterset_class() → type[django_filters.FilterSet]#

Return the base FilterSet to build the filter set on.

Mirrors TableFactory.get_base_table_class(): a sample or measurement type’s generated filter set carries SampleFilterMixin’s or MeasurementFilterMixin’s declared filters rather than a bare FilterSet. Both mixins are already FilterSet subclasses, so, unlike the form factory, no wrapping is needed here.

Returns: The mixin for the model’s hierarchy, or FilterSet for any other model.

class AdminFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating Django Admin ModelAdmin classes.

Generates list_display, search_fields and list_filter, groups fields into fieldsets, marks timestamps and ids read-only and adds a date hierarchy where the model has a date field.

Initialization

generate() → type[django.contrib.admin.ModelAdmin]#

Generate a Django ModelAdmin class.

Returns: ModelAdmin subclass configured for the model

class SerializerFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating DRF ModelSerializer classes.

A sample or measurement type gets a serializer built on BaseSampleSerializer or BaseMeasurementSerializer, so it carries the fields common to its kind followed by every field its registration declares. A relation to a project, dataset, sample or measurement is shown as a reference, any other relation as its string form.

Initialization

generate() → type#

Generate a DRF ModelSerializer class.

Returns: A ModelSerializer subclass named <Model>Serializer.

class ResourceFactory(model: type[django.db.models.Model], fields: list[str] | None = None)#

Bases: fairdm.registry.factories.ComponentFactory

Factory for generating import/export Resource classes.

Foreign keys are matched on the related model’s natural key where it has one, otherwise on its primary key.

Initialization

generate() → type#

Generate an import/export Resource class.

Returns: A ModelResource subclass with foreign key widgets.