Merge app and project, use SQLite instead and
This commit is contained in:
parent
f0e7a35774
commit
4c16d14e61
29 changed files with 221 additions and 454 deletions
|
|
@ -1,4 +1,5 @@
|
|||
POSTGRES_USER=
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_DB=feedvault
|
||||
DEBUG=True
|
||||
SECRET_KEY=
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
DISCORD_WEBHOOK_URL=
|
||||
|
|
|
|||
16
.github/workflows/docker-publish.yml
vendored
16
.github/workflows/docker-publish.yml
vendored
|
|
@ -15,29 +15,15 @@ jobs:
|
|||
ADMIN_EMAIL: 4153203+TheLovinator1@users.noreply.github.com
|
||||
EMAIL_HOST_USER: ${{ secrets.EMAIL_HOST_USER }}
|
||||
EMAIL_HOST_PASSWORD: ${{ secrets.EMAIL_HOST_PASSWORD }}
|
||||
POSTGRES_PASSWORD: githubtest
|
||||
POSTGRES_HOST: 127.0.0.1
|
||||
POSTGRES_USER: feedvault
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
|
||||
POSTGRES_HOST: ${{ env.POSTGRES_HOST }}
|
||||
POSTGRES_USER: ${{ env.POSTGRES_USER }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --health-cmd pg_isready --health-interval 1s --health-timeout 5s --health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
- run: pipx install poetry
|
||||
- run: pipx inject poetry poetry-plugin-export
|
||||
- run: poetry install
|
||||
- run: poetry run python manage.py migrate
|
||||
# - run: poetry run python manage.py test
|
||||
- run: poetry run python manage.py test
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
_A seed vault for your feeds._
|
||||
|
||||
FeedVault is an open-source web application that allows users to archive and search their favorite RSS, Atom, and JSON feeds. With FeedVault, users can effortlessly add their favorite feeds, ensuring they have a centralized location for accessing and preserving valuable content.
|
||||
[FeedVault](https://feedvault.se/) is an open-source web application that allows users to archive and search their favorite RSS, Atom, and JSON feeds. With FeedVault, users can effortlessly add their favorite feeds, ensuring they have a centralized location for accessing and preserving valuable content.
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ _Note: Some features are currently in development._
|
|||
|
||||
## Usage
|
||||
|
||||
- Visit the FeedVault website.
|
||||
- [Visit the FeedVault website](https://feedvault.se/).
|
||||
- Sign up for an account or log in if you already have one.
|
||||
- Add your favorite feeds to start archiving content.
|
||||
- Explore, manage, and enjoy your centralized feed archive.
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
services:
|
||||
# Django - Web framework
|
||||
feedvault: &feedvault
|
||||
feedvault:
|
||||
container_name: feedvault
|
||||
image: ghcr.io/thelovinator1/feedvault:latest
|
||||
user: "1000:1000"
|
||||
restart: always
|
||||
networks:
|
||||
- feedvault_db
|
||||
- feedvault_web
|
||||
environment:
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- DEBUG=${DEBUG}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||
- EMAIL_HOST_USER=${EMAIL_HOST_USER}
|
||||
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}
|
||||
- POSTGRES_HOST=feedvault_postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_USER=feedvault
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL}
|
||||
volumes:
|
||||
- /mnt/Fourteen/Docker/FeedVault/staticfiles:/app/staticfiles
|
||||
- /mnt/Fourteen/Docker/FeedVault/media:/app/media
|
||||
- /mnt/Fourteen/Docker/FeedVault/data:/app/data
|
||||
|
||||
# Nginx - Reverse proxy
|
||||
web:
|
||||
|
|
@ -51,27 +47,8 @@ services:
|
|||
environment:
|
||||
- TUNNEL_URL=http://feedvault_web:80
|
||||
|
||||
# Postgres - Database
|
||||
postgres:
|
||||
container_name: feedvault_postgres
|
||||
image: postgres:16
|
||||
user: "1000:1000"
|
||||
ports:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=feedvault
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=feedvault
|
||||
volumes:
|
||||
- /mnt/Fourteen/Docker/FeedVault/Postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- feedvault_db
|
||||
|
||||
networks:
|
||||
feedvault_tunnel:
|
||||
driver: bridge
|
||||
feedvault_db:
|
||||
driver: bridge
|
||||
feedvault_web:
|
||||
driver: bridge
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FeedsConfig(AppConfig):
|
||||
"""This Django app is responsible for managing the feeds."""
|
||||
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "feeds"
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
# Generated by Django 5.0.2 on 2024-02-23 05:27
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('feeds', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='author',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Author', 'verbose_name_plural': 'Authors'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='domain',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Domain', 'verbose_name_plural': 'Domains'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='entry',
|
||||
options={'ordering': ['-created_parsed'], 'verbose_name': 'Entry', 'verbose_name_plural': 'Entries'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='feed',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Feed', 'verbose_name_plural': 'Feeds'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='generator',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Feed generator', 'verbose_name_plural': 'Feed generators'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='links',
|
||||
options={'ordering': ['href'], 'verbose_name': 'Link', 'verbose_name_plural': 'Links'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='publisher',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Publisher', 'verbose_name_plural': 'Publishers'},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='author',
|
||||
unique_together={('name', 'email', 'href')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='links',
|
||||
unique_together={('href', 'rel')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='publisher',
|
||||
unique_together={('name', 'email', 'href')},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 5.0.2 on 2024-02-23 05:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('feeds', '0002_alter_author_options_alter_domain_options_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='feed',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.contrib.sitemaps import GenericSitemap
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.urls import URLPattern, path
|
||||
from django.views.decorators.cache import cache_page
|
||||
|
||||
from feeds import views
|
||||
from feeds.models import Domain, Feed
|
||||
from feeds.sitemaps import StaticViewSitemap
|
||||
|
||||
from .views import APIView, CustomLoginView, CustomLogoutView, ProfileView, RegisterView
|
||||
|
||||
app_name: str = "feeds"
|
||||
|
||||
sitemaps = {
|
||||
"static": StaticViewSitemap,
|
||||
"feeds": GenericSitemap({"queryset": Feed.objects.all(), "date_field": "created_at"}),
|
||||
"domains": GenericSitemap({"queryset": Domain.objects.all(), "date_field": "created_at"}),
|
||||
}
|
||||
|
||||
|
||||
# Normal pages
|
||||
urlpatterns: list[URLPattern] = [
|
||||
path(route="", view=views.IndexView.as_view(), name="index"),
|
||||
path(route="feed/<int:feed_id>/", view=views.FeedView.as_view(), name="feed"),
|
||||
path(route="feeds/", view=views.FeedsView.as_view(), name="feeds"),
|
||||
path(route="add", view=views.AddView.as_view(), name="add"),
|
||||
path(route="upload", view=views.UploadView.as_view(), name="upload"),
|
||||
path(route="robots.txt", view=cache_page(timeout=60 * 60 * 365)(views.RobotsView.as_view()), name="robots"),
|
||||
path(
|
||||
"sitemap.xml",
|
||||
sitemap,
|
||||
{"sitemaps": sitemaps},
|
||||
name="django.contrib.sitemaps.views.sitemap",
|
||||
),
|
||||
path(route="domains/", view=views.DomainsView.as_view(), name="domains"),
|
||||
path(route="domain/<int:domain_id>/", view=views.DomainView.as_view(), name="domain"),
|
||||
]
|
||||
|
||||
# API urls
|
||||
urlpatterns += [
|
||||
path(route="api/", view=APIView.as_view(), name="api"),
|
||||
path(route="api/feeds/", view=views.APIFeedsView.as_view(), name="api_feeds"),
|
||||
path(route="api/feeds/<int:feed_id>/", view=views.APIFeedView.as_view(), name="api_feeds_id"),
|
||||
path(route="api/feeds/<int:feed_id>/entries/", view=views.APIFeedEntriesView.as_view(), name="api_feed_entries"),
|
||||
path(route="api/entries/", view=views.APIEntriesView.as_view(), name="api_entries"),
|
||||
path(route="api/entries/<int:entry_id>/", view=views.APIEntryView.as_view(), name="api_entries_id"),
|
||||
]
|
||||
|
||||
# Account urls
|
||||
urlpatterns += [
|
||||
path(route="accounts/login/", view=CustomLoginView.as_view(), name="login"),
|
||||
path(route="accounts/register/", view=RegisterView.as_view(), name="register"),
|
||||
path(route="accounts/logout/", view=CustomLogoutView.as_view(), name="logout"),
|
||||
# path(route="accounts/change-password/", view=CustomPasswordChangeView.as_view(), name="change_password"),
|
||||
path(route="accounts/profile/", view=ProfileView.as_view(), name="profile"),
|
||||
]
|
||||
|
|
@ -10,7 +10,7 @@ import feedparser
|
|||
from django.utils import timezone
|
||||
from feedparser import FeedParserDict
|
||||
|
||||
from feeds.models import Author, Domain, Entry, Feed, Generator, Publisher
|
||||
from feedvault.models import Author, Domain, Entry, Feed, Generator, Publisher
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
|
||||
|
|
@ -129,7 +129,7 @@ def parse_feed(url: str | None) -> dict | None:
|
|||
Returns:
|
||||
The parsed feed.
|
||||
"""
|
||||
# TODO(TheLovinator): Backup the feed URL to a cloudflare worker. # noqa: TD003
|
||||
# TODO(TheLovinator): Backup the feed URL. # noqa: TD003
|
||||
if not url:
|
||||
return None
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ def add_feed(url: str | None, user: AbstractBaseUser | AnonymousUser) -> Feed |
|
|||
try:
|
||||
feed.save()
|
||||
except Exception:
|
||||
logger.exception("Error saving feed: %s", feed)
|
||||
logger.exception("Got exception while saving feed: %s", url)
|
||||
return None
|
||||
|
||||
entries = parsed_feed.get("entries", [])
|
||||
8
feedvault/apps.py
Normal file
8
feedvault/apps.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FeedVaultConfig(AppConfig):
|
||||
"""FeedVault app configuration."""
|
||||
|
||||
default_auto_field: str = "django.db.models.BigAutoField"
|
||||
name: str = "feedvault"
|
||||
|
|
@ -15,7 +15,7 @@ def add_global_context(request: HttpRequest) -> dict[str, str | int]: # noqa: A
|
|||
Returns:
|
||||
A dictionary with the global context.
|
||||
"""
|
||||
from feeds.stats import get_db_size # noqa: PLC0415
|
||||
from feedvault.stats import get_db_size # noqa: PLC0415
|
||||
|
||||
from .models import Feed # noqa: PLC0415
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# Generated by Django 5.0.2 on 2024-02-19 02:47
|
||||
# Generated by Django 5.0.3 on 2024-03-15 01:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
@ -9,20 +10,10 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Author',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.TextField(blank=True)),
|
||||
('href', models.TextField(blank=True)),
|
||||
('email', models.TextField(blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
|
|
@ -36,21 +27,14 @@ class Migration(migrations.Migration):
|
|||
('hidden_at', models.DateTimeField(blank=True, null=True)),
|
||||
('hidden_reason', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Domain',
|
||||
'verbose_name_plural': 'Domains',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Links',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('rel', models.TextField(blank=True)),
|
||||
('type', models.TextField(blank=True)),
|
||||
('href', models.TextField(blank=True)),
|
||||
('title', models.TextField(blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Publisher',
|
||||
name='Author',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
|
|
@ -59,6 +43,12 @@ class Migration(migrations.Migration):
|
|||
('href', models.TextField(blank=True)),
|
||||
('email', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Author',
|
||||
'verbose_name_plural': 'Authors',
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('name', 'email', 'href')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Generator',
|
||||
|
|
@ -71,9 +61,47 @@ class Migration(migrations.Migration):
|
|||
('version', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Feed generator',
|
||||
'verbose_name_plural': 'Feed generators',
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('name', 'version', 'href')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Links',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('rel', models.TextField(blank=True)),
|
||||
('type', models.TextField(blank=True)),
|
||||
('href', models.TextField(blank=True)),
|
||||
('title', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Link',
|
||||
'verbose_name_plural': 'Links',
|
||||
'ordering': ['href'],
|
||||
'unique_together': {('href', 'rel')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Publisher',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.TextField(blank=True)),
|
||||
('href', models.TextField(blank=True)),
|
||||
('email', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Publisher',
|
||||
'verbose_name_plural': 'Publishers',
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('name', 'email', 'href')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Feed',
|
||||
fields=[
|
||||
|
|
@ -123,11 +151,17 @@ class Migration(migrations.Migration):
|
|||
('ttl', models.TextField(blank=True)),
|
||||
('updated', models.TextField(blank=True)),
|
||||
('updated_parsed', models.DateTimeField(blank=True, null=True)),
|
||||
('author_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feeds.author')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.domain')),
|
||||
('generator_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feeds.generator')),
|
||||
('publisher_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feeds.publisher')),
|
||||
('author_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feedvault.author')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feedvault.domain')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('generator_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feedvault.generator')),
|
||||
('publisher_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='feeds', to='feedvault.publisher')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Feed',
|
||||
'verbose_name_plural': 'Feeds',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Entry',
|
||||
|
|
@ -159,9 +193,14 @@ class Migration(migrations.Migration):
|
|||
('title_detail', models.JSONField(blank=True, null=True)),
|
||||
('updated', models.TextField(blank=True)),
|
||||
('updated_parsed', models.DateTimeField(blank=True, null=True)),
|
||||
('author_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='feeds.author')),
|
||||
('feed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.feed')),
|
||||
('publisher_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='feeds.publisher')),
|
||||
('author_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='feedvault.author')),
|
||||
('feed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feedvault.feed')),
|
||||
('publisher_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='feedvault.publisher')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entry',
|
||||
'verbose_name_plural': 'Entries',
|
||||
'ordering': ['-created_parsed'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -7,22 +7,43 @@ from dotenv import find_dotenv, load_dotenv
|
|||
|
||||
load_dotenv(dotenv_path=find_dotenv(), verbose=True)
|
||||
|
||||
|
||||
# Run Django in debug mode
|
||||
DEBUG: bool = os.getenv(key="DEBUG", default="True").lower() == "true"
|
||||
|
||||
BASE_DIR: Path = Path(__file__).resolve().parent.parent
|
||||
|
||||
# The secret key is used for cryptographic signing, and should be set to a unique, unpredictable value.
|
||||
SECRET_KEY: str = os.getenv("SECRET_KEY", default="")
|
||||
ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "django@feedvault.se")]
|
||||
ALLOWED_HOSTS: list[str] = [".feedvault.se", ".localhost", "127.0.0.1"]
|
||||
CSRF_COOKIE_DOMAIN = ".feedvault.se"
|
||||
CSRF_TRUSTED_ORIGINS: list[str] = ["https://feedvault.se", "https://www.feedvault.se"]
|
||||
TIME_ZONE = "Europe/Stockholm"
|
||||
USE_TZ = True
|
||||
USE_I18N = False
|
||||
LANGUAGE_CODE = "en-us"
|
||||
DECIMAL_SEPARATOR = ","
|
||||
THOUSAND_SEPARATOR = " "
|
||||
EMAIL_HOST = "smtp.gmail.com"
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST_USER: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
EMAIL_HOST_PASSWORD: str = os.getenv(key="EMAIL_HOST_PASSWORD", default="")
|
||||
EMAIL_SUBJECT_PREFIX = "[FeedVault] "
|
||||
EMAIL_USE_LOCALTIME = True
|
||||
EMAIL_TIMEOUT = 10
|
||||
DEFAULT_FROM_EMAIL: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
SERVER_EMAIL: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
USE_X_FORWARDED_HOST = True
|
||||
INTERNAL_IPS: list[str] = ["127.0.0.1", "localhost"]
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
SITE_ID = 1
|
||||
PASSWORD_HASHERS: list[str] = ["django.contrib.auth.hashers.Argon2PasswordHasher"]
|
||||
ROOT_URLCONF = "feedvault.urls"
|
||||
WSGI_APPLICATION = "feedvault.wsgi.application"
|
||||
|
||||
INSTALLED_APPS: list[str] = [
|
||||
"feeds.apps.FeedsConfig",
|
||||
"feedvault.apps.FeedVaultConfig",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sitemaps",
|
||||
]
|
||||
|
||||
|
|
@ -36,22 +57,15 @@ MIDDLEWARE: list[str] = [
|
|||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "feedvault.urls"
|
||||
|
||||
|
||||
WSGI_APPLICATION = "feedvault.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||
DATABASES: dict[str, dict[str, str]] = {
|
||||
database_folder: Path = BASE_DIR / "data"
|
||||
database_folder.mkdir(parents=True, exist_ok=True)
|
||||
DATABASES: dict[str, dict[str, str | Path | bool]] = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "feedvault",
|
||||
"USER": os.getenv(key="POSTGRES_USER", default=""),
|
||||
"PASSWORD": os.getenv(key="POSTGRES_PASSWORD", default=""),
|
||||
"HOST": os.getenv(key="POSTGRES_HOST", default=""),
|
||||
"PORT": os.getenv(key="POSTGRES_PORT", default="5432"),
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": database_folder / "feedvault.sqlite3",
|
||||
"ATOMIC_REQUESTS": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +100,7 @@ TEMPLATES = [
|
|||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"feeds.context_processors.add_global_context",
|
||||
"feedvault.context_processors.add_global_context",
|
||||
],
|
||||
"loaders": [
|
||||
(
|
||||
|
|
@ -100,62 +114,3 @@ TEMPLATES = [
|
|||
},
|
||||
},
|
||||
]
|
||||
|
||||
# A list of all the people who get code error notifications. When DEBUG=False and a view raises an exception, Django
|
||||
ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "django@feedvault.se")]
|
||||
|
||||
# A list of strings representing the host/domain names that this Django site can serve.
|
||||
# .feedvault.se will match *.feedvault.se and feedvault.se
|
||||
ALLOWED_HOSTS: list[str] = [".feedvault.se", ".localhost", "127.0.0.1"]
|
||||
CSRF_COOKIE_DOMAIN = ".feedvault.se"
|
||||
CSRF_TRUSTED_ORIGINS: list[str] = ["https://feedvault.se", "https://www.feedvault.se"]
|
||||
|
||||
# The time zone that Django will use to display datetimes in templates and to interpret datetimes entered in forms
|
||||
TIME_ZONE = "Europe/Stockholm"
|
||||
|
||||
# If datetimes will be timezone-aware by default. If True, Django will use timezone-aware datetimes internally.
|
||||
USE_TZ = True
|
||||
|
||||
# Don't use Django's translation system
|
||||
USE_I18N = False
|
||||
|
||||
# Decides which translation is served to all users.
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
# Default decimal separator used when formatting decimal numbers.
|
||||
DECIMAL_SEPARATOR = ","
|
||||
|
||||
# Use a space as the thousand separator instead of a comma
|
||||
THOUSAND_SEPARATOR = " "
|
||||
|
||||
# Use gmail for sending emails
|
||||
EMAIL_HOST = "smtp.gmail.com"
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST_USER: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
EMAIL_HOST_PASSWORD: str = os.getenv(key="EMAIL_HOST_PASSWORD", default="")
|
||||
EMAIL_SUBJECT_PREFIX = "[FeedVault] "
|
||||
EMAIL_USE_LOCALTIME = True
|
||||
EMAIL_TIMEOUT = 10
|
||||
DEFAULT_FROM_EMAIL: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
SERVER_EMAIL: str = os.getenv(key="EMAIL_HOST_USER", default="webmaster@localhost")
|
||||
|
||||
# Use the X-Forwarded-Host header
|
||||
# USE_X_FORWARDED_HOST = True
|
||||
|
||||
# Set the Referrer Policy HTTP header on all responses that do not already have one.
|
||||
# SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
||||
|
||||
# Internal IPs that are allowed to see debug views
|
||||
INTERNAL_IPS: list[str] = ["127.0.0.1", "localhost"]
|
||||
|
||||
STATIC_URL = "static/"
|
||||
STATIC_ROOT: Path = BASE_DIR / "staticfiles"
|
||||
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Our site ID
|
||||
SITE_ID = 1
|
||||
|
||||
PASSWORD_HASHERS: list[str] = ["django.contrib.auth.hashers.Argon2PasswordHasher"]
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ class StaticViewSitemap(Sitemap):
|
|||
changefreq: str = "daily"
|
||||
priority: float = 0.5
|
||||
|
||||
def items(self: StaticViewSitemap) -> list[str]:
|
||||
def items(self: StaticViewSitemap) -> list[str]: # noqa: PLR6301
|
||||
"""Return all the items in the sitemap."""
|
||||
return ["feeds:index", "feeds:feeds", "feeds:domains"]
|
||||
|
||||
def location(self, item: str) -> str:
|
||||
def location(self: StaticViewSitemap, item: str) -> str: # noqa: PLR6301
|
||||
"""Return the location of the item."""
|
||||
return reverse(item)
|
||||
|
|
@ -21,13 +21,18 @@ def get_db_size() -> str:
|
|||
logger.debug("Got db_size from cache")
|
||||
return db_size
|
||||
|
||||
# Get SQLite database size
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT pg_size_pretty(pg_database_size(current_database()))")
|
||||
row = cursor.fetchone()
|
||||
cursor.execute("PRAGMA page_size")
|
||||
page_size_result = cursor.fetchone()
|
||||
page_size = page_size_result[0] if page_size_result else None
|
||||
|
||||
db_size = "0 MB" if row is None else str(row[0])
|
||||
cursor.execute("PRAGMA page_count")
|
||||
page_count_result = cursor.fetchone()
|
||||
page_count = page_count_result[0] if page_count_result else None
|
||||
|
||||
db_size = page_size * page_count if page_size and page_count else None
|
||||
|
||||
# Store value in cache for 15 minutes
|
||||
cache.set("db_size", db_size, 60 * 15)
|
||||
|
||||
return db_size
|
||||
return f"{db_size / 1024 / 1024:.2f} MB" if db_size is not None else "0 MB"
|
||||
|
|
@ -1,5 +1,48 @@
|
|||
from django.urls import include, path
|
||||
from __future__ import annotations
|
||||
|
||||
urlpatterns = [
|
||||
path("", include("feeds.urls")),
|
||||
from django.contrib.sitemaps import GenericSitemap
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.urls import URLPattern, path
|
||||
from django.views.decorators.cache import cache_page
|
||||
|
||||
from feedvault import views
|
||||
from feedvault.models import Domain, Feed
|
||||
from feedvault.sitemaps import StaticViewSitemap
|
||||
from feedvault.views import APIView, CustomLoginView, CustomLogoutView, ProfileView, RegisterView
|
||||
|
||||
app_name: str = "feedvault"
|
||||
|
||||
sitemaps = {
|
||||
"static": StaticViewSitemap,
|
||||
"feeds": GenericSitemap({"queryset": Feed.objects.all(), "date_field": "created_at"}),
|
||||
"domains": GenericSitemap({"queryset": Domain.objects.all(), "date_field": "created_at"}),
|
||||
}
|
||||
|
||||
|
||||
urlpatterns: list[URLPattern] = [
|
||||
path(route="", view=views.IndexView.as_view(), name="index"),
|
||||
path(route="feed/<int:feed_id>/", view=views.FeedView.as_view(), name="feed"),
|
||||
path(route="feeds/", view=views.FeedsView.as_view(), name="feeds"),
|
||||
path(route="add", view=views.AddView.as_view(), name="add"),
|
||||
path(route="upload", view=views.UploadView.as_view(), name="upload"),
|
||||
path(route="robots.txt", view=cache_page(timeout=60 * 60 * 365)(views.RobotsView.as_view()), name="robots"),
|
||||
path(
|
||||
"sitemap.xml",
|
||||
sitemap,
|
||||
{"sitemaps": sitemaps},
|
||||
name="django.contrib.sitemaps.views.sitemap",
|
||||
),
|
||||
path(route="domains/", view=views.DomainsView.as_view(), name="domains"),
|
||||
path(route="domain/<int:domain_id>/", view=views.DomainView.as_view(), name="domain"),
|
||||
path(route="api/", view=APIView.as_view(), name="api"),
|
||||
path(route="api/feeds/", view=views.APIFeedsView.as_view(), name="api_feeds"),
|
||||
path(route="api/feeds/<int:feed_id>/", view=views.APIFeedView.as_view(), name="api_feeds_id"),
|
||||
path(route="api/feeds/<int:feed_id>/entries/", view=views.APIFeedEntriesView.as_view(), name="api_feed_entries"),
|
||||
path(route="api/entries/", view=views.APIEntriesView.as_view(), name="api_entries"),
|
||||
path(route="api/entries/<int:entry_id>/", view=views.APIEntryView.as_view(), name="api_entries_id"),
|
||||
path(route="accounts/login/", view=CustomLoginView.as_view(), name="login"),
|
||||
path(route="accounts/register/", view=RegisterView.as_view(), name="register"),
|
||||
path(route="accounts/logout/", view=CustomLogoutView.as_view(), name="logout"),
|
||||
# path(route="accounts/change-password/", view=CustomPasswordChangeView.as_view(), name="change_password"),
|
||||
path(route="accounts/profile/", view=ProfileView.as_view(), name="profile"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ from django.views import View
|
|||
from django.views.generic.edit import CreateView
|
||||
from django.views.generic.list import ListView
|
||||
|
||||
from feeds.add_feeds import add_feed
|
||||
from feeds.models import Domain, Entry, Feed
|
||||
from feedvault.add_feeds import add_feed
|
||||
from feedvault.models import Domain, Entry, Feed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.contrib.auth.models import User
|
||||
|
|
@ -188,7 +188,7 @@ class RegisterView(CreateView):
|
|||
|
||||
template_name = "accounts/register.html"
|
||||
form_class = UserCreationForm
|
||||
success_url = reverse_lazy("feeds:login")
|
||||
success_url = reverse_lazy("login")
|
||||
|
||||
# Add context data to the view
|
||||
def get_context_data(self, **kwargs) -> dict: # noqa: ANN003
|
||||
|
|
@ -205,14 +205,14 @@ class RegisterView(CreateView):
|
|||
class CustomLogoutView(LogoutView):
|
||||
"""Logout view."""
|
||||
|
||||
next_page = "feeds:index" # Redirect to index after logout
|
||||
next_page = "index" # Redirect to index after logout
|
||||
|
||||
|
||||
class CustomPasswordChangeView(SuccessMessageMixin, PasswordChangeView):
|
||||
"""Custom password change view."""
|
||||
|
||||
template_name = "accounts/change_password.html"
|
||||
success_url = reverse_lazy("feeds:index")
|
||||
success_url = reverse_lazy("index")
|
||||
success_message = "Your password was successfully updated!"
|
||||
|
||||
# Add context data to the view
|
||||
125
poetry.lock
generated
125
poetry.lock
generated
|
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "argon2-cffi"
|
||||
|
|
@ -302,13 +302,13 @@ six = ">=1.13.0"
|
|||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.9.17"
|
||||
version = "0.9.22"
|
||||
description = "A Python implementation of the JSON5 data format."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "json5-0.9.17-py2.py3-none-any.whl", hash = "sha256:f8ec1ecf985951d70f780f6f877c4baca6a47b6e61e02c4cd190138d10a7805a"},
|
||||
{file = "json5-0.9.17.tar.gz", hash = "sha256:717d99d657fa71b7094877b1d921b1cce40ab444389f6d770302563bb7dfd9ae"},
|
||||
{file = "json5-0.9.22-py3-none-any.whl", hash = "sha256:6621007c70897652f8b5d03885f732771c48d1925591ad989aa80c7e0e5ad32f"},
|
||||
{file = "json5-0.9.22.tar.gz", hash = "sha256:b729bde7650b2196a35903a597d2b704b8fdf8648bfb67368cfb79f1174a17bd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
|
@ -316,13 +316,13 @@ dev = ["hypothesis"]
|
|||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
version = "24.0"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -336,104 +336,6 @@ files = [
|
|||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.1.18"
|
||||
description = "PostgreSQL database adapter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"},
|
||||
{file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
psycopg-binary = {version = "3.1.18", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""}
|
||||
typing-extensions = ">=4.1"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
binary = ["psycopg-binary (==3.1.18)"]
|
||||
c = ["psycopg-c (==3.1.18)"]
|
||||
dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
|
||||
docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
pool = ["psycopg-pool"]
|
||||
test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.1.18"
|
||||
description = "PostgreSQL database adapter for Python -- C optimisation distribution"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57"},
|
||||
{file = "psycopg_binary-3.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d"},
|
||||
{file = "psycopg_binary-3.1.18-cp311-cp311-win_amd64.whl", hash = "sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5"},
|
||||
{file = "psycopg_binary-3.1.18-cp312-cp312-win_amd64.whl", hash = "sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080"},
|
||||
{file = "psycopg_binary-3.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e"},
|
||||
{file = "psycopg_binary-3.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d"},
|
||||
{file = "psycopg_binary-3.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
|
|
@ -704,17 +606,6 @@ notebook = ["ipywidgets (>=6)"]
|
|||
slack = ["slack-sdk"]
|
||||
telegram = ["requests"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.1"
|
||||
|
|
@ -729,4 +620,4 @@ files = [
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "1fee2dcd23f6eebc52722c0bb70c478026ec820e2999ebfd63295fc1d0395f08"
|
||||
content-hash = "dd4d8ba16bb5e34d2e0f94009d4ea86de094a6b1d6d1af3e6b69c14e881ccf3e"
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ readme = "README.md"
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
django = {extras = ["argon2"], version = "^5.0.2"}
|
||||
django = { extras = ["argon2"], version = "^5.0.3" }
|
||||
python-dotenv = "^1.0.1"
|
||||
feedparser = "^6.0.11"
|
||||
psycopg = {extras = ["binary"], version = "^3.1.18"}
|
||||
gunicorn = "^21.2.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
|
@ -26,17 +25,21 @@ exclude = ["migrations"]
|
|||
fix = true
|
||||
unsafe-fixes = true
|
||||
preview = true
|
||||
line-length = 120
|
||||
lint.select = ["ALL"]
|
||||
lint.ignore = [
|
||||
"PLR6301", # Checks for the presence of unused self parameter in methods definitions
|
||||
"CPY001", # Missing copyright notice at top of file
|
||||
"ERA001", # Found commented-out code
|
||||
"FIX002", # Line contains TODO
|
||||
"D104", # Missing docstring in public package # TODO(TheLovinator): Fix this
|
||||
"D100", # Missing docstring in public module # TODO(TheLovinator): Fix this
|
||||
# https://github.com/TheLovinator1/panso.se/issues/25
|
||||
"CPY001", # Missing copyright notice at top of file
|
||||
"ERA001", # Found commented-out code
|
||||
"FIX002", # Line contains TODO
|
||||
"D100", # Checks for undocumented public module definitions.
|
||||
"D101", # Checks for undocumented public class definitions.
|
||||
"D102", # Checks for undocumented public method definitions.
|
||||
"D104", # Missing docstring in public package.
|
||||
"D105", # Missing docstring in magic method.
|
||||
"D106", # Checks for undocumented public class definitions, for nested classes.
|
||||
"COM812", # Checks for the absence of trailing commas.
|
||||
"ISC001", # Checks for implicitly concatenated strings on a single line.
|
||||
]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
|
@ -51,6 +54,5 @@ convention = "google"
|
|||
"PLR6301", # Checks for the presence of unused self parameter in methods definitions.
|
||||
]
|
||||
|
||||
|
||||
[tool.djlint]
|
||||
format_attribute_template_tags = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<p>
|
||||
You can register <a href='{% url "feeds:register" %}'>here</a>.
|
||||
You can register <a href="{% url 'register' %}">here</a>.
|
||||
</p>
|
||||
<h2>Login</h2>
|
||||
<form method="post">
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
{% endif %}
|
||||
<span class="title">
|
||||
<h1>
|
||||
<a href='{% url "feeds:index" %}'>FeedVault</a>
|
||||
<a href="{% url 'index' %}">FeedVault</a>
|
||||
</h1>
|
||||
</span>
|
||||
<div class="leftright">
|
||||
|
|
@ -104,21 +104,21 @@
|
|||
<small>
|
||||
<div class="leftright">
|
||||
<div class="left">
|
||||
<a href='{% url "feeds:index" %}'>Home</a> |
|
||||
<a href='{% url "feeds:domains" %}'>Domains</a> |
|
||||
<a href='{% url "feeds:feeds" %}'>Feeds</a> |
|
||||
<a href='{% url "feeds:api" %}'>API</a>
|
||||
<a href="{% url 'index' %}">Home</a> |
|
||||
<a href="{% url 'domains' %}">Domains</a> |
|
||||
<a href="{% url 'feeds' %}">Feeds</a> |
|
||||
<a href="{% url 'api' %}">API</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="https://github.com/TheLovinator1/FeedVault">GitHub</a> |
|
||||
<a href="https://github.com/sponsors/TheLovinator1">Donate</a>
|
||||
<!-- Show login if not logged in -->
|
||||
{% if not user.is_authenticated %}
|
||||
| <a href='{% url "feeds:login" %}'>Login</a>
|
||||
| <a href="{% url 'login' %}">Login</a>
|
||||
{% endif %}
|
||||
<!-- Show username if logged in -->
|
||||
{% if user.is_authenticated %}
|
||||
| <a href='{% url "feeds:profile" %}'>{{ user.username }}</a>
|
||||
| <a href="{% url 'profile' %}">{{ user.username }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<ul>
|
||||
{% for feed in feeds %}
|
||||
<li>
|
||||
<a href="/feed/{{ feed.id }}">{{ feed.feed_url }}</a>
|
||||
<a href="{% url 'feed' feed.id %}">{{ feed.feed_url }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>Found no feeds for this domain.</li>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{% for domain in domains %}
|
||||
{% if not domain.hidden %}
|
||||
<li>
|
||||
<a href="/domain/{{ domain.id }}">{{ domain.url }}</a> - {{ domain.created_at|date }}
|
||||
<a href="{% url 'domain' domain.id %}">{{ domain.url }}</a> - {{ domain.created_at|date }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
{% for feed in feeds %}
|
||||
<li>{{ feed.feed_url }} - {{ feed.created_at|date }}</li>
|
||||
<li>
|
||||
<a href="/feed/{{ feed.id }}">View</a>
|
||||
<a href="{% url 'feed' feed.id %}">View</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>No feeds yet.</li>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<p>
|
||||
Input the URLs of the feeds you wish to archive below. You can add as many as needed, and access them through the website or API. Alternatively, include links to .opml files, and the feeds within will be archived.
|
||||
</p>
|
||||
<form action='{% url "feeds:add" %}' method='post'>
|
||||
<form action="/add" method='post'>
|
||||
{% csrf_token %}
|
||||
<textarea id="urls" name="urls" rows="5" cols="50" required></textarea>
|
||||
<button type="submit">Add feeds</button>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
</p>
|
||||
<form enctype="multipart/form-data"
|
||||
method="post"
|
||||
action="{% url 'feeds:upload' %}">
|
||||
action="{% url 'upload' %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<input type="file" name="file" id="file" required>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
FeedVault is a service that archives web feeds. It allows users to access and search for historical content from various websites. The service is designed to preserve the history of the web and provide a reliable source for accessing content that may no longer be available on the original websites.
|
||||
</p>
|
||||
<p>
|
||||
You need to <a href='{% url "feeds:login" %}'>login</a> or <a href='{% url "feeds:register" %}'>register</a> to add new feeds or upload files.
|
||||
You need to <a href="{% url 'login' %}">login</a> or <a href="{% url 'register' %}">register</a> to add new feeds or upload files.
|
||||
</p>
|
||||
{% endif %}
|
||||
<h2>FAQ</h2>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue