Motivation For Sharing

Some weeks back I had planned on building a media sharing application. Then I gradually came to the conclusion I should just extend and/or combine various Django Apps, avoiding inventing solutions that were already a part of the django ecosystem. This is the one the main benefits of picking a good framework with a vast ecosystem.

fusion.... HA

Requirements

  • Upload video/photos to a gallery
  • display them together

Setup

I will assume you have a basic django app setup and ready to go or know how to do so. If you dont and would like a quick docker based solution, check out my django seed project.

add these to requirements.txt

django-photologue>=3.7, <4.0
django-video-encoding>=1.0, <2.0

Note: the >= x.x, <x.x is a habit I have picked up to explicitly ignore upgrading major breaking versions of my dependencies.

Install the Apps

Both of these apps have enough funcitonality out of the box that we dont need any configuration in your settings.py other than just adding them to your installed apps list. If you’d like to tweak the settings you can see them here and here.

First create a new django app of you own

django-admin startapp medialogue or if you’re my seed docker-compose run --rm web django-admin startapp medialogue

In your settings file

INSTALLED_APPS=[
    'medialogue',
    #... other apps
    'photologue',
    'sortedm2m',
    'video_encoding',
]
STATIC_URL = '/static/'
STATICFILES_DIRS = [
        BASE_DIR / "static",
        '/var/www/static/',
    ]
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = '/media/'

Model Changes

Two main things need to happen here;

  1. Create a Video Model that will have a django-video-encoding VideoField.
  2. Create a Gallery Model that inherits from photologue.models.Gallery and has a SortedManyToManyField pointing to our video model
  3. Sync the database

1) Creating a Video Model

medialogue/models.py

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
from video_encoding.fields import VideoField
from video_encoding.models import Format

class Video(models.Model):
    width = models.PositiveIntegerField(editable=False, null=True)
    height = models.PositiveIntegerField(editable=False, null=True)
    duration = models.FloatField(editable=False, null=True)
    file = VideoField(
        width_field='width',
	height_field='height',
	duration_field='duration'
	)
    format_set = GenericRelation(Format)

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

There we have it. file is the magic field here for this model. It points to a field in django-encode video that does a thing that updates my models width, height, and duration. Quite frankly, I dont know and dont care how. Yay apps!

Modify medialogue/models.py

# imports
from sortedm2m.fields import SortedManyToManyField
from photologue.models import Gallery

# add under class Video
class MediaGallery(Gallery):
    videos = SortedManyToManyField(
        'medialogue.Video',
	related_name='galleries',
	verbose_name=('videos'),
	blank=True
    )

3) Now sync your DB now

a. ./manage.py makemigrations && ./manage.py migrate b. if you’re running my seed docker project…

   docker-compose run --rm web python ./manage.py makemmigration && ./manage.py migrate

Configure the admin app

medialogue/admin.py

from django.contrib import admin
from .models import MediaGallery, Video

from video_encoding.admin import FormatInline

@admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
   inlines = (FormatInline,)

@admin.register(MediaGallery)
class MediaGalleryAdmin(admin.ModelAdmin):
    pass

Simple Manual Test

Go ahead and poke around at this point!

  • /admin/medialogue/video/ - Upload videos that will be processed into the various formats that come out of the box with django-video-encoding.
  • /admin/photologue/photo/ - Upload videos that will be processed by photolgoue into various formats.
  • /admin/medialogue/gallery/ - Create Galleries that you can add photos AND videos to.

But wait, there’s more! We have a stale model that we do not want users to add to. The photologue.Gallery is still displaying in the admin. You can remove this in your main application url file.

in root urls.py

from django.contrib import admin
from photologue.models import Gallery
admin.site.unregister(Gallery)

Rendering Galleries in a list

The Templates

/templates/base.html


{% load static %}
<!doctype html>
<html lang="en">
    <head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>{% block head_title %}Medialogue{% endblock %}</title>
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>

medialogue/templates/medialogue/mediagallery-list.html


{% extends "base.html" %}

{% block title %}All galleries{% endblock %}

{% block content %}

    <div class="row">
        <div class="col-lg-12">
            <h1>All galleries</h1>
        </div>
    </div>

    {% if object_list %}
        {% for gallery in object_list.all %}
            <div class="row">
                <div class="col-lg-12">
                    <h2>{{gallery.title}}</h2>
                    <p> Published {{gallery.date_added}}</p>
                    <ul>
                    {% for photo in gallery.photos.all %}	
                        <li><img src="{{photo.image.url}}" width="300" /></li>

                    {% endfor %}
                    {% for video in gallery.videos.all %}	
                        <li><video controls src="{{video.file.url}}" width="300"></video></li>
                    {% endfor %}
                </div>
            </div>
        {% endfor %}
    {% else %}
        <div class="row">
            <div class="col-lg-12">No galleries were found.</div>
        </div>
    {% endif %}
{% endblock %}

The Views

medialogue/views.py

from django.views.generic.list import ListView
from .models import MediaGallery

def GalleryListView(ListView):
    queryset = MediaGallery.objects.on_site().is_public() 

Add urls

in ROOT urls.py

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

urlpatterns = [
   path('admin/', admin.site.urls),
   path('photologue/', include('photologue.urls'), namespace='photologue')
   path('', include('medialogue.urls', namespace='medialogue')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

/medialogue/urls.py

from django.urls import path
from .views import GalleryListView
app_name = 'photologue'
urlpatterns = [
	path('',
        GalleryListView.as_view(),
        name='gallery-list'),
]

Conclusion

That’s pretty much it. Your galleries will now appear in a listview as the homepage. It was wayyyy easier than I thought to smash these applications together, however, I’d like to highlight some ways I would like to improvement this code, such as;

  • running time consuming tasks like thumbnail generation/video encoding in a queue like rq
  • ANY type of css/styling. Currently its a raw dump of images and videos in a list.
  • thumbnail generation for videos.
  • detail views and/or a slideshow like photoswipe. In fact there are a bunch of different specific views we can emulate.
  • Combining the videos/photos in the view and sort them by date.

Next Week

I will be diving into rq, specifically django_rq, and demonstrating how to implement this in docker-compose.

PS - shoutout to Jason Gaylord for helping me figure out how to display liquid-like tags in code blocks