diff --git a/app/YtManagerApp/appmain.py b/app/YtManagerApp/appmain.py index 8651670..03e3ae7 100644 --- a/app/YtManagerApp/appmain.py +++ b/app/YtManagerApp/appmain.py @@ -5,7 +5,7 @@ import sys 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 .scheduler import initialize_scheduler from django.db.utils import OperationalError @@ -40,7 +40,7 @@ def main(): __initialize_logger() try: - if global_prefs['hidden__initialized']: + if appconfig.initialized: initialize_scheduler() schedule_synchronize_global() except OperationalError: diff --git a/app/YtManagerApp/dynamic_preferences_registry.py b/app/YtManagerApp/dynamic_preferences_registry.py index 895f158..d8a3235 100644 --- a/app/YtManagerApp/dynamic_preferences_registry.py +++ b/app/YtManagerApp/dynamic_preferences_registry.py @@ -33,14 +33,6 @@ class YouTubeAPIKey(StringPreference): required = True -@global_preferences_registry.register -class AllowCDN(BooleanPreference): - section = general - name = 'allow_cdn' - default = True - required = True - - @global_preferences_registry.register class AllowRegistrations(BooleanPreference): section = general diff --git a/app/YtManagerApp/management/appconfig.py b/app/YtManagerApp/management/appconfig.py index 1743fd6..43e4137 100644 --- a/app/YtManagerApp/management/appconfig.py +++ b/app/YtManagerApp/management/appconfig.py @@ -1,3 +1,34 @@ 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() +appconfig = AppConfig(global_prefs) diff --git a/app/YtManagerApp/management/jobs/synchronize.py b/app/YtManagerApp/management/jobs/synchronize.py index d55636a..bd6814c 100644 --- a/app/YtManagerApp/management/jobs/synchronize.py +++ b/app/YtManagerApp/management/jobs/synchronize.py @@ -5,7 +5,7 @@ from threading import Lock from apscheduler.triggers.cron import CronTrigger 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.models import * from YtManagerApp.utils import youtube @@ -149,7 +149,7 @@ def synchronize_subscription(subscription: Subscription): 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) log.info('Scheduled synchronize job job=%s', job.id) diff --git a/app/YtManagerApp/models.py b/app/YtManagerApp/models.py index 0d5c7cf..c500f36 100644 --- a/app/YtManagerApp/models.py +++ b/app/YtManagerApp/models.py @@ -1,11 +1,11 @@ import logging -from typing import Callable, Union, Any, Optional 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.db import models from django.db.models.functions import Lower + from YtManagerApp.utils import youtube # help_text = user shown text diff --git a/app/YtManagerApp/scheduler.py b/app/YtManagerApp/scheduler.py index ca6958e..92514f9 100644 --- a/app/YtManagerApp/scheduler.py +++ b/app/YtManagerApp/scheduler.py @@ -2,7 +2,7 @@ import logging from apscheduler.schedulers.background import BackgroundScheduler -from YtManagerApp.management.appconfig import global_prefs +from YtManagerApp.management.appconfig import appconfig scheduler: BackgroundScheduler = None @@ -14,7 +14,7 @@ def initialize_scheduler(): executors = { 'default': { 'type': 'threadpool', - 'max_workers': global_prefs['scheduler__concurrency'] + 'max_workers': appconfig.concurrency } } job_defaults = { diff --git a/app/YtManagerApp/static/YtManagerApp/css/style.css b/app/YtManagerApp/static/YtManagerApp/css/style.css index c3a4aca..b18229c 100644 --- a/app/YtManagerApp/static/YtManagerApp/css/style.css +++ b/app/YtManagerApp/static/YtManagerApp/css/style.css @@ -9,7 +9,7 @@ bottom: 0; height: 2rem; line-height: 2rem; - padding: 0rem 1rem; + padding: 0 1rem; display: flex; align-content: center; font-size: 10pt; } diff --git a/app/YtManagerApp/static/YtManagerApp/css/style.css.map b/app/YtManagerApp/static/YtManagerApp/css/style.css.map index a16ab80..57051a8 100644 --- a/app/YtManagerApp/static/YtManagerApp/css/style.css.map +++ b/app/YtManagerApp/static/YtManagerApp/css/style.css.map @@ -1,6 +1,6 @@ { "version": 3, -"mappings": "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", +"mappings": "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", "sources": ["style.scss"], "names": [], "file": "style.css" diff --git a/app/YtManagerApp/static/YtManagerApp/css/style.scss b/app/YtManagerApp/static/YtManagerApp/css/style.scss index 33a9fb9..83b0400 100644 --- a/app/YtManagerApp/static/YtManagerApp/css/style.scss +++ b/app/YtManagerApp/static/YtManagerApp/css/style.scss @@ -12,7 +12,7 @@ $accent-color: #007bff; bottom: 0; height: 2rem; line-height: 2rem; - padding: 0rem 1rem; + padding: 0 1rem; display: flex; align-content: center; font-size: 10pt; diff --git a/app/YtManagerApp/templates/YtManagerApp/master_default.html b/app/YtManagerApp/templates/YtManagerApp/master_default.html index 6b025f6..3f23b69 100644 --- a/app/YtManagerApp/templates/YtManagerApp/master_default.html +++ b/app/YtManagerApp/templates/YtManagerApp/master_default.html @@ -46,6 +46,9 @@ @@ -54,9 +57,11 @@ - + {% if global_preferences.general__allow_registrations %} + + {% endif %} {% endif %} diff --git a/app/YtManagerApp/templates/YtManagerApp/settings_admin.html b/app/YtManagerApp/templates/YtManagerApp/settings_admin.html new file mode 100644 index 0000000..35bf333 --- /dev/null +++ b/app/YtManagerApp/templates/YtManagerApp/settings_admin.html @@ -0,0 +1,11 @@ +{% extends "YtManagerApp/master_default.html" %} +{% load crispy_forms_tags %} + +{% block body %} + +
+

Admin settings

+ {% crispy form %} +
+ +{% endblock body %} \ No newline at end of file diff --git a/app/YtManagerApp/templates/registration/register.html b/app/YtManagerApp/templates/registration/register.html index 73f9c99..a8c43d0 100644 --- a/app/YtManagerApp/templates/registration/register.html +++ b/app/YtManagerApp/templates/registration/register.html @@ -24,9 +24,17 @@ {% endif %} {% endif %} -
Register
+ {% if not global_preferences.general__allow_registrations %} + - {% crispy form %} + {% else %} + +
Register
+ {% crispy form %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/app/YtManagerApp/urls.py b/app/YtManagerApp/urls.py index 3ca0bef..dc487c1 100644 --- a/app/YtManagerApp/urls.py +++ b/app/YtManagerApp/urls.py @@ -23,7 +23,7 @@ from .views.actions import SyncNowView, DeleteVideoFilesView, DownloadVideoFiles from .views.auth import ExtendedLoginView, RegisterView, RegisterDoneView from .views.index import index, ajax_get_tree, ajax_get_videos, CreateFolderModal, UpdateFolderModal, DeleteFolderModal, \ CreateSubscriptionModal, UpdateSubscriptionModal, DeleteSubscriptionModal, ImportSubscriptionsModal -from .views.settings import SettingsView +from .views.settings import SettingsView, AdminSettingsView from .views import first_time urlpatterns = [ @@ -59,8 +59,9 @@ urlpatterns = [ # Pages path('', index, name='home'), 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/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'), diff --git a/app/YtManagerApp/utils/youtube.py b/app/YtManagerApp/utils/youtube.py index 9e735ff..e2d7576 100644 --- a/app/YtManagerApp/utils/youtube.py +++ b/app/YtManagerApp/utils/youtube.py @@ -7,7 +7,8 @@ class YoutubeAPI(YouTube): @staticmethod 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 # def build_oauth() -> 'YoutubeAPI': diff --git a/app/YtManagerApp/views/auth.py b/app/YtManagerApp/views/auth.py index 1a39429..ec668fe 100644 --- a/app/YtManagerApp/views/auth.py +++ b/app/YtManagerApp/views/auth.py @@ -2,10 +2,12 @@ from django.contrib.auth import login, authenticate from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.contrib.auth.views import LoginView +from django.http import HttpResponseForbidden from django.urls import reverse_lazy 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): @@ -33,6 +35,12 @@ class RegisterView(FormView): context['is_first_user'] = (User.objects.count() == 0) 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): template_name = 'registration/register_done.html' diff --git a/app/YtManagerApp/views/first_time.py b/app/YtManagerApp/views/first_time.py index beae807..9b20dd8 100644 --- a/app/YtManagerApp/views/first_time.py +++ b/app/YtManagerApp/views/first_time.py @@ -7,7 +7,7 @@ from django.shortcuts import redirect from django.urls import reverse_lazy 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.first_time import WelcomeForm, ApiKeyForm, PickAdminUserForm, ServerConfigForm, DoneForm, UserCreationForm @@ -18,7 +18,7 @@ class WizardStepMixin(object): def get(self, request, *args, **kwargs): # 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 " f"page.") return redirect('home') @@ -26,7 +26,7 @@ class WizardStepMixin(object): return super().get(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.") return HttpResponseForbidden() return super().post(request, *args, **kwargs) @@ -51,14 +51,14 @@ class Step1ApiKeyView(WizardStepMixin, FormView): def get_initial(self): initial = super().get_initial() - initial['api_key'] = global_prefs['general__youtube_api_key'] + initial['api_key'] = appconfig.youtube_api_key return initial def form_valid(self, form): key = form.cleaned_data['api_key'] # TODO: validate key 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) - def form_invalid(self, form): - print("FORM INVALID!") - def form_valid(self, form): if isinstance(form, ExtendedAuthenticationForm): login(self.request, form.get_user()) @@ -139,8 +136,8 @@ class Step3ConfigureView(WizardStepMixin, FormView): def get_initial(self): initial = super().get_initial() - initial['allow_registrations'] = global_prefs['general__allow_registrations'] - initial['sync_schedule'] = global_prefs['scheduler__synchronization_schedule'] + initial['allow_registrations'] = appconfig.allow_registrations + initial['sync_schedule'] = appconfig.sync_schedule initial['auto_download'] = self.request.user.preferences['downloader__auto_enabled'] initial['download_location'] = self.request.user.preferences['downloader__download_path'] return initial @@ -148,11 +145,11 @@ class Step3ConfigureView(WizardStepMixin, FormView): def form_valid(self, form): allow_registrations = form.cleaned_data['allow_registrations'] 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'] 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'] if auto_download is not None: @@ -163,7 +160,7 @@ class Step3ConfigureView(WizardStepMixin, FormView): self.request.user.preferences['downloader__download_path'] = download_location # Set initialized to true - global_prefs['hidden__initialized'] = True + appconfig.initialized = True return super().form_valid(form) diff --git a/app/YtManagerApp/views/forms/first_time.py b/app/YtManagerApp/views/forms/first_time.py index 128191a..6f4dfae 100644 --- a/app/YtManagerApp/views/forms/first_time.py +++ b/app/YtManagerApp/views/forms/first_time.py @@ -3,11 +3,10 @@ import logging from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, HTML, Submit, Column from django import forms -from django.contrib.auth import authenticate, login from django.contrib.auth.models import User -from YtManagerApp.views.forms.auth import ExtendedUserCreationForm from django.urls import reverse_lazy -from YtManagerApp.management.appconfig import global_prefs + +from YtManagerApp.views.forms.auth import ExtendedUserCreationForm logger = logging.getLogger("FirstTimeWizard") diff --git a/app/YtManagerApp/views/forms/settings.py b/app/YtManagerApp/views/forms/settings.py new file mode 100644 index 0000000..b22e096 --- /dev/null +++ b/app/YtManagerApp/views/forms/settings.py @@ -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('

Download settings

'), + 'auto_download', + 'download_path', + 'download_file_pattern', + 'download_format', + 'download_order', + 'download_global_limit', + 'download_subscription_limit', + HTML('

Subtitles download settings

'), + '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('

General settings

'), + 'api_key', + 'allow_registrations', + HTML('

Scheduler settings

'), + 'sync_schedule', + 'scheduler_concurrency', + Submit('submit', value='Save') + ) diff --git a/app/YtManagerApp/views/index.py b/app/YtManagerApp/views/index.py index 2a0c791..49a2909 100644 --- a/app/YtManagerApp/views/index.py +++ b/app/YtManagerApp/views/index.py @@ -10,7 +10,7 @@ from django.views.generic import CreateView, UpdateView, DeleteView, FormView from django.views.generic.edit import FormMixin from django.conf import settings 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.utils import youtube, subscription_file_parser from YtManagerApp.views.controls.modal import ModalMixin @@ -96,7 +96,7 @@ def __tree_sub_id(sub_id): def index(request: HttpRequest): - if not global_prefs['hidden__initialized']: + if not appconfig.initialized: return redirect('first_time_0') context = { diff --git a/app/YtManagerApp/views/settings.py b/app/YtManagerApp/views/settings.py index f7a019b..ac4f8ad 100644 --- a/app/YtManagerApp/views/settings.py +++ b/app/YtManagerApp/views/settings.py @@ -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.http import HttpResponseForbidden 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 - - -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('

Download settings

'), - 'auto_download', - 'download_path', - 'download_file_pattern', - 'download_format', - 'download_order', - 'download_global_limit', - 'download_subscription_limit', - HTML('

Subtitles download settings

'), - 'download_subtitles', - 'download_subtitles_langs', - 'download_subtitles_all', - 'download_autogenerated_subtitles', - 'download_subtitles_format', - Submit('submit', value='Save') - ) +from YtManagerApp.views.forms.settings import SettingsForm, AdminSettingsForm class SettingsView(LoginRequiredMixin, UpdateView): @@ -49,3 +17,53 @@ class SettingsView(LoginRequiredMixin, UpdateView): def get_object(self, queryset=None): obj, _ = self.model.objects.get_or_create(user=self.request.user) 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)