Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1ed7ec389 | ||
|
|
bceaf6904a | ||
|
|
ee598e4810 | ||
|
|
6e7d6b1e2a | ||
|
|
1ef440fb8f | ||
|
|
21d6d265ed | ||
|
|
41f7836a48 | ||
|
|
eab2268bab | ||
|
|
09f55f8ea5 |
29
README.md
29
README.md
@@ -5,4 +5,31 @@ A simple Website to provide a `NNI (Non-Nerd Interface)` to [epub2go.py](https:/
|
||||
This project uses [watchman](https://facebook.github.io/watchman/) for file watching and reloading.
|
||||
Follow the [official instructions](https://facebook.github.io/watchman/docs/install.html) for your system to install, django will default to its standard watcher otherwise.
|
||||
|
||||
Also, [celery](https://docs.celeryq.dev/en/stable/) is used as a task queue with [redis](https://hub.docker.com/_/redis) as backend.
|
||||
To run the server, install the dependencies described in `pyproject.toml` and `uv.lock`
|
||||
in a virtual environment, ideally using [uv](https://docs.astral.sh/uv/) (or pip).
|
||||
|
||||
[Celery](https://docs.celeryq.dev/en/stable/) is used as a task queue with [redis](https://hub.docker.com/_/redis) as backend.
|
||||
A container for it is provided in the `docker-compose.yml`.
|
||||
After the container is up, simply start your Celery workers from `src/` using
|
||||
```bash
|
||||
celery -A epub2go_web worker --loglevel=INFO
|
||||
```
|
||||
|
||||
Finally, run the **development** server using
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
## Deployment
|
||||
Follow the Development instructions, except replace the final command for the development server with
|
||||
```bash
|
||||
gunicorn -c gunicorn.py
|
||||
```
|
||||
[Gunicorn](https://gunicorn.org/) does not serve static files and is intended to be deployed behind [nginx](https://nginx.org/).
|
||||
An example configuration is provided in `nginx.conf`.
|
||||
|
||||
To collect the static files for nginx, run
|
||||
```bash
|
||||
python manage.py collectstatic
|
||||
```
|
||||
and point nginx to the resulting folder.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpRequest, HttpResponse, FileResponse, HttpResponseBadRequest
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from epub2go.convert import get_all_books, Book, allbooks_url
|
||||
|
||||
@@ -15,15 +16,10 @@ books = sorted(get_all_books(), key= lambda b: b.title)# TODO get from pickle
|
||||
gbnetloc = urlparse(allbooks_url).netloc
|
||||
|
||||
def index(request: HttpRequest):
|
||||
context = {
|
||||
'title': 'epub2go',
|
||||
'http_host': f'http://{ request.META['HTTP_HOST'] }',
|
||||
'books': books,
|
||||
'book_count': len(books),
|
||||
'allbooks_url': allbooks_url,
|
||||
}
|
||||
localbooks = books
|
||||
|
||||
targetParam = request.GET.get('t', None)
|
||||
searchParam = request.GET.get('s', None)
|
||||
if targetParam:
|
||||
if validateUrl(targetParam):
|
||||
# download file
|
||||
@@ -36,7 +32,28 @@ def index(request: HttpRequest):
|
||||
response['Content-Disposition'] = f'attachment; filename="{fname}"'
|
||||
return response
|
||||
else: return HttpResponseBadRequest('Input URL invalid.')
|
||||
else:
|
||||
elif searchParam:
|
||||
localbooks = [book for book in books if searchParam in book.title]
|
||||
|
||||
# paginate items
|
||||
paginationParam = request.GET.get('p', '')
|
||||
try:
|
||||
pageNo = int(paginationParam)
|
||||
except ValueError:
|
||||
logger.error('Failed to cast %s to int', paginationParam)
|
||||
pageNo = 1
|
||||
|
||||
pages = Paginator(localbooks, 100)
|
||||
if pageNo < 1 or pageNo > pages.num_pages:
|
||||
pageNo = 1
|
||||
page = pages.page(pageNo)
|
||||
context = {
|
||||
'title': 'epub2go',
|
||||
'http_host': f'http://{ request.META['HTTP_HOST'] }',
|
||||
'page': page,
|
||||
'allbooks_count': len(books),
|
||||
'allbooks_url': allbooks_url,
|
||||
}
|
||||
# return base view
|
||||
return render(request, 'home.html', context)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ PROJ_DIR = BASE_DIR / 'epub2go_web'
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
# SECURITY WARNING: keep the secret key used in production secret! TODO
|
||||
SECRET_KEY = "django-insecure-^@m5bl*8x+=@c^b0lhkgb-%_#9#&oad=v15jq=!0$g#x17zjf8"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
|
||||
@@ -15,23 +15,16 @@ document.addEventListener('keydown', (event)=>{
|
||||
let searchParam = params.get('s');
|
||||
if (searchParam){
|
||||
searchInput.value = searchParam;
|
||||
console.log(searchParam);
|
||||
search(searchParam);
|
||||
}
|
||||
|
||||
function submitSearch(event){
|
||||
event.preventDefault();
|
||||
search();
|
||||
let path = window.location.pathname + "./?s="+encodeURIComponent(searchInput.value);
|
||||
window.location.href= path;
|
||||
}
|
||||
function search(searchStr = searchInput.value){
|
||||
searchStr= searchStr.toLowerCase();
|
||||
function showMatch(tr){
|
||||
// match search with list
|
||||
let searchSuccess = Array.from(tr.getElementsByClassName('table-data')).map(e => e.textContent.toLowerCase())
|
||||
.join(' ')
|
||||
.indexOf(searchStr) > -1;
|
||||
if (searchSuccess) tr.style.display = "";
|
||||
else tr.style.display = "none";
|
||||
}
|
||||
table_r.map(showMatch, table_r);
|
||||
|
||||
function buildURI(p){
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
params.set('p', p);
|
||||
return `${window.location.pathname}?${params.toString()}`;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ body{
|
||||
font-size:18px;
|
||||
color:var(--fg);
|
||||
padding:0 .2em;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1,h2,h3{
|
||||
@@ -85,3 +86,10 @@ a:hover, a:any-link{
|
||||
height: 100%;
|
||||
padding: 1px;
|
||||
}
|
||||
.pagination{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.pagination a{
|
||||
padding-left: 2em;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
<input type="search" id="searchInput" placeholder="Suche nach Titel" minlength="3">
|
||||
</form>
|
||||
</search>
|
||||
<small>Im Moment finden sich hier <a href="{{ allbooks_url }}">{{ book_count }} Bücher.</a> </small>
|
||||
<small>Im Moment finden sich hier <a href="{{ allbooks_url }}">{{ allbooks_count }} Bücher.</a> </small>
|
||||
<a href="javascript:void(window.open('{{ http_host }}/?t=' + encodeURIComponent(window.location.toString())))" title="Als Lesezeichen speichern">
|
||||
<img src="{% static 'bookmark.svg' %}" alt="Bookmarklet" class="header-icon">
|
||||
</a>
|
||||
@@ -16,17 +16,17 @@
|
||||
<img src="{% static 'github.svg' %}" alt="GitHub" class="header-icon">
|
||||
</a>
|
||||
</div>
|
||||
<!-- NOTE use dl here?-->
|
||||
<table id="table">
|
||||
<caption>{{ page.paginator.count }} Ergebnisse gefunden:</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Titel</th>
|
||||
<th>Titel (anklicken zum herunterladen)</th>
|
||||
<th>Autor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in books %}
|
||||
{% for item in page.object_list %}
|
||||
<tr class="table-entry">
|
||||
<td>
|
||||
<a href= {{ item.url }} target="_blank" rel="noopener noreferrer" class="table-link">
|
||||
@@ -45,5 +45,17 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if page.has_other_pages %}
|
||||
<div class="pagination">
|
||||
{% if page.has_previous %}
|
||||
<a onclick="window.location.href = buildURI(1)" href="#"> << </a>
|
||||
<a onclick="window.location.href = buildURI('{{ page.previous_page_number }}')" href="#"> < </a>
|
||||
{% endif %}
|
||||
{% if page.has_next %}
|
||||
<a onclick="window.location.href = buildURI('{{ page.next_page_number }}')" href="#"> > </a>
|
||||
<a onclick="window.location.href = buildURI('{{ page.paginator.num_pages }}')" href="#"> >> </a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -2,18 +2,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" >
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}">
|
||||
|
||||
<link rel="icon" type="image/png" href="{% static '/favicon/favicon-96x96.png' %}" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="{% static '/favicon/favicon.svg' %}" />
|
||||
<link rel="shortcut icon" href="{% static '/favicon/favicon.ico' %}" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static '/favicon/apple-touch-icon.png' %}" />
|
||||
<meta name="apple-mobile-web-app-title" content="epub2go" />
|
||||
<link rel="manifest" href="{% static 'site.webmanifest' %}" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" >
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}">
|
||||
<link rel="icon" type="image/png" href="{% static '/favicon/favicon-96x96.png' %}" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="{% static '/favicon/favicon.svg' %}" />
|
||||
<link rel="shortcut icon" href="{% static '/favicon/favicon.ico' %}" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static '/favicon/apple-touch-icon.png' %}" />
|
||||
<meta name="apple-mobile-web-app-title" content="epub2go" />
|
||||
<link rel="manifest" href="{% static 'site.webmanifest' %}" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
||||
10
uv.lock
generated
10
uv.lock
generated
@@ -172,22 +172,22 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.1.7"
|
||||
version = "5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/57/11186e493ddc5a5e92cc7924a6363f7d4c2b645f7d7cb04a26a63f9bfb8b/Django-5.1.7.tar.gz", hash = "sha256:30de4ee43a98e5d3da36a9002f287ff400b43ca51791920bfb35f6917bfe041c", size = 10716510 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/1b/c6da718c65228eb3a7ff7ba6a32d8e80fa840ca9057490504e099e4dd1ef/Django-5.2.tar.gz", hash = "sha256:1a47f7a7a3d43ce64570d350e008d2949abe8c7e21737b351b6a1611277c6d89", size = 10824891 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/0f/7e042df3d462d39ae01b27a09ee76653692442bc3701fbfa6cb38e12889d/Django-5.1.7-py3-none-any.whl", hash = "sha256:1323617cb624add820cb9611cdcc788312d250824f92ca6048fda8625514af2b", size = 8276912 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epub2go"
|
||||
version = "2.2.1"
|
||||
source = { git = "https://github.com/eneller/epub2go.py#401d02e0ca26ca0a12ee331140a2cde273559251" }
|
||||
version = "2.2.2"
|
||||
source = { git = "https://github.com/eneller/epub2go.py#b3cd49326f871ee6e73215ec2a8c393476a85f98" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "click" },
|
||||
|
||||
Reference in New Issue
Block a user