Healthcare applications require strict compliance. Medical booking systems handle sensitive data while managing complex scheduling. At ZIRA Software, we've built HIPAA-compliant Django applications for healthcare providers serving thousands of patients.
HIPAA Compliance Essentials
HIPAA requirements:
- Encryption at rest and in transit
- Access controls and audit logs
- Data backup and recovery
- Patient data privacy
- Secure authentication
Django security settings:
# settings.py
import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['medbook.zirasoftware.com']
# HTTPS enforcement
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Security headers
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# HSTS
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Database Schema
# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import RegexValidator
from encrypted_model_fields.fields import EncryptedCharField, EncryptedTextField
class User(AbstractUser):
USER_TYPE_CHOICES = (
('patient', 'Patient'),
('doctor', 'Doctor'),
('admin', 'Admin'),
)
user_type = models.CharField(max_length=10, choices=USER_TYPE_CHOICES)
phone = models.CharField(max_length=15)
class Patient(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
date_of_birth = models.DateField()
# Encrypted fields for HIPAA compliance
ssn = EncryptedCharField(max_length=11, blank=True)
insurance_number = EncryptedCharField(max_length=50, blank=True)
medical_history = EncryptedTextField(blank=True)
allergies = EncryptedTextField(blank=True)
current_medications = EncryptedTextField(blank=True)
emergency_contact_name = models.CharField(max_length=100)
emergency_contact_phone = models.CharField(max_length=15)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.user.get_full_name()}"
class Doctor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
specialization = models.CharField(max_length=100)
license_number = models.CharField(max_length=50, unique=True)
years_of_experience = models.IntegerField()
consultation_fee = models.DecimalField(max_digits=10, decimal_places=2)
bio = models.TextField()
qualifications = models.TextField()
def __str__(self):
return f"Dr. {self.user.get_full_name()}"
class DoctorAvailability(models.Model):
WEEKDAY_CHOICES = (
(0, 'Monday'),
(1, 'Tuesday'),
(2, 'Wednesday'),
(3, 'Thursday'),
(4, 'Friday'),
(5, 'Saturday'),
(6, 'Sunday'),
)
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE, related_name='availability')
weekday = models.IntegerField(choices=WEEKDAY_CHOICES)
start_time = models.TimeField()
end_time = models.TimeField()
slot_duration = models.IntegerField(default=30) # minutes
class Meta:
unique_together = ['doctor', 'weekday', 'start_time']
class Appointment(models.Model):
STATUS_CHOICES = (
('scheduled', 'Scheduled'),
('confirmed', 'Confirmed'),
('completed', 'Completed'),
('cancelled', 'Cancelled'),
('no_show', 'No Show'),
)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE, related_name='appointments')
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE, related_name='appointments')
appointment_date = models.DateField()
appointment_time = models.TimeField()
duration = models.IntegerField(default=30) # minutes
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='scheduled')
reason = models.TextField()
notes = EncryptedTextField(blank=True) # Doctor's notes
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['appointment_date', 'appointment_time']
unique_together = ['doctor', 'appointment_date', 'appointment_time']
def __str__(self):
return f"{self.patient} with {self.doctor} on {self.appointment_date}"
Availability Management
# services/availability.py
from datetime import datetime, timedelta
from django.db.models import Q
class AvailabilityService:
@staticmethod
def get_available_slots(doctor, date):
"""Get available time slots for a doctor on a specific date"""
weekday = date.weekday()
# Get doctor's availability for the day
availability = DoctorAvailability.objects.filter(
doctor=doctor,
weekday=weekday
).first()
if not availability:
return []
# Get existing appointments
existing_appointments = Appointment.objects.filter(
doctor=doctor,
appointment_date=date,
status__in=['scheduled', 'confirmed']
).values_list('appointment_time', 'duration')
# Generate all possible slots
slots = []
current_time = datetime.combine(date, availability.start_time)
end_time = datetime.combine(date, availability.end_time)
while current_time < end_time:
slot_time = current_time.time()
# Check if slot is available
is_available = True
for apt_time, apt_duration in existing_appointments:
apt_start = datetime.combine(date, apt_time)
apt_end = apt_start + timedelta(minutes=apt_duration)
slot_start = current_time
slot_end = current_time + timedelta(minutes=availability.slot_duration)
# Check for overlap
if slot_start < apt_end and slot_end > apt_start:
is_available = False
break
if is_available:
slots.append({
'time': slot_time,
'datetime': current_time,
'available': True
})
current_time += timedelta(minutes=availability.slot_duration)
return slots
@staticmethod
def book_appointment(patient, doctor, appointment_date, appointment_time, duration, reason):
"""Book an appointment with validation"""
# Check if slot is available
conflicting = Appointment.objects.filter(
doctor=doctor,
appointment_date=appointment_date,
appointment_time=appointment_time,
status__in=['scheduled', 'confirmed']
).exists()
if conflicting:
raise ValueError('This time slot is not available')
# Create appointment
appointment = Appointment.objects.create(
patient=patient,
doctor=doctor,
appointment_date=appointment_date,
appointment_time=appointment_time,
duration=duration,
reason=reason,
status='scheduled'
)
# Send notifications
send_appointment_confirmation(appointment)
return appointment
Appointment Booking View
# views.py
from django.views.generic import CreateView, ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404, redirect
from django.contrib import messages
class BookAppointmentView(LoginRequiredMixin, CreateView):
model = Appointment
template_name = 'appointments/book.html'
fields = ['doctor', 'appointment_date', 'appointment_time', 'reason']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
doctor_id = self.request.GET.get('doctor')
date = self.request.GET.get('date')
if doctor_id and date:
doctor = get_object_or_404(Doctor, id=doctor_id)
appointment_date = datetime.strptime(date, '%Y-%m-%d').date()
context['available_slots'] = AvailabilityService.get_available_slots(
doctor, appointment_date
)
context['selected_doctor'] = doctor
context['selected_date'] = appointment_date
context['doctors'] = Doctor.objects.all()
return context
def form_valid(self, form):
try:
appointment = AvailabilityService.book_appointment(
patient=self.request.user.patient,
doctor=form.cleaned_data['doctor'],
appointment_date=form.cleaned_data['appointment_date'],
appointment_time=form.cleaned_data['appointment_time'],
duration=30,
reason=form.cleaned_data['reason']
)
messages.success(self.request, 'Appointment booked successfully!')
return redirect('appointment_detail', pk=appointment.pk)
except ValueError as e:
messages.error(self.request, str(e))
return self.form_invalid(form)
class PatientAppointmentsView(LoginRequiredMixin, ListView):
model = Appointment
template_name = 'appointments/patient_list.html'
context_object_name = 'appointments'
paginate_by = 10
def get_queryset(self):
return Appointment.objects.filter(
patient=self.request.user.patient
).select_related('doctor', 'doctor__user')
Notifications
# notifications.py
from django.core.mail import send_mail
from django.template.loader import render_to_string
from twilio.rest import Client
import os
def send_appointment_confirmation(appointment):
"""Send email and SMS confirmation"""
# Email
subject = f'Appointment Confirmation - {appointment.appointment_date}'
html_message = render_to_string('emails/appointment_confirmation.html', {
'appointment': appointment,
'patient': appointment.patient,
'doctor': appointment.doctor,
})
send_mail(
subject,
'', # Plain text (optional)
'contact@zirasoftware.com',
[appointment.patient.user.email],
html_message=html_message,
)
# SMS (using Twilio)
client = Client(
os.environ.get('TWILIO_ACCOUNT_SID'),
os.environ.get('TWILIO_AUTH_TOKEN')
)
message = client.messages.create(
body=f'Appointment confirmed with {appointment.doctor} on {appointment.appointment_date} at {appointment.appointment_time}',
from_=os.environ.get('TWILIO_PHONE_NUMBER'),
to=appointment.patient.user.phone
)
def send_appointment_reminder(appointment):
"""Send reminder 24 hours before appointment"""
# Implementation similar to confirmation
pass
Audit Logging
# middleware.py
import logging
from django.utils.deprecation import MiddlewareMixin
audit_logger = logging.getLogger('audit')
class AuditMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if request.user.is_authenticated:
audit_logger.info(
f'User: {request.user.username} | '
f'Action: {request.method} {request.path} | '
f'IP: {self.get_client_ip(request)} | '
f'Status: {response.status_code}'
)
return response
def get_client_ip(self, request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
API for Mobile App
# api/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class AppointmentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentSerializer
def get_queryset(self):
return Appointment.objects.filter(
patient=self.request.user.patient
)
@action(detail=False, methods=['get'])
def available_slots(self, request):
doctor_id = request.query_params.get('doctor')
date = request.query_params.get('date')
if not doctor_id or not date:
return Response(
{'error': 'Doctor and date required'},
status=status.HTTP_400_BAD_REQUEST
)
doctor = Doctor.objects.get(id=doctor_id)
appointment_date = datetime.strptime(date, '%Y-%m-%d').date()
slots = AvailabilityService.get_available_slots(doctor, appointment_date)
return Response({'slots': slots})
Conclusion
Medical booking systems require careful attention to security and compliance. Django's encrypted fields, audit logging, and robust authentication make it suitable for healthcare applications.
Building healthcare applications? Contact ZIRA Software for HIPAA-compliant development.