Django Rest Framework File Upload

I’ve been working on a Django project that required a file upload. I like to practice what is these days called an API first approach to app design, but has been a pretty good practice well before the term was coined. So, my projects generally start with a REST framework. In the Python/Django ecosystem, I default to the exceptionally well designed django-rest-framework.

I had to do quite a bit of digging to get a solution to uploading a file, so I hope this helps someone. I wanted to use a ModelViewSet, since you get a lot of leverage with a reasonable amount of customizations. There are cases where ViewSets don’t cut it, but I’m happy to report that this is not one of them. Let’s dig in…

The model is pretty straight forward. Since I want an object-level permission model later, I’m going to keep track of the owner of the file:

# models.py 
from django.db import models
from probably.users.models import User


class FileUpload(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, to_field='id')
datafile = models.FileField()

There is a little more going on in the serializer, but it’s still pretty straight forward. I marked everything as read_only since they shouldn’t change after creation. The owner id is slugged so that we can see who created the file. My User model is not a rest framework model since I bootstrapped with an existing Django template, so there will be some additional work to use a hyperlinked field.

# serializers.py
from rest_framework import serializers
from probably.obtain.models import FileUpload

class FileUploadSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.SlugRelatedField(
read_only=True,
slug_field='id'
)

class Meta:
model = FileUpload
read_only_fields = ('created', 'datafile', 'owner')

The view is again really simple. The

# views.py
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.viewsets import ModelViewSet
from probably.obtain.models import FileUpload
from probably.obtain.serializers import FileUploadSerializer


class FileUploadViewSet(ModelViewSet):

queryset = FileUpload.objects.all()
serializer_class = FileUploadSerializer
parser_classes = (MultiPartParser, FormParser,)

def perform_create(self, serializer):
serializer.save(owner=self.request.user,
datafile=self.request.data.get('datafile'))

The perform_create method gets the owner from the authenticated user in the request, and puts sets the datafile. So, let’s test it out.

# tests.py
class FileUploadTests(APITestCase):

def setUp(self):
self.tearDown()
u = User.objects.create_user('test', password='test',
email='test@test.test')
u.save()

def tearDown(self):
try:
u = User.objects.get_by_natural_key('test')
u.delete()

except ObjectDoesNotExist:
pass
FileUpload.objects.all().delete()

def _create_test_file(self, path):
f = open(path, 'w')
f.write('test123\n')
f.close()
f = open(path, 'rb')
return {'datafile': f}

def test_upload_file(self):
url = reverse('fileupload-list')
data = self._create_test_file('/tmp/test_upload')

# assert authenticated user can upload file
token = Token.objects.get(user__username='test')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
response = client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertIn('created', response.data)
self.assertTrue(urlparse(
response.data['datafile']).path.startswith(settings.MEDIA_URL))
self.assertEqual(response.data['owner'],
User.objects.get_by_natural_key('test').id)
self.assertIn('created', response.data)

# assert unauthenticated user can not upload file
client.logout()
response = client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

That’s about all there is to it. On a side note, I’m not 100% thrilled with the Medium editor with respect to code. Am I missing some hidden formatting potential?

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.