mirror of
https://github.com/chibicitiberiu/ytsm.git
synced 2024-02-24 05:43:31 +00:00
Added docker support
This commit is contained in:
0
app/YtManagerApp/views/__init__.py
Normal file
0
app/YtManagerApp/views/__init__.py
Normal file
51
app/YtManagerApp/views/actions.py
Normal file
51
app/YtManagerApp/views/actions.py
Normal file
@ -0,0 +1,51 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import JsonResponse
|
||||
from django.views.generic import View
|
||||
|
||||
from YtManagerApp.management.jobs.synchronize import schedule_synchronize_now
|
||||
from YtManagerApp.models import Video
|
||||
|
||||
|
||||
class SyncNowView(LoginRequiredMixin, View):
|
||||
def post(self, *args, **kwargs):
|
||||
schedule_synchronize_now()
|
||||
return JsonResponse({
|
||||
'success': True
|
||||
})
|
||||
|
||||
|
||||
class DeleteVideoFilesView(LoginRequiredMixin, View):
|
||||
def post(self, *args, **kwargs):
|
||||
video = Video.objects.get(id=kwargs['pk'])
|
||||
video.delete_files()
|
||||
return JsonResponse({
|
||||
'success': True
|
||||
})
|
||||
|
||||
|
||||
class DownloadVideoFilesView(LoginRequiredMixin, View):
|
||||
def post(self, *args, **kwargs):
|
||||
video = Video.objects.get(id=kwargs['pk'])
|
||||
video.download()
|
||||
return JsonResponse({
|
||||
'success': True
|
||||
})
|
||||
|
||||
|
||||
class MarkVideoWatchedView(LoginRequiredMixin, View):
|
||||
def post(self, *args, **kwargs):
|
||||
video = Video.objects.get(id=kwargs['pk'])
|
||||
video.mark_watched()
|
||||
return JsonResponse({
|
||||
'success': True
|
||||
})
|
||||
|
||||
|
||||
class MarkVideoUnwatchedView(LoginRequiredMixin, View):
|
||||
def post(self, *args, **kwargs):
|
||||
video = Video.objects.get(id=kwargs['pk'])
|
||||
video.mark_unwatched()
|
||||
video.save()
|
||||
return JsonResponse({
|
||||
'success': True
|
||||
})
|
83
app/YtManagerApp/views/auth.py
Normal file
83
app/YtManagerApp/views/auth.py
Normal file
@ -0,0 +1,83 @@
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
from django import forms
|
||||
from django.contrib.auth import login, authenticate
|
||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import FormView, TemplateView
|
||||
|
||||
|
||||
class ExtendedAuthenticationForm(AuthenticationForm):
|
||||
remember_me = forms.BooleanField(label='Remember me', required=False, initial=False)
|
||||
|
||||
def clean(self):
|
||||
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()
|
||||
|
||||
|
||||
class ExtendedLoginView(LoginView):
|
||||
form_class = ExtendedAuthenticationForm
|
||||
|
||||
|
||||
class ExtendedUserCreationForm(UserCreationForm):
|
||||
email = forms.EmailField(required=False,
|
||||
label='E-mail address',
|
||||
help_text='The e-mail address is optional, but it is the only way to recover a lost '
|
||||
'password.')
|
||||
first_name = forms.CharField(max_length=30, required=False,
|
||||
label='First name')
|
||||
last_name = forms.CharField(max_length=150, required=False,
|
||||
label='Last name')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.label_class = 'col-3'
|
||||
self.helper.field_class = 'col-9'
|
||||
self.helper.form_class = 'form-horizontal'
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_action = reverse_lazy('register')
|
||||
self.helper.add_input(Submit('submit', 'register'))
|
||||
|
||||
class Meta(UserCreationForm.Meta):
|
||||
fields = ['username', 'email', 'first_name', 'last_name']
|
||||
|
||||
|
||||
class RegisterView(FormView):
|
||||
template_name = 'registration/register.html'
|
||||
form_class = ExtendedUserCreationForm
|
||||
success_url = reverse_lazy('register_done')
|
||||
|
||||
def form_valid(self, form):
|
||||
is_first_user = (User.objects.count() == 0)
|
||||
|
||||
user = form.save()
|
||||
if is_first_user:
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
|
||||
username = form.cleaned_data.get('username')
|
||||
password = form.cleaned_data.get('password1')
|
||||
user = authenticate(username=username, password=password)
|
||||
login(self.request, user)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['is_first_user'] = (User.objects.count() == 0)
|
||||
return context
|
||||
|
||||
|
||||
class RegisterDoneView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'registration/register_done.html'
|
0
app/YtManagerApp/views/controls/__init__.py
Normal file
0
app/YtManagerApp/views/controls/__init__.py
Normal file
53
app/YtManagerApp/views/controls/modal.py
Normal file
53
app/YtManagerApp/views/controls/modal.py
Normal file
@ -0,0 +1,53 @@
|
||||
from django.views.generic.base import ContextMixin
|
||||
from django.http import JsonResponse
|
||||
|
||||
|
||||
class ModalMixin(ContextMixin):
|
||||
template_name = 'YtManagerApp/controls/modal.html'
|
||||
success_url = '/'
|
||||
|
||||
def __init__(self, modal_id='dialog', title='', fade=True, centered=True, small=False, large=False, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.id = modal_id
|
||||
self.title = title
|
||||
self.fade = fade
|
||||
self.centered = centered
|
||||
self.small = small
|
||||
self.large = large
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data['modal_id'] = self.id
|
||||
|
||||
data['modal_classes'] = ''
|
||||
if self.fade:
|
||||
data['modal_classes'] += 'fade '
|
||||
|
||||
data['modal_dialog_classes'] = ''
|
||||
if self.centered:
|
||||
data['modal_dialog_classes'] += 'modal-dialog-centered '
|
||||
if self.small:
|
||||
data['modal_dialog_classes'] += 'modal-sm '
|
||||
elif self.large:
|
||||
data['modal_dialog_classes'] += 'modal-lg '
|
||||
|
||||
data['modal_title'] = self.title
|
||||
|
||||
return data
|
||||
|
||||
def modal_response(self, form, success=True, error_msg=None):
|
||||
result = {'success': success}
|
||||
if not success:
|
||||
result['errors'] = form.errors.get_json_data(escape_html=True)
|
||||
if error_msg is not None:
|
||||
result['errors']['__all__'] = [{'message': error_msg}]
|
||||
|
||||
return JsonResponse(result)
|
||||
|
||||
def form_valid(self, form):
|
||||
super().form_valid(form)
|
||||
return self.modal_response(form, success=True)
|
||||
|
||||
def form_invalid(self, form):
|
||||
super().form_invalid(form)
|
||||
return self.modal_response(form, success=False)
|
348
app/YtManagerApp/views/index.py
Normal file
348
app/YtManagerApp/views/index.py
Normal file
@ -0,0 +1,348 @@
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Field, HTML
|
||||
from django import forms
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
from django.http import HttpRequest, HttpResponseBadRequest, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.views.generic import CreateView, UpdateView, DeleteView
|
||||
from django.views.generic.edit import FormMixin
|
||||
|
||||
from YtManagerApp.management.videos import get_videos
|
||||
from YtManagerApp.models import Subscription, SubscriptionFolder, VIDEO_ORDER_CHOICES, VIDEO_ORDER_MAPPING
|
||||
from YtManagerApp.utils import youtube
|
||||
from YtManagerApp.views.controls.modal import ModalMixin
|
||||
|
||||
|
||||
class VideoFilterForm(forms.Form):
|
||||
CHOICES_SHOW_WATCHED = (
|
||||
('y', 'Watched'),
|
||||
('n', 'Not watched'),
|
||||
('all', '(All)')
|
||||
)
|
||||
|
||||
CHOICES_SHOW_DOWNLOADED = (
|
||||
('y', 'Downloaded'),
|
||||
('n', 'Not downloaded'),
|
||||
('all', '(All)')
|
||||
)
|
||||
|
||||
MAPPING_SHOW = {
|
||||
'y': True,
|
||||
'n': False,
|
||||
'all': None
|
||||
}
|
||||
|
||||
query = forms.CharField(label='', required=False)
|
||||
sort = forms.ChoiceField(label='Sort:', choices=VIDEO_ORDER_CHOICES, initial='newest')
|
||||
show_watched = forms.ChoiceField(label='Show only: ', choices=CHOICES_SHOW_WATCHED, initial='all')
|
||||
show_downloaded = forms.ChoiceField(label='', choices=CHOICES_SHOW_DOWNLOADED, initial='all')
|
||||
subscription_id = forms.IntegerField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
folder_id = forms.IntegerField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
|
||||
def __init__(self, data=None):
|
||||
super().__init__(data, auto_id='form_video_filter_%s')
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_id = 'form_video_filter'
|
||||
self.helper.form_class = 'form-inline'
|
||||
self.helper.form_method = 'POST'
|
||||
self.helper.form_action = 'ajax_get_videos'
|
||||
self.helper.field_class = 'mr-1'
|
||||
self.helper.label_class = 'ml-2 mr-1 no-asterisk'
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Field('query', placeholder='Search'),
|
||||
'sort',
|
||||
'show_watched',
|
||||
'show_downloaded',
|
||||
'subscription_id',
|
||||
'folder_id'
|
||||
)
|
||||
|
||||
def clean_sort(self):
|
||||
data = self.cleaned_data['sort']
|
||||
return VIDEO_ORDER_MAPPING[data]
|
||||
|
||||
def clean_show_downloaded(self):
|
||||
data = self.cleaned_data['show_downloaded']
|
||||
return VideoFilterForm.MAPPING_SHOW[data]
|
||||
|
||||
def clean_show_watched(self):
|
||||
data = self.cleaned_data['show_watched']
|
||||
return VideoFilterForm.MAPPING_SHOW[data]
|
||||
|
||||
|
||||
def __tree_folder_id(fd_id):
|
||||
if fd_id is None:
|
||||
return '#'
|
||||
return 'folder' + str(fd_id)
|
||||
|
||||
|
||||
def __tree_sub_id(sub_id):
|
||||
if sub_id is None:
|
||||
return '#'
|
||||
return 'sub' + str(sub_id)
|
||||
|
||||
|
||||
def index(request: HttpRequest):
|
||||
if request.user.is_authenticated:
|
||||
context = {
|
||||
'filter_form': VideoFilterForm()
|
||||
}
|
||||
return render(request, 'YtManagerApp/index.html', context)
|
||||
else:
|
||||
return render(request, 'YtManagerApp/index_unauthenticated.html')
|
||||
|
||||
|
||||
@login_required
|
||||
def ajax_get_tree(request: HttpRequest):
|
||||
|
||||
def visit(node):
|
||||
if isinstance(node, SubscriptionFolder):
|
||||
return {
|
||||
"id": __tree_folder_id(node.id),
|
||||
"text": node.name,
|
||||
"type": "folder",
|
||||
"state": {"opened": True},
|
||||
"parent": __tree_folder_id(node.parent_id)
|
||||
}
|
||||
elif isinstance(node, Subscription):
|
||||
return {
|
||||
"id": __tree_sub_id(node.id),
|
||||
"type": "sub",
|
||||
"text": node.name,
|
||||
"icon": node.icon_default,
|
||||
"parent": __tree_folder_id(node.parent_folder_id)
|
||||
}
|
||||
|
||||
result = SubscriptionFolder.traverse(None, request.user, visit)
|
||||
return JsonResponse(result, safe=False)
|
||||
|
||||
|
||||
@login_required
|
||||
def ajax_get_videos(request: HttpRequest):
|
||||
if request.method == 'POST':
|
||||
form = VideoFilterForm(request.POST)
|
||||
if form.is_valid():
|
||||
videos = get_videos(
|
||||
user=request.user,
|
||||
sort_order=form.cleaned_data['sort'],
|
||||
query=form.cleaned_data['query'],
|
||||
subscription_id=form.cleaned_data['subscription_id'],
|
||||
folder_id=form.cleaned_data['folder_id'],
|
||||
only_watched=form.cleaned_data['show_watched'],
|
||||
only_downloaded=form.cleaned_data['show_downloaded']
|
||||
)
|
||||
|
||||
context = {
|
||||
'videos': videos
|
||||
}
|
||||
|
||||
return render(request, 'YtManagerApp/index_videos.html', context)
|
||||
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
class SubscriptionFolderForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = SubscriptionFolder
|
||||
fields = ['name', 'parent']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
return name.strip()
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
name = cleaned_data.get('name')
|
||||
parent = cleaned_data.get('parent')
|
||||
|
||||
# Check name is unique in parent folder
|
||||
args_id = []
|
||||
if self.instance is not None:
|
||||
args_id.append(~Q(id=self.instance.id))
|
||||
|
||||
if SubscriptionFolder.objects.filter(parent=parent, name__iexact=name, *args_id).count() > 0:
|
||||
raise forms.ValidationError(
|
||||
'A folder with the same name already exists in the given parent directory!', code='already_exists')
|
||||
|
||||
# Check for cycles
|
||||
if self.instance is not None:
|
||||
self.__test_cycles(parent)
|
||||
|
||||
def __test_cycles(self, new_parent):
|
||||
visited = [self.instance.id]
|
||||
current = new_parent
|
||||
while current is not None:
|
||||
if current.id in visited:
|
||||
raise forms.ValidationError('Selected parent would create a parenting cycle!', code='parenting_cycle')
|
||||
visited.append(current.id)
|
||||
current = current.parent
|
||||
|
||||
|
||||
class CreateFolderModal(LoginRequiredMixin, ModalMixin, CreateView):
|
||||
template_name = 'YtManagerApp/controls/folder_create_modal.html'
|
||||
form_class = SubscriptionFolderForm
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UpdateFolderModal(LoginRequiredMixin, ModalMixin, UpdateView):
|
||||
template_name = 'YtManagerApp/controls/folder_update_modal.html'
|
||||
model = SubscriptionFolder
|
||||
form_class = SubscriptionFolderForm
|
||||
|
||||
|
||||
class DeleteFolderForm(forms.Form):
|
||||
keep_subscriptions = forms.BooleanField(required=False, initial=False, label="Keep subscriptions")
|
||||
|
||||
|
||||
class DeleteFolderModal(LoginRequiredMixin, ModalMixin, FormMixin, DeleteView):
|
||||
template_name = 'YtManagerApp/controls/folder_delete_modal.html'
|
||||
model = SubscriptionFolder
|
||||
form_class = DeleteFolderForm
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object.delete_folder(keep_subscriptions=form.cleaned_data['keep_subscriptions'])
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class CreateSubscriptionForm(forms.ModelForm):
|
||||
playlist_url = forms.URLField(label='Playlist/Channel URL')
|
||||
|
||||
class Meta:
|
||||
model = Subscription
|
||||
fields = ['parent_folder', 'auto_download',
|
||||
'download_limit', 'download_order', 'delete_after_watched']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.yt_api = youtube.YoutubeAPI.build_public()
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.layout = Layout(
|
||||
'playlist_url',
|
||||
'parent_folder',
|
||||
HTML('<hr>'),
|
||||
HTML('<h5>Download configuration overloads</h5>'),
|
||||
'auto_download',
|
||||
'download_limit',
|
||||
'download_order',
|
||||
'delete_after_watched'
|
||||
)
|
||||
|
||||
def clean_playlist_url(self):
|
||||
playlist_url: str = self.cleaned_data['playlist_url']
|
||||
try:
|
||||
parsed_url = self.yt_api.parse_url(playlist_url)
|
||||
except youtube.InvalidURL as e:
|
||||
raise forms.ValidationError(str(e))
|
||||
|
||||
is_playlist = 'playlist' in parsed_url
|
||||
is_channel = parsed_url['type'] in ('channel', 'user', 'channel_custom')
|
||||
|
||||
if not is_channel and not is_playlist:
|
||||
raise forms.ValidationError('The given URL must link to a channel or a playlist!')
|
||||
|
||||
return playlist_url
|
||||
|
||||
|
||||
class CreateSubscriptionModal(LoginRequiredMixin, ModalMixin, CreateView):
|
||||
template_name = 'YtManagerApp/controls/subscription_create_modal.html'
|
||||
form_class = CreateSubscriptionForm
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
api = youtube.YoutubeAPI.build_public()
|
||||
try:
|
||||
form.instance.fetch_from_url(form.cleaned_data['playlist_url'], api)
|
||||
except youtube.InvalidURL as e:
|
||||
return self.modal_response(form, False, str(e))
|
||||
except ValueError as e:
|
||||
return self.modal_response(form, False, str(e))
|
||||
# except youtube.YoutubeUserNotFoundException:
|
||||
# return self.modal_response(
|
||||
# form, False, 'Could not find an user based on the given URL. Please verify that the URL is correct.')
|
||||
# except youtube.YoutubePlaylistNotFoundException:
|
||||
# return self.modal_response(
|
||||
# form, False, 'Could not find a playlist based on the given URL. Please verify that the URL is correct.')
|
||||
# except youtube.YoutubeException as e:
|
||||
# return self.modal_response(
|
||||
# form, False, str(e))
|
||||
# except youtube.APIError as e:
|
||||
# return self.modal_response(
|
||||
# form, False, 'An error occurred while communicating with the YouTube API: ' + str(e))
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UpdateSubscriptionForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Subscription
|
||||
fields = ['name', 'parent_folder', 'auto_download',
|
||||
'download_limit', 'download_order', 'delete_after_watched']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.layout = Layout(
|
||||
'name',
|
||||
'parent_folder',
|
||||
HTML('<hr>'),
|
||||
HTML('<h5>Download configuration overloads</h5>'),
|
||||
'auto_download',
|
||||
'download_limit',
|
||||
'download_order',
|
||||
'delete_after_watched'
|
||||
)
|
||||
|
||||
|
||||
class UpdateSubscriptionModal(LoginRequiredMixin, ModalMixin, UpdateView):
|
||||
template_name = 'YtManagerApp/controls/subscription_update_modal.html'
|
||||
model = Subscription
|
||||
form_class = UpdateSubscriptionForm
|
||||
|
||||
|
||||
class DeleteSubscriptionForm(forms.Form):
|
||||
keep_downloaded_videos = forms.BooleanField(required=False, initial=False, label="Keep downloaded videos")
|
||||
|
||||
|
||||
class DeleteSubscriptionModal(LoginRequiredMixin, ModalMixin, FormMixin, DeleteView):
|
||||
template_name = 'YtManagerApp/controls/subscription_delete_modal.html'
|
||||
model = Subscription
|
||||
form_class = DeleteSubscriptionForm
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object.delete_subscription(keep_downloaded_videos=form.cleaned_data['keep_downloaded_videos'])
|
||||
return super().form_valid(form)
|
51
app/YtManagerApp/views/settings.py
Normal file
51
app/YtManagerApp/views/settings.py
Normal file
@ -0,0 +1,51 @@
|
||||
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.urls import reverse_lazy
|
||||
from django.views.generic import UpdateView
|
||||
|
||||
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 SettingsView(LoginRequiredMixin, UpdateView):
|
||||
form_class = SettingsForm
|
||||
model = UserSettings
|
||||
template_name = 'YtManagerApp/settings.html'
|
||||
success_url = reverse_lazy('home')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj, _ = self.model.objects.get_or_create(user=self.request.user)
|
||||
return obj
|
Reference in New Issue
Block a user