Added admin settings page. Improve dynamic_preferences integration.

This commit is contained in:
Tiberiu Chibici 2018-12-29 17:11:20 +02:00
parent 33ee71dcbb
commit 8eca3dc7a9
20 changed files with 233 additions and 82 deletions

View File

@ -5,7 +5,7 @@ import sys
from django.conf import settings as dj_settings from django.conf import settings as dj_settings
from .management.appconfig import global_prefs from .management.appconfig import appconfig
from .management.jobs.synchronize import schedule_synchronize_global from .management.jobs.synchronize import schedule_synchronize_global
from .scheduler import initialize_scheduler from .scheduler import initialize_scheduler
from django.db.utils import OperationalError from django.db.utils import OperationalError
@ -40,7 +40,7 @@ def main():
__initialize_logger() __initialize_logger()
try: try:
if global_prefs['hidden__initialized']: if appconfig.initialized:
initialize_scheduler() initialize_scheduler()
schedule_synchronize_global() schedule_synchronize_global()
except OperationalError: except OperationalError:

View File

@ -33,14 +33,6 @@ class YouTubeAPIKey(StringPreference):
required = True required = True
@global_preferences_registry.register
class AllowCDN(BooleanPreference):
section = general
name = 'allow_cdn'
default = True
required = True
@global_preferences_registry.register @global_preferences_registry.register
class AllowRegistrations(BooleanPreference): class AllowRegistrations(BooleanPreference):
section = general section = general

View File

@ -1,3 +1,34 @@
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from YtManagerApp.dynamic_preferences_registry import Initialized, YouTubeAPIKey, AllowRegistrations, SyncSchedule, SchedulerConcurrency
class AppConfig(object):
# Properties
props = {
'initialized': Initialized,
'youtube_api_key': YouTubeAPIKey,
'allow_registrations': AllowRegistrations,
'sync_schedule': SyncSchedule,
'concurrency': SchedulerConcurrency
}
# Init
def __init__(self, pref_manager):
self.__pref_manager = pref_manager
def __getattr__(self, item):
prop_class = AppConfig.props[item]
prop_full_name = prop_class.section.name + "__" + prop_class.name
return self.__pref_manager[prop_full_name]
def __setattr__(self, key, value):
if key in AppConfig.props:
prop_class = AppConfig.props[key]
prop_full_name = prop_class.section.name + "__" + prop_class.name
self.__pref_manager[prop_full_name] = value
else:
super().__setattr__(key, value)
global_prefs = global_preferences_registry.manager() global_prefs = global_preferences_registry.manager()
appconfig = AppConfig(global_prefs)

View File

@ -5,7 +5,7 @@ from threading import Lock
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from YtManagerApp import scheduler from YtManagerApp import scheduler
from YtManagerApp.management.appconfig import global_prefs from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.management.downloader import fetch_thumbnail, downloader_process_all, downloader_process_subscription from YtManagerApp.management.downloader import fetch_thumbnail, downloader_process_all, downloader_process_subscription
from YtManagerApp.models import * from YtManagerApp.models import *
from YtManagerApp.utils import youtube from YtManagerApp.utils import youtube
@ -149,7 +149,7 @@ def synchronize_subscription(subscription: Subscription):
def schedule_synchronize_global(): def schedule_synchronize_global():
trigger = CronTrigger.from_crontab(global_prefs['scheduler__synchronization_schedule']) trigger = CronTrigger.from_crontab(appconfig.sync_schedule)
job = scheduler.scheduler.add_job(synchronize, trigger, max_instances=1, coalesce=True) job = scheduler.scheduler.add_job(synchronize, trigger, max_instances=1, coalesce=True)
log.info('Scheduled synchronize job job=%s', job.id) log.info('Scheduled synchronize job job=%s', job.id)

View File

@ -1,11 +1,11 @@
import logging import logging
from typing import Callable, Union, Any, Optional
import os import os
from typing import Callable, Union, Any, Optional
from django.contrib.auth.models import User
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models.functions import Lower from django.db.models.functions import Lower
from YtManagerApp.utils import youtube from YtManagerApp.utils import youtube
# help_text = user shown text # help_text = user shown text

View File

@ -2,7 +2,7 @@ import logging
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from YtManagerApp.management.appconfig import global_prefs from YtManagerApp.management.appconfig import appconfig
scheduler: BackgroundScheduler = None scheduler: BackgroundScheduler = None
@ -14,7 +14,7 @@ def initialize_scheduler():
executors = { executors = {
'default': { 'default': {
'type': 'threadpool', 'type': 'threadpool',
'max_workers': global_prefs['scheduler__concurrency'] 'max_workers': appconfig.concurrency
} }
} }
job_defaults = { job_defaults = {

View File

@ -9,7 +9,7 @@
bottom: 0; bottom: 0;
height: 2rem; height: 2rem;
line-height: 2rem; line-height: 2rem;
padding: 0rem 1rem; padding: 0 1rem;
display: flex; display: flex;
align-content: center; align-content: center;
font-size: 10pt; } font-size: 10pt; }

View File

@ -1,6 +1,6 @@
{ {
"version": 3, "version": 3,
"mappings": "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", "mappings": "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",
"sources": ["style.scss"], "sources": ["style.scss"],
"names": [], "names": [],
"file": "style.css" "file": "style.css"

View File

@ -12,7 +12,7 @@ $accent-color: #007bff;
bottom: 0; bottom: 0;
height: 2rem; height: 2rem;
line-height: 2rem; line-height: 2rem;
padding: 0rem 1rem; padding: 0 1rem;
display: flex; display: flex;
align-content: center; align-content: center;
font-size: 10pt; font-size: 10pt;

View File

@ -46,6 +46,9 @@
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown">
<a class="dropdown-item" href="{% url 'settings' %}">Settings</a> <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> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'logout' %}">Log out</a> <a class="dropdown-item" href="{% url 'logout' %}">Log out</a>
</div> </div>
@ -54,9 +57,11 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a> <a class="nav-link" href="{% url 'login' %}">Login</a>
</li> </li>
<li class="nav-item"> {% if global_preferences.general__allow_registrations %}
<a class="nav-link" href="{% url 'register' %}">Register</a> <li class="nav-item">
</li> <a class="nav-link" href="{% url 'register' %}">Register</a>
</li>
{% endif %}
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,11 @@
{% extends "YtManagerApp/master_default.html" %}
{% load crispy_forms_tags %}
{% block body %}
<div class="container">
<h1>Admin settings</h1>
{% crispy form %}
</div>
{% endblock body %}

View File

@ -24,9 +24,17 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
<h5>Register</h5> {% if not global_preferences.general__allow_registrations %}
<div class="alert alert-danger" role="alert">
Registrations are disabled by the administrator!
</div>
{% crispy form %} {% else %}
<h5>Register</h5>
{% crispy form %}
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -23,7 +23,7 @@ from .views.actions import SyncNowView, DeleteVideoFilesView, DownloadVideoFiles
from .views.auth import ExtendedLoginView, RegisterView, RegisterDoneView from .views.auth import ExtendedLoginView, RegisterView, RegisterDoneView
from .views.index import index, ajax_get_tree, ajax_get_videos, CreateFolderModal, UpdateFolderModal, DeleteFolderModal, \ from .views.index import index, ajax_get_tree, ajax_get_videos, CreateFolderModal, UpdateFolderModal, DeleteFolderModal, \
CreateSubscriptionModal, UpdateSubscriptionModal, DeleteSubscriptionModal, ImportSubscriptionsModal CreateSubscriptionModal, UpdateSubscriptionModal, DeleteSubscriptionModal, ImportSubscriptionsModal
from .views.settings import SettingsView from .views.settings import SettingsView, AdminSettingsView
from .views import first_time from .views import first_time
urlpatterns = [ urlpatterns = [
@ -59,8 +59,9 @@ urlpatterns = [
# Pages # Pages
path('', index, name='home'), path('', index, name='home'),
path('settings/', SettingsView.as_view(), name='settings'), path('settings/', SettingsView.as_view(), name='settings'),
path('admin_settings/', AdminSettingsView.as_view(), name='admin_settings'),
# First time setup # First time setup
path('first_time/step0_welcome', first_time.Step0WelcomeView.as_view(), name='first_time_0'), path('first_time/step0_welcome', first_time.Step0WelcomeView.as_view(), name='first_time_0'),
path('first_time/step1_apikey', first_time.Step1ApiKeyView.as_view(), name='first_time_1'), path('first_time/step1_apikey', first_time.Step1ApiKeyView.as_view(), name='first_time_1'),
path('first_time/step2_admin', first_time.Step2SetupAdminUserView.as_view(), name='first_time_2'), path('first_time/step2_admin', first_time.Step2SetupAdminUserView.as_view(), name='first_time_2'),

View File

@ -7,7 +7,8 @@ class YoutubeAPI(YouTube):
@staticmethod @staticmethod
def build_public() -> 'YoutubeAPI': def build_public() -> 'YoutubeAPI':
return YoutubeAPI(key=settings.YOUTUBE_API_KEY) from YtManagerApp.management.appconfig import youtube_api_key
return YoutubeAPI(key=youtube_api_key)
# @staticmethod # @staticmethod
# def build_oauth() -> 'YoutubeAPI': # def build_oauth() -> 'YoutubeAPI':

View File

@ -2,10 +2,12 @@ from django.contrib.auth import login, authenticate
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.http import HttpResponseForbidden
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import FormView, TemplateView from django.views.generic import FormView, TemplateView
from .forms.auth import ExtendedAuthenticationForm, ExtendedUserCreationForm from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.views.forms.auth import ExtendedAuthenticationForm, ExtendedUserCreationForm
class ExtendedLoginView(LoginView): class ExtendedLoginView(LoginView):
@ -33,6 +35,12 @@ class RegisterView(FormView):
context['is_first_user'] = (User.objects.count() == 0) context['is_first_user'] = (User.objects.count() == 0)
return context return context
def post(self, request, *args, **kwargs):
if not appconfig.allow_registrations:
return HttpResponseForbidden("Registrations are disabled!")
return super().post(request, *args, **kwargs)
class RegisterDoneView(LoginRequiredMixin, TemplateView): class RegisterDoneView(LoginRequiredMixin, TemplateView):
template_name = 'registration/register_done.html' template_name = 'registration/register_done.html'

View File

@ -7,7 +7,7 @@ from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import FormView from django.views.generic import FormView
from YtManagerApp.management.appconfig import global_prefs from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.views.forms.auth import ExtendedAuthenticationForm from YtManagerApp.views.forms.auth import ExtendedAuthenticationForm
from YtManagerApp.views.forms.first_time import WelcomeForm, ApiKeyForm, PickAdminUserForm, ServerConfigForm, DoneForm, UserCreationForm from YtManagerApp.views.forms.first_time import WelcomeForm, ApiKeyForm, PickAdminUserForm, ServerConfigForm, DoneForm, UserCreationForm
@ -18,7 +18,7 @@ class WizardStepMixin(object):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Prevent access if application is already initialized # Prevent access if application is already initialized
if global_prefs['hidden__initialized']: if appconfig.initialized:
logger.debug(f"Attempted to access {request.path}, but first time setup already run. Redirected to home " logger.debug(f"Attempted to access {request.path}, but first time setup already run. Redirected to home "
f"page.") f"page.")
return redirect('home') return redirect('home')
@ -26,7 +26,7 @@ class WizardStepMixin(object):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if global_prefs['hidden__initialized']: if appconfig.initialized:
logger.debug(f"Attempted to post {request.path}, but first time setup already run.") logger.debug(f"Attempted to post {request.path}, but first time setup already run.")
return HttpResponseForbidden() return HttpResponseForbidden()
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
@ -51,14 +51,14 @@ class Step1ApiKeyView(WizardStepMixin, FormView):
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
initial['api_key'] = global_prefs['general__youtube_api_key'] initial['api_key'] = appconfig.youtube_api_key
return initial return initial
def form_valid(self, form): def form_valid(self, form):
key = form.cleaned_data['api_key'] key = form.cleaned_data['api_key']
# TODO: validate key # TODO: validate key
if key is not None and len(key) > 0: if key is not None and len(key) > 0:
global_prefs['general__youtube_api_key'] = key appconfig.youtube_api_key = key
# #
@ -100,9 +100,6 @@ class Step2SetupAdminUserView(WizardStepMixin, FormView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def form_invalid(self, form):
print("FORM INVALID!")
def form_valid(self, form): def form_valid(self, form):
if isinstance(form, ExtendedAuthenticationForm): if isinstance(form, ExtendedAuthenticationForm):
login(self.request, form.get_user()) login(self.request, form.get_user())
@ -139,8 +136,8 @@ class Step3ConfigureView(WizardStepMixin, FormView):
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
initial['allow_registrations'] = global_prefs['general__allow_registrations'] initial['allow_registrations'] = appconfig.allow_registrations
initial['sync_schedule'] = global_prefs['scheduler__synchronization_schedule'] initial['sync_schedule'] = appconfig.sync_schedule
initial['auto_download'] = self.request.user.preferences['downloader__auto_enabled'] initial['auto_download'] = self.request.user.preferences['downloader__auto_enabled']
initial['download_location'] = self.request.user.preferences['downloader__download_path'] initial['download_location'] = self.request.user.preferences['downloader__download_path']
return initial return initial
@ -148,11 +145,11 @@ class Step3ConfigureView(WizardStepMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
allow_registrations = form.cleaned_data['allow_registrations'] allow_registrations = form.cleaned_data['allow_registrations']
if allow_registrations is not None: if allow_registrations is not None:
global_prefs['general__allow_registrations'] = allow_registrations appconfig.allow_registrations = allow_registrations
sync_schedule = form.cleaned_data['sync_schedule'] sync_schedule = form.cleaned_data['sync_schedule']
if sync_schedule is not None and len(sync_schedule) > 0: if sync_schedule is not None and len(sync_schedule) > 0:
global_prefs['scheduler__synchronization_schedule'] = sync_schedule appconfig.sync_schedule = sync_schedule
auto_download = form.cleaned_data['auto_download'] auto_download = form.cleaned_data['auto_download']
if auto_download is not None: if auto_download is not None:
@ -163,7 +160,7 @@ class Step3ConfigureView(WizardStepMixin, FormView):
self.request.user.preferences['downloader__download_path'] = download_location self.request.user.preferences['downloader__download_path'] = download_location
# Set initialized to true # Set initialized to true
global_prefs['hidden__initialized'] = True appconfig.initialized = True
return super().form_valid(form) return super().form_valid(form)

View File

@ -3,11 +3,10 @@ import logging
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML, Submit, Column from crispy_forms.layout import Layout, HTML, Submit, Column
from django import forms from django import forms
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User from django.contrib.auth.models import User
from YtManagerApp.views.forms.auth import ExtendedUserCreationForm
from django.urls import reverse_lazy from django.urls import reverse_lazy
from YtManagerApp.management.appconfig import global_prefs
from YtManagerApp.views.forms.auth import ExtendedUserCreationForm
logger = logging.getLogger("FirstTimeWizard") logger = logging.getLogger("FirstTimeWizard")

View File

@ -0,0 +1,80 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML, Submit
from django import forms
from YtManagerApp.models import UserSettings
class SettingsForm(forms.ModelForm):
class Meta:
model = UserSettings
exclude = ['user']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-3'
self.helper.field_class = 'col-lg-9'
self.helper.layout = Layout(
'mark_deleted_as_watched',
'delete_watched',
HTML('<h2>Download settings</h2>'),
'auto_download',
'download_path',
'download_file_pattern',
'download_format',
'download_order',
'download_global_limit',
'download_subscription_limit',
HTML('<h2>Subtitles download settings</h2>'),
'download_subtitles',
'download_subtitles_langs',
'download_subtitles_all',
'download_autogenerated_subtitles',
'download_subtitles_format',
Submit('submit', value='Save')
)
class AdminSettingsForm(forms.Form):
api_key = forms.CharField(label="YouTube API key")
allow_registrations = forms.BooleanField(
label="Allow user registrations",
help_text="Disabling this option will prevent anyone from registering to the site.",
initial=True,
required=False
)
sync_schedule = forms.CharField(
label="Synchronization schedule",
help_text="How often should the application look for new videos.",
initial="5 * * * *",
required=True
)
scheduler_concurrency = forms.IntegerField(
label="Synchronization concurrency",
help_text="How many jobs are executed executed in parallel. Since most jobs are I/O bound (mostly use the hard "
"drive and network), there is no significant advantage to increase it.",
initial=2,
required=True
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-3'
self.helper.field_class = 'col-lg-9'
self.helper.layout = Layout(
HTML('<h2>General settings</h2>'),
'api_key',
'allow_registrations',
HTML('<h2>Scheduler settings</h2>'),
'sync_schedule',
'scheduler_concurrency',
Submit('submit', value='Save')
)

View File

@ -10,7 +10,7 @@ from django.views.generic import CreateView, UpdateView, DeleteView, FormView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.conf import settings from django.conf import settings
from YtManagerApp.management.videos import get_videos from YtManagerApp.management.videos import get_videos
from YtManagerApp.management.appconfig import global_prefs from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.models import Subscription, SubscriptionFolder, VIDEO_ORDER_CHOICES, VIDEO_ORDER_MAPPING from YtManagerApp.models import Subscription, SubscriptionFolder, VIDEO_ORDER_CHOICES, VIDEO_ORDER_MAPPING
from YtManagerApp.utils import youtube, subscription_file_parser from YtManagerApp.utils import youtube, subscription_file_parser
from YtManagerApp.views.controls.modal import ModalMixin from YtManagerApp.views.controls.modal import ModalMixin
@ -96,7 +96,7 @@ def __tree_sub_id(sub_id):
def index(request: HttpRequest): def index(request: HttpRequest):
if not global_prefs['hidden__initialized']: if not appconfig.initialized:
return redirect('first_time_0') return redirect('first_time_0')
context = { context = {

View File

@ -1,43 +1,11 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML, Submit
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseForbidden
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import UpdateView from django.views.generic import UpdateView, FormView
from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.models import UserSettings from YtManagerApp.models import UserSettings
from YtManagerApp.views.forms.settings import SettingsForm, AdminSettingsForm
class SettingsForm(forms.ModelForm):
class Meta:
model = UserSettings
exclude = ['user']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-3'
self.helper.field_class = 'col-lg-9'
self.helper.layout = Layout(
'mark_deleted_as_watched',
'delete_watched',
HTML('<h2>Download settings</h2>'),
'auto_download',
'download_path',
'download_file_pattern',
'download_format',
'download_order',
'download_global_limit',
'download_subscription_limit',
HTML('<h2>Subtitles download settings</h2>'),
'download_subtitles',
'download_subtitles_langs',
'download_subtitles_all',
'download_autogenerated_subtitles',
'download_subtitles_format',
Submit('submit', value='Save')
)
class SettingsView(LoginRequiredMixin, UpdateView): class SettingsView(LoginRequiredMixin, UpdateView):
@ -49,3 +17,53 @@ class SettingsView(LoginRequiredMixin, UpdateView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
obj, _ = self.model.objects.get_or_create(user=self.request.user) obj, _ = self.model.objects.get_or_create(user=self.request.user)
return obj return obj
class AdminSettingsView(LoginRequiredMixin, FormView):
form_class = AdminSettingsForm
template_name = 'YtManagerApp/settings_admin.html'
success_url = reverse_lazy('home')
def get(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.is_superuser:
return HttpResponseForbidden()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.is_superuser:
return HttpResponseForbidden()
return super().post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# TODO: present stats
return context
def get_initial(self):
initial = super().get_initial()
initial['api_key'] = appconfig.youtube_api_key
initial['allow_registrations'] = appconfig.allow_registrations
initial['sync_schedule'] = appconfig.sync_schedule
initial['scheduler_concurrency'] = appconfig.concurrency
return initial
def form_valid(self, form):
api_key = form.cleaned_data['api_key']
if api_key is not None and len(api_key) > 0:
appconfig.youtube_api_key = api_key
allow_registrations = form.cleaned_data['allow_registrations']
if allow_registrations is not None:
appconfig.allow_registrations = allow_registrations
sync_schedule = form.cleaned_data['sync_schedule']
if sync_schedule is not None and len(sync_schedule) > 0:
appconfig.sync_schedule = sync_schedule
concurrency = form.cleaned_data['scheduler_concurrency']
if concurrency is not None:
appconfig.concurrency = concurrency
return super().form_valid(form)