Merge changes from development

This commit is contained in:
MartenBE
2020-08-03 00:48:45 +02:00
14 changed files with 143 additions and 43 deletions

1
.gitignore vendored
View File

@@ -131,6 +131,7 @@ uwsgi.ini
installer.log installer.log
pyshelf_nginx.conf pyshelf_nginx.conf
!docker/pyshelf_nginx.conf !docker/pyshelf_nginx.conf
.env
tags tags
TAGS TAGS
config.json config.json

View File

@@ -8,5 +8,6 @@
"USER": "pyshelf", "USER": "pyshelf",
"PASSWORD": "pyshelf", "PASSWORD": "pyshelf",
"BOOKSHELF": "data/shelf.json", "BOOKSHELF": "data/shelf.json",
"ALLOWED_HOSTS": "*" "ALLOWED_HOSTS": "*",
"SECRET": "r0m#@$d(cs^si9_jmm)z-z#6-4-(snoctd)l(4becso9k=dwvs"
} }

32
docker/development.docker-compose.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
version: "3.7"
# This file is used to test the docker image. To host pyShelf yourself for
# production, please use the official pyShelf image on
# https://hub.docker.com/r/pyshelf/pyshelf
# For development, use the following command in the root folder:
# `docker-compose -f .\docker\development.docker-compose.yml up --build`
services:
db:
image: "postgres"
environment:
- "POSTGRES_PASSWORD=pyshelf"
- "POSTGRES_USER=pyshelf"
- "POSTGRES_DB=pyshelf"
volumes:
- "db_data:/var/lib/postgresql/data/"
pyshelf:
build:
context: ..
dockerfile: ./docker/Dockerfile
ports:
- "8080:8000"
volumes:
- "${LOCAL_BOOK_DIR}:/books"
depends_on:
- db
volumes:
db_data:

View File

@@ -38,13 +38,13 @@ PROJECT_NAME = "pyShelf Open Source Ebook Server"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # 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 # 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 # 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. # 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 # 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 # in the documentation. The maximum height of the logo should not exceed 55

View File

@@ -5,9 +5,10 @@ import sys
from src.backend.lib.storage import Storage from src.backend.lib.storage import Storage
from src.backend.pyShelf_ScanLibrary import execute_scan from src.backend.pyShelf_ScanLibrary import execute_scan
from src.backend.pyShelf_MakeCollections import MakeCollections
PRG_PATH = pathlib.Path.cwd() PRG_PATH = pathlib.Path.cwd()
LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib")
sys.path.insert(0, PRG_PATH) sys.path.insert(0, PRG_PATH)
print("\n") print("\n")
execute_scan(PRG_PATH) execute_scan(PRG_PATH)
MakeCollections(PRG_PATH)

View File

@@ -36,6 +36,7 @@ class Config:
self.allowed_hosts = _data["ALLOWED_HOSTS"] self.allowed_hosts = _data["ALLOWED_HOSTS"]
self.db_user = _data["USER"] self.db_user = _data["USER"]
self.db_pass = _data["PASSWORD"] self.db_pass = _data["PASSWORD"]
self.SECRET = _data["SECRET"]
def open_file(self, _cp): def open_file(self, _cp):
""" """

View File

@@ -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/ # See https://docs.djangoproject.com/en/2.2/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 = "@(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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = TEMPLATE_DEBUG = False DEBUG = TEMPLATE_DEBUG = False
if DEBUG is True: if DEBUG is True:

View File

@@ -15,17 +15,17 @@ Including another URLconf
""" """
from django.conf import settings from django.conf import settings
from django.contrib import admin 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 import views as auth_views
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.shortcuts import HttpResponse from django.shortcuts import HttpResponse
from django.urls import include, path, re_path
from interface import views from interface import views
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("", views.index, name="index"), path("", views.index, name="index"),
path("home", views.home, name="home"), 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("sort/<_order>", views.index, name="index"),
path("flip_sort/<_order>", views.flip_sort, name="index"), path("flip_sort/<_order>", views.flip_sort, name="index"),
path("download/<pk>", views.download, name="download"), path("download/<pk>", views.download, name="download"),

View File

@@ -22,7 +22,6 @@ class CustomUserAdmin(UserAdmin):
) )
# Register your models here.
admin.site.register(Books) admin.site.register(Books)
admin.site.register(Collections) admin.site.register(Collections)
admin.site.register(Favorites) admin.site.register(Favorites)

View File

@@ -10754,4 +10754,8 @@ a.nav_link {
.center { .center {
width: fit-content !important; width: fit-content !important;
margin: auto; margin: auto;
}
.collection {
cursor: pointer;
} }

View File

@@ -618,3 +618,6 @@ a.nav_link {
width: fit-content !important; width: fit-content !important;
margin: auto; margin: auto;
} }
.collection{
cursor: pointer;
}

View File

@@ -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 max_height = win_height - (hdr_height + ftr_height) - (scr_height - win_height); // Set our available height
var u_string = "Username"; var u_string = "Username";
var p_string = "Password"; 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]); customlog([cmp_height]);
$(".search_submit").click(function(){ $(".search_submit").click(function(){
var query = $('.nav_search').val(); var query = $('.nav_search').val();
@@ -35,8 +35,8 @@ $(document).ready(function(){
}); });
$('.nav_link').on('mouseover', function (e){ $('.nav_link').on('mouseover', function (e){
var popover_str = $(this).attr('alt'); var popover_str = $(this).attr('alt');
x = $(this).offset().left x = $(this).offset().left;
y = $(this).offset().top y = $(this).offset().to;
$('.popover').html(popover_str); $('.popover').html(popover_str);
$('.popover').css('left', x+nav_width); $('.popover').css('left', x+nav_width);
$('.popover').css('top', y); $('.popover').css('top', y);
@@ -44,15 +44,15 @@ $(document).ready(function(){
}); });
$('.nav_link').on('mouseout', function (e){ $('.nav_link').on('mouseout', function (e){
var popover_str = $(this).attr('alt'); var popover_str = $(this).attr('alt');
x = $(this).offset().left x = $(this).offset().left;
y = $(this).offset().top y = $(this).offset().top;
$('.popover').html(popover_str); $('.popover').html(popover_str);
$('.popover').css('left', x); $('.popover').css('left', x);
$('.popover').css('top', y); $('.popover').css('top', y);
$('.popover').css('display','none'); $('.popover').css('display','none');
}); });
$('#btn_collections').on('click', function (e){ $('#btn_collections').on('click', function (e){
$('.hidden.vert-nav.collections').toggle() $('.hidden.vert-nav.collections').toggle();
}); });
$('.input_box').on('click', function(){ $('.input_box').on('click', function(){
$(this).attr("value",""); $(this).attr("value","");
@@ -78,16 +78,16 @@ $(document).ready(function(){
var optionSelected = $(this).find("option:selected"); var optionSelected = $(this).find("option:selected");
var valueSelected = optionSelected.val(); var valueSelected = optionSelected.val();
var textSelected = optionSelected.text(); var textSelected = optionSelected.text();
window.location.href="/"+valueSelected window.location.href="/"+valueSelected;
}); });
$('#btn-home').on("click", function(){ $('#btn-home').on("click", function(){
_location = $(this).attr('data-location'); _location = $(this).attr('data-location');
window.location.href=_location; window.location.href=_location;
}); });
$('#flip_sort').on("click", function(){ $('#flip_sort').on("click", function(){
window.location.href="/flip_sort/"+$("#_order").val() window.location.href="/flip_sort/"+$("#_order").val();
}); });
$('#search_string').html("<i> "+$('#_search').val().substr(0,15)+"</i>") $('#search_string').html("<i> "+$('#_search').val().substr(0,15)+"</i>");
$('#pop_over_0').dialog({ autoOpen: false }); $('#pop_over_0').dialog({ autoOpen: false });
resize_search(); resize_search();
@@ -97,10 +97,10 @@ $(document).ready(function(){
window.location.href = '/show_collection/'+$(this).attr('data'); window.location.href = '/show_collection/'+$(this).attr('data');
}); });
$('#btn_login').on('click', function() { $('#btn_login').on('click', function() {
var isopen = $('#pop_over_0').dialog("isOpen") var isopen = $('#pop_over_0').dialog("isOpen");
if (isopen) { if (isopen) {
$('#pop_over_0').dialog("close"); $('#pop_over_0').dialog("close");
return return;
} }
customlog(['Login Clicked']); customlog(['Login Clicked']);
$.ajax({ $.ajax({
@@ -138,10 +138,10 @@ $(document).ready(function(){
window.location.href = '/logout'; window.location.href = '/logout';
}); });
$('#coll_button').on('click', function(){ $('#coll_button').on('click', function(){
var isopen = $('#pop_over_0').dialog("isOpen") var isopen = $('#pop_over_0').dialog("isOpen");
if (isopen){ if (isopen){
$('#pop_over_0').dialog("close"); $('#pop_over_0').dialog("close");
return return;
} }
customlog(['Collections Clicked']); customlog(['Collections Clicked']);
$.ajax({ $.ajax({
@@ -171,7 +171,49 @@ $(document).ready(function(){
error: function(response){ error: function(response){
customlog(["Failure", 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: response.data['title'],
maxHeight: (win_height-100),
minWidth: $("#horiz_nav_main").width(),
hide: { effect: "blind", duration: 1000 },
show: { effect: "blind", duration: 1000 },
position: { my: "center center", at: "center center", of: window
}
});
// clear and create a new container
$('#pop_over_0').html('<div id=book_expanded>');
// Populate the container from response.data
$('#book_expanded').append('<div class=row><div class="col-auto">Title</div><div class="col-auto text-muted">'+response.data['title']+'</div></div>')
$('#book_expanded').append('<div class=row><div class="col-auto">Author</div><div class="col-auto text-muted">'+response.data['author']+'</div></div>')
if (response.data['description']!== null){
$('#book_expanded').append('<div class=row><div class="col-auto">Expanded Description</div><div class="col-auto text-muted">'+response.data['description']+'</div></div>')
}
if (response.data['tags'] !== null){
$('#book_expanded').append('<div class=row><div class="col-auto">Tags</div><div class="col-auto text-muted">'+response.data['tags']+'</div></div>')
}
// Close the container
$('#pop_over').append('</div>');
// Now open this dialog
$('#pop_over_0').dialog("open");
},
error: function(response){
customlog(["Failure", response]);
}
});
}); });
}); });
function resize_search(win_width){ function resize_search(win_width){

View File

@@ -120,8 +120,8 @@
<span class="share-button controls"> <span class="share-button controls">
<a href="{% url 'share' pk=book.pk %}" class="book_link"><i class="fas fa-share icon"></i></a> <a href="{% url 'share' pk=book.pk %}" class="book_link"><i class="fas fa-share icon"></i></a>
</span> </span>
<span class="info-button controls"> <span class="info-button controls" data="{{ book.pk }}">
<a href="{% url 'info' pk=book.pk %}" class="book_link"><i class="fas fa-info icon"></i></a> <i class="fas fa-info icon"></i>
</span> </span>
</li> </li>
</ul> </ul>

View File

@@ -1,22 +1,22 @@
import json
import os import os
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from pathlib import Path from pathlib import Path
from backend.lib.config import Config 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.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import HttpResponse, render, redirect # render_to_response from django.shortcuts import HttpResponse, redirect, render # 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.template.loader import render_to_string 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("../")) config = Config(Path("../"))
@@ -380,6 +380,12 @@ def collections_list():
def live(request, **kwargs): 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"} err_txt = {"err": "There is no responder for your request"}
try: hook = request.GET['hook'] try: hook = request.GET['hook']
except MultiValueDictKeyError as e: return JsonResponse(err_txt, status=404) except MultiValueDictKeyError as e: return JsonResponse(err_txt, status=404)
@@ -387,8 +393,11 @@ def live(request, **kwargs):
if hook == "collection_listing": if hook == "collection_listing":
collections = collections_list() collections = collections_list()
return JsonResponse({"data": collections}, status=200) return JsonResponse({"data": collections}, status=200)
elif hook == "book_details": elif hook == "details":
return JsonResponse({"data": Books.objects.get(pk=kwargs['pk'])}, status=200) 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": elif hook == "register":
html = render_to_string('signup.html', {'form': SignUpForm}, request) html = render_to_string('signup.html', {'form': SignUpForm}, request)
html += render_to_string('login.html', {'form': UserLoginForm}, request) html += render_to_string('login.html', {'form': UserLoginForm}, request)
@@ -397,7 +406,15 @@ def live(request, **kwargs):
return JsonResponse({"data": "Response sent"}, status=200) 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): def payload(request, query, _set, _limit, _order, **kwargs):
""" """
Return formatted data to template Return formatted data to template
@@ -445,10 +462,10 @@ def payload(request, query, _set, _limit, _order, **kwargs):
_r, _r_len = \ _r, _r_len = \
_results.order_by(_order)[_set_min:_set_max],\ _results.order_by(_order)[_set_min:_set_max],\
_results.count() _results.count()
else: else: # No new query was passed
try: try:
query = request.session['cached_query'] # Is there a cached query? 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']: if request.session['ascending']:
_results = Books().generic_search(query) _results = Books().generic_search(query)
else: _results = Books().generic_search(query).reverse() else: _results = Books().generic_search(query).reverse()
@@ -464,7 +481,7 @@ def payload(request, query, _set, _limit, _order, **kwargs):
_bookstats = Books.objects.all().count() _bookstats = Books.objects.all().count()
if (_r_len): _btotal = str(_r_len) if (_r_len): _btotal = str(_r_len)
else: _btotal = str(_bookstats) else: _btotal = str(_bookstats)
# Format the payload and return it to the view
return { return {
"Books": _r, "Books": _r,
"Set": str(_set), "Set": str(_set),
@@ -479,4 +496,3 @@ def payload(request, query, _set, _limit, _order, **kwargs):
"SearchLen": _r_len, "SearchLen": _r_len,
"Order": _order, "Order": _order,
} }