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.
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;
- Create a Video Model that will have a django-video-encoding VideoField.
- Create a Gallery Model that inherits from
photologue.models.Gallery
and has aSortedManyToManyField
pointing to our video model - 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!
2) Extending the Gallery Model
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.
Remove stale photologue Gallery from /admin/
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