1 Commits
v2.0 ... v0.2

Author SHA1 Message Date
eneller
c5ef95d69b fix: table headers 2025-03-20 21:53:26 +01:00
25 changed files with 167 additions and 349 deletions

View File

@@ -4,32 +4,3 @@ A simple Website to provide a `NNI (Non-Nerd Interface)` to [epub2go.py](https:/
## Development ## Development
This project uses [watchman](https://facebook.github.io/watchman/) for file watching and reloading. 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. 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.
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.

View File

@@ -1,6 +0,0 @@
name: task-queue
services:
redis:
ports:
- 6379:6379
image: redis

View File

@@ -1,15 +0,0 @@
server {
listen 80;
server_name epub2go.example.org;
access_log /var/log/nginx/epub2go.log;
location /static {
alias /www/epub2go;
}
location / {
proxy_pass http://127.0.0.1:50000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@@ -5,13 +5,11 @@ description = "Web Interface to epub2go.py"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"celery[redis]>=5.4.0", "celery>=5.4.0",
"django>=5.1.6", "django>=5.1.6",
"epub2go", "epub2go",
"gunicorn>=23.0.0",
"python-dotenv>=1.0.1", "python-dotenv>=1.0.1",
"pywatchman>=2.0.0", "pywatchman>=2.0.0",
"redis>=5.2.1",
] ]
[tool.uv.sources] [tool.uv.sources]

View File

@@ -1,30 +1,34 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpRequest, HttpResponse, FileResponse, HttpResponseBadRequest from django.http import HttpRequest, HttpResponse, FileResponse, HttpResponseBadRequest
from django.core.paginator import Paginator from django.conf import settings
from celery import shared_task
from epub2go.convert import get_all_books, Book, allbooks_url from epub2go.convert import get_all_books, Book, GBConvert, allbooks_url
import os import os
from urllib.parse import urlparse from urllib.parse import urlparse
import logging import logging
from epub2go_web.tasks import getEpub
logger = logging.getLogger(__name__) #TODO configure logging logger = logging.getLogger(__name__) #TODO configure logging
converter = GBConvert(downloaddir=settings.MEDIA_ROOT)
books = sorted(get_all_books(), key= lambda b: b.title)# TODO get from pickle books = sorted(get_all_books(), key= lambda b: b.title)# TODO get from pickle
gbnetloc = urlparse(allbooks_url).netloc gbnetloc = urlparse(allbooks_url).netloc
def index(request: HttpRequest): def index(request: HttpRequest):
localbooks = books context = {
'title': 'epub2go',
'http_host': request.META['HTTP_HOST'],
'books': books,
'book_count': len(books),
'allbooks_url': allbooks_url,
}
targetParam = request.GET.get('t', None) targetParam = request.GET.get('t', None)
searchParam = request.GET.get('s', None)
if targetParam: if targetParam:
if validateUrl(targetParam): if validateUrl(targetParam):
# download file # download file
result = getEpub.delay(targetParam) fpath = getEpub(targetParam)
fpath = result.get(timeout=60)
fname = os.path.basename(fpath) fname = os.path.basename(fpath)
file = open(fpath, 'rb') file = open(fpath, 'rb')
response = FileResponse(file) response = FileResponse(file)
@@ -32,30 +36,9 @@ def index(request: HttpRequest):
response['Content-Disposition'] = f'attachment; filename="{fname}"' response['Content-Disposition'] = f'attachment; filename="{fname}"'
return response return response
else: return HttpResponseBadRequest('Input URL invalid.') else: return HttpResponseBadRequest('Input URL invalid.')
elif searchParam: else:
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 base view
return render(request, 'home.html', context) return render(request, 'index.html', context)
def validateUrl(param)->bool : def validateUrl(param)->bool :
@@ -63,3 +46,9 @@ def validateUrl(param)->bool :
if(netloc == gbnetloc): return True if(netloc == gbnetloc): return True
return False return False
# TODO make this async and show some indication of progress/loading
#@shared_task
def getEpub(book_url):
# TODO check for existing file and age
return converter.download(book_url)

View File

@@ -1,5 +0,0 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

View File

@@ -1,23 +0,0 @@
# Start Celery workers from src/ using `celery -A epub2go_web worker --loglevel=INFO`
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'epub2go_web.settings')
app = Celery('epub2go_web')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@app.task(bind=True, ignore_result=True)
def debug_task(self):
print(f'Request: {self.request!r}')

View File

@@ -20,7 +20,7 @@ PROJ_DIR = BASE_DIR / 'epub2go_web'
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! TODO # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-^@m5bl*8x+=@c^b0lhkgb-%_#9#&oad=v15jq=!0$g#x17zjf8" 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! # SECURITY WARNING: don't run with debug turned on in production!
@@ -32,6 +32,8 @@ ALLOWED_HOSTS = ['*']
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
@@ -44,6 +46,7 @@ MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
@@ -61,6 +64,7 @@ TEMPLATES = [
"context_processors": [ "context_processors": [
"django.template.context_processors.debug", "django.template.context_processors.debug",
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
], ],
}, },
@@ -69,18 +73,48 @@ TEMPLATES = [
WSGI_APPLICATION = "epub2go_web.wsgi.application" WSGI_APPLICATION = "epub2go_web.wsgi.application"
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Celery settings # Database
CELERY_BROKER_URL = "redis://localhost:6379/0" # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
CELERY_TASK_ALWAYS_EAGER = True #DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
# }
#}
# Password validation
# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/ # https://docs.djangoproject.com/en/5.1/howto/static-files/
@@ -93,3 +127,8 @@ STATIC_ROOT = PROJ_DIR/ "staticfiles"
MEDIA_URL = "media/" MEDIA_URL = "media/"
MEDIA_ROOT = PROJ_DIR / "media/" MEDIA_ROOT = PROJ_DIR / "media/"
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -7,7 +7,6 @@ const table_r = Array.from(table.getElementsByClassName('table-entry'));
document.addEventListener('keydown', (event)=>{ document.addEventListener('keydown', (event)=>{
if (event.ctrlKey && event.key === 'k'){ if (event.ctrlKey && event.key === 'k'){
event.preventDefault(); event.preventDefault();
searchInput.focus();
searchInput.select(); searchInput.select();
} }
}); });
@@ -15,16 +14,23 @@ document.addEventListener('keydown', (event)=>{
let searchParam = params.get('s'); let searchParam = params.get('s');
if (searchParam){ if (searchParam){
searchInput.value = searchParam; searchInput.value = searchParam;
console.log(searchParam);
search(searchParam);
} }
function submitSearch(event){ function submitSearch(event){
event.preventDefault(); event.preventDefault();
let path = window.location.pathname + "./?s="+encodeURIComponent(searchInput.value); search();
window.location.href= path;
} }
function search(searchStr = searchInput.value){
function buildURI(p){ searchStr= searchStr.toLowerCase();
let params = new URLSearchParams(window.location.search); function showMatch(tr){
params.set('p', p); // match search with list
return `${window.location.pathname}?${params.toString()}`; 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);
} }

View File

@@ -1,21 +0,0 @@
{
"name": "epub2go Converter",
"short_name": "epub2go",
"icons": [
{
"src": "/static/favicon/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/favicon/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -11,14 +11,12 @@ html{
} }
body{ body{
background-color: var(--bg); background-color: var(--bg);
overflow-x: hidden; margin:40px auto;
margin:.5em auto; max-width:800px;
max-width:min(100%,800px);
line-height:1.4; line-height:1.4;
font-size:18px; font-size:18px;
color:var(--fg); color:var(--fg);
padding:0 .2em; padding:0 10px;
justify-content: center;
} }
h1,h2,h3{ h1,h2,h3{
@@ -28,18 +26,14 @@ h1,h2,h3{
header{ header{
text-align: center; text-align: center;
justify-content: center; justify-content: center;
margin-bottom: 4%;
} }
small{ small{
color: var(--fg-deemph); color: var(--fg-deemph);
} }
.search-bar{ .searchbar{
width: 100%; width: 100%;
} }
.search-container{
text-align: center;
justify-content: center;
margin-bottom: 4%;
}
#searchInput { #searchInput {
background-color: var(--bg); background-color: var(--bg);
width: 100%; width: 100%;
@@ -49,13 +43,9 @@ small{
border-color: var(--bg-acc); border-color: var(--bg-acc);
box-shadow: var(--bg-acc) 2px 2px; box-shadow: var(--bg-acc) 2px 2px;
} }
.main-banner{
text-align: center;
}
table, tr{ table, tr{
/* make table not resize when elements are hidden by searching */ /* make table not resize when elements are hidden by searching */
width: 100%; width: 100%;
word-break: break-all;
} }
th{ th{
text-align: left; text-align: left;
@@ -80,16 +70,9 @@ a:hover, a:any-link{
color: inherit; color: inherit;
} }
.table-link{ .table-link{
/* TODO fix links with no title/content being almost unclickable */
display: block; display: block;
width: 100%; width: 100%;
min-height: 1em;
height: 100%; height: 100%;
padding: 1px; padding: 1px;
} }
.pagination{
display: flex;
justify-content: center;
}
.pagination a{
padding-left: 2em;
}

View File

@@ -1,11 +0,0 @@
from celery import shared_task
from django.conf import settings
from epub2go.convert import GBConvert
converter = GBConvert(downloaddir=settings.MEDIA_ROOT)
@shared_task
def getEpub(book_url):
# TODO check for existing file and age
return converter.download(book_url)

View File

@@ -1,61 +0,0 @@
{% extends 'index.html' %}
{% load static %}
{% block main %}
<main>
<div class="search-container">
<search>
<form onsubmit="submitSearch(event)" class="search-bar">
<input type="search" id="searchInput" placeholder="Suche nach Titel" minlength="3">
</form>
</search>
<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>
<a href="https://github.com/eneller/epub2go-web" title="Projekt auf GitHub ansehen">
<img src="{% static 'github.svg' %}" alt="GitHub" class="header-icon">
</a>
</div>
<table id="table">
<caption>{{ page.paginator.count }} Ergebnisse gefunden:</caption>
<thead>
<tr>
<th></th>
<th>Titel (anklicken zum herunterladen)</th>
<th>Autor</th>
</tr>
</thead>
<tbody>
{% for item in page.object_list %}
<tr class="table-entry">
<td>
<a href= {{ item.url }} target="_blank" rel="noopener noreferrer" class="table-link">
<img src="{% static 'open-link.svg' %}" alt="Open Link" class="inline-icon">
</a>
</td>
<td class="table-data">
<a href="./?t= {{ item.url }}" class="table-link">
{{ item.title }}
</a>
</td>
<td>
<a> {{ item.author }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if page.has_other_pages %}
<div class="pagination">
{% if page.has_previous %}
<a onclick="window.location.href = buildURI(1)" href="#"> &lt;&lt; </a>
<a onclick="window.location.href = buildURI('{{ page.previous_page_number }}')" href="#"> &lt; </a>
{% endif %}
{% if page.has_next %}
<a onclick="window.location.href = buildURI('{{ page.next_page_number }}')" href="#"> &gt; </a>
<a onclick="window.location.href = buildURI('{{ page.paginator.num_pages }}')" href="#"> &gt;&gt; </a>
{% endif %}
</div>
{% endif %}
</main>
{% endblock %}

View File

@@ -6,20 +6,57 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0" > <meta name="viewport" content="width=device-width,initial-scale=1.0" >
<link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}"> <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> </head>
<body> <body>
<header> <header>
<h1><a href="{{ http_host }}">{{ title }}</a></h1> <div>
<h1>{{ title }}</h1>
<h2>1 Click Download für Literatur</h2> <h2>1 Click Download für Literatur</h2>
</div>
<search>
<form onsubmit="submitSearch(event)" class="searchbar">
<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>
<a href="javascript:void(window.open('http://{{ http_host }}/?t=' + encodeURIComponent(window.location.toString())))" title="Als Lesezeichen speichern"><!--TODO fix domain part as variable-->
<img src="{% static 'bookmark.svg' %}" alt="Bookmarklet" class="header-icon">
</a>
<a href="https://github.com/eneller/epub2go-web">
<img src="{% static 'github.svg' %}" alt="GitHub" class="header-icon">
</a>
</header> </header>
{% block main %} <main>
{% endblock %} <!-- NOTE use dl here?-->
<table id="table">
<thead>
<tr>
<th></th>
<th>Titel</th>
<th>Autor</th>
</tr>
</thead>
<tbody>
{% for item in books %}
<tr class="table-entry">
<td>
<a href= {{ item.url }} target="_blank" rel="noopener noreferrer" class="table-link">
<img src="{% static 'open-link.svg' %}" alt="Open Link" class="inline-icon">
</a>
</td>
<td class="table-data">
<a href="./?t= {{ item.url }}" class="table-link">
{{ item.title }}
</a>
</td>
<td>
<a> {{ item.author }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
<footer> <footer>
</footer> </footer>
</body> </body>

View File

@@ -1,6 +0,0 @@
{% extends 'index.html' %}
{% block main %}
<main>
<p class="main-banner">Einen Moment bitte. Deine Datei wird heruntergeladen.</p>
</main>
{% endblock %}

View File

@@ -15,6 +15,7 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
@@ -22,5 +23,6 @@ from epub2go.convert import get_all_books, GBConvert, Book
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls),
path('',include('core.urls')) path('',include('core.urls'))
] ]

View File

@@ -1,18 +0,0 @@
# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "epub2go_web.wsgi"
# The granularity of Error log outputs
loglevel = "debug"
# The number of worker processes for handling requests
workers = 4
# The socket to bind
bind = "127.0.0.1:50000"
# Restart workers when code changes (development only!)
#reload = True
# Write access and error info to /var/log
#accesslog = errorlog = "/var/log/gunicorn/dev.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
#pidfile = "/var/run/gunicorn/dev.pid"
# Daemonize the Gunicorn process (detach & enter background)
#daemon = True

78
uv.lock generated
View File

@@ -46,7 +46,7 @@ wheels = [
[[package]] [[package]]
name = "celery" name = "celery"
version = "5.5.0" version = "5.4.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "billiard" }, { name = "billiard" },
@@ -56,16 +56,12 @@ dependencies = [
{ name = "click-repl" }, { name = "click-repl" },
{ name = "kombu" }, { name = "kombu" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "tzdata" },
{ name = "vine" }, { name = "vine" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/07/0c/0aaf310f4a2a048f010ebf63b8b0aaeb02ede2a79864fdbcdf31757a139d/celery-5.5.0.tar.gz", hash = "sha256:10d49f9926d16237310109b0e6e1e2f7a2133b84e684bb36534d7663e66919bb", size = 1665645 } sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/98/d4eefe492ef810f304203cfff0f60554fab95514ec51b701a4529b66fa92/celery-5.5.0-py3-none-any.whl", hash = "sha256:f4170c6e5952281318448a899d9e9a15b9cbd007e002091766900dc8f71b9394", size = 438396 }, { url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983 },
]
[package.optional-dependencies]
redis = [
{ name = "redis" },
] ]
[[package]] [[package]]
@@ -172,22 +168,22 @@ wheels = [
[[package]] [[package]]
name = "django" name = "django"
version = "5.2" version = "5.1.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" }, { name = "asgiref" },
{ name = "sqlparse" }, { name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/4c/1b/c6da718c65228eb3a7ff7ba6a32d8e80fa840ca9057490504e099e4dd1ef/Django-5.2.tar.gz", hash = "sha256:1a47f7a7a3d43ce64570d350e008d2949abe8c7e21737b351b6a1611277c6d89", size = 10824891 } sdist = { url = "https://files.pythonhosted.org/packages/5f/57/11186e493ddc5a5e92cc7924a6363f7d4c2b645f7d7cb04a26a63f9bfb8b/Django-5.1.7.tar.gz", hash = "sha256:30de4ee43a98e5d3da36a9002f287ff400b43ca51791920bfb35f6917bfe041c", size = 10716510 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361 }, { url = "https://files.pythonhosted.org/packages/ba/0f/7e042df3d462d39ae01b27a09ee76653692442bc3701fbfa6cb38e12889d/Django-5.1.7-py3-none-any.whl", hash = "sha256:1323617cb624add820cb9611cdcc788312d250824f92ca6048fda8625514af2b", size = 8276912 },
] ]
[[package]] [[package]]
name = "epub2go" name = "epub2go"
version = "2.2.2" version = "2.1"
source = { git = "https://github.com/eneller/epub2go.py#b3cd49326f871ee6e73215ec2a8c393476a85f98" } source = { git = "https://github.com/eneller/epub2go.py#42677007633c1a4f7f87a2f8f02b9faa1715f037" }
dependencies = [ dependencies = [
{ name = "beautifulsoup4" }, { name = "beautifulsoup4" },
{ name = "click" }, { name = "click" },
@@ -202,36 +198,20 @@ name = "epub2go-py-web"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "celery", extra = ["redis"] }, { name = "celery" },
{ name = "django" }, { name = "django" },
{ name = "epub2go" }, { name = "epub2go" },
{ name = "gunicorn" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "pywatchman" }, { name = "pywatchman" },
{ name = "redis" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "celery", extras = ["redis"], specifier = ">=5.4.0" }, { name = "celery", specifier = ">=5.4.0" },
{ name = "django", specifier = ">=5.1.6" }, { name = "django", specifier = ">=5.1.6" },
{ name = "epub2go", git = "https://github.com/eneller/epub2go.py" }, { name = "epub2go", git = "https://github.com/eneller/epub2go.py" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "python-dotenv", specifier = ">=1.0.1" }, { name = "python-dotenv", specifier = ">=1.0.1" },
{ name = "pywatchman", specifier = ">=2.0.0" }, { name = "pywatchman", specifier = ">=2.0.0" },
{ name = "redis", specifier = ">=5.2.1" },
]
[[package]]
name = "gunicorn"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 },
] ]
[[package]] [[package]]
@@ -245,25 +225,16 @@ wheels = [
[[package]] [[package]]
name = "kombu" name = "kombu"
version = "5.5.2" version = "5.5.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "amqp" }, { name = "amqp" },
{ name = "tzdata" }, { name = "tzdata" },
{ name = "vine" }, { name = "vine" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/c8/12/7a340f48920f30d6febb65d0c4aca70ed01b29e116131152977df78a9a39/kombu-5.5.2.tar.gz", hash = "sha256:2dd27ec84fd843a4e0a7187424313f87514b344812cb98c25daddafbb6a7ff0e", size = 461522 } sdist = { url = "https://files.pythonhosted.org/packages/0f/a5/eddeaf1044b94d802ba9d69f5446f758a14b6770bcfd7498d5270fc2e68f/kombu-5.5.0.tar.gz", hash = "sha256:72e65c062e903ee1b4e8b68d348f63c02afc172eda409e3aca85867752e79c0b", size = 460768 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/af/ba/939f3db0fca87715c883e42cc93045347d61a9d519c270a38e54a06db6e1/kombu-5.5.2-py3-none-any.whl", hash = "sha256:40f3674ed19603b8a771b6c74de126dbf8879755a0337caac6602faa82d539cd", size = 209763 }, { url = "https://files.pythonhosted.org/packages/23/29/f4f109c490d3cab3cbbba53403895e6052c84a40f18c28f75cd4dd53f11d/kombu-5.5.0-py3-none-any.whl", hash = "sha256:526c6cf038c986b998639109a1eb762502f831e8da148cc928f1f95cd91eb874", size = 209313 },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
] ]
[[package]] [[package]]
@@ -301,11 +272,11 @@ wheels = [
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.1.0" version = "1.0.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
] ]
[[package]] [[package]]
@@ -317,15 +288,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/7f/5d68d803489770cffa5d2b44be99b978c866f8a4d8e835f9da850415ed8a/pywatchman-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:51c2b4c72bea6b9fd90caf20759f5bc47febf0fd27bf2f247b87c66e2f6bab02", size = 52557 }, { url = "https://files.pythonhosted.org/packages/51/7f/5d68d803489770cffa5d2b44be99b978c866f8a4d8e835f9da850415ed8a/pywatchman-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:51c2b4c72bea6b9fd90caf20759f5bc47febf0fd27bf2f247b87c66e2f6bab02", size = 52557 },
] ]
[[package]]
name = "redis"
version = "5.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502 },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@@ -382,11 +344,11 @@ wheels = [
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2025.2" version = "2025.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
] ]
[[package]] [[package]]