mirror of
https://github.com/ajurna/cbwebreader.git
synced 2025-12-06 06:17:17 +00:00
Merge pull request #5 from apoclyps/black-formatting
[ISSUE-4] Applying black formatting
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
|
||||||
118
cbreader/tests/test_settings.py
Normal file
118
cbreader/tests/test_settings.py
Normal 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"
|
||||||
@@ -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'))
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
207
comic/forms.py
207
comic/forms.py
@@ -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(
|
||||||
|
help_text="Username",
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}),
|
||||||
attrs={
|
)
|
||||||
'class': 'form-control disabled',
|
email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
|
||||||
'readonly': True,
|
password = forms.CharField(
|
||||||
}
|
help_text="New Password", required=False, widget=forms.PasswordInput(attrs={"class": "form-control"})
|
||||||
))
|
)
|
||||||
email = forms.CharField(help_text='Email Address',
|
password_confirm = forms.CharField(
|
||||||
widget=forms.TextInput(
|
help_text="New Password Confirmation",
|
||||||
attrs={
|
|
||||||
'class': 'form-control'
|
|
||||||
}
|
|
||||||
))
|
|
||||||
password = forms.CharField(help_text='New Password',
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.PasswordInput(
|
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||||
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(
|
||||||
|
help_text="Username",
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}),
|
||||||
attrs={
|
)
|
||||||
'class': 'form-control disabled',
|
email = forms.CharField(help_text="Email Address", widget=forms.TextInput(attrs={"class": "form-control"}))
|
||||||
'readonly': True,
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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'),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
380
comic/rarfile.py
380
comic/rarfile.py
@@ -74,10 +74,10 @@ For more details, refer to source.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '2.7'
|
__version__ = "2.7"
|
||||||
|
|
||||||
# export only interesting items
|
# export only interesting items
|
||||||
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
|
__all__ = ["is_rarfile", "RarInfo", "RarFile", "RarExtFile"]
|
||||||
|
|
||||||
##
|
##
|
||||||
## Imports and compat - support both Python 2.x and 3.x
|
## Imports and compat - support both Python 2.x and 3.x
|
||||||
@@ -93,6 +93,7 @@ from datetime import datetime
|
|||||||
# only needed for encryped headers
|
# only needed for encryped headers
|
||||||
try:
|
try:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -108,6 +109,8 @@ if sys.hexversion < 0x3000000:
|
|||||||
# py2.6 has broken bytes()
|
# py2.6 has broken bytes()
|
||||||
def bytes(s, enc):
|
def bytes(s, enc):
|
||||||
return str(s)
|
return str(s)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
unicode = str
|
unicode = str
|
||||||
|
|
||||||
@@ -116,15 +119,18 @@ try:
|
|||||||
bytearray
|
bytearray
|
||||||
except NameError:
|
except NameError:
|
||||||
import array
|
import array
|
||||||
|
|
||||||
class bytearray:
|
class bytearray:
|
||||||
def __init__(self, val = ''):
|
def __init__(self, val=""):
|
||||||
self.arr = array.array('B', val)
|
self.arr = array.array("B", val)
|
||||||
self.append = self.arr.append
|
self.append = self.arr.append
|
||||||
self.__getitem__ = self.arr.__getitem__
|
self.__getitem__ = self.arr.__getitem__
|
||||||
self.__len__ = self.arr.__len__
|
self.__len__ = self.arr.__len__
|
||||||
|
|
||||||
def decode(self, *args):
|
def decode(self, *args):
|
||||||
return self.arr.tostring().decode(*args)
|
return self.arr.tostring().decode(*args)
|
||||||
|
|
||||||
|
|
||||||
# Optimized .readinto() requires memoryview
|
# Optimized .readinto() requires memoryview
|
||||||
try:
|
try:
|
||||||
memoryview
|
memoryview
|
||||||
@@ -136,21 +142,27 @@ except NameError:
|
|||||||
try:
|
try:
|
||||||
from struct import Struct
|
from struct import Struct
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
class Struct:
|
class Struct:
|
||||||
def __init__(self, fmt):
|
def __init__(self, fmt):
|
||||||
self.format = fmt
|
self.format = fmt
|
||||||
self.size = struct.calcsize(fmt)
|
self.size = struct.calcsize(fmt)
|
||||||
|
|
||||||
def unpack(self, buf):
|
def unpack(self, buf):
|
||||||
return unpack(self.format, buf)
|
return unpack(self.format, buf)
|
||||||
|
|
||||||
def unpack_from(self, buf, ofs=0):
|
def unpack_from(self, buf, ofs=0):
|
||||||
return unpack(self.format, buf[ofs : ofs + self.size])
|
return unpack(self.format, buf[ofs : ofs + self.size])
|
||||||
|
|
||||||
def pack(self, *args):
|
def pack(self, *args):
|
||||||
return pack(self.format, *args)
|
return pack(self.format, *args)
|
||||||
|
|
||||||
|
|
||||||
# file object superclass
|
# file object superclass
|
||||||
try:
|
try:
|
||||||
from io import RawIOBase
|
from io import RawIOBase
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
class RawIOBase(object):
|
class RawIOBase(object):
|
||||||
def close(self):
|
def close(self):
|
||||||
pass
|
pass
|
||||||
@@ -164,19 +176,19 @@ except ImportError:
|
|||||||
DEFAULT_CHARSET = "windows-1252"
|
DEFAULT_CHARSET = "windows-1252"
|
||||||
|
|
||||||
#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
|
#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
|
||||||
TRY_ENCODINGS = ('utf8', 'utf-16le')
|
TRY_ENCODINGS = ("utf8", "utf-16le")
|
||||||
|
|
||||||
#: 'unrar', 'rar' or full path to either one
|
#: 'unrar', 'rar' or full path to either one
|
||||||
UNRAR_TOOL = "unrar"
|
UNRAR_TOOL = "unrar"
|
||||||
|
|
||||||
#: Command line args to use for opening file for reading.
|
#: Command line args to use for opening file for reading.
|
||||||
OPEN_ARGS = ('p', '-inul')
|
OPEN_ARGS = ("p", "-inul")
|
||||||
|
|
||||||
#: Command line args to use for extracting file to disk.
|
#: Command line args to use for extracting file to disk.
|
||||||
EXTRACT_ARGS = ('x', '-y', '-idq')
|
EXTRACT_ARGS = ("x", "-y", "-idq")
|
||||||
|
|
||||||
#: args for testrar()
|
#: args for testrar()
|
||||||
TEST_ARGS = ('t', '-idq')
|
TEST_ARGS = ("t", "-idq")
|
||||||
|
|
||||||
#
|
#
|
||||||
# Allow use of tool that is not compatible with unrar.
|
# Allow use of tool that is not compatible with unrar.
|
||||||
@@ -189,11 +201,11 @@ TEST_ARGS = ('t', '-idq')
|
|||||||
# - Does not support password-protected archives.
|
# - Does not support password-protected archives.
|
||||||
#
|
#
|
||||||
|
|
||||||
ALT_TOOL = 'bsdtar'
|
ALT_TOOL = "bsdtar"
|
||||||
ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f')
|
ALT_OPEN_ARGS = ("-x", "--to-stdout", "-f")
|
||||||
ALT_EXTRACT_ARGS = ('-x', '-f')
|
ALT_EXTRACT_ARGS = ("-x", "-f")
|
||||||
ALT_TEST_ARGS = ('-t', '-f')
|
ALT_TEST_ARGS = ("-t", "-f")
|
||||||
ALT_CHECK_ARGS = ('--help',)
|
ALT_CHECK_ARGS = ("--help",)
|
||||||
|
|
||||||
#: whether to speed up decompression by using tmp archive
|
#: whether to speed up decompression by using tmp archive
|
||||||
USE_EXTRACT_HACK = 1
|
USE_EXTRACT_HACK = 1
|
||||||
@@ -212,7 +224,7 @@ USE_DATETIME = 0
|
|||||||
|
|
||||||
#: Separator for path name components. RAR internally uses '\\'.
|
#: Separator for path name components. RAR internally uses '\\'.
|
||||||
#: Use '/' to be similar with zipfile.
|
#: Use '/' to be similar with zipfile.
|
||||||
PATH_SEP = '\\'
|
PATH_SEP = "\\"
|
||||||
|
|
||||||
##
|
##
|
||||||
## rar constants
|
## rar constants
|
||||||
@@ -227,8 +239,8 @@ RAR_BLOCK_OLD_EXTRA = 0x76 # v
|
|||||||
RAR_BLOCK_OLD_SUB = 0x77 # w
|
RAR_BLOCK_OLD_SUB = 0x77 # w
|
||||||
RAR_BLOCK_OLD_RECOVERY = 0x78 # x
|
RAR_BLOCK_OLD_RECOVERY = 0x78 # x
|
||||||
RAR_BLOCK_OLD_AUTH = 0x79 # y
|
RAR_BLOCK_OLD_AUTH = 0x79 # y
|
||||||
RAR_BLOCK_SUB = 0x7a # z
|
RAR_BLOCK_SUB = 0x7A # z
|
||||||
RAR_BLOCK_ENDARC = 0x7b # {
|
RAR_BLOCK_ENDARC = 0x7B # {
|
||||||
|
|
||||||
# flags for RAR_BLOCK_MAIN
|
# flags for RAR_BLOCK_MAIN
|
||||||
RAR_MAIN_VOLUME = 0x0001
|
RAR_MAIN_VOLUME = 0x0001
|
||||||
@@ -248,15 +260,15 @@ RAR_FILE_SPLIT_AFTER = 0x0002
|
|||||||
RAR_FILE_PASSWORD = 0x0004
|
RAR_FILE_PASSWORD = 0x0004
|
||||||
RAR_FILE_COMMENT = 0x0008
|
RAR_FILE_COMMENT = 0x0008
|
||||||
RAR_FILE_SOLID = 0x0010
|
RAR_FILE_SOLID = 0x0010
|
||||||
RAR_FILE_DICTMASK = 0x00e0
|
RAR_FILE_DICTMASK = 0x00E0
|
||||||
RAR_FILE_DICT64 = 0x0000
|
RAR_FILE_DICT64 = 0x0000
|
||||||
RAR_FILE_DICT128 = 0x0020
|
RAR_FILE_DICT128 = 0x0020
|
||||||
RAR_FILE_DICT256 = 0x0040
|
RAR_FILE_DICT256 = 0x0040
|
||||||
RAR_FILE_DICT512 = 0x0060
|
RAR_FILE_DICT512 = 0x0060
|
||||||
RAR_FILE_DICT1024 = 0x0080
|
RAR_FILE_DICT1024 = 0x0080
|
||||||
RAR_FILE_DICT2048 = 0x00a0
|
RAR_FILE_DICT2048 = 0x00A0
|
||||||
RAR_FILE_DICT4096 = 0x00c0
|
RAR_FILE_DICT4096 = 0x00C0
|
||||||
RAR_FILE_DIRECTORY = 0x00e0
|
RAR_FILE_DIRECTORY = 0x00E0
|
||||||
RAR_FILE_LARGE = 0x0100
|
RAR_FILE_LARGE = 0x0100
|
||||||
RAR_FILE_UNICODE = 0x0200
|
RAR_FILE_UNICODE = 0x0200
|
||||||
RAR_FILE_SALT = 0x0400
|
RAR_FILE_SALT = 0x0400
|
||||||
@@ -294,71 +306,116 @@ RAR_M5 = 0x35
|
|||||||
## internal constants
|
## internal constants
|
||||||
##
|
##
|
||||||
|
|
||||||
RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii')
|
RAR_ID = bytes("Rar!\x1a\x07\x00", "ascii")
|
||||||
ZERO = bytes("\0", 'ascii')
|
ZERO = bytes("\0", "ascii")
|
||||||
EMPTY = bytes("", 'ascii')
|
EMPTY = bytes("", "ascii")
|
||||||
|
|
||||||
S_BLK_HDR = Struct('<HBHH')
|
S_BLK_HDR = Struct("<HBHH")
|
||||||
S_FILE_HDR = Struct('<LLBLLBBHL')
|
S_FILE_HDR = Struct("<LLBLLBBHL")
|
||||||
S_LONG = Struct('<L')
|
S_LONG = Struct("<L")
|
||||||
S_SHORT = Struct('<H')
|
S_SHORT = Struct("<H")
|
||||||
S_BYTE = Struct('<B')
|
S_BYTE = Struct("<B")
|
||||||
S_COMMENT_HDR = Struct('<HBBH')
|
S_COMMENT_HDR = Struct("<HBBH")
|
||||||
|
|
||||||
##
|
##
|
||||||
## Public interface
|
## Public interface
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
"""Base class for rarfile errors."""
|
"""Base class for rarfile errors."""
|
||||||
|
|
||||||
|
|
||||||
class BadRarFile(Error):
|
class BadRarFile(Error):
|
||||||
"""Incorrect data in archive."""
|
"""Incorrect data in archive."""
|
||||||
|
|
||||||
|
|
||||||
class NotRarFile(Error):
|
class NotRarFile(Error):
|
||||||
"""The file is not RAR archive."""
|
"""The file is not RAR archive."""
|
||||||
|
|
||||||
|
|
||||||
class BadRarName(Error):
|
class BadRarName(Error):
|
||||||
"""Cannot guess multipart name components."""
|
"""Cannot guess multipart name components."""
|
||||||
|
|
||||||
|
|
||||||
class NoRarEntry(Error):
|
class NoRarEntry(Error):
|
||||||
"""File not found in RAR"""
|
"""File not found in RAR"""
|
||||||
|
|
||||||
|
|
||||||
class PasswordRequired(Error):
|
class PasswordRequired(Error):
|
||||||
"""File requires password"""
|
"""File requires password"""
|
||||||
|
|
||||||
|
|
||||||
class NeedFirstVolume(Error):
|
class NeedFirstVolume(Error):
|
||||||
"""Need to start from first volume."""
|
"""Need to start from first volume."""
|
||||||
|
|
||||||
|
|
||||||
class NoCrypto(Error):
|
class NoCrypto(Error):
|
||||||
"""Cannot parse encrypted headers - no crypto available."""
|
"""Cannot parse encrypted headers - no crypto available."""
|
||||||
|
|
||||||
|
|
||||||
class RarExecError(Error):
|
class RarExecError(Error):
|
||||||
"""Problem reported by unrar/rar."""
|
"""Problem reported by unrar/rar."""
|
||||||
|
|
||||||
|
|
||||||
class RarWarning(RarExecError):
|
class RarWarning(RarExecError):
|
||||||
"""Non-fatal error"""
|
"""Non-fatal error"""
|
||||||
|
|
||||||
|
|
||||||
class RarFatalError(RarExecError):
|
class RarFatalError(RarExecError):
|
||||||
"""Fatal error"""
|
"""Fatal error"""
|
||||||
|
|
||||||
|
|
||||||
class RarCRCError(RarExecError):
|
class RarCRCError(RarExecError):
|
||||||
"""CRC error during unpacking"""
|
"""CRC error during unpacking"""
|
||||||
|
|
||||||
|
|
||||||
class RarLockedArchiveError(RarExecError):
|
class RarLockedArchiveError(RarExecError):
|
||||||
"""Must not modify locked archive"""
|
"""Must not modify locked archive"""
|
||||||
|
|
||||||
|
|
||||||
class RarWriteError(RarExecError):
|
class RarWriteError(RarExecError):
|
||||||
"""Write error"""
|
"""Write error"""
|
||||||
|
|
||||||
|
|
||||||
class RarOpenError(RarExecError):
|
class RarOpenError(RarExecError):
|
||||||
"""Open error"""
|
"""Open error"""
|
||||||
|
|
||||||
|
|
||||||
class RarUserError(RarExecError):
|
class RarUserError(RarExecError):
|
||||||
"""User error"""
|
"""User error"""
|
||||||
|
|
||||||
|
|
||||||
class RarMemoryError(RarExecError):
|
class RarMemoryError(RarExecError):
|
||||||
"""Memory error"""
|
"""Memory error"""
|
||||||
|
|
||||||
|
|
||||||
class RarCreateError(RarExecError):
|
class RarCreateError(RarExecError):
|
||||||
"""Create error"""
|
"""Create error"""
|
||||||
|
|
||||||
|
|
||||||
class RarNoFilesError(RarExecError):
|
class RarNoFilesError(RarExecError):
|
||||||
"""No files that match pattern were found"""
|
"""No files that match pattern were found"""
|
||||||
|
|
||||||
|
|
||||||
class RarUserBreak(RarExecError):
|
class RarUserBreak(RarExecError):
|
||||||
"""User stop"""
|
"""User stop"""
|
||||||
|
|
||||||
|
|
||||||
class RarUnknownError(RarExecError):
|
class RarUnknownError(RarExecError):
|
||||||
"""Unknown exit code"""
|
"""Unknown exit code"""
|
||||||
|
|
||||||
|
|
||||||
class RarSignalExit(RarExecError):
|
class RarSignalExit(RarExecError):
|
||||||
"""Unrar exited with signal"""
|
"""Unrar exited with signal"""
|
||||||
|
|
||||||
|
|
||||||
class RarCannotExec(RarExecError):
|
class RarCannotExec(RarExecError):
|
||||||
"""Executable not found."""
|
"""Executable not found."""
|
||||||
|
|
||||||
|
|
||||||
def is_rarfile(xfile):
|
def is_rarfile(xfile):
|
||||||
'''Check quickly whether file is rar archive.'''
|
"""Check quickly whether file is rar archive."""
|
||||||
fd = XFile(xfile)
|
fd = XFile(xfile)
|
||||||
buf = fd.read(len(RAR_ID))
|
buf = fd.read(len(RAR_ID))
|
||||||
fd.close()
|
fd.close()
|
||||||
@@ -366,7 +423,7 @@ def is_rarfile(xfile):
|
|||||||
|
|
||||||
|
|
||||||
class RarInfo(object):
|
class RarInfo(object):
|
||||||
r'''An entry in rar archive.
|
r"""An entry in rar archive.
|
||||||
|
|
||||||
:mod:`zipfile`-compatible fields:
|
:mod:`zipfile`-compatible fields:
|
||||||
|
|
||||||
@@ -417,49 +474,46 @@ class RarInfo(object):
|
|||||||
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
|
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
|
||||||
flags
|
flags
|
||||||
For files, RAR_FILE_* bits.
|
For files, RAR_FILE_* bits.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
# zipfile-compatible fields
|
# zipfile-compatible fields
|
||||||
'filename',
|
"filename",
|
||||||
'file_size',
|
"file_size",
|
||||||
'compress_size',
|
"compress_size",
|
||||||
'date_time',
|
"date_time",
|
||||||
'comment',
|
"comment",
|
||||||
'CRC',
|
"CRC",
|
||||||
'volume',
|
"volume",
|
||||||
'orig_filename', # bytes in unknown encoding
|
"orig_filename", # bytes in unknown encoding
|
||||||
|
|
||||||
# rar-specific fields
|
# rar-specific fields
|
||||||
'extract_version',
|
"extract_version",
|
||||||
'compress_type',
|
"compress_type",
|
||||||
'host_os',
|
"host_os",
|
||||||
'mode',
|
"mode",
|
||||||
'type',
|
"type",
|
||||||
'flags',
|
"flags",
|
||||||
|
|
||||||
# optional extended time fields
|
# optional extended time fields
|
||||||
# tuple where the sec is float, or datetime().
|
# tuple where the sec is float, or datetime().
|
||||||
'mtime', # same as .date_time
|
"mtime", # same as .date_time
|
||||||
'ctime',
|
"ctime",
|
||||||
'atime',
|
"atime",
|
||||||
'arctime',
|
"arctime",
|
||||||
|
|
||||||
# RAR internals
|
# RAR internals
|
||||||
'name_size',
|
"name_size",
|
||||||
'header_size',
|
"header_size",
|
||||||
'header_crc',
|
"header_crc",
|
||||||
'file_offset',
|
"file_offset",
|
||||||
'add_size',
|
"add_size",
|
||||||
'header_data',
|
"header_data",
|
||||||
'header_base',
|
"header_base",
|
||||||
'header_offset',
|
"header_offset",
|
||||||
'salt',
|
"salt",
|
||||||
'volume_file',
|
"volume_file",
|
||||||
)
|
)
|
||||||
|
|
||||||
def isdir(self):
|
def isdir(self):
|
||||||
'''Returns True if the entry is a directory.'''
|
"""Returns True if the entry is a directory."""
|
||||||
if self.type == RAR_BLOCK_FILE:
|
if self.type == RAR_BLOCK_FILE:
|
||||||
return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY
|
return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY
|
||||||
return False
|
return False
|
||||||
@@ -469,15 +523,14 @@ class RarInfo(object):
|
|||||||
|
|
||||||
|
|
||||||
class RarFile(object):
|
class RarFile(object):
|
||||||
'''Parse RAR structure, provide access to files in archive.
|
"""Parse RAR structure, provide access to files in archive.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
#: Archive comment. Byte string or None. Use :data:`UNICODE_COMMENTS`
|
#: Archive comment. Byte string or None. Use :data:`UNICODE_COMMENTS`
|
||||||
#: to get automatic decoding to unicode.
|
#: to get automatic decoding to unicode.
|
||||||
comment = None
|
comment = None
|
||||||
|
|
||||||
def __init__(self, rarfile, mode="r", charset=None, info_callback=None,
|
def __init__(self, rarfile, mode="r", charset=None, info_callback=None, crc_check=True, errors="stop"):
|
||||||
crc_check = True, errors = "stop"):
|
|
||||||
"""Open and parse a RAR archive.
|
"""Open and parse a RAR archive.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
@@ -529,39 +582,39 @@ class RarFile(object):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def setpassword(self, password):
|
def setpassword(self, password):
|
||||||
'''Sets the password to use when extracting.'''
|
"""Sets the password to use when extracting."""
|
||||||
self._password = password
|
self._password = password
|
||||||
if not self._main:
|
if not self._main:
|
||||||
self._parse()
|
self._parse()
|
||||||
|
|
||||||
def needs_password(self):
|
def needs_password(self):
|
||||||
'''Returns True if any archive entries require password for extraction.'''
|
"""Returns True if any archive entries require password for extraction."""
|
||||||
return self._needs_password
|
return self._needs_password
|
||||||
|
|
||||||
def namelist(self):
|
def namelist(self):
|
||||||
'''Return list of filenames in archive.'''
|
"""Return list of filenames in archive."""
|
||||||
return [f.filename for f in self._info_list]
|
return [f.filename for f in self._info_list]
|
||||||
|
|
||||||
def infolist(self):
|
def infolist(self):
|
||||||
'''Return RarInfo objects for all files/directories in archive.'''
|
"""Return RarInfo objects for all files/directories in archive."""
|
||||||
return self._info_list
|
return self._info_list
|
||||||
|
|
||||||
def volumelist(self):
|
def volumelist(self):
|
||||||
'''Returns filenames of archive volumes.
|
"""Returns filenames of archive volumes.
|
||||||
|
|
||||||
In case of single-volume archive, the list contains
|
In case of single-volume archive, the list contains
|
||||||
just the name of main archive file.
|
just the name of main archive file.
|
||||||
'''
|
"""
|
||||||
return self._vol_list
|
return self._vol_list
|
||||||
|
|
||||||
def getinfo(self, fname):
|
def getinfo(self, fname):
|
||||||
'''Return RarInfo for file.'''
|
"""Return RarInfo for file."""
|
||||||
|
|
||||||
if isinstance(fname, RarInfo):
|
if isinstance(fname, RarInfo):
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
# accept both ways here
|
# accept both ways here
|
||||||
if PATH_SEP == '/':
|
if PATH_SEP == "/":
|
||||||
fname2 = fname.replace("\\", "/")
|
fname2 = fname.replace("\\", "/")
|
||||||
else:
|
else:
|
||||||
fname2 = fname.replace("/", "\\")
|
fname2 = fname.replace("/", "\\")
|
||||||
@@ -574,8 +627,8 @@ class RarFile(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoRarEntry("No such file: " + fname)
|
raise NoRarEntry("No such file: " + fname)
|
||||||
|
|
||||||
def open(self, fname, mode = 'r', psw = None):
|
def open(self, fname, mode="r", psw=None):
|
||||||
'''Returns file-like object (:class:`RarExtFile`),
|
"""Returns file-like object (:class:`RarExtFile`),
|
||||||
from where the data can be read.
|
from where the data can be read.
|
||||||
|
|
||||||
The object implements :class:`io.RawIOBase` interface, so it can
|
The object implements :class:`io.RawIOBase` interface, so it can
|
||||||
@@ -597,9 +650,9 @@ class RarFile(object):
|
|||||||
must be 'r'
|
must be 'r'
|
||||||
psw
|
psw
|
||||||
password to use for extracting.
|
password to use for extracting.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if mode != 'r':
|
if mode != "r":
|
||||||
raise NotImplementedError("RarFile.open() supports only mode=r")
|
raise NotImplementedError("RarFile.open() supports only mode=r")
|
||||||
|
|
||||||
# entry lookup
|
# entry lookup
|
||||||
@@ -654,7 +707,7 @@ class RarFile(object):
|
|||||||
password to use for extracting.
|
password to use for extracting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f = self.open(fname, 'r', psw)
|
f = self.open(fname, "r", psw)
|
||||||
try:
|
try:
|
||||||
return f.read()
|
return f.read()
|
||||||
finally:
|
finally:
|
||||||
@@ -752,7 +805,7 @@ class RarFile(object):
|
|||||||
old.compress_size += item.compress_size
|
old.compress_size += item.compress_size
|
||||||
|
|
||||||
# parse new-style comment
|
# parse new-style comment
|
||||||
if item.type == RAR_BLOCK_SUB and item.filename == 'CMT':
|
if item.type == RAR_BLOCK_SUB and item.filename == "CMT":
|
||||||
if not NEED_COMMENTS:
|
if not NEED_COMMENTS:
|
||||||
pass
|
pass
|
||||||
elif item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
|
elif item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
|
||||||
@@ -849,9 +902,10 @@ class RarFile(object):
|
|||||||
|
|
||||||
# AES encrypted headers
|
# AES encrypted headers
|
||||||
_last_aes_key = (None, None, None) # (salt, key, iv)
|
_last_aes_key = (None, None, None) # (salt, key, iv)
|
||||||
|
|
||||||
def _decrypt_header(self, fd):
|
def _decrypt_header(self, fd):
|
||||||
if not _have_crypto:
|
if not _have_crypto:
|
||||||
raise NoCrypto('Cannot parse encrypted headers - no crypto')
|
raise NoCrypto("Cannot parse encrypted headers - no crypto")
|
||||||
salt = fd.read(8)
|
salt = fd.read(8)
|
||||||
if self._last_aes_key[0] == salt:
|
if self._last_aes_key[0] == salt:
|
||||||
key, iv = self._last_aes_key[1:]
|
key, iv = self._last_aes_key[1:]
|
||||||
@@ -872,7 +926,7 @@ class RarFile(object):
|
|||||||
# now read actual header
|
# now read actual header
|
||||||
return self._parse_block_header(fd)
|
return self._parse_block_header(fd)
|
||||||
except struct.error:
|
except struct.error:
|
||||||
self._set_error('Broken header in RAR file')
|
self._set_error("Broken header in RAR file")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# common header
|
# common header
|
||||||
@@ -899,7 +953,7 @@ class RarFile(object):
|
|||||||
|
|
||||||
# unexpected EOF?
|
# unexpected EOF?
|
||||||
if len(h.header_data) != h.header_size:
|
if len(h.header_data) != h.header_size:
|
||||||
self._set_error('Unexpected EOF when reading header')
|
self._set_error("Unexpected EOF when reading header")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# block has data assiciated with it?
|
# block has data assiciated with it?
|
||||||
@@ -943,8 +997,9 @@ class RarFile(object):
|
|||||||
return h
|
return h
|
||||||
|
|
||||||
# header parsing failed.
|
# header parsing failed.
|
||||||
self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)',
|
self._set_error(
|
||||||
h.type, h.header_crc, calc_crc, len(crcdat))
|
"Header CRC error (%02x): exp=%x got=%x (xlen = %d)", h.type, h.header_crc, calc_crc, len(crcdat)
|
||||||
|
)
|
||||||
|
|
||||||
# instead panicing, send eof
|
# instead panicing, send eof
|
||||||
return None
|
return None
|
||||||
@@ -987,8 +1042,8 @@ class RarFile(object):
|
|||||||
h.filename = self._decode(name)
|
h.filename = self._decode(name)
|
||||||
|
|
||||||
# change separator, if requested
|
# change separator, if requested
|
||||||
if PATH_SEP != '\\':
|
if PATH_SEP != "\\":
|
||||||
h.filename = h.filename.replace('\\', PATH_SEP)
|
h.filename = h.filename.replace("\\", PATH_SEP)
|
||||||
|
|
||||||
if h.flags & RAR_FILE_SALT:
|
if h.flags & RAR_FILE_SALT:
|
||||||
h.salt = h.header_data[pos : pos + 8]
|
h.salt = h.header_data[pos : pos + 8]
|
||||||
@@ -1045,8 +1100,7 @@ class RarFile(object):
|
|||||||
declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos)
|
declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos)
|
||||||
pos += S_COMMENT_HDR.size
|
pos += S_COMMENT_HDR.size
|
||||||
data = hdata[pos:pos_next]
|
data = hdata[pos:pos_next]
|
||||||
cmt = rar_decompress(ver, meth, data, declen, sflags,
|
cmt = rar_decompress(ver, meth, data, declen, sflags, crc, self._password)
|
||||||
crc, self._password)
|
|
||||||
if not self._crc_check:
|
if not self._crc_check:
|
||||||
h.comment = self._decode_comment(cmt)
|
h.comment = self._decode_comment(cmt)
|
||||||
elif crc32(cmt) & 0xFFFF == crc:
|
elif crc32(cmt) & 0xFFFF == crc:
|
||||||
@@ -1100,7 +1154,7 @@ class RarFile(object):
|
|||||||
def _next_newvol(self, volfile):
|
def _next_newvol(self, volfile):
|
||||||
i = len(volfile) - 1
|
i = len(volfile) - 1
|
||||||
while i >= 0:
|
while i >= 0:
|
||||||
if volfile[i] >= '0' and volfile[i] <= '9':
|
if volfile[i] >= "0" and volfile[i] <= "9":
|
||||||
return self._inc_volname(volfile, i)
|
return self._inc_volname(volfile, i)
|
||||||
i -= 1
|
i -= 1
|
||||||
raise BadRarName("Cannot construct volume name: " + volfile)
|
raise BadRarName("Cannot construct volume name: " + volfile)
|
||||||
@@ -1108,20 +1162,20 @@ class RarFile(object):
|
|||||||
# old-style next volume
|
# old-style next volume
|
||||||
def _next_oldvol(self, volfile):
|
def _next_oldvol(self, volfile):
|
||||||
# rar -> r00
|
# rar -> r00
|
||||||
if volfile[-4:].lower() == '.rar':
|
if volfile[-4:].lower() == ".rar":
|
||||||
return volfile[:-2] + '00'
|
return volfile[:-2] + "00"
|
||||||
return self._inc_volname(volfile, len(volfile) - 1)
|
return self._inc_volname(volfile, len(volfile) - 1)
|
||||||
|
|
||||||
# increase digits with carry, otherwise just increment char
|
# increase digits with carry, otherwise just increment char
|
||||||
def _inc_volname(self, volfile, i):
|
def _inc_volname(self, volfile, i):
|
||||||
fn = list(volfile)
|
fn = list(volfile)
|
||||||
while i >= 0:
|
while i >= 0:
|
||||||
if fn[i] != '9':
|
if fn[i] != "9":
|
||||||
fn[i] = chr(ord(fn[i]) + 1)
|
fn[i] = chr(ord(fn[i]) + 1)
|
||||||
break
|
break
|
||||||
fn[i] = '0'
|
fn[i] = "0"
|
||||||
i -= 1
|
i -= 1
|
||||||
return ''.join(fn)
|
return "".join(fn)
|
||||||
|
|
||||||
def _open_clear(self, inf):
|
def _open_clear(self, inf):
|
||||||
return DirectReader(self, inf)
|
return DirectReader(self, inf)
|
||||||
@@ -1135,7 +1189,7 @@ class RarFile(object):
|
|||||||
rf = XFile(inf.volume_file, 0)
|
rf = XFile(inf.volume_file, 0)
|
||||||
rf.seek(inf.header_offset)
|
rf.seek(inf.header_offset)
|
||||||
|
|
||||||
tmpfd, tmpname = mkstemp(suffix='.rar')
|
tmpfd, tmpname = mkstemp(suffix=".rar")
|
||||||
tmpf = os.fdopen(tmpfd, "wb")
|
tmpf = os.fdopen(tmpfd, "wb")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1148,7 +1202,7 @@ class RarFile(object):
|
|||||||
else:
|
else:
|
||||||
buf = rf.read(size)
|
buf = rf.read(size)
|
||||||
if not buf:
|
if not buf:
|
||||||
raise BadRarFile('read failed: ' + inf.filename)
|
raise BadRarFile("read failed: " + inf.filename)
|
||||||
tmpf.write(buf)
|
tmpf.write(buf)
|
||||||
size -= len(buf)
|
size -= len(buf)
|
||||||
tmpf.close()
|
tmpf.close()
|
||||||
@@ -1170,14 +1224,15 @@ class RarFile(object):
|
|||||||
rf.close()
|
rf.close()
|
||||||
|
|
||||||
# decompress
|
# decompress
|
||||||
cmt = rar_decompress(inf.extract_version, inf.compress_type, data,
|
cmt = rar_decompress(
|
||||||
inf.file_size, inf.flags, inf.CRC, psw, inf.salt)
|
inf.extract_version, inf.compress_type, data, inf.file_size, inf.flags, inf.CRC, psw, inf.salt
|
||||||
|
)
|
||||||
|
|
||||||
# check crc
|
# check crc
|
||||||
if self._crc_check:
|
if self._crc_check:
|
||||||
crc = crc32(cmt)
|
crc = crc32(cmt)
|
||||||
if crc < 0:
|
if crc < 0:
|
||||||
crc += (long(1) << 32)
|
crc += long(1) << 32
|
||||||
if crc != inf.CRC:
|
if crc != inf.CRC:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1208,7 +1263,7 @@ class RarFile(object):
|
|||||||
return val.decode(c)
|
return val.decode(c)
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
pass
|
pass
|
||||||
return val.decode(self._charset, 'replace')
|
return val.decode(self._charset, "replace")
|
||||||
|
|
||||||
def _decode_comment(self, val):
|
def _decode_comment(self, val):
|
||||||
if UNICODE_COMMENTS:
|
if UNICODE_COMMENTS:
|
||||||
@@ -1241,10 +1296,12 @@ class RarFile(object):
|
|||||||
output = p.communicate()[0]
|
output = p.communicate()[0]
|
||||||
check_returncode(p, output)
|
check_returncode(p, output)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## Utility classes
|
## Utility classes
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
class UnicodeFilename:
|
class UnicodeFilename:
|
||||||
"""Handle unicode filename decompression"""
|
"""Handle unicode filename decompression"""
|
||||||
|
|
||||||
@@ -1269,7 +1326,7 @@ class UnicodeFilename:
|
|||||||
return self.std_name[self.pos]
|
return self.std_name[self.pos]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.failed = 1
|
self.failed = 1
|
||||||
return ord('?')
|
return ord("?")
|
||||||
|
|
||||||
def put(self, lo, hi):
|
def put(self, lo, hi):
|
||||||
self.buf.append(lo)
|
self.buf.append(lo)
|
||||||
@@ -1295,7 +1352,7 @@ class UnicodeFilename:
|
|||||||
n = self.enc_byte()
|
n = self.enc_byte()
|
||||||
if n & 0x80:
|
if n & 0x80:
|
||||||
c = self.enc_byte()
|
c = self.enc_byte()
|
||||||
for i in range((n & 0x7f) + 2):
|
for i in range((n & 0x7F) + 2):
|
||||||
lo = (self.std_byte() + c) & 0xFF
|
lo = (self.std_byte() + c) & 0xFF
|
||||||
self.put(lo, hi)
|
self.put(lo, hi)
|
||||||
else:
|
else:
|
||||||
@@ -1326,7 +1383,7 @@ class RarExtFile(RawIOBase):
|
|||||||
|
|
||||||
# standard io.* properties
|
# standard io.* properties
|
||||||
self.name = inf.filename
|
self.name = inf.filename
|
||||||
self.mode = 'rb'
|
self.mode = "rb"
|
||||||
|
|
||||||
self.rf = rf
|
self.rf = rf
|
||||||
self.inf = inf
|
self.inf = inf
|
||||||
@@ -1375,12 +1432,12 @@ class RarExtFile(RawIOBase):
|
|||||||
if not self.crc_check:
|
if not self.crc_check:
|
||||||
return
|
return
|
||||||
if self.returncode:
|
if self.returncode:
|
||||||
check_returncode(self, '')
|
check_returncode(self, "")
|
||||||
if self.remain != 0:
|
if self.remain != 0:
|
||||||
raise BadRarFile("Failed the read enough data")
|
raise BadRarFile("Failed the read enough data")
|
||||||
crc = self.CRC
|
crc = self.CRC
|
||||||
if crc < 0:
|
if crc < 0:
|
||||||
crc += (long(1) << 32)
|
crc += long(1) << 32
|
||||||
if crc != self.inf.CRC:
|
if crc != self.inf.CRC:
|
||||||
raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename)
|
raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename)
|
||||||
|
|
||||||
@@ -1412,6 +1469,7 @@ class RarExtFile(RawIOBase):
|
|||||||
buf[:n] = data
|
buf[:n] = data
|
||||||
except TypeError:
|
except TypeError:
|
||||||
import array
|
import array
|
||||||
|
|
||||||
if not isinstance(buf, array.array):
|
if not isinstance(buf, array.array):
|
||||||
raise
|
raise
|
||||||
buf[:n] = array.array(buf.typecode, data)
|
buf[:n] = array.array(buf.typecode, data)
|
||||||
@@ -1443,7 +1501,7 @@ class RarExtFile(RawIOBase):
|
|||||||
elif whence == 2: # seek from end of file
|
elif whence == 2: # seek from end of file
|
||||||
new_ofs = fsize + ofs
|
new_ofs = fsize + ofs
|
||||||
else:
|
else:
|
||||||
raise ValueError('Invalid value for whence')
|
raise ValueError("Invalid value for whence")
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if new_ofs < 0:
|
if new_ofs < 0:
|
||||||
@@ -1566,6 +1624,7 @@ class PipeReader(RarExtFile):
|
|||||||
self.tempfile = None
|
self.tempfile = None
|
||||||
|
|
||||||
if have_memoryview:
|
if have_memoryview:
|
||||||
|
|
||||||
def readinto(self, buf):
|
def readinto(self, buf):
|
||||||
"""Zero-copy read directly into buffer."""
|
"""Zero-copy read directly into buffer."""
|
||||||
cnt = len(buf)
|
cnt = len(buf)
|
||||||
@@ -1676,6 +1735,7 @@ class DirectReader(RarExtFile):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if have_memoryview:
|
if have_memoryview:
|
||||||
|
|
||||||
def readinto(self, buf):
|
def readinto(self, buf):
|
||||||
"""Zero-copy read directly into buffer."""
|
"""Zero-copy read directly into buffer."""
|
||||||
got = 0
|
got = 0
|
||||||
@@ -1705,6 +1765,7 @@ class DirectReader(RarExtFile):
|
|||||||
|
|
||||||
class HeaderDecrypt:
|
class HeaderDecrypt:
|
||||||
"""File-like object that decrypts from another file"""
|
"""File-like object that decrypts from another file"""
|
||||||
|
|
||||||
def __init__(self, f, key, iv):
|
def __init__(self, f, key, iv):
|
||||||
self.f = f
|
self.f = f
|
||||||
self.ciph = AES.new(key, AES.MODE_CBC, iv)
|
self.ciph = AES.new(key, AES.MODE_CBC, iv)
|
||||||
@@ -1715,7 +1776,7 @@ class HeaderDecrypt:
|
|||||||
|
|
||||||
def read(self, cnt=None):
|
def read(self, cnt=None):
|
||||||
if cnt > 8 * 1024:
|
if cnt > 8 * 1024:
|
||||||
raise BadRarFile('Bad count to header decrypt - wrong password?')
|
raise BadRarFile("Bad count to header decrypt - wrong password?")
|
||||||
|
|
||||||
# consume old data
|
# consume old data
|
||||||
if cnt <= len(self.buf):
|
if cnt <= len(self.buf):
|
||||||
@@ -1743,9 +1804,11 @@ class HeaderDecrypt:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
# handle (filename|filelike) object
|
# handle (filename|filelike) object
|
||||||
class XFile(object):
|
class XFile(object):
|
||||||
__slots__ = ('_fd', '_need_close')
|
__slots__ = ("_fd", "_need_close")
|
||||||
|
|
||||||
def __init__(self, xfile, bufsize=1024):
|
def __init__(self, xfile, bufsize=1024):
|
||||||
if is_filelike(xfile):
|
if is_filelike(xfile):
|
||||||
self._need_close = False
|
self._need_close = False
|
||||||
@@ -1753,41 +1816,51 @@ class XFile(object):
|
|||||||
self._fd.seek(0)
|
self._fd.seek(0)
|
||||||
else:
|
else:
|
||||||
self._need_close = True
|
self._need_close = True
|
||||||
self._fd = open(xfile, 'rb', bufsize)
|
self._fd = open(xfile, "rb", bufsize)
|
||||||
|
|
||||||
def read(self, n=None):
|
def read(self, n=None):
|
||||||
return self._fd.read(n)
|
return self._fd.read(n)
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self._fd.tell()
|
return self._fd.tell()
|
||||||
|
|
||||||
def seek(self, ofs, whence=0):
|
def seek(self, ofs, whence=0):
|
||||||
return self._fd.seek(ofs, whence)
|
return self._fd.seek(ofs, whence)
|
||||||
|
|
||||||
def readinto(self, dst):
|
def readinto(self, dst):
|
||||||
return self._fd.readinto(dst)
|
return self._fd.readinto(dst)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self._need_close:
|
if self._need_close:
|
||||||
self._fd.close()
|
self._fd.close()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, typ, val, tb):
|
def __exit__(self, typ, val, tb):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## Utility functions
|
## Utility functions
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
def is_filelike(obj):
|
def is_filelike(obj):
|
||||||
if isinstance(obj, str) or isinstance(obj, unicode):
|
if isinstance(obj, str) or isinstance(obj, unicode):
|
||||||
return False
|
return False
|
||||||
res = True
|
res = True
|
||||||
for a in ('read', 'tell', 'seek'):
|
for a in ("read", "tell", "seek"):
|
||||||
res = res and hasattr(obj, a)
|
res = res and hasattr(obj, a)
|
||||||
if not res:
|
if not res:
|
||||||
raise ValueError("Invalid object passed as file")
|
raise ValueError("Invalid object passed as file")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def rar3_s2k(psw, salt):
|
def rar3_s2k(psw, salt):
|
||||||
"""String-to-key hash for RAR3."""
|
"""String-to-key hash for RAR3."""
|
||||||
|
|
||||||
seed = psw.encode('utf-16le') + salt
|
seed = psw.encode("utf-16le") + salt
|
||||||
iv = EMPTY
|
iv = EMPTY
|
||||||
h = sha1()
|
h = sha1()
|
||||||
for i in range(16):
|
for i in range(16):
|
||||||
@@ -1800,6 +1873,7 @@ def rar3_s2k(psw, salt):
|
|||||||
key_le = pack("<LLLL", *unpack(">LLLL", key_be))
|
key_le = pack("<LLLL", *unpack(">LLLL", key_be))
|
||||||
return key_le, iv
|
return key_le, iv
|
||||||
|
|
||||||
|
|
||||||
def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None):
|
def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None):
|
||||||
"""Decompress blob of compressed data.
|
"""Decompress blob of compressed data.
|
||||||
|
|
||||||
@@ -1815,11 +1889,10 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
|||||||
flags |= RAR_LONG_BLOCK
|
flags |= RAR_LONG_BLOCK
|
||||||
|
|
||||||
# file header
|
# file header
|
||||||
fname = bytes('data', 'ascii')
|
fname = bytes("data", "ascii")
|
||||||
date = 0
|
date = 0
|
||||||
mode = 0x20
|
mode = 0x20
|
||||||
fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc,
|
fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc, date, vers, meth, len(fname), mode)
|
||||||
date, vers, meth, len(fname), mode)
|
|
||||||
fhdr += fname
|
fhdr += fname
|
||||||
if flags & RAR_FILE_SALT:
|
if flags & RAR_FILE_SALT:
|
||||||
if not salt:
|
if not salt:
|
||||||
@@ -1836,7 +1909,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
|||||||
mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2 + 4)
|
mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2 + 4)
|
||||||
|
|
||||||
# decompress via temp rar
|
# decompress via temp rar
|
||||||
tmpfd, tmpname = mkstemp(suffix='.rar')
|
tmpfd, tmpname = mkstemp(suffix=".rar")
|
||||||
tmpf = os.fdopen(tmpfd, "wb")
|
tmpf = os.fdopen(tmpfd, "wb")
|
||||||
try:
|
try:
|
||||||
tmpf.write(RAR_ID + mh + hdr + data)
|
tmpf.write(RAR_ID + mh + hdr + data)
|
||||||
@@ -1852,6 +1925,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
|||||||
tmpf.close()
|
tmpf.close()
|
||||||
os.unlink(tmpname)
|
os.unlink(tmpname)
|
||||||
|
|
||||||
|
|
||||||
def to_datetime(t):
|
def to_datetime(t):
|
||||||
"""Convert 6-part time tuple into datetime object."""
|
"""Convert 6-part time tuple into datetime object."""
|
||||||
|
|
||||||
@@ -1871,13 +1945,20 @@ def to_datetime(t):
|
|||||||
|
|
||||||
# sanitize invalid values
|
# sanitize invalid values
|
||||||
MDAY = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
MDAY = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||||
if mon < 1: mon = 1
|
if mon < 1:
|
||||||
if mon > 12: mon = 12
|
mon = 1
|
||||||
if day < 1: day = 1
|
if mon > 12:
|
||||||
if day > MDAY[mon]: day = MDAY[mon]
|
mon = 12
|
||||||
if h > 23: h = 23
|
if day < 1:
|
||||||
if m > 59: m = 59
|
day = 1
|
||||||
if s > 59: s = 59
|
if day > MDAY[mon]:
|
||||||
|
day = MDAY[mon]
|
||||||
|
if h > 23:
|
||||||
|
h = 23
|
||||||
|
if m > 59:
|
||||||
|
m = 59
|
||||||
|
if s > 59:
|
||||||
|
s = 59
|
||||||
if mon == 2 and day == 29:
|
if mon == 2 and day == 29:
|
||||||
try:
|
try:
|
||||||
return datetime(year, mon, day, h, m, s, us)
|
return datetime(year, mon, day, h, m, s, us)
|
||||||
@@ -1885,30 +1966,35 @@ def to_datetime(t):
|
|||||||
day = 28
|
day = 28
|
||||||
return datetime(year, mon, day, h, m, s, us)
|
return datetime(year, mon, day, h, m, s, us)
|
||||||
|
|
||||||
|
|
||||||
def parse_dos_time(stamp):
|
def parse_dos_time(stamp):
|
||||||
"""Parse standard 32-bit DOS timestamp."""
|
"""Parse standard 32-bit DOS timestamp."""
|
||||||
|
|
||||||
sec = stamp & 0x1F; stamp = stamp >> 5
|
sec = stamp & 0x1F
|
||||||
min = stamp & 0x3F; stamp = stamp >> 6
|
stamp = stamp >> 5
|
||||||
hr = stamp & 0x1F; stamp = stamp >> 5
|
min = stamp & 0x3F
|
||||||
day = stamp & 0x1F; stamp = stamp >> 5
|
stamp = stamp >> 6
|
||||||
mon = stamp & 0x0F; stamp = stamp >> 4
|
hr = stamp & 0x1F
|
||||||
|
stamp = stamp >> 5
|
||||||
|
day = stamp & 0x1F
|
||||||
|
stamp = stamp >> 5
|
||||||
|
mon = stamp & 0x0F
|
||||||
|
stamp = stamp >> 4
|
||||||
yr = (stamp & 0x7F) + 1980
|
yr = (stamp & 0x7F) + 1980
|
||||||
return (yr, mon, day, hr, min, sec * 2)
|
return (yr, mon, day, hr, min, sec * 2)
|
||||||
|
|
||||||
|
|
||||||
def custom_popen(cmd):
|
def custom_popen(cmd):
|
||||||
"""Disconnect cmd from parent fds, read only from stdout."""
|
"""Disconnect cmd from parent fds, read only from stdout."""
|
||||||
|
|
||||||
# needed for py2exe
|
# needed for py2exe
|
||||||
creationflags = 0
|
creationflags = 0
|
||||||
if sys.platform == 'win32':
|
if sys.platform == "win32":
|
||||||
creationflags = 0x08000000 # CREATE_NO_WINDOW
|
creationflags = 0x08000000 # CREATE_NO_WINDOW
|
||||||
|
|
||||||
# run command
|
# run command
|
||||||
try:
|
try:
|
||||||
p = Popen(cmd, bufsize = 0,
|
p = Popen(cmd, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, creationflags=creationflags)
|
||||||
stdout = PIPE, stdin = PIPE, stderr = STDOUT,
|
|
||||||
creationflags = creationflags)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
ex = sys.exc_info()[1]
|
ex = sys.exc_info()[1]
|
||||||
if ex.errno == errno.ENOENT:
|
if ex.errno == errno.ENOENT:
|
||||||
@@ -1916,6 +2002,7 @@ def custom_popen(cmd):
|
|||||||
raise
|
raise
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
def custom_check(cmd, ignore_retcode=False):
|
def custom_check(cmd, ignore_retcode=False):
|
||||||
"""Run command, collect output, raise error if needed."""
|
"""Run command, collect output, raise error if needed."""
|
||||||
p = custom_popen(cmd)
|
p = custom_popen(cmd)
|
||||||
@@ -1924,14 +2011,16 @@ def custom_check(cmd, ignore_retcode=False):
|
|||||||
raise RarExecError("Check-run failed")
|
raise RarExecError("Check-run failed")
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def add_password_arg(cmd, psw, required=False):
|
def add_password_arg(cmd, psw, required=False):
|
||||||
"""Append password switch to commandline."""
|
"""Append password switch to commandline."""
|
||||||
if UNRAR_TOOL == ALT_TOOL:
|
if UNRAR_TOOL == ALT_TOOL:
|
||||||
return
|
return
|
||||||
if psw is not None:
|
if psw is not None:
|
||||||
cmd.append('-p' + psw)
|
cmd.append("-p" + psw)
|
||||||
else:
|
else:
|
||||||
cmd.append('-p-')
|
cmd.append("-p-")
|
||||||
|
|
||||||
|
|
||||||
def check_returncode(p, out):
|
def check_returncode(p, out):
|
||||||
"""Raise exception according to unrar exit code"""
|
"""Raise exception according to unrar exit code"""
|
||||||
@@ -1941,10 +2030,19 @@ def check_returncode(p, out):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# map return code to exception class
|
# map return code to exception class
|
||||||
errmap = [None,
|
errmap = [
|
||||||
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
None,
|
||||||
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
RarWarning,
|
||||||
RarCreateError, RarNoFilesError] # codes from rar.txt
|
RarFatalError,
|
||||||
|
RarCRCError,
|
||||||
|
RarLockedArchiveError,
|
||||||
|
RarWriteError,
|
||||||
|
RarOpenError,
|
||||||
|
RarUserError,
|
||||||
|
RarMemoryError,
|
||||||
|
RarCreateError,
|
||||||
|
RarNoFilesError,
|
||||||
|
] # codes from rar.txt
|
||||||
if UNRAR_TOOL == ALT_TOOL:
|
if UNRAR_TOOL == ALT_TOOL:
|
||||||
errmap = [None]
|
errmap = [None]
|
||||||
if code > 0 and code < len(errmap):
|
if code > 0 and code < len(errmap):
|
||||||
@@ -1964,6 +2062,7 @@ def check_returncode(p, out):
|
|||||||
|
|
||||||
raise exc(msg)
|
raise exc(msg)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Check if unrar works
|
# Check if unrar works
|
||||||
#
|
#
|
||||||
@@ -1983,4 +2082,3 @@ except RarCannotExec:
|
|||||||
except RarCannotExec:
|
except RarCannotExec:
|
||||||
# no usable tool, only uncompressed archives work
|
# no usable tool, only uncompressed archives work
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
355
comic/views.py
355
comic/views.py
@@ -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/')]),
|
"breadcrumbs": generate_breadcrumbs_from_menu([("Recent", "/comic/recent/")]),
|
||||||
'menu': Menu(request.user, 'Recent'),
|
"menu": Menu(request.user, "Recent"),
|
||||||
'title': 'Recent Comics',
|
"title": "Recent Comics",
|
||||||
'feed_id': urlsafe_base64_encode(feed_id.feed_id.bytes),
|
"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/")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
label="",
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
attrs={
|
attrs={"class": "form-control", "placeholder": "Username", "autofocus": True, "required": True}
|
||||||
'class': 'form-control',
|
),
|
||||||
'placeholder': 'Username',
|
)
|
||||||
'autofocus': True,
|
password = forms.CharField(
|
||||||
'required': True,
|
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())
|
||||||
|
|||||||
@@ -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/")
|
||||||
|
|||||||
@@ -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
2
pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[tool.black]
|
||||||
|
line_length = 119
|
||||||
17
setup.py
17
setup.py
@@ -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"],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user