Merge pull request #5 from apoclyps/black-formatting

[ISSUE-4] Applying black formatting
This commit is contained in:
2019-07-26 08:45:41 +01:00
committed by GitHub
37 changed files with 990 additions and 1150 deletions

View File

@@ -22,10 +22,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '=3tf-@u1t7x4%$yr++59+8tspl4ao&r3&!bb6l(t&$#6@bfkwg' SECRET_KEY = "=3tf-@u1t7x4%$yr++59+8tspl4ao&r3&!bb6l(t&$#6@bfkwg"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get('DJANGO_DEBUG', True) DEBUG = os.environ.get("DJANGO_DEBUG", True)
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",") ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")
@@ -33,46 +33,46 @@ ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'snowpenguin.django.recaptcha2', "snowpenguin.django.recaptcha2",
'comic', "comic",
'comic_auth', "comic_auth",
) )
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
ROOT_URLCONF = 'cbreader.urls' ROOT_URLCONF = "cbreader.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': ['templates'], "DIRS": ["templates"],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ]
}, },
}, }
] ]
WSGI_APPLICATION = 'cbreader.wsgi.application' WSGI_APPLICATION = "cbreader.wsgi.application"
# Database # Database
@@ -83,20 +83,15 @@ DATABASE_URL = os.getenv("DATABASE_URL")
if DATABASE_URL: if DATABASE_URL:
DATABASES = {"default": dj_database_url.config(conn_max_age=500)} DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
else: else:
DATABASES = { DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3")}}
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-ie' LANGUAGE_CODE = "en-ie"
TIME_ZONE = 'UTC' TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@@ -108,16 +103,16 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/ # https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"
LOGIN_REDIRECT_URL = '/comic/' LOGIN_REDIRECT_URL = "/comic/"
LOGIN_URL = '/login/' LOGIN_URL = "/login/"
UNRAR_TOOL = os.getenv("UNRAR_TOOL", None) UNRAR_TOOL = os.getenv("UNRAR_TOOL", None)
CBREADER_USE_RECAPTCHA = False CBREADER_USE_RECAPTCHA = False
RECAPTCHA_PRIVATE_KEY = '' RECAPTCHA_PRIVATE_KEY = ""
RECAPTCHA_PUBLIC_KEY = '' RECAPTCHA_PUBLIC_KEY = ""
COMIC_DIR = "/media/comics" COMIC_DIR = "/media/comics"

View File

@@ -1,123 +0,0 @@
"""
Django settings for cbreader project.
Generated by 'django-admin startproject' using Django 1.8.2.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import dj_database_url
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '=3tf-@u1t7x4%$yr++59+8tspl4ao&r3&!bb6l(t&$#6@bfkwg'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get('DJANGO_DEBUG', True)
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'snowpenguin.django.recaptcha2',
'comic',
'comic_auth',
)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'cbreader.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'cbreader.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASE_URL = os.getenv("TEST_DATABASE_URL")
if DATABASE_URL:
DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-ie'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/'
LOGIN_REDIRECT_URL = '/comic/'
LOGIN_URL = '/login/'
UNRAR_TOOL = os.getenv("UNRAR_TOOL", None)
CBREADER_USE_RECAPTCHA = False
RECAPTCHA_PRIVATE_KEY = ''
RECAPTCHA_PUBLIC_KEY = ''
COMIC_DIR = "/media/comics"

View File

@@ -0,0 +1,118 @@
"""
Django settings for cbreader project.
Generated by 'django-admin startproject' using Django 1.8.2.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import dj_database_url
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "=3tf-@u1t7x4%$yr++59+8tspl4ao&r3&!bb6l(t&$#6@bfkwg"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DJANGO_DEBUG", True)
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")
# Application definition
INSTALLED_APPS = (
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"snowpenguin.django.recaptcha2",
"comic",
"comic_auth",
)
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "cbreader.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]
},
}
]
WSGI_APPLICATION = "cbreader.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASE_URL = os.getenv("TEST_DATABASE_URL")
if DATABASE_URL:
DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
else:
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3")}}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = "en-ie"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = "/static/"
LOGIN_REDIRECT_URL = "/comic/"
LOGIN_URL = "/login/"
UNRAR_TOOL = os.getenv("UNRAR_TOOL", None)
CBREADER_USE_RECAPTCHA = False
RECAPTCHA_PRIVATE_KEY = ""
RECAPTCHA_PUBLIC_KEY = ""
COMIC_DIR = "/media/comics"

View File

@@ -20,11 +20,11 @@ import comic.views
import comic_auth.views import comic_auth.views
urlpatterns = [ urlpatterns = [
url(r'^$', comic.views.comic_redirect), url(r"^$", comic.views.comic_redirect),
url(r'^login/', comic_auth.views.comic_login), url(r"^login/", comic_auth.views.comic_login),
url(r'^logout/', comic_auth.views.comic_logout), url(r"^logout/", comic_auth.views.comic_logout),
url(r'^setup/', comic.views.initial_setup), url(r"^setup/", comic.views.initial_setup),
url(r'^comic/', include('comic.urls')), url(r"^comic/", include("comic.urls")),
url(r'^admin/', admin.site.urls), url(r"^admin/", admin.site.urls),
# url(r'^silk/', include('silk.urls', namespace='silk')) # url(r'^silk/', include('silk.urls', namespace='silk'))
] ]

View File

@@ -5,24 +5,24 @@ from comic.models import Setting, ComicBook, ComicPage, ComicStatus, Directory
@admin.register(Setting) @admin.register(Setting)
class SettingAdmin(admin.ModelAdmin): class SettingAdmin(admin.ModelAdmin):
list_display = ('name', 'value') list_display = ("name", "value")
@admin.register(ComicBook) @admin.register(ComicBook)
class ComicBookAdmin(admin.ModelAdmin): class ComicBookAdmin(admin.ModelAdmin):
list_display = ['file_name', 'date_added'] list_display = ["file_name", "date_added"]
search_fields = ['file_name'] search_fields = ["file_name"]
@admin.register(ComicPage) @admin.register(ComicPage)
class ComicPageAdmin(admin.ModelAdmin): class ComicPageAdmin(admin.ModelAdmin):
list_display = ('Comic', 'index', 'page_file_name', 'content_type') list_display = ("Comic", "index", "page_file_name", "content_type")
list_filter = ['Comic'] list_filter = ["Comic"]
@admin.register(ComicStatus) @admin.register(ComicStatus)
class ComicStatusAdmin(admin.ModelAdmin): class ComicStatusAdmin(admin.ModelAdmin):
list_display = ['user', 'comic', 'last_read_page', 'unread'] list_display = ["user", "comic", "last_read_page", "unread"]
@admin.register(Directory) @admin.register(Directory)

View File

@@ -19,14 +19,14 @@ class RecentComics(Feed):
@staticmethod @staticmethod
def items() -> ComicBook: def items() -> ComicBook:
return ComicBook.objects.order_by('-date_added')[:10] return ComicBook.objects.order_by("-date_added")[:10]
def item_title(self, item: ComicBook) -> str: def item_title(self, item: ComicBook) -> str:
return item.file_name return item.file_name
def item_description(self, item: ComicBook) -> str: def item_description(self, item: ComicBook) -> str:
return item.date_added.strftime('%a, %e %b %Y %H:%M') return item.date_added.strftime("%a, %e %b %Y %H:%M")
# item_link is only needed if NewsItem has no get_absolute_url method. # item_link is only needed if NewsItem has no get_absolute_url method.
def item_link(self, item: ComicBook) -> str: def item_link(self, item: ComicBook) -> str:
return '/comic/read/{0}/0/'.format(urlsafe_base64_encode(item.selector.bytes)) return "/comic/read/{0}/0/".format(urlsafe_base64_encode(item.selector.bytes))

View File

@@ -7,214 +7,137 @@ from comic.models import Setting
class InitialSetupForm(forms.Form): class InitialSetupForm(forms.Form):
username = forms.CharField(help_text='Username', username = forms.CharField(help_text="Username", widget=forms.TextInput(attrs={"class": "form-control"}))
widget=forms.TextInput( email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
attrs={ password = forms.CharField(help_text="New Password", widget=forms.PasswordInput(attrs={"class": "form-control"}))
'class': 'form-control', password_confirm = forms.CharField(
} help_text="New Password Confirmation", widget=forms.PasswordInput(attrs={"class": "form-control"})
)) )
email = forms.CharField(help_text='Email Address', base_dir = forms.CharField(help_text="Base Directory", widget=forms.TextInput(attrs={"class": "form-control"}))
widget=forms.TextInput(
attrs={
'class': 'form-control'
}
))
password = forms.CharField(help_text='New Password',
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
password_confirm = forms.CharField(help_text='New Password Confirmation',
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
base_dir = forms.CharField(help_text='Base Directory',
widget=forms.TextInput(
attrs={
'class': 'form-control'
}
))
def clean_base_dir(self): def clean_base_dir(self):
data = self.cleaned_data['base_dir'] data = self.cleaned_data["base_dir"]
if not path.isdir(data): if not path.isdir(data):
raise forms.ValidationError('This is not a valid Directory') raise forms.ValidationError("This is not a valid Directory")
return data return data
def clean(self): def clean(self):
form_data = self.cleaned_data form_data = self.cleaned_data
if form_data['password'] != form_data['password_confirm']: if form_data["password"] != form_data["password_confirm"]:
raise forms.ValidationError('Passwords do not match.') raise forms.ValidationError("Passwords do not match.")
if len(form_data['password']) < 8: if len(form_data["password"]) < 8:
raise forms.ValidationError('Password is too short') raise forms.ValidationError("Password is too short")
return form_data return form_data
class AccountForm(forms.Form): class AccountForm(forms.Form):
username = forms.CharField(help_text='Username', username = forms.CharField(
required=False, help_text="Username",
widget=forms.TextInput( required=False,
attrs={ widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}),
'class': 'form-control disabled', )
'readonly': True, email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
} password = forms.CharField(
)) help_text="New Password", required=False, widget=forms.PasswordInput(attrs={"class": "form-control"})
email = forms.CharField(help_text='Email Address', )
widget=forms.TextInput( password_confirm = forms.CharField(
attrs={ help_text="New Password Confirmation",
'class': 'form-control' required=False,
} widget=forms.PasswordInput(attrs={"class": "form-control"}),
)) )
password = forms.CharField(help_text='New Password',
required=False,
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
password_confirm = forms.CharField(help_text='New Password Confirmation',
required=False,
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
def clean_email(self): def clean_email(self):
data = self.cleaned_data['email'] data = self.cleaned_data["email"]
user = User.objects.get(username=self.cleaned_data['username']) user = User.objects.get(username=self.cleaned_data["username"])
if data == user.email: if data == user.email:
return data return data
if User.objects.filter(email=data).exists(): if User.objects.filter(email=data).exists():
raise forms.ValidationError('Email Address is in use') raise forms.ValidationError("Email Address is in use")
return data return data
def clean(self): def clean(self):
form_data = self.cleaned_data form_data = self.cleaned_data
if form_data['password'] != form_data['password_confirm']: if form_data["password"] != form_data["password_confirm"]:
raise forms.ValidationError('Passwords do not match.') raise forms.ValidationError("Passwords do not match.")
if len(form_data['password']) < 8 & len(form_data['password']) != 0: if len(form_data["password"]) < 8 & len(form_data["password"]) != 0:
raise forms.ValidationError('Password is too short') raise forms.ValidationError("Password is too short")
return form_data return form_data
class AddUserForm(forms.Form): class AddUserForm(forms.Form):
username = forms.CharField(help_text='Username', username = forms.CharField(help_text="Username", widget=forms.TextInput(attrs={"class": "form-control"}))
widget=forms.TextInput( email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
attrs={ password = forms.CharField(help_text="New Password", widget=forms.PasswordInput(attrs={"class": "form-control"}))
'class': 'form-control', password_confirm = forms.CharField(
} help_text="New Password Confirmation", widget=forms.PasswordInput(attrs={"class": "form-control"})
)) )
email = forms.CharField(help_text='Email Address',
widget=forms.TextInput(
attrs={
'class': 'form-control'
}
))
password = forms.CharField(help_text='New Password',
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
password_confirm = forms.CharField(help_text='New Password Confirmation',
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
def clean_username(self): def clean_username(self):
data = self.cleaned_data['username'] data = self.cleaned_data["username"]
if User.objects.filter(username=data).exists(): if User.objects.filter(username=data).exists():
raise forms.ValidationError('This username Exists.') raise forms.ValidationError("This username Exists.")
return data return data
def clean_email(self): def clean_email(self):
data = self.cleaned_data['email'] data = self.cleaned_data["email"]
if User.objects.filter(email=data).exists(): if User.objects.filter(email=data).exists():
raise forms.ValidationError('Email Address is in use') raise forms.ValidationError("Email Address is in use")
return data return data
def clean(self): def clean(self):
form_data = self.cleaned_data form_data = self.cleaned_data
if form_data['password'] != form_data['password_confirm']: if form_data["password"] != form_data["password_confirm"]:
raise forms.ValidationError('Passwords do not match.') raise forms.ValidationError("Passwords do not match.")
if len(form_data['password']) < 8: if len(form_data["password"]) < 8:
raise forms.ValidationError('Password is too short') raise forms.ValidationError("Password is too short")
return form_data return form_data
class EditUserForm(forms.Form): class EditUserForm(forms.Form):
username = forms.CharField(help_text='Username', username = forms.CharField(
required=False, help_text="Username",
widget=forms.TextInput( required=False,
attrs={ widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}),
'class': 'form-control disabled', )
'readonly': True, email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
} password = forms.CharField(
)) help_text="New Password", required=False, widget=forms.PasswordInput(attrs={"class": "form-control"})
email = forms.CharField(help_text='Email Address', )
widget=forms.TextInput(
attrs={
'class': 'form-control'
}
))
password = forms.CharField(help_text='New Password',
required=False,
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
}
))
# TODO: allow setting superuser on users # TODO: allow setting superuser on users
@staticmethod @staticmethod
def get_initial_values(user): def get_initial_values(user):
out = { out = {"username": user.username, "email": user.email}
'username': user.username,
'email': user.email
}
return out return out
def clean_email(self): def clean_email(self):
data = self.cleaned_data['email'] data = self.cleaned_data["email"]
user = User.objects.get(username=self.cleaned_data['username']) user = User.objects.get(username=self.cleaned_data["username"])
if data == user.email: if data == user.email:
return data return data
if User.objects.filter(email=data).exists(): if User.objects.filter(email=data).exists():
raise forms.ValidationError('Email Address is in use') raise forms.ValidationError("Email Address is in use")
return data return data
def clean_password(self): def clean_password(self):
data = self.cleaned_data['password'] data = self.cleaned_data["password"]
if len(data) < 8 & len(data) != 0: if len(data) < 8 & len(data) != 0:
raise forms.ValidationError('Password is too short') raise forms.ValidationError("Password is too short")
return data return data
class SettingsForm(forms.Form): class SettingsForm(forms.Form):
base_dir = forms.CharField(help_text='Base Directory', base_dir = forms.CharField(help_text="Base Directory", widget=forms.TextInput(attrs={"class": "form-control"}))
widget=forms.TextInput(
attrs={
'class': 'form-control'
}
))
def clean_base_dir(self): def clean_base_dir(self):
data = self.cleaned_data['base_dir'] data = self.cleaned_data["base_dir"]
if not path.isdir(data): if not path.isdir(data):
raise forms.ValidationError('This is not a valid Directory') raise forms.ValidationError("This is not a valid Directory")
return data return data
@staticmethod @staticmethod
def get_initial_values(): def get_initial_values():
base_dir, _ = Setting.objects.get_or_create(name='BASE_DIR') base_dir, _ = Setting.objects.get_or_create(name="BASE_DIR")
initial = { initial = {"base_dir": base_dir.value}
'base_dir': base_dir.value,
}
return initial return initial

View File

@@ -7,11 +7,11 @@ from comic.models import Setting, Directory, ComicBook
class Command(BaseCommand): class Command(BaseCommand):
help = 'Scan directories to Update Comic DB' help = "Scan directories to Update Comic DB"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.base_dir = Setting.objects.get(name='BASE_DIR').value self.base_dir = Setting.objects.get(name="BASE_DIR").value
def handle(self, *args, **options): def handle(self, *args, **options):
self.scan_directory() self.scan_directory()
@@ -36,25 +36,21 @@ class Command(BaseCommand):
for file in os.listdir(comic_dir): for file in os.listdir(comic_dir):
if isdir(os.path.join(comic_dir, file)): if isdir(os.path.join(comic_dir, file)):
if directory: if directory:
next_directory, created = Directory.objects.get_or_create(name=file, next_directory, created = Directory.objects.get_or_create(name=file, parent=directory)
parent=directory)
else: else:
next_directory, created = Directory.objects.get_or_create(name=file, next_directory, created = Directory.objects.get_or_create(name=file, parent__isnull=True)
parent__isnull=True)
if created: if created:
next_directory.save() next_directory.save()
self.scan_directory(next_directory) self.scan_directory(next_directory)
else: else:
try: try:
if directory: if directory:
book = ComicBook.objects.get(file_name=file, book = ComicBook.objects.get(file_name=file, directory=directory)
directory=directory)
if book.version == 0: if book.version == 0:
book.version = 1 book.version = 1
book.save() book.save()
else: else:
book = ComicBook.objects.get(file_name=file, book = ComicBook.objects.get(file_name=file, directory__isnull=True)
directory__isnull=True)
if book.version == 0: if book.version == 0:
if directory: if directory:
book.directory = directory book.directory = directory

View File

@@ -6,16 +6,15 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Setting', name="Setting",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50)), ("name", models.CharField(max_length=50)),
('value', models.TextField()), ("value", models.TextField()),
], ],
), )
] ]

View File

@@ -6,14 +6,8 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0001_initial")]
('comic', '0001_initial'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(model_name="setting", name="name", field=models.CharField(unique=True, max_length=50))
model_name='setting',
name='name',
field=models.CharField(unique=True, max_length=50),
),
] ]

View File

@@ -6,27 +6,25 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0002_auto_20150616_1613")]
('comic', '0002_auto_20150616_1613'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ComicBook', name="ComicBook",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('file_name', models.CharField(unique=True, max_length=100)), ("file_name", models.CharField(unique=True, max_length=100)),
('last_read_page', models.IntegerField()), ("last_read_page", models.IntegerField()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='ComicPage', name="ComicPage",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('index', models.IntegerField()), ("index", models.IntegerField()),
('page_file_name', models.CharField(max_length=100)), ("page_file_name", models.CharField(max_length=100)),
('content_type', models.CharField(max_length=30)), ("content_type", models.CharField(max_length=30)),
('Comic', models.ForeignKey(to='comic.ComicBook', on_delete=models.CASCADE)), ("Comic", models.ForeignKey(to="comic.ComicBook", on_delete=models.CASCADE)),
], ],
), ),
] ]

View File

@@ -6,15 +6,10 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0003_comicbook_comicpage")]
('comic', '0003_comicbook_comicpage'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='comicbook', model_name="comicbook", name="unread", field=models.BooleanField(default=True), preserve_default=False
name='unread', )
field=models.BooleanField(default=True),
preserve_default=False,
),
] ]

View File

@@ -7,36 +7,27 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("comic", "0004_comicbook_unread")]
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('comic', '0004_comicbook_unread'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ComicStatus', name="ComicStatus",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('last_read_page', models.IntegerField()), ("last_read_page", models.IntegerField()),
('unread', models.BooleanField()), ("unread", models.BooleanField()),
], ],
), ),
migrations.RemoveField( migrations.RemoveField(model_name="comicbook", name="last_read_page"),
model_name='comicbook', migrations.RemoveField(model_name="comicbook", name="unread"),
name='last_read_page', migrations.AddField(
), model_name="comicstatus",
migrations.RemoveField( name="comic",
model_name='comicbook', field=models.ForeignKey(to="comic.ComicBook", on_delete=models.CASCADE),
name='unread',
), ),
migrations.AddField( migrations.AddField(
model_name='comicstatus', model_name="comicstatus",
name='comic', name="user",
field=models.ForeignKey(to='comic.ComicBook', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='comicstatus',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
] ]

View File

@@ -6,19 +6,9 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0005_auto_20150625_1400")]
('comic', '0005_auto_20150625_1400'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(model_name="comicstatus", name="last_read_page", field=models.IntegerField(default=0)),
model_name='comicstatus', migrations.AlterField(model_name="comicstatus", name="unread", field=models.BooleanField(default=True)),
name='last_read_page',
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name='comicstatus',
name='unread',
field=models.BooleanField(default=True),
),
] ]

View File

@@ -6,14 +6,8 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0006_auto_20150625_1411")]
('comic', '0006_auto_20150625_1411'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(model_name="setting", name="name", field=models.CharField(unique=True, max_length=100))
model_name='setting',
name='name',
field=models.CharField(unique=True, max_length=100),
),
] ]

View File

@@ -11,39 +11,40 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0007_auto_20150626_1820")]
('comic', '0007_auto_20150626_1820'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Directory', name="Directory",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
('name', models.CharField(max_length=100)), ("name", models.CharField(max_length=100)),
('selector', models.UUIDField(default=uuid.uuid4, null=True)), ("selector", models.UUIDField(default=uuid.uuid4, null=True)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comic.Directory')), (
"parent",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="comic.Directory"
),
),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='comicbook', model_name="comicbook",
name='date_added', name="date_added",
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 31, 10, 40, 30, 62170, tzinfo=utc)), field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(2016, 3, 31, 10, 40, 30, 62170, tzinfo=utc)
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='comicbook', model_name="comicbook", name="selector", field=models.UUIDField(default=uuid.uuid4, null=True)
name='selector',
field=models.UUIDField(default=uuid.uuid4, null=True),
), ),
migrations.AddField(model_name="comicbook", name="version", field=models.IntegerField(default=0)),
migrations.AddField( migrations.AddField(
model_name='comicbook', model_name="comicbook",
name='version', name="directory",
field=models.IntegerField(default=0), field=models.ForeignKey(
), blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="comic.Directory"
migrations.AddField( ),
model_name='comicbook',
name='directory',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comic.Directory'),
), ),
] ]

View File

@@ -8,11 +8,11 @@ import uuid
def gen_uuid(apps, schema_editor): def gen_uuid(apps, schema_editor):
comicbook = apps.get_model('comic', 'comicbook') comicbook = apps.get_model("comic", "comicbook")
for row in comicbook.objects.all(): for row in comicbook.objects.all():
row.selector = uuid.uuid4() row.selector = uuid.uuid4()
row.save() row.save()
directory = apps.get_model('comic', 'directory') directory = apps.get_model("comic", "directory")
for row in directory.objects.all(): for row in directory.objects.all():
row.selector = uuid.uuid4() row.selector = uuid.uuid4()
row.save() row.save()
@@ -20,10 +20,6 @@ def gen_uuid(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0008_auto_20160331_1140")]
('comic', '0008_auto_20160331_1140'),
]
operations = [ operations = [migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop)]
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
]

View File

@@ -9,19 +9,13 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0009_auto_20160331_1140")]
('comic', '0009_auto_20160331_1140'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='comicbook', model_name="comicbook", name="selector", field=models.UUIDField(default=uuid.uuid4, unique=True)
name='selector',
field=models.UUIDField(default=uuid.uuid4, unique=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='directory', model_name="directory", name="selector", field=models.UUIDField(default=uuid.uuid4, unique=True)
name='selector',
field=models.UUIDField(default=uuid.uuid4, unique=True),
), ),
] ]

View File

@@ -7,14 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0010_auto_20160331_1140")]
('comic', '0010_auto_20160331_1140'),
]
operations = [ operations = [migrations.AlterField(model_name="comicbook", name="version", field=models.IntegerField(default=1))]
migrations.AlterField(
model_name='comicbook',
name='version',
field=models.IntegerField(default=1),
),
]

View File

@@ -8,19 +8,17 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0011_auto_20160331_1141")]
('comic', '0011_auto_20160331_1141'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='comicbook', model_name="comicbook",
name='selector', name="selector",
field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='directory', model_name="directory",
name='selector', name="selector",
field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
), ),
] ]

View File

@@ -7,14 +7,8 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0012_auto_20160401_0949")]
('comic', '0012_auto_20160401_0949'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(model_name="comicstatus", name="finished", field=models.BooleanField(default=False))
model_name='comicstatus',
name='finished',
field=models.BooleanField(default=False),
),
] ]

View File

@@ -5,22 +5,19 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
from django.db.models import Max from django.db.models import Max
def set_finished(apps, schema_editor): def set_finished(apps, schema_editor):
comicstatus = apps.get_model('comic', 'comicstatus') comicstatus = apps.get_model("comic", "comicstatus")
comicpage = apps.get_model('comic', 'ComicPage') comicpage = apps.get_model("comic", "ComicPage")
for row in comicstatus.objects.all(): for row in comicstatus.objects.all():
last_page = comicpage.objects.filter(Comic=row.comic).aggregate(Max('index')) last_page = comicpage.objects.filter(Comic=row.comic).aggregate(Max("index"))
if row.last_read_page == last_page['index__max']: if row.last_read_page == last_page["index__max"]:
row.finished = True row.finished = True
row.save() row.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0013_comicstatus_finished")]
('comic', '0013_comicstatus_finished'),
]
operations = [ operations = [migrations.RunPython(set_finished, reverse_code=migrations.RunPython.noop)]
migrations.RunPython(set_finished, reverse_code=migrations.RunPython.noop),
]

View File

@@ -7,14 +7,8 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0014_auto_20160404_1402")]
('comic', '0014_auto_20160404_1402'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(model_name="comicbook", name="file_name", field=models.CharField(max_length=100))
model_name='comicbook',
name='file_name',
field=models.CharField(max_length=100),
),
] ]

View File

@@ -6,14 +6,8 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0015_auto_20160405_1126")]
('comic', '0015_auto_20160405_1126'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(model_name="comicpage", name="page_file_name", field=models.CharField(max_length=200))
model_name='comicpage',
name='page_file_name',
field=models.CharField(max_length=200),
),
] ]

View File

@@ -10,19 +10,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("comic", "0016_auto_20160414_1335")]
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('comic', '0016_auto_20160414_1335'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='UserMisc', name="UserMisc",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
('feed_id', models.UUIDField(db_index=True, default=uuid.uuid4, unique=True)), ("feed_id", models.UUIDField(db_index=True, default=uuid.uuid4, unique=True)),
( (
'user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), "user",
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
], ],
), )
] ]

View File

@@ -6,17 +6,13 @@ from django.db import migrations
def gen_feeds(apps, schema_editor): def gen_feeds(apps, schema_editor):
user_misc = apps.get_model('comic', 'UserMisc') user_misc = apps.get_model("comic", "UserMisc")
User = apps.get_model('auth', 'user') User = apps.get_model("auth", "user")
for user in User.objects.all(): for user in User.objects.all():
um = user_misc.objects.create(user=user) um = user_misc.objects.create(user=user)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comic", "0017_usermisc")]
('comic', '0017_usermisc'),
]
operations = [ operations = [migrations.RunPython(gen_feeds, reverse_code=migrations.RunPython.noop)]
migrations.RunPython(gen_feeds, reverse_code=migrations.RunPython.noop),
]

View File

@@ -27,11 +27,11 @@ class Setting(models.Model):
class Directory(models.Model): class Directory(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
parent = models.ForeignKey('Directory', null=True, blank=True, on_delete=models.CASCADE) parent = models.ForeignKey("Directory", null=True, blank=True, on_delete=models.CASCADE)
selector = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True) selector = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True)
def __str__(self): def __str__(self):
return 'Directory: {0}; {1}'.format(self.name, self.parent) return "Directory: {0}; {1}".format(self.name, self.parent)
@property @property
def path(self): def path(self):
@@ -83,7 +83,7 @@ class ComicBook(models.Model):
return urlsafe_base64_encode(self.selector.bytes) return urlsafe_base64_encode(self.selector.bytes)
def get_image(self, page): def get_image(self, page):
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
if self.directory: if self.directory:
archive_path = path.join(base_dir, self.directory.path, self.file_name) archive_path = path.join(base_dir, self.directory.path, self.file_name)
else: else:
@@ -109,11 +109,11 @@ class ComicBook(models.Model):
class Navigation: class Navigation:
next_index = 0 next_index = 0
next_path = '' next_path = ""
prev_index = 0 prev_index = 0
prev_path = '' prev_path = ""
cur_index = 0 cur_index = 0
cur_path = '' cur_path = ""
q_prev_to_directory = False q_prev_to_directory = False
q_next_to_directory = False q_next_to_directory = False
@@ -122,10 +122,7 @@ class ComicBook(models.Model):
setattr(self, arg, kwargs[arg]) setattr(self, arg, kwargs[arg])
def nav(self, page, user): def nav(self, page, user):
out = self.Navigation( out = self.Navigation(cur_index=page, cur_path=urlsafe_base64_encode(self.selector.bytes))
cur_index=page,
cur_path=urlsafe_base64_encode(self.selector.bytes)
)
if page == 0: if page == 0:
out.prev_path, out.prev_index = self.nav_get_prev_comic(user) out.prev_path, out.prev_index = self.nav_get_prev_comic(user)
if out.prev_index == -1: if out.prev_index == -1:
@@ -144,7 +141,7 @@ class ComicBook(models.Model):
return out return out
def nav_get_prev_comic(self, user): def nav_get_prev_comic(self, user):
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
if self.directory: if self.directory:
folder = path.join(base_dir, self.directory.path) folder = path.join(base_dir, self.directory.path)
else: else:
@@ -155,7 +152,7 @@ class ComicBook(models.Model):
if self.directory: if self.directory:
comic_path = urlsafe_base64_encode(self.directory.selector.bytes) comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
else: else:
comic_path = '' comic_path = ""
index = -1 index = -1
else: else:
prev_comic = dir_list[comic_index - 1] prev_comic = dir_list[comic_index - 1]
@@ -163,11 +160,9 @@ class ComicBook(models.Model):
if not path.isdir(path.join(folder, prev_comic)): if not path.isdir(path.join(folder, prev_comic)):
try: try:
if self.directory: if self.directory:
book = ComicBook.objects.get(file_name=prev_comic, book = ComicBook.objects.get(file_name=prev_comic, directory=self.directory)
directory=self.directory)
else: else:
book = ComicBook.objects.get(file_name=prev_comic, book = ComicBook.objects.get(file_name=prev_comic, directory__isnull=True)
directory__isnull=True)
except ComicBook.DoesNotExist: except ComicBook.DoesNotExist:
if self.directory: if self.directory:
book = ComicBook.process_comic_book(prev_comic, self.directory) book = ComicBook.process_comic_book(prev_comic, self.directory)
@@ -180,12 +175,12 @@ class ComicBook(models.Model):
if self.directory: if self.directory:
comic_path = urlsafe_base64_encode(self.directory.selector.bytes) comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
else: else:
comic_path = '' comic_path = ""
index = -1 index = -1
return comic_path, index return comic_path, index
def nav_get_next_comic(self, user): def nav_get_next_comic(self, user):
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
if self.directory: if self.directory:
folder = path.join(base_dir, self.directory.path) folder = path.join(base_dir, self.directory.path)
else: else:
@@ -196,11 +191,9 @@ class ComicBook(models.Model):
next_comic = dir_list[comic_index + 1] next_comic = dir_list[comic_index + 1]
try: try:
if self.directory: if self.directory:
book = ComicBook.objects.get(file_name=next_comic, book = ComicBook.objects.get(file_name=next_comic, directory=self.directory)
directory=self.directory)
else: else:
book = ComicBook.objects.get(file_name=next_comic, book = ComicBook.objects.get(file_name=next_comic, directory__isnull=True)
directory__isnull=True)
except ComicBook.DoesNotExist: except ComicBook.DoesNotExist:
if self.directory: if self.directory:
book = ComicBook.process_comic_book(next_comic, self.directory) book = ComicBook.process_comic_book(next_comic, self.directory)
@@ -215,18 +208,18 @@ class ComicBook(models.Model):
if self.directory: if self.directory:
comic_path = urlsafe_base64_encode(self.directory.selector.bytes) comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
else: else:
comic_path = '' comic_path = ""
index = -1 index = -1
return comic_path, index return comic_path, index
class DirFile: class DirFile:
def __init__(self): def __init__(self):
self.name = '' self.name = ""
self.isdir = False self.isdir = False
self.icon = '' self.icon = ""
self.iscb = False self.iscb = False
self.location = '' self.location = ""
self.label = '' self.label = ""
self.cur_page = 0 self.cur_page = 0
def __str__(self): def __str__(self):
@@ -234,7 +227,7 @@ class ComicBook(models.Model):
@property @property
def pages(self): def pages(self):
return [cp for cp in ComicPage.objects.filter(Comic=self).order_by('index')] return [cp for cp in ComicPage.objects.filter(Comic=self).order_by("index")]
def page_name(self, index): def page_name(self, index):
return ComicPage.objects.get(Comic=self, index=index).page_file_name return ComicPage.objects.get(Comic=self, index=index).page_file_name
@@ -247,15 +240,14 @@ class ComicBook(models.Model):
:type directory: Directory :type directory: Directory
""" """
try: try:
book = ComicBook.objects.get(file_name=comic_file_name, book = ComicBook.objects.get(file_name=comic_file_name, version=0)
version=0)
book.directory = directory book.directory = directory
book.version = 1 book.version = 1
book.save() book.save()
return book return book
except ComicBook.DoesNotExist: except ComicBook.DoesNotExist:
pass pass
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
if directory: if directory:
comic_full_path = path.join(base_dir, directory.get_path(), comic_file_name) comic_full_path = path.join(base_dir, directory.get_path(), comic_file_name)
else: else:
@@ -272,32 +264,30 @@ class ComicBook(models.Model):
return comic_file_name return comic_file_name
with atomic(): with atomic():
if directory: if directory:
book = ComicBook(file_name=comic_file_name, book = ComicBook(file_name=comic_file_name, directory=directory)
directory=directory)
else: else:
book = ComicBook(file_name=comic_file_name) book = ComicBook(file_name=comic_file_name)
book.save() book.save()
page_index = 0 page_index = 0
for page_file_name in sorted([str(x) for x in cbx.namelist()], key=str.lower): for page_file_name in sorted([str(x) for x in cbx.namelist()], key=str.lower):
try: try:
dot_index = page_file_name.rindex('.') + 1 dot_index = page_file_name.rindex(".") + 1
except ValueError: except ValueError:
continue continue
ext = page_file_name.lower()[dot_index:] ext = page_file_name.lower()[dot_index:]
if ext in ['jpg', 'jpeg']: if ext in ["jpg", "jpeg"]:
content_type = 'image/jpeg' content_type = "image/jpeg"
elif ext == 'png': elif ext == "png":
content_type = 'image/png' content_type = "image/png"
elif ext == 'bmp': elif ext == "bmp":
content_type = 'image/bmp' content_type = "image/bmp"
elif ext == 'gif': elif ext == "gif":
content_type = 'image/gif' content_type = "image/gif"
else: else:
content_type = 'text/plain' content_type = "text/plain"
page = ComicPage(Comic=book, page = ComicPage(
index=page_index, Comic=book, index=page_index, page_file_name=page_file_name, content_type=content_type
page_file_name=page_file_name, )
content_type=content_type)
page.save() page.save()
page_index += 1 page_index += 1
return book return book
@@ -336,8 +326,12 @@ class ComicStatus(models.Model):
return self.__repr__() return self.__repr__()
def __repr__(self): def __repr__(self):
return f'<ComicStatus:{self.user.username}:{self.comic.file_name}:{self.last_read_page}:' \ return (
f'{self.unread}:{self.finished}' f"<ComicStatus:{self.user.username}:{self.comic.file_name}:{self.last_read_page}:"
f"{self.unread}:{self.finished}"
)
# TODO: add support to reference items last being read # TODO: add support to reference items last being read

File diff suppressed because it is too large Load Diff

View File

@@ -12,9 +12,7 @@ from comic.util import generate_directory
class ComicBookTests(TestCase): class ComicBookTests(TestCase):
def setUp(self): def setUp(self):
Setting.objects.create( Setting.objects.create(name="BASE_DIR", value=path.join(os.getcwd(), "comic", "test"))
name="BASE_DIR", value=path.join(os.getcwd(), "comic", "test")
)
User.objects.create_user("test", "test@test.com", "test") User.objects.create_user("test", "test@test.com", "test")
user = User.objects.first() user = User.objects.first()
ComicBook.process_comic_book("test1.rar") ComicBook.process_comic_book("test1.rar")
@@ -133,51 +131,34 @@ class ComicBookTests(TestCase):
d = Directory.objects.get(name="test_folder", parent__isnull=True) d = Directory.objects.get(name="test_folder", parent__isnull=True)
location = "/comic/{0}/".format(urlsafe_base64_encode(d.selector.bytes)) location = "/comic/{0}/".format(urlsafe_base64_encode(d.selector.bytes))
self.assertEqual(dir1.location, location) self.assertEqual(dir1.location, location)
self.assertEqual( self.assertEqual(dir1.label, '<center><span class="label label-default">Empty</span></center>')
dir1.label,
'<center><span class="label label-default">Empty</span></center>',
)
dir2 = folders[1] dir2 = folders[1]
self.assertEqual(dir2.name, "test1.rar") self.assertEqual(dir2.name, "test1.rar")
self.assertEqual(dir2.type, "book") self.assertEqual(dir2.type, "book")
self.assertEqual(dir2.icon, "glyphicon-book") self.assertEqual(dir2.icon, "glyphicon-book")
c = ComicBook.objects.get(file_name="test1.rar", directory__isnull=True) c = ComicBook.objects.get(file_name="test1.rar", directory__isnull=True)
location = "/comic/read/{0}/{1}/".format( location = "/comic/read/{0}/{1}/".format(urlsafe_base64_encode(c.selector.bytes), "0")
urlsafe_base64_encode(c.selector.bytes), "0"
)
self.assertEqual(dir2.location, location) self.assertEqual(dir2.location, location)
self.assertEqual( self.assertEqual(dir2.label, '<center><span class="label label-default">Unread</span></center>')
dir2.label,
'<center><span class="label label-default">Unread</span></center>',
)
dir3 = folders[2] dir3 = folders[2]
self.assertEqual(dir3.name, "test2.rar") self.assertEqual(dir3.name, "test2.rar")
self.assertEqual(dir3.type, "book") self.assertEqual(dir3.type, "book")
self.assertEqual(dir3.icon, "glyphicon-book") self.assertEqual(dir3.icon, "glyphicon-book")
c = ComicBook.objects.get(file_name="test2.rar", directory__isnull=True) c = ComicBook.objects.get(file_name="test2.rar", directory__isnull=True)
location = "/comic/read/{0}/{1}/".format( location = "/comic/read/{0}/{1}/".format(urlsafe_base64_encode(c.selector.bytes), "2")
urlsafe_base64_encode(c.selector.bytes), "2"
)
self.assertEqual(dir3.location, location) self.assertEqual(dir3.location, location)
self.assertEqual( self.assertEqual(dir3.label, '<center><span class="label label-primary">3/4</span></center>')
dir3.label, '<center><span class="label label-primary">3/4</span></center>'
)
dir4 = folders[3] dir4 = folders[3]
self.assertEqual(dir4.name, "test3.rar") self.assertEqual(dir4.name, "test3.rar")
self.assertEqual(dir4.type, "book") self.assertEqual(dir4.type, "book")
self.assertEqual(dir3.icon, "glyphicon-book") self.assertEqual(dir3.icon, "glyphicon-book")
c = ComicBook.objects.get(file_name="test3.rar", directory__isnull=True) c = ComicBook.objects.get(file_name="test3.rar", directory__isnull=True)
location = "/comic/read/{0}/{1}/".format( location = "/comic/read/{0}/{1}/".format(urlsafe_base64_encode(c.selector.bytes), "0")
urlsafe_base64_encode(c.selector.bytes), "0"
)
self.assertEqual(dir4.location, location) self.assertEqual(dir4.location, location)
self.assertEqual( self.assertEqual(dir4.label, '<center><span class="label label-default">Unread</span></center>')
dir4.label,
'<center><span class="label label-default">Unread</span></center>',
)
def test_pages(self): def test_pages(self):
book = ComicBook.objects.get(file_name="test1.rar") book = ComicBook.objects.get(file_name="test1.rar")
@@ -217,9 +198,7 @@ class ComicBookTests(TestCase):
response = c.post("/comic/list_json/") response = c.post("/comic/list_json/")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
directory = Directory.objects.first() directory = Directory.objects.first()
response = c.post( response = c.post(f"/comic/list_json/{urlsafe_base64_encode(directory.selector.bytes)}/")
f"/comic/list_json/{urlsafe_base64_encode(directory.selector.bytes)}/"
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_recent_comics(self): def test_recent_comics(self):
@@ -241,12 +220,7 @@ class ComicBookTests(TestCase):
generate_directory(User.objects.first()) generate_directory(User.objects.first())
ComicStatus.objects.all().delete() ComicStatus.objects.all().delete()
req_data = { req_data = {"start": "0", "length": "10", "search[value]": "", "order[0][dir]": "desc"}
"start": "0",
"length": "10",
"search[value]": "",
"order[0][dir]": "desc",
}
response = c.post("/comic/recent/json/", req_data) response = c.post("/comic/recent/json/", req_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
req_data["search[value]"] = "test1.rar" req_data["search[value]"] = "test1.rar"
@@ -261,13 +235,11 @@ class ComicBookTests(TestCase):
{ {
"date": book.date_added.strftime("%d/%m/%y-%H:%M"), "date": book.date_added.strftime("%d/%m/%y-%H:%M"),
"icon": '<span class="glyphicon glyphicon-book"></span>', "icon": '<span class="glyphicon glyphicon-book"></span>',
"label": '<center><span class="label ' "label": '<center><span class="label ' 'label-default">Unread</span></center>',
'label-default">Unread</span></center>',
"name": "test1.rar", "name": "test1.rar",
"selector": urlsafe_base64_encode(book.selector.bytes), "selector": urlsafe_base64_encode(book.selector.bytes),
"type": "book", "type": "book",
"url": f"/comic/read/" "url": f"/comic/read/" f"{urlsafe_base64_encode(book.selector.bytes)}/0/",
f"{urlsafe_base64_encode(book.selector.bytes)}/0/",
} }
], ],
"recordsFiltered": 1, "recordsFiltered": 1,
@@ -302,11 +274,7 @@ class ComicBookTests(TestCase):
response = c.get("/comic/edit/") response = c.get("/comic/edit/")
self.assertEqual(response.status_code, 405) self.assertEqual(response.status_code, 405)
req_data = { req_data = {"comic_list_length": 10, "func": "unread", "selected": book.selector_string}
"comic_list_length": 10,
"func": "unread",
"selected": book.selector_string,
}
response = c.post("/comic/edit/", req_data) response = c.post("/comic/edit/", req_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@@ -2,20 +2,21 @@ from django.conf.urls import url
from . import feeds from . import feeds
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.comic_list, name='index'), url(r"^$", views.comic_list, name="index"),
url(r'^settings/$', views.settings_page, name='settings'), url(r"^settings/$", views.settings_page, name="settings"),
url(r'^settings/users/$', views.users_page, name='users'), url(r"^settings/users/$", views.users_page, name="users"),
url(r'^settings/users/(?P<user_id>[0-9]+)/$', views.user_config_page, name='users'), url(r"^settings/users/(?P<user_id>[0-9]+)/$", views.user_config_page, name="users"),
url(r'^settings/users/add/$', views.user_add_page, name='users'), url(r"^settings/users/add/$", views.user_add_page, name="users"),
url(r'^account/$', views.account_page, name='account'), url(r"^account/$", views.account_page, name="account"),
url(r'^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/$', views.read_comic, name='read_comic'), url(r"^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/$", views.read_comic, name="read_comic"),
url(r'^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/img$', views.get_image, name='get_image'), url(r"^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/img$", views.get_image, name="get_image"),
url(r'^list_json/$', views.comic_list_json, name='comic_list_json1'), url(r"^list_json/$", views.comic_list_json, name="comic_list_json1"),
url(r'^list_json/(?P<directory_selector>[\w-]+)/$', views.comic_list_json, name='comic_list_json2'), url(r"^list_json/(?P<directory_selector>[\w-]+)/$", views.comic_list_json, name="comic_list_json2"),
url(r'^recent/$', views.recent_comics, name='recent_comics'), url(r"^recent/$", views.recent_comics, name="recent_comics"),
url(r'^recent/json/$', views.recent_comics_json, name='recent_comics_json'), url(r"^recent/json/$", views.recent_comics_json, name="recent_comics_json"),
url(r'^edit/$', views.comic_edit, name='comic_edit'), url(r"^edit/$", views.comic_edit, name="comic_edit"),
url(r'^feed/(?P<user_selector>[\w-]+)/$', feeds.RecentComics()), url(r"^feed/(?P<user_selector>[\w-]+)/$", feeds.RecentComics()),
url(r'^(?P<directory_selector>[\w-]+)/$', views.comic_list, name='comic_list'), url(r"^(?P<directory_selector>[\w-]+)/$", views.comic_list, name="comic_list"),
] ]

View File

@@ -8,32 +8,32 @@ from .models import ComicBook, Setting, ComicStatus, Directory
def generate_title_from_path(file_path): def generate_title_from_path(file_path):
if file_path == '': if file_path == "":
return 'CBWebReader' return "CBWebReader"
return 'CBWebReader - ' + ' - '.join(file_path.split(path.sep)) return "CBWebReader - " + " - ".join(file_path.split(path.sep))
class Menu: class Menu:
def __init__(self, user, page=''): def __init__(self, user, page=""):
""" """
:type page: str :type page: str
""" """
self.menu_items = OrderedDict() self.menu_items = OrderedDict()
self.menu_items['Browse'] = '/comic/' self.menu_items["Browse"] = "/comic/"
self.menu_items['Recent'] = '/comic/recent/' self.menu_items["Recent"] = "/comic/recent/"
self.menu_items['Account'] = '/comic/account/' self.menu_items["Account"] = "/comic/account/"
if user.is_superuser: if user.is_superuser:
self.menu_items['Settings'] = '/comic/settings/' self.menu_items["Settings"] = "/comic/settings/"
self.menu_items['Users'] = '/comic/settings/users/' self.menu_items["Users"] = "/comic/settings/users/"
self.menu_items['Logout'] = '/logout/' self.menu_items["Logout"] = "/logout/"
self.current_page = page self.current_page = page
class Breadcrumb: class Breadcrumb:
def __init__(self): def __init__(self):
self.name = 'Home' self.name = "Home"
self.url = '/comic/' self.url = "/comic/"
def __str__(self): def __str__(self):
return self.name return self.name
@@ -56,12 +56,12 @@ def generate_breadcrumbs_from_path(directory=False, book=False):
for item in folders[::-1]: for item in folders[::-1]:
bc = Breadcrumb() bc = Breadcrumb()
bc.name = item.name bc.name = item.name
bc.url = '/comic/' + urlsafe_base64_encode(item.selector.bytes) bc.url = "/comic/" + urlsafe_base64_encode(item.selector.bytes)
output.append(bc) output.append(bc)
if book: if book:
bc = Breadcrumb() bc = Breadcrumb()
bc.name = book.file_name bc.name = book.file_name
bc.url = '/read/' + urlsafe_base64_encode(book.selector.bytes) bc.url = "/read/" + urlsafe_base64_encode(book.selector.bytes)
output.append(bc) output.append(bc)
return output return output
@@ -79,46 +79,45 @@ def generate_breadcrumbs_from_menu(paths):
class DirFile: class DirFile:
def __init__(self): def __init__(self):
self.name = '' self.name = ""
self.icon = '' self.icon = ""
self.location = '' self.location = ""
self.label = '' self.label = ""
self.type = '' self.type = ""
self.selector = '' self.selector = ""
def __str__(self): def __str__(self):
return self.name return self.name
def populate_directory(self, directory, user): def populate_directory(self, directory, user):
self.name = directory.name self.name = directory.name
self.icon = 'glyphicon-folder-open' self.icon = "glyphicon-folder-open"
self.selector = urlsafe_base64_encode(directory.selector.bytes) self.selector = urlsafe_base64_encode(directory.selector.bytes)
self.location = '/comic/{0}/'.format(self.selector) self.location = "/comic/{0}/".format(self.selector)
self.label = generate_dir_status(user, directory) self.label = generate_dir_status(user, directory)
self.type = 'directory' self.type = "directory"
def populate_comic(self, comic, user): def populate_comic(self, comic, user):
if type(comic) == str: if type(comic) == str:
self.icon = 'glyphicon-remove' self.icon = "glyphicon-remove"
self.name = comic self.name = comic
self.selector = '0' self.selector = "0"
self.location = '/' self.location = "/"
self.label = '<center><span class="label label-danger">Error</span></center>' self.label = '<center><span class="label label-danger">Error</span></center>'
self.type = 'book' self.type = "book"
else: else:
self.icon = 'glyphicon-book' self.icon = "glyphicon-book"
self.name = comic.file_name self.name = comic.file_name
status, created = ComicStatus.objects.get_or_create(comic=comic, user=user) status, created = ComicStatus.objects.get_or_create(comic=comic, user=user)
if created: if created:
status.save() status.save()
self.selector = urlsafe_base64_encode(comic.selector.bytes) self.selector = urlsafe_base64_encode(comic.selector.bytes)
self.location = '/comic/read/{0}/{1}/'.format(self.selector, self.location = "/comic/read/{0}/{1}/".format(self.selector, status.last_read_page)
status.last_read_page)
self.label = generate_label(comic, status) self.label = generate_label(comic, status)
self.type = 'book' self.type = "book"
def __repr__(self): def __repr__(self):
return f'<DirFile: {self.name}: {self.type}>' return f"<DirFile: {self.name}: {self.type}>"
def generate_directory(user, directory=False): def generate_directory(user, directory=False):
@@ -126,7 +125,7 @@ def generate_directory(user, directory=False):
:type user: User :type user: User
:type directory: Directory :type directory: Directory
""" """
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
files = [] files = []
if directory: if directory:
ordered_dir_list = listdir(path.join(base_dir, directory.path)) ordered_dir_list = listdir(path.join(base_dir, directory.path))
@@ -136,15 +135,11 @@ def generate_directory(user, directory=False):
dir_list = [x for x in ordered_dir_list if path.isdir(path.join(base_dir, x))] dir_list = [x for x in ordered_dir_list if path.isdir(path.join(base_dir, x))]
file_list = [x for x in ordered_dir_list if x not in dir_list] file_list = [x for x in ordered_dir_list if x not in dir_list]
if directory: if directory:
dir_list_obj = Directory.objects.filter(name__in=dir_list, dir_list_obj = Directory.objects.filter(name__in=dir_list, parent=directory)
parent=directory) file_list_obj = ComicBook.objects.filter(file_name__in=file_list, directory=directory)
file_list_obj = ComicBook.objects.filter(file_name__in=file_list,
directory=directory)
else: else:
dir_list_obj = Directory.objects.filter(name__in=dir_list, dir_list_obj = Directory.objects.filter(name__in=dir_list, parent__isnull=True)
parent__isnull=True) file_list_obj = ComicBook.objects.filter(file_name__in=file_list, directory__isnull=True)
file_list_obj = ComicBook.objects.filter(file_name__in=file_list,
directory__isnull=True)
for directory_obj in dir_list_obj: for directory_obj in dir_list_obj:
df = DirFile() df = DirFile()
df.populate_directory(directory_obj, user) df.populate_directory(directory_obj, user)
@@ -159,8 +154,7 @@ def generate_directory(user, directory=False):
file_list.remove(file_obj.file_name) file_list.remove(file_obj.file_name)
for directory_name in dir_list: for directory_name in dir_list:
if directory: if directory:
directory_obj = Directory(name=directory_name, directory_obj = Directory(name=directory_name, parent=directory)
parent=directory)
else: else:
directory_obj = Directory(name=directory_name) directory_obj = Directory(name=directory_name)
directory_obj.save() directory_obj.save()
@@ -168,7 +162,7 @@ def generate_directory(user, directory=False):
df.populate_directory(directory_obj, user) df.populate_directory(directory_obj, user)
files.append(df) files.append(df)
for file_name in file_list: for file_name in file_list:
if file_name.lower()[-4:] in ['.rar', '.zip', '.cbr', '.cbz']: if file_name.lower()[-4:] in [".rar", ".zip", ".cbr", ".cbz"]:
book = ComicBook.process_comic_book(file_name, directory) book = ComicBook.process_comic_book(file_name, directory)
df = DirFile() df = DirFile()
df.populate_comic(book, user) df.populate_comic(book, user)
@@ -184,17 +178,17 @@ def generate_label(book, status):
elif (status.last_read_page + 1) == book.page_count: elif (status.last_read_page + 1) == book.page_count:
label_text = '<center><span class="label label-success">Read</span></center>' label_text = '<center><span class="label label-success">Read</span></center>'
else: else:
label_text = '<center><span class="label label-primary">%s/%s</span></center>' % \ label_text = '<center><span class="label label-primary">%s/%s</span></center>' % (
(status.last_read_page + 1, book.page_count) status.last_read_page + 1,
book.page_count,
)
return label_text return label_text
def generate_dir_status(user, directory): def generate_dir_status(user, directory):
cb_list = ComicBook.objects.filter(directory=directory) cb_list = ComicBook.objects.filter(directory=directory)
total = cb_list.count() total = cb_list.count()
total_read = ComicStatus.objects.filter(user=user, total_read = ComicStatus.objects.filter(user=user, comic__in=cb_list, finished=True).count()
comic__in=cb_list,
finished=True).count()
if total == 0: if total == 0:
return '<center><span class="label label-default">Empty</span></center>' return '<center><span class="label label-default">Empty</span></center>'
elif total == total_read: elif total == total_read:

View File

@@ -18,8 +18,14 @@ from django.views.decorators.http import require_POST
from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm
from .models import Setting, ComicBook, ComicStatus, Directory, ComicPage, UserMisc from .models import Setting, ComicBook, ComicStatus, Directory, ComicPage, UserMisc
from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu, \ from .util import (
generate_title_from_path, Menu, generate_directory, generate_label generate_breadcrumbs_from_path,
generate_breadcrumbs_from_menu,
generate_title_from_path,
Menu,
generate_directory,
generate_label,
)
# noinspection PyTypeChecker # noinspection PyTypeChecker
@@ -27,11 +33,11 @@ from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu
@login_required @login_required
def comic_list(request, directory_selector=False): def comic_list(request, directory_selector=False):
try: try:
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name="BASE_DIR").value
except Setting.DoesNotExist: except Setting.DoesNotExist:
return redirect('/comic/settings/') return redirect("/comic/settings/")
if not path.isdir(base_dir): if not path.isdir(base_dir):
return redirect('/comic/settings/') return redirect("/comic/settings/")
if directory_selector: if directory_selector:
selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector)) selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector))
@@ -42,18 +48,17 @@ def comic_list(request, directory_selector=False):
if directory: if directory:
title = generate_title_from_path(directory.path) title = generate_title_from_path(directory.path)
breadcrumbs = generate_breadcrumbs_from_path(directory) breadcrumbs = generate_breadcrumbs_from_path(directory)
json_url = '/comic/list_json/{0}/'.format(directory_selector) json_url = "/comic/list_json/{0}/".format(directory_selector)
else: else:
title = generate_title_from_path('Home') title = generate_title_from_path("Home")
breadcrumbs = generate_breadcrumbs_from_path() breadcrumbs = generate_breadcrumbs_from_path()
json_url = '/comic/list_json/' json_url = "/comic/list_json/"
return render(request, 'comic/comic_list.html', { return render(
'breadcrumbs': breadcrumbs, request,
'menu': Menu(request.user, 'Browse'), "comic/comic_list.html",
'title': title, {"breadcrumbs": breadcrumbs, "menu": Menu(request.user, "Browse"), "title": title, "json_url": json_url},
'json_url': json_url )
})
@login_required @login_required
@@ -67,100 +72,99 @@ def comic_list_json(request, directory_selector=False):
directory = False directory = False
files = generate_directory(request.user, directory) files = generate_directory(request.user, directory)
response_data = dict() response_data = dict()
response_data['data'] = [] response_data["data"] = []
for file in files: for file in files:
response_data['data'].append({ response_data["data"].append(
'blank': '', {
'selector': file.selector, "blank": "",
'type': file.type, "selector": file.selector,
'icon': icon_str.format(file.icon), "type": file.type,
'name': file.name, "icon": icon_str.format(file.icon),
'label': file.label, "name": file.name,
'url': file.location, "label": file.label,
}) "url": file.location,
return HttpResponse( }
json.dumps(response_data), )
content_type="application/json" return HttpResponse(json.dumps(response_data), content_type="application/json")
)
@login_required @login_required
def recent_comics(request): def recent_comics(request):
feed_id, _ = UserMisc.objects.get_or_create(user=request.user) feed_id, _ = UserMisc.objects.get_or_create(user=request.user)
return render(request, return render(
'comic/recent_comics.html', request,
{ "comic/recent_comics.html",
'breadcrumbs': generate_breadcrumbs_from_menu([('Recent', '/comic/recent/')]), {
'menu': Menu(request.user, 'Recent'), "breadcrumbs": generate_breadcrumbs_from_menu([("Recent", "/comic/recent/")]),
'title': 'Recent Comics', "menu": Menu(request.user, "Recent"),
'feed_id': urlsafe_base64_encode(feed_id.feed_id.bytes), "title": "Recent Comics",
}) "feed_id": urlsafe_base64_encode(feed_id.feed_id.bytes),
},
@login_required
@require_POST
def recent_comics_json(request):
start = int(request.POST['start'])
end = start + int(request.POST['length'])
icon = '<span class="glyphicon glyphicon-book"></span>'
comics = ComicBook.objects.all()
response_data = dict()
response_data['recordsTotal'] = comics.count()
if request.POST['search[value]']:
comics = comics.filter(file_name__contains=request.POST['search[value]'])
order_string = ''
# Ordering
if request.POST['order[0][dir]'] == 'desc':
order_string += '-'
if request.POST['order[0][dir]'] == '3':
order_string += 'date_added'
elif request.POST['order[0][dir]'] == '2':
order_string += 'date_added'
else:
order_string += 'date_added'
comics = comics.order_by(order_string)
response_data['recordsFiltered'] = comics.count()
response_data['data'] = list()
for book in comics[start:end]:
status, created = ComicStatus.objects.get_or_create(comic=book,
user=request.user)
if created:
status.save()
response_data['data'].append({
'selector': urlsafe_base64_encode(book.selector.bytes),
'icon': icon,
'type': 'book',
'name': book.file_name,
'date': book.date_added.strftime('%d/%m/%y-%H:%M'),
'label': generate_label(book, status),
'url': '/comic/read/{0}/{1}/'.format(urlsafe_base64_encode(book.selector.bytes),
status.last_read_page)
})
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
) )
@login_required
@require_POST
def recent_comics_json(request):
start = int(request.POST["start"])
end = start + int(request.POST["length"])
icon = '<span class="glyphicon glyphicon-book"></span>'
comics = ComicBook.objects.all()
response_data = dict()
response_data["recordsTotal"] = comics.count()
if request.POST["search[value]"]:
comics = comics.filter(file_name__contains=request.POST["search[value]"])
order_string = ""
# Ordering
if request.POST["order[0][dir]"] == "desc":
order_string += "-"
if request.POST["order[0][dir]"] == "3":
order_string += "date_added"
elif request.POST["order[0][dir]"] == "2":
order_string += "date_added"
else:
order_string += "date_added"
comics = comics.order_by(order_string)
response_data["recordsFiltered"] = comics.count()
response_data["data"] = list()
for book in comics[start:end]:
status, created = ComicStatus.objects.get_or_create(comic=book, user=request.user)
if created:
status.save()
response_data["data"].append(
{
"selector": urlsafe_base64_encode(book.selector.bytes),
"icon": icon,
"type": "book",
"name": book.file_name,
"date": book.date_added.strftime("%d/%m/%y-%H:%M"),
"label": generate_label(book, status),
"url": "/comic/read/{0}/{1}/".format(
urlsafe_base64_encode(book.selector.bytes), status.last_read_page
),
}
)
return HttpResponse(json.dumps(response_data), content_type="application/json")
@login_required @login_required
@require_POST @require_POST
def comic_edit(request): def comic_edit(request):
if 'selected' not in request.POST: if "selected" not in request.POST:
return HttpResponse(status=200) return HttpResponse(status=200)
if request.POST['func'] == 'choose': if request.POST["func"] == "choose":
return HttpResponse(status=200) return HttpResponse(status=200)
selected = [uuid.UUID(bytes=urlsafe_base64_decode(item)) for item in request.POST.getlist('selected')] selected = [uuid.UUID(bytes=urlsafe_base64_decode(item)) for item in request.POST.getlist("selected")]
comics = ComicBook.objects.filter(selector__in=selected) comics = ComicBook.objects.filter(selector__in=selected)
with atomic(): with atomic():
for comic in comics: for comic in comics:
status, _ = ComicStatus.objects.get_or_create(comic=comic, status, _ = ComicStatus.objects.get_or_create(comic=comic, user=request.user)
user=request.user) if request.POST["func"] == "read":
if request.POST['func'] == 'read':
status.unread = False status.unread = False
status.finished = True status.finished = True
status.last_read_page = comic.page_count - 1 status.last_read_page = comic.page_count - 1
elif request.POST['func'] == 'unread': elif request.POST["func"] == "unread":
status.unread = True status.unread = True
status.finished = False status.finished = False
status.last_read_page = 0 status.last_read_page = 0
@@ -174,44 +178,37 @@ def account_page(request):
if request.POST: if request.POST:
form = AccountForm(request.POST) form = AccountForm(request.POST)
if form.is_valid(): if form.is_valid():
if form.cleaned_data['email'] != request.user.email: if form.cleaned_data["email"] != request.user.email:
request.user.email = form.cleaned_data['email'] request.user.email = form.cleaned_data["email"]
success_message.append('Email Updated.') success_message.append("Email Updated.")
if len(form.cleaned_data['password']) != 0: if len(form.cleaned_data["password"]) != 0:
request.user.set_password(form.cleaned_data['password']) request.user.set_password(form.cleaned_data["password"])
success_message.append('Password Updated.') success_message.append("Password Updated.")
request.user.save() request.user.save()
else: else:
form = AccountForm(initial={ form = AccountForm(initial={"username": request.user.username, "email": request.user.email})
'username': request.user.username, crumbs = [("Account", "/comic/account/")]
'email': request.user.email,
})
crumbs = [
('Account', '/comic/account/'),
]
context = { context = {
'form': form, "form": form,
'menu': Menu(request.user, 'Account'), "menu": Menu(request.user, "Account"),
'error_message': form.errors, "error_message": form.errors,
'success_message': '</br>'.join(success_message), "success_message": "</br>".join(success_message),
'breadcrumbs': generate_breadcrumbs_from_menu(crumbs), "breadcrumbs": generate_breadcrumbs_from_menu(crumbs),
'title': 'CBWebReader - Account', "title": "CBWebReader - Account",
} }
return render(request, 'comic/settings_page.html', context) return render(request, "comic/settings_page.html", context)
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
def users_page(request): def users_page(request):
users = User.objects.all() users = User.objects.all()
crumbs = [ crumbs = [("Users", "/comic/settings/users/")]
('Users', '/comic/settings/users/'),
]
context = { context = {
'users': users, "users": users,
'menu': Menu(request.user, 'Users'), "menu": Menu(request.user, "Users"),
'breadcrumbs': generate_breadcrumbs_from_menu(crumbs), "breadcrumbs": generate_breadcrumbs_from_menu(crumbs),
} }
return render(request, 'comic/users_page.html', context) return render(request, "comic/users_page.html", context)
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
@@ -221,89 +218,78 @@ def user_config_page(request, user_id):
if request.POST: if request.POST:
form = EditUserForm(request.POST) form = EditUserForm(request.POST)
if form.is_valid(): if form.is_valid():
if 'password' in form.cleaned_data: if "password" in form.cleaned_data:
if len(form.cleaned_data['password']) != 0: if len(form.cleaned_data["password"]) != 0:
user.set_password(form.cleaned_data['password']) user.set_password(form.cleaned_data["password"])
success_message.append('Password Updated.') success_message.append("Password Updated.")
if form.cleaned_data['email'] != user.email: if form.cleaned_data["email"] != user.email:
user.email = form.cleaned_data['email'] user.email = form.cleaned_data["email"]
success_message.append('Email Updated.</br>') success_message.append("Email Updated.</br>")
user.save() user.save()
else: else:
form = EditUserForm(initial=EditUserForm.get_initial_values(user)) form = EditUserForm(initial=EditUserForm.get_initial_values(user))
users = User.objects.all() users = User.objects.all()
crumbs = [ crumbs = [("Users", "/comic/settings/users/"), (user.username, "/comic/settings/users/" + str(user.id))]
('Users', '/comic/settings/users/'),
(user.username, '/comic/settings/users/' + str(user.id)),
]
context = { context = {
'form': form, "form": form,
'users': users, "users": users,
'menu': Menu(request.user, 'Users'), "menu": Menu(request.user, "Users"),
'error_message': form.errors, "error_message": form.errors,
'breadcrumbs': generate_breadcrumbs_from_menu(crumbs), "breadcrumbs": generate_breadcrumbs_from_menu(crumbs),
'success_message': '</br>'.join(success_message), "success_message": "</br>".join(success_message),
'title': 'CBWebReader - Edit User - ' + user.username, "title": "CBWebReader - Edit User - " + user.username,
} }
return render(request, 'comic/settings_page.html', context) return render(request, "comic/settings_page.html", context)
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
def user_add_page(request): def user_add_page(request):
success_message = '' success_message = ""
if request.POST: if request.POST:
form = AddUserForm(request.POST) form = AddUserForm(request.POST)
if form.is_valid(): if form.is_valid():
user = User( user = User(username=form.cleaned_data["username"], email=form.cleaned_data["email"])
username=form.cleaned_data['username'], user.set_password(form.cleaned_data["password"])
email=form.cleaned_data['email'],
)
user.set_password(form.cleaned_data['password'])
user.save() user.save()
UserMisc.objects.create(user=user) UserMisc.objects.create(user=user)
success_message = 'User {} created.'.format(user.username) success_message = "User {} created.".format(user.username)
else: else:
form = AddUserForm() form = AddUserForm()
crumbs = [ crumbs = [("Users", "/comic/settings/users/"), ("Add", "/comic/settings/users/add/")]
('Users', '/comic/settings/users/'),
('Add', '/comic/settings/users/add/'),
]
context = { context = {
'form': form, "form": form,
'menu': Menu(request.user, 'Users'), "menu": Menu(request.user, "Users"),
'breadcrumbs': generate_breadcrumbs_from_menu(crumbs), "breadcrumbs": generate_breadcrumbs_from_menu(crumbs),
'error_message': form.errors, "error_message": form.errors,
'success_message': success_message, "success_message": success_message,
'title': 'CBWebReader - Add User', "title": "CBWebReader - Add User",
} }
return render(request, 'comic/settings_page.html', context) return render(request, "comic/settings_page.html", context)
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
def settings_page(request): def settings_page(request):
success_message = [] success_message = []
crumbs = [ crumbs = [("Settings", "/comic/settings/")]
('Settings', '/comic/settings/'),
]
if request.POST: if request.POST:
form = SettingsForm(request.POST) form = SettingsForm(request.POST)
if form.is_valid(): if form.is_valid():
base_dir = Setting.objects.get(name='BASE_DIR') base_dir = Setting.objects.get(name="BASE_DIR")
base_dir.value = form.cleaned_data['base_dir'] base_dir.value = form.cleaned_data["base_dir"]
base_dir.save() base_dir.save()
success_message.append('Settings updated.') success_message.append("Settings updated.")
form = SettingsForm(initial=SettingsForm.get_initial_values()) form = SettingsForm(initial=SettingsForm.get_initial_values())
context = { context = {
'error_message': form.errors, "error_message": form.errors,
'success_message': '</br>'.join(success_message), "success_message": "</br>".join(success_message),
'form': form, "form": form,
'menu': Menu(request.user, 'Settings'), "menu": Menu(request.user, "Settings"),
'title': 'CBWebReader - Settings', "title": "CBWebReader - Settings",
'breadcrumbs': generate_breadcrumbs_from_menu(crumbs), "breadcrumbs": generate_breadcrumbs_from_menu(crumbs),
} }
return render(request, 'comic/settings_page.html', context) return render(request, "comic/settings_page.html", context)
@login_required @login_required
@@ -317,21 +303,21 @@ def read_comic(request, comic_selector, page):
status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user) status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user)
status.unread = False status.unread = False
status.last_read_page = page status.last_read_page = page
if ComicPage.objects.filter(Comic=book).aggregate(Max('index'))['index__max'] == status.last_read_page: if ComicPage.objects.filter(Comic=book).aggregate(Max("index"))["index__max"] == status.last_read_page:
status.finished = True status.finished = True
else: else:
status.finished = False status.finished = False
status.save() status.save()
title = 'CBWebReader - ' + book.file_name + ' - Page: ' + str(page) title = "CBWebReader - " + book.file_name + " - Page: " + str(page)
context = { context = {
'book': book, "book": book,
'orig_file_name': book.page_name(page), "orig_file_name": book.page_name(page),
'nav': book.nav(page, request.user), "nav": book.nav(page, request.user),
'breadcrumbs': breadcrumbs, "breadcrumbs": breadcrumbs,
'menu': Menu(request.user), "menu": Menu(request.user),
'title': title, "title": title,
} }
return render(request, 'comic/read_comic.html', context) return render(request, "comic/read_comic.html", context)
@login_required @login_required
@@ -344,34 +330,29 @@ def get_image(_, comic_selector, page):
def initial_setup(request): def initial_setup(request):
if User.objects.all().exists(): if User.objects.all().exists():
return redirect('/comic/') return redirect("/comic/")
if request.POST: if request.POST:
form = InitialSetupForm(request.POST) form = InitialSetupForm(request.POST)
if form.is_valid(): if form.is_valid():
user = User( user = User(
username=form.cleaned_data['username'], username=form.cleaned_data["username"],
email=form.cleaned_data['email'], email=form.cleaned_data["email"],
is_staff=True, is_staff=True,
is_superuser=True, is_superuser=True,
) )
user.set_password(form.cleaned_data['password']) user.set_password(form.cleaned_data["password"])
user.save() user.save()
base_dir, _ = Setting.objects.get_or_create(name='BASE_DIR') base_dir, _ = Setting.objects.get_or_create(name="BASE_DIR")
base_dir.value = form.cleaned_data['base_dir'] base_dir.value = form.cleaned_data["base_dir"]
base_dir.save() base_dir.save()
user = authenticate(username=form.cleaned_data['username'], user = authenticate(username=form.cleaned_data["username"], password=form.cleaned_data["password"])
password=form.cleaned_data['password'])
login(request, user) login(request, user)
return redirect('/comic/') return redirect("/comic/")
else: else:
form = InitialSetupForm() form = InitialSetupForm()
context = { context = {"form": form, "title": "CBWebReader - Setup", "error_message": form.errors}
'form': form, return render(request, "comic/settings_page.html", context)
'title': 'CBWebReader - Setup',
'error_message': form.errors,
}
return render(request, 'comic/settings_page.html', context)
def comic_redirect(_): def comic_redirect(_):
return redirect('/comic/') return redirect("/comic/")

View File

@@ -6,26 +6,19 @@ from snowpenguin.django.recaptcha2.widgets import ReCaptchaWidget
class LoginForm(forms.Form): class LoginForm(forms.Form):
username = forms.CharField(max_length=50, username = forms.CharField(
label='', max_length=50,
widget=forms.TextInput( label="",
attrs={ widget=forms.TextInput(
'class': 'form-control', attrs={"class": "form-control", "placeholder": "Username", "autofocus": True, "required": True}
'placeholder': 'Username', ),
'autofocus': True, )
'required': True, password = forms.CharField(
} label="Password",
)) widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "Username", "required": True}),
password = forms.CharField(label='Password', )
widget=forms.PasswordInput(
attrs={
'class': 'form-control',
'placeholder': 'Username',
'required': True,
}
))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs) super(LoginForm, self).__init__(*args, **kwargs)
if settings.CBREADER_USE_RECAPTCHA if hasattr(settings, 'CBREADER_USE_RECAPTCHA') else False: if settings.CBREADER_USE_RECAPTCHA if hasattr(settings, "CBREADER_USE_RECAPTCHA") else False:
self.fields['captcha'] = ReCaptchaField(widget=ReCaptchaWidget()) self.fields["captcha"] = ReCaptchaField(widget=ReCaptchaWidget())

View File

@@ -9,45 +9,28 @@ def comic_login(request):
if request.POST: if request.POST:
form = LoginForm(request.POST) form = LoginForm(request.POST)
if form.is_valid(): if form.is_valid():
user = authenticate(username=form.cleaned_data['username'], user = authenticate(username=form.cleaned_data["username"], password=form.cleaned_data["password"])
password=form.cleaned_data['password'])
if user is not None: if user is not None:
if user.is_active: if user.is_active:
login(request, user) login(request, user)
if 'next' in request.GET: if "next" in request.GET:
return redirect(request.GET['next']) return redirect(request.GET["next"])
else: else:
return redirect('/comic/') return redirect("/comic/")
else: else:
return render(request, return render(request, "comic_auth/login.html", {"error": True})
'comic_auth/login.html',
{
'error': True,
})
else: else:
return render(request, return render(request, "comic_auth/login.html", {"error": True, "form": form})
'comic_auth/login.html',
{
'error': True,
'form': form
})
else: else:
return render(request, return render(request, "comic_auth/login.html", {"error": True, "form": form})
'comic_auth/login.html',
{
'error': True,
'form': form
})
else: else:
if not User.objects.all().exists(): if not User.objects.all().exists():
return redirect('/setup/') return redirect("/setup/")
form = LoginForm() form = LoginForm()
context = { context = {"form": form}
'form': form return render(request, "comic_auth/login.html", context)
}
return render(request, 'comic_auth/login.html', context)
def comic_logout(request): def comic_logout(request):
logout(request) logout(request)
return redirect('/login/') return redirect("/login/")

View File

@@ -14,11 +14,11 @@ services:
- database - database
ports: ports:
- "8000:8000" - "8000:8000"
volumes: # volumes:
- ./cbreader:/src/cbreader # - ./cbreader:/src/cbreader
- ./comic:/src/comic # - ./comic:/src/comic
- ./comic_auth:/src/comic_auth # - ./comic_auth:/src/comic_auth
- ${COMIC_BOOK_VOLUME}:/data # - ${COMIC_BOOK_VOLUME}:/data
command: python manage.py runserver 0.0.0.0:8000 command: python manage.py runserver 0.0.0.0:8000
database: database:

2
pyproject.toml Normal file
View File

@@ -0,0 +1,2 @@
[tool.black]
line_length = 119

View File

@@ -1,12 +1,13 @@
from distutils.core import setup from distutils.core import setup
setup( setup(
name='cbwebreader', name="cbwebreader",
version='', version="",
packages=['comic', 'comic.migrations', 'cbreader', 'comic_auth', 'comic_auth.migrations'], packages=["comic", "comic.migrations", "cbreader", "comic_auth", "comic_auth.migrations"],
url='https://github.com/ajurna/cbwebreader', url="https://github.com/ajurna/cbwebreader",
license='http://creativecommons.org/licenses/by-sa/4.0/', license="http://creativecommons.org/licenses/by-sa/4.0/",
author='Ajurna', author="Ajurna",
author_email='ajurna@gmail.com', author_email="ajurna@gmail.com",
description='Comic Book Web Reader', requires=['django-recaptcha', 'django', 'ujson'] description="Comic Book Web Reader",
requires=["django-recaptcha", "django", "ujson"],
) )