add app/static and app/media
This commit is contained in:
parent
37d390c8d8
commit
cde919422f
|
@ -60,6 +60,8 @@ coverage.xml
|
|||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
/app/static/
|
||||
/app/media/
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
|
|
|
@ -31,6 +31,7 @@ $form-input-border-active-colour: $colour-orange;
|
|||
$form-select-border-colour: $colour-light-blue;
|
||||
$form-error-background-colour: $colour-red;
|
||||
$form-error-text-colour: $colour-near-white;
|
||||
$form-help-text-colour: $colour-light-blue;
|
||||
|
||||
$box-error-background-colour: $colour-red;
|
||||
$box-error-text-colour: $colour-white;
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
.row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.help-text {
|
||||
color: $form-help-text-colour;
|
||||
padding: 1rem 0 1rem 0;
|
||||
}
|
||||
label {
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
original code under a GPLv3 licence is available at
|
||||
<a href="https://github.com/meeb/tubesync" class="nowrap"><i class="fab fa-github"></i> https://github.com/meeb/tubesync</a>.
|
||||
</p>
|
||||
<p>TubeSync version {{ app_version }} with embedded <a href="https://yt-dl.org/"><i class="fas fa-link"></i> youtube-dl</a> version {{ youtube_dl_version }}.</p>
|
||||
<p>TubeSync version {{ app_version }} with <a href="https://yt-dl.org/"><i class="fas fa-link"></i> youtube-dl</a> version {{ youtube_dl_version }}.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
@ -10,8 +10,16 @@
|
|||
{% if field.field.widget.input_type == 'hidden' %}{{ field }}{% else %}
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
<label>
|
||||
{{ field }}
|
||||
<span>{{ field.label }}</span>
|
||||
</label>
|
||||
{% else %}
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
{% endif %}
|
||||
{% if field.help_text %}<span class="help-text"><i class="fas fa-info-circle"></i> {{ field.help_text }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -11127,6 +11127,10 @@ strong {
|
|||
.simpleform .row {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.simpleform .help-text {
|
||||
color: #2ec4b6;
|
||||
padding: 1rem 0 1rem 0; }
|
||||
|
||||
.simpleform label {
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -54,9 +54,9 @@ class Source(models.Model):
|
|||
FALLBACK_NEXT_HD = 'h'
|
||||
FALLBACKS = (FALLBACK_FAIL, FALLBACK_NEXT_SD, FALLBACK_NEXT_HD)
|
||||
FALLBACK_CHOICES = (
|
||||
(FALLBACK_FAIL, _('Fail')),
|
||||
(FALLBACK_NEXT_SD, _('Next best SD')),
|
||||
(FALLBACK_NEXT_HD, _('Next best HD')),
|
||||
(FALLBACK_FAIL, _('Fail, do not download any media')),
|
||||
(FALLBACK_NEXT_SD, _('Get next best SD media instead')),
|
||||
(FALLBACK_NEXT_HD, _('Get next best HD media instead')),
|
||||
)
|
||||
|
||||
uuid = models.UUIDField(
|
||||
|
@ -102,7 +102,7 @@ class Source(models.Model):
|
|||
_('name'),
|
||||
max_length=100,
|
||||
db_index=True,
|
||||
help_text=_('Friendly name for the source, used locally')
|
||||
help_text=_('Friendly name for the source, used locally in TubeSync only')
|
||||
)
|
||||
directory = models.CharField(
|
||||
_('directory'),
|
||||
|
@ -144,7 +144,7 @@ class Source(models.Model):
|
|||
db_index=True,
|
||||
choices=OUTPUT_FORMAT_CHOICES,
|
||||
default=OUTPUT_FORMAT_MKV,
|
||||
help_text=_('Output format, the codec and container to save media')
|
||||
help_text=_('Output format, the file format container in which to save media')
|
||||
)
|
||||
fallback = models.CharField(
|
||||
_('fallback'),
|
||||
|
@ -152,7 +152,7 @@ class Source(models.Model):
|
|||
db_index=True,
|
||||
choices=FALLBACK_CHOICES,
|
||||
default=FALLBACK_FAIL,
|
||||
help_text=_('What do do when your first choice is not available')
|
||||
help_text=_('What do do when media in your source profile is not available')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block headtitle %}Add a new source{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<h1>Add a source</h1>
|
||||
<p>
|
||||
You can use this form to add a new source. A source is what's polled on regular
|
||||
basis to find new media to download, such as a channel or playlist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="{% url 'sync:add-source' %}" 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">Add source <i class="fas fa-fw fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,11 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block headtitle %}Source - Add{% endblock %}
|
||||
{% block headtitle %}Validate a {{ help_item }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<h1>Add a {{ help_item }}</h1>
|
||||
<h1>Validate a {{ help_item }}</h1>
|
||||
<p>{{ help_text|safe }}</p>
|
||||
<p>Example: <strong>{{ help_example }}</strong></p>
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@
|
|||
{% 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">Add {{ help_item }} <i class="fas fa-fw fa-plus"></i></button>
|
||||
<button class="btn" type="submit" name="action">Validate {{ help_item }} <i class="fas fa-check"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.urls import path
|
||||
from .views import (DashboardView, SourcesView, ValidateSourceView, MediaView,
|
||||
TasksView, LogsView)
|
||||
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
|
||||
MediaView, TasksView, LogsView)
|
||||
|
||||
|
||||
app_name = 'sync'
|
||||
|
@ -20,6 +20,10 @@ urlpatterns = [
|
|||
ValidateSourceView.as_view(),
|
||||
name='validate-source'),
|
||||
|
||||
path('source/add',
|
||||
AddSourceView.as_view(),
|
||||
name='add-source'),
|
||||
|
||||
path('media',
|
||||
MediaView.as_view(),
|
||||
name='media'),
|
||||
|
|
|
@ -5,10 +5,12 @@ from django.forms import ValidationError
|
|||
|
||||
def validate_url(url, validator):
|
||||
'''
|
||||
Validate a URL against a dict of validation requirements.
|
||||
Validate a URL against a dict of validation requirements. Returns an extracted
|
||||
part of the URL if the URL is valid, if invalid raises a ValidationError.
|
||||
'''
|
||||
valid_scheme, valid_netloc, valid_path, valid_query = (validator['scheme'],
|
||||
validator['domain'], validator['path_regex'], validator['qs_args'])
|
||||
valid_scheme, valid_netloc, valid_path, valid_query, extract_parts = (
|
||||
validator['scheme'], validator['domain'], validator['path_regex'],
|
||||
validator['qs_args'], validator['extract_key'])
|
||||
url_parts = urlsplit(str(url).strip())
|
||||
url_scheme = str(url_parts.scheme).strip().lower()
|
||||
if url_scheme != valid_scheme:
|
||||
|
@ -17,13 +19,26 @@ def validate_url(url, validator):
|
|||
if url_netloc != valid_netloc:
|
||||
raise ValidationError(f'domain "{url_netloc}" must be "{valid_netloc}"')
|
||||
url_path = str(url_parts.path).strip()
|
||||
matches = re.match(valid_path, url_path)
|
||||
if matches is None:
|
||||
matches = re.findall(valid_path, url_path)
|
||||
if not matches:
|
||||
raise ValidationError(f'path "{url_path}" must match "{valid_path}"')
|
||||
url_query = str(url_parts.query).strip()
|
||||
url_query_parts = parse_qs(url_query)
|
||||
for required_query in valid_query:
|
||||
if required_query not in url_query_parts:
|
||||
raise ValidationError(f'query string "{url_query}" must '
|
||||
f'contain "{required_query}"')
|
||||
return True
|
||||
f'contain the parameter "{required_query}"')
|
||||
extract_from, extract_param = extract_parts
|
||||
extract_value = ''
|
||||
if extract_from == 'path_regex':
|
||||
try:
|
||||
submatches = matches[0]
|
||||
try:
|
||||
extract_value = submatches[extract_param]
|
||||
except IndexError:
|
||||
pass
|
||||
except IndexError:
|
||||
pass
|
||||
elif extract_from == 'qs_args':
|
||||
extract_value = url_query_parts[extract_param][0]
|
||||
return extract_value
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from django.http import Http404
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from django.views.generic.edit import FormView, CreateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from common.utils import append_uri_params
|
||||
from .models import Source
|
||||
from .forms import ValidateSourceForm
|
||||
from .utils import validate_url
|
||||
|
@ -36,7 +37,7 @@ class ValidateSourceView(FormView):
|
|||
Validate a URL and prepopulate a create source view form with confirmed
|
||||
accurate data. The aim here is to streamline onboarding of new sources
|
||||
which otherwise may not be entirely obvious to add, such as the "key"
|
||||
being just a playlist ID or some other reasonably unobvious internals.
|
||||
being just a playlist ID or some other reasonably opaque internals.
|
||||
'''
|
||||
|
||||
template_name = 'sync/source-validate.html'
|
||||
|
@ -77,8 +78,9 @@ class ValidateSourceView(FormView):
|
|||
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: {
|
||||
'scheme': 'https',
|
||||
'domain': 'www.youtube.com',
|
||||
'path_regex': '^\/(c\/)?[^\/]+$',
|
||||
'path_regex': '^\/(c\/)?([^\/]+)$',
|
||||
'qs_args': [],
|
||||
'extract_key': ('path_regex', 1),
|
||||
'example': 'https://www.youtube.com/SOMECHANNEL'
|
||||
},
|
||||
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: {
|
||||
|
@ -86,6 +88,7 @@ class ValidateSourceView(FormView):
|
|||
'domain': 'www.youtube.com',
|
||||
'path_regex': '^\/watch$',
|
||||
'qs_args': ['v', 'list'],
|
||||
'extract_key': ('qs_args', 'list'),
|
||||
'example': 'https://www.youtube.com/watch?v=VIDEOID&list=PLAYLISTID'
|
||||
},
|
||||
}
|
||||
|
@ -93,6 +96,7 @@ class ValidateSourceView(FormView):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.source_type_str = ''
|
||||
self.source_type = None
|
||||
self.key = ''
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -127,9 +131,8 @@ class ValidateSourceView(FormView):
|
|||
source_url = form.cleaned_data['source_url']
|
||||
validation_url = self.validation_urls.get(self.source_type)
|
||||
try:
|
||||
validate_url(source_url, validation_url)
|
||||
self.key = validate_url(source_url, validation_url)
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
error = self.errors.get('invalid_url')
|
||||
item = self.help_item.get(self.source_type)
|
||||
form.add_error(
|
||||
|
@ -144,14 +147,23 @@ class ValidateSourceView(FormView):
|
|||
return super().form_invalid(form)
|
||||
return super().form_valid(form)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
print(cleaned_data)
|
||||
return cleaned_data
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sync:dashboard')
|
||||
url = reverse_lazy('sync:add-source')
|
||||
return append_uri_params(url, {'source_type': self.source_type,
|
||||
'key': self.key})
|
||||
|
||||
|
||||
class AddSourceView(CreateView):
|
||||
'''
|
||||
Adds a new source, optionally takes some initial data querystring values to
|
||||
prepopulate some of the more unclear values.
|
||||
'''
|
||||
|
||||
template_name = 'sync/source-add.html'
|
||||
model = Source
|
||||
fields = ('source_type', 'key', 'name', 'directory', 'delete_old_media',
|
||||
'days_to_keep', 'source_profile', 'prefer_60fps', 'prefer_hdr',
|
||||
'output_format', 'fallback')
|
||||
|
||||
|
||||
class MediaView(TemplateView):
|
||||
|
|
Loading…
Reference in New Issue