mirror of
https://github.com/ajurna/cbwebreader.git
synced 2025-12-06 06:17:17 +00:00
Classification (#32)
* added some code cleanup for views.py * added some code cleanup for views.py * fixed comics not working in the base directory.
This commit is contained in:
@@ -40,6 +40,7 @@ INSTALLED_APPS = (
|
||||
"comic_auth",
|
||||
'django_extensions',
|
||||
'imagekit',
|
||||
'django_boost',
|
||||
)
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -47,5 +47,5 @@ class ComicStatusAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(UserMisc)
|
||||
class UserMiscAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'user', 'feed_id')
|
||||
list_display = ('user', 'feed_id', 'allowed_to_read')
|
||||
list_filter = ('user',)
|
||||
@@ -3,6 +3,8 @@ from os import path
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from comic.models import Directory
|
||||
|
||||
|
||||
class InitialSetupForm(forms.Form):
|
||||
username = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))
|
||||
@@ -87,28 +89,23 @@ class EditUserForm(forms.Form):
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}),
|
||||
)
|
||||
email = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))
|
||||
email = forms.EmailField(widget=forms.TextInput(attrs={"class": "form-control"}))
|
||||
password = forms.CharField(
|
||||
required=False, widget=forms.PasswordInput(attrs={"class": "form-control"})
|
||||
)
|
||||
# TODO: allow setting superuser on users
|
||||
allowed_to_read = forms.ChoiceField(choices=Directory.Classification.choices)
|
||||
|
||||
@staticmethod
|
||||
def get_initial_values(user):
|
||||
out = {"username": user.username, "email": user.email}
|
||||
out = {"username": user.username, "email": user.email, "allowed_to_read": user.usermisc.allowed_to_read}
|
||||
return out
|
||||
|
||||
def clean_email(self):
|
||||
data = self.cleaned_data["email"]
|
||||
user = User.objects.get(username=self.cleaned_data["username"])
|
||||
if data == user.email:
|
||||
return data
|
||||
if User.objects.filter(email=data).exists():
|
||||
raise forms.ValidationError("Email Address is in use")
|
||||
return data
|
||||
|
||||
def clean_password(self):
|
||||
data = self.cleaned_data["password"]
|
||||
if len(data) < 8 & len(data) != 0:
|
||||
raise forms.ValidationError("Password is too short")
|
||||
return data
|
||||
return data
|
||||
|
||||
|
||||
class DirectoryEditForm(forms.Form):
|
||||
classification = forms.ChoiceField(choices=Directory.Classification.choices)
|
||||
|
||||
23
comic/migrations/0025_auto_20210506_1342.py
Normal file
23
comic/migrations/0025_auto_20210506_1342.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2 on 2021-05-06 12:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comic', '0024_auto_20210422_0855'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='directory',
|
||||
name='classification',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'G'), (1, 'PG'), (2, '12'), (3, '15'), (4, '18')], default=4),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usermisc',
|
||||
name='allowed_to_read',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'G'), (1, 'PG'), (2, '12'), (3, '15'), (4, '18')], default=4),
|
||||
),
|
||||
]
|
||||
22
comic/migrations/0026_alter_usermisc_user.py
Normal file
22
comic/migrations/0026_alter_usermisc_user.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 3.2 on 2021-05-06 12:50
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import django_boost.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('comic', '0025_auto_20210506_1342'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='usermisc',
|
||||
name='user',
|
||||
field=django_boost.models.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
26
comic/migrations/0027_auto_20210506_1356.py
Normal file
26
comic/migrations/0027_auto_20210506_1356.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 3.2 on 2021-05-06 12:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import django_boost.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('comic', '0026_alter_usermisc_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='usermisc',
|
||||
name='id',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usermisc',
|
||||
name='user',
|
||||
field=django_boost.models.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -3,8 +3,7 @@ import mimetypes
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import reduce
|
||||
from itertools import zip_longest
|
||||
from os import listdir
|
||||
from itertools import zip_longest, chain
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Union, Tuple
|
||||
|
||||
@@ -18,6 +17,7 @@ from django.db import models
|
||||
from django.db.transaction import atomic
|
||||
from django.templatetags.static import static
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django_boost.models.fields import AutoOneToOneField
|
||||
from imagekit.models import ProcessedImageField
|
||||
from imagekit.processors import ResizeToFill
|
||||
|
||||
@@ -28,6 +28,13 @@ if settings.UNRAR_TOOL:
|
||||
|
||||
|
||||
class Directory(models.Model):
|
||||
class Classification(models.IntegerChoices):
|
||||
C_G = 0, 'G'
|
||||
C_PG = 1, 'PG'
|
||||
C_12 = 2, '12'
|
||||
C_15 = 3, '15'
|
||||
C_18 = 4, '18'
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
parent = models.ForeignKey("Directory", null=True, blank=True, on_delete=models.CASCADE)
|
||||
selector = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True)
|
||||
@@ -40,6 +47,7 @@ class Directory(models.Model):
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='directory_thumbnail_issue')
|
||||
thumbnail_index = models.PositiveIntegerField(default=0)
|
||||
classification = models.PositiveSmallIntegerField(choices=Classification.choices, default=Classification.C_18)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
@@ -108,6 +116,10 @@ class Directory(models.Model):
|
||||
def url_safe_selector(self):
|
||||
return urlsafe_base64_encode(self.selector.bytes)
|
||||
|
||||
def set_classification(self, form_data):
|
||||
self.classification = form_data['classification']
|
||||
self.save()
|
||||
|
||||
|
||||
class ComicBook(models.Model):
|
||||
file_name = models.TextField()
|
||||
@@ -144,7 +156,7 @@ class ComicBook(models.Model):
|
||||
def url_safe_selector(self):
|
||||
return urlsafe_base64_encode(self.selector.bytes)
|
||||
|
||||
def get_pdf(self):
|
||||
def get_pdf(self) -> Path:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
return Path(base_dir, self.directory.get_path(), self.file_name)
|
||||
|
||||
@@ -209,6 +221,7 @@ class ComicBook(models.Model):
|
||||
self.save()
|
||||
|
||||
def _get_pdf_image(self, page_index: int):
|
||||
# noinspection PyTypeChecker
|
||||
doc = fitz.open(self.get_pdf())
|
||||
page = doc[page_index]
|
||||
pix = page.get_pixmap()
|
||||
@@ -219,7 +232,6 @@ class ComicBook(models.Model):
|
||||
img.seek(0)
|
||||
return img, pil_data
|
||||
|
||||
|
||||
def is_last_page(self, page):
|
||||
if (self.page_count - 1) == page:
|
||||
return True
|
||||
@@ -265,9 +277,9 @@ class ComicBook(models.Model):
|
||||
book = ComicBook.objects.get(file_name=prev_comic, directory__isnull=True)
|
||||
except ComicBook.DoesNotExist:
|
||||
if self.directory:
|
||||
book = ComicBook.process_comic_book(prev_comic, self.directory)
|
||||
book = ComicBook.process_comic_book(Path(prev_comic), self.directory)
|
||||
else:
|
||||
book = ComicBook.process_comic_book(prev_comic)
|
||||
book = ComicBook.process_comic_book(Path(prev_comic))
|
||||
cs, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
|
||||
comic_path = urlsafe_base64_encode(book.selector.bytes)
|
||||
|
||||
@@ -290,9 +302,9 @@ class ComicBook(models.Model):
|
||||
book = ComicBook.objects.get(file_name=next_comic, directory__isnull=True)
|
||||
except ComicBook.DoesNotExist:
|
||||
if self.directory:
|
||||
book = ComicBook.process_comic_book(next_comic, self.directory)
|
||||
book = ComicBook.process_comic_book(Path(next_comic), self.directory)
|
||||
else:
|
||||
book = ComicBook.process_comic_book(next_comic)
|
||||
book = ComicBook.process_comic_book(Path(next_comic))
|
||||
except ComicBook.MultipleObjectsReturned:
|
||||
if self.directory:
|
||||
books = ComicBook.objects.filter(file_name=next_comic, directory=self.directory).order_by('id')
|
||||
@@ -353,21 +365,21 @@ class ComicBook(models.Model):
|
||||
with atomic():
|
||||
for page_index in range(archive.page_count):
|
||||
page = ComicPage(
|
||||
Comic=book, index=page_index, page_file_name=page_index+1, content_type='application/pdf'
|
||||
Comic=book, index=page_index, page_file_name=page_index + 1, content_type='application/pdf'
|
||||
)
|
||||
page.save()
|
||||
return book
|
||||
|
||||
@staticmethod
|
||||
def get_ordered_dir_list(folder):
|
||||
def get_ordered_dir_list(folder: Path) -> List[str]:
|
||||
directories = []
|
||||
files = []
|
||||
for item in listdir(folder):
|
||||
if Path(folder, item).is_dir():
|
||||
for item in folder.glob('*'):
|
||||
if item.is_dir():
|
||||
directories.append(item)
|
||||
else:
|
||||
files.append(item)
|
||||
return sorted(directories) + sorted(files)
|
||||
return [x.name for x in chain(sorted(directories), sorted(files))]
|
||||
|
||||
@property
|
||||
def get_archive_path(self):
|
||||
@@ -388,6 +400,7 @@ class ComicBook(models.Model):
|
||||
pass
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
return fitz.open(str(archive_path)), 'pdf'
|
||||
except RuntimeError:
|
||||
pass
|
||||
@@ -475,5 +488,8 @@ class ComicStatus(models.Model):
|
||||
|
||||
|
||||
class UserMisc(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
|
||||
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
feed_id = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True)
|
||||
allowed_to_read = models.PositiveSmallIntegerField(default=Directory.Classification.C_18,
|
||||
choices=Directory.Classification.choices)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
@@ -48,7 +49,7 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<h5 class="card-title {{ file.selector }}">
|
||||
{% if file.item_type == 'Directory' %}
|
||||
<a href="{% url "comic_list" file.selector %}" class="search-name">
|
||||
{% elif file.item_type == 'ComicBook' %}
|
||||
@@ -63,7 +64,7 @@
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ file.percent }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</p>
|
||||
<div class="btn-group" role="group" aria-label="Comic Actions">
|
||||
<div class="btn-group w-100" role="group" aria-label="Comic Actions">
|
||||
<button type="button" class="btn btn-primary comic_action" title="Mark Un-Read" selector="{{ file.selector }}" itemtype="{{ file.item_type }}" comic_action="mark_unread"><i class="fas fa-book"></i></button>
|
||||
<button type="button" class="btn btn-primary comic_action" title="Mark Read" selector="{{ file.selector }}" itemtype="{{ file.item_type }}" comic_action="mark_read"><i class="fas fa-book-open"></i></button>
|
||||
<div class="btn-group" role="group">
|
||||
@@ -75,25 +76,48 @@
|
||||
<button type="button" class="btn btn-primary dropdown-item comic_action" title="Mark Read" selector="{{ file.selector }}" itemtype="{{ file.item_type }}" comic_action="mark_read"><i class="fas fa-book-open">Mark Read</i></button>
|
||||
{% if file.item_type != 'Directory' %}
|
||||
<button type="button" class="btn btn-primary dropdown-item comic_action" title="Mark Previous Read" selector="{{ file.selector }}" itemtype="{{ file.item_type }}" comic_action="mark_previous"><i class="fas fa-book"><i class="fas fa-arrow-up">Mark Previous Read</i></i></button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary dropdown-item modal-button" title="Edit Comic" data-toggle="modal" data-target="#editModal" selector="{{ file.selector }}" itemtype="{{ file.item_type }}"><i class="fas fa-edit">Edit Comic</i></button>
|
||||
{% endif %}
|
||||
{# <button type="button" class="btn btn-primary dropdown-item" title="Edit Comic"><i class="fas fa-edit">Edit Comic</i></button>#}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% if file.total_unread and file.item_type == 'Directory' %}
|
||||
<span class="badge rounded-pill bg-primary card-badge">{{ file.total_unread }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% if file.total_unread and file.item_type == 'Directory' %}
|
||||
<span class="badge rounded-pill bg-primary unread-badge">{{ file.total_unread }}</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-warning {{ file.selector }} classification-badge" classification="{{ file.obj.classification }}">{{ file.obj.get_classification_display }}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Modal title</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="save_button">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script type="text/javascript" src="{% static "js/comic_list.min.js" %}"></script>
|
||||
{{ js_urls|json_script:'js_urls' }}
|
||||
<script type="text/javascript" src="{% static "js/comic_list.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
@@ -11,6 +11,7 @@
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Superuser</th>
|
||||
<th>Classification</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-link="row" class="rowlink">
|
||||
@@ -20,6 +21,7 @@
|
||||
<td><a href="{% url 'user_details' user.id %}">{{user.username}}</a></td>
|
||||
<td>{{user.email}}</td>
|
||||
<td>{{user.is_superuser}}</td>
|
||||
<td>{{ user.usermisc.get_allowed_to_read_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
0
comic/templatetags/__init__.py
Normal file
0
comic/templatetags/__init__.py
Normal file
4
comic/templatetags/comic_tags.py
Normal file
4
comic/templatetags/comic_tags.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -17,7 +17,7 @@ urlpatterns = [
|
||||
path("recent/", views.recent_comics, name="recent_comics"),
|
||||
path("recent/json/", views.recent_comics_json, name="recent_comics_json"),
|
||||
path("edit/", views.comic_edit, name="comic_edit"),
|
||||
path("feed/<int:user_selector>/", feeds.RecentComics()),
|
||||
path("feed/<user_selector>/", feeds.RecentComics()),
|
||||
path("<directory_selector>/", views.comic_list, name="comic_list"),
|
||||
path("<directory_selector>/thumb", views.directory_thumbnail, name="directory_thumbnail"),
|
||||
path("action/<operation>/<item_type>/<selector>/", views.perform_action, name="perform_action")
|
||||
|
||||
@@ -4,7 +4,8 @@ from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Count, Q, F
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Count, Q, F, Case, When, PositiveSmallIntegerField
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
|
||||
from .models import ComicBook, Directory, ComicStatus
|
||||
@@ -112,7 +113,7 @@ class DirFile:
|
||||
self.name = self.obj.file_name
|
||||
|
||||
|
||||
def generate_directory(user, directory=False):
|
||||
def generate_directory(user: User, directory=False):
|
||||
"""
|
||||
:type user: User
|
||||
:type directory: Directory
|
||||
@@ -150,7 +151,12 @@ def generate_directory(user, directory=False):
|
||||
total_read=F('comicstatus__last_read_page'),
|
||||
finished=F('comicstatus__finished'),
|
||||
unread=F('comicstatus__unread'),
|
||||
user=F('comicstatus__user')
|
||||
user=F('comicstatus__user'),
|
||||
classification=Case(
|
||||
When(directory__isnull=True, then=Directory.Classification.C_18),
|
||||
default=F('directory__classification'),
|
||||
output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices)
|
||||
)
|
||||
).filter(Q(user__isnull=True) | Q(user=user.id))
|
||||
|
||||
for directory_obj in dir_list_obj:
|
||||
@@ -170,6 +176,7 @@ def generate_directory(user, directory=False):
|
||||
directory_obj.total = 0
|
||||
directory_obj.total_read = 0
|
||||
files.append(DirFile(directory_obj))
|
||||
files = [file for file in files if file.obj.classification <= user.usermisc.allowed_to_read]
|
||||
|
||||
for file_name in file_list:
|
||||
if file_name.suffix.lower() in [".rar", ".zip", ".cbr", ".cbz", ".pdf"]:
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from PIL import Image
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.db.models import Max, Count, F
|
||||
from django.db.transaction import atomic
|
||||
from django.http import HttpResponse, FileResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from .forms import AccountForm, AddUserForm, EditUserForm, InitialSetupForm
|
||||
from .models import ComicBook, ComicPage, ComicStatus, Directory, UserMisc
|
||||
from .forms import AccountForm, AddUserForm, EditUserForm, InitialSetupForm, DirectoryEditForm
|
||||
from .models import ComicBook, ComicPage, ComicStatus, Directory
|
||||
from .util import (
|
||||
Menu,
|
||||
generate_breadcrumbs_from_menu,
|
||||
@@ -47,6 +46,7 @@ def comic_list(request, directory_selector=False):
|
||||
breadcrumbs = generate_breadcrumbs_from_path()
|
||||
|
||||
files = generate_directory(request.user, directory)
|
||||
form = DirectoryEditForm()
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -56,14 +56,18 @@ def comic_list(request, directory_selector=False):
|
||||
"menu": Menu(request.user, "Browse"),
|
||||
"title": title,
|
||||
"files": files,
|
||||
"selector": directory_selector if directory_selector else 'None'
|
||||
"form": form,
|
||||
"selector": directory_selector if directory_selector else 'None',
|
||||
'js_urls': {
|
||||
"perform_action": reverse('perform_action', args=('operation', 'item_type', 'selector'))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def perform_action(request, operation, item_type, selector):
|
||||
if operation not in ['mark_read', 'mark_unread', 'mark_previous']:
|
||||
if operation not in ['mark_read', 'mark_unread', 'mark_previous', 'set_classification']:
|
||||
return HttpResponse(400)
|
||||
elif operation == 'mark_previous' and item_type == 'Directory':
|
||||
return HttpResponse(422)
|
||||
@@ -74,20 +78,29 @@ def perform_action(request, operation, item_type, selector):
|
||||
for book in ComicBook.objects.filter(directory__isnull=True):
|
||||
getattr(book, operation)(request.user)
|
||||
return HttpResponse(204)
|
||||
else:
|
||||
return HttpResponse(400)
|
||||
if operation == 'set_classification':
|
||||
form = DirectoryEditForm(request.POST)
|
||||
if form.is_valid() and item_type == 'Directory':
|
||||
pass
|
||||
else:
|
||||
return HttpResponse(400)
|
||||
if item_type == 'ComicBook':
|
||||
book = get_object_or_404(ComicBook, selector=selector_uuid)
|
||||
getattr(book, operation)(request.user)
|
||||
return HttpResponse(204)
|
||||
elif item_type == 'Directory':
|
||||
directory = get_object_or_404(Directory, selector=selector_uuid)
|
||||
getattr(directory, operation)(request.user)
|
||||
if operation == 'set_classification':
|
||||
getattr(directory, operation)(form.cleaned_data)
|
||||
else:
|
||||
getattr(directory, operation)(request.user)
|
||||
return HttpResponse(204)
|
||||
|
||||
|
||||
@login_required
|
||||
def recent_comics(request):
|
||||
feed_id, _ = UserMisc.objects.get_or_create(user=request.user)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"comic/recent_comics.html",
|
||||
@@ -95,7 +108,7 @@ def recent_comics(request):
|
||||
"breadcrumbs": generate_breadcrumbs_from_menu([("Recent", "/comic/recent/")]),
|
||||
"menu": Menu(request.user, "Recent"),
|
||||
"title": "Recent Comics",
|
||||
"feed_id": urlsafe_base64_encode(feed_id.feed_id.bytes),
|
||||
"feed_id": urlsafe_base64_encode(request.user.usermisc.feed_id.bytes),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -197,7 +210,7 @@ def account_page(request):
|
||||
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def users_page(request):
|
||||
users = User.objects.all()
|
||||
users = User.objects.all().select_related('usermisc')
|
||||
crumbs = [("Users", "/comic/settings/users/")]
|
||||
context = {
|
||||
"users": users,
|
||||
@@ -221,6 +234,8 @@ def user_config_page(request, user_id):
|
||||
if form.cleaned_data["email"] != user.email:
|
||||
user.email = form.cleaned_data["email"]
|
||||
success_message.append("Email Updated.</br>")
|
||||
user.usermisc.allowed_to_read = form.cleaned_data['allowed_to_read']
|
||||
user.usermisc.save()
|
||||
user.save()
|
||||
else:
|
||||
form = EditUserForm(initial=EditUserForm.get_initial_values(user))
|
||||
@@ -248,7 +263,6 @@ def user_add_page(request):
|
||||
user = User(username=form.cleaned_data["username"], email=form.cleaned_data["email"])
|
||||
user.set_password(form.cleaned_data["password"])
|
||||
user.save()
|
||||
UserMisc.objects.create(user=user)
|
||||
success_message = "User {} created.".format(user.username)
|
||||
|
||||
else:
|
||||
@@ -269,14 +283,10 @@ def user_add_page(request):
|
||||
def read_comic(request, comic_selector):
|
||||
|
||||
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||
try:
|
||||
book = ComicBook.objects.get(selector=selector)
|
||||
except ComicBook.DoesNotExist:
|
||||
Directory.objects.get(selector=selector)
|
||||
return redirect('comic_list', directory_selector=comic_selector)
|
||||
except Directory.DoesNotExist:
|
||||
return HttpResponse(status=404)
|
||||
book = get_object_or_404(ComicBook, selector=selector)
|
||||
if book.directory:
|
||||
if book.directory.classification > request.user.usermisc.allowed_to_read:
|
||||
return redirect('index')
|
||||
|
||||
pages = ComicPage.objects.filter(Comic=book)
|
||||
|
||||
@@ -318,33 +328,42 @@ def set_read_page(request, comic_selector, page):
|
||||
|
||||
@xframe_options_sameorigin
|
||||
@login_required
|
||||
def get_image(_, comic_selector, page):
|
||||
def get_image(request, comic_selector, page):
|
||||
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||
book = ComicBook.objects.get(selector=selector)
|
||||
if book.directory:
|
||||
if book.directory.classification > request.user.usermisc.allowed_to_read:
|
||||
return HttpResponse(status=401)
|
||||
img, content = book.get_image(int(page))
|
||||
return FileResponse(img, content_type=content)
|
||||
|
||||
|
||||
@xframe_options_sameorigin
|
||||
@login_required
|
||||
def comic_thumbnail(_, comic_selector):
|
||||
def comic_thumbnail(request, comic_selector):
|
||||
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||
book = ComicBook.objects.get(selector=selector)
|
||||
if book.directory.classification > request.user.usermisc.allowed_to_read:
|
||||
return HttpResponse(status=401)
|
||||
return redirect(book.get_thumbnail_url())
|
||||
|
||||
|
||||
@xframe_options_sameorigin
|
||||
@login_required
|
||||
def directory_thumbnail(_, directory_selector):
|
||||
def directory_thumbnail(request, directory_selector):
|
||||
selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector))
|
||||
folder = Directory.objects.get(selector=selector)
|
||||
if folder.classification > request.user.usermisc.allowed_to_read:
|
||||
return HttpResponse(status=401)
|
||||
return redirect(folder.get_thumbnail_url())
|
||||
|
||||
|
||||
@login_required
|
||||
def get_pdf(_, comic_selector):
|
||||
def get_pdf(request, comic_selector):
|
||||
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||
book = ComicBook.objects.get(selector=selector)
|
||||
if book.directory.classification > request.user.usermisc.allowed_to_read:
|
||||
return HttpResponse(status=401)
|
||||
return FileResponse(open(book.get_pdf(), 'rb'), content_type='application/pdf')
|
||||
|
||||
|
||||
|
||||
45
poetry.lock
generated
45
poetry.lock
generated
@@ -107,6 +107,18 @@ python-versions = "*"
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django-boost"
|
||||
version = "1.7.2"
|
||||
description = "Django Extension library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=2.0"
|
||||
user-agents = ">=2.0"
|
||||
|
||||
[[package]]
|
||||
name = "django-bootstrap4"
|
||||
version = "3.0.0"
|
||||
@@ -443,6 +455,14 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "ua-parser"
|
||||
version = "0.10.0"
|
||||
description = "Python port of Browserscope's user agent parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.4"
|
||||
@@ -456,6 +476,17 @@ secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "cer
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "user-agents"
|
||||
version = "2.2.0"
|
||||
description = "A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
ua-parser = ">=0.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.0.3"
|
||||
@@ -470,7 +501,7 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "71642aa577156d70c6033dbc260a2ab03d247a17d9b0b0500a9c9a0e0228fd68"
|
||||
content-hash = "8364532c96609a5598f24f5e77f2b647763fafc8052c1d466dabc154a90c6d09"
|
||||
|
||||
[metadata.files]
|
||||
asgiref = [
|
||||
@@ -564,6 +595,10 @@ django-appconf = [
|
||||
{file = "django-appconf-1.0.4.tar.gz", hash = "sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380"},
|
||||
{file = "django_appconf-1.0.4-py2.py3-none-any.whl", hash = "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06"},
|
||||
]
|
||||
django-boost = [
|
||||
{file = "django_boost-1.7.2-py3-none-any.whl", hash = "sha256:b2460f8613920cdb309cb5c27a0a18d8fb83812f6019c6bb2218f82b33a67ea3"},
|
||||
{file = "django_boost-1.7.2.tar.gz", hash = "sha256:ca80641314f75446ba815ed9632c64ede025c72bc18ec59af89abce97769a65f"},
|
||||
]
|
||||
django-bootstrap4 = [
|
||||
{file = "django-bootstrap4-3.0.0.tar.gz", hash = "sha256:bffc96f65386fbd49cae1474393e01d4b414c12fcab0fff50545e6142e7ba19b"},
|
||||
{file = "django_bootstrap4-3.0.0-py3-none-any.whl", hash = "sha256:76a52fb22a8d3dbb2f7609b21908ce863e941a4462be079bf1d12025e551af37"},
|
||||
@@ -849,10 +884,18 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
||||
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
||||
]
|
||||
ua-parser = [
|
||||
{file = "ua-parser-0.10.0.tar.gz", hash = "sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"},
|
||||
{file = "ua_parser-0.10.0-py2.py3-none-any.whl", hash = "sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
|
||||
{file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
|
||||
]
|
||||
user-agents = [
|
||||
{file = "user-agents-2.2.0.tar.gz", hash = "sha256:d36d25178db65308d1458c5fa4ab39c9b2619377010130329f3955e7626ead26"},
|
||||
{file = "user_agents-2.2.0-py3-none-any.whl", hash = "sha256:a98c4dc72ecbc64812c4534108806fb0a0b3a11ec3fd1eafe807cee5b0a942e7"},
|
||||
]
|
||||
win32-setctime = [
|
||||
{file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"},
|
||||
{file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"},
|
||||
|
||||
@@ -27,6 +27,7 @@ django-imagekit = "^4.0.2"
|
||||
PyMuPDF = "^1.18.12"
|
||||
django-bootstrap4 = "^3.0.0"
|
||||
django-csp = "^3.7"
|
||||
django-boost = "^1.7.2"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
mypy = "^0.812"
|
||||
|
||||
@@ -76,6 +76,9 @@ dj-database-url==0.5.0 \
|
||||
django-appconf==1.0.4 \
|
||||
--hash=sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380 \
|
||||
--hash=sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06
|
||||
django-boost==1.7.2 \
|
||||
--hash=sha256:b2460f8613920cdb309cb5c27a0a18d8fb83812f6019c6bb2218f82b33a67ea3 \
|
||||
--hash=sha256:ca80641314f75446ba815ed9632c64ede025c72bc18ec59af89abce97769a65f
|
||||
django-bootstrap4==3.0.0; python_version >= "3.6" \
|
||||
--hash=sha256:bffc96f65386fbd49cae1474393e01d4b414c12fcab0fff50545e6142e7ba19b \
|
||||
--hash=sha256:76a52fb22a8d3dbb2f7609b21908ce863e941a4462be079bf1d12025e551af37
|
||||
@@ -271,9 +274,15 @@ sqlparse==0.4.1; python_version >= "3.6" \
|
||||
toml==0.10.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5" \
|
||||
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
|
||||
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
|
||||
ua-parser==0.10.0 \
|
||||
--hash=sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033 \
|
||||
--hash=sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a
|
||||
urllib3==1.26.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" \
|
||||
--hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \
|
||||
--hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937
|
||||
user-agents==2.2.0 \
|
||||
--hash=sha256:d36d25178db65308d1458c5fa4ab39c9b2619377010130329f3955e7626ead26 \
|
||||
--hash=sha256:a98c4dc72ecbc64812c4534108806fb0a0b3a11ec3fd1eafe807cee5b0a942e7
|
||||
win32-setctime==1.0.3; sys_platform == "win32" and python_version >= "3.5" \
|
||||
--hash=sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e \
|
||||
--hash=sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
.card_list_card {
|
||||
width: 200px;
|
||||
}
|
||||
.card .card-badge {
|
||||
.card .unread-badge {
|
||||
position:absolute;
|
||||
top:10px;
|
||||
left:10px;
|
||||
padding:5px;
|
||||
color:white;
|
||||
}
|
||||
.card .classification-badge {
|
||||
position:absolute;
|
||||
top:10px;
|
||||
right: 10px;
|
||||
padding:5px;
|
||||
color:black;
|
||||
}
|
||||
2
static/css/base.min.css
vendored
2
static/css/base.min.css
vendored
@@ -1 +1 @@
|
||||
#comic_list caption{caption-side:top}.card_list_card{width:200px}.card .card-badge{position:absolute;top:10px;left:10px;padding:5px;color:#fff}
|
||||
#comic_list caption{caption-side:top}.card_list_card{width:200px}.card .unread-badge{position:absolute;top:10px;left:10px;padding:5px;color:#fff}.card .classification-badge{position:absolute;top:10px;right:10px;padding:5px;color:#000}
|
||||
@@ -1,45 +1,48 @@
|
||||
var qsRegex;
|
||||
var buttonFilter;
|
||||
var $grid = $('.comic-container').isotope({
|
||||
itemSelector: '.grid-item',
|
||||
layoutMode: 'fitRows',
|
||||
filter: function() {
|
||||
var $this = $(this);
|
||||
var searchResult = qsRegex ? $this.text().match( qsRegex ) : true;
|
||||
var buttonResult = buttonFilter ? $this.is( buttonFilter ) : true;
|
||||
return searchResult && buttonResult;
|
||||
}
|
||||
});
|
||||
$('#filters').on( 'click', 'button', function() {
|
||||
if (typeof $( this ).attr('data-filter') === "undefined") {
|
||||
let qsRegex;
|
||||
let buttonFilter;
|
||||
const js_urls = JSON.parse(document.getElementById('js_urls').textContent)
|
||||
|
||||
}else {
|
||||
buttonFilter = $( this ).attr('data-filter');
|
||||
sessionStorage.setItem(window.location.href+"button", buttonFilter);
|
||||
$grid.isotope();
|
||||
}
|
||||
});
|
||||
let $grid = $('.comic-container').isotope({
|
||||
itemSelector: '.grid-item',
|
||||
layoutMode: 'fitRows',
|
||||
filter: function() {
|
||||
let $this = $(this);
|
||||
let searchResult = qsRegex ? $this.text().match( qsRegex ) : true;
|
||||
let buttonResult = buttonFilter ? $this.is( buttonFilter ) : true;
|
||||
return searchResult && buttonResult;
|
||||
}
|
||||
});
|
||||
|
||||
var $quicksearch = $('#quicksearch').keyup( debounce( function() {
|
||||
qsRegex = new RegExp($quicksearch.val(), 'gi');
|
||||
sessionStorage.setItem(window.location.href+'text', $quicksearch.val());
|
||||
$grid.isotope();
|
||||
}) );
|
||||
$('#filters').on( 'click', 'button', function() {
|
||||
if (typeof $( this ).attr('data-filter') === "undefined") {
|
||||
|
||||
}else {
|
||||
buttonFilter = $( this ).attr('data-filter');
|
||||
sessionStorage.setItem(window.location.href+"button", buttonFilter);
|
||||
$grid.isotope();
|
||||
}
|
||||
});
|
||||
|
||||
let $quicksearch = $('#quicksearch').keyup( debounce( function() {
|
||||
qsRegex = new RegExp($quicksearch.val(), 'gi');
|
||||
sessionStorage.setItem(window.location.href+'text', $quicksearch.val());
|
||||
$grid.isotope();
|
||||
}) );
|
||||
|
||||
// debounce so filtering doesn't happen every millisecond
|
||||
function debounce( fn, threshold ) {
|
||||
var timeout;
|
||||
threshold = threshold || 100;
|
||||
return function debounced() {
|
||||
clearTimeout( timeout );
|
||||
var args = arguments;
|
||||
var _this = this;
|
||||
function delayed() {
|
||||
fn.apply( _this, args );
|
||||
}
|
||||
timeout = setTimeout( delayed, threshold );
|
||||
};
|
||||
function debounce( fn, threshold ) {
|
||||
var timeout;
|
||||
threshold = threshold || 100;
|
||||
return function debounced() {
|
||||
clearTimeout( timeout );
|
||||
var args = arguments;
|
||||
var _this = this;
|
||||
function delayed() {
|
||||
fn.apply( _this, args );
|
||||
}
|
||||
timeout = setTimeout( delayed, threshold );
|
||||
};
|
||||
}
|
||||
setInterval(function (){
|
||||
$grid.isotope();
|
||||
}, 1000)
|
||||
@@ -84,3 +87,49 @@ comic_action_elements.forEach(el => el.addEventListener('click', event => {
|
||||
let action = target.attr('comic_action')
|
||||
comic_action(selector, item_type, action)
|
||||
}));
|
||||
|
||||
let modal_buttons = document.getElementsByClassName('modal-button')
|
||||
|
||||
modal_buttons.forEach(el => el.addEventListener('click', event => {
|
||||
|
||||
let target = $(event.target).closest('button')
|
||||
let selector = target.attr('selector')
|
||||
|
||||
let modal = $('#editModal')
|
||||
modal.attr('selector', selector)
|
||||
modal.attr('itemtype', target.attr('itemtype'))
|
||||
|
||||
let title = $('#editModalLabel')
|
||||
let title_source = $('.card-title.'+selector)
|
||||
title.text(title_source.text())
|
||||
|
||||
let classification = $('select[name="classification"]')
|
||||
let classification_value = $('.classification-badge.'+selector)
|
||||
classification.val(classification_value.attr('classification'))
|
||||
|
||||
}))
|
||||
|
||||
let save_button = document.getElementById('save_button')
|
||||
|
||||
save_button.addEventListener('click', function (event){
|
||||
let modal = $('#editModal')
|
||||
let selector = modal.attr('selector')
|
||||
let itemtype = modal.attr('itemtype')
|
||||
let classification = $('select[name="classification"]')
|
||||
let classification_badge = $('.classification-badge.'+selector)
|
||||
let action_url = js_urls.perform_action.replace('operation', 'set_classification').replace('item_type', itemtype).replace('selector', selector)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: action_url,
|
||||
data: {
|
||||
classification: classification.val(),
|
||||
csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr('value')
|
||||
},
|
||||
success: function (ev){
|
||||
classification_badge.text($('select[name="classification"] option:selected').text())
|
||||
modal.modal('hide')
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
2
static/js/comic_list.min.js
vendored
2
static/js/comic_list.min.js
vendored
@@ -1 +1 @@
|
||||
var qsRegex;var buttonFilter;var $grid=$(".comic-container").isotope({itemSelector:".grid-item",layoutMode:"fitRows",filter:function(){var $this=$(this);var searchResult=qsRegex?$this.text().match(qsRegex):true;var buttonResult=buttonFilter?$this.is(buttonFilter):true;return searchResult&&buttonResult}});$("#filters").on("click","button",function(){if(typeof $(this).attr("data-filter")==="undefined"){}else{buttonFilter=$(this).attr("data-filter");sessionStorage.setItem(window.location.href+"button",buttonFilter);$grid.isotope()}});var $quicksearch=$("#quicksearch").keyup(debounce(function(){qsRegex=new RegExp($quicksearch.val(),"gi");sessionStorage.setItem(window.location.href+"text",$quicksearch.val());$grid.isotope()}));function debounce(fn,threshold){var timeout;threshold=threshold||100;return function debounced(){clearTimeout(timeout);var args=arguments;var _this=this;function delayed(){fn.apply(_this,args)}timeout=setTimeout(delayed,threshold)}}setInterval(function(){$grid.isotope()},1e3);let field=document.getElementById("quicksearch");if(sessionStorage.getItem(window.location.href+"text")||sessionStorage.getItem(window.location.href+"button")){field.value=sessionStorage.getItem(window.location.href+"text");qsRegex=new RegExp($quicksearch.val(),"gi");buttonFilter=sessionStorage.getItem(window.location.href+"button");$grid.isotope()}field.addEventListener("change",function(){});function comic_action(selector,item_type,action){$.ajax({url:"/comic/action/"+action+"/"+item_type+"/"+selector+"/",success:function(){window.location.reload()}})}$(".progress-bar").each(function(index){let bar=$(this);bar.css("width",bar.attr("aria-valuenow")+"%")});let comic_action_elements=document.getElementsByClassName("comic_action");comic_action_elements.forEach(el=>el.addEventListener("click",event=>{let target=$(event.target).closest("button");let selector=target.attr("selector");let item_type=target.attr("itemtype");let action=target.attr("comic_action");comic_action(selector,item_type,action)}));
|
||||
let qsRegex;let buttonFilter;const js_urls=JSON.parse(document.getElementById("js_urls").textContent);let $grid=$(".comic-container").isotope({itemSelector:".grid-item",layoutMode:"fitRows",filter:function(){let $this=$(this);let searchResult=qsRegex?$this.text().match(qsRegex):true;let buttonResult=buttonFilter?$this.is(buttonFilter):true;return searchResult&&buttonResult}});$("#filters").on("click","button",function(){if(typeof $(this).attr("data-filter")==="undefined"){}else{buttonFilter=$(this).attr("data-filter");sessionStorage.setItem(window.location.href+"button",buttonFilter);$grid.isotope()}});let $quicksearch=$("#quicksearch").keyup(debounce(function(){qsRegex=new RegExp($quicksearch.val(),"gi");sessionStorage.setItem(window.location.href+"text",$quicksearch.val());$grid.isotope()}));function debounce(fn,threshold){var timeout;threshold=threshold||100;return function debounced(){clearTimeout(timeout);var args=arguments;var _this=this;function delayed(){fn.apply(_this,args)}timeout=setTimeout(delayed,threshold)}}setInterval(function(){$grid.isotope()},1e3);let field=document.getElementById("quicksearch");if(sessionStorage.getItem(window.location.href+"text")||sessionStorage.getItem(window.location.href+"button")){field.value=sessionStorage.getItem(window.location.href+"text");qsRegex=new RegExp($quicksearch.val(),"gi");buttonFilter=sessionStorage.getItem(window.location.href+"button");$grid.isotope()}field.addEventListener("change",function(){});function comic_action(selector,item_type,action){$.ajax({url:"/comic/action/"+action+"/"+item_type+"/"+selector+"/",success:function(){window.location.reload()}})}$(".progress-bar").each(function(index){let bar=$(this);bar.css("width",bar.attr("aria-valuenow")+"%")});let comic_action_elements=document.getElementsByClassName("comic_action");comic_action_elements.forEach(el=>el.addEventListener("click",event=>{let target=$(event.target).closest("button");let selector=target.attr("selector");let item_type=target.attr("itemtype");let action=target.attr("comic_action");comic_action(selector,item_type,action)}));let modal_buttons=document.getElementsByClassName("modal-button");modal_buttons.forEach(el=>el.addEventListener("click",event=>{let target=$(event.target).closest("button");let selector=target.attr("selector");let modal=$("#editModal");modal.attr("selector",selector);modal.attr("itemtype",target.attr("itemtype"));let title=$("#editModalLabel");let title_source=$(".card-title."+selector);title.text(title_source.text());let classification=$('select[name="classification"]');let classification_value=$(".classification-badge."+selector);classification.val(classification_value.attr("classification"))}));let save_button=document.getElementById("save_button");save_button.addEventListener("click",function(event){let modal=$("#editModal");let selector=modal.attr("selector");let itemtype=modal.attr("itemtype");let classification=$('select[name="classification"]');let classification_badge=$(".classification-badge."+selector);let action_url=js_urls.perform_action.replace("operation","set_classification").replace("item_type",itemtype).replace("selector",selector);$.ajax({type:"POST",url:action_url,data:{classification:classification.val(),csrfmiddlewaretoken:$('input[name="csrfmiddlewaretoken"]').attr("value")},success:function(ev){classification_badge.text($('select[name="classification"] option:selected').text());modal.modal("hide")}})});
|
||||
Reference in New Issue
Block a user