Django: File uploads in forms

There are so many usecases where you’ll want to be able to upload a whole bunch of files to a website at once and yet, there isn’t really a well documented way to do it. The Django documentation itself is a bit half baked and not really all that helpful. So, in this article, I’m going to show you how to do exactly that.

First off, let’s create a file called forms.py which will sit under your app directory. So, if you have: Django_Project > App Name > forms.py. In here, we are going to define the form we want to use. You can seem we have a title, project, version and description field, which are pretty similar to how you may have defined Django forms in the past (in HTML), but we also have a FileField. In here, you can see that multiple is set to true, meaning we can upload many files at once.

from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    project= forms.CharField(max_length=100)
    version = forms.CharField(max_length=10)
    description = forms.CharField(max_length=400)
    file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

So then, let’s work through our views.py. We have the upload_file function, which takes that form data and initially extracts title and version from the form. Next, it extracts the list of files that have been uploaded, chunks those files and writes the files to a directory. You’ll notice that I also use a couple of OS functions to create the project directories if they don’t already exist.

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            title = form.cleaned_data.get('title')
            version = form.cleaned_data.get('version')
            for file in request.FILES.getlist('file_field'):
                chunks = []
                filepath = 'rrls/media/upload/'+str(title)+'/'+str(version)+'/'
                if not os.path.exists(filepath):
                    os.makedirs(filepath)
                
                with open(filepath + str(file), 'wb+') as destination:  
                    for chunk in file.chunks():
                        chunks.append(chunk)
                        destination.write(chunk)
    return render(request, 'index.html', {'project': title})

Next, let’s define the HTML we’re going to use. Here, make sure the action is equal to the URL set in urls.py (which I’ll look at in just a moment).

<body>  
<form action='upload_file' method="POST" class="post-form" enctype="multipart/form-data">  
        {% csrf_token %}  
        {{ form.as_p }}  
        <button type="submit" class="save btn btn-default">Save</button>  
</form>  
</body> 

Finally and as promised, here are our URL definitions. We have a path to upload_file, which references the view upload_file which we created above.

from django.contrib import admin
from django.urls import path
from rrls import views
from django.urls import path, include # new

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('enter', views.dash, name='enter'),
    path('accounts/', include('django.contrib.auth.urls')),
    path('upload_file', views.upload_file, name='upload_file'),
]

Now, let’s take this a step further. I want to implement version control. When the user uploads a file they define the project it is part of. The script then finds the next version number of that project. So, each time we upload, we get a new version number.

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            title = form.cleaned_data.get('title')
     
            version = 1
            exists = 0

            while exists == 0:
                filepath = 'rrls/media/upload/' + str(title) + '/' + str(version) + '/'
                if os.path.exists(filepath) == False:
                    exists = 1
                else:
                    version = version + 1
           

            version = str(version) 
            filepath = 'rrls/media/upload/' + str(title) + '/' + str(version) + '/'

            for file in request.FILES.getlist('file_field'):
                
                chunks = []
                
                if not os.path.exists(filepath):
                    os.makedirs(filepath)
                
                with open(filepath + str(file), 'wb+') as destination:  
                    for chunk in file.chunks():
                        chunks.append(chunk)
                        destination.write(chunk)

    return render(request, 'index.html', {'project': filepath})