delete source page, start of youtube-dl wrapper
This commit is contained in:
parent
c87adf86d5
commit
060691202b
|
@ -36,7 +36,7 @@ $form-error-text-colour: $colour-near-white;
|
||||||
$form-help-text-colour: $colour-light-blue;
|
$form-help-text-colour: $colour-light-blue;
|
||||||
$form-delete-button-background-colour: $colour-red;
|
$form-delete-button-background-colour: $colour-red;
|
||||||
|
|
||||||
$collection-no-items-text-colour: $colour-light-blue;
|
$collection-no-items-text-colour: $colour-near-black;
|
||||||
$collection-background-hover-colour: $colour-orange;
|
$collection-background-hover-colour: $colour-orange;
|
||||||
$collection-text-hover-colour: $colour-near-white;
|
$collection-text-hover-colour: $colour-near-white;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ValidateSourceForm(forms.Form):
|
class ValidateSourceForm(forms.Form):
|
||||||
|
@ -10,6 +11,14 @@ class ValidateSourceForm(forms.Form):
|
||||||
widget=forms.HiddenInput()
|
widget=forms.HiddenInput()
|
||||||
)
|
)
|
||||||
source_url = forms.URLField(
|
source_url = forms.URLField(
|
||||||
label='Source URL',
|
label=_('Source URL'),
|
||||||
required=True
|
required=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmDeleteSourceForm(forms.Form):
|
||||||
|
|
||||||
|
delete_media = forms.BooleanField(
|
||||||
|
label=_('Also delete downloaded media'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Source(models.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
URLS = {
|
URLS = {
|
||||||
SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/{key}',
|
SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/c/{key}',
|
||||||
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}',
|
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,10 +173,14 @@ class Source(models.Model):
|
||||||
def icon(self):
|
def icon(self):
|
||||||
return self.ICONS.get(self.source_type)
|
return self.ICONS.get(self.source_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_url(obj, source_type, key):
|
||||||
|
url = obj.URLS.get(source_type)
|
||||||
|
return url.format(key=key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
url = self.URLS.get(self.source_type)
|
return Source.create_url(self.source_type, self.key)
|
||||||
return url.format(key=self.key)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def directory_path(self):
|
def directory_path(self):
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block headtitle %}Delete source - {{ source.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row no-margin-bottom">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1>Delete source <strong>{{ source.name }}</strong></h1>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete this source? Deleting a source is permanent.
|
||||||
|
By default, deleting a source does not delete any saved media files. You can
|
||||||
|
tick the "also delete downloaded media" checkbox to also remove save
|
||||||
|
media when you delete the source. Deleting a source cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<form method="post" action="{% url 'sync:delete-source' pk=source.pk %}" class="col s12 simpleform">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'simpleform.html' with form=form %}
|
||||||
|
<div class="row no-margin-bottom padding-top">
|
||||||
|
<div class="col s12">
|
||||||
|
<button class="btn" type="submit" name="action">Really delete source <i class="fas fa-trash-alt"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -80,7 +80,7 @@
|
||||||
<a href="{% url 'sync:update-source' pk=source.pk %}" class="btn">Edit source <i class="fas fa-pen-square"></i></a>
|
<a href="{% url 'sync:update-source' pk=source.pk %}" class="btn">Edit source <i class="fas fa-pen-square"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 l6 margin-bottom">
|
<div class="col s12 l6 margin-bottom">
|
||||||
<a href="" class="btn delete-button">Delete source <i class="fas fa-trash-alt"></i></a>
|
<a href="{% url 'sync:delete-source' pk=source.pk %}" class="btn delete-button">Delete source <i class="fas fa-trash-alt"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
|
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
|
||||||
SourceView, UpdateSourceView, MediaView, TasksView, LogsView)
|
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
||||||
|
TasksView, LogsView)
|
||||||
|
|
||||||
|
|
||||||
app_name = 'sync'
|
app_name = 'sync'
|
||||||
|
@ -36,9 +37,13 @@ urlpatterns = [
|
||||||
UpdateSourceView.as_view(),
|
UpdateSourceView.as_view(),
|
||||||
name='update-source'),
|
name='update-source'),
|
||||||
|
|
||||||
# Media URLs
|
path('source-delete/<uuid:pk>',
|
||||||
|
DeleteSourceView.as_view(),
|
||||||
|
name='delete-source'),
|
||||||
|
|
||||||
path('media',
|
# Media URLs (note /media/ is the static media URL, don't use that)
|
||||||
|
|
||||||
|
path('mediafiles',
|
||||||
MediaView.as_view(),
|
MediaView.as_view(),
|
||||||
name='media'),
|
name='media'),
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.views.generic import TemplateView, ListView, DetailView
|
from django.views.generic import TemplateView, ListView, DetailView
|
||||||
from django.views.generic.edit import FormView, CreateView, UpdateView
|
from django.views.generic.edit import (FormView, FormMixin, CreateView, UpdateView,
|
||||||
|
DeleteView)
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from common.utils import append_uri_params
|
from common.utils import append_uri_params
|
||||||
from .models import Source
|
from .models import Source
|
||||||
from .forms import ValidateSourceForm
|
from .forms import ValidateSourceForm, ConfirmDeleteSourceForm
|
||||||
from .utils import validate_url
|
from .utils import validate_url
|
||||||
|
from . import youtube
|
||||||
|
|
||||||
|
|
||||||
class DashboardView(TemplateView):
|
class DashboardView(TemplateView):
|
||||||
|
@ -155,7 +157,7 @@ class ValidateSourceView(FormView):
|
||||||
ValidationError(self.errors['invalid_source'])
|
ValidationError(self.errors['invalid_source'])
|
||||||
)
|
)
|
||||||
source_url = form.cleaned_data['source_url']
|
source_url = form.cleaned_data['source_url']
|
||||||
validation_url = self.validation_urls.get(self.source_type)
|
validation_url = self.validation_urls.get(source_type)
|
||||||
try:
|
try:
|
||||||
self.key = validate_url(source_url, validation_url)
|
self.key = validate_url(source_url, validation_url)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
@ -246,6 +248,30 @@ class UpdateSourceView(UpdateView):
|
||||||
return append_uri_params(url, {'message': 'source-updated'})
|
return append_uri_params(url, {'message': 'source-updated'})
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteSourceView(DeleteView, FormMixin):
|
||||||
|
'''
|
||||||
|
Confirm the deletion of a source with an option to delete all the media
|
||||||
|
associated with the source from disk when the source is deleted.
|
||||||
|
'''
|
||||||
|
|
||||||
|
template_name = 'sync/source-delete.html'
|
||||||
|
model = Source
|
||||||
|
form_class = ConfirmDeleteSourceForm
|
||||||
|
context_object_name = 'source'
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
delete_media_val = request.POST.get('delete_media', False)
|
||||||
|
delete_media = True if delete_media_val is not False else False
|
||||||
|
if delete_media:
|
||||||
|
# TODO: delete media files from disk linked to this source
|
||||||
|
pass
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
url = reverse_lazy('sync:sources')
|
||||||
|
return append_uri_params(url, {'message': 'source-deleted'})
|
||||||
|
|
||||||
|
|
||||||
class MediaView(TemplateView):
|
class MediaView(TemplateView):
|
||||||
'''
|
'''
|
||||||
A bare list of media added with their states.
|
A bare list of media added with their states.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
'''
|
||||||
|
Wrapper for the youtube-dl library. Used so if there are any library interface
|
||||||
|
updates we only need to udpate them in one place.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import youtube_dl
|
||||||
|
|
||||||
|
|
||||||
|
_defaults = getattr(settings, 'YOUTUBE_DEFAULTS', {})
|
||||||
|
|
||||||
|
|
||||||
|
class YouTubeError(youtube_dl.utils.DownloadError):
|
||||||
|
'''
|
||||||
|
Generic wrapped error for all errors that could be raised by youtube-dl.
|
||||||
|
'''
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def extract_info(url):
|
||||||
|
'''
|
||||||
|
Extracts information from a YouTube URL and returns it as a dict. For a channel
|
||||||
|
or playlist this returns a dict of all the videos on the channel or playlist
|
||||||
|
as well as associated metadata.
|
||||||
|
'''
|
||||||
|
opts = _defaults.update({
|
||||||
|
'skip_download': True,
|
||||||
|
'forcejson': True,
|
||||||
|
'simulate': True,
|
||||||
|
'extract_flat': 'in_playlist',
|
||||||
|
'playlist_items': 1,
|
||||||
|
})
|
||||||
|
response = {}
|
||||||
|
with youtube_dl.YoutubeDL(opts) as y:
|
||||||
|
try:
|
||||||
|
response = y.extract_info(url, download=False)
|
||||||
|
except youtube_dl.utils.DownloadError as e:
|
||||||
|
raise YouTubeError(f'Failed to extract_info for "{url}": {e}') from e
|
||||||
|
return response
|
|
@ -119,6 +119,11 @@ DJANGO_SIMPLE_TASK_WORKERS = 2
|
||||||
SOURCES_PER_PAGE = 25
|
SOURCES_PER_PAGE = 25
|
||||||
|
|
||||||
|
|
||||||
|
YOUTUBE_DEFAULTS = {
|
||||||
|
'age_limit': 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .local_settings import *
|
from .local_settings import *
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
|
|
Loading…
Reference in New Issue