在表单向导Django中保存多个图像Django和ManyToMany字段不起作用

时间:2019-07-13 08:14:56

标签: python django manytomanyfield django-formwizard

我正在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)。我应该在代码中添加些什么以确保按预期方式保存此数据?

0 个答案:

没有答案