mirror of
				https://github.com/chibicitiberiu/ytsm.git
				synced 2024-02-24 05:43:31 +00:00 
			
		
		
		
	Merge branch 'status-on-ui' of https://github.com/chibicitiberiu/ytsm into status-on-ui
This commit is contained in:
		| @@ -1,4 +1,5 @@ | ||||
| import logging | ||||
| import mimetypes | ||||
| import os | ||||
| from typing import Callable, Union, Any, Optional | ||||
|  | ||||
| @@ -218,6 +219,19 @@ class Video(models.Model): | ||||
|                 if file.startswith(file_pattern): | ||||
|                     yield os.path.join(directory, file) | ||||
|  | ||||
|     def find_video(self): | ||||
|         """ | ||||
|         Finds the video file from the downloaded files, and | ||||
|         returns | ||||
|         :return: Tuple containing file path and mime type | ||||
|         """ | ||||
|         for file in self.get_files(): | ||||
|             mime, _ = mimetypes.guess_type(file) | ||||
|             if mime is not None and mime.startswith('video/'): | ||||
|                 return (file, mime) | ||||
|  | ||||
|         return None, None | ||||
|  | ||||
|     def delete_files(self): | ||||
|         if self.downloaded_path is not None: | ||||
|             from YtManagerApp.management.jobs.delete_video import schedule_delete_video | ||||
|   | ||||
| @@ -6,13 +6,17 @@ | ||||
|         {% for video in videos %} | ||||
|             <div class="card-wrapper d-flex align-items-stretch" style="width: 18rem;"> | ||||
|                 <div class="card mx-auto"> | ||||
|                     <a href="{% url 'video' video.id %}"> | ||||
|                         <img class="card-img-top" src="{{ video.icon_best }}" alt="Thumbnail"> | ||||
|                     </a> | ||||
|                     <div class="card-body"> | ||||
|                         <h5 class="card-title"> | ||||
|                             {% if not video.watched %} | ||||
|                             <sup class="badge badge-primary">New</sup> | ||||
|                             {% endif %} | ||||
|                             <a href="{% url 'video' video.id %}"> | ||||
|                                 {{ video.name }} | ||||
|                             </a> | ||||
|                         </h5> | ||||
|                         <p class="card-text small text-muted"> | ||||
|                             <span>{{ video.views | intcomma }} views</span> | ||||
|   | ||||
							
								
								
									
										83
									
								
								app/YtManagerApp/templates/YtManagerApp/video.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								app/YtManagerApp/templates/YtManagerApp/video.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| {% extends "YtManagerApp/master_default.html" %} | ||||
| {% load static %} | ||||
| {% load humanize %} | ||||
| {% load ratings %} | ||||
|  | ||||
| {% block body %} | ||||
|  | ||||
|     <div class="container"> | ||||
|  | ||||
|         <h2>{{ object.name }}</h2> | ||||
|  | ||||
|         <div class="row"> | ||||
|  | ||||
|             <div class="col-8"> | ||||
|                 {% if video_mime != None %} | ||||
|                     <video width="100%" controls autoplay> | ||||
|                         <source src="{% url 'video-src' object.id %}" type="{{ video_mime }}"> | ||||
|                     </video> | ||||
|                 {% else %} | ||||
|                     <iframe id="ytplayer" type="text/html" width="100%" height="400" | ||||
|                           src="https://www.youtube.com/embed/{{ object.video_id }}?autoplay=1" | ||||
|                           frameborder="0"></iframe> | ||||
|                 {% endif %} | ||||
|  | ||||
|                 <div class="row mx-0 mt-2"> | ||||
|                     <p class="text-muted mb-1"> | ||||
|                         <span>{{ object.views | intcomma }} views</span> | ||||
|                         <span>•</span> | ||||
|                         <span>{{ object.publish_date | naturaltime }}</span> | ||||
|                     </p> | ||||
|                     <div class="ml-auto"> | ||||
|                         {% starrating object.rating %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="video-description"> | ||||
|                     {{ object.description | linebreaks | urlize }} | ||||
|                 </div> | ||||
|  | ||||
|                 </p> | ||||
|             </div> | ||||
|  | ||||
|             <div class="col-4"> | ||||
|                 <h5>Manage video</h5> | ||||
|                 <p> | ||||
|                     <a class="btn btn-secondary" href="https://youtube.com/watch?v={{ object.video_id }}"> | ||||
|                         Watch on YouTube <span class="typcn typcn-social-youtube"></span> | ||||
|                     </a> | ||||
|                 </p> | ||||
|  | ||||
|                 {% if object.watched %} | ||||
|                     <p> | ||||
|                         <a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_mark_video_unwatched' object.id %}"> | ||||
|                             Mark not watched | ||||
|                         </a> | ||||
|                     </p> | ||||
|                 {% else %} | ||||
|                     <p> | ||||
|                         <a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_mark_video_watched' object.id %}"> | ||||
|                             Mark watched | ||||
|                         </a> | ||||
|                     </p> | ||||
|                 {% endif %} | ||||
|  | ||||
|                 {% if object.downloaded_path %} | ||||
|                     <p> | ||||
|                     <a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_delete_video_files' object.id %}"> | ||||
|                         Delete downloaded | ||||
|                     </a> | ||||
|                     </p> | ||||
|                 {% else %} | ||||
|                     <p> | ||||
|                     <a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_download_video_files' object.id %}" > | ||||
|                         Download | ||||
|                     </a> | ||||
|                     </p> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
| {% endblock %} | ||||
| @@ -26,6 +26,7 @@ from .views.index import index, ajax_get_tree, ajax_get_videos, CreateFolderModa | ||||
|     CreateSubscriptionModal, UpdateSubscriptionModal, DeleteSubscriptionModal, ImportSubscriptionsModal | ||||
| from .views.notifications import ajax_get_notifications | ||||
| from .views.settings import SettingsView, AdminSettingsView | ||||
| from .views.video import VideoDetailView, video_detail_view | ||||
|  | ||||
| urlpatterns = [ | ||||
|     # Authentication URLs | ||||
| @@ -63,6 +64,8 @@ urlpatterns = [ | ||||
|     path('', index, name='home'), | ||||
|     path('settings/', SettingsView.as_view(), name='settings'), | ||||
|     path('admin_settings/', AdminSettingsView.as_view(), name='admin_settings'), | ||||
|     path('video/<int:pk>/', VideoDetailView.as_view(), name='video'), | ||||
|     path('video-src/<int:pk>/', video_detail_view, name='video-src'), | ||||
|  | ||||
|     # First time setup | ||||
|     path('first_time/step0_welcome', first_time.Step0WelcomeView.as_view(), name='first_time_0'), | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class RegisterView(FormView): | ||||
|     success_url = reverse_lazy('register_done') | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|  | ||||
|         form.apply_session_expiry(self.request) | ||||
|         form.save() | ||||
|  | ||||
|         username = form.cleaned_data.get('username') | ||||
|   | ||||
| @@ -11,8 +11,8 @@ from django.views.generic import FormView | ||||
| from YtManagerApp.management.appconfig import appconfig | ||||
| from YtManagerApp.management.jobs.synchronize import schedule_synchronize_global | ||||
| from YtManagerApp.scheduler import initialize_scheduler | ||||
| 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, LoginForm | ||||
|  | ||||
| logger = logging.getLogger("FirstTimeWizard") | ||||
|  | ||||
| @@ -85,38 +85,35 @@ class Step2SetupAdminUserView(WizardStepMixin, FormView): | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.__form_class = UserCreationForm | ||||
|  | ||||
|     def get_form_class(self): | ||||
|         return self.__form_class | ||||
|  | ||||
|     def get(self, request, *args, **kwargs): | ||||
|  | ||||
|         have_users = User.objects.count() > 0 | ||||
|         have_admin = User.objects.filter(is_superuser=True).count() > 0 | ||||
|  | ||||
|         # Check if an admin user already exists | ||||
|         if have_admin: | ||||
|             logger.debug("Admin user already exists and is not logged in!") | ||||
|             return LoginForm | ||||
|  | ||||
|         elif have_users and 'register' not in self.kwargs: | ||||
|             logger.debug("There are users but no admin!") | ||||
|             return PickAdminUserForm | ||||
|  | ||||
|         logger.debug("No admin user exists, will register a new account!") | ||||
|         return UserCreationForm | ||||
|  | ||||
|     def get(self, request, *args, **kwargs): | ||||
|  | ||||
|         # Skip if admin is already logged in | ||||
|         if request.user.is_authenticated and request.user.is_superuser: | ||||
|             logger.debug("Admin user already exists and is logged in!") | ||||
|             return redirect(self.success_url) | ||||
|  | ||||
|         # Check if an admin user already exists | ||||
|         elif have_admin: | ||||
|             logger.debug("Admin user already exists and is not logged in!") | ||||
|             self.__form_class = ExtendedAuthenticationForm | ||||
|  | ||||
|         elif have_users and 'register' not in kwargs: | ||||
|             logger.debug("There are users but no admin!") | ||||
|             self.__form_class = PickAdminUserForm | ||||
|  | ||||
|         else: | ||||
|             logger.debug("No admin user exists, will register a new account!") | ||||
|             self.__form_class = UserCreationForm | ||||
|  | ||||
|         return super().get(request, *args, **kwargs) | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|         if isinstance(form, ExtendedAuthenticationForm): | ||||
|         if isinstance(form, LoginForm): | ||||
|             form.apply_session_expiry(self.request) | ||||
|             login(self.request, form.get_user()) | ||||
|  | ||||
|         elif isinstance(form, UserCreationForm): | ||||
|   | ||||
| @@ -8,15 +8,14 @@ from django.urls import reverse_lazy | ||||
| class ExtendedAuthenticationForm(AuthenticationForm): | ||||
|     remember_me = forms.BooleanField(label='Remember me', required=False, initial=False) | ||||
|  | ||||
|     def clean(self): | ||||
|     def apply_session_expiry(self, request): | ||||
|         remember_me = self.cleaned_data.get('remember_me') | ||||
|         if remember_me: | ||||
|             expiry = 3600 * 24 * 30 | ||||
|         else: | ||||
|             expiry = 0 | ||||
|         self.request.session.set_expiry(expiry) | ||||
|  | ||||
|         return super().clean() | ||||
|         request.session.set_expiry(expiry) | ||||
|  | ||||
|  | ||||
| class ExtendedUserCreationForm(UserCreationForm): | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from django import forms | ||||
| from django.contrib.auth.models import User | ||||
| from django.urls import reverse_lazy | ||||
|  | ||||
| from YtManagerApp.views.forms.auth import ExtendedUserCreationForm | ||||
| from YtManagerApp.views.forms.auth import ExtendedUserCreationForm, ExtendedAuthenticationForm | ||||
|  | ||||
| logger = logging.getLogger("FirstTimeWizard") | ||||
|  | ||||
| @@ -30,7 +30,7 @@ class ApiKeyForm(forms.Form): | ||||
|             'api_key', | ||||
|             Column( | ||||
|                 Submit('submit', value='Continue'), | ||||
|                 HTML('<a href="{% url \'first_time_2\' %}" class="btn">Skip</a>') | ||||
|                 HTML('<a href="{% url \'first_time_2\' %}" class="btn btn-secondary">Skip</a>') | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
| @@ -39,6 +39,22 @@ class UserCreationForm(ExtendedUserCreationForm): | ||||
|     form_action = reverse_lazy('first_time_2') | ||||
|  | ||||
|  | ||||
| class LoginForm(ExtendedAuthenticationForm): | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.helper = FormHelper() | ||||
|         self.helper.layout = Layout( | ||||
|             'username', | ||||
|             'password', | ||||
|             'remember_me', | ||||
|             Column( | ||||
|                 Submit('submit', value='Continue'), | ||||
|                 HTML('<a href="{% url \'first_time_2\' %}?register=1" class="btn">Register new admin account</a>') | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class PickAdminUserForm(forms.Form): | ||||
|     admin_user = forms.ModelChoiceField( | ||||
|             User.objects.order_by('username'), | ||||
|   | ||||
							
								
								
									
										30
									
								
								app/YtManagerApp/views/video.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/YtManagerApp/views/video.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| from django.contrib.auth.decorators import login_required | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.http import HttpRequest, StreamingHttpResponse, FileResponse | ||||
| from django.urls import reverse, reverse_lazy | ||||
| from django.views import View | ||||
| from django.views.generic import DetailView | ||||
|  | ||||
| from YtManagerApp.models import Video | ||||
|  | ||||
|  | ||||
| class VideoDetailView(LoginRequiredMixin, DetailView): | ||||
|     template_name = 'YtManagerApp/video.html' | ||||
|     model = Video | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         video, mime = self.object.find_video() | ||||
|         if video is not None: | ||||
|             context['video_mime'] = mime | ||||
|  | ||||
|         return context | ||||
|  | ||||
|  | ||||
| @login_required | ||||
| def video_detail_view(request: HttpRequest, pk): | ||||
|     video = Video.objects.get(id = pk) | ||||
|     video_file, _ = video.find_video() | ||||
|  | ||||
|     f = open(video_file, 'rb') | ||||
|     return FileResponse(f) | ||||
		Reference in New Issue
	
	Block a user