Customization¶
This guide covers advanced customization: custom querysets and managers, custom form fields, base class configuration, database triggers, and performance tips.
Custom querysets and managers¶
Extend the package’s querysets and managers to add domain-specific query methods.
Custom queryset¶
from django_tenant_options.models import OptionQuerySet, OptionManager
class PriorityOptionQuerySet(OptionQuerySet):
def critical_only(self):
return self.active().filter(name__icontains="critical")
def active_priority(self):
return self.active().filter(priority_weight__gt=0)
Custom manager¶
class PriorityOptionManager(OptionManager):
def get_queryset(self):
return PriorityOptionQuerySet(self.model, using=self._db)
Using them in your model¶
class TaskPriorityOption(AbstractOption):
objects = PriorityOptionManager()
priority_weight = models.IntegerField(default=0)
tenant_model = "yourapp.Tenant"
selection_model = "yourapp.TaskPrioritySelection"
# ...
Important
Custom managers must inherit from OptionManager (for Option models) or SelectionManager (for Selection models). Custom querysets must inherit from OptionQuerySet or SelectionQuerySet. System checks will flag incorrect inheritance.
Custom form fields¶
Customize how options appear in SelectionsForm by subclassing OptionsModelMultipleChoiceField:
from django_tenant_options.form_fields import OptionsModelMultipleChoiceField
class CustomOptionsField(OptionsModelMultipleChoiceField):
def label_from_instance(self, obj):
label = obj.name
if obj.option_type == "dm":
label += " [Required]"
elif obj.option_type == "do":
label += " [Available]"
else:
label += " [Custom]"
return label
Configure globally in settings:
DJANGO_TENANT_OPTIONS = {
"DEFAULT_MULTIPLE_CHOICE_FIELD": "yourapp.forms.CustomOptionsField",
}
Or set it per-form by overriding multiple_choice_field_class before the SelectionsForm initializes its fields.
Base class configuration¶
The package allows you to replace the default Django base classes used internally. This is useful when integrating with packages like django-auto-prefetch that provide optimized base classes.
Configuration through settings¶
import auto_prefetch
DJANGO_TENANT_OPTIONS = {
"MODEL_CLASS": auto_prefetch.Model, # Default: django.db.models.Model
"MANAGER_CLASS": "auto_prefetch.Manager", # Default: django.db.models.Manager
"QUERYSET_CLASS": "auto_prefetch.QuerySet", # Default: django.db.models.QuerySet
"FOREIGNKEY_CLASS": auto_prefetch.ForeignKey, # Default: django.db.models.ForeignKey
"ONETOONEFIELD_CLASS": "auto_prefetch.OneToOneField", # Default: django.db.models.OneToOneField
}
Values can be actual class references or dotted string paths.
Programmatic configuration¶
For more granular control:
from django_tenant_options.app_settings import model_config
model_config.model_class = "auto_prefetch.Model"
model_config.manager_class = auto_prefetch.Manager
model_config.queryset_class = auto_prefetch.QuerySet
model_config.foreignkey_class = auto_prefetch.ForeignKey
model_config.onetoonefield_class = "auto_prefetch.OneToOneField"
Meta class with custom base models¶
When using a custom base model class that has its own Meta, combine both in your model’s Meta:
class TaskPriorityOption(AbstractOption):
class Meta(AbstractOption.Meta, auto_prefetch.Model.Meta):
verbose_name = "Task Priority Option"
Database triggers¶
Database triggers provide an additional layer of referential integrity enforcement at the database level.
What they protect¶
Triggers ensure that a Selection’s tenant always matches the Option’s tenant (for custom options) or that the Option is a default option. This prevents mismatches that could occur from:
Direct SQL operations bypassing Django’s ORM
Race conditions in concurrent operations
Bugs in custom code that skip model validation
Supported databases¶
Database |
Support |
|---|---|
PostgreSQL |
Full |
MySQL |
Full |
SQLite |
Full |
Oracle |
Full |
Custom database backends¶
If you use a custom backend (e.g., PostGIS) built on a supported database, override the vendor detection:
DJANGO_TENANT_OPTIONS = {
"DB_VENDOR_OVERRIDE": "postgresql",
}
Or pass it per-command:
python manage.py maketriggers --db-vendor-override postgresql
Regenerating triggers¶
If you change your model structure, regenerate triggers:
python manage.py maketriggers --force
python manage.py migrate
Performance tips¶
Cache frequently accessed options¶
Options don’t change often. Cache them to avoid repeated database queries:
from django.core.cache import cache
def get_tenant_options(tenant):
cache_key = f"tenant_{tenant.id}_options"
options = cache.get(cache_key)
if options is None:
options = list(TaskPriorityOption.objects.selected_options_for_tenant(tenant))
cache.set(cache_key, options, timeout=3600)
return options
Remember to invalidate the cache when selections change (e.g., in a post-save signal or after SelectionsForm.save()).
Consider auto_prefetch¶
If your application has many ForeignKey lookups, the django-auto-prefetch package can automatically optimize queries. Configure it using the base class settings described above.
Further reading¶
Models Guide – Foundational model concepts
Configuration Reference – All available settings
Commands – The
maketriggersandremovetriggerscommands