diora-web/feeds/migrations/0001_initial.py
mrwnslz 08baf970ca Feed-Reader (Server): Nachrichten und Blogs, Volltext, Meldungen bündeln
Neue App feeds: Quellen sind entweder Nachrichten oder Blog.
- Abruf wie bei Podcasts (ETag, Body-Hash, Backoff), refresh_newsfeeds für den Cron
- Anrisstexte: der Artikel wird von der Seite geholt (trafilatura), HTML bereinigt,
  relative Links absolut
- Nachrichten: gleiche Meldung aus mehreren Quellen wird eine Story (tf-idf, je
  Artikel verglichen), Briefing „Seit letztem Mal“ (Top-Themen, dann nach Ressort),
  verfallen nach news_keep_days (Standard 2)
- Blogs: Ungelesen-Liste je Blog, bleiben bis gelesen
- API unter /feeds/: Quellen suchen/abonnieren/OPML, News, Stories, Blogs,
  Artikel, gelesen, merken

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:57:23 +02:00

111 lines
5.8 KiB
Python

# Generated by Django 6.1 on 2026-10-04 17:54
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='FeedSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('news_seen_at', models.DateTimeField(blank=True, null=True)),
('news_keep_days', models.PositiveIntegerField(default=2)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='feed_settings', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Source',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField(max_length=1000)),
('title', models.CharField(max_length=300)),
('site_url', models.URLField(blank=True, max_length=1000)),
('icon_url', models.URLField(blank=True, max_length=1000)),
('kind', models.CharField(choices=[('news', 'Nachrichten'), ('blog', 'Blog')], default='news', max_length=10)),
('category', models.CharField(blank=True, max_length=100)),
('fulltext', models.CharField(choices=[('auto', 'Automatisch'), ('always', 'Immer'), ('never', 'Nie')], default='auto', max_length=10)),
('added_at', models.DateTimeField(auto_now_add=True)),
('last_refreshed_at', models.DateTimeField(blank=True, null=True)),
('http_etag', models.CharField(blank=True, max_length=500)),
('http_last_modified', models.CharField(blank=True, max_length=100)),
('http_body_hash', models.CharField(blank=True, max_length=64)),
('last_error', models.CharField(blank=True, max_length=500)),
('last_error_at', models.DateTimeField(blank=True, null=True)),
('error_count', models.PositiveIntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feed_sources', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['title'],
'unique_together': {('user', 'url')},
},
),
migrations.CreateModel(
name='Item',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('kind', models.CharField(choices=[('news', 'Nachrichten'), ('blog', 'Blog')], max_length=10)),
('guid_hash', models.CharField(max_length=64)),
('url', models.URLField(blank=True, max_length=2000)),
('title', models.CharField(max_length=500)),
('author', models.CharField(blank=True, max_length=300)),
('published', models.DateTimeField(db_index=True)),
('fetched_at', models.DateTimeField(auto_now_add=True)),
('summary', models.TextField(blank=True)),
('content', models.TextField(blank=True)),
('content_full', models.BooleanField(default=False)),
('fulltext_failed', models.BooleanField(default=False)),
('image_url', models.URLField(blank=True, max_length=2000)),
('read_at', models.DateTimeField(blank=True, null=True)),
('starred', models.BooleanField(default=False)),
('gone', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feed_items', to=settings.AUTH_USER_MODEL)),
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='feeds.source')),
],
options={
'ordering': ['-published'],
},
),
migrations.CreateModel(
name='Story',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_at', models.DateTimeField()),
('last_at', models.DateTimeField(db_index=True)),
('size', models.PositiveIntegerField(default=1)),
('sources', models.PositiveIntegerField(default=1)),
('terms', models.TextField(blank=True)),
('lead', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='feeds.item')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feed_stories', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-last_at'],
},
),
migrations.AddField(
model_name='item',
name='story',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items', to='feeds.story'),
),
migrations.AddIndex(
model_name='item',
index=models.Index(fields=['user', 'kind', 'published'], name='feeds_item_user_id_1a8bce_idx'),
),
migrations.AddIndex(
model_name='item',
index=models.Index(fields=['user', 'kind', 'read_at'], name='feeds_item_user_id_0007dd_idx'),
),
migrations.AlterUniqueTogether(
name='item',
unique_together={('source', 'guid_hash')},
),
]