User Photos

Part 1, Chapter 8

Viewing a user's photo is an important piece of functionality in ride-sharing apps. In fact, most of these apps make it mandatory to provide a photo before you can drive or ride. From one perspective, it's a security issue--riders need to confirm that the drivers are who they expect before they enter their vehicles. User photos are also good design and add life to the product.

Our app will allow users to add their photos at sign up.

Media Files

Media files are a form of user-generated static files and Django handles both in a similar way. We need to provide two new settings, MEDIA_ROOT and MEDIA_URL.

The MEDIA_ROOT is the path to the directory where file uploads will be saved. For the purpose of this tutorial, we can create a media folder on the same level as our example directory. In a production environment, we'd specify an absolute path to a directory on the server or we'd store files with a service like AWS S3. The MEDIA_URL is the prefix to use in our URL path.

Set both the MEDIA_URL and the MEDIA_ROOT within the settings file:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

One last step is required to get our local environment to serve media files. Update example_taxi/ like so:

# example_taxi/
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path

from example.apis import SignUpView, LogInView, LogOutView

urlpatterns = [
    path('api/sign_up/', SignUpView.as_view(), name='sign_up'),
    path('api/log_in/', LogInView.as_view(), name='log_in'),
    path('api/log_out/', LogOutView.as_view(), name='log_out'),
    path('api/trip/', include('example.urls', 'trip',)),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # new

Media files can now be retrieved via http://localhost:8000/media/<file_path>/ on your local machine.

To test, add a new folder called "media" to "django-taxi/example_taxi". Then, add a test file called test.txt to that folder and add some random text to the file. Fire up the server and navigate to http://localhost:8000/media/test.txt to view the file.

Make sure you remove the static() function from the urlpatterns when you deploy your application. We only need that for local development.


Change the existing AuthenticationTest.test_user_can_sign_up test in the following way:

# example/
def test_user_can_sign_up(self):
    photo_file = create_photo_file()
    response ='sign_up'), data={
        'username': '[email protected]',
        'first_name': 'Test',
        'last_name': 'User',
        'password1': PASSWORD,
        'password2': PASSWORD,
        'group': 'rider',
        'photo': photo_file,
    user = get_user_model().objects.last()
    self.assertEqual(status.HTTP_201_CREATED, response.status_code)
    self.assertEqual(['username'], user.username)
    self.assertEqual(['first_name'], user.first_name)
    self.assertEqual(['last_name'], user.last_name)

Add the create_photo_file helper function right after the create_user helper:

# example/
def create_photo_file():
    data = BytesIO()'RGB', (100, 100)).save(data, 'PNG')
    return SimpleUploadedFile('photo.png', data.getvalue())

This code leverages the Pillow library, BytesIO from the standard library, and Django's SimpleUploadedFile to create fake image data.

Add the imports:

# example/
from io import BytesIO
from PIL import Image
from django.core.files.uploadedfile import SimpleUploadedFile

Of course the test will fail since we need to update our user model and its serializer:

AttributeError: 'User' object has no attribute 'photo'

File Changes

Modify the user model:

# example/
class User(AbstractUser):
    photo = models.ImageField(upload_to='photos', null=True, blank=True)

    def group(self):
        groups = self.groups.all()
        return groups[0].name if groups else None

Now, when users upload their photos, the app will save them in a photos subdirectory within our media folder.

By default, our serializer will get the absolute path to our photo field. Let's set the use_url argument on the ImageField() to False to get the file string instead. This will come in handy when we implement Docker later on in the tutorial.

# example/
class UserSerializer(serializers.ModelSerializer):

    photo = serializers.ImageField(allow_empty_file=True, use_url=False) # new

    class Meta:
        model = get_user_model()
        fields = (
            'id', 'username', 'password1', 'password2', 'email',
            'first_name', 'last_name', 'group',
            'photo', # new
        read_only_fields = ('id',)

One last thing—create a migration for the new photo field on our user table and run the migrations.

(env)$ python makemigrations
(env)$ python migrate

Now the tests should pass.

(env)$ python test example.tests

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
Ran 5 tests in 0.972s

Destroying test database for alias 'default'...

Mark as Completed