请求讲解自定义Django模型字段基础及OrderField示例解析
Hey there! I totally get how frustrating it is when you're trying to wrap your head around a complex Django feature and can't find straightforward explanations outside the official docs. Let's break down this custom OrderField from scratch—no prior custom field experience required.
First, Why Do We Need a Custom Field?
Django gives you tons of built-in fields like PositiveIntegerField, but sometimes you need logic that those fields don't handle out of the box. This OrderField solves a specific problem: automatically generating sequential numbers for model instances, even grouping them by other fields (like assigning order numbers to modules within a course, where each course starts counting from 0).
Let's Walk Through the Code
1. The Class Definition
class OrderField(models.PositiveIntegerField):
This line means our OrderField inherits all the behavior of Django's PositiveIntegerField—so it stores non-negative integers, works with forms, and integrates with the database just like the built-in field. We're just adding extra logic on top.
2. The __init__ Method
def __init__(self, for_fields=None, *args, **kwargs): self.for_fields = for_fields super(OrderField, self).__init__(*args, **kwargs)
for_fields=None: This is our custom parameter. It lets us specify which fields to "group" by. For example, if we passfor_fields=['course'], the order numbers will reset to 0 for each new course.self.for_fields = for_fields: We store this parameter so we can use it later in the field logic.super(...): We call the parent class's__init__method to make sure all the standardPositiveIntegerFieldsetup happens (like handlingnull,blank, or other arguments you might pass when defining the field in a model).
3. The pre_save Method (The Magic Happens Here)
Django calls pre_save right before saving a model instance to the database. This is where we generate the automatic order number.
Let's break it down line by line:
def pre_save(self, model_instance, add): if getattr(model_instance, self.attname) is None: # no current value try: qs = self.model.objects.all() if self.for_fields: # filter by objects with the same field values for the fields in "for_fields" query = {field: getattr(model_instance, field) for field in self.for_fields} qs = qs.filter(**query) # get the order of the last item last_item = qs.latest(self.attname) value = last_item.order + 1 except ObjectDoesNotExist: value = 0 setattr(model_instance, self.attname, value) return value else: return super(OrderField, self).pre_save(model_instance, add)
Step 1: Check if the field has no value yet
if getattr(model_instance, self.attname) is None:self.attname is the name of the field as it exists on the model (e.g., if your model has order = OrderField(...), attname is 'order'). We're checking if the instance doesn't already have an order number set.
Step 2: Get relevant instances
qs = self.model.objects.all(): We start with all instances of the current model (e.g., allCourseModuleinstances).- If
for_fieldsis set (like['course']), we filter the queryset to only include instances that match the current instance's values for those fields. For example, if the current module belongs to "Django 101", we only get other modules in "Django 101".- The dictionary
query = {field: getattr(model_instance, field) ...}builds the filter arguments dynamically. qs = qs.filter(**query)applies that filter to the queryset.
- The dictionary
Step 3: Generate the new order number
last_item = qs.latest(self.attname): We get the instance with the highest existing order number in our filtered queryset.value = last_item.order + 1: The new order number is one higher than the last one.except ObjectDoesNotExist: If there are no existing instances (this is the first one), we setvalue = 0.
Step 4: Assign the value to the instance
setattr(model_instance, self.attname, value): We add the generated order number to the model instance before it's saved to the database.
Step 5: If the field already has a value
If the instance already has an order number (e.g., we're updating an existing record), we just call the parent class's pre_save method to handle it normally.
A Quick Example to Make It Concrete
Suppose we have this model:
class Course(models.Model): name = models.CharField(max_length=100) class CourseModule(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE) title = models.CharField(max_length=100) order = OrderField(for_fields=['course'])
- When we create the first
CourseModulefor "Django 101", itsorderwill be 0. - The second module for "Django 101" gets
order = 1. - The first module for "Python Basics" gets
order = 0(since it's a new course group).
Perfect—each course's modules have their own sequential order numbers, generated automatically!
内容的提问来源于stack exchange,提问作者Mohamed Samir

