Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5ef95d69b |
31
README.md
@@ -3,33 +3,4 @@ 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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
name: task-queue
|
|
||||||
services:
|
|
||||||
redis:
|
|
||||||
ports:
|
|
||||||
- 6379:6379
|
|
||||||
image: redis
|
|
||||||
15
nginx.conf
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "epub2go-py-web"
|
name = "epub2go-py-web"
|
||||||
version = "2.1"
|
version = "0.1.0"
|
||||||
description = "Web Interface to epub2go.py"
|
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]
|
||||||
|
|||||||
@@ -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.lower() in book.title.lower()]
|
# return base view
|
||||||
|
return render(request, 'index.html', context)
|
||||||
# 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)
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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',)
|
|
||||||
@@ -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}')
|
|
||||||
@@ -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"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 52 KiB |
@@ -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){
|
||||||
|
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()}`;
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
|
||||||
@@ -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="#"> << </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,24 +2,61 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{% extends 'index.html' %}
|
|
||||||
{% block main %}
|
|
||||||
<main>
|
|
||||||
<p class="main-banner">Einen Moment bitte. Deine Datei wird heruntergeladen.</p>
|
|
||||||
</main>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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'))
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
@@ -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.3"
|
version = "2.1"
|
||||||
source = { git = "https://github.com/eneller/epub2go.py#75974ae11957b8a65d607b1f00ad2fae41a6840b" }
|
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]]
|
||||||
|
|||||||