Merged changes from master

This commit is contained in:
2019-01-01 23:35:38 +02:00
161 changed files with 66822 additions and 663 deletions

View File

@ -0,0 +1,24 @@
{% if config_errors %}
<div class="alert alert-danger alert-card mx-auto">
<p>Attention! Some critical configuration errors have been found!</p>
<ul>
{% for err in config_errors %}
<li>{{ err }}</li>
{% endfor %}
</ul>
<p>Until these problems are fixed, the server may have encounter serious problems while running.
Please correct these errors, and then restart the server.</p>
</div>
{% endif %}
{% if config_warnings %}
<div class="alert alert-warning alert-card mx-auto">
<p>Warning: some configuration problems have been found!</p>
<ul>
{% for err in config_warnings %}
<li>{{ err }}</li>
{% endfor %}
</ul>
<p>We recommend that you fix these issues before continuing.</p>
</div>
{% endif %}

View File

@ -0,0 +1,22 @@
{% extends 'YtManagerApp/controls/modal.html' %}
{% load crispy_forms_tags %}
{% block modal_title %}
Import subscriptions
{% endblock modal_title %}
{% block modal_content %}
<form action="{% url 'modal_import_subscriptions' %}" method="post"
enctype="multipart/form-data">
{{ block.super }}
</form>
{% endblock %}
{% block modal_body %}
{% crispy form %}
{% endblock modal_body %}
{% block modal_footer %}
<input class="btn btn-primary" type="submit" value="Import">
<input class="btn btn-secondary" type="button" value="Cancel" data-dismiss="modal" aria-label="Cancel">
{% endblock modal_footer %}

View File

@ -0,0 +1,12 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% block body %}
<div class="container">
<h1>Done!</h1>
<p>The setup is finished, and the application is now ready to use!</p>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -0,0 +1,15 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% block body %}
<div class="container">
{% include "YtManagerApp/controls/setup_errors_banner.html" %}
<h1>Welcome</h1>
<p>This wizard will guide you through setting up the application.</p>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -0,0 +1,64 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block body %}
<div class="container">
<h1>Step 1: Set up YouTube API key (optional)</h1>
<p>This program uses the YouTube API in order to obtain information about videos, channels and playlists.
To access this API, YouTube requires users to register on their site and request an API key. YouTube
uses this key to control access and limit the number of requests made to their platform, in order to prevent abuses.</p>
<p>To use this program, it is <em>recommended</em>, but not required, that you create an API key for your own account. While a key
is already provided the developer, there is a chance that the quota limits will be reached, which will prevent
this program from reaching YouTube. Follow the steps below, or press <strong>Skip</strong> to use the provided key.</p>
<h4>
<a data-toggle="collapse" href="#HowToObtainKey" role="button" aria-expanded="false" aria-controls="HowToObtainKey">
<span class="typcn typcn-arrow-sorted-down"></span>How to obtain a key
</a>
</h4>
<div class="collapse" id="HowToObtainKey">
<ol>
<li>Visit <a href="https://developers.google.com/">https://developers.google.com/</a>, log in or create an account, if necessary.</li>
<li>Go to <a href="https://console.developers.google.com/project"></a>, and click on the <strong>Create project</strong> button.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_create_project.png' %}">
</li>
<li>Give a name to the project, and then click on <strong>Create</strong>. Wait for a few seconds, until Google finishes creating the project.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_project_name.png' %}">
</li>
<li>Make sure the newly created project is selected in the top bar and then, in the left sidebar,
go to <strong>APIs & Services</strong> - <strong>Library</strong>.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_goto_apis.png' %}">
</li>
<li>Find and click on the <strong>YouTube Data API v3</strong> from the list.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_select_ytapi.png' %}">
</li>
<li>Click <strong>Enable</strong> to enable the YouTube API for your account.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_enable_ytapi.png' %}">
</li>
<li>In the navigation sidebar, go to the <strong>Credentials</strong> page.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_goto_credentials.png' %}">
</li>
<li>Click on <strong>Create credentials</strong>.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_create_credential.png' %}">
</li>
<li>Fill the requested information; we will need access to the <strong>YouTube Data v3</strong>,
we will be calling the API from a <strong>Web server</strong>, and we will access the <strong>Public data</strong>.
After filling the information, click on the <strong>What credentials do I need?</strong> button.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_create_credential_options.png' %}">
</li>
<li>Copy the created key in the box below, and then hit <strong>Done</strong>.
<img class="d-block" src="{% static 'YtManagerApp/img/first_time/ytapi_done.png' %}">
</li>
</ol>
</div>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -0,0 +1,15 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block body %}
<div class="container">
<h1>Step 2: Create an administrator account</h1>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -0,0 +1,17 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block body %}
<div class="container">
<h1>Step 3: Configure the server</h1>
<p>Here you can customize some basic options for the application. There are many more options which can be changed in the settings page.</p>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -3,11 +3,11 @@
{% load crispy_forms_tags %}
{% block stylesheets %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<link rel="stylesheet" href="{% static 'YtManagerApp/import/jstree/dist/themes/default/style.min.css' %}" />
{% endblock %}
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<script src="{% static 'YtManagerApp/import/jstree/dist/jstree.min.js' %}"></script>
<script>
let LAST_NOTIFICATION_ID = {{ current_notification_id }};
@ -26,24 +26,34 @@
</div>
</div>
{% include 'YtManagerApp/controls/setup_errors_banner.html' %}
<div class="row">
<div class="col-3">
{# Tree toolbar #}
<div class="btn-toolbar" role="toolbar" aria-label="Subscriptions toolbar">
<div class="btn-group btn-group-sm mr-2" role="group">
<button id="btn_create_sub" type="button" class="btn btn-secondary" >
<div class="btn-group mr-2" role="group">
<button id="btn_create_sub" type="button" class="btn btn-light"
data-toggle="tooltip" title="Add subscription...">
<span class="typcn typcn-plus" aria-hidden="true"></span>
</button>
<button id="btn_create_folder" type="button" class="btn btn-secondary">
<button id="btn_create_folder" type="button" class="btn btn-light"
data-toggle="tooltip" title="Add folder...">
<span class="typcn typcn-folder-add" aria-hidden="true"></span>
</button>
<button id="btn_import" type="button" class="btn btn-light"
data-toggle="tooltip" title="Import from file...">
<span class="typcn typcn-document-add" aria-hidden="true"></span>
</button>
</div>
<div class="btn-group btn-group-sm mr-2" role="group">
<button id="btn_edit_node" type="button" class="btn btn-secondary" >
<div class="btn-group mr-2" role="group">
<button id="btn_edit_node" type="button" class="btn btn-light"
data-toggle="tooltip" title="Edit selection">
<span class="typcn typcn-edit" aria-hidden="true"></span>
</button>
<button id="btn_delete_node" type="button" class="btn btn-secondary" >
<button id="btn_delete_node" type="button" class="btn btn-light"
data-toggle="tooltip" title="Delete selection">
<span class="typcn typcn-trash" aria-hidden="true"></span>
</button>
</div>

View File

@ -3,6 +3,8 @@
{% block body %}
{% include 'YtManagerApp/controls/setup_errors_banner.html' %}
<h1>Hello</h1>
<h2>Please log in to continue</h2>

View File

@ -58,4 +58,38 @@
</div>
{% endfor %}
</div>
<div class="pagination row">
<div class="btn-toolbar mx-auto">
<div class="btn-group mr-2" role="group">
<button id="btn_page_first" type="button" class="btn btn-light btn-paging"
data-toggle="tooltip" title="First page"
{% if videos.has_previous %} data-navigation-page="1" {% else %} disabled {% endif %}>
<span class="typcn typcn-chevron-left-outline" aria-hidden="true"></span>
</button>
<button id="btn_page_prev" type="button" class="btn btn-light btn-paging"
data-toggle="tooltip" title="Previous"
{% if videos.has_previous %} data-navigation-page="{{ videos.previous_page_number }}" {% else %} disabled {% endif %} >
<span class="typcn typcn-chevron-left" aria-hidden="true"></span>
</button>
</div>
<small class="my-auto mx-2">
Page {{ videos.number }} of {{ videos.paginator.num_pages }}
</small>
<div class="btn-group mx-2" role="group">
<button id="btn_page_next" type="button" class="btn btn-light btn-paging"
data-toggle="tooltip" title="Next"
{% if videos.has_next %} data-navigation-page="{{ videos.next_page_number }}" {% else %} disabled {% endif %} >
<span class="typcn typcn-chevron-right" aria-hidden="true"></span>
</button>
<button id="btn_page_last" type="button" class="btn btn-light btn-paging"
data-toggle="tooltip" title="Last"
{% if videos.has_next %} data-navigation-page="{{ videos.paginator.num_pages }}" {% else %} disabled {% endif %} >
<span class="typcn typcn-chevron-right-outline" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
</div>

View File

@ -53,7 +53,21 @@ class AjaxModal
_submit(e) {
let pThis = this;
let url = this.form.attr('action');
$.post(url, this.form.serialize())
let ajax_settings = {
url: url,
};
if (this.form.attr('enctype') === 'multipart/form-data') {
ajax_settings.data = new FormData(this.form[0]);
ajax_settings.contentType = false;
ajax_settings.processData = false;
ajax_settings.cache = false;
}
else {
ajax_settings.data = this.form.serialize();
}
$.post(ajax_settings)
.done(function(result) {
pThis._submitDone(result);
})

View File

@ -123,16 +123,33 @@ function videos_Reload()
let videos_timeout = null;
function videos_ReloadWithTimer()
function videos_ResetPageAndReloadWithTimer()
{
let filters_form = $("#form_video_filter");
filters_form.find('input[name=page]').val("1");
clearTimeout(videos_timeout);
videos_timeout = setTimeout(function()
{
videos_Submit.call($('#form_video_filter'));
videos_Reload();
videos_timeout = null;
}, 200);
}
function videos_PageClicked()
{
// Obtain page from button
let page = $(this).data('navigation-page');
// Set page
let filters_form = $("#form_video_filter");
filters_form.find('input[name=page]').val(page);
// Reload
videos_Reload();
$("html, body").animate({ scrollTop: 0 }, "slow");
}
function videos_Submit(e)
{
let loadingDiv = $('#videos-loading');
@ -145,6 +162,7 @@ function videos_Submit(e)
.done(function(result) {
$("#videos-wrapper").html(result);
$(".ajax-link").on("click", ajaxLink_Clicked);
$(".btn-paging").on("click", videos_PageClicked);
})
.fail(function() {
$("#videos-wrapper").html('<div class="alert alert-danger">An error occurred while retrieving the video list!</div>');
@ -186,6 +204,9 @@ function get_and_process_notifications()
///
$(document).ready(function ()
{
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
tree_Initialize();
// Subscription toolbar
@ -199,16 +220,23 @@ $(document).ready(function ()
modal.setSubmitCallback(tree_Refresh);
modal.loadAndShow();
});
$("#btn_import").on("click", function () {
let modal = new AjaxModal("{% url 'modal_import_subscriptions' %}");
modal.setSubmitCallback(tree_Refresh);
modal.loadAndShow();
});
$("#btn_edit_node").on("click", treeNode_Edit);
$("#btn_delete_node").on("click", treeNode_Delete);
// Videos filters
let filters_form = $("#form_video_filter");
filters_form.submit(videos_Submit);
filters_form.find('input[name=query]').on('change', videos_ReloadWithTimer);
filters_form.find('select[name=sort]').on('change', videos_ReloadWithTimer);
filters_form.find('select[name=show_watched]').on('change', videos_ReloadWithTimer);
filters_form.find('select[name=show_downloaded]').on('change', videos_ReloadWithTimer);
filters_form.find('input[name=query]').on('change', videos_ResetPageAndReloadWithTimer);
filters_form.find('select[name=sort]').on('change', videos_ResetPageAndReloadWithTimer);
filters_form.find('select[name=show_watched]').on('change', videos_ResetPageAndReloadWithTimer);
filters_form.find('select[name=show_downloaded]').on('change', videos_ResetPageAndReloadWithTimer);
filters_form.find('select[name=results_per_page]').on('change', videos_ResetPageAndReloadWithTimer);
videos_Reload();
// Notification manager

View File

@ -6,21 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}YouTube Subscription Manager{% endblock %}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="shortcut icon" type="image/x-icon" href="{% static 'YtManagerApp/favicon.ico' %}"/>
<link rel="stylesheet" href="{% static 'YtManagerApp/import/bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'YtManagerApp/import/typicons/typicons.min.css' %}" />
<link rel="stylesheet" href="{% static 'YtManagerApp/css/style.css' %}">
{% block stylesheets %}
{% endblock %}
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script>
{% include 'YtManagerApp/js/common.js' %}
</script>
{% block scripts %}
{% endblock %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{% url 'home' %}">YouTube Manager</a>
@ -44,6 +38,9 @@
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown">
<a class="dropdown-item" href="{% url 'settings' %}">Settings</a>
{% if request.user.is_superuser %}
<a class="dropdown-item" href="{% url 'admin_settings' %}">Admin settings</a>
{% endif %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'logout' %}">Log out</a>
</div>
@ -52,9 +49,11 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">Register</a>
</li>
{% if global_preferences.general__allow_registrations %}
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">Register</a>
</li>
{% endif %}
{% endif %}
</ul>
</div>
@ -72,5 +71,14 @@
<span class="typcn typcn-arrow-sync" aria-hidden="true"></span>
</button>
</footer>
<script src="{% static 'YtManagerApp/import/jquery/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'YtManagerApp/import/popper/popper.min.js' %}"></script>
<script src="{% static 'YtManagerApp/import/bootstrap/js/bootstrap.min.js' %}"></script>
<script>
{% include 'YtManagerApp/js/common.js' %}
</script>
{% block scripts %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,19 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% block body %}
<div class="container">
<h1>Admin settings</h1>
{% if not request.user.is_authenticated or not request.user.is_superuser %}
<div class="alert alert-danger" role="alert">
You must be an administrator to access this page!
</div>
{% else %}
{% crispy form %}
{% endif %}
</div>
{% endblock body %}

View File

@ -24,15 +24,17 @@
{% endif %}
{% endif %}
{% if is_first_user %}
<div class="alert alert-info" role="alert">
Since this is the first user to register, it will be the system administrator.
{% if not global_preferences.general__allow_registrations %}
<div class="alert alert-danger" role="alert">
Registrations are disabled by the administrator!
</div>
{% else %}
<h5>Register</h5>
{% crispy form %}
{% endif %}
<h5>Register</h5>
{% crispy form %}
</div>
{% endblock %}