26

I want to upload multiple files through a ModelForm,with all files to be assigned to a file field of the Model.I have gone through the docs and I saw an example on it and I ve implemented it here but I can only get my form to pick multiple files but only one get saved and assigned to filesfield.Below are my codes

models.py

class Feed(models.Model):
    user=models.ForeignKey(User,on_delete=models.CASCADE,related_name='feeds')
    text=models.TextField(blank=False,max_length=500)
    files = models.FileField(upload_to="files/%Y/%m/%d")

forms.py

class FeedForm(ModelForm):
    class Meta:
        model=Feed
        fields=('text','auth','files')
        widgets={"files":forms.FileInput(attrs={'id':'files','required':True,'multiple':True})}

and views.py

def post_feed(request):
    form_class = FeedForm
    if request.method == 'POST':
        form = form_class(request.POST,request.FILES)
        if form.is_valid():
            feed = form.save(commit=False)
            feed.user = User.objects.get(pk=1)
            feed.pub_date=timezone.now()
            #instance = Feed(files=request.FILES['files'])
           # feed.files=request.FILES['files']
            feed.save()
            return redirect('home')
    else:
        form = form_class()
        return render(request, 'post_feed.html', {'form': form,})

from django.views.generic.edit import FormView
from .forms import FeedForm

class FileFieldView(FormView):
    form_class=FeedForm
    template_name='post_feed.html'
 '''success_url=???   #I dont know what to write here.I thought of putting this
render(request, 'post_feed.html', {'form': form,}) because I just want 
to reload the page but it gave an error,so I removed it entirely.'''

    def post_feed(self,request,*args,**kwargs):
        form_class=self.get_form_class()
        form=self.get_form(form_class)
        filez=request.FILES.getlist('files')
        if form.is_valid():
            for f in filez:
                f.save()
            return self.form_valid(form)     
        else:
            return self.form_invalid(form) 

Kindly help me out,Thanks in advance.

Bolaji
  • 556
  • 1
  • 6
  • 11

3 Answers3

32

You have to create a separate model for the files and connect them with a foreign key:

class Feed(models.Model):
    user=models.ForeignKey(User, on_delete=models.CASCADE, related_name='feeds')
    text=models.TextField(blank=False, max_length=500)


class FeedFile(models.Model):
    file = models.FileField(upload_to="files/%Y/%m/%d")
    feed = models.ForeignKey(Feed, on_delete=models.CASCADE, related_name='files')

I hope this helps.

Carlos Mermingas
  • 3,822
  • 2
  • 21
  • 40
  • 1
    Thanks.But which will be preferable?.To put the file foreign key in the Feed model or the above you suggested?,considering I want to use FeedForm in which file is a field? – Bolaji Jul 08 '16 at 04:38
  • 6
    If you put the file in the Feed model, you will only have **one** reference to the file in the database, even if multiple files exist on the `upload_to` directory. I think that you should use a form for `Feed` and inline formsets for `FeedFile`. [This blog post](http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/) may help you better than anything that I type here. I hope this helps! – Carlos Mermingas Jul 08 '16 at 21:55
16

Phew, it took me a whole day to figure out this. My goal was to assign multiple files to one instance of a class, like a Blog instance can have multiple Images. First things first, you cannot do this with one models.FileField inside a model (for example inside Blog class), because this field was not designed to save multiple files. So the solution is to create separate model for the files and connect them with One-to-Many Relationship (Foreign Key) as it was answered by @Carlos Mermingas. Enough words, here is the code for the above situation:

# models.py
class Feed(models.Model):
    user=models.ForeignKey(User, on_delete=models.CASCADE)
    text=models.TextField(blank=False, max_length=500)

class FeedFile(models.Model):
    file = models.FileField(upload_to="files/%Y/%m/%d")
    feed = models.ForeignKey(Feed, on_delete=models.CASCADE)

# forms.py
...
from django.forms import ClearableFileInput
...
class FeedModelForm(forms.ModelForm):
    class Meta:
        model = Feed
        fields = ['text']

class FileModelForm(forms.ModelForm):
    class Meta:
        model = FeedFile
        fields = ['file']
        widgets = {
            'file': ClearableFileInput(attrs={'multiple': True}),
        }
        # widget is important to upload multiple files

# views.py
from .models import FeedFile
...
def create_to_feed(request):
    user = request.user
    if request.method == 'POST':
        form = FeedModelForm(request.POST)
        file_form = FileModelForm(request.POST, request.FILES)
        files = request.FILES.getlist('file') #field name in model
        if form.is_valid() and file_form.is_valid():
            feed_instance = form.save(commit=False)
            feed_instance.user = user
            feed_instance.save()
            for f in files:
                file_instance = FeedFile(file=f, feed=feed_instance)
                file_instance.save()
    else:
        form = FeedModelForm()
        file_form = FileModelForm()

    # the rest is the basic code: template_name, context, render etc. 

# in your template.html <form> tag must include enctype="multipart/form-data"

Bonus: if you want to see uploaded files in admin panel, you can use InlineModelAdmin objects. Here is the code:

# admin.py of your app
from django.contrib import admin
from .models import Feed, FeedFile

class FeedFileInline(admin.TabularInline):
    model = FeedFile


class FeedAdmin(admin.ModelAdmin):
    inlines = [
        FeedFileInline,
    ]

admin.site.register(Feed, FeedAdmin)

For the more details on file upload, Model Forms, how to include widget in Model Form

John R Perry
  • 3,916
  • 2
  • 38
  • 62
karjaubayev
  • 571
  • 5
  • 10
  • How can i preview the image? What will be the path of the image inside urls.py? – Irene Mar 18 '19 at 13:40
  • Please include the HTML template instead of putting "the rest is the basic code" to be completely clear how to render the upload form. – Olney1 Nov 08 '22 at 15:08
  • @karjaubayev thanks for this comprehensive answer, but how can I achieve this to instead take multiple filenames from the FileField and NOT store the files themselves? – Buzz B Dec 01 '22 at 14:37
7

Would suggest using an M2M field from Feed model to FeedFile model. Makes it all the more easier while querying for files of a particular Feed object, which i feel is also the most common usecase for Feed objects

class Feed(models.Model):
    user=models.ForeignKey(User, on_delete=models.CASCADE, related_name='feeds')
    text=models.TextField(blank=False, max_length=500)
    files=models.ManyToManyField(FeedFile)

class FeedFile(models.Model):
    file = models.FileField(upload_to="files/%Y/%m/%d")
dreamer
  • 901
  • 2
  • 15
  • 38
  • i prefer this solution a lot better. It would also give the option to upload files as the feed model instance is getting created, instead of the other options which would see feed instance getting created first before files can be uploaded – Opeyemi Odedeyi Apr 27 '20 at 16:32