Add Docker support; add favicon
This commit is contained in:
parent
fd856d839b
commit
033c13e931
18 changed files with 264 additions and 107 deletions
27
.dockerignore
Normal file
27
.dockerignore
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
.env
|
||||||
|
.env.example
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.venv
|
||||||
|
.vscode/
|
||||||
|
.vscode/
|
||||||
|
*.json
|
||||||
|
*.log
|
||||||
|
*.py[codz]
|
||||||
|
**/__pycache__/
|
||||||
|
*$py.class
|
||||||
|
archive/
|
||||||
|
check_these_please/
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
env.bak/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
responses/
|
||||||
|
staticfiles/
|
||||||
|
tests/
|
||||||
|
venv.bak/
|
||||||
|
venv/
|
||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
|
@ -6,6 +6,7 @@
|
||||||
"appauthor",
|
"appauthor",
|
||||||
"appname",
|
"appname",
|
||||||
"ASGI",
|
"ASGI",
|
||||||
|
"botuser",
|
||||||
"collectstatic",
|
"collectstatic",
|
||||||
"colorama",
|
"colorama",
|
||||||
"createsuperuser",
|
"createsuperuser",
|
||||||
|
|
@ -16,6 +17,9 @@
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"dropcampaign",
|
"dropcampaign",
|
||||||
"elif",
|
"elif",
|
||||||
|
"filterwarnings",
|
||||||
|
"granian",
|
||||||
|
"gunicorn",
|
||||||
"Hellsén",
|
"Hellsén",
|
||||||
"hostnames",
|
"hostnames",
|
||||||
"httpx",
|
"httpx",
|
||||||
|
|
@ -27,14 +31,17 @@
|
||||||
"Mailgun",
|
"Mailgun",
|
||||||
"makemigrations",
|
"makemigrations",
|
||||||
"McCabe",
|
"McCabe",
|
||||||
|
"noinput",
|
||||||
"noopener",
|
"noopener",
|
||||||
"noreferrer",
|
"noreferrer",
|
||||||
"platformdirs",
|
"platformdirs",
|
||||||
"prefetcher",
|
"prefetcher",
|
||||||
"psutil",
|
"psutil",
|
||||||
|
"pycache",
|
||||||
"pydantic",
|
"pydantic",
|
||||||
"pydocstyle",
|
"pydocstyle",
|
||||||
"pygments",
|
"pygments",
|
||||||
|
"pyproject",
|
||||||
"pyright",
|
"pyright",
|
||||||
"pytest",
|
"pytest",
|
||||||
"Ravendawn",
|
"Ravendawn",
|
||||||
|
|
@ -42,12 +49,14 @@
|
||||||
"runserver",
|
"runserver",
|
||||||
"sendgrid",
|
"sendgrid",
|
||||||
"speculationrules",
|
"speculationrules",
|
||||||
|
"staticfiles",
|
||||||
"testchannel",
|
"testchannel",
|
||||||
"testpass",
|
"testpass",
|
||||||
"tqdm",
|
"tqdm",
|
||||||
"ttvdrops",
|
"ttvdrops",
|
||||||
"venv",
|
"venv",
|
||||||
"wrongpassword",
|
"wrongpassword",
|
||||||
|
"wsgi",
|
||||||
"xdist"
|
"xdist"
|
||||||
],
|
],
|
||||||
"python.analysis.typeCheckingMode": "standard"
|
"python.analysis.typeCheckingMode": "standard"
|
||||||
|
|
|
||||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM python:3.14-slim-trixie
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||||
|
|
||||||
|
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
||||||
|
ENV UV_NO_DEV=1
|
||||||
|
ENV UV_PYTHON_DOWNLOADS=0
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
RUN uv sync
|
||||||
|
RUN chmod +x /app/start.sh
|
||||||
|
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8000/ || exit 1
|
||||||
|
|
||||||
|
VOLUME ["/home/root/.local/share/TTVDrops"]
|
||||||
|
EXPOSE 8000
|
||||||
|
ENTRYPOINT [ "/app/start.sh" ]
|
||||||
|
CMD ["uv", "run", "gunicorn", "config.wsgi:application", "--workers", "27", "--bind", "0.0.0.0:8000"]
|
||||||
|
|
@ -6,12 +6,10 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import django_stubs_ext
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from platformdirs import user_data_dir
|
from platformdirs import user_data_dir
|
||||||
|
|
||||||
logger: logging.Logger = logging.getLogger("ttvdrops.settings")
|
logger: logging.Logger = logging.getLogger("ttvdrops.settings")
|
||||||
django_stubs_ext.monkeypatch()
|
|
||||||
|
|
||||||
load_dotenv(verbose=True)
|
load_dotenv(verbose=True)
|
||||||
|
|
||||||
|
|
@ -100,7 +98,7 @@ MEDIA_ROOT: Path = DATA_DIR / "media"
|
||||||
MEDIA_ROOT.mkdir(exist_ok=True)
|
MEDIA_ROOT.mkdir(exist_ok=True)
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
STATIC_ROOT: Path = BASE_DIR / "staticfiles"
|
STATIC_ROOT: Path = DATA_DIR / "staticfiles"
|
||||||
STATIC_ROOT.mkdir(exist_ok=True)
|
STATIC_ROOT.mkdir(exist_ok=True)
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
|
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
|
||||||
|
|
@ -181,18 +179,3 @@ DATABASES: dict[str, dict[str, str | Path | dict[str, str]]] = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
TESTING: bool = "test" in sys.argv or "PYTEST_VERSION" in os.environ
|
|
||||||
|
|
||||||
if not TESTING:
|
|
||||||
DEBUG_TOOLBAR_CONFIG: dict[str, str] = {
|
|
||||||
"ROOT_TAG_EXTRA_ATTRS": "hx-preserve",
|
|
||||||
}
|
|
||||||
INSTALLED_APPS = [ # pyright: ignore[reportConstantRedefinition]
|
|
||||||
*INSTALLED_APPS,
|
|
||||||
"debug_toolbar",
|
|
||||||
]
|
|
||||||
MIDDLEWARE = [ # pyright: ignore[reportConstantRedefinition]
|
|
||||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
|
||||||
*MIDDLEWARE,
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,6 @@ urlpatterns: list[URLResolver] | list[URLPattern | URLResolver] = [ # type: ign
|
||||||
path(route="", view=include("twitch.urls", namespace="twitch")),
|
path(route="", view=include("twitch.urls", namespace="twitch")),
|
||||||
]
|
]
|
||||||
|
|
||||||
if not settings.TESTING:
|
|
||||||
# Import debug_toolbar lazily to avoid ImportError when not installed in testing environments
|
|
||||||
from debug_toolbar.toolbar import debug_toolbar_urls # pyright: ignore[reportMissingTypeStubs]
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
*urlpatterns,
|
|
||||||
*debug_toolbar_urls(),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Serve media in development
|
# Serve media in development
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(
|
urlpatterns += static(
|
||||||
|
|
|
||||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
services:
|
||||||
|
ttvdrops:
|
||||||
|
container_name: ttvdrops
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY?Please set DJANGO_SECRET_KEY in your environment}
|
||||||
|
- DEBUG=1
|
||||||
|
- EMAIL_HOST=${EMAIL_HOST:-smtp.example.com}
|
||||||
|
- EMAIL_PORT=${EMAIL_PORT:-587}
|
||||||
|
- EMAIL_HOST_USER=${EMAIL_HOST_USER:-}
|
||||||
|
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD:-}
|
||||||
|
volumes:
|
||||||
|
# Data is stored in /home/root/.local/share/TTVDrops" inside the container
|
||||||
|
- ./data:/home/root/.local/share/TTVDrops
|
||||||
|
restart: unless-stopped
|
||||||
|
|
@ -3,27 +3,24 @@ name = "ttvdrops"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Get notified when a new drop is available on Twitch."
|
description = "Get notified when a new drop is available on Twitch."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dateparser>=1.2.2",
|
"dateparser",
|
||||||
"django-debug-toolbar>=5.2.0",
|
"django-debug-toolbar",
|
||||||
"django>=5.2.4",
|
"django",
|
||||||
"djlint>=1.36.4",
|
"json-repair",
|
||||||
"json-repair>=0.50.0",
|
"platformdirs",
|
||||||
"platformdirs>=4.3.8",
|
"python-dotenv",
|
||||||
"python-dotenv>=1.1.1",
|
"pygments",
|
||||||
"pygments>=2.19.2",
|
"httpx",
|
||||||
"httpx>=0.28.1",
|
"pydantic",
|
||||||
"pydantic>=2.12.5",
|
"tqdm",
|
||||||
"tqdm>=4.67.1",
|
"colorama",
|
||||||
"colorama>=0.4.6",
|
"gunicorn",
|
||||||
"django-stubs-ext>=5.2.8",
|
|
||||||
"django-stubs[compatible-mypy]>=5.2.8",
|
|
||||||
"types-pygments>=2.19.0.20251121",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = ["pytest>=8.4.1", "pytest-django>=4.11.1"]
|
dev = ["pytest", "pytest-django", "djlint"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
DJANGO_SETTINGS_MODULE = "config.settings"
|
DJANGO_SETTINGS_MODULE = "config.settings"
|
||||||
|
|
@ -98,9 +95,3 @@ line-length = 120
|
||||||
[tool.djlint]
|
[tool.djlint]
|
||||||
profile = "django"
|
profile = "django"
|
||||||
ignore = "H021"
|
ignore = "H021"
|
||||||
|
|
||||||
[tool.mypy]
|
|
||||||
plugins = ["mypy_django_plugin.main"]
|
|
||||||
|
|
||||||
[tool.django-stubs]
|
|
||||||
django_settings_module = "config.settings"
|
|
||||||
|
|
|
||||||
8
start.sh
Executable file
8
start.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Collecting static files..."
|
||||||
|
uv run python manage.py collectstatic --noinput
|
||||||
|
|
||||||
|
echo "Starting Django server..."
|
||||||
|
exec "$@"
|
||||||
6
static/about.txt
Normal file
6
static/about.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
This favicon was generated using the following graphics from Twemoji:
|
||||||
|
|
||||||
|
- Graphics Title: 1f4a6.svg
|
||||||
|
- Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/jdecked/twemoji)
|
||||||
|
- Graphics Source: https://github.com/jdecked/twemoji/blob/master/assets/svg/1f4a6.svg
|
||||||
|
- Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
|
||||||
BIN
static/android-chrome-192x192.png
Normal file
BIN
static/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
static/android-chrome-512x512.png
Normal file
BIN
static/android-chrome-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
static/apple-touch-icon.png
Normal file
BIN
static/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6 KiB |
BIN
static/favicon-16x16.png
Normal file
BIN
static/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 454 B |
BIN
static/favicon-32x32.png
Normal file
BIN
static/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 854 B |
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
19
static/site.webmanifest
Normal file
19
static/site.webmanifest
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "ttvdrops",
|
||||||
|
"short_name": "ttvdrops",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
|
@ -1,74 +1,156 @@
|
||||||
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<script type="speculationrules">
|
<link rel="apple-touch-icon"
|
||||||
{"prefetch":[{"where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":"/accounts/logout/"}}]}}],"prerender":[{"where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":"/accounts/logout/"}}]},"eagerness":"eager"}]}
|
sizes="180x180"
|
||||||
</script>
|
href="{% static 'apple-touch-icon.png' %}" />
|
||||||
<script>
|
<link rel="icon"
|
||||||
if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('speculationrules')) {
|
type="image/png"
|
||||||
const preloadedUrls = {};
|
sizes="32x32"
|
||||||
|
href="{% static 'favicon-32x32.png' %}" />
|
||||||
function pointerenterHandler () {
|
<link rel="icon"
|
||||||
if (!preloadedUrls[this.href]) {
|
type="image/png"
|
||||||
preloadedUrls[this.href] = true;
|
sizes="16x16"
|
||||||
|
href="{% static 'favicon-16x16.png' %}" />
|
||||||
const prefetcher = document.createElement('link');
|
<link rel="manifest" href="{% static 'site.webmanifest' %}" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
prefetcher.as = prefetcher.relList.supports('prefetch') ? 'document' : 'fetch';
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
prefetcher.rel = prefetcher.relList.supports('prefetch') ? 'prefetch' : 'preload';
|
|
||||||
prefetcher.href = this.href;
|
|
||||||
|
|
||||||
document.head.appendChild(prefetcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('a[href^="/"]').forEach(item => {
|
|
||||||
item.addEventListener('pointerenter', pointerenterHandler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="Twitch Drops Tracker - Track your Twitch drops and campaigns easily.">
|
content="Twitch Drops Tracker - Track your Twitch drops and campaigns easily." />
|
||||||
<meta name="keywords" content="Twitch, Drops">
|
<meta name="keywords" content="Twitch, Drops" />
|
||||||
<title>
|
<title>
|
||||||
{% block title %}
|
{% block title %}
|
||||||
ttvdrops
|
ttvdrops
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
</title>
|
</title>
|
||||||
<style>
|
<style>
|
||||||
html { color-scheme: light dark; }
|
html {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; line-height: 1.4; padding: 0; font-size: 115%; max-width: 75%; margin: 0 auto;}
|
body {
|
||||||
@media (max-width: 900px) { body { max-width: 95%; } }
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 115%;
|
||||||
|
max-width: 75%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
table { width: 100%; }
|
@media (max-width: 900px) {
|
||||||
th, td { padding: 8px; text-align: left; vertical-align: middle; }
|
body {
|
||||||
th {background-color: Canvas; color: CanvasText; font-weight: bold; }
|
max-width: 95%;
|
||||||
tr:nth-child(even) { background-color: color-mix(in srgb, Canvas 95%, CanvasText 5%); }
|
}
|
||||||
td > img { display: block; height: 160px; width: 160px; object-fit: cover; border-radius: 4px; }
|
}
|
||||||
|
|
||||||
.campaign-benefits { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 8px; }
|
table {
|
||||||
.benefit-item { display: inline-flex; align-items: center; padding: 2px 6px; background-color: color-mix(in srgb, Canvas 90%, CanvasText 10%); border-radius: 4px; font-size: 0.9em; }
|
width: 100%;
|
||||||
.benefit-item img { width: 24px !important; height: 24px !important; display: inline-block !important; margin-right: 4px; border-radius: 2px; }
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: Canvas;
|
||||||
|
color: CanvasText;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, Canvas 95%, CanvasText 5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
td>img {
|
||||||
|
display: block;
|
||||||
|
height: 160px;
|
||||||
|
width: 160px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-benefits {
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background-color: color-mix(in srgb, Canvas 90%, CanvasText 10%);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-item img {
|
||||||
|
width: 24px !important;
|
||||||
|
height: 24px !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.highlight { background: #0d1117; color: #E6EDF3; }
|
.highlight {
|
||||||
.highlight .p { color: #E6EDF3; }
|
background: #0d1117;
|
||||||
.highlight .nt { color: #7EE787; }
|
color: #E6EDF3;
|
||||||
.highlight .s2, .highlight .mi { color: #A5D6FF; }
|
}
|
||||||
.highlight .kc { color: #79C0FF; }
|
|
||||||
.highlight .w { color: #6E7681; }
|
.highlight .p {
|
||||||
|
color: #E6EDF3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nt {
|
||||||
|
color: #7EE787;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .s2,
|
||||||
|
.highlight .mi {
|
||||||
|
color: #A5D6FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kc {
|
||||||
|
color: #79C0FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .w {
|
||||||
|
color: #6E7681;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.highlight { background: #f6f8fa; color: #24292e; }
|
.highlight {
|
||||||
.highlight .p { color: #24292e; }
|
background: #f6f8fa;
|
||||||
.highlight .nt { color: #005cc5; }
|
color: #24292e;
|
||||||
.highlight .s2, .highlight .mi { color: #032f62; }
|
}
|
||||||
.highlight .kc { color: #d73a49; }
|
|
||||||
.highlight .w { color: #6a737d; }
|
.highlight .p {
|
||||||
|
color: #24292e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nt {
|
||||||
|
color: #005cc5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .s2,
|
||||||
|
.highlight .mi {
|
||||||
|
color: #032f62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kc {
|
||||||
|
color: #d73a49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .w {
|
||||||
|
color: #6a737d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -86,7 +168,7 @@
|
||||||
<input type="search"
|
<input type="search"
|
||||||
name="q"
|
name="q"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
value="{{ request.GET.q }}">
|
value="{{ request.GET.q }}" />
|
||||||
<button type="submit">Search</button>
|
<button type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ class Command(BaseCommand):
|
||||||
for name, model, cache_attr in progress_bar:
|
for name, model, cache_attr in progress_bar:
|
||||||
self.load_cache_for_model(progress_bar, name, model, cache_attr)
|
self.load_cache_for_model(progress_bar, name, model, cache_attr)
|
||||||
tqdm.write("")
|
tqdm.write("")
|
||||||
except (DatabaseError, OSError, RuntimeError, ValueError, TypeError):
|
except DatabaseError, OSError, RuntimeError, ValueError, TypeError:
|
||||||
# If cache loading fails completely, just use empty caches
|
# If cache loading fails completely, just use empty caches
|
||||||
tqdm.write(f"{Fore.YELLOW}⚠{Style.RESET_ALL} Cache preload skipped (database error)\n")
|
tqdm.write(f"{Fore.YELLOW}⚠{Style.RESET_ALL} Cache preload skipped (database error)\n")
|
||||||
|
|
||||||
|
|
@ -1120,7 +1120,7 @@ class Command(BaseCommand):
|
||||||
campaign_structure=campaign_structure,
|
campaign_structure=campaign_structure,
|
||||||
)
|
)
|
||||||
|
|
||||||
except (ValidationError, json.JSONDecodeError):
|
except ValidationError, json.JSONDecodeError:
|
||||||
if options["crash_on_error"]:
|
if options["crash_on_error"]:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -1229,7 +1229,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
progress_bar.update(1)
|
progress_bar.update(1)
|
||||||
progress_bar.write(f"{Fore.GREEN}✓{Style.RESET_ALL} {file_path.name}")
|
progress_bar.write(f"{Fore.GREEN}✓{Style.RESET_ALL} {file_path.name}")
|
||||||
except (ValidationError, json.JSONDecodeError):
|
except ValidationError, json.JSONDecodeError:
|
||||||
if options["crash_on_error"]:
|
if options["crash_on_error"]:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue