我正在Django中的一个项目上工作,我必须使用表单向导。要求的一部分是在表单步骤之一中保存多个图像,并保存多对多字段,这些字段无法按预期工作。让我先分享我的代码,然后再解释问题。
models.py
from django.db import models
from django.contrib.auth.models import User
from location_field.models.plain import PlainLocationField
from PIL import Image
from slugify import slugify
from django.utils.translation import gettext as _
from django.core.validators import MaxValueValidator, MinValueValidator
from listing_admin_data.models import (Service, SubscriptionType, PropertySubCategory,
PropertyFeatures, VehicleModel, VehicleBodyType, VehicleFuelType,
VehicleColour, VehicleFeatures, BusinessAmenities, Currency, EventsType
)
import datetime
from django_google_maps.fields import AddressField, GeoLocationField
def current_year():
return datetime.date.today().year
def max_value_current_year(value):
return MaxValueValidator(current_year())(value)
class Listing(models.Model):
listing_type_choices = [('P', 'Property'), ('V', 'Vehicle'), ('B', 'Business/Service'), ('E', 'Events')]
listing_title = models.CharField(max_length=255)
listing_type = models.CharField(choices=listing_type_choices, max_length=1, default='P')
status = models.BooleanField(default=False)
featured = models.BooleanField(default=False)
city = models.CharField(max_length=255, blank=True)
location = PlainLocationField(based_fields=['city'], zoom=7, blank=True)
address = AddressField(max_length=100)
geolocation = GeoLocationField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
expires_on = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(User,
on_delete=models.CASCADE, editable=False, null=True, blank=True
)
listing_owner = models.ForeignKey(User,
on_delete=models.CASCADE, related_name='list_owner'
)
def __str__(self):
return self.listing_title
class Meta:
ordering = ['-created_at']
def get_image_filename(instance, filename):
title = instance.listing.listing_title
slug = slugify(title)
return "listings_pics/%s-%s" % (slug, filename)
class ListingImages(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.CASCADE)
image_url = models.ImageField(upload_to=get_image_filename,
verbose_name='Listing Images')
main_image = models.BooleanField(default=False)
class Meta:
verbose_name_plural = "Listing Images"
def __str__(self):
return f'{self.listing.listing_title} Image'
class Subscriptions(models.Model):
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE)
subscription_date = models.DateTimeField(auto_now_add=True)
subscription_amount = models.DecimalField(max_digits=6, decimal_places=2)
subscribed_by = models.ForeignKey(User, on_delete=models.CASCADE)
duration = models.PositiveIntegerField(default=0)
listing_subscription = models.ManyToManyField(Listing)
updated_at = models.DateTimeField(auto_now=True)
status = models.BooleanField(default=False)
class Meta:
verbose_name_plural = "Subscriptions"
def __str__(self):
return f'{self.listing.listing_title} Subscription'
class Property(models.Model):
sale_hire_choices = [('S', 'Sale'), ('R', 'Rent')]
fully_furnished_choices = [('Y', 'Yes'), ('N', 'No')]
listing = models.OneToOneField(Listing, on_delete=models.CASCADE)
sub_category = models.ForeignKey(PropertySubCategory, on_delete=models.CASCADE)
for_sale_rent = models.CharField(choices=sale_hire_choices, max_length=1, default=None)
bedrooms = models.PositiveIntegerField(default=0)
bathrooms = models.PositiveIntegerField(default=0)
rooms = models.PositiveIntegerField(default=0)
land_size = models.DecimalField(max_digits=10, decimal_places=2)
available_from = models.DateField()
car_spaces = models.PositiveIntegerField(default=0)
fully_furnished = models.CharField(choices=fully_furnished_choices, max_length=1, default=None)
desc = models.TextField()
property_features = models.ManyToManyField(PropertyFeatures)
price = models.DecimalField(max_digits=15, decimal_places=2)
currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Properties"
def __str__(self):
return f'{self.listing.listing_title}'
class Vehicle(models.Model):
sale_hire_choices = [('S', 'Sale'), ('H', 'Hire')]
transmission_choices = [('A', 'Automatic'), ('M', 'Manual')]
drive_choices = [('L', 'Left'), ('R', 'Right')]
condition_choices = [('L', 'Locally Used'), ('F', 'Foreign Used'), ('N', 'Brand New')]
interior_choices = [('C', 'Cloth'), ('L', 'Leather'), ('O', 'Other')]
listing = models.OneToOneField(Listing, on_delete=models.CASCADE)
for_sale_hire = models.CharField(choices=sale_hire_choices, max_length=1, default=None)
year_of_manufacture = models.IntegerField(_('year'), validators=[MinValueValidator(1900), max_value_current_year])
engine_capacity = models.PositiveIntegerField()
model = models.ForeignKey(VehicleModel, on_delete=models.CASCADE)
description = models.TextField()
transmission = models.CharField(choices=transmission_choices, max_length=1, default=None)
drive = models.CharField(choices=drive_choices, max_length=1, default=None)
current_millage = models.PositiveIntegerField(validators=[MinValueValidator(0)])
condition = models.CharField(choices=condition_choices, max_length=1, default=None)
interior = models.CharField(choices=interior_choices, max_length=1, default=None)
number_of_doors = models.PositiveIntegerField(default=0)
body_type = models.ForeignKey(VehicleBodyType, on_delete=models.CASCADE)
fuel_type = models.ForeignKey(VehicleFuelType, on_delete=models.CASCADE)
colour = models.ForeignKey(VehicleColour, on_delete=models.CASCADE)
vehicle_features = models.ManyToManyField(VehicleFeatures)
asking_price = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.listing.listing_title}'
class Business(models.Model):
listing = models.OneToOneField(Listing, on_delete=models.CASCADE)
service = models.ForeignKey(Service, on_delete=models.CASCADE)
business_name = models.CharField(max_length=100)
slogan = models.CharField(max_length=255)
desc = models.TextField()
website_address = models.CharField(max_length=50)
email_address = models.EmailField(max_length=50)
business_amenities = models.ManyToManyField(BusinessAmenities)
contact = models.CharField(max_length=15)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Businesses"
def __str__(self):
return f'{self.listing.listing_title}'
class Event(models.Model):
listing = models.OneToOneField(Listing, on_delete=models.CASCADE)
event_title = models.CharField(max_length=255)
event_type = models.ForeignKey(EventsType, on_delete=models.CASCADE)
event_date = models.DateTimeField()
end_date = models.DateTimeField()
overview = models.TextField()
entry_fee = models.CharField(max_length=50)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.event_title}'
forms.py
from django import forms
import datetime
from .models import Listing, Property, Vehicle, Business, ListingImages, Event
from django.forms import modelformset_factory
from django.forms.widgets import CheckboxSelectMultiple
from bootstrap_datepicker_plus import (
DatePickerInput, TimePickerInput, DateTimePickerInput,
MonthPickerInput, YearPickerInput
)
from listing_admin_data.models import (
PropertyFeatures, VehicleFeatures, BusinessAmenities
)
from django_google_maps.widgets import GoogleMapsAddressWidget
def year_choices():
return [(r,r) for r in range(1900, datetime.date.today().year+1)]
def current_year():
return datetime.date.today().year
class DateInput(forms.DateInput):
input_type = 'date'
class ListingDetails(forms.ModelForm):
class Meta:
model = Listing
fields = ['listing_title', 'address', 'geolocation']
widgets = {
"address": GoogleMapsAddressWidget,
}
class PropertyDetails1(forms.ModelForm):
class Meta:
model = Property
fields = ['sub_category', 'for_sale_rent', 'bedrooms', 'bathrooms',
'rooms', 'land_size', 'available_from', 'car_spaces', 'fully_furnished',
'desc', 'currency', 'price'
]
widgets = {
'available_from': DatePickerInput(),
}
class PropertyDetails2(forms.ModelForm):
class Meta:
model = Property
fields = ['property_features']
def __init__(self, *args, **kwargs):
super(PropertyDetails2, self).__init__(*args, **kwargs)
self.fields["property_features"].widget = CheckboxSelectMultiple()
self.fields["property_features"].queryset = PropertyFeatures.objects.all()
class ListingImagesForm(forms.ModelForm):
image_url = forms.ImageField(label='Listing Image',
widget=forms.ClearableFileInput(attrs={'multiple': True}),
required=False
)
class Meta:
model = ListingImages
fields = ['image_url']
class VehicleDetails1(forms.ModelForm):
make = forms.CharField(max_length=255)
year_of_manufacture = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=current_year())
class Meta:
model = Vehicle
fields = ['for_sale_hire', 'make', 'model', 'year_of_manufacture',
'current_millage', 'body_type', 'transmission', 'condition',
'asking_price'
]
class VehicleDetails2(forms.ModelForm):
class Meta:
model = Vehicle
fields = ['engine_capacity', 'description', 'drive', 'interior',
'number_of_doors', 'fuel_type', 'colour', 'vehicle_features'
]
def __init__(self, *args, **kwargs):
super(VehicleDetails2, self).__init__(*args, **kwargs)
self.fields["vehicle_features"].widget = CheckboxSelectMultiple()
self.fields["vehicle_features"].queryset = VehicleFeatures.objects.all()
class BusinessDetails1(forms.ModelForm):
class Meta:
model = Business
fields = ['service', 'business_name', 'slogan', 'website_address', 'email_address', 'contact', 'desc']
class BusinessDetails2(forms.ModelForm):
class Meta:
model = Business
fields = ['business_amenities']
class EventDetails1(forms.ModelForm):
class Meta:
model = Event
fields = ['event_title', 'event_type', 'event_date', 'end_date', 'overview', 'entry_fee']
views.py
from django.shortcuts import render, redirect
import os
from .forms import (ListingDetails, ListingImagesForm,
PropertyDetails1, PropertyDetails2, VehicleDetails1,
VehicleDetails2, BusinessDetails1, BusinessDetails2, EventDetails1
)
from django.views.generic import (
DetailView, ListView
)
from .models import ListingImages, Listing, Property, Vehicle, Business, Event
from formtools.wizard.views import SessionWizardView
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.forms import modelformset_factory
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse
from django.forms.models import construct_instance
from .filters import PropertyFilter, VehicleFilter, BusinessFilter, EventFilter
class PropertyView(SessionWizardView):
template_name = "listings/create_listing.html"
form_list = [ListingDetails, PropertyDetails1, PropertyDetails2, ListingImagesForm]
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'media'))
def done(self, form_list, **kwargs):
listing_instance = Listing()
property_instance = Property()
listing_image_instance = ListingImages()
listing_instance.created_by = self.request.user
listing_instance.listing_owner = self.request.user
listing_instance.listing_type = 'P'
for form in form_list:
listing_instance = construct_instance(form, listing_instance, form._meta.fields, form._meta.exclude)
property_instance = construct_instance(form, property_instance, form._meta.fields, form._meta.exclude)
listing_image_instance = construct_instance(form, listing_image_instance, form._meta.fields, form._meta.exclude)
listing_instance.save()
property_instance.listing = listing_instance
property_instance.save()
# listing_image_instance.listing = listing_instance
# listing_image_instance.save()
for image in listing_image_instance:
image.listing = listing_instance # saved above
image.save()
return HttpResponse('data saved successfully')
# return render(self0.request, 'done.html', {
# 'form_data': [form.cleaned_data for form in form_list],
# })
def get(self, request, *args, **kwargs):
try:
return self.render(self.get_form())
except KeyError:
return super().get(request, *args, **kwargs)
class VehicleView(SessionWizardView):
template_name = "listings/create_listing.html"
form_list = [ListingDetails, VehicleDetails1, VehicleDetails2, ListingImagesForm]
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'media'))
def done(self, form_list, **kwargs):
listing_instance = Listing()
vehicle_instance = Vehicle()
listing_image_instance = ListingImages()
listing_instance.created_by = self.request.user
listing_instance.listing_owner = self.request.user
listing_instance.listing_type = 'V'
for form in form_list:
listing_instance = construct_instance(form, listing_instance, form._meta.fields, form._meta.exclude)
vehicle_instance = construct_instance(form, vehicle_instance, form._meta.fields, form._meta.exclude)
listing_image_instance = construct_instance(form, listing_image_instance, form._meta.fields, form._meta.exclude)
listing_instance.save()
vehicle_instance.listing = listing_instance
vehicle_instance.save()
listing_image_instance.listing = listing_instance
listing_image_instance.save()
# for li_obj in listing_image_instances:
# li_obj.listing = listing_instance # saved above
# li_obj.save()
return HttpResponse('data saved successfully')
# return render(self0.request, 'done.html', {
# 'form_data': [form.cleaned_data for form in form_list],
# })
def get(self, request, *args, **kwargs):
try:
return self.render(self.get_form())
except KeyError:
return super().get(request, *args, **kwargs)
class BusinessView(SessionWizardView):
template_name = "listings/create_listing.html"
form_list = [ListingDetails, BusinessDetails1, BusinessDetails2, ListingImagesForm]
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'media'))
def done(self, form_list, **kwargs):
listing_instance = Listing()
business_instance = Business()
listing_image_instance = ListingImages()
listing_instance.created_by = self.request.user
listing_instance.listing_owner = self.request.user
listing_instance.listing_type = 'B'
for form in form_list:
listing_instance = construct_instance(form, listing_instance, form._meta.fields, form._meta.exclude)
business_instance = construct_instance(form, business_instance, form._meta.fields, form._meta.exclude)
listing_image_instance = construct_instance(form, listing_image_instance, form._meta.fields, form._meta.exclude)
listing_instance.save()
business_instance.listing = listing_instance
business_instance.save()
listing_image_instance.listing = listing_instance
listing_image_instance.save()
# for li_obj in listing_image_instances:
# li_obj.listing = listing_instance # saved above
# li_obj.save()
return HttpResponse('data saved successfully')
# return render(self0.request, 'done.html', {
# 'form_data': [form.cleaned_data for form in form_list],
# })
def get(self, request, *args, **kwargs):
try:
return self.render(self.get_form())
except KeyError:
return super().get(request, *args, **kwargs)
class EventView(SessionWizardView):
template_name = "listings/create_listing.html"
form_list = [ListingDetails, EventDetails1, ListingImagesForm]
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'media'))
def done(self, form_list, **kwargs):
listing_instance = Listing()
event_instance = Event()
listing_image_instance = ListingImages()
listing_instance.created_by = self.request.user
listing_instance.listing_owner = self.request.user
listing_instance.listing_type = 'E'
for form in form_list:
listing_instance = construct_instance(form, listing_instance, form._meta.fields, form._meta.exclude)
event_instance = construct_instance(form, event_instance, form._meta.fields, form._meta.exclude)
listing_image_instance = construct_instance(form, listing_image_instance, form._meta.fields, form._meta.exclude)
listing_instance.save()
event_instance.listing = listing_instance
event_instance.save()
listing_image_instance.listing = listing_instance
listing_image_instance.save()
# for li_obj in listing_image_instances:
# li_obj.listing = listing_instance # saved above
# li_obj.save()
return HttpResponse('data saved successfully')
# return render(self0.request, 'done.html', {
# 'form_data': [form.cleaned_data for form in form_list],
# })
def get(self, request, *args, **kwargs):
try:
return self.render(self.get_form())
except KeyError:
return super().get(request, *args, **kwargs)
这是视图使用的模板
create_listing.html
{% extends "base/base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% load i18n %}
<!-- necessary for date picker -->
<!-- {% block extra_css %}
{{ form.media.css }}
{% endblock %}
{% block extra_js %}
{{ form.media.js }}
{% endblock %} -->
<!-- end date picker -->
{% block content %}
<!-- ============================ Page Title Start================================== -->
<div class="page-title image-title" style="background-image:url('/static/base/assets/img/banner-4.jpg');">
<div class="finding-overlay op-70"></div>
<div class="container">
<div class="page-title-wrap">
<h1>Submit Your Listing</h1>
<p><a href="#" class="theme-cl">Home</a> <span class="current-page active">Add Your listing</span></p>
</div>
</div>
</div>
<!-- ============================ Page Title End ================================== -->
<!-- =========================== Add Form Start ============================================ -->
<section>
<div class="container">
<div class="row justify-content-md-center">
<div class="col-lg-10 col-md-10 col-sm-12">
<form class="add-listing-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- general information -->
<div class="tr-single-box">
<div class="tr-single-header">
<h4><i class="ti-medall-alt"></i> Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</h4>
</div>
<div class="tr-single-body">
{{ wizard.management_form }}
{{ form|crispy }}
</div>
</div>
{% if wizard.steps.prev %}
<div class="d-flex justify-content-around pb-5">
<button name="wizard_goto_step" type="submit" class="btn btn-primary" value="{{ wizard.steps.first }}">First Step</button>
<button name="wizard_goto_step" type="submit" class="btn btn-primary" value="{{ wizard.steps.prev }}">Previous Step</button>
</div>
{% endif %}
<button class="btn btn-primary full-width" type="submit">Submit</button>
</form>
</div>
</div>
</div>
</section>
{% endblock content %}
我正在尝试在视图中实现第一个类,这与保存数据的其余类本质上相似。这部分给我一个错误:
for image in listing_image_instance:
image.listing = listing_instance # saved above
image.save()
我得到的错误是:'ListingImages' object is not iterable
。指向的行是views.py
中的40行,它实际上是上面的for循环。我是否忘记了通过表单可重复提交的ListingImages
模型?
另一个问题是,即使数据已保存到Property
模型中,也不会保存其ManyToMany
字段,尽管有从表单提交的数据。有问题的字段是:property_features = models.ManyToManyField(PropertyFeatures)
。我应该在代码中添加些什么以确保按预期方式保存此数据?