From 8ec8c686c681192dc29c06ebb102f2488b00520b Mon Sep 17 00:00:00 2001 From: MassiveAtoms Date: Tue, 7 Jul 2020 23:11:21 -0300 Subject: [PATCH] reasonably working calibreweb thing --- .../CalibreWebCompanion/__init__.py | 0 .../CalibreWebCompanion/asgi.py | 16 + .../CalibreWebCompanion/settings.py | 137 +++++++ .../CalibreWebCompanion/urls.py | 27 ++ .../CalibreWebCompanion/wsgi.py | 16 + CalibreWebCompanion/db.sqlite3 | Bin 0 -> 131072 bytes CalibreWebCompanion/db_routers/__init__.py | 2 + CalibreWebCompanion/db_routers/routers.py | 76 ++++ CalibreWebCompanion/library/__init__.py | 0 CalibreWebCompanion/library/_models.py | 265 ++++++++++++ CalibreWebCompanion/library/admin.py | 27 ++ CalibreWebCompanion/library/apps.py | 5 + CalibreWebCompanion/library/calibredb.py | 107 +++++ .../library/migrations/0001_initial.py | 206 ++++++++++ .../library/migrations/__init__.py | 0 CalibreWebCompanion/library/models.py | 387 ++++++++++++++++++ .../library/static/css/styles.css | 58 +++ CalibreWebCompanion/library/static/js/nav.js | 14 + .../library/templates/base.html | 54 +++ .../templates/library/authors_detail.html | 19 + .../templates/library/authors_list.html | 17 + .../templates/library/books_detail.html | 47 +++ .../library/templates/library/books_list.html | 17 + .../templates/library/publishers_detail.html | 18 + .../templates/library/publishers_list.html | 17 + .../templates/library/ratings_detail.html | 20 + .../templates/library/ratings_list.html | 17 + .../templates/library/tags_detail.html | 20 + .../library/templates/library/tags_list.html | 17 + .../library/templatetags/__init__.py | 1 + .../library/templatetags/custom.py | 9 + CalibreWebCompanion/library/tests.py | 3 + CalibreWebCompanion/library/urls.py | 20 + CalibreWebCompanion/library/views.py | 81 ++++ CalibreWebCompanion/manage.py | 21 + README.md | 10 + models.vsdx | Bin 0 -> 55251 bytes requirements.txt | 1 + 38 files changed, 1752 insertions(+) create mode 100644 CalibreWebCompanion/CalibreWebCompanion/__init__.py create mode 100644 CalibreWebCompanion/CalibreWebCompanion/asgi.py create mode 100644 CalibreWebCompanion/CalibreWebCompanion/settings.py create mode 100644 CalibreWebCompanion/CalibreWebCompanion/urls.py create mode 100644 CalibreWebCompanion/CalibreWebCompanion/wsgi.py create mode 100644 CalibreWebCompanion/db.sqlite3 create mode 100644 CalibreWebCompanion/db_routers/__init__.py create mode 100644 CalibreWebCompanion/db_routers/routers.py create mode 100644 CalibreWebCompanion/library/__init__.py create mode 100644 CalibreWebCompanion/library/_models.py create mode 100644 CalibreWebCompanion/library/admin.py create mode 100644 CalibreWebCompanion/library/apps.py create mode 100644 CalibreWebCompanion/library/calibredb.py create mode 100644 CalibreWebCompanion/library/migrations/0001_initial.py create mode 100644 CalibreWebCompanion/library/migrations/__init__.py create mode 100644 CalibreWebCompanion/library/models.py create mode 100644 CalibreWebCompanion/library/static/css/styles.css create mode 100644 CalibreWebCompanion/library/static/js/nav.js create mode 100644 CalibreWebCompanion/library/templates/base.html create mode 100644 CalibreWebCompanion/library/templates/library/authors_detail.html create mode 100644 CalibreWebCompanion/library/templates/library/authors_list.html create mode 100644 CalibreWebCompanion/library/templates/library/books_detail.html create mode 100644 CalibreWebCompanion/library/templates/library/books_list.html create mode 100644 CalibreWebCompanion/library/templates/library/publishers_detail.html create mode 100644 CalibreWebCompanion/library/templates/library/publishers_list.html create mode 100644 CalibreWebCompanion/library/templates/library/ratings_detail.html create mode 100644 CalibreWebCompanion/library/templates/library/ratings_list.html create mode 100644 CalibreWebCompanion/library/templates/library/tags_detail.html create mode 100644 CalibreWebCompanion/library/templates/library/tags_list.html create mode 100644 CalibreWebCompanion/library/templatetags/__init__.py create mode 100644 CalibreWebCompanion/library/templatetags/custom.py create mode 100644 CalibreWebCompanion/library/tests.py create mode 100644 CalibreWebCompanion/library/urls.py create mode 100644 CalibreWebCompanion/library/views.py create mode 100644 CalibreWebCompanion/manage.py create mode 100644 README.md create mode 100644 models.vsdx create mode 100644 requirements.txt diff --git a/CalibreWebCompanion/CalibreWebCompanion/__init__.py b/CalibreWebCompanion/CalibreWebCompanion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CalibreWebCompanion/CalibreWebCompanion/asgi.py b/CalibreWebCompanion/CalibreWebCompanion/asgi.py new file mode 100644 index 0000000..41b4f52 --- /dev/null +++ b/CalibreWebCompanion/CalibreWebCompanion/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for CalibreWebCompanion project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CalibreWebCompanion.settings') + +application = get_asgi_application() diff --git a/CalibreWebCompanion/CalibreWebCompanion/settings.py b/CalibreWebCompanion/CalibreWebCompanion/settings.py new file mode 100644 index 0000000..d7fef39 --- /dev/null +++ b/CalibreWebCompanion/CalibreWebCompanion/settings.py @@ -0,0 +1,137 @@ +""" +Django settings for CalibreWebCompanion project. + +Generated by 'django-admin startproject' using Django 3.0.8. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +CALIBRE_DIR = os.path.abspath("C:\\Users\\MassiveAtoms\\Documents\\Calibre Library") + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'u(8^+rb%rz5hsx4v^^y(ul7g(4n7a8!db@s*9(m5cs*2_ppy8+' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + "library" +] + +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 = 'CalibreWebCompanion.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.join(BASE_DIR, '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 = 'CalibreWebCompanion.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + }, + 'calibre': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(CALIBRE_DIR, 'metadata.db'), + } +} + + + + + +DATABASE_ROUTERS = ["db_routers.DjangoRouter", "db_routers.CalibreRouter"] + +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'America/Paramaribo' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + +STATICFILES_DIRS = [ + os.path.abspath(CALIBRE_DIR), + # '/static/', +] + +STATIC_URL = '/static/' diff --git a/CalibreWebCompanion/CalibreWebCompanion/urls.py b/CalibreWebCompanion/CalibreWebCompanion/urls.py new file mode 100644 index 0000000..64829d1 --- /dev/null +++ b/CalibreWebCompanion/CalibreWebCompanion/urls.py @@ -0,0 +1,27 @@ +"""CalibreWebCompanion URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static +from django.views.generic import RedirectView + +urlpatterns = [ + path('admin/', admin.site.urls), + path('library/', include('library.urls')), + path('', RedirectView.as_view(url='library/', permanent=True)), +] +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/CalibreWebCompanion/CalibreWebCompanion/wsgi.py b/CalibreWebCompanion/CalibreWebCompanion/wsgi.py new file mode 100644 index 0000000..46b629e --- /dev/null +++ b/CalibreWebCompanion/CalibreWebCompanion/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for CalibreWebCompanion project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CalibreWebCompanion.settings') + +application = get_wsgi_application() diff --git a/CalibreWebCompanion/db.sqlite3 b/CalibreWebCompanion/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..b92ca6170112118dae90f72ba652f6d34fbd1cc9 GIT binary patch literal 131072 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 aXm}!=nutaMk*Ra&H<~<~N`+(L@c#u|*H7~R literal 0 HcmV?d00001 diff --git a/CalibreWebCompanion/db_routers/__init__.py b/CalibreWebCompanion/db_routers/__init__.py new file mode 100644 index 0000000..ace8f6b --- /dev/null +++ b/CalibreWebCompanion/db_routers/__init__.py @@ -0,0 +1,2 @@ +from .routers import DjangoRouter, CalibreRouter +__name__ = "db_routers" \ No newline at end of file diff --git a/CalibreWebCompanion/db_routers/routers.py b/CalibreWebCompanion/db_routers/routers.py new file mode 100644 index 0000000..672dcc9 --- /dev/null +++ b/CalibreWebCompanion/db_routers/routers.py @@ -0,0 +1,76 @@ + + +class DjangoRouter: + """ + A router to control all database operations on models in the + auth and contenttypes applications. + """ + route_app_labels = {'auth', 'contenttypes', "sessions", "sites", "admin", "flatpages"} + + def db_for_read(self, model, **hints): + """ + Attempts to read auth and contenttypes models go to default. + """ + if model._meta.app_label in self.route_app_labels: + return 'default' + return None + + def db_for_write(self, model, **hints): + """ + Attempts to write auth and contenttypes models go to django. + """ + if model._meta.app_label in self.route_app_labels: + return 'default' + return None + + def allow_relation(self, obj1, obj2, **hints): + """ + Allow relations if a model in the auth or contenttypes apps is + involved. + """ + if ( + obj1._meta.app_label in self.route_app_labels or + obj2._meta.app_label in self.route_app_labels + ): + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + """ + Make sure the auth and contenttypes apps only appear in the + 'django' database. + """ + if app_label in self.route_app_labels: + return db == 'default' + return None + + + +class CalibreRouter: + """ + A router to control all database operations on models in the + auth and contenttypes applications. + """ + def db_for_read(self, model, **hints): + """ + Attempts to read anything else goes to calibre + """ + return 'calibre' + + # def db_for_write(self, model, **hints): # might be prudent not to allow writes + # """ + # Attempts to write auth and contenttypes models go to 'calibre'. + # """ + # return 'calibre' + + def allow_relation(self, obj1, obj2, **hints): + """ + Allow relations. + """ + return True + + # def allow_migrate(self, db, app_label, model_name=None, **hints): # might be prudent not to allow migrations + # """ + # Yes + # """ + # return True \ No newline at end of file diff --git a/CalibreWebCompanion/library/__init__.py b/CalibreWebCompanion/library/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CalibreWebCompanion/library/_models.py b/CalibreWebCompanion/library/_models.py new file mode 100644 index 0000000..f9320a5 --- /dev/null +++ b/CalibreWebCompanion/library/_models.py @@ -0,0 +1,265 @@ +# This is an auto-generated Django model module. +# You'll have to do the following manually to clean this up: +# * Rearrange models' order +# * Make sure each model has one field with primary_key=True +# * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior +# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table +# Feel free to rename the models, but don't rename db_table values or field names. +from django.db import models + + +class Authors(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + link = models.TextField() + + class Meta: + managed = False + db_table = 'authors' + + +class Comments(models.Model): + book = models.ForeignField("Book") + text = models.TextField() + + class Meta: + managed = False + db_table = 'comments' + + +class Data(models.Model): + book = models.IntegerField() + format = models.TextField() + uncompressed_size = models.IntegerField() + name = models.TextField() + + class Meta: + managed = False + db_table = 'data' + + +class Identifiers(models.Model): + book = models.IntegerField() + type = models.TextField() + val = models.TextField() + + class Meta: + managed = False + db_table = 'identifiers' + + +class Languages(models.Model): + lang_code = models.TextField() + + class Meta: + managed = False + db_table = 'languages' + + +class Publishers(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'publishers' + + +class Ratings(models.Model): + rating = models.IntegerField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'ratings' + + +class Series(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'series' + + +class Tags(models.Model): + name = models.TextField() + + class Meta: + managed = False + db_table = 'tags' + + +class Books(models.Model): + title = models.TextField() + sort = models.TextField(blank=True, null=True) + # This field type is a guess. + timestamp = models.TextField(blank=True, null=True) + # This field type is a guess. + pubdate = models.TextField(blank=True, null=True) + series_index = models.FloatField() + author_sort = models.TextField(blank=True, null=True) + isbn = models.TextField(blank=True, null=True) + lccn = models.TextField(blank=True, null=True) + path = models.TextField() + flags = models.IntegerField() + uuid = models.TextField(blank=True, null=True) + has_cover = models.BooleanField(blank=True, null=True) + last_modified = models.TextField() # This field type is a guess. + authors = models.ManyToManyField( + Authors, + through='BooksAuthorsLink', + through_fields=('book', 'author')) + languages = models.ManyToManyField( + Languages, + through='BooksLanguagesLink', + through_fields=('book', 'lang_code')) + publishers = models.ManyToManyField( + Publishers, + through='BooksPublishersLink', + through_fields=('book', 'publisher')) + series = models.ManyToManyField( + Series, + through='BooksSeriesLink', + through_fields=('book', 'series')) + tags = models.ManyToManyField( + Tags, + through='BooksTagsLink', + through_fields=('book', 'tag')) + + class Meta: + managed = False + db_table = 'books' + + +class BooksAuthorsLink(models.Model): + book = models.ForeignKey(db_column="book") + author = models.ForeignKey(db_column="author") + + class Meta: + managed = False + db_table = 'books_authors_link' + + +class BooksLanguagesLink(models.Model): + book = models.ForeignKey(db_colum="book") + lang_code = models.ForeignKey(db_column="lang_code") + item_order = models.IntegerField() + + class Meta: + managed = False + db_table = 'books_languages_link' + + +class BooksPublishersLink(models.Model): + book = models.ForeignKey(db_column="book") + publisher = models.ForeignKey(db_column="publisher") + + class Meta: + managed = False + db_table = 'books_publishers_link' + + +# class BooksRatingsLink(models.Model): # TODO add this somehow +# book = models.ForeignKey(db_column="book") +# rating = models.IntegerField() +# class Meta: +# managed = False +# db_table = 'books_ratings_link' + + +class BooksSeriesLink(models.Model): + book = models.ForeignKey(db_column="book") + series = models.ForeignKey(db_column="series") + + class Meta: + managed = False + db_table = 'books_series_link' + + +class BooksTagsLink(models.Model): + book = models.ForeignKey(db_column="book") + tag = models.ForeignKey(db_column="tag") + + class Meta: + managed = False + db_table = 'books_tags_link' + + +# class BooksPluginData(models.Model): +# book = models.IntegerField() +# name = models.TextField() +# val = models.TextField() + +# class Meta: +# managed = False +# db_table = 'books_plugin_data' + + +# class ConversionOptions(models.Model): +# format = models.TextField() +# book = models.IntegerField(blank=True, null=True) +# data = models.BinaryField() +# +# class Meta: +# managed = False +# db_table = 'conversion_options' +# +# class LibraryId(models.Model): +# uuid = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'library_id' +# +# class CustomColumns(models.Model): +# label = models.TextField() +# name = models.TextField() +# datatype = models.TextField() +# mark_for_delete = models.BooleanField() +# editable = models.BooleanField() +# display = models.TextField() +# is_multiple = models.BooleanField() +# normalized = models.BooleanField() +# +# class Meta: +# managed = False +# db_table = 'custom_columns' +# +# class Preferences(models.Model): +# key = models.TextField() +# val = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'preferences' +# +# class Feeds(models.Model): +# title = models.TextField() +# script = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'feeds' +# +# +# class LastReadPositions(models.Model): +# book = models.IntegerField() +# format = models.TextField() +# user = models.TextField() +# device = models.TextField() +# cfi = models.TextField() +# epoch = models.FloatField() +# pos_frac = models.FloatField() +# +# class Meta: +# managed = False +# db_table = 'last_read_positions' + + +# class MetadataDirtied(models.Model): +# book = models.IntegerField() + +# class Meta: +# managed = False +# db_table = 'metadata_dirtied' diff --git a/CalibreWebCompanion/library/admin.py b/CalibreWebCompanion/library/admin.py new file mode 100644 index 0000000..69776b3 --- /dev/null +++ b/CalibreWebCompanion/library/admin.py @@ -0,0 +1,27 @@ +from django.contrib import admin +from .models import Authors, Books, Languages, Publishers, Series, Tags +# Register your models here. + +@admin.register(Authors) +class AuthorAdmin(admin.ModelAdmin): + list_display = (["name"]) + +@admin.register(Languages) +class LanguageAdmin(admin.ModelAdmin): + list_display = (["id", "lang_code"]) + +@admin.register(Publishers) +class PublisherAdmin(admin.ModelAdmin): + list_display = (["id","name"]) + +@admin.register(Series) +class SeriesAdmin(admin.ModelAdmin): + list_display = (["id","name"]) + +@admin.register(Tags) +class TagAdmin(admin.ModelAdmin): + list_display = (["id","name"]) + +@admin.register(Books) +class BookAdmin(admin.ModelAdmin): + list_display = (["id","title", "author_sort"]) \ No newline at end of file diff --git a/CalibreWebCompanion/library/apps.py b/CalibreWebCompanion/library/apps.py new file mode 100644 index 0000000..e01db0a --- /dev/null +++ b/CalibreWebCompanion/library/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LibraryConfig(AppConfig): + name = 'library' diff --git a/CalibreWebCompanion/library/calibredb.py b/CalibreWebCompanion/library/calibredb.py new file mode 100644 index 0000000..a0cdb7c --- /dev/null +++ b/CalibreWebCompanion/library/calibredb.py @@ -0,0 +1,107 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +engine = create_engine('sqlite:///C://Users//MassiveAtoms//Documents//Calibre Library//metadata.db.', echo=True) +Base = declarative_base(engine) +######################################################################## + +class Author(Base): # needed + """""" + __tablename__ = 'authors' + __table_args__ = {'autoload':True} + # has int id, text name, sort + +class Comment(Base): # needed + """""" + __tablename__ = 'comments' + __table_args__ = {'autoload':True} + # has int id, int book, text text + +# class Data(Base): # maybe +# """""" +# __tablename__ = 'data' +# __table_args__ = {'autoload':True} +# # has int id, int book, text format, text name + +class Identifier(Base): # needed + """""" + __tablename__ = 'identifiers' + __table_args__ = {'autoload':True} + # has int id, int book, text value + +class Publisher(Base): # needed + """""" + __tablename__ = 'publishers' + __table_args__ = {'autoload':True} + # has int id, text name + +class Rating(Base): # needed + """""" + __tablename__ = 'ratings' + __table_args__ = {'autoload':True} + # has int id, int rating + +class Series(Base): # needed + """""" + __tablename__ = 'series' + __table_args__ = {'autoload':True} + # has int id, text name + +class Tag(Base): # needed + """""" + __tablename__ = 'tags' + __table_args__ = {'autoload':True} + # has int id, text name + +class Book(Base): # needed + """""" + __tablename__ = 'books' + __table_args__ = {'autoload':True} + # has int id, text title, text sort, time timestamp, time pubdate, + # float series_index, text path + +class Book_author_link(Base): # needed + """""" + __tablename__ = 'books_authors_link' + __table_args__ = {'autoload':True} + # has int id, id book, id author + +class Book_publisher_link(Base): # needed + """""" + __tablename__ = 'books_publishers_link' + __table_args__ = {'autoload':True} + # has int id, id book, id publisher + +class Book_rating_link(Base): # needed + """""" + __tablename__ = 'books_ratings_link' + __table_args__ = {'autoload':True} + # has int id, id book, id rating + +class Book_series_link(Base): # needed + """""" + __tablename__ = 'books_series_link' + __table_args__ = {'autoload':True} + # has int id, id book, id series + +class Book_tags_link(Base): # needed + """""" + __tablename__ = 'books_tags_link' + __table_args__ = {'autoload':True} + # has int id, id book, id tag + + +#---------------------------------------------------------------------- +def loadSession(): + """""" + metadata = Base.metadata + Session = sessionmaker(bind=engine) + session = Session() + return session + +if __name__ == "__main__": + session = loadSession() + res = session.query(Book).all() + for i in res: + print(i.id, i.title, i.sort, i.timestamp, i.pubdate, i.series_index, i.path) + diff --git a/CalibreWebCompanion/library/migrations/0001_initial.py b/CalibreWebCompanion/library/migrations/0001_initial.py new file mode 100644 index 0000000..cd7a498 --- /dev/null +++ b/CalibreWebCompanion/library/migrations/0001_initial.py @@ -0,0 +1,206 @@ +# Generated by Django 3.0.8 on 2020-07-07 17:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Authors', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ('sort', models.TextField(blank=True, null=True)), + ('link', models.TextField()), + ], + options={ + 'db_table': 'authors', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Books', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.TextField()), + ('sort', models.TextField(blank=True, null=True)), + ('timestamp', models.TextField(blank=True, null=True)), + ('pubdate', models.TextField(blank=True, null=True)), + ('series_index', models.FloatField()), + ('author_sort', models.TextField(blank=True, null=True)), + ('isbn', models.TextField(blank=True, null=True)), + ('lccn', models.TextField(blank=True, null=True)), + ('path', models.TextField()), + ('flags', models.IntegerField()), + ('uuid', models.TextField(blank=True, null=True)), + ('has_cover', models.BooleanField(blank=True, null=True)), + ('last_modified', models.TextField()), + ], + options={ + 'db_table': 'books', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksAuthorsLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'books_authors_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksLanguagesLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('item_order', models.IntegerField()), + ], + options={ + 'db_table': 'books_languages_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksPublishersLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'books_publishers_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksRatingsLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'books_ratings_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksSeriesLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'books_series_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='BooksTagsLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'books_tags_link', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Comments', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField()), + ], + options={ + 'db_table': 'comments', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Data', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('book', models.IntegerField()), + ('format', models.TextField()), + ('uncompressed_size', models.IntegerField()), + ('name', models.TextField()), + ], + options={ + 'db_table': 'data', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Identifiers', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('book', models.IntegerField()), + ('type', models.TextField()), + ('val', models.TextField()), + ], + options={ + 'db_table': 'identifiers', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Languages', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lang_code', models.TextField()), + ], + options={ + 'db_table': 'languages', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Publishers', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ('sort', models.TextField(blank=True, null=True)), + ], + options={ + 'db_table': 'publishers', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Ratings', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.IntegerField(blank=True, null=True)), + ], + options={ + 'db_table': 'ratings', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Series', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ('sort', models.TextField(blank=True, null=True)), + ], + options={ + 'db_table': 'series', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Tags', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ], + options={ + 'db_table': 'tags', + 'managed': False, + }, + ), + ] diff --git a/CalibreWebCompanion/library/migrations/__init__.py b/CalibreWebCompanion/library/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CalibreWebCompanion/library/models.py b/CalibreWebCompanion/library/models.py new file mode 100644 index 0000000..a9177c9 --- /dev/null +++ b/CalibreWebCompanion/library/models.py @@ -0,0 +1,387 @@ +# This is an auto-generated Django model module. +# You'll have to do the following manually to clean this up: +# * Rearrange models' order +# * Make sure each model has one field with primary_key=True +# * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior +# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table +# Feel free to rename the models, but don't rename db_table values or field names. +from django.db import models +from django.urls import reverse + + +class Authors(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + link = models.TextField() + + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('author-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.name + + class Meta: + managed = False + db_table = 'authors' + + + +class Comments(models.Model): + book = models.ForeignKey("Books", db_column="book", on_delete=models.CASCADE) + text = models.TextField() + + + class Meta: + managed = False + db_table = 'comments' + indexes = [ + models.Index(fields=["book"], name="comments_idx"), + ] + + + +class Data(models.Model): + book = models.IntegerField() + format = models.TextField() + uncompressed_size = models.IntegerField() + name = models.TextField() + + class Meta: + managed = False + db_table = 'data' + indexes = [ + models.Index(fields=["format"], name="formats_idx"), + models.Index(fields=["book"], name="data_idx"), + ] + + +class Identifiers(models.Model): + book = models.IntegerField() + type = models.TextField() + val = models.TextField() + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.val + + class Meta: + managed = False + db_table = 'identifiers' + + +class Languages(models.Model): + lang_code = models.TextField() + + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('language-detail-view', args=[str(self.lang_code)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.lang_code + + class Meta: + managed = False + db_table = 'languages' + indexes = [ + models.Index(fields=["lang_code"], name="languages_idx"), + ] + + +class Publishers(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + released = models.ManyToManyField( + "Books", + through='BooksPublishersLink', + through_fields=('publisher', 'book'), + related_name="released" + ) + + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('publisher-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.name + + class Meta: + managed = False + db_table = 'publishers' + indexes = [ + models.Index(fields=["name"], name="publishers_idx"), + ] + + +class Ratings(models.Model): + rating = models.IntegerField(blank=True, null=True) + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('rating-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return str(self.rating) + + class Meta: + managed = False + db_table = 'ratings' + + +class Series(models.Model): + name = models.TextField() + sort = models.TextField(blank=True, null=True) + + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('series-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.name + + class Meta: + managed = False + db_table = 'series' + indexes = [ + models.Index(fields=["name"], name="series_idx"), + ] + + +class Tags(models.Model): + name = models.TextField() + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('tag-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.name + + class Meta: + managed = False + db_table = 'tags' + indexes = [ + models.Index(fields=["name"], name="tags_idx"), + ] + + +class Books(models.Model): + title = models.TextField() + sort = models.TextField(blank=True, null=True) + # This field type is a guess. + timestamp = models.TextField(blank=True, null=True) + # This field type is a guess. + pubdate = models.TextField(blank=True, null=True) + series_index = models.FloatField() + author_sort = models.TextField(blank=True, null=True) + isbn = models.TextField(blank=True, null=True) + lccn = models.TextField(blank=True, null=True) + path = models.TextField() + flags = models.IntegerField() + uuid = models.TextField(blank=True, null=True) + has_cover = models.BooleanField(blank=True, null=True) + last_modified = models.TextField() # This field type is a guess. + authors = models.ManyToManyField( + Authors, + through='BooksAuthorsLink', + through_fields=('book', 'author')) + languages = models.ManyToManyField( + Languages, + through='BooksLanguagesLink', + through_fields=('book', 'lang_code')) + publishers = models.ManyToManyField( + Publishers, + through='BooksPublishersLink', + through_fields=('book', 'publisher')) + series = models.ManyToManyField( + Series, + through='BooksSeriesLink', + through_fields=('book', 'series')) + tags = models.ManyToManyField( + Tags, + through='BooksTagsLink', + through_fields=('book', 'tag')) + ratings = models.ManyToManyField( + Ratings, + through='BooksRatingsLink', + through_fields=('book', 'rating')) + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('book-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.title + + class Meta: + managed = False + db_table = 'books' + indexes = [ + models.Index(fields=["sort"], name="books_idx"), + models.Index(fields=["author_sort"], name="authors_idx"), + ] + + +class BooksAuthorsLink(models.Model): + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + author = models.ForeignKey( + Authors, db_column="author", on_delete=models.CASCADE) + + class Meta: + managed = False + db_table = 'books_authors_link' + indexes = [ + models.Index(fields=["book"], name="books_authors_link_bidx"), + models.Index(fields=["author"], name="books_authors_link_aidx"), + ] + + +class BooksLanguagesLink(models.Model): + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + lang_code = models.ForeignKey( + Languages, db_column="lang_code", on_delete=models.CASCADE) + item_order = models.IntegerField() + + class Meta: + managed = False + db_table = 'books_languages_link' + indexes = [ + models.Index(fields=["book"], name="books_languages_link_bidx"), + models.Index(fields=["lang_code"], + name="books_languages_link_aidx"), + ] + + +class BooksPublishersLink(models.Model): + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + publisher = models.ForeignKey( + Publishers, db_column="publisher", on_delete=models.CASCADE) + + class Meta: + managed = False + db_table = 'books_publishers_link' + indexes = [ + models.Index(fields=["book"], name="books_publishers_link_bidx"), + models.Index(fields=["publisher"], + name="books_publishers_link_aidx"), + ] + + +class BooksRatingsLink(models.Model): # TODO add this somehow + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + rating = models.ForeignKey( + Ratings, db_column="rating", on_delete=models.CASCADE) + + class Meta: + managed = False + db_table = 'books_ratings_link' + + +class BooksSeriesLink(models.Model): + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + series = models.ForeignKey( + Series, db_column="series", on_delete=models.CASCADE) + + class Meta: + managed = False + db_table = 'books_series_link' + indexes = [ + models.Index(fields=["book"], name="books_series_link_bidx"), + models.Index(fields=["series"], name="books_series_link_aidx"), + ] + + +class BooksTagsLink(models.Model): + book = models.ForeignKey(Books, db_column="book", on_delete=models.CASCADE) + tag = models.ForeignKey(Tags, db_column="tag", on_delete=models.CASCADE) + + class Meta: + managed = False + db_table = 'books_tags_link' + indexes = [ + models.Index(fields=["book"], name="books_tags_link_bidx"), + models.Index(fields=["tag"], name="books_tags_link_aidx"), + ] + + +# class BooksPluginData(models.Model): +# book = models.IntegerField() +# name = models.TextField() +# val = models.TextField() + +# class Meta: +# managed = False +# db_table = 'books_plugin_data' + + +# class ConversionOptions(models.Model): +# format = models.TextField() +# book = models.IntegerField(blank=True, null=True) +# data = models.BinaryField() +# +# class Meta: +# managed = False +# db_table = 'conversion_options' +# +# class LibraryId(models.Model): +# uuid = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'library_id' +# +# class CustomColumns(models.Model): +# label = models.TextField() +# name = models.TextField() +# datatype = models.TextField() +# mark_for_delete = models.BooleanField() +# editable = models.BooleanField() +# display = models.TextField() +# is_multiple = models.BooleanField() +# normalized = models.BooleanField() +# +# class Meta: +# managed = False +# db_table = 'custom_columns' +# +# class Preferences(models.Model): +# key = models.TextField() +# val = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'preferences' +# +# class Feeds(models.Model): +# title = models.TextField() +# script = models.TextField() +# +# class Meta: +# managed = False +# db_table = 'feeds' +# +# +# class LastReadPositions(models.Model): +# book = models.IntegerField() +# format = models.TextField() +# user = models.TextField() +# device = models.TextField() +# cfi = models.TextField() +# epoch = models.FloatField() +# pos_frac = models.FloatField() +# +# class Meta: +# managed = False +# db_table = 'last_read_positions' + + +# class MetadataDirtied(models.Model): +# book = models.IntegerField() + +# class Meta: +# managed = False +# db_table = 'metadata_dirtied' diff --git a/CalibreWebCompanion/library/static/css/styles.css b/CalibreWebCompanion/library/static/css/styles.css new file mode 100644 index 0000000..b49e6a5 --- /dev/null +++ b/CalibreWebCompanion/library/static/css/styles.css @@ -0,0 +1,58 @@ + /* Fixed sidenav, full height */ + .sidenav { + height: 100%; + width: 200px; + position: fixed; + z-index: 1; + top: 0; + left: 0; + background-color: #111; + overflow-x: hidden; + padding-top: 20px; + } + + /* Style the sidenav links and the dropdown button */ + .sidenav a, .dropdown-btn { + padding: 6px 8px 6px 16px; + text-decoration: none; + font-size: 20px; + color: #818181; + display: block; + border: none; + background: none; + width:100%; + text-align: left; + cursor: pointer; + outline: none; + } + + /* On mouse-over */ + .sidenav a:hover, .dropdown-btn:hover { + color: #f1f1f1; + } + + /* Main content */ + .main { + margin-left: 200px; /* Same as the width of the sidenav */ + font-size: 20px; /* Increased text to enable scrolling */ + padding: 0px 10px; + } + + /* Add an active class to the active dropdown button */ + .active { + background-color: green; + color: white; + } + + /* Dropdown container (hidden by default). Optional: add a lighter background color and some left padding to change the design of the dropdown content */ + .dropdown-container { + display: none; + background-color: #262626; + padding-left: 8px; + } + + /* Optional: Style the caret down icon */ + .fa-caret-down { + float: right; + padding-right: 8px; + } \ No newline at end of file diff --git a/CalibreWebCompanion/library/static/js/nav.js b/CalibreWebCompanion/library/static/js/nav.js new file mode 100644 index 0000000..9503bdc --- /dev/null +++ b/CalibreWebCompanion/library/static/js/nav.js @@ -0,0 +1,14 @@ +var dropdown = document.getElementsByClassName("dropdown-btn"); +var i; + +for (i = 0; i < dropdown.length; i++) { + dropdown[i].addEventListener("click", function() { + this.classList.toggle("active"); + var dropdownContent = this.nextElementSibling; + if (dropdownContent.style.display === "block") { + dropdownContent.style.display = "none"; + } else { + dropdownContent.style.display = "block"; + } + }); +} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/base.html b/CalibreWebCompanion/library/templates/base.html new file mode 100644 index 0000000..0b47aae --- /dev/null +++ b/CalibreWebCompanion/library/templates/base.html @@ -0,0 +1,54 @@ + + + + {% block title %}Local Library{% endblock %} + + + + + {% load static %} + + + +
+
+
+ {% block sidebar %} +
+ About + Services + Clients + Contact + + + {{counter}} books +
+ {% endblock %} +
+
{% block content %}{% endblock %}
+
+
+ + + \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/authors_detail.html b/CalibreWebCompanion/library/templates/library/authors_detail.html new file mode 100644 index 0000000..d3e832e --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/authors_detail.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

{{authors}}

+ {% if books %} + + {% else %} + {%endif%} + + + + +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/authors_list.html b/CalibreWebCompanion/library/templates/library/authors_list.html new file mode 100644 index 0000000..2da171f --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/authors_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

Author List

+ {% if authors_list %} + + {% else %} +

There are no authors in the library.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/books_detail.html b/CalibreWebCompanion/library/templates/library/books_detail.html new file mode 100644 index 0000000..9265b46 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/books_detail.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

{{books.title}} by + {% if books.authors %} + {% for author in books.authors.all %} + {{author.name}} + {%endfor%} + {% else %} + + {{books.author_sort}} + {%endif%} + + Published by + {% if books.publishers %} + {% for pub in books.publishers.all %} + {{pub.name}} + {%endfor%} + {% else %} + Unknown + + {%endif%} + + Tags: + {% if books.tags %} + {% for tag in books.tags.all %} + {{tag.name}}, + {%endfor%} + {% else %} + {%endif%} + + Rating: + {% if books.ratings %} + {% for rating in books.ratings.all %} + {{rating}} + {%endfor%} + {% else %} + {%endif%} + + {{book.publisher}} +

+{{comment}} + + + +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/books_list.html b/CalibreWebCompanion/library/templates/library/books_list.html new file mode 100644 index 0000000..81c97f8 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/books_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

Book List

+ {% if books_list %} +
    + {% for book in books_list %} +
  • + {{ book.title }} ({{book.author_sort}}) +
  • + {% endfor %} +
+ {% else %} +

There are no books in the library.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/publishers_detail.html b/CalibreWebCompanion/library/templates/library/publishers_detail.html new file mode 100644 index 0000000..834da20 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/publishers_detail.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

{{authors}}

+ + {% if publishers.released %} +
    + {% for book in publishers.released.all %} + +
  • {{book.title}}
  • + {%endfor%} +
+ {% else %} + {%endif%} + + +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/publishers_list.html b/CalibreWebCompanion/library/templates/library/publishers_list.html new file mode 100644 index 0000000..a9557be --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/publishers_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

Publishers List

+ {% if publishers_list %} + + {% else %} +

There are no publishers in the library.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/ratings_detail.html b/CalibreWebCompanion/library/templates/library/ratings_detail.html new file mode 100644 index 0000000..4cfcb26 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/ratings_detail.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} + +

{{ratings}}

+ {% if books %} + + {% else %} + {%endif%} + + + + +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/ratings_list.html b/CalibreWebCompanion/library/templates/library/ratings_list.html new file mode 100644 index 0000000..b4d09f4 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/ratings_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

Ratings List

+ {% if ratings_list %} + + {% else %} +

There are no ratings in the library.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/tags_detail.html b/CalibreWebCompanion/library/templates/library/tags_detail.html new file mode 100644 index 0000000..8b8edbc --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/tags_detail.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} + +

{{tags}}

+ {% if books %} + + {% else %} + {%endif%} + + + + +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templates/library/tags_list.html b/CalibreWebCompanion/library/templates/library/tags_list.html new file mode 100644 index 0000000..7d26ee9 --- /dev/null +++ b/CalibreWebCompanion/library/templates/library/tags_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +{% load static %} +

Tags List

+ {% if tags_list %} + + {% else %} +

There are no tags in the library.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/CalibreWebCompanion/library/templatetags/__init__.py b/CalibreWebCompanion/library/templatetags/__init__.py new file mode 100644 index 0000000..3eaef43 --- /dev/null +++ b/CalibreWebCompanion/library/templatetags/__init__.py @@ -0,0 +1 @@ +from . import custom \ No newline at end of file diff --git a/CalibreWebCompanion/library/templatetags/custom.py b/CalibreWebCompanion/library/templatetags/custom.py new file mode 100644 index 0000000..3662f74 --- /dev/null +++ b/CalibreWebCompanion/library/templatetags/custom.py @@ -0,0 +1,9 @@ +from django import template +from ..models import Books + +register = template.Library +print("I ACTUALLY CAME HEReEe") + +@register.simple_tag +def dummy(): + return Books.objects.count() \ No newline at end of file diff --git a/CalibreWebCompanion/library/tests.py b/CalibreWebCompanion/library/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/CalibreWebCompanion/library/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/CalibreWebCompanion/library/urls.py b/CalibreWebCompanion/library/urls.py new file mode 100644 index 0000000..8e03baf --- /dev/null +++ b/CalibreWebCompanion/library/urls.py @@ -0,0 +1,20 @@ +from django.urls import path +from . import views + + + +urlpatterns = [ + path('authors/', views.AuthorListView.as_view(), name='authors'), + path('books/', views.BookListView.as_view(), name='books'), + path('publishers/', views.PublisherListView.as_view(), name='publishers'), + path('ratings/', views.RatingListView.as_view(), name='ratings'), + path('tags/', views.TagListView.as_view(), name='tags'), + + path('author/', views.AuthorDetailView.as_view(), name='author-detail-view'), + path('book/', views.BookDetailView.as_view(), name='book-detail-view'), + path('publisher/', views.PublisherDetailView.as_view(), name='publisher-detail-view'), + path('rating/', views.RatingDetailView.as_view(), name='rating-detail-view'), + path('tag/', views.TagDetailView.as_view(), name='tag-detail-view'), + + +] \ No newline at end of file diff --git a/CalibreWebCompanion/library/views.py b/CalibreWebCompanion/library/views.py new file mode 100644 index 0000000..d4d724e --- /dev/null +++ b/CalibreWebCompanion/library/views.py @@ -0,0 +1,81 @@ +from django.shortcuts import render +from django.views import generic +from .models import Authors, Books, Comments, Ratings, BooksAuthorsLink, Publishers, Tags, BooksTagsLink, BooksRatingsLink, Data + + +class AuthorListView(generic.ListView): + model = Authors + + +class BookListView(generic.ListView): + model = Books + + +class PublisherListView(generic.ListView): + model = Publishers + + +class RatingListView(generic.ListView): + model = Ratings + + +class TagListView(generic.ListView): + model = Tags + + +class AuthorDetailView(generic.DetailView): + model = Authors + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(AuthorDetailView, self).get_context_data(**kwargs) + # Create any data and add it to the context + books = BooksAuthorsLink.objects.filter(author=context["object"].id) + context['books'] = context['books'] = sorted([b.book for b in books.all()], key=lambda x: x.title) + return context + + +class BookDetailView(generic.DetailView): + model = Books + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(BookDetailView, self).get_context_data(**kwargs) + # Create any data and add it to the context + try: + context['comment'] = Comments.objects.get( + book=context["object"].id).text + except: + pass + context["imgpath"] = context["object"].path + "/cover.jpg" + download = Data.objects.get(book=context["object"].id) + context["download"] = f"{context['object'].path}/{download.name}.{download.format}" + return context + + +class PublisherDetailView(generic.DetailView): + model = Publishers + + +class RatingDetailView(generic.DetailView): + model = Ratings + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(RatingDetailView, self).get_context_data(**kwargs) + # Create any data and add it to the context + books = BooksRatingsLink.objects.filter(rating=context["object"].id) + context['books'] = sorted([b.book for b in books.all()], key=lambda x: x.title) + return context + + +class TagDetailView(generic.DetailView): + model = Tags + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(TagDetailView, self).get_context_data(**kwargs) + # Create any data and add it to the context + books = BooksTagsLink.objects.filter(tag=context["object"].id) + context['books'] = sorted([b.book for b in books.all()], key=lambda x: x.title) + return context diff --git a/CalibreWebCompanion/manage.py b/CalibreWebCompanion/manage.py new file mode 100644 index 0000000..bd30ba8 --- /dev/null +++ b/CalibreWebCompanion/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CalibreWebCompanion.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fc27c8 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# requirements +Django 3.0 + +# how to use: +Edit `./CalibreWebCompanion/CalibreWebCompanion/settings`. +Set CALIBREPATH to the path of your library +`./CalibreWebCompanion` +run `./manage.py runserver` + +this is in development mode. don't actually use it or release it like this. The debug info it shows is spicy. \ No newline at end of file diff --git a/models.vsdx b/models.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..8ccda43895105c539e900ab8653454fb71000c52 GIT binary patch literal 55251 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 S(eIvi9K003r^ue&Z~p=FH%yxV literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e9b1726 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +django=3.0.8 \ No newline at end of file