10 KiB
Tenant Settings Templates Implementation
Overview
This document describes the implementation of the tenant settings templates system, which allows administrators to configure tenant-specific settings through a structured, validated interface.
Architecture
Models
1. SettingTemplate
Defines the structure and validation rules for available settings.
Key Fields:
key: Unique identifier (e.g., 'basic_clinic_name_en')category: Organizes settings into logical groupsdata_type: Defines the type of value (STRING, INTEGER, BOOLEAN, CHOICE, FILE, ENCRYPTED, etc.)is_required: Whether the setting must be providedvalidation_regex: Optional regex pattern for validationchoices: For CHOICE type, defines available optionsorder: Display order within category
Categories:
- BASIC: Basic clinic information (name, logo, colors)
- VAT: VAT registration details
- ADDRESS: Physical address information
- ZATCA: ZATCA e-invoicing configuration
- NPHIES: NPHIES integration settings
- SMS: SMS/WhatsApp integration
- LAB: Laboratory integration
- RADIOLOGY: Radiology integration
2. TenantSetting
Stores actual setting values for each tenant.
Key Fields:
tenant: Foreign key to Tenanttemplate: Foreign key to SettingTemplatevalue: Text field for most data typesencrypted_value: Binary field for sensitive datafile_value: File field for uploadsupdated_by: Tracks who made the change
Service Layer
TenantSettingsService
Located in core/settings_service.py, provides:
Key Methods:
get_setting(key, default): Retrieve a setting with type conversionset_setting(key, value, user): Save a setting with validationget_category_settings(category): Get all settings in a categoryvalidate_required_settings(): Check if all required settings are setget_missing_required_settings(): List missing required settingsexport_settings(): Export settings as JSONimport_settings(data, user): Import settings from JSON
Features:
- Type-safe value retrieval
- Automatic type conversion
- Validation against template rules
- Encryption/decryption for sensitive data
- Caching for performance (5-minute TTL)
- Audit trail via updated_by field
Admin Interface
Enhanced TenantAdmin
The Tenant admin interface now includes:
- Settings Status Indicator: Shows completion status in list view
- Inline Settings Editor: Edit settings directly on tenant page
- Visual Indicators: Color-coded required vs optional fields
- Help Text: Inline guidance for each setting
- Settings Status Detail: Detailed view of missing required settings
SettingTemplateAdmin
Manage setting templates:
- Create/edit setting definitions
- Set validation rules
- Define choices for dropdown fields
- Control display order
TenantSettingAdmin
View and manage individual setting values:
- Filter by category and tenant
- Search by setting name or value
- View audit history
- Masked display for encrypted values
Usage
For Administrators
Configuring Tenant Settings
- Navigate to Django Admin → Core → Tenants
- Select a tenant to edit
- Scroll to the "Tenant Settings" inline section
- Add/edit settings as needed
- Required settings are marked with visual indicators
- Save the tenant
Checking Settings Status
The tenant list view shows a "Settings Status" column:
- ✓ Complete: All required settings configured
- ✗ X Missing: Number of missing required settings
For Developers
Retrieving Settings
from core.settings_service import get_tenant_settings_service
# Get service for a tenant
service = get_tenant_settings_service(tenant)
# Get a single setting
clinic_name = service.get_setting('basic_clinic_name_en')
vat_number = service.get_setting('vat_registration_number')
# Get with default value
logo = service.get_setting('basic_logo', default=None)
# Get all settings in a category
basic_settings = service.get_category_settings('BASIC')
zatca_settings = service.get_category_settings('ZATCA')
# Get all settings
all_settings = service.get_all_settings()
Setting Values
from core.settings_service import get_tenant_settings_service
service = get_tenant_settings_service(tenant)
# Set a setting
service.set_setting('basic_clinic_name_en', 'Agdar Centre', user=request.user)
# Set encrypted value
service.set_setting('zatca_otp', 'secret123', user=request.user)
# Set boolean
service.set_setting('sms_enable_notifications', True, user=request.user)
Validation
# Check if all required settings are configured
is_valid = service.validate_required_settings()
# Get list of missing required settings
missing = service.get_missing_required_settings()
for template in missing:
print(f"Missing: {template.label_en} ({template.get_category_display()})")
Export/Import
# Export settings
settings_dict = service.export_settings()
# Import settings
count = service.import_settings(settings_dict, user=request.user)
print(f"Imported {count} settings")
Setting Templates
Current Templates (40 total)
Basic Information (6)
- Clinic Name (English) - Required
- Clinic Name (Arabic)
- Clinic Code - Required
- Clinic Logo
- Primary Brand Color
- Secondary Brand Color
VAT Registration (3)
- VAT Registration Number - Required
- Tax ID
- Commercial Registration Number
Address Information (6)
- Street Address - Required
- Building Number
- City - Required
- Postal Code - Required
- Country Code - Required
- Additional Number
ZATCA E-Invoicing (7)
- ZATCA Environment - Required
- ZATCA OTP - Required
- CSID (auto-generated)
- Certificate (auto-generated)
- Private Key (auto-generated)
- Device Name
- Solution Name
NPHIES Integration (5)
- NPHIES Environment - Required
- Client ID - Required
- Client Secret - Required (encrypted)
- Organization License Number - Required
- Provider License Number
SMS/WhatsApp Integration (7)
- SMS Provider - Required
- Account SID - Required
- Auth Token - Required (encrypted)
- SMS Phone Number - Required
- WhatsApp Number
- Enable SMS Notifications
- Enable WhatsApp Notifications
Lab Integration (3)
- Lab API URL
- Lab API Key (encrypted)
- Enable Lab Integration
Radiology Integration (3)
- Radiology API URL
- Radiology API Key (encrypted)
- Enable Radiology Integration
Management Commands
populate_setting_templates
Populates or updates setting templates with default definitions.
python manage.py populate_setting_templates
This command:
- Creates new templates if they don't exist
- Updates existing templates with new definitions
- Is idempotent (safe to run multiple times)
Security
Encrypted Fields
Sensitive data (API keys, tokens, passwords) are stored encrypted:
- Uses Fernet symmetric encryption
- Encryption key derived from Django SECRET_KEY
- Encrypted values stored in
encrypted_valuebinary field - Displayed as "ENCRYPTED" in admin interface
Audit Trail
All setting changes are tracked:
updated_by: User who made the changeupdated_at: Timestamp of change- Historical records via django-simple-history
Performance
Caching
Settings are cached for 5 minutes to reduce database queries:
- Cache key format:
tenant_setting:{tenant_id}:{setting_key} - Automatic cache invalidation on update
- Manual cache clearing:
service.clear_cache()
Database Indexes
Optimized queries with indexes on:
tenant+template(unique together)template.category+template.ordertemplate.key
Migration Path
From Legacy JSON Settings
The old Tenant.settings JSONField is preserved for backward compatibility:
- Marked as "Legacy Settings (Deprecated)" in admin
- Collapsed by default
- Can be migrated to new system using import/export
Migration Steps
- Export existing settings from JSON field
- Map to new setting keys
- Import using
service.import_settings() - Verify all settings migrated correctly
- Remove old JSON field in future migration
Extending the System
Adding New Settings
- Add template definition to
populate_setting_templates.py - Run
python manage.py populate_setting_templates - New setting appears in admin interface
Adding New Categories
- Add category to
SettingTemplate.Categorychoices incore/models.py - Create migration:
python manage.py makemigrations - Run migration:
python manage.py migrate - Add templates for new category
Custom Validation
Add custom validation in TenantSettingsService._validate_value():
elif template.data_type == SettingTemplate.DataType.CUSTOM:
# Add custom validation logic
if not custom_validator(value):
raise ValidationError("Custom validation failed")
Testing
Unit Tests
Test the service layer:
from django.test import TestCase
from core.models import Tenant, SettingTemplate
from core.settings_service import get_tenant_settings_service
class TenantSettingsServiceTest(TestCase):
def setUp(self):
self.tenant = Tenant.objects.create(name="Test Clinic", code="TEST")
self.service = get_tenant_settings_service(self.tenant)
def test_get_setting(self):
value = self.service.get_setting('basic_clinic_name_en', 'Default')
self.assertEqual(value, 'Default')
def test_set_setting(self):
self.service.set_setting('basic_clinic_name_en', 'Test Clinic')
value = self.service.get_setting('basic_clinic_name_en')
self.assertEqual(value, 'Test Clinic')
Troubleshooting
Common Issues
Issue: Settings not appearing in admin
- Solution: Run
python manage.py populate_setting_templates
Issue: Validation errors when saving
- Solution: Check validation_regex in template definition
Issue: Encrypted values not decrypting
- Solution: Ensure SECRET_KEY hasn't changed
Issue: Cache not updating
- Solution: Call
service.clear_cache()or wait 5 minutes
Future Enhancements
Potential improvements:
- Settings versioning and rollback
- Settings templates per tenant type
- Conditional settings (show/hide based on other settings)
- Settings import/export via admin UI
- Settings validation dashboard
- Multi-language support for all help text
- Settings change notifications
- Bulk settings update across tenants
Support
For issues or questions:
- Check this documentation
- Review code comments in
core/models.pyandcore/settings_service.py - Check Django admin for validation errors
- Review audit logs for setting changes