From f55a31e12b6ac60ea93e3ef0bb36470b4585f4fd Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 17:48:41 +0200 Subject: [PATCH 01/29] Update packages Django to 3.0, for 1.11 and 2.2 are too old --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index e455772..be0f020 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Django==1.11 -pkg-resources==0.0.0 -pytz==2018.7 +Django==3.0.* +# pkg-resources==0.0.0 +pytz From a2839c9f912dc4876d1b49098fe30c6fddabb960 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 18:03:03 +0200 Subject: [PATCH 02/29] Fix models --- videodinges/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/videodinges/models.py b/videodinges/models.py index 92a2766..d353eec 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -7,15 +7,15 @@ Quality = namedtuple('Quality', ['name', 'width', 'height', 'priority']) qualities = ( Quality(name='360p', width=640, height=360, priority=1), Quality(name='480p', width=853, height=480, priority=2), - Quality(name='480p', width=1280, height=720, priority=2), + Quality(name='720p', width=1280, height=720, priority=2), Quality(name='1080p', width=1920, height=1080, priority=1), ) class Video(models.Model): id = models.AutoField(primary_key=True) - title = models.CharField() - slug = models.CharField() + title = models.CharField(max_length=256) + slug = models.CharField(max_length=256) description = models.TextField() created_at = models.DateTimeField(default=datetime.now) updated_at = models.DateTimeField(default=datetime.now) @@ -28,5 +28,5 @@ class Video(models.Model): class Transcoding(models.Model): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video, on_delete=models.CASCADE) - quality = models.CharField(choices=(quality.name, quality.name) for quality in qualities) + quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) file = models.FileField() From 83b3856616905e97140e16cec1c6c15f8d7fb7e7 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 18:03:10 +0200 Subject: [PATCH 03/29] Add videodinges to apps to make manage.py work --- videodinges/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/videodinges/settings.py b/videodinges/settings.py index 8d8292f..097fd62 100644 --- a/videodinges/settings.py +++ b/videodinges/settings.py @@ -37,6 +37,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'videodinges', ] MIDDLEWARE = [ From cdca198228104e624047365c7b87e85669613536 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 19:31:13 +0200 Subject: [PATCH 04/29] use better typing.NamedTuple --- videodinges/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/videodinges/models.py b/videodinges/models.py index d353eec..1afd708 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -1,8 +1,13 @@ from datetime import datetime -from collections import namedtuple +from typing import NamedTuple + from django.db import models -Quality = namedtuple('Quality', ['name', 'width', 'height', 'priority']) +class Quality(NamedTuple): + name: str + width: int + height: int + priority: int qualities = ( Quality(name='360p', width=640, height=360, priority=1), From ee8d8e0ea8a7a5ab51a05e1f386eab18a7e54420 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 19:35:51 +0200 Subject: [PATCH 05/29] Add some testviews --- videodinges/models.py | 20 ++++++++++++++++++-- videodinges/testviews.py | 36 ++++++++++++++++++++++++++++++++++++ videodinges/urls.py | 5 +++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 videodinges/testviews.py diff --git a/videodinges/models.py b/videodinges/models.py index 1afd708..1dc5d5a 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import NamedTuple +from typing import NamedTuple, Optional from django.db import models @@ -29,9 +29,25 @@ class Video(models.Model): self.updated_at = datetime.now() super().save(force_insert, force_update, using, update_fields) + def __str__(self): + return self.title + class Transcoding(models.Model): id = models.AutoField(primary_key=True) - video = models.ForeignKey(Video, on_delete=models.CASCADE) + video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) file = models.FileField() + + def __str__(self): + return self.quality + + @property + def quality_obj(self): + return get_quality_by_name(self.quality) + + +def get_quality_by_name(name: str) -> Optional[Quality]: + for quality in qualities: + if quality.name == name: + return quality diff --git a/videodinges/testviews.py b/videodinges/testviews.py new file mode 100644 index 0000000..b00002d --- /dev/null +++ b/videodinges/testviews.py @@ -0,0 +1,36 @@ +from typing import List + +from django.http import HttpResponse, HttpRequest + +from . import models + +__all__ = ['index'] + +def index(request: HttpRequest) -> HttpResponse: + videos = models.Video.objects.all() + if videos.count() == 0: + video = models.Video( + title='Test', + slug='test', + description='Test object', + ) + video.save() + else: + video = videos[0] + + if video.transcodings.count() == 0: + transcoding = models.Transcoding(video=video, quality='360p', file='somefile') + transcoding.save() + #transcodings: List[models.Transcoding] = video.transcodings.objects.all() + + videos_html = [] + for video in videos: + videos_html.append( + '
  • {title}: {transcodings}
  • '.format( + title=video.title, + transcodings=', '.join(tr.quality_obj.name for tr in video.transcodings.all()), + ) + ) + + + return HttpResponse('

    Index!

    \n
      {videos}
    '.format(videos='\n'.join(videos_html))) \ No newline at end of file diff --git a/videodinges/urls.py b/videodinges/urls.py index ef8c102..54630d6 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -16,6 +16,11 @@ Including another URLconf from django.conf.urls import url from django.contrib import admin +from . import testviews + urlpatterns = [ url(r'^admin/', admin.site.urls), ] + +for i in testviews.__all__: + urlpatterns.append(url(r'^test/{}$'.format(i), testviews.__dict__[i])) From 287f652fd2c5853b97343f57321de40cad527830 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 19:36:05 +0200 Subject: [PATCH 06/29] Add an admin --- videodinges/admin.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 videodinges/admin.py diff --git a/videodinges/admin.py b/videodinges/admin.py new file mode 100644 index 0000000..2c3a133 --- /dev/null +++ b/videodinges/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +from . import models + +class TranscodingsInline(admin.TabularInline): + model = models.Transcoding + fields = ['quality', 'file'] + extra = 0 + +class VideoAdmin(admin.ModelAdmin): + model = models.Video + fields = ['title', 'description', 'slug'] + inlines = [TranscodingsInline] + +admin.site.register(models.Video, VideoAdmin) \ No newline at end of file From ed1a16b08ae3b6f5d57d16c4ef4b90b809e7169c Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 19:48:00 +0200 Subject: [PATCH 07/29] Slug unique --- videodinges/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/videodinges/models.py b/videodinges/models.py index 1dc5d5a..e31555e 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -20,7 +20,7 @@ qualities = ( class Video(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=256) - slug = models.CharField(max_length=256) + slug = models.CharField(max_length=256, unique=True) description = models.TextField() created_at = models.DateTimeField(default=datetime.now) updated_at = models.DateTimeField(default=datetime.now) From 2d6e26e4741bb6fcd784c0842720fe5793a8208e Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 19:48:11 +0200 Subject: [PATCH 08/29] video and quality unique --- videodinges/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/videodinges/models.py b/videodinges/models.py index e31555e..82b907c 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -46,6 +46,8 @@ class Transcoding(models.Model): def quality_obj(self): return get_quality_by_name(self.quality) + class Meta: + unique_together = ('video', 'quality') def get_quality_by_name(name: str) -> Optional[Quality]: for quality in qualities: From 11a7a736c230a0137a79ccc6bc617536ddca7768 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 20:26:44 +0200 Subject: [PATCH 09/29] Add proper file storage settings --- videodinges/settings.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/videodinges/settings.py b/videodinges/settings.py index 097fd62..99afb1a 100644 --- a/videodinges/settings.py +++ b/videodinges/settings.py @@ -119,3 +119,16 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' + +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] + +MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') + +MEDIA_URL = '/uploads/' + +FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler'] + +FILE_UPLOAD_MAX_MEMORY_SIZE = 2147483648 # 2GB + +FILE_UPLOAD_TEMP_DIR = os.path.join(BASE_DIR, 'tmp') # probably default /tmp is too small for video files + From 297a16a0b319d2c3d7f0ea2225bf60f80615d6a4 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 20:27:14 +0200 Subject: [PATCH 10/29] dirs for file storage --- static/.placeholder | 0 tmp/.placeholder | 0 uploads/.placeholder | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/.placeholder create mode 100644 tmp/.placeholder create mode 100644 uploads/.placeholder diff --git a/static/.placeholder b/static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/tmp/.placeholder b/tmp/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/uploads/.placeholder b/uploads/.placeholder new file mode 100644 index 0000000..e69de29 From 2b5785444f3eb3efca9222f59546925a00e53907 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 21:36:09 +0200 Subject: [PATCH 11/29] Added better upload model --- videodinges/admin.py | 23 ++++++++++++++++++++--- videodinges/models.py | 17 ++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/videodinges/admin.py b/videodinges/admin.py index 2c3a133..21431d5 100644 --- a/videodinges/admin.py +++ b/videodinges/admin.py @@ -1,10 +1,26 @@ +from django import forms from django.contrib import admin +from django.core.exceptions import ValidationError from . import models -class TranscodingsInline(admin.TabularInline): +class TranscodingsForm(forms.ModelForm): + def clean(self): + cleaned_data = super().clean() + if not cleaned_data['url'] and not cleaned_data['upload']: + validation_error = ValidationError('Either url or upload must be given', code='invalid') + self.add_error('url', validation_error) + self.add_error('upload', validation_error) + if cleaned_data['url'] and cleaned_data['upload']: + validation_error = ValidationError('Cannot fill both url and upload', code='invalid') + self.add_error('url', validation_error) + self.add_error('upload', validation_error) + return cleaned_data + +class TranscodingsInline(admin.StackedInline): model = models.Transcoding - fields = ['quality', 'file'] + form = TranscodingsForm + fields = ['quality', 'url', 'upload'] extra = 0 class VideoAdmin(admin.ModelAdmin): @@ -12,4 +28,5 @@ class VideoAdmin(admin.ModelAdmin): fields = ['title', 'description', 'slug'] inlines = [TranscodingsInline] -admin.site.register(models.Video, VideoAdmin) \ No newline at end of file +admin.site.register(models.Video, VideoAdmin) +admin.site.register(models.Upload) diff --git a/videodinges/models.py b/videodinges/models.py index 82b907c..4bd2779 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -1,7 +1,10 @@ +import os from datetime import datetime from typing import NamedTuple, Optional from django.db import models +from django.db.models import constraints +from django.db.models.query_utils import Q class Quality(NamedTuple): name: str @@ -32,12 +35,19 @@ class Video(models.Model): def __str__(self): return self.title +class Upload(models.Model): + id = models.AutoField(primary_key=True) + file = models.FileField() + + def __str__(self): + return os.path.basename(self.file.path) class Transcoding(models.Model): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) - file = models.FileField() + upload = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True) + url = models.CharField(max_length=256, null=True, blank=True, unique=True) def __str__(self): return self.quality @@ -48,6 +58,11 @@ class Transcoding(models.Model): class Meta: unique_together = ('video', 'quality') + constraints = [constraints.CheckConstraint(check=Q(upload__isnull=False) | Q(url__isnull=False), + name='upload_or_url_is_filled'), + constraints.CheckConstraint(check=~(Q(upload__isnull=False) & Q(url__isnull=False)), + name='upload_and_url_cannot_both_be_filled'), + ] def get_quality_by_name(name: str) -> Optional[Quality]: for quality in qualities: From fcabf013e081cd745439c2d87b975e92cdc755e4 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 22:12:13 +0200 Subject: [PATCH 12/29] Added transcoding types --- videodinges/admin.py | 2 +- videodinges/models.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/videodinges/admin.py b/videodinges/admin.py index 21431d5..ad40a16 100644 --- a/videodinges/admin.py +++ b/videodinges/admin.py @@ -20,7 +20,7 @@ class TranscodingsForm(forms.ModelForm): class TranscodingsInline(admin.StackedInline): model = models.Transcoding form = TranscodingsForm - fields = ['quality', 'url', 'upload'] + fields = ['quality', 'type', 'url', 'upload'] extra = 0 class VideoAdmin(admin.ModelAdmin): diff --git a/videodinges/models.py b/videodinges/models.py index 4bd2779..461aea6 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -12,6 +12,13 @@ class Quality(NamedTuple): height: int priority: int +class TranscodingType(NamedTuple): + name: str + short_name: str + + def __str__(self): + return self.name + qualities = ( Quality(name='360p', width=640, height=360, priority=1), Quality(name='480p', width=853, height=480, priority=2), @@ -19,6 +26,13 @@ qualities = ( Quality(name='1080p', width=1920, height=1080, priority=1), ) +transcoding_types = ( + TranscodingType(name='video/webm', short_name='vp8'), + TranscodingType(name='video/webm; codecs="vp8, vorbis"', short_name='vp8'), + TranscodingType(name='video/webm; codecs="vp9, opus"', short_name='vp9'), + TranscodingType(name='video/mp4', short_name='h.264'), + TranscodingType(name='video/mp4; codecs="avc1.64001f,mp4a.40.2"', short_name='h.264'), +) class Video(models.Model): id = models.AutoField(primary_key=True) @@ -46,6 +60,7 @@ class Transcoding(models.Model): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) + type = models.CharField(choices=((str(type_), str(type_)) for type_ in transcoding_types), max_length=128) upload = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True) url = models.CharField(max_length=256, null=True, blank=True, unique=True) @@ -57,7 +72,7 @@ class Transcoding(models.Model): return get_quality_by_name(self.quality) class Meta: - unique_together = ('video', 'quality') + unique_together = ('video', 'quality', 'type') constraints = [constraints.CheckConstraint(check=Q(upload__isnull=False) | Q(url__isnull=False), name='upload_or_url_is_filled'), constraints.CheckConstraint(check=~(Q(upload__isnull=False) & Q(url__isnull=False)), From 427bf3ac82d49b077b950b11fe109a9d90972ad9 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 23:29:57 +0200 Subject: [PATCH 13/29] Add get_short_name_of_transcoding_type() --- videodinges/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/videodinges/models.py b/videodinges/models.py index 461aea6..b534533 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -1,6 +1,6 @@ import os from datetime import datetime -from typing import NamedTuple, Optional +from typing import NamedTuple, Optional, Union from django.db import models from django.db.models import constraints @@ -83,3 +83,12 @@ def get_quality_by_name(name: str) -> Optional[Quality]: for quality in qualities: if quality.name == name: return quality + +def get_short_name_of_transcoding_type(transcoding_type: Union[str, TranscodingType]) -> str: + if isinstance(transcoding_type, str): + for type_ in transcoding_types: + if type_.name == transcoding_type: + transcoding_type = type_ + + if isinstance(transcoding_type, TranscodingType): + return transcoding_type.short_name \ No newline at end of file From f5be1b515695cee86bb7f7cc18ff7ee1ef9b6768 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Mon, 4 May 2020 23:40:14 +0200 Subject: [PATCH 14/29] Added poster and og_image --- videodinges/admin.py | 2 +- videodinges/models.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/videodinges/admin.py b/videodinges/admin.py index ad40a16..72ee9b8 100644 --- a/videodinges/admin.py +++ b/videodinges/admin.py @@ -25,7 +25,7 @@ class TranscodingsInline(admin.StackedInline): class VideoAdmin(admin.ModelAdmin): model = models.Video - fields = ['title', 'description', 'slug'] + fields = ['title', 'description', 'slug', 'poster', 'og_image', 'created_at'] inlines = [TranscodingsInline] admin.site.register(models.Video, VideoAdmin) diff --git a/videodinges/models.py b/videodinges/models.py index b534533..9d4437a 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -34,11 +34,20 @@ transcoding_types = ( TranscodingType(name='video/mp4; codecs="avc1.64001f,mp4a.40.2"', short_name='h.264'), ) +class Upload(models.Model): + id = models.AutoField(primary_key=True) + file = models.FileField() + + def __str__(self): + return os.path.basename(self.file.path) + class Video(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=256) slug = models.CharField(max_length=256, unique=True) description = models.TextField() + poster = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_poster') + og_image = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_og_image') created_at = models.DateTimeField(default=datetime.now) updated_at = models.DateTimeField(default=datetime.now) @@ -49,13 +58,6 @@ class Video(models.Model): def __str__(self): return self.title -class Upload(models.Model): - id = models.AutoField(primary_key=True) - file = models.FileField() - - def __str__(self): - return os.path.basename(self.file.path) - class Transcoding(models.Model): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') From 3a06e4ec9f50507dac6bfd64f565985cc258ed3d Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 12:16:15 +0200 Subject: [PATCH 15/29] Implement video view page --- static/js/video.js | 24 +++++++++++ videodinges/models.py | 3 ++ videodinges/settings.py | 6 +++ videodinges/templates/video.html.j2 | 31 ++++++++++++++ videodinges/urls.py | 3 +- videodinges/views.py | 66 +++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 static/js/video.js create mode 100644 videodinges/templates/video.html.j2 create mode 100644 videodinges/views.py diff --git a/static/js/video.js b/static/js/video.js new file mode 100644 index 0000000..716009b --- /dev/null +++ b/static/js/video.js @@ -0,0 +1,24 @@ +function init() { + + var hash = document.location.hash; + var res = hash.match(/t=([0-9]+)/); + if (res) { + var vids = document.getElementsByTagName('video'); + // only first video + var vid = vids[0]; + vid.currentTime = res[1]; + vid.autoplay = true; + } +} + +init(); + +function vidTimeInUrl(el) { + var vids = document.getElementsByTagName('video'); + // only first video + var vid = vids[0]; + var currentTime = vid.currentTime; + var href = el.href + "#t=" + parseInt(currentTime); + el.href = href; + return true; +} diff --git a/videodinges/models.py b/videodinges/models.py index 9d4437a..994c484 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -58,6 +58,9 @@ class Video(models.Model): def __str__(self): return self.title + class Meta: + indexes = [models.Index(fields=['slug']), models.Index(fields=['created_at'])] + class Transcoding(models.Model): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') diff --git a/videodinges/settings.py b/videodinges/settings.py index 99afb1a..099943e 100644 --- a/videodinges/settings.py +++ b/videodinges/settings.py @@ -66,6 +66,12 @@ TEMPLATES = [ ], }, }, + { + 'BACKEND': 'django.template.backends.jinja2.Jinja2', + 'DIRS': [os.path.join(BASE_DIR, 'videodinges', 'templates')], + 'APP_DIRS': False, + 'OPTIONS': {}, + }, ] WSGI_APPLICATION = 'videodinges.wsgi.application' diff --git a/videodinges/templates/video.html.j2 b/videodinges/templates/video.html.j2 new file mode 100644 index 0000000..4bd7555 --- /dev/null +++ b/videodinges/templates/video.html.j2 @@ -0,0 +1,31 @@ + + + {# #} + {% if og_image %} + + {% endif %} + {{ title }} + + +

    {{ title }}

    +
    +

    +{% for quality in qualities %} +{{ quality }} versie{% if not loop.last %} | {% endif %} +{% endfor %} +

    +

    +{{ description|safe }} +

    +
    +
    0 comments total
    +
    + + + + \ No newline at end of file diff --git a/videodinges/urls.py b/videodinges/urls.py index 54630d6..c1121d3 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -16,10 +16,11 @@ Including another URLconf from django.conf.urls import url from django.contrib import admin -from . import testviews +from . import testviews, views urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'^(?P[\w-]+).html', views.video) ] for i in testviews.__all__: diff --git a/videodinges/views.py b/videodinges/views.py new file mode 100644 index 0000000..585b9de --- /dev/null +++ b/videodinges/views.py @@ -0,0 +1,66 @@ +from collections import defaultdict +from typing import List, Dict, Any + +from django.http import HttpResponse, HttpRequest, Http404 +from django.shortcuts import render +from . import models + +def video(request: HttpRequest, slug: str) -> HttpResponse: + try: + video = models.Video.objects.get(slug=slug) + except models.Video.DoesNotExist: + raise Http404('Video not found') + + template_data = dict( + og_image=video.og_image.file.url if video.og_image else None, + title=video.title, + poster=video.poster.file.url if video.poster else None, + description=video.description, + slug=video.slug + ) + + qualities = _get_qualities(video) + try: + quality = qualities[request.GET['quality']] + except: + quality = next(iter(qualities.values())) + + template_data.update( + width=quality[0].quality_obj.width, + height=quality[0].quality_obj.height, + ) + + template_data['sources'] = [ + { + 'src': _url_for(transcoding), + 'type': transcoding.type, + } + for transcoding in quality ] + + template_data['used_codecs'] = [ + models.get_short_name_of_transcoding_type(transcoding.type) + for transcoding in quality + ] + + template_data['qualities'] = qualities.keys() + + return render(request, 'video.html.j2', template_data, using='jinja2') + +def _get_dict_from_models_with_fields(model, *fields: str) -> Dict[str, Any]: + ret = {} + for field in fields: + ret[field] = model.__dict__[field] + return ret + +def _get_qualities(video: models.Video) -> Dict[str, List[models.Transcoding]]: + transcodings: List[models.Transcoding] = video.transcodings.order_by('quality').all() + qualities = defaultdict(list) + for transcoding in transcodings: + qualities[transcoding.quality_obj.name].append(transcoding) + return qualities + +def _url_for(transcoding: models.Transcoding) -> str: + if transcoding.url: + return transcoding.url + elif transcoding.upload: + return transcoding.upload.file.url From 4bff78d0ac0f06ef509726c384d0ca25d83f2d5c Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 12:36:23 +0200 Subject: [PATCH 16/29] Fix uploads serving --- videodinges/urls.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/videodinges/urls.py b/videodinges/urls.py index c1121d3..16fac84 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -13,6 +13,8 @@ Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ +from django.conf import settings +from django.conf.urls.static import static from django.conf.urls import url from django.contrib import admin @@ -23,5 +25,7 @@ urlpatterns = [ url(r'^(?P[\w-]+).html', views.video) ] +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + for i in testviews.__all__: urlpatterns.append(url(r'^test/{}$'.format(i), testviews.__dict__[i])) From 8140a77e61625ae5a793f7852250c1163a02e4c0 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 12:36:40 +0200 Subject: [PATCH 17/29] Fix defaultdict problem --- videodinges/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/videodinges/views.py b/videodinges/views.py index 585b9de..0945f7f 100644 --- a/videodinges/views.py +++ b/videodinges/views.py @@ -57,7 +57,7 @@ def _get_qualities(video: models.Video) -> Dict[str, List[models.Transcoding]]: qualities = defaultdict(list) for transcoding in transcodings: qualities[transcoding.quality_obj.name].append(transcoding) - return qualities + return dict(qualities) def _url_for(transcoding: models.Transcoding) -> str: if transcoding.url: From 8f57e6c0cf7e396f5e4fdaac562c8f55ae3b546e Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 13:10:30 +0200 Subject: [PATCH 18/29] Highlight current quality --- videodinges/templates/video.html.j2 | 7 ++++++- videodinges/views.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/videodinges/templates/video.html.j2 b/videodinges/templates/video.html.j2 index 4bd7555..79b0330 100644 --- a/videodinges/templates/video.html.j2 +++ b/videodinges/templates/video.html.j2 @@ -16,7 +16,12 @@

    {% for quality in qualities %} -{{ quality }} versie{% if not loop.last %} | {% endif %} + {% if quality == current_quality %} + {{ quality }} versie + {% else %} + {{ quality }} versie + {% endif %} + {% if not loop.last %}|{% endif %} {% endfor %}

    diff --git a/videodinges/views.py b/videodinges/views.py index 0945f7f..a5e17ef 100644 --- a/videodinges/views.py +++ b/videodinges/views.py @@ -28,6 +28,7 @@ def video(request: HttpRequest, slug: str) -> HttpResponse: template_data.update( width=quality[0].quality_obj.width, height=quality[0].quality_obj.height, + current_quality=quality[0].quality_obj.name ) template_data['sources'] = [ From 1634bf33c2b3a90cf929fbcd1f9e9242ad5a1e59 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 13:14:51 +0200 Subject: [PATCH 19/29] Useful video list view in admin --- videodinges/admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/videodinges/admin.py b/videodinges/admin.py index 72ee9b8..ae6c088 100644 --- a/videodinges/admin.py +++ b/videodinges/admin.py @@ -27,6 +27,8 @@ class VideoAdmin(admin.ModelAdmin): model = models.Video fields = ['title', 'description', 'slug', 'poster', 'og_image', 'created_at'] inlines = [TranscodingsInline] + list_display = ('title', 'slug', 'created_at', 'updated_at') + ordering = ('-created_at', ) admin.site.register(models.Video, VideoAdmin) admin.site.register(models.Upload) From be747db93fa9edb99b23ff7a382c64a09c895a4b Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 13:31:12 +0200 Subject: [PATCH 20/29] Ability to specify default quality for video --- videodinges/admin.py | 2 +- videodinges/models.py | 2 ++ videodinges/views.py | 8 +++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/videodinges/admin.py b/videodinges/admin.py index ae6c088..bd352ae 100644 --- a/videodinges/admin.py +++ b/videodinges/admin.py @@ -25,7 +25,7 @@ class TranscodingsInline(admin.StackedInline): class VideoAdmin(admin.ModelAdmin): model = models.Video - fields = ['title', 'description', 'slug', 'poster', 'og_image', 'created_at'] + fields = ['title', 'description', 'slug', 'poster', 'og_image', 'created_at', 'default_quality'] inlines = [TranscodingsInline] list_display = ('title', 'slug', 'created_at', 'updated_at') ordering = ('-created_at', ) diff --git a/videodinges/models.py b/videodinges/models.py index 994c484..d77123e 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -48,6 +48,8 @@ class Video(models.Model): description = models.TextField() poster = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_poster') og_image = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_og_image') + default_quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), + max_length=128, blank=True, null=True) created_at = models.DateTimeField(default=datetime.now) updated_at = models.DateTimeField(default=datetime.now) diff --git a/videodinges/views.py b/videodinges/views.py index a5e17ef..43cffb2 100644 --- a/videodinges/views.py +++ b/videodinges/views.py @@ -21,9 +21,15 @@ def video(request: HttpRequest, slug: str) -> HttpResponse: qualities = _get_qualities(video) try: + # find quality specified by URL param quality = qualities[request.GET['quality']] except: - quality = next(iter(qualities.values())) + # find quality specified by default quality specified for video + try: + quality = qualities[video.default_quality] + except: + # take default first quality + quality = next(iter(qualities.values())) template_data.update( width=quality[0].quality_obj.width, From fc7af4fa7808fc54c7f5d25bf5abfd58bd6c6ba5 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 13:50:15 +0200 Subject: [PATCH 21/29] Simple index page --- videodinges/templates/index.html.j2 | 10 ++++++++++ videodinges/urls.py | 1 + videodinges/views.py | 4 ++++ 3 files changed, 15 insertions(+) create mode 100644 videodinges/templates/index.html.j2 diff --git a/videodinges/templates/index.html.j2 b/videodinges/templates/index.html.j2 new file mode 100644 index 0000000..df10823 --- /dev/null +++ b/videodinges/templates/index.html.j2 @@ -0,0 +1,10 @@ + + +

    Video's

    + + + \ No newline at end of file diff --git a/videodinges/urls.py b/videodinges/urls.py index 16fac84..9ae9eee 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -22,6 +22,7 @@ from . import testviews, views urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'^$', views.index), url(r'^(?P[\w-]+).html', views.video) ] diff --git a/videodinges/views.py b/videodinges/views.py index 43cffb2..39bad45 100644 --- a/videodinges/views.py +++ b/videodinges/views.py @@ -53,6 +53,10 @@ def video(request: HttpRequest, slug: str) -> HttpResponse: return render(request, 'video.html.j2', template_data, using='jinja2') +def index(request: HttpRequest) -> HttpResponse: + videos = models.Video.objects.order_by('-created_at').all() + return render(request, 'index.html.j2', dict(videos=videos), using='jinja2') + def _get_dict_from_models_with_fields(model, *fields: str) -> Dict[str, Any]: ret = {} for field in fields: From b8221ba26191fc5bcabccfdf83880304a71887f1 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 13:59:43 +0200 Subject: [PATCH 22/29] Custom table names better in my opinion. especially if we once decide to rename the django app --- videodinges/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/videodinges/models.py b/videodinges/models.py index d77123e..4417967 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -41,6 +41,9 @@ class Upload(models.Model): def __str__(self): return os.path.basename(self.file.path) + class Meta: + db_table = 'uploads' + class Video(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=256) @@ -62,6 +65,7 @@ class Video(models.Model): class Meta: indexes = [models.Index(fields=['slug']), models.Index(fields=['created_at'])] + db_table = 'videos' class Transcoding(models.Model): id = models.AutoField(primary_key=True) @@ -85,6 +89,7 @@ class Transcoding(models.Model): constraints.CheckConstraint(check=~(Q(upload__isnull=False) & Q(url__isnull=False)), name='upload_and_url_cannot_both_be_filled'), ] + db_table = 'transcodings' def get_quality_by_name(name: str) -> Optional[Quality]: for quality in qualities: From 8d807454692164e101af2b8d25c3828a23b4a9d0 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 14:20:04 +0200 Subject: [PATCH 23/29] Add Jinja2 to requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index be0f020..462f705 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ Django==3.0.* -# pkg-resources==0.0.0 pytz +Jinja2==2.11.* From 7daff47d5c5698c3e48bc380e61f7adc62f053e0 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 14:25:03 +0200 Subject: [PATCH 24/29] No prefixing slash in URLs --- videodinges/settings.py | 4 ++-- videodinges/templates/video.html.j2 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/videodinges/settings.py b/videodinges/settings.py index 099943e..22fc105 100644 --- a/videodinges/settings.py +++ b/videodinges/settings.py @@ -124,13 +124,13 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = 'static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') -MEDIA_URL = '/uploads/' +MEDIA_URL = 'uploads/' FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler'] diff --git a/videodinges/templates/video.html.j2 b/videodinges/templates/video.html.j2 index 79b0330..4dc8453 100644 --- a/videodinges/templates/video.html.j2 +++ b/videodinges/templates/video.html.j2 @@ -31,6 +31,6 @@
    0 comments total
    - + \ No newline at end of file From 1ee58f1b66f4729cf4e1bd8b4164c00cd5be1b8d Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 14:28:52 +0200 Subject: [PATCH 25/29] Reorganized settings ability to override with localsettings --- videodinges/settings/__init__.py | 4 ++++ .../{settings.py => settings/defaultsettings.py} | 3 +-- videodinges/settings/localsettings-example.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 videodinges/settings/__init__.py rename videodinges/{settings.py => settings/defaultsettings.py} (97%) create mode 100644 videodinges/settings/localsettings-example.py diff --git a/videodinges/settings/__init__.py b/videodinges/settings/__init__.py new file mode 100644 index 0000000..15c964f --- /dev/null +++ b/videodinges/settings/__init__.py @@ -0,0 +1,4 @@ +try: + from .localsettings import * +except ImportError: + from .defaultsettings import * \ No newline at end of file diff --git a/videodinges/settings.py b/videodinges/settings/defaultsettings.py similarity index 97% rename from videodinges/settings.py rename to videodinges/settings/defaultsettings.py index 22fc105..2533020 100644 --- a/videodinges/settings.py +++ b/videodinges/settings/defaultsettings.py @@ -13,7 +13,7 @@ https://docs.djangoproject.com/en/1.11/ref/settings/ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..', '..', '..')) # Quick-start development settings - unsuitable for production @@ -137,4 +137,3 @@ FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHand FILE_UPLOAD_MAX_MEMORY_SIZE = 2147483648 # 2GB FILE_UPLOAD_TEMP_DIR = os.path.join(BASE_DIR, 'tmp') # probably default /tmp is too small for video files - diff --git a/videodinges/settings/localsettings-example.py b/videodinges/settings/localsettings-example.py new file mode 100644 index 0000000..df7a467 --- /dev/null +++ b/videodinges/settings/localsettings-example.py @@ -0,0 +1,15 @@ +""" + Copy this file to localsettings.py to make local overrides. + BEWARE to always import defaultsettings as well if activate this file. +""" + +from .defaultsettings import * + +DATABASES['default'] = { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'videos', # database name + 'USER': 'videos', + 'PASSWORD': 'v3r7s3cr3t', + 'HOST': 'localhost', + 'PORT': '5432', +} From c6da8766202e682f88c98d75c95fb4f1c1db5b0a Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 14:48:04 +0200 Subject: [PATCH 26/29] Fix URLs for else the admin wont work --- videodinges/settings/defaultsettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/videodinges/settings/defaultsettings.py b/videodinges/settings/defaultsettings.py index 2533020..634bf87 100644 --- a/videodinges/settings/defaultsettings.py +++ b/videodinges/settings/defaultsettings.py @@ -124,13 +124,13 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') -MEDIA_URL = 'uploads/' +MEDIA_URL = '/uploads/' FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler'] From 24edb7717a4648097e3f1c05295037186ac71ed5 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Tue, 5 May 2020 16:26:19 +0200 Subject: [PATCH 27/29] Ability to use a base URL usefull to prefix the application URL on deployment --- videodinges/settings/defaultsettings.py | 2 ++ videodinges/urls.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/videodinges/settings/defaultsettings.py b/videodinges/settings/defaultsettings.py index 634bf87..3d99790 100644 --- a/videodinges/settings/defaultsettings.py +++ b/videodinges/settings/defaultsettings.py @@ -137,3 +137,5 @@ FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHand FILE_UPLOAD_MAX_MEMORY_SIZE = 2147483648 # 2GB FILE_UPLOAD_TEMP_DIR = os.path.join(BASE_DIR, 'tmp') # probably default /tmp is too small for video files + +URL_BASE = '' # usefull to prefix the application URL on deployment diff --git a/videodinges/urls.py b/videodinges/urls.py index 9ae9eee..e593ef0 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -17,16 +17,22 @@ from django.conf import settings from django.conf.urls.static import static from django.conf.urls import url from django.contrib import admin +from django.urls import include from . import testviews, views -urlpatterns = [ +_urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', views.index), url(r'^(?P[\w-]+).html', views.video) ] -urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +_urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) for i in testviews.__all__: - urlpatterns.append(url(r'^test/{}$'.format(i), testviews.__dict__[i])) + _urlpatterns.append(url(r'^test/{}$'.format(i), testviews.__dict__[i])) + +if settings.URL_BASE: + urlpatterns = [url(r'^{}/'.format(settings.URL_BASE), include(_urlpatterns))] +else: + urlpatterns = _urlpatterns From 04c96bcb5324501867626a5ac3ba2991aed0bfc1 Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Wed, 20 May 2020 14:09:36 +0200 Subject: [PATCH 28/29] Remove testviews --- videodinges/testviews.py | 36 ------------------------------------ videodinges/urls.py | 5 +---- 2 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 videodinges/testviews.py diff --git a/videodinges/testviews.py b/videodinges/testviews.py deleted file mode 100644 index b00002d..0000000 --- a/videodinges/testviews.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import List - -from django.http import HttpResponse, HttpRequest - -from . import models - -__all__ = ['index'] - -def index(request: HttpRequest) -> HttpResponse: - videos = models.Video.objects.all() - if videos.count() == 0: - video = models.Video( - title='Test', - slug='test', - description='Test object', - ) - video.save() - else: - video = videos[0] - - if video.transcodings.count() == 0: - transcoding = models.Transcoding(video=video, quality='360p', file='somefile') - transcoding.save() - #transcodings: List[models.Transcoding] = video.transcodings.objects.all() - - videos_html = [] - for video in videos: - videos_html.append( - '
  • {title}: {transcodings}
  • '.format( - title=video.title, - transcodings=', '.join(tr.quality_obj.name for tr in video.transcodings.all()), - ) - ) - - - return HttpResponse('

    Index!

    \n
      {videos}
    '.format(videos='\n'.join(videos_html))) \ No newline at end of file diff --git a/videodinges/urls.py b/videodinges/urls.py index e593ef0..c75f719 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -19,7 +19,7 @@ from django.conf.urls import url from django.contrib import admin from django.urls import include -from . import testviews, views +from . import views _urlpatterns = [ url(r'^admin/', admin.site.urls), @@ -29,9 +29,6 @@ _urlpatterns = [ _urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -for i in testviews.__all__: - _urlpatterns.append(url(r'^test/{}$'.format(i), testviews.__dict__[i])) - if settings.URL_BASE: urlpatterns = [url(r'^{}/'.format(settings.URL_BASE), include(_urlpatterns))] else: From 88de77b261b3457419c0d9515b9ae933ada5c67b Mon Sep 17 00:00:00 2001 From: Bastiaan Welmers Date: Thu, 21 May 2020 16:55:33 +0200 Subject: [PATCH 29/29] Convert remaining space indentings to tabs --- manage.py | 34 ++++----- videodinges/models.py | 91 ++++++++++++----------- videodinges/settings/defaultsettings.py | 98 ++++++++++++------------- videodinges/urls.py | 20 ++--- 4 files changed, 126 insertions(+), 117 deletions(-) diff --git a/manage.py b/manage.py index 8d28da1..9e1fd57 100755 --- a/manage.py +++ b/manage.py @@ -3,20 +3,20 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "videodinges.settings") - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise - execute_from_command_line(sys.argv) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "videodinges.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/videodinges/models.py b/videodinges/models.py index 4417967..8012c9a 100644 --- a/videodinges/models.py +++ b/videodinges/models.py @@ -20,10 +20,10 @@ class TranscodingType(NamedTuple): return self.name qualities = ( - Quality(name='360p', width=640, height=360, priority=1), - Quality(name='480p', width=853, height=480, priority=2), - Quality(name='720p', width=1280, height=720, priority=2), - Quality(name='1080p', width=1920, height=1080, priority=1), + Quality(name='360p', width=640, height=360, priority=1), + Quality(name='480p', width=853, height=480, priority=2), + Quality(name='720p', width=1280, height=720, priority=2), + Quality(name='1080p', width=1920, height=1080, priority=1), ) transcoding_types = ( @@ -45,51 +45,60 @@ class Upload(models.Model): db_table = 'uploads' class Video(models.Model): - id = models.AutoField(primary_key=True) - title = models.CharField(max_length=256) - slug = models.CharField(max_length=256, unique=True) - description = models.TextField() - poster = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_poster') - og_image = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_og_image') - default_quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), - max_length=128, blank=True, null=True) - created_at = models.DateTimeField(default=datetime.now) - updated_at = models.DateTimeField(default=datetime.now) + id = models.AutoField(primary_key=True) + title = models.CharField(max_length=256) + slug = models.CharField(max_length=256, unique=True) + description = models.TextField() + poster = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_poster') + og_image = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_og_image') + default_quality = models.CharField( + choices=((quality.name, quality.name) for quality in qualities), + max_length=128, + blank=True, + null=True + ) + created_at = models.DateTimeField(default=datetime.now) + updated_at = models.DateTimeField(default=datetime.now) - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - self.updated_at = datetime.now() - super().save(force_insert, force_update, using, update_fields) + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + self.updated_at = datetime.now() + super().save(force_insert, force_update, using, update_fields) - def __str__(self): - return self.title + def __str__(self): + return self.title - class Meta: - indexes = [models.Index(fields=['slug']), models.Index(fields=['created_at'])] - db_table = 'videos' + class Meta: + indexes = [models.Index(fields=['slug']), models.Index(fields=['created_at'])] + db_table = 'videos' class Transcoding(models.Model): - id = models.AutoField(primary_key=True) - video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') - quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) - type = models.CharField(choices=((str(type_), str(type_)) for type_ in transcoding_types), max_length=128) - upload = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True) - url = models.CharField(max_length=256, null=True, blank=True, unique=True) + id = models.AutoField(primary_key=True) + video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings') + quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128) + type = models.CharField(choices=((str(type_), str(type_)) for type_ in transcoding_types), max_length=128) + upload = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True) + url = models.CharField(max_length=256, null=True, blank=True, unique=True) - def __str__(self): - return self.quality + def __str__(self): + return self.quality - @property - def quality_obj(self): - return get_quality_by_name(self.quality) + @property + def quality_obj(self): + return get_quality_by_name(self.quality) - class Meta: - unique_together = ('video', 'quality', 'type') - constraints = [constraints.CheckConstraint(check=Q(upload__isnull=False) | Q(url__isnull=False), - name='upload_or_url_is_filled'), - constraints.CheckConstraint(check=~(Q(upload__isnull=False) & Q(url__isnull=False)), - name='upload_and_url_cannot_both_be_filled'), - ] - db_table = 'transcodings' + class Meta: + unique_together = ('video', 'quality', 'type') + constraints = [ + constraints.CheckConstraint( + check=Q(upload__isnull=False) | Q(url__isnull=False), + name='upload_or_url_is_filled' + ), + constraints.CheckConstraint( + check=~(Q(upload__isnull=False) & Q(url__isnull=False)), + name='upload_and_url_cannot_both_be_filled' + ), + ] + db_table = 'transcodings' def get_quality_by_name(name: str) -> Optional[Quality]: for quality in qualities: diff --git a/videodinges/settings/defaultsettings.py b/videodinges/settings/defaultsettings.py index 3d99790..1eb64db 100644 --- a/videodinges/settings/defaultsettings.py +++ b/videodinges/settings/defaultsettings.py @@ -31,47 +31,47 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'videodinges', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'videodinges', ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'videodinges.urls' TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, - { - 'BACKEND': 'django.template.backends.jinja2.Jinja2', - 'DIRS': [os.path.join(BASE_DIR, 'videodinges', 'templates')], - 'APP_DIRS': False, - 'OPTIONS': {}, - }, + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, + { + 'BACKEND': 'django.template.backends.jinja2.Jinja2', + 'DIRS': [os.path.join(BASE_DIR, 'videodinges', 'templates')], + 'APP_DIRS': False, + 'OPTIONS': {}, + }, ] WSGI_APPLICATION = 'videodinges.wsgi.application' @@ -81,10 +81,10 @@ WSGI_APPLICATION = 'videodinges.wsgi.application' # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } } @@ -92,18 +92,18 @@ DATABASES = { # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, ] diff --git a/videodinges/urls.py b/videodinges/urls.py index c75f719..7f43c21 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -1,17 +1,17 @@ """videodinges URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.11/topics/http/urls/ + https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf import settings from django.conf.urls.static import static @@ -22,9 +22,9 @@ from django.urls import include from . import views _urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^$', views.index), - url(r'^(?P[\w-]+).html', views.video) + url(r'^admin/', admin.site.urls), + url(r'^$', views.index), + url(r'^(?P[\w-]+).html', views.video) ] _urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)