Using the Registry#
Once you’ve registered your Sample and Measurement models with FairDM, you’ll want to access and work with them programmatically. The FairDM registry provides a powerful API for discovering, inspecting, and iterating over registered models.
Accessing the Registry#
The global registry instance is available throughout your application:
from fairdm.registry import registry
Introspection API (New)#
The FairDM registry provides convenient properties for programmatically discovering and iterating over registered models. This introspection API enables dynamic workflows and flexible data processing.
Iterate Over All Registered Samples#
# Get all registered Sample model classes
for sample_model in registry.samples:
print(f"Sample: {sample_model.__name__}")
print(f"Module: {sample_model.__module__}")
# Get configuration for this model
config = registry.get_for_model(sample_model)
print(f"Display Name: {config.display_name}")
print(f"Fields: {config.fields}")
print("---")
Iterate Over All Registered Measurements#
# Get all registered Measurement model classes
for measurement_model in registry.measurements:
print(f"Measurement: {measurement_model.__name__}")
print(f"Module: {measurement_model.__module__}")
# Access the model's configuration
config = registry.get_for_model(measurement_model)
print(f"Display Name: {config.display_name}")
print(f"Description: {config.description}")
print("---")
Access All Registered Models#
# Get all registered models (Samples + Measurements combined)
all_models = list(registry.models)
print(f"Total registered models: {len(all_models)}")
for model_class in registry.models:
config = registry.get_for_model(model_class)
model_type = "Sample" if issubclass(model_class, Sample) else "Measurement"
print(f"{model_type}: {model_class.__name__} - {config.display_name}")
Dynamic Model Discovery#
The introspection API is particularly useful for building dynamic UIs and processing workflows:
from fairdm.core.models import Sample, Measurement
def process_all_samples():
"""Process all registered sample models dynamically."""
for sample_model in registry.samples:
# Get recent instances
recent_samples = sample_model.objects.filter(
created__gte=timezone.now() - timedelta(days=30)
)
print(f"Processing {len(recent_samples)} recent {sample_model.__name__} instances")
# Access auto-generated components
config = registry.get_for_model(sample_model)
form_class = config.form
table_class = config.table
# Dynamic processing based on model type
# ... your processing logic here
def create_measurement_reports():
"""Generate reports for all measurement types."""
for measurement_model in registry.measurements:
config = registry.get_for_model(measurement_model)
# Generate report using model's table configuration
report_data = measurement_model.objects.all()
table = config.table(report_data)
print(f"Generated report for {measurement_model.__name__}: {len(report_data)} records")
Model Filtering and Selection#
# Filter models by specific criteria
def get_models_with_geo_fields():
"""Find all registered models that have geographic fields."""
geo_models = []
for model_class in registry.models:
# Check if model has geographic fields
for field in model_class._meta.get_fields():
if field.name in ['latitude', 'longitude', 'location', 'coordinates']:
geo_models.append(model_class)
break
return geo_models
# Get models by app
def get_models_by_app(app_label):
"""Get all registered models from a specific Django app."""
app_models = []
for model_class in registry.models:
if model_class._meta.app_label == app_label:
app_models.append(model_class)
return app_models
Integration with Django Admin#
from django.contrib import admin
# Dynamically register all Sample models with custom admin
for sample_model in registry.samples:
config = registry.get_for_model(sample_model)
admin_class = config.admin # Auto-generated ModelAdmin
# Customize admin registration
if not admin.site.is_registered(sample_model):
admin.site.register(sample_model, admin_class)
Registry Overview#
Get a quick overview of all registered models:
# Print a formatted summary of all registered models
registry.summarise()
This will output something like:
============================================================
FairDM Registry Summary
============================================================
Total Registered Models: 4
📊 SAMPLES (2)
----------------------------------------
• WaterSample (myproject)
Display: Water Sample
Verbose: water sample
• SoilSample (myproject)
Display: Soil Sample
Verbose: soil sample
📊 MEASUREMENTS (2)
----------------------------------------
• ChemicalAnalysis (myproject)
Display: Chemical Analysis
Verbose: chemical analysis
• PhysicalMeasurement (myproject)
Display: Physical Properties
Verbose: physical measurement
============================================================
You can also get the summary data programmatically without printing:
summary = registry.summarise(print_output=False)
print(f"Total models: {summary['total_registered']}")
print(f"Samples: {summary['samples']['count']}")
print(f"Measurements: {summary['measurements']['count']}")
Accessing All Registered Models#
# Get all registered models
all_models = registry.all
# Each item is a dictionary with model metadata
for model_info in all_models:
print(f"Model: {model_info['class'].__name__}")
print(f"App: {model_info['app_label']}")
print(f"Type: {model_info['type']}") # 'sample' or 'measurement'
print(f"Verbose Name: {model_info['verbose_name']}")
print("---")
Working with Samples#
Get All Registered Samples#
# Get all registered sample models
samples = registry.samples
for sample_info in samples:
model_class = sample_info['class']
config = sample_info['config']
print(f"Sample Model: {model_class.__name__}")
print(f"Display Name: {config.display_name}")
print(f"Description: {config.get_description()}")
print(f"List Fields: {config.get_list_fields()}")
print("---")
Iterate Over Sample Instances#
from django.apps import apps
# Get all sample model classes
for sample_info in registry.samples:
model_class = sample_info['class']
# Query all instances of this sample type
instances = model_class.objects.all()
print(f"\n{model_class.__name__} instances:")
for instance in instances[:5]: # Show first 5
print(f" - {instance.name} (ID: {instance.id})")
Access Sample Configuration#
# Get configuration for a specific sample model
# Method 1: Using model class
from myproject.models import WaterSample
sample_config = registry.get_for_model(WaterSample)
if sample_config:
config = sample_config['config']
print(f"Display Name: {config.display_name}")
print(f"List Fields: {config.get_list_fields()}")
print(f"Filter Fields: {config.get_filter_fields()}")
print(f"Private Fields: {config.private_fields}")
# Method 2: Using string reference (useful for dynamic lookups)
sample_config = registry.get_for_model("myproject.watersample")
if sample_config:
config = sample_config['config']
print(f"Model class: {sample_config['class']}")
Working with Measurements#
Get All Registered Measurements#
# Get all registered measurement models
measurements = registry.measurements
for measurement_info in measurements:
model_class = measurement_info['class']
config = measurement_info['config']
print(f"Measurement Model: {model_class.__name__}")
print(f"Display Name: {config.display_name}")
print(f"Description: {config.get_description()}")
print("---")
Filtering Models by App#
If you have models from multiple Django apps, you can filter by app label:
# Get models from a specific app
myapp_models = [
model_info for model_info in registry.all
if model_info['app_label'] == 'myproject'
]
for model_info in myapp_models:
print(f"{model_info['type'].title()}: {model_info['class'].__name__}")
Checking if a Model is Registered#
from myproject.models import WaterSample
# Check if a model is registered
if registry.get_for_model(WaterSample):
print("WaterSample is registered")
else:
print("WaterSample is not registered")
Advanced Usage#
Accessing Auto-Generated Components#
The registry automatically generates forms, tables, filters, and serializers for registered models. You can access these:
from myproject.models import WaterSample
# Get the model registration info
model_info = registry.get_for_model(WaterSample)
config = model_info['config']
# Access auto-generated components
if hasattr(config, 'get_form_class'):
form_class = config.get_form_class()
print(f"Form class: {form_class}")
if hasattr(config, 'get_table_class'):
table_class = config.get_table_class()
print(f"Table class: {table_class}")
Working with Field Configurations#
# Inspect field configurations for all models
for model_info in registry.all:
config = model_info['config']
model_name = model_info['class'].__name__
print(f"\n{model_name} Field Configuration:")
print(f" List Fields: {config.get_list_fields()}")
print(f" Detail Fields: {config.get_detail_fields()}")
print(f" Filter Fields: {config.get_filter_fields()}")
if hasattr(config, 'private_fields') and config.private_fields:
print(f" Private Fields: {config.private_fields}")
Integration with Django Admin#
If you’re using Django admin, you can create a custom admin view that shows registry information:
from django.contrib import admin
from django.http import HttpResponse
from fairdm.registry import registry
def registry_summary_view(request):
"""Admin view showing registry summary."""
summary = registry.summarise(print_output=False)
html = "<h1>FairDM Registry Summary</h1>"
html += f"<p>Total Registered Models: {summary['total_registered']}</p>"
html += f"<h2>Samples ({summary['samples']['count']})</h2><ul>"
for model in summary['samples']['models']:
html += f"<li><strong>{model['name']}</strong> - {model['display_name']}</li>"
html += "</ul>"
html += f"<h2>Measurements ({summary['measurements']['count']})</h2><ul>"
for model in summary['measurements']['models']:
html += f"<li><strong>{model['name']}</strong> - {model['display_name']}</li>"
html += "</ul>"
return HttpResponse(html)
# Add to your admin URLs
# admin.site.register_view('registry-summary/', registry_summary_view, name='Registry Summary')
Measurement-Specific Registration#
Measurements have additional configuration options beyond basic model registration. This section covers measurement-specific patterns and configurations.
Base Measurement Configuration Fields#
When registering measurements, you can configure these model-specific options:
from fairdm.registry import register
from fairdm.registry.config import ModelConfiguration
from myapp.models import XRFMeasurement
@register
class XRFMeasurementConfig(ModelConfiguration):
model = XRFMeasurement
# Core Configuration
display_name = "XRF Measurement"
description = "X-ray fluorescence elemental analysis"
# Field Sets
fields = ["name", "sample", "dataset", "element", "concentration_ppm"] # General use
list_fields = ["name", "sample", "element", "concentration_ppm"] # Admin list view
detail_fields = ["name", "sample", "dataset", "element", "concentration_ppm", "detection_limit_ppm"] # Forms
filterset_fields = ["element", "dataset", "sample"] # Filter sidebar
# Admin Configuration
search_fields = ["name", "element", "sample__name"]
ordering = ["-created"]
# Custom Components (Optional)
form_class = None # Use auto-generated form
table_class = None # Use auto-generated table
filterset_class = None # Use auto-generated filterset
admin_class = None # Use auto-generated admin
Field Set Priority:
If
list_fieldsis specified, it’s used for admin list viewIf
list_fieldsis not specified, falls back tofieldsIf neither is specified, uses model’s first 5 fields
The same logic applies for detail_fields and filterset_fields.
Polymorphic Admin Validation Rules#
Measurements use polymorphic models, which have special validation rules in the admin:
Rule 1: Child admin must inherit from MeasurementChildAdmin
from fairdm.core.measurement.admin import MeasurementChildAdmin
class XRFMeasurementAdmin(MeasurementChildAdmin):
# Configuration for XRFMeasurement specifically
list_display = ["name", "sample", "element", "concentration_ppm"]
Rule 2: Don’t register polymorphic parent directly
# ❌ BAD
from fairdm.core import Measurement
admin.site.register(Measurement, SomeAdmin)
# ✅ GOOD - Register specific types only
from myapp.models import XRFMeasurement
admin.site.register(XRFMeasurement, XRFMeasurementAdmin)
The parent Measurement model is registered automatically with MeasurementParentAdmin which provides the type selection interface.
Rule 3: Fieldsets must only include fields that exist on the specific model
class XRFMeasurementAdmin(MeasurementChildAdmin):
fieldsets = [
("Basic", {
"fields": ["name", "sample", "dataset"] # ✅ These exist on all measurements
}),
("XRF Data", {
"fields": ["element", "concentration_ppm"] # ✅ These exist on XRFMeasurement
}),
# ❌ DON'T include fields from other measurement types
# ("ICP-MS Data", {
# "fields": ["isotope"] # ❌ This is ICP_MS_Measurement only
# }),
]
Rule 4: Use base_form for polymorphic types
If you need custom validation that applies to all measurement types:
from fairdm.core.measurement.forms import MeasurementFormMixin
class CustomMeasurementForm(MeasurementFormMixin, forms.ModelForm):
def clean(self):
# Custom validation for all measurements
cleaned_data = super().clean()
# Your validation here
return cleaned_data
class XRFMeasurementAdmin(MeasurementChildAdmin):
form = CustomMeasurementForm
QuerySet Optimization for Measurements#
When working with measurements, use the built-in QuerySet methods:
# Get measurement configuration
from myapp.models import XRFMeasurement
config = registry.get_for_model(XRFMeasurement)
# Access the model class
model_class = config.model
# Use optimized querysets
measurements = model_class.objects.with_related() # Loads sample, dataset
measurements = model_class.objects.with_metadata() # Loads descriptions, dates, identifiers
measurements = model_class.objects.with_related().with_metadata() # Both
Measurement Registration Examples#
Example 1: Minimal Configuration
@register
class SimpleMeasurementConfig(ModelConfiguration):
model = SimpleMeasurement
fields = ["name", "sample", "dataset", "value", "unit"]
Auto-generates:
Admin with list/detail views
ModelForm for create/edit
FilterSet for sidebar filtering
Table for list display
Example 2: Full Configuration
@register
class AdvancedMeasurementConfig(ModelConfiguration):
model = AdvancedMeasurement
# Display names
display_name = "Advanced Measurement"
description = "Complex measurement with multiple analysis steps"
# Field configuration
list_fields = ["name", "sample", "method", "result", "dataset"]
detail_fields = ["name", "sample", "dataset", "method", "result", "uncertainty", "quality_flag"]
filterset_fields = {
"method": ["exact"],
"quality_flag": ["exact"],
"dataset": ["exact"],
"created": ["gte", "lte"],
}
search_fields = ["name", "sample__name", "method"]
# Admin configuration
ordering = ["-created", "name"]
list_filter = ["method", "quality_flag", "dataset"]
# Custom components
form_class = AdvancedMeasurementForm
table_class = AdvancedMeasurementTable
filterset_class = AdvancedMeasurementFilter
Example 3: Measurement with Custom Admin
# First define custom admin
class SpectroscopyMeasurementAdmin(MeasurementChildAdmin):
list_display = ["name", "sample", "wavelength_range", "resolution", "dataset"]
fieldsets = [
("Identification", {
"fields": ["name", "sample", "dataset"]
}),
("Spectroscopy Parameters", {
"fields": ["min_wavelength_nm", "max_wavelength_nm", "resolution", "instrument"]
}),
("Data Files", {
"fields": ["spectrum_file"],
"classes": ["collapse"]
}),
]
def wavelength_range(self, obj):
return f"{obj.min_wavelength_nm}-{obj.max_wavelength_nm} nm"
wavelength_range.short_description = "Wavelength Range"
# Then register with custom admin reference
@register
class SpectroscopyMeasurementConfig(ModelConfiguration):
model = SpectroscopyMeasurement
fields = ["name", "sample", "dataset", "min_wavelength_nm", "max_wavelength_nm"]
admin_class = SpectroscopyMeasurementAdmin # Reference custom admin
Troubleshooting#
Problem: Measurement not appearing in admin
Check these common issues:
Migrations not run
poetry run python manage.py makemigrations poetry run python manage.py migrate
Model not registered
# Verify registration from fairdm.registry import registry config = registry.get_for_model(YourMeasurement) if config is None: print("Not registered!")
Import error in models.py
# Make sure base import works from fairdm.core import Measurement # Should not error
Problem: Admin shows wrong fields for measurement type
Cause: Polymorphic type confusion
Solution: Ensure correct admin base class:
# ✅ Correct
class YourMeasurementAdmin(MeasurementChildAdmin):
pass
# ❌ Wrong
class YourMeasurementAdmin(admin.ModelAdmin): # Missing polymorphic handling
pass
Problem: Registration validation errors
Example error: "Field 'nonexistent_field' does not exist on model YourMeasurement"
Solution: Check that all fields in your configuration exist on the model:
@register
class YourMeasurementConfig(ModelConfiguration):
model = YourMeasurement
fields = ["name", "sample", "dataset", "your_field"] # All must exist
Problem: N+1 queries in admin list
Cause: Not using optimized querysets
Solution: Override get_queryset in admin:
class YourMeasurementAdmin(MeasurementChildAdmin):
def get_queryset(self, request):
return super().get_queryset(request).with_related()
Problem: Type dropdown empty when creating measurement
Cause: No measurement types registered
Solution: Check registry:
from fairdm.registry import registry
print("Registered measurements:", len(list(registry.measurements)))
for model in registry.measurements:
print(f" - {model.__name__}")
If empty, ensure your config.py is being imported (check apps.py):
# myapp/apps.py
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
import myapp.config # Trigger registration
Cache Registry Queries: If you’re accessing the registry frequently, consider caching the results:
from django.core.cache import cache def get_cached_samples(): samples = cache.get('registry_samples') if samples is None: samples = registry.samples cache.set('registry_samples', samples, 300) # Cache for 5 minutes return samples
Use Type Checking: When iterating over models, check their type:
for model_info in registry.all: if model_info['type'] == 'sample': # Handle sample-specific logic pass elif model_info['type'] == 'measurement': # Handle measurement-specific logic pass
Error Handling: Always check if models exist before accessing them:
model_info = registry.get_for_model(MyModel) if model_info is not None: config = model_info['config'] # Safe to use config
Performance Considerations: For large datasets, use select_related() and prefetch_related():
for measurement_info in registry.measurements: model_class = measurement_info['class'] instances = model_class.objects.select_related('sample', 'dataset')
REST API Support#
Current Status: The FairDM registry auto-generates Django REST Framework serializers for registered models, but a full REST API with ViewSets and URL routing is not yet implemented.
What’s Available Now:
The registry creates serializers for each registered model:
from fairdm.registry import registry
# Access auto-generated serializer
config = registry.get_for_model(MyMeasurement)
serializer_class = config.serializer
# Use in your own views
from rest_framework.views import APIView
from rest_framework.response import Response
class MyMeasurementAPIView(APIView):
def get(self, request):
measurements = MyMeasurement.objects.all()
serializer = serializer_class(measurements, many=True)
return Response(serializer.data)
Custom Serializers:
You can provide custom serializers in your configuration:
from rest_framework import serializers
from fairdm.registry import register
from fairdm.registry.config import ModelConfiguration
class MyMeasurementSerializer(serializers.ModelSerializer):
class Meta:
model = MyMeasurement
fields = ["id", "name", "sample", "dataset", "element", "concentration_ppm"]
read_only_fields = ["id"]
@register
class MyMeasurementConfig(ModelConfiguration):
model = MyMeasurement
serializer_class = MyMeasurementSerializer # Use custom serializer
Planned Features:
A full REST API module is planned for a future release, which will include:
Auto-generated ModelViewSets for all registered models
Automatic URL routing configuration
Polymorphic API endpoints (measurements by type)
Filtering, searching, and pagination support
Nested serializers for relationships (sample → measurements, dataset → samples)
Permission integration with object-level access control
API documentation generation
For more information, see the project roadmap.
Current Workaround:
If you need a REST API now, you can create ViewSets manually:
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class MyMeasurementViewSet(viewsets.ModelViewSet):
"""API endpoint for MyMeasurement."""
queryset = MyMeasurement.objects.with_related()
permission_classes = [IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
# Use registry-generated serializer
config = registry.get_for_model(MyMeasurement)
return config.serializer
Then add to your URLs:
from rest_framework.routers import DefaultRouter
from myapp.api import MyMeasurementViewSet
router = DefaultRouter()
router.register(r'measurements/xrf', MyMeasurementViewSet, basename='xrf-measurement')
urlpatterns = [
path('api/', include(router.urls)),
]
The registry system provides a powerful foundation for building data-driven applications that can adapt to your evolving data models. Use these patterns to create flexible, maintainable code that works with any combination of registered Sample and Measurement models.