Django创建Listing实例时自动填充关联ListingDetails字段
Django创建Listing实例时自动生成关联ListingDetails的实现方案
先修正原模型的已知问题
原模型代码存在几处运行时会报错的问题,先调整:
ImageField必须指定upload_to参数定义文件上传存储路径,同时依赖Pillow库,需提前执行pip install pillow安装- 两个外键字段的
related_name不能重复,否则反向关联时会出现映射冲突 - 外键字段不需要手动加
_id后缀,Django会自动为外键生成带_id后缀的数据库列,手动添加会导致列名变成listing_id_id这类冗余命名 created_at字段的default不要加括号调用timezone.now(),否则会在模型类加载时就固定时间值,不会取实例创建时的当前时间,正确写法是传函数引用timezone.now
修正后的基础模型代码参考:
from django.db import models from django.utils import timezone from django.contrib.auth.models import AbstractUser class User(AbstractUser): pass class Listing(models.Model): title = models.CharField(max_length=32) details = models.CharField(max_length=300, blank=True) price = models.DecimalField(max_digits=7, decimal_places=2) photo = models.ImageField(upload_to="listing_photos/") class ListingDetails(models.Model): listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="detail", unique=True) owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="owned_listings") created_at = models.DateTimeField(default=timezone.now)
实现方式1:使用post_save信号(推荐,解耦性好)
Django内置的post_save信号会在模型实例保存到数据库后自动触发,适合做这类关联数据自动创建的逻辑。
- 先修改对应app的
apps.py,导入信号模块确保注册生效:from django.apps import AppConfig class AuctionConfig(AppConfig): # 替换成你自己的app配置类名 default_auto_field = "django.db.models.BigAutoField" name = "auction" # 替换成你的app名称 def ready(self): from . import signals - 在app目录下新建
signals.py文件,编写自动创建逻辑:from django.db.models.signals import post_save from django.dispatch import receiver from .models import Listing, ListingDetails @receiver(post_save, sender=Listing) def auto_create_listing_detail(sender, instance, created, **kwargs): # 仅在Listing首次创建时生成对应详情,更新时不重复创建 if created: ListingDetails.objects.create( listing=instance, # 从实例上取提前挂载的创建用户 owner=instance._create_user ) - 在创建Listing的位置(视图、接口等),提前把当前登录用户挂载到实例上即可:
以普通函数视图为例:from django.shortcuts import render, redirect from .forms import ListingForm def create_listing(request): if request.method == "POST": form = ListingForm(request.POST, request.FILES) if form.is_valid(): listing = form.save(commit=False) # 把当前登录用户挂载到实例,供信号读取 listing._create_user = request.user listing.save() return redirect("listing_list") else: form = ListingForm() return render(request, "create_listing.html", {"form": form})
实现方式2:重写Listing模型的save方法
小项目可以直接重写模型的save方法,逻辑更直观,不需要额外配置信号:
修改Listing模型的save方法:
class Listing(models.Model): title = models.CharField(max_length=32) details = models.CharField(max_length=300, blank=True) price = models.DecimalField(max_digits=7, decimal_places=2) photo = models.ImageField(upload_to="listing_photos/") def save(self, *args, **kwargs): # 判断是否为首次创建:无主键说明还未存入数据库 is_new = self.pk is None # 把owner参数从kwargs中提取,避免传给父类save方法报错 create_user = kwargs.pop("owner", None) super().save(*args, **kwargs) # 首次创建且传入了创建用户时,自动生成详情 if is_new and create_user: ListingDetails.objects.create( listing=self, owner=create_user )
创建Listing时直接把用户作为owner参数传入save方法即可:
listing = form.save(commit=False) listing.save(owner=request.user)
补充:Django Admin适配
如果需要在Django Admin后台创建Listing时也自动生成详情,需要重写Admin的保存方法,把当前登录用户传入:
from django.contrib import admin from .models import Listing, ListingDetails class ListingAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): # change为False表示是新建而非修改已有数据 if not change: obj.save(owner=request.user) else: super().save_model(request, obj, form, change) admin.site.register(Listing, ListingAdmin) admin.site.register(ListingDetails)
内容的提问来源于stack exchange,提问作者MWD
相关产品推荐
相关产品推荐

