Every hospital had its own forms. Every department its own fields. Every doctor their own preferences.

"Can we add a field for baby weight?" "Our department needs a different form for follow-ups." "This field should only be required if the patient is diabetic."

Tickets piled up. Modifying a consultation template: 2 hours of dev, tests, deployment. Multiply by dozens of requests per week. The technical team spent more time on form modifications than on real features.

I proposed a radical solution: give control to the business users.

The Engine Architecture

The system relies on three concepts: Templates, Fields, and Instances.

A Template defines a form's structure: general consultation, prenatal follow-up, laboratory results. It belongs to a specific hospital or to the shared catalogue.

Fields compose the template: text, number, dropdown, vital signs, Glasgow scale. Each field has validation rules, translations, display conditions.

An Instance is a filled form. Patient data. Values entered by the doctor. Linked to the template that generated it.

class Template(models.Model):
    name = models.CharField(max_length=255)
    medical_act_type = models.CharField(choices=MEDICAL_ACT_TYPES)
    hospital = models.ForeignKey(Hospital, null=True)  # null = shared catalogue
    version = models.IntegerField(default=1)
    is_active = models.BooleanField(default=True)

class TemplateField(models.Model):
    template = models.ForeignKey(Template, related_name='fields')
    name = models.CharField(max_length=255)  # Technical name
    label_fr = models.CharField(max_length=255)
    label_en = models.CharField(max_length=255)
    field_type = models.CharField(choices=FIELD_TYPES)
    required = models.BooleanField(default=False)
    step = models.IntegerField(default=1)  # For multi-step forms
    order = models.IntegerField(default=0)
    validation_rules = models.JSONField(null=True)
    conditional_rules = models.JSONField(null=True)

Field Types

The system supports over 30 field types. From basic to highly specialized.

Basic fields: text, textarea, number, decimal, date, datetime, checkbox, select, multiselect.

Medical fields: vital signs (one field = blood pressure, pulse, temperature, saturation), pain scale, Glasgow score, laboratory results with reference values.

Advanced fields: dynamic tables, repeatable groups (for multiple births), integrated appointment booking.

Each type has native validation. A vital_signs field automatically validates that systolic pressure is between 60 and 250 mmHg. A glasgow_coma_scale field calculates the total score from three components.

FIELD_TYPES = [
    ('text', 'Short text'),
    ('textarea', 'Long text'),
    ('number', 'Integer'),
    ('decimal', 'Decimal number'),
    ('date', 'Date'),
    ('select', 'Dropdown'),
    ('vital_signs', 'Vital signs'),
    ('glasgow_coma_scale', 'Glasgow Coma Scale'),
    ('lab_test_result', 'Laboratory result'),
    ('table', 'Dynamic table'),
    # ... 20+ other types
]

Conditional Logic

This is where the system becomes powerful. Fields can appear or become required based on other fields' values.

{
  "conditional_rules": {
    "visible_if": {
      "field": "is_diabetic",
      "operator": "equals",
      "value": true
    },
    "required_if": {
      "field": "glucose_level",
      "operator": "not_empty"
    }
  }
}

Supported operators: equals, not_equals, in, not_in, contains, not_empty, is_empty.

The frontend evaluates these rules in real-time. The "Glycemic monitoring" field only appears if "Diabetic patient" is checked. The backend validates the same rules on submission. Double verification.

Multi-Tenant Isolation

Three hospitals use the platform. Each has its own templates. But some templates are shared (common catalogue).

class Template(GlobalWithHospitalMixin):
    hospital = models.ForeignKey(Hospital, null=True, blank=True)
    
    def is_shared_template(self):
        return self.hospital is None
    
    def is_available_to_hospital(self, hospital):
        if self.is_shared_template():
            return True
        return self.hospital == hospital

A shared template (hospital=null) is visible to all. A specific template is only visible to its hospital.

Hospital administrators can clone a shared template to customize it. The clone belongs to their hospital. Modifications don't affect others.

Versioning

Templates evolve. But existing instances must remain consistent.

Each major modification creates a new version. The old version stays active for ongoing instances. New instances use the latest version.

def clone_to_new_version(self, user=None):
    new_template = Template.objects.create(
        name=self.name,
        medical_act_type=self.medical_act_type,
        version=self.version + 1,
        hospital=self.hospital,
        created_by=user
    )
    
    # Clone all fields
    for field in self.fields.all():
        TemplateField.objects.create(
            template=new_template,
            **field.get_clonable_data()
        )
    
    return new_template

The interface shows all versions, allows switching between them, activating an old version if needed.

The Admin Interface

Drag-and-drop is handled on the frontend with Vue and vue-draggable-plus. The administrator sees:

They can reorganize fields by drag-and-drop. Create new steps. Configure conditional rules via a visual form (no JSON to write).

A "JSON" tab allows advanced users to directly edit the structure. Useful for imports/exports or bulk modifications.

What This Changed

Before: every form modification required a ticket, a dev, a deployment.

After: the hospital administrator modifies the template, saves, it's in production.

Doctors no longer depend on the technical team to customize their tools. They experiment. They iterate. They adapt forms to their patients.


The Takeaway

For months, I coded forms by hand. Every modification was a ticket. Every hospital wanted variants. The technical team became a bottleneck.

The templating engine reversed the dynamic. Business users have control. Devs focus on real features. Workflows adapt to real field needs.

It's the no-code principle applied to business forms. No magic. Just a configuration system expressive enough to cover 95% of needs. The remaining 5% still go through code. And that's perfectly fine.