基于DRY原则复用Django多Model的CRUD实现方案咨询
Hey there! I’ve been in exactly this situation before—copy-pasting CRUD code for every model gets tedious fast, especially when you need to stick to a custom theme without relying on Django Admin. Let’s break down actionable ways to eliminate repetition across all your components:
1. Reusable View Base Classes
Django’s class-based views (CBVs) are perfect for this. Create generic base CRUD views that encapsulate all your custom logic (like theme-specific template paths, context data, permission checks, or breadcrumbs), then have each model’s views inherit from these bases.
Example Base Views:
# views/base.py from django.views.generic import ( ListView, CreateView, UpdateView, DeleteView ) from django.urls import reverse_lazy class BaseListView(ListView): # Common list view config template_name = "crud/generic_list.html" paginate_by = 20 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Add custom theme context (e.g., active sidebar menu) context["active_menu"] = self.model._meta.verbose_name_plural.lower() return context class BaseCreateView(CreateView): template_name = "crud/generic_form.html" def get_success_url(self): return reverse_lazy(f"{self.model._meta.model_name}-list") # Repeat similar base classes for UpdateView/DeleteView
Then Use for Specific Models:
# views/book.py from .base import BaseListView, BaseCreateView from .models import Book from .forms import BookForm class BookListView(BaseListView): model = Book class BookCreateView(BaseCreateView): model = Book form_class = BookForm
2. Generic Form Mixins or Factories
Avoid writing a full ModelForm for every model by creating a base mixin that adds common form behavior (like custom CSS classes, field exclusions, or validation).
Example Form Mixin:
# forms/base.py from django.forms import ModelForm class BaseModelFormMixin: # Common form attributes error_css_class = "is-invalid" required_css_class = "is-required" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add custom classes to all fields for field in self.fields.values(): field.widget.attrs["class"] = "form-control" # Specific model form class BookForm(BaseModelFormMixin, ModelForm): class Meta: model = Book fields = "__all__" # Or specify fields as needed
For even less repetition, you could write a form factory function that dynamically generates ModelForms with your base config:
def create_base_model_form(model, fields="__all__"): class BaseModelForm(BaseModelFormMixin, ModelForm): class Meta: model = model fields = fields return BaseModelForm # Usage: BookForm = create_base_model_form(Book)
3. Dynamic URL Routing
Instead of writing separate URL patterns for each model, create a reusable function that generates CRUD routes for any model and its views.
Example URL Factory:
# urls/crud.py from django.urls import path def generate_crud_urls(model, view_classes): """ view_classes should be a dict like: {"list": BookListView, "create": BookCreateView, ...} """ model_name = model._meta.model_name urlpatterns = [ path("", view_classes["list"].as_view(), name=f"{model_name}-list"), path("create/", view_classes["create"].as_view(), name=f"{model_name}-create"), path("<int:pk>/", view_classes["detail"].as_view(), name=f"{model_name}-detail"), path("<int:pk>/update/", view_classes["update"].as_view(), name=f"{model_name}-update"), path("<int:pk>/delete/", view_classes["delete"].as_view(), name=f"{model_name}-delete"), ] return urlpatterns
Use in Your Main URLs:
# urls.py from django.urls import include, path from .views.book import BookListView, BookCreateView, BookDetailView, BookUpdateView, BookDeleteView from .models import Book from .urls.crud import generate_crud_urls urlpatterns = [ # Other routes... path("books/", include(generate_crud_urls(Book, { "list": BookListView, "create": BookCreateView, "detail": BookDetailView, "update": BookUpdateView, "delete": BookDeleteView }))), # Repeat for other models ]
4. Reusable Template Components
Leverage Django’s template inheritance and include tags to create generic CRUD templates that work with any model, while letting you override specific parts for custom themes.
Example Generic List Template (crud/generic_list.html):
{% extends "base.html" %} {% load crud_tags %} {% block content %} <div class="card"> <div class="card-header"> <h1>{{ model_verbose_name_plural|title }}</h1> <a href="{% url model_name|add:'-create' %}" class="btn btn-primary">Add New</a> </div> <div class="card-body"> <table class="table table-striped"> <thead> <tr> <!-- Dynamically render table headers from model fields --> {% for field in list_fields %} <th>{{ field|title }}</th> {% endfor %} <th>Actions</th> </tr> </thead> <tbody> {% for object in object_list %} <tr> {% for field in list_fields %} <td>{{ object|get_attr:field }}</td> {% endfor %} <td> <a href="{% url model_name|add:'-detail' object.pk %}" class="btn btn-sm btn-info">View</a> <a href="{% url model_name|add:'-update' object.pk %}" class="btn btn-sm btn-warning">Edit</a> <a href="{% url model_name|add:'-delete' object.pk %}" class="btn btn-sm btn-danger">Delete</a> </td> </tr> {% endfor %} </tbody> </table> </div> </div> {% endblock %}
Tip: Add a Custom Template Filter for Field Access
To make dynamic field rendering work, create a filter to get an object’s attribute by name:
# templatetags/crud_tags.py from django import template register = template.Library() @register.filter def get_attr(obj, attr): return getattr(obj, attr)
Then in your base view, pass the list of fields to display:
class BaseListView(ListView): # ... list_fields = ["id", "name"] # Default fields; override in child views def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["model_name"] = self.model._meta.model_name context["model_verbose_name_plural"] = self.model._meta.verbose_name_plural context["list_fields"] = self.list_fields return context
Bonus: Advanced Reusability
- Mixins: Create mixins for cross-cutting concerns like
LoginRequiredMixin,PermissionRequiredMixin, or aSearchFilterMixinto add search functionality to any list view. - Context Processors: Add global theme-related data (like sidebar menus) to all templates via a context processor, so you don’t have to repeat it in every view.
内容的提问来源于stack exchange,提问作者Amaan Khan

