You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django抽象基类模型场景下URL、视图与模板的正确使用及优化方法

Django重复视图与路由的DRY优化方案

问题背景

现有Django项目中Contact类模型继承自同一基类,对应的视图、路由代码重复度极高,违反DRY原则,使用版本为Django 3.2.9 + Python 3.9.6。
原有代码结构如下:

模型代码(models/contact.py)

class BaseContact(AbstractUser):
    other_name = models.CharField(max_length=50, null=True, blank=True)
    contact_number = models.CharField(max_length=10, null=True, blank=True)
    email = models.EmailField(unique=True)
    postal_address = models.ForeignKey(location.Address, on_delete=models.CASCADE, null=True, blank=True)
    tax_id = models.CharField(max_length=11, null=True, blank=True)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name']

    class Meta:
        default_related_name = 'base'
        verbose_name = 'Contact'
        verbose_name_plural = 'Contacts'

    def get_absolute_url(self):
        return reverse("%s:detail-%s" % (str(type(self).__name__.lower()), str(type(self).__name__.lower())),
                       kwargs={"pk": self.pk})


class Customer(BaseContact):
    class Meta:
        default_related_name = 'customer'


class Supplier(BaseContact):
    class Meta:
        default_related_name = 'supplier'


class Employee(BaseContact):
    class Meta:
        default_related_name = 'employee'
        verbose_name = 'Employee'
        verbose_name_plural = 'Employees'

原有路由代码(urls/contact.py)

from django.urls import path
from main.views.contact import UpdateEmployeeView, DetailEmployeeView, CreateEmployeeView, DeleteEmployeeView
from main.views.contact import UpdateSupplierView, DetailSupplierView, CreateSupplierView, DeleteSupplierView
from main.views.contact import UpdateCustomerView, DetailCustomerView, CreateCustomerView, DeleteCustomerView

urlpatterns = [
    path('employee/create/', CreateEmployeeView.as_view(), name='create-employee'),
    path('employee/detail/<int:pk>', DetailEmployeeView.as_view(), name='detail-employee'),
    path('employee/update/<int:pk>', UpdateEmployeeView.as_view(), name='update-employee'),
    path('employee/delete/<int:pk>', DeleteEmployeeView.as_view(), name='delete-employee'),

    path('supplier/create/', CreateSupplierView.as_view(), name='create-supplier'),
    path('supplier/detail/<int:pk>', DetailSupplierView.as_view(), name='detail-supplier'),
    path('supplier/update/<int:pk>', UpdateSupplierView.as_view(), name='update-supplier'),
    path('supplier/delete/<int:pk>', DeleteSupplierView.as_view(), name='delete-supplier'),

    path('customer/create/', CreateCustomerView.as_view(), name='create-customer'),
    path('customer/detail/<int:pk>', DetailCustomerView.as_view(), name='detail-customer'),
    path('customer/update/<int:pk>', UpdateCustomerView.as_view(), name='update-customer'),
    path('customer/delete/<int:pk>', DeleteCustomerView.as_view(), name='delete-customer'),
]

原有视图代码(views/contact.py)

class CreateEmployeeView(CreateView):
    model = Employee
    template_name_suffix = '_create_form'
    fields = ['__all__']


class DetailEmployeeView(DetailView):
    model = Employee


class UpdateEmployeeView(UpdateView):
    model = Employee
    form_class = UpdateEmployeeForm
    template_name_suffix = '_update_form'


class DeleteEmployeeView(DeleteView):
    model = Employee
    success_url = reverse_lazy('home')


class CreateSupplierView(CreateView):
    model = Supplier
    template_name_suffix = '_create_form'
    fields = ['__all__']


class DetailSupplierView(DetailView):
    model = Supplier


class UpdateSupplierView(UpdateView):
    model = Supplier
    form_class = UpdateSupplierForm
    template_name_suffix = '_update_form'


class DeleteSupplierView(DeleteView):
    model = Supplier
    success_url = reverse_lazy('home')


class CreateCustomerView(CreateView):
    model = Customer
    template_name_suffix = '_create_form'
    fields = ['__all__']


class DetailCustomerView(DetailView):
    model = Customer


class UpdateCustomerView(UpdateView):
    model = Customer
    form_class = UpdateCustomerForm
    template_name_suffix = '_update_form'


class DeleteCustomerView(DeleteView):
    model = Customer
    success_url = reverse_lazy('home')

优化方案

第一步:优化视图层代码

通过封装基础通用视图类,消除重复的属性和方法定义:

# 优化后的views/contact.py代码
from django.views.generic import CreateView, DetailView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Employee, Supplier, Customer
from .forms import UpdateEmployeeForm, UpdateSupplierForm, UpdateCustomerForm

# 封装通用基础视图
class BaseContactCreateView(CreateView):
    template_name_suffix = '_create_form'
    fields = '__all__'

class BaseContactDetailView(DetailView):
    pass

class BaseContactUpdateView(UpdateView):
    template_name_suffix = '_update_form'

class BaseContactDeleteView(DeleteView):
    success_url = reverse_lazy('home')

# 各模型对应视图仅需指定model即可,无需重复定义属性
class CreateEmployeeView(BaseContactCreateView): model = Employee
class DetailEmployeeView(BaseContactDetailView): model = Employee
class UpdateEmployeeView(BaseContactUpdateView): 
    model = Employee
    form_class = UpdateEmployeeForm
class DeleteEmployeeView(BaseContactDeleteView): model = Employee

class CreateSupplierView(BaseContactCreateView): model = Supplier
class DetailSupplierView(BaseContactDetailView): model = Supplier
class UpdateSupplierView(BaseContactUpdateView): 
    model = Supplier
    form_class = UpdateSupplierForm
class DeleteSupplierView(BaseContactDeleteView): model = Supplier

class CreateCustomerView(BaseContactCreateView): model = Customer
class DetailCustomerView(BaseContactDetailView): model = Customer
class UpdateCustomerView(BaseContactUpdateView): 
    model = Customer
    form_class = UpdateCustomerForm
class DeleteCustomerView(BaseContactDeleteView): model = Customer

如果Form类命名严格遵循Update{模型名}Form的规则,还可以进一步简化Update视图的form_class定义,通过动态获取省略每个子类的form_class指定:

class BaseContactUpdateView(UpdateView):
    template_name_suffix = '_update_form'
    def get_form_class(self):
        return globals()[f'Update{self.model.__name__}Form']

# 后续Update子类无需再指定form_class
class UpdateEmployeeView(BaseContactUpdateView): model = Employee
class UpdateSupplierView(BaseContactUpdateView): model = Supplier
class UpdateCustomerView(BaseContactUpdateView): model = Customer

第二步:优化路由层代码

通过循环动态生成路由,无需手动编写12条重复的path规则:

# 优化后的urls/contact.py代码
from django.urls import path
from main.views.contact import *

CONTACT_TYPES = ['employee', 'supplier', 'customer']
# 配置操作映射:路由后缀、视图类前缀、路由name前缀
OPERATIONS = [
    ('create/', 'Create', 'create'),
    ('detail/<int:pk>', 'Detail', 'detail'),
    ('update/<int:pk>', 'Update', 'update'),
    ('delete/<int:pk>', 'Delete', 'delete'),
]

urlpatterns = []
for contact_type in CONTACT_TYPES:
    for path_suffix, view_prefix, name_prefix in OPERATIONS:
        view_cls = globals()[f'{view_prefix}{contact_type.capitalize()}View']
        urlpatterns.append(
            path(f'{contact_type}/{path_suffix}', view_cls.as_view(), name=f'{name_prefix}-{contact_type}')
        )

模板适配

原有模板命名规则完全适配优化后的代码,无需修改,模板后缀遵循{模型名}_{操作}_form.html的约定即可正常渲染。

补充:模型方法优化

原有get_absolute_url方法可以改用f-string简化写法,可读性更强:

def get_absolute_url(self):
    model_name = type(self).__name__.lower()
    return reverse(f"{model_name}:detail-{model_name}", kwargs={"pk": self.pk})

后续扩展

如果后续新增其他继承自BaseContact的模型,仅需完成3步即可:

  1. 在models中定义新的模型子类
  2. 在views中新增4行对应视图类(仅指定model)
  3. 在urls的CONTACT_TYPES列表中新增模型小写名称
    无需重复编写大量重复逻辑。

内容的提问来源于stack exchange,提问作者Rishu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 15:06:03