Model Construction and Implementation
This document describes how LDAP ORM models are constructed from their field definitions and Meta classes, and how this process compares to Django’s own model construction.
Overview
The LDAP ORM uses a metaclass-based approach to construct models, similar to
Django’s ORM. When you define a model class, the metaclass processes the class
attributes and Meta class to create a fully functional model with all necessary
metadata, field mappings, and manager setup.
The construction process involves several key components:
LdapModelBase- The metaclass that orchestrates model creationOptions- The metadata container (similar to Django’s Model._meta)Field- Field instances that define model attributesLdapManager- The default manager for model operations
Model Construction Process
When you define a model class, the following sequence occurs:
Class Definition Detection: The metaclass checks if the class inherits from
ModelBasic Class Creation: A basic class object is created with minimal attributes
Meta Class Processing: The Meta class is extracted and processed into an Options instance
Field Registration: Each field attribute is registered with the model
Model Preparation: Final setup including manager creation and validation
Signal Emission: The class_prepared signal is sent
Here’s a detailed breakdown of each step:
### 1. Class Definition Detection
The metaclass first checks if the class being created inherits from a model class:
def __new__(cls, name, bases, attrs, **kwargs):
super_new = super().__new__
parents = [b for b in bases if isinstance(b, LdapModelBase)]
if not parents:
return super_new(cls, name, bases, attrs)
# Process as a model class...
This ensures that only classes inheriting from Model are processed as models.
### 2. Basic Class Creation
A basic class object is created with essential attributes:
module = attrs.pop("__module__")
new_attrs = {"__module__": module}
classcell = attrs.pop("__classcell__", None)
if classcell is not None:
new_attrs["__classcell__"] = classcell
new_class = super_new(cls, name, bases, new_attrs, **kwargs)
### 3. Meta Class Processing
The Meta class is extracted and converted to an Options instance:
attr_meta = attrs.pop("Meta", None)
meta = attr_meta or getattr(new_class, "Meta", None)
new_class.add_to_class("_meta", Options(meta))
The Options class processes the Meta class
attributes and sets up default values for LDAP-specific configuration.
If a model is a subclass of another model, the Meta classes are combined in MRO (Method Resolution Order). This means that the Meta class for the subclass will have all the options from the parent class, plus any options that are defined or overridden in the subclass. This allows for inheritance of LDAP configuration while still allowing subclasses to customize specific options.
### 4. Field Registration
Each field attribute is registered with the model:
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
The add_to_class method calls contribute_to_class on field
objects, which registers them with the model’s metadata.
### 5. Model Preparation
Final setup occurs in the _prepare method:
def _prepare(cls) -> None:
opts = cls._meta
opts._prepare(cls)
# Add manager
manager = opts.manager_class()
cls.add_to_class("objects", manager)
# Send signal
class_prepared.send(sender=cls)
### 6. Signal Emission
The class_prepared signal is sent to notify
other parts of the system that the model class is ready.
Field Registration Process
When a field is added to a model, the following occurs:
Field Validation: The field’s
checkmethod validates its configurationMetadata Registration: The field is added to the model’s field list
Primary Key Setup: If the field is marked as primary_key, it’s set as the model’s pk
Attribute Mapping: Field names are mapped to LDAP attribute names
Example field registration:
class LDAPUser(Model):
uid = CharField('uid', primary_key=True, max_length=50)
cn = CharField('cn', max_length=100)
When this model is created:
The uid field is registered and marked as the primary key
The cn field is registered as a regular field
Both fields are added to the model’s field list
LDAP attribute mappings are created (uid → uid, cn → cn)
Meta Class Processing
The Meta class is processed by the Options class, which:
Sets Default Values: Establishes default values for all configurable options
Processes Meta Attributes: Extracts and validates Meta class attributes
Creates Mappings: Builds field-to-attribute and attribute-to-field mappings
Validates Configuration: Ensures required options are present
Example Meta processing:
class Meta:
ldap_server = 'default'
basedn = 'ou=users,dc=example,dc=com'
objectclass = 'person'
This Meta class would result in:
LDAP server configuration from settings.LDAP_SERVERS[‘default’]
Base DN for searches set to ‘ou=users,dc=example,dc=com’
Object class filtering for ‘person’ objects
Automatic addition of an objectclass field to the model
Comparison with Django’s Model Construction
The LDAP ORM model construction process closely mirrors Django’s approach, with some key differences:
### Similarities
Metaclass-based: Both use metaclasses to process class definitions
Field Registration: Both register fields through contribute_to_class
Meta Processing: Both process Meta classes into metadata objects
Manager Setup: Both create default managers
Signal System: Both emit class_prepared signals
### Key Differences
LDAP-Specific Options: LDAP ORM adds LDAP-specific configuration options
Attribute Mapping: LDAP ORM maps Python field names to LDAP attribute names
Object Class Handling: LDAP ORM automatically adds objectclass fields
Primary Key Requirements: LDAP ORM requires explicit primary key fields
No Database Migration: LDAP ORM doesn’t generate database migrations
### Django’s ModelBase vs LdapModelBase
Django’s ModelBase metaclass:
Processes database-specific options (db_table, indexes, etc.)
Handles model inheritance and proxy models
Sets up database connections and migrations
Manages model relationships (ForeignKey, ManyToMany, etc.)
LDAP ORM’s LdapModelBase metaclass:
Processes LDAP-specific options (basedn, objectclass, etc.)
Handles LDAP attribute mapping
Sets up LDAP connections and search filters
Manages LDAP-specific field types
Field Contribution Process
When a field is added to a model, it goes through the following process:
Field Initialization: The field’s __init__ method sets up basic attributes
Model Association: The field is associated with its model class
Validation: The field’s check method validates its configuration
Registration: The field is added to the model’s field list
Primary Key Setup: If applicable, the field is set as the primary key
Example field contribution:
def contribute_to_class(self, cls, name: str) -> None:
self.set_attributes_from_name(name)
self.model = cls
self.check()
cls._meta.add_field(self)
if self.choices:
setattr(cls, f"get_{self.name}_display",
partialmethod(cls._get_FIELD_display, field=self))
This process ensures that:
Field names are properly set
Fields are associated with their model
Field configuration is valid
Fields are registered in the model’s metadata
Choice fields get display methods
Manager Setup
After all fields are registered, the model’s manager is set up:
Manager Creation: An instance of the manager class is created
Model Association: The manager is associated with the model
Configuration: The manager is configured with model metadata
Attribute Assignment: The manager is assigned to the ‘objects’ attribute
The manager setup process:
def contribute_to_class(self, cls, accessor_name) -> None:
self.pk = cls._meta.pk.name
self.basedn = cls._meta.basedn
self.objectclass = cls._meta.objectclass
# ... other configuration
self.model = cls
cls._meta.base_manager = self
setattr(cls, accessor_name, self)
This ensures that the manager has access to all necessary model metadata for LDAP operations.
Conclusion
The LDAP ORM model construction process provides a Django-like interface while adapting to LDAP-specific requirements. The metaclass-based approach ensures that models are properly configured with all necessary metadata, field mappings, and manager setup before they’re used.
This design allows developers familiar with Django to work with LDAP data using familiar patterns, while the underlying implementation handles the complexities of LDAP attribute mapping and object class management.