From 5e36fc1d1659761bf9f64f1a5cfec55dfe109166 Mon Sep 17 00:00:00 2001 From: Raelon Masters Date: Fri, 31 Jul 2020 02:27:07 -0400 Subject: [PATCH 1/2] Added Ajax handler for book info call --- src/interface/static/js/pyshelf_ux.js | 64 +++++++++++++++++++++------ src/interface/templates/index.html | 4 +- src/interface/views.py | 35 ++++++++------- 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/interface/static/js/pyshelf_ux.js b/src/interface/static/js/pyshelf_ux.js index f23e013..8d68491 100755 --- a/src/interface/static/js/pyshelf_ux.js +++ b/src/interface/static/js/pyshelf_ux.js @@ -18,7 +18,7 @@ $(document).ready(function(){ var max_height = win_height - (hdr_height + ftr_height) - (scr_height - win_height); // Set our available height var u_string = "Username"; var p_string = "Password"; - var s_string = "search by Title, Author, Tags, or Collections" + var s_string = "search by Title, Author, Tags, or Collections"; customlog([cmp_height]); $(".search_submit").click(function(){ var query = $('.nav_search').val(); @@ -35,8 +35,8 @@ $(document).ready(function(){ }); $('.nav_link').on('mouseover', function (e){ var popover_str = $(this).attr('alt'); - x = $(this).offset().left - y = $(this).offset().top + x = $(this).offset().left; + y = $(this).offset().to; $('.popover').html(popover_str); $('.popover').css('left', x+nav_width); $('.popover').css('top', y); @@ -44,15 +44,15 @@ $(document).ready(function(){ }); $('.nav_link').on('mouseout', function (e){ var popover_str = $(this).attr('alt'); - x = $(this).offset().left - y = $(this).offset().top + x = $(this).offset().left; + y = $(this).offset().top; $('.popover').html(popover_str); $('.popover').css('left', x); $('.popover').css('top', y); $('.popover').css('display','none'); }); $('#btn_collections').on('click', function (e){ - $('.hidden.vert-nav.collections').toggle() + $('.hidden.vert-nav.collections').toggle(); }); $('.input_box').on('click', function(){ $(this).attr("value",""); @@ -78,16 +78,16 @@ $(document).ready(function(){ var optionSelected = $(this).find("option:selected"); var valueSelected = optionSelected.val(); var textSelected = optionSelected.text(); - window.location.href="/"+valueSelected + window.location.href="/"+valueSelected; }); $('#btn-home').on("click", function(){ _location = $(this).attr('data-location'); window.location.href=_location; }); $('#flip_sort').on("click", function(){ - window.location.href="/flip_sort/"+$("#_order").val() + window.location.href="/flip_sort/"+$("#_order").val(); }); - $('#search_string').html(" "+$('#_search').val().substr(0,15)+"") + $('#search_string').html(" "+$('#_search').val().substr(0,15)+""); $('#pop_over_0').dialog({ autoOpen: false }); resize_search(); @@ -97,10 +97,10 @@ $(document).ready(function(){ window.location.href = '/show_collection/'+$(this).attr('data'); }); $('#btn_login').on('click', function() { - var isopen = $('#pop_over_0').dialog("isOpen") + var isopen = $('#pop_over_0').dialog("isOpen"); if (isopen) { $('#pop_over_0').dialog("close"); - return + return; } customlog(['Login Clicked']); $.ajax({ @@ -138,10 +138,10 @@ $(document).ready(function(){ window.location.href = '/logout'; }); $('#coll_button').on('click', function(){ - var isopen = $('#pop_over_0').dialog("isOpen") + var isopen = $('#pop_over_0').dialog("isOpen"); if (isopen){ $('#pop_over_0').dialog("close"); - return + return; } customlog(['Collections Clicked']); $.ajax({ @@ -171,7 +171,43 @@ $(document).ready(function(){ error: function(response){ customlog(["Failure", response]); } - }) + }); + }); + $('.info-button').on('click', function(){ + var isopen = $('#pop_over_0').dialog("isOpen"); + if (isopen){ + $('#pop_over_0').dialog("close"); + return; + } + customlog(['Book Details Clicked', $(this).attr('data')]); + $.ajax({ + type: "GET", url: "/live", data: {hook: 'details', pk:$(this).attr('data')}, + success: function(response){ + // Set the dialog title + $('#pop_over_0').dialog({ + title: "Collections", + maxHeight: (win_height-100), + minWidth: $("#horiz_nav_main").width(), + hide: { effect: "blind", duration: 1000 }, + show: { effect: "blind", duration: 1000 }, + position: { my: "top", at: "bottom", of: $("#horiz_nav_main") + } + }); + // clear and create a new container + $('#pop_over_0').html('
'); + // Populate the container from response.data + $.each(response.data, function(index, value){ + $('#collections').append("
"+value+"
"); + }); + // Close the container + $('#pop_over').append('
'); + // Now open this dialog + $('#pop_over_0').dialog("open"); + }, + error: function(response){ + customlog(["Failure", response]); + } + }); }); }); function resize_search(win_width){ diff --git a/src/interface/templates/index.html b/src/interface/templates/index.html index 60bafe3..731e7ab 100755 --- a/src/interface/templates/index.html +++ b/src/interface/templates/index.html @@ -120,8 +120,8 @@ - - + + diff --git a/src/interface/views.py b/src/interface/views.py index 449bc58..e15276a 100755 --- a/src/interface/views.py +++ b/src/interface/views.py @@ -1,22 +1,22 @@ +import json import os from base64 import b64decode, b64encode from pathlib import Path from backend.lib.config import Config +from django.conf import settings +from django.contrib import auth +from django.contrib.auth import authenticate, get_user_model, login, logout +from django.contrib.auth.models import User from django.db import models from django.http import JsonResponse -from django.shortcuts import HttpResponse, render, redirect # render_to_response -from django.utils.text import slugify -from django.contrib.auth import login, authenticate, logout -from django.contrib import auth -from django.contrib.auth.models import User -from django.conf import settings -from django.contrib.auth import get_user_model -from django.utils.datastructures import MultiValueDictKeyError -import json -from .forms import SignUpForm, UserLoginForm -from .models import Books, Collections, Navigation, Favorites, User +from django.shortcuts import HttpResponse, redirect, render # render_to_response from django.template.loader import render_to_string +from django.utils.datastructures import MultiValueDictKeyError +from django.utils.text import slugify + +from .forms import SignUpForm, UserLoginForm +from .models import Books, Collections, Favorites, Navigation, User config = Config(Path("../")) @@ -380,6 +380,12 @@ def collections_list(): def live(request, **kwargs): + """ + Respond to live requests. Primarily used as a response object for Ajax calls + :param GET['hook']: collection_listing, book_details, register + :param kwargs['pk']: Primary key of requested object + :return: JsonResponse Object, status response code + """ err_txt = {"err": "There is no responder for your request"} try: hook = request.GET['hook'] except MultiValueDictKeyError as e: return JsonResponse(err_txt, status=404) @@ -445,10 +451,10 @@ def payload(request, query, _set, _limit, _order, **kwargs): _r, _r_len = \ _results.order_by(_order)[_set_min:_set_max],\ _results.count() - else: + else: # No new query was passed try: query = request.session['cached_query'] # Is there a cached query? - if query == None: raise KeyError + if query == None: raise KeyError # No cached query exists jump to KeyError if request.session['ascending']: _results = Books().generic_search(query) else: _results = Books().generic_search(query).reverse() @@ -464,7 +470,7 @@ def payload(request, query, _set, _limit, _order, **kwargs): _bookstats = Books.objects.all().count() if (_r_len): _btotal = str(_r_len) else: _btotal = str(_bookstats) - + # Format the payload and return it to the view return { "Books": _r, "Set": str(_set), @@ -479,4 +485,3 @@ def payload(request, query, _set, _limit, _order, **kwargs): "SearchLen": _r_len, "Order": _order, } - From 0fc3d445fc1f2d2f06f64f5f38213d30ae541149 Mon Sep 17 00:00:00 2001 From: Raelon Masters Date: Sun, 2 Aug 2020 13:17:41 -0400 Subject: [PATCH 2/2] Django secret generated per install, --- config.json | 2 +- doxygen.conf | 4 ++-- importBooks | 3 ++- installer | 10 +++++----- src/backend/lib/config.py | 2 ++ src/frontend/settings.py | 4 ++-- src/frontend/urls.py | 4 ++-- src/interface/admin.py | 1 - src/interface/static/css/main.css | 4 ++++ src/interface/static/css/main.scss | 3 +++ src/interface/static/js/pyshelf_ux.js | 18 ++++++++++++------ src/interface/views.py | 17 ++++++++++++++--- 12 files changed, 49 insertions(+), 23 deletions(-) mode change 100755 => 100644 config.json diff --git a/config.json b/config.json old mode 100755 new mode 100644 index 2347e08..b7f990d --- a/config.json +++ b/config.json @@ -1 +1 @@ -{"TITLE": "pyShelf E-Book Server", "VERSION": "0.6.0", "BOOKPATH": "/home/raelon/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*", "hostname": "localhost", "webport": "8000", "wsgiport": "8001"} \ No newline at end of file +{"TITLE": "pyShelf E-Book Server", "VERSION": "0.6.0", "BOOKPATH": "/home/raelon/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*", "hostname": "localhost", "webport": "8000", "wsgiport": "8001", "SECRET": "r0m#@$d(cs^si9_jmm)z-z#6-4-(snoctd)l(4becso9k=dwvs"} \ No newline at end of file diff --git a/doxygen.conf b/doxygen.conf index b33a684..44466aa 100755 --- a/doxygen.conf +++ b/doxygen.conf @@ -38,13 +38,13 @@ PROJECT_NAME = "pyShelf Open Source Ebook Server" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.4.1 +PROJECT_NUMBER = 0.6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "Open source, console based E-book server" +PROJECT_BRIEF = "FOSS E-Book Server, https://pyshelf.com" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 diff --git a/importBooks b/importBooks index 272b643..c3ad6e8 100755 --- a/importBooks +++ b/importBooks @@ -5,9 +5,10 @@ import sys from src.backend.lib.storage import Storage from src.backend.pyShelf_ScanLibrary import execute_scan - +from src.backend.pyShelf_MakeCollections import MakeCollections PRG_PATH = pathlib.Path.cwd() LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") sys.path.insert(0, PRG_PATH) print("\n") execute_scan(PRG_PATH) +MakeCollections(PRG_PATH) diff --git a/installer b/installer index 3440163..217f076 100755 --- a/installer +++ b/installer @@ -1,12 +1,9 @@ -#!/usr/bin/ env python +#!/usr/bin/ env python3 import json import os import pathlib import platform -import pprint -import subprocess as sp -import sys -from shutil import copyfile +from django.core.management.utils import get_random_secret_key import psutil from src.backend.lib.display import TerminalDisplay @@ -34,6 +31,8 @@ class Configuration: json.dump(data, backup_file) return data except Exception as e: + f = open(str(self._cp),"w") + f print(e) return False @@ -225,6 +224,7 @@ for key in install_answers: config[key["name"]] = key["answer"] # config["USER"] = os.environ["USER"] config["USER"] = "pyshelf" +config["SECRET"] = get_random_secret_key() # Write configuration Configuration().write_file(config) # Start checking for our list of required services diff --git a/src/backend/lib/config.py b/src/backend/lib/config.py index c36c0c3..6b2bfc7 100755 --- a/src/backend/lib/config.py +++ b/src/backend/lib/config.py @@ -17,6 +17,7 @@ class Config: """ _cp = pathlib.Path.joinpath(root, self._fp) _data = self.open_file(_cp) + breakpoint() self.book_path = _data["BOOKPATH"] self.TITLE = _data["TITLE"] self.VERSION = _data["VERSION"] @@ -36,6 +37,7 @@ class Config: self.allowed_hosts = _data["ALLOWED_HOSTS"] self.db_user = _data["USER"] self.db_pass = _data["PASSWORD"] + self.SECRET = _data["SECRET"] def open_file(self, _cp): """ diff --git a/src/frontend/settings.py b/src/frontend/settings.py index 60d1ce9..12e87f6 100755 --- a/src/frontend/settings.py +++ b/src/frontend/settings.py @@ -30,8 +30,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18" - +# SECRET_KEY = "@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18" +SECRET_KEY = CONFIG.SECRET # SECURITY WARNING: don't run with debug turned on in production! DEBUG = TEMPLATE_DEBUG = True if DEBUG is True: diff --git a/src/frontend/urls.py b/src/frontend/urls.py index 2d8f4c6..532dc8e 100755 --- a/src/frontend/urls.py +++ b/src/frontend/urls.py @@ -15,17 +15,17 @@ Including another URLconf """ from django.conf import settings from django.contrib import admin -from django.urls import include, path, re_path from django.contrib.auth import views as auth_views from django.contrib.auth.models import User from django.shortcuts import HttpResponse +from django.urls import include, path, re_path from interface import views urlpatterns = [ path("admin/", admin.site.urls), path("", views.index, name="index"), path("home", views.home, name="home"), - re_path("^live", views.live, name="liverequest"), + re_path("^live$", views.live, name="live"), path("sort/<_order>", views.index, name="index"), path("flip_sort/<_order>", views.flip_sort, name="index"), path("download/", views.download, name="download"), diff --git a/src/interface/admin.py b/src/interface/admin.py index 96c61be..56212ba 100755 --- a/src/interface/admin.py +++ b/src/interface/admin.py @@ -22,7 +22,6 @@ class CustomUserAdmin(UserAdmin): ) -# Register your models here. admin.site.register(Books) admin.site.register(Collections) admin.site.register(Favorites) diff --git a/src/interface/static/css/main.css b/src/interface/static/css/main.css index a946d94..0e2d542 100644 --- a/src/interface/static/css/main.css +++ b/src/interface/static/css/main.css @@ -10754,4 +10754,8 @@ a.nav_link { .center { width: fit-content !important; margin: auto; +} + +.collection { + cursor: pointer; } \ No newline at end of file diff --git a/src/interface/static/css/main.scss b/src/interface/static/css/main.scss index 065059a..d13398c 100755 --- a/src/interface/static/css/main.scss +++ b/src/interface/static/css/main.scss @@ -618,3 +618,6 @@ a.nav_link { width: fit-content !important; margin: auto; } +.collection{ + cursor: pointer; +} diff --git a/src/interface/static/js/pyshelf_ux.js b/src/interface/static/js/pyshelf_ux.js index 8d68491..008cf3b 100755 --- a/src/interface/static/js/pyshelf_ux.js +++ b/src/interface/static/js/pyshelf_ux.js @@ -185,20 +185,26 @@ $(document).ready(function(){ success: function(response){ // Set the dialog title $('#pop_over_0').dialog({ - title: "Collections", + title: response.data['title'], maxHeight: (win_height-100), minWidth: $("#horiz_nav_main").width(), hide: { effect: "blind", duration: 1000 }, show: { effect: "blind", duration: 1000 }, - position: { my: "top", at: "bottom", of: $("#horiz_nav_main") + position: { my: "center center", at: "center center", of: window } }); // clear and create a new container - $('#pop_over_0').html('
'); + $('#pop_over_0').html('
'); // Populate the container from response.data - $.each(response.data, function(index, value){ - $('#collections').append("
"+value+"
"); - }); + $('#book_expanded').append('
Title
'+response.data['title']+'
') + $('#book_expanded').append('
Author
'+response.data['author']+'
') + if (response.data['description']!== null){ + $('#book_expanded').append('
Expanded Description
'+response.data['description']+'
') + + } + if (response.data['tags'] !== null){ + $('#book_expanded').append('
Tags
'+response.data['tags']+'
') + } // Close the container $('#pop_over').append('
'); // Now open this dialog diff --git a/src/interface/views.py b/src/interface/views.py index e15276a..030c70d 100755 --- a/src/interface/views.py +++ b/src/interface/views.py @@ -393,8 +393,11 @@ def live(request, **kwargs): if hook == "collection_listing": collections = collections_list() return JsonResponse({"data": collections}, status=200) - elif hook == "book_details": - return JsonResponse({"data": Books.objects.get(pk=kwargs['pk'])}, status=200) + elif hook == "details": + try: _pk = request.GET['pk'] + except KeyError as e: return False + book = book_details(Books.objects.get(pk=_pk)) + return JsonResponse({"data": book}, status=200) elif hook == "register": html = render_to_string('signup.html', {'form': SignUpForm}, request) html += render_to_string('login.html', {'form': UserLoginForm}, request) @@ -403,7 +406,15 @@ def live(request, **kwargs): return JsonResponse({"data": "Response sent"}, status=200) - +def book_details(book): + return { + 'title': book.title, + 'author': book.author, + 'description': book.description, + 'tags': book.tags, + 'rights': book.rights, + 'pk': book.id + } def payload(request, query, _set, _limit, _order, **kwargs): """ Return formatted data to template