如何使用内联表单集通过CreateView上传文件?

时间:2019-05-11 00:41:45

标签: python django

我无法使用嵌入式表单集通过CreateView上传文件。

理想情况下,它将是多个文件,类似于它在管理页面中的行为,但是在这一点上,我试图至少建立一个文件。在以下示例中,一个Workshop应该可以有多个文件。

上传时,所有内容都会保存,当然文件除外

models.py

...


class Workshop (models.Model):
    title = models.CharField(max_length=120)
    created_by = models.ForeignKey(User)
    slug = models.SlugField(blank=True, null=True, unique=True)

    def __str__(self):
        return self.title

...

def upload_workshop_file_loc(instance, filename):
    slug = instance.workshop.slug
    if not slug:
        slug = unique_slug_generator(instance.workshop)
    location = "workshop/{}/".format(slug)
    return location + filename


class WorkshopFile(models.Model):
    workshop = models.ForeignKey(Workshop, related_name='files', on_delete=models.CASCADE)
    name = models.CharField()
    file = models.FileField(
        upload_to=upload_workshop_file_loc,
        null=True,
        validators=[FileExtensionValidator
(allowed_extensions=['pdf', 'ppt'])]
    )

    def __str__(self):
        return str(self.file.name)

...

forms.py

from django import forms
from .models import Workshop, WorkshopFile
from django.forms.models import inlineformset_factory


class AddWorkshopForm(forms.ModelForm):

    class Meta:
        model = Workshop
        exclude = []


FileFormSet = inlineformset_factory(Workshop,
WorkshopFile,
fields=['workshop','name', 'file'],
exclude=[], 
extra=1, 
can_delete=True
)

最有可能是罪魁祸首

views.py

...
class AddWorkshopView(LoginRequiredMixin, CreateView):

    model = Workshop
    form_class = AddWorkshopForm
    template_name = "modules/add-workshop.html"
    success_url = "/modules/workshop-list/"

    def post(self, request, *args, **kwargs):
        form = AddWorkshopForm(request.POST, request.FILES)
        workshop = form.save(commit=False)
        workshop.save()
        workshop.created_by = request.user
        return redirect('modules:workshop', workshop.slug)


    def get_context_data(self, **kwargs):
        data = super(AddWorkshopView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['files'] = FileFormSet(self.request.POST)
        else:
            data['files'] = FileFormSet()
        return data


    def form_valid(self, form):
        context = self.get_context_data()
        files = context['files']

        with transaction.atomic():
            form.instance.created_by = self.request.user
            form.instance.updated_by = self.request.user
            self.object = form.save()

        if files.is_valid():
            files.instance = self.object
            files.save()

        return super(AddWorkshopView, self).form_valid(form)
...

add-workshop.html

...
  <div>
    <form method="post" action='' enctype='multipart/form-data'>
{% csrf_token %}
      {{ form | crispy }}
      <hr/>
        <div>
        {{ files | crispy }}
        </div>

      <input type="submit" class="btn btn-primary btn-md float-left" value="Save" />
    </form>
  </div>
...

2 个答案:

答案 0 :(得分:0)

您不应覆盖post方法-它在幕后调用form_valid,因此实际上并没有处理表单。另一件事是您的FileFormSet没有得到request.FILES-这就是文件格式不对其进行处理的原因。

views.py:

class AddWorkshopView(LoginRequiredMixin, CreateView):

    model = Workshop
    form_class = AddWorkshopForm
    template_name = "modules/add-workshop.html"
    success_url = "/modules/workshop-list/"

    def get_context_data(self, **kwargs):
        data = super(AddWorkshopView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['files'] = FileFormSet(self.request.POST, self.request.FILES)
        else:
            data['files'] = FileFormSet()
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        files = context['files']

        with transaction.atomic():
            form.instance.created_by = self.request.user
            form.instance.updated_by = self.request.user
            self.object = form.save()

            if files.is_valid():
                files.instance = self.object
                files.save()

        return super(AddWorkshopView, self).form_valid(form)

答案 1 :(得分:0)

在我的情况下,将图像通过CreateView上载到s3,并且不带Formset。我希望这会对寻找这种情况的人有所帮助。

from bootstrap_modal_forms.generic import (BSModalCreateView)
from vcweb.settings import base
from .forms import InputForm
from utils import upload_to_s3 #global method for upload files to s3
from datetime import datetime
from django.utils.timezone import get_current_timezone

curdatetime = datetime.now(tz=get_current_timezone())
curdatetime=(str(curdatetime)).replace('-','_').replace('+','_').replace('.','_')
curdatetime=curdatetime.replace(':','_').replace(' ','_')


class InputCreateView(BSModalCreateView):
   model = Input_Product
   form_class = InputForm
   template_name = 'inputs/create_product.html'
   success_message = 'Success: Input Product is created.'
   success_url = reverse_lazy('input_product_list')

   def form_valid(self, form):
      form.instance.created_by = self.request.user
      form.instance.updated_by = self.request.user

       # get the image by using request.FILES.get
      filepath = self.request.FILES.get('product_Image', False)
      user_id = self.request.user.id

      #generate filename along with datetime string
      key = base.INPUT_PRODUCT_IMAGE_FOLDER+'/images/' + str(user_id) + '/productimages/' + curdatetime + '.png'

      if filepath == False:
          print(filepath)
      else: #if file exists send the those file and s3 details to upload_to_s3 to upload the data into s3               
          s3status = upload_to_s3(base.S3_KEY, base.S3_SECRET, filepath, base.S3_BUCKET, key, callback=None, md5=None,
                                reduced_redundancy=False, content_type=None)

       # after upload the image, set the filepath value to column(file or image field variable)
       form.instance.product_Image=key
       return super().form_valid(form)

创建utils.py文件并声明upload_to_s3方法

import os
import boot
from boto.s3.key import Key

def upload_to_s3(aws_access_key_id, aws_secret_access_key, file, bucket, key, 
          callback=None, md5=None,
             reduced_redundancy=False, content_type=None):
    try:
       size = os.fstat(file.fileno()).st_size
    except:
       # Not all file objects implement fileno(),
       # so we fall back on this
       file.seek(0, os.SEEK_END)
       size = file.tell()

    conn = boto.connect_s3(aws_access_key_id, aws_secret_access_key)
    bucket = conn.get_bucket(bucket, validate=True)
    k = Key(bucket)
    k.key = key
    if content_type:
       k.set_metadata('Content-Type', content_type)
    sent = k.set_contents_from_file(file, cb=callback, md5=md5, 
        reduced_redundancy=reduced_redundancy, rewind=True)

    # Rewind for later use
    file.seek(0)

    if sent == size:
       return True
    return False