Core Data Model#
A major strength of the FairDM framework lies in its use of a shared core data model. This foundational structure ensures that all domain-specific extensions are built on a consistent, interoperable base, which dramatically improves data discoverability, integration, and reuse across disciplines.

The core model is organized into four interconnected levels—Project, Dataset, Sample, and Measurement—each designed to capture essential metadata common to a wide range of research domains:
The Project level captures overarching metadata such as collaborators, funding, and general project descriptions.
The Dataset level aligns with internationally-recognised standards like DataCite, making it easier to formally publish and cite datasets using external services.
The Sample level supports persistent identification through systems like IGSN, and enables structured relationships between complex physical or digital samples.
The Measurement level offers a generalizable framework for describing observed values, while allowing communities to define their own domain-specific measurement schemas.
By establishing this shared foundation, FairDM ensures that even as individual communities define their own specialized data models, they continue to speak a common language at the structural level. This has several important benefits:
Interoperability by design: Since all community-defined models inherit from the same base, data produced in different domains can be more easily integrated, compared, or reused in broader research contexts.
Reduced duplication of effort: Communities don’t need to reinvent basic structures or metadata concepts. Instead, they can focus their energy on refining and extending what’s already there, adding only the domain-specific details that matter most.
Tool and service compatibility: Standardized core schemas enable the development of reusable tools, APIs, and validation mechanisms that can operate across all FairDM-based portals without custom configuration for each community.
Scalability and sustainability: A modular, extensible approach encourages collaboration across communities, while also supporting long-term maintenance of the infrastructure as standards evolve.
In short, the core model acts as a semantic and technical glue that connects diverse research communities under a unified, extensible framework—balancing the need for local autonomy with the broader goals of FAIR data stewardship.
The Project Model#
In FairDM, a Project is the highest-level concept in the data model. Think of it as a container for all the research activities and data associated with a particular scientific investigation. Every dataset, sample, and measurement can be linked to a specific project—this helps keep things organized and traceable.
What does a Project represent?#
A project typically includes:
A name that identifies the research.
An image that visually represents the project (like a logo or key figure).
Keywords and tags that make the project easier to find in searches.
Status information, like whether the project is still being developed or is already published.
Visibility settings, which control who can see the project (private, public, etc.).
Contributor information, such as the people and organizations involved.
Funding details, like grant numbers or funding agencies.
Time stamps, to show when the project was created or last updated.
These fields make sure that each project includes enough essential metadata to support the FAIR principles (Findable, Accessible, Interoperable, Reusable).
The Dataset Model#
In FairDM, a Dataset is the second key building block, directly below a Project. You can think of a dataset as a folder of research data that belongs to a specific project. This folder not only contains data itself—like measurements or samples—but also all the extra information needed to understand and reuse that data.
Every dataset must belong to a project, and every sample, measurement, or site you add will be connected to a dataset.
What does a Dataset include?#
A dataset typically holds:
A name: The title or short description of what the dataset contains.
An image: (Optional) A visual related to the dataset, like a photo of a study site or chart.
Keywords and tags: These help people find the dataset more easily when searching.
Visibility settings: These control who can view the dataset (private or public).
Time stamps: Indicate when the dataset was created or last modified.
Contributor information: Who created or worked on this dataset.
Descriptions: Structured descriptions explaining what the dataset contains and how it was collected.
Optional links to publications: References to scientific articles or reports related to the dataset.
License information: Explains how the data can be reused. By default, FairDM comes with support for standard Creative Commons licenses.
The Sample Model#
A Sample in FairDM represents a physical or digital object that is part of a dataset. This could be anything from a rock or water sample, to a digital file like an image, audio clip, or simulation result. Every sample belongs to a dataset, which in turn is part of a project.
While Project and Dataset are not meant to be extended or modified, the Sample model can be extended. This means researchers or developers can create custom sample types to store extra information specific to their scientific domain.
What does a Sample include?#
Each sample by default includes:
A name: A short, descriptive title for the sample.
An image: (Optional) A visual representation of the sample.
Keywords and tags: Help make the sample more searchable and easier to categorize.
A local ID: A custom identifier used internally by the researchers, often printed on labels or containers.
A status: Such as “collected”, “analyzed”, or “unknown” to indicate the sample’s current state.
A location: If the sample has a known geographic origin, you can link it to a location record.
Time stamps: Automatically track when the sample was created and last updated.
Contributors: You can list who helped collect, prepare, or manage the sample.
Descriptions: Structured descriptions explaining what the sample is and how it was collected.
And most importantly:
It must belong to a dataset, ensuring traceability and context.
Can I add custom fields to the Sample model?#
Yes. Unlike Project or Dataset, the Sample model is designed to be extended. This means we encourage you to develop and maintain data model appropriate to your specific research community.
For example, if you’re studying tree cores, you could create a new sample type called TreeCoreSample that inherits the default Sample metadata and adds extra fields like:
Tree species
Core length
Ring count
This way, you don’t directly modify the base Sample model, but you still get all of its standard features and metadata.
What does it mean to extend the Sample model?#
Technically, your custom sample type will still be treated as a Sample in the system, so all search tools and visualizations will work seamlessly. But you’ll also have your own extra fields, which only appear on your specialized sample type.
This makes the system flexible and powerful across disciplines—whether you’re studying soil, ice cores, fossils, or digital simulations.
Understanding the Measurement Model#
A Measurement in FairDM represents a result or observation made on a sample. Think of it as the data you collect from a sample—like a weight, pH reading, chemical composition, image analysis, or laboratory test result.
Like samples, measurements are polymorphic, which means they can be extended to include additional fields specific to your scientific needs. However, measurements have unique characteristics that distinguish them from samples:
Measurements always reference a sample - they cannot exist independently
Measurements belong to a dataset - which may differ from the sample’s dataset (enabling cross-dataset workflows)
Measurements are polymorphic - different measurement types (XRF, ICP-MS, microscopy) can coexist with different field structures
Measurements have optimized querysets - for efficient loading of related data (sample, dataset, metadata)
Measurement Model Architecture#
The Measurement model extends the core SampleMetadataBase class, giving it all the standard metadata capabilities (descriptions, dates, identifiers, contributors, tags) plus measurement-specific features:
from fairdm.core import Measurement
class XRFMeasurement(Measurement):
"""X-ray fluorescence measurement for elemental analysis."""
element = models.CharField(max_length=10)
concentration_ppm = models.FloatField()
detection_limit_ppm = models.FloatField(null=True, blank=True)
class Meta:
verbose_name = "XRF Measurement"
Key features:
Inherits from
Measurementbase model (polymorphic)Automatically gets
sampleanddatasetforeign keysIncludes all FAIR metadata fields (descriptions, dates, identifiers)
Can add domain-specific fields (element, concentration, etc.)
Entity Relationship Diagram#
The following diagram shows the core measurement model structure and relationships:
erDiagram
Project ||--o{ Dataset : contains
Dataset ||--o{ Sample : contains
Dataset ||--o{ Measurement : contains
Sample ||--o{ Measurement : "measured by"
Measurement ||--o{ MeasurementDescription : "has"
Measurement ||--o{ MeasurementDate : "has"
Measurement ||--o{ MeasurementIdentifier : "has"
Measurement ||--o{ Contribution : "has"
Measurement {
uuid id PK
string name
uuid sample_id FK
uuid dataset_id FK
datetime added
datetime modified
image image
}
Sample {
uuid id PK
string name
uuid dataset_id FK
}
Dataset {
uuid id PK
string name
uuid project_id FK
}
Project {
uuid id PK
string name
}
MeasurementDescription {
int id PK
string type
text value
uuid measurement_id FK
}
MeasurementDate {
int id PK
string type
string value
uuid measurement_id FK
}
MeasurementIdentifier {
int id PK
string type
string value
uuid measurement_id FK
}
Contribution {
int id PK
uuid contributor_id FK
string roles
int order
}
Key relationships:
Measurement → Sample: Every measurement references exactly one sample (required)
Measurement → Dataset: Every measurement belongs to exactly one dataset (can differ from sample’s dataset)
Measurement ↔ Metadata: One-to-many relationships with descriptions, dates, and identifiers
Sample → Dataset: Samples belong to datasets (establishing ownership and permissions)
Polymorphic Measurement Types#
FairDM supports multiple measurement types through polymorphic inheritance. Here’s how different measurement types relate:
classDiagram
class Measurement {
<<abstract>>
+uuid id
+string name
+Sample sample
+Dataset dataset
+image image
+get_value() string
}
class XRFMeasurement {
+string element
+float concentration_ppm
+float detection_limit_ppm
+get_value() "X ppm Element"
}
class pHMeasurement {
+float ph_value
+float temperature_c
+string instrument
+get_value() "pH X @ Y°C"
}
class MicroscopyMeasurement {
+string microscope_type
+int magnification
+float scale_bar_microns
+file image
+get_value() "Type Xmag"
}
class ICP_MS_Measurement {
+string isotope
+float counts_per_second
+float concentration_ppb
+string standard_used
+get_value() "X ppb Isotope"
}
Measurement <|-- XRFMeasurement
Measurement <|-- pHMeasurement
Measurement <|-- MicroscopyMeasurement
Measurement <|-- ICP_MS_Measurement
How polymorphic measurements work:
All types share the base
Measurementtable (single table inheritance)Each type adds custom fields specific to that analysis method
Query all measurements:
Measurement.objects.all()returns all typesQuery specific type:
XRFMeasurement.objects.all()returns only XRF measurementsAdmin interface automatically routes to type-specific forms and displays type-specific fields
Relationship Flows: Measurement-Sample-Dataset#
Measurements have two key relationships that enable flexible research workflows:
Standard Flow: Measurement in Same Dataset as Sample#
Project
└─ Dataset A
├─ Sample 1
└─ Measurement 1 (references Sample 1, belongs to Dataset A)
This is the typical workflow where measurements are stored in the same dataset as their samples.
Cross-Dataset Flow: Measurement in Different Dataset#
Project
├─ Dataset A (Sample Collection)
│ └─ Sample 1 (collected 2024-01-15)
└─ Dataset B (Laboratory Analysis)
└─ Measurement 1 (references Sample 1 from Dataset A, belongs to Dataset B)
The following diagram visualizes cross-dataset measurement workflows:
graph TB
subgraph Project["Project: Regional Geology Study"]
subgraph DatasetA["Dataset A: Field Samples 2023"]
S1[Sample: RS-001]
S2[Sample: RS-002]
S3[Sample: RS-003]
end
subgraph DatasetB["Dataset B: XRF Analysis 2024"]
M1[Measurement: XRF-RS001-Fe]
M2[Measurement: XRF-RS001-Si]
M3[Measurement: XRF-RS002-Fe]
end
subgraph DatasetC["Dataset C: ICP-MS Analysis 2024"]
M4[Measurement: ICPMS-RS001-U238]
M5[Measurement: ICPMS-RS003-Th232]
end
end
S1 -.->|measured by| M1
S1 -.->|measured by| M2
S2 -.->|measured by| M3
S1 -.->|measured by| M4
S3 -.->|measured by| M5
style S1 fill:#e3f2fd
style S2 fill:#e3f2fd
style S3 fill:#e3f2fd
style M1 fill:#fff9c4
style M2 fill:#fff9c4
style M3 fill:#fff9c4
style M4 fill:#ffe0b2
style M5 fill:#ffe0b2
Why cross-dataset linking matters:
Separation of concerns: Sample collection metadata separate from analysis metadata
Different teams: Field team manages samples, lab team manages measurements
Different timelines: Samples collected years before analysis
Permission boundaries: Lab team can add measurements without editing sample dataset
Provenance tracking: Clear lineage showing which dataset performed which analyses
Polymorphic Measurement Pattern#
Measurements use Django’s polymorphic models to support multiple measurement types in a single table structure:
Benefits:
Query all measurements regardless of type:
Measurement.objects.all()Filter by specific type:
XRFMeasurement.objects.all()Admin interface shows type-specific fields automatically
Type-safe:
isinstance(measurement, XRFMeasurement)works correctly
Type selection in admin: When creating a measurement, portal admins see a dropdown with all registered measurement types (XRF Measurement, ICP-MS Measurement, etc.). Selecting a type loads the appropriate form with type-specific fields.
Measurement Lifecycle#
flowchart LR
A[Sample Collected] --> B[Measurement Created]
B --> C[Measurement Edited]
C --> D[Metadata Added]
D --> E[Published]
style A fill:#e3f2fd
style B fill:#fff9c4
style C fill:#fff9c4
style D fill:#fff9c4
style E fill:#c8e6c9
1. Sample Collected - Sample exists in a dataset 2. Measurement Created - Linked to sample and dataset 3. Measurement Edited - Values and metadata updated 4. Metadata Added - Descriptions, dates, identifiers, contributors 5. Published - Dataset made public (if desired)
What does a Measurement include?#
Each measurement records:
A name: A descriptive title of what the measurement is (e.g., “XRF Analysis - Iron Content”)
An image: (Optional) A visual representation related to the measurement (e.g., spectrum plot)
Keywords and tags: Help categorize and improve discoverability
A dataset: All measurements are tied to a dataset for traceability
A sample: Every measurement must be linked to the sample it was measured from (can be cross-dataset)
Timestamps: Automatically track when the measurement was created and last modified
Contributors: Optionally record who performed or contributed to the measurement
Descriptions: Structured descriptions explaining what the measurement represents and how it was produced
Uses measurement-specific vocabulary types (
measured,method,protocol, etc.)
Dates: Structured dates for measurement events (analysis date, calibration date, etc.)
Uses measurement-specific vocabulary types (
analyzed,calibrated, etc.)
Identifiers: External IDs or references (lab notebook IDs, instrument IDs, etc.)
Can I extend the Measurement model?#
Yes. The Measurement model is designed to be flexible and domain-specific. You can create your own custom measurement types by subclassing the base Measurement model.
Example: pH Measurement
class pHMeasurement(Measurement):
"""pH measurement with temperature compensation."""
ph_value = models.FloatField(help_text="Measured pH value")
temperature_c = models.FloatField(help_text="Temperature at measurement (°C)")
instrument = models.CharField(max_length=100)
def get_value(self):
return f"pH {self.ph_value} at {self.temperature_c}°C"
Example: Spectral Measurement
class SpectralMeasurement(Measurement):
"""Spectroscopic measurement with wavelength range."""
min_wavelength_nm = models.FloatField(help_text="Minimum wavelength (nm)")
max_wavelength_nm = models.FloatField(help_text="Maximum wavelength (nm)")
resolution = models.FloatField(help_text="Resolution (nm)")
spectrum_file = models.FileField(upload_to="spectra/")
def get_value(self):
return f"{self.min_wavelength_nm}-{self.max_wavelength_nm} nm ({self.resolution} nm resolution)"
Registration with FairDM: After defining your measurement type, register it with the FairDM registry to enable automatic admin, forms, filters, and API endpoints:
from fairdm.registry import register
from fairdm.registry.config import ModelConfiguration
@register
class XRFMeasurementConfig(ModelConfiguration):
model = XRFMeasurement
fields = ["name", "sample", "dataset", "element", "concentration_ppm"]
See the Measurement Development Guide for complete implementation details