Follow zhongyi TCM system best practices and coding conventions. Use when writing new code, reviewing existing code, or ensuring consistency with project patterns. Helps maintain Django/DRF patterns, i18n, Bootstrap5 styling, and TCM-specific conventions.
Use this guide when writing or modifying code for the zhongyi Traditional Chinese Medicine (TCM) system to ensure consistency with established patterns.
The project follows a standard Django structure at /home/user/zhongyi-system/zhongyi_project/:
zhongyi_project/
├── zhongyi_project/ # Project settings
│ ├── settings.py
│ └── urls.py
├── accounts/ # User authentication
├── patients/ # Patient management
├── diagnosis/ # TCM diagnosis system
├── prescriptions/ # Herbal prescriptions
├── api/ # REST API layer
├── templates/ # All HTML templates
├── static/ # CSS, JS, images
└── locale/ # i18n translations
Every model must include:
from django.db import models
from django.utils.translation import gettext_lazy as _
class YourModel(models.Model):
# Always include timestamps
created_at = models.DateTimeField(_('Created At (创建时间)'), auto_now_add=True)
updated_at = models.DateTimeField(_('Updated At (更新时间)'), auto_now=True)
# Always include soft delete
is_active = models.BooleanField(_('Active (活跃)'), default=True)
class Meta:
verbose_name = _('Model Name (模型名称)')
verbose_name_plural = _('Model Names (模型名称)')
ordering = ['-updated_at']
def __str__(self):
return self.name # Always define __str__
Always use bilingual format for verbose names:
name = models.CharField(_('Name (名称)'), max_length=200)
Use TextChoices or IntegerChoices:
class Category(models.TextChoices):
GENERAL = 'general', _('General (全身)')
HEAD = 'head', _('Head & Face (头面)')
Reference User model correctly:
from django.conf import settings
assigned_practitioner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='model_assignments'
)
Always use these mixins for protected views:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class PractitionerRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
"""Require user to be practitioner or admin."""
def test_func(self):
return self.request.user.is_practitioner or self.request.user.is_admin
class YourModelListView(PractitionerRequiredMixin, ListView):
model = YourModel
template_name = 'yourapp/yourmodel_list.html'
context_object_name = 'items'
paginate_by = 20
def get_queryset(self):
queryset = YourModel.objects.filter(is_active=True)
# Filter by practitioner for non-admin users
if self.request.user.is_practitioner and not self.request.user.is_admin:
queryset = queryset.filter(assigned_practitioner=self.request.user)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(name__icontains=search) |
Q(code__icontains=search)
)
return queryset.order_by('-updated_at')
class YourModelCreateView(PractitionerRequiredMixin, CreateView):
model = YourModel
form_class = YourModelForm
template_name = 'yourapp/yourmodel_form.html'
success_url = reverse_lazy('yourapp:list')
def form_valid(self, form):
form.instance.assigned_practitioner = self.request.user
messages.success(self.request, _('Created successfully'))
return super().form_valid(form)
Never hard delete - use soft delete:
class YourModelDeleteView(PractitionerRequiredMixin, DeleteView):
model = YourModel
success_url = reverse_lazy('yourapp:list')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.is_active = False
self.object.save()
messages.success(request, _('Deleted successfully'))
return redirect(self.success_url)
All forms must use Bootstrap5 classes:
from django import forms
class YourModelForm(forms.ModelForm):
class Meta:
model = YourModel
fields = ('name', 'category', 'description', 'is_active')
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'category': forms.Select(attrs={'class': 'form-select'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
from django.urls import path
from . import views
app_name = 'yourapp'
urlpatterns = [
path('', views.YourModelListView.as_view(), name='list'),
path('create/', views.YourModelCreateView.as_view(), name='create'),
path('<int:pk>/', views.YourModelDetailView.as_view(), name='detail'),
path('<int:pk>/edit/', views.YourModelUpdateView.as_view(), name='update'),
path('<int:pk>/delete/', views.YourModelDeleteView.as_view(), name='delete'),
]
Create separate serializers for list and detail views:
from rest_framework import serializers
class YourModelListSerializer(serializers.ModelSerializer):
"""Lightweight serializer for list views."""
class Meta:
model = YourModel
fields = ['id', 'name', 'category', 'created_at']
class YourModelDetailSerializer(serializers.ModelSerializer):
"""Full serializer with nested relationships."""
assigned_practitioner = UserSerializer(read_only=True)
assigned_practitioner_id = serializers.PrimaryKeyRelatedField(
source='assigned_practitioner',
queryset=User.objects.filter(role='practitioner'),
write_only=True
)
class Meta:
model = YourModel
fields = '__all__'
read_only_fields = ['id', 'created_at', 'updated_at']
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.filters import SearchFilter, OrderingFilter
class YourModelViewSet(viewsets.ModelViewSet):
queryset = YourModel.objects.filter(is_active=True)
permission_classes = [IsAuthenticated, IsPractitionerOrAdmin]
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['name', 'code']
ordering_fields = ['created_at', 'name']
def get_serializer_class(self):
if self.action == 'list':
return YourModelListSerializer
return YourModelDetailSerializer
def get_queryset(self):
queryset = super().get_queryset()
if not self.request.user.is_admin:
queryset = queryset.filter(assigned_practitioner=self.request.user)
return queryset
@action(detail=False, methods=['get'])
def search(self, request):
"""Custom search endpoint."""
query = request.query_params.get('q', '')
results = self.get_queryset().filter(name__icontains=query)[:10]
serializer = self.get_serializer(results, many=True)
return Response(serializer.data)
from django.contrib import admin
@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'is_active', 'created_at')
list_filter = ('category', 'is_active', 'created_at')
search_fields = ('name', 'code', 'description')
readonly_fields = ('created_at', 'updated_at')
ordering = ('-updated_at',)
fieldsets = (
(_('Basic Information (基本信息)'), {
'fields': ('name', 'category', 'description')
}),
(_('System (系统)'), {
'fields': ('is_active', 'assigned_practitioner')
}),
(_('Timestamps (时间戳)'), {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
{% extends 'base.html' %}
{% load i18n crispy_forms_tags %}
{% block title %}Page Title{% endblock %}
{% block content %}
<div class="container my-4">
<!-- Content here -->
</div>
{% endblock %}
<div class="card"><table class="table table-hover"><button class="btn btn-primary">{% crispy form %}Use Bootstrap Icons: <i class="bi bi-icon-name"></i>
from django.utils.translation import gettext_lazy as _
verbose_name = _('English (中文)')
{% load i18n %}
{% trans "Text" %}
{% blocktrans %}Dynamic {{ variable }}{% endblocktrans %}
from rest_framework import permissions
class IsPractitionerOrAdmin(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated and (
request.user.is_practitioner or request.user.is_admin
)
The system has three roles:
admin - Full accesspractitioner - Access to assigned patients/recordspatient - Limited access to own recordsBefore committing code, verify:
created_at, updated_at, is_active fieldsPractitionerRequiredMixin)app_namepatients/models.pypatients/views.pypatients/forms.pypatients/admin.pyapi/serializers.pyapi/views.pypatients/urls.pytemplates/patients/