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

如何创建Django ModelForm及处理外键关联的视图(避免数据重复)

问题描述

我有一个包含多个外键关联的Django模型ParkingSpace:

class ParkingSpace(models.Model):
    name = models.CharField(max_length=50, blank=False)
    description = models.TextField(blank=False)
    image = models.ImageField(null=True, upload_to="images/parking_spaces")
    price = models.ForeignKey('Price', on_delete=models.CASCADE, null=True, blank=False)
    opening_hours = models.ForeignKey('OpeningHours', on_delete=models.CASCADE, null=True, blank=False)
    location = models.OneToOneField('Location', on_delete=models.CASCADE, null=True, blank=False)
    size = models.ForeignKey('Size', on_delete=models.CASCADE, null=True, blank=False)
    type = models.ForeignKey('Type', on_delete=models.CASCADE, null=True, blank=False)
    features = models.ManyToManyField('Features', blank=False, db_table='ParkingSpaceFeatures')
    reviews = models.ManyToManyField('Review', blank=False, db_table='ParkingSpaceReviews')
    contact_information = models.ForeignKey('ContactInformation', on_delete=models.CASCADE, null=True, blank=False)
    seller_account = models.ForeignKey('Account', on_delete=models.CASCADE, null=True, blank=False)

以OpeningHours模型为例,这类关联模型包含多个字段:

class OpeningHours(models.Model):
    # 一周每天的开闭店时间
    monday_open = models.TimeField(null=True,blank=False)
    monday_close = models.TimeField(null=True,blank=False)
    tuesday_open = models.TimeField(null=True,blank=False)
    tuesday_close = models.TimeField(null=True,blank=False)
    wednesday_open = models.TimeField(null=True,blank=False)
    wednesday_close = models.TimeField(null=True,blank=False)
    thursday_open = models.TimeField(null=True,blank=False)
    thursday_close = models.TimeField(null=True,blank=False)
    friday_open = models.TimeField(null=True,blank=False)
    friday_close = models.TimeField(null=True,blank=False)
    saturday_open = models.TimeField(null=True,blank=False)
    saturday_close = models.TimeField(null=True,blank=False)
    sunday_open = models.TimeField(null=True,blank=False)
    sunday_close = models.TimeField(null=True,blank=False)

我需要创建一个表单,让用户录入信息创建停车场。对于price、opening_hours、location、contact_information这类关联多字段模型的外键,希望Django自动处理存储流程:如果关联对象(比如Price)已存在,直接关联而非创建重复对象,避免多表单分步保存导致的数据重复。请问该如何实现这个表单?


解决方案

核心思路是利用Django的get_or_create方法,在表单提交时根据用户输入的字段值查询数据库:存在匹配的关联对象则直接使用,不存在则创建新对象,再关联到ParkingSpace。以下是具体实现步骤:

1. 为关联模型设置唯一约束

先为每个关联模型确定唯一判断依据,通过unique_together或unique字段约束避免重复数据。以Price模型为例:

class Price(models.Model):
    hourly_rate = models.DecimalField(max_digits=8, decimal_places=2)
    daily_rate = models.DecimalField(max_digits=8, decimal_places=2)
    monthly_rate = models.DecimalField(max_digits=8, decimal_places=2)

    class Meta:
        # 组合唯一约束:确保相同费率的Price不会重复创建
        unique_together = ('hourly_rate', 'daily_rate', 'monthly_rate')

2. 创建自定义表单

自定义表单类,包含ParkingSpace自身字段及所有关联模型的字段,不直接依赖ModelForm:

from django import forms
from .models import ParkingSpace, Price, OpeningHours, Location, ContactInformation, Size, Type, Features

class ParkingSpaceCreateForm(forms.Form):
    # ParkingSpace自身字段
    name = forms.CharField(max_length=50, required=True)
    description = forms.CharField(widget=forms.Textarea, required=True)
    image = forms.ImageField(required=False)

    # Price字段
    hourly_rate = forms.DecimalField(max_digits=8, decimal_places=2, required=True)
    daily_rate = forms.DecimalField(max_digits=8, decimal_places=2, required=True)
    monthly_rate = forms.DecimalField(max_digits=8, decimal_places=2, required=True)

    # OpeningHours字段
    monday_open = forms.TimeField(required=True)
    monday_close = forms.TimeField(required=True)
    tuesday_open = forms.TimeField(required=True)
    tuesday_close = forms.TimeField(required=True)
    wednesday_open = forms.TimeField(required=True)
    wednesday_close = forms.TimeField(required=True)
    thursday_open = forms.TimeField(required=True)
    thursday_close = forms.TimeField(required=True)
    friday_open = forms.TimeField(required=True)
    friday_close = forms.TimeField(required=True)
    saturday_open = forms.TimeField(required=True)
    saturday_close = forms.TimeField(required=True)
    sunday_open = forms.TimeField(required=True)
    sunday_close = forms.TimeField(required=True)

    # Location字段
    location_address = forms.CharField(max_length=200, required=True)
    location_latitude = forms.FloatField(required=True)
    location_longitude = forms.FloatField(required=True)

    # ContactInformation字段
    contact_phone = forms.CharField(max_length=20, required=True)
    contact_email = forms.EmailField(required=True)

    # Size、Type字段
    size_name = forms.CharField(max_length=50, required=True)
    type_name = forms.CharField(max_length=50, required=True)

    # ManyToMany字段(Features)
    features = forms.ModelMultipleChoiceField(queryset=Features.objects.all(), required=True)

3. 视图中处理表单提交

在视图里验证表单后,逐个调用get_or_create处理关联对象,最后创建ParkingSpace并完成关联:

from django.shortcuts import render, redirect
from .forms import ParkingSpaceCreateForm
from .models import ParkingSpace, Price, OpeningHours, Location, ContactInformation, Size, Type

def create_parking_space(request):
    if request.method == 'POST':
        form = ParkingSpaceCreateForm(request.POST, request.FILES)
        if form.is_valid():
            # 获取或创建Price对象
            price, _ = Price.objects.get_or_create(
                hourly_rate=form.cleaned_data['hourly_rate'],
                daily_rate=form.cleaned_data['daily_rate'],
                monthly_rate=form.cleaned_data['monthly_rate']
            )

            # 获取或创建OpeningHours对象
            opening_hours, _ = OpeningHours.objects.get_or_create(
                monday_open=form.cleaned_data['monday_open'],
                monday_close=form.cleaned_data['monday_close'],
                tuesday_open=form.cleaned_data['tuesday_open'],
                tuesday_close=form.cleaned_data['tuesday_close'],
                wednesday_open=form.cleaned_data['wednesday_open'],
                wednesday_close=form.cleaned_data['wednesday_close'],
                thursday_open=form.cleaned_data['thursday_open'],
                thursday_close=form.cleaned_data['thursday_close'],
                friday_open=form.cleaned_data['friday_open'],
                friday_close=form.cleaned_data['friday_close'],
                saturday_open=form.cleaned_data['saturday_open'],
                saturday_close=form.cleaned_data['saturday_close'],
                sunday_open=form.cleaned_data['sunday_open'],
                sunday_close=form.cleaned_data['sunday_close']
            )

            # 获取或创建Location对象
            location, _ = Location.objects.get_or_create(
                address=form.cleaned_data['location_address'],
                latitude=form.cleaned_data['location_latitude'],
                longitude=form.cleaned_data['location_longitude']
            )

            # 获取或创建ContactInformation对象
            contact_info, _ = ContactInformation.objects.get_or_create(
                phone=form.cleaned_data['contact_phone'],
                email=form.cleaned_data['contact_email']
            )

            # 获取或创建Size、Type对象
            size, _ = Size.objects.get_or_create(name=form.cleaned_data['size_name'])
            type_obj, _ = Type.objects.get_or_create(name=form.cleaned_data['type_name'])

            # 创建ParkingSpace并关联所有对象
            parking_space = ParkingSpace.objects.create(
                name=form.cleaned_data['name'],
                description=form.cleaned_data['description'],
                image=form.cleaned_data.get('image'),
                price=price,
                opening_hours=opening_hours,
                location=location,
                size=size,
                type=type_obj,
                contact_information=contact_info,
                seller_account=request.user.account  # 假设当前用户关联了Account模型
            )

            # 处理ManyToMany字段
            parking_space.features.add(*form.cleaned_data['features'])

            return redirect('parking_space_detail', pk=parking_space.pk)
    else:
        form = ParkingSpaceCreateForm()
    return render(request, 'create_parking_space.html', {'form': form})

4. 模板渲染表单

在模板create_parking_space.html中渲染表单即可:

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">创建停车场</button>
</form>

关键说明

  • get_or_create返回元组(对象, 是否创建),第二个布尔值标记是获取已有对象还是创建新对象。
  • 必须为关联模型设置唯一约束,否则get_or_create可能生成重复数据。
  • 对于OneToOneField(如location),若业务允许同一Location关联多个ParkingSpace,可改为ForeignKey。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:03:14