如何创建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
相关产品推荐
相关产品推荐

