feat: SQLAlchemy models for Article, Image, Issue, Setting
This commit is contained in:
@@ -1 +1,74 @@
|
||||
pass
|
||||
import json
|
||||
from datetime import datetime, date, timezone
|
||||
from app import db
|
||||
|
||||
|
||||
class Article(db.Model):
|
||||
__tablename__ = "articles"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
guid = db.Column(db.Text, unique=True, nullable=False)
|
||||
title = db.Column(db.Text, nullable=False)
|
||||
author = db.Column(db.Text, nullable=False)
|
||||
pub_date = db.Column(db.DateTime, nullable=False)
|
||||
categories = db.Column(db.Text, nullable=False, default="[]")
|
||||
link = db.Column(db.Text, nullable=False)
|
||||
content_html = db.Column(db.Text, nullable=False, default="")
|
||||
fetched_at = db.Column(
|
||||
db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
images = db.relationship("Image", backref="article", lazy=True,
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class Image(db.Model):
|
||||
__tablename__ = "images"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
article_id = db.Column(db.Integer, db.ForeignKey("articles.id"), nullable=False)
|
||||
original_url = db.Column(db.Text, nullable=False)
|
||||
local_path = db.Column(db.Text, nullable=False)
|
||||
width = db.Column(db.Integer, nullable=False)
|
||||
height = db.Column(db.Integer, nullable=False)
|
||||
|
||||
|
||||
class Issue(db.Model):
|
||||
__tablename__ = "issues"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
week_start = db.Column(db.Date, nullable=False)
|
||||
week_end = db.Column(db.Date, nullable=False)
|
||||
cover_method = db.Column(db.Text, nullable=False)
|
||||
cover_path = db.Column(db.Text, nullable=False)
|
||||
epub_path = db.Column(db.Text, nullable=False)
|
||||
article_ids = db.Column(db.Text, nullable=False, default="[]")
|
||||
excluded_article_ids = db.Column(db.Text, nullable=False, default="[]")
|
||||
created_at = db.Column(
|
||||
db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
status = db.Column(db.Text, nullable=False, default="draft")
|
||||
|
||||
|
||||
class Setting(db.Model):
|
||||
__tablename__ = "settings"
|
||||
|
||||
key = db.Column(db.Text, primary_key=True)
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
|
||||
@staticmethod
|
||||
def get(key, default=None):
|
||||
row = Setting.query.get(key)
|
||||
if row is None:
|
||||
return default
|
||||
return json.loads(row.value)
|
||||
|
||||
@staticmethod
|
||||
def set(key, value):
|
||||
row = Setting.query.get(key)
|
||||
if row is None:
|
||||
row = Setting(key=key, value=json.dumps(value))
|
||||
db.session.add(row)
|
||||
else:
|
||||
row.value = json.dumps(value)
|
||||
db.session.commit()
|
||||
|
||||
86
tests/test_models.py
Normal file
86
tests/test_models.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import json
|
||||
from datetime import datetime, date
|
||||
from src.models import Article, Image, Issue, Setting
|
||||
|
||||
|
||||
def test_create_article(db):
|
||||
article = Article(
|
||||
guid="https://example.com/?p=100",
|
||||
title="Test Article",
|
||||
author="Test Author",
|
||||
pub_date=datetime(2026, 4, 6, 12, 0, 0),
|
||||
categories=json.dumps(["Government"]),
|
||||
link="https://example.com/test",
|
||||
content_html="<p>Test content</p>",
|
||||
)
|
||||
db.session.add(article)
|
||||
db.session.commit()
|
||||
|
||||
saved = Article.query.filter_by(guid="https://example.com/?p=100").first()
|
||||
assert saved is not None
|
||||
assert saved.title == "Test Article"
|
||||
assert saved.author == "Test Author"
|
||||
assert json.loads(saved.categories) == ["Government"]
|
||||
assert saved.fetched_at is not None
|
||||
|
||||
|
||||
def test_article_guid_unique(db):
|
||||
a1 = Article(guid="dup", title="A", author="X", pub_date=datetime.now(),
|
||||
categories="[]", link="http://a", content_html="")
|
||||
a2 = Article(guid="dup", title="B", author="Y", pub_date=datetime.now(),
|
||||
categories="[]", link="http://b", content_html="")
|
||||
db.session.add(a1)
|
||||
db.session.commit()
|
||||
db.session.add(a2)
|
||||
try:
|
||||
db.session.commit()
|
||||
assert False, "Should have raised IntegrityError"
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
def test_create_image(db):
|
||||
article = Article(guid="img-test", title="A", author="X",
|
||||
pub_date=datetime.now(), categories="[]",
|
||||
link="http://a", content_html="")
|
||||
db.session.add(article)
|
||||
db.session.commit()
|
||||
|
||||
img = Image(
|
||||
article_id=article.id,
|
||||
original_url="https://example.com/photo.jpg",
|
||||
local_path="data/images/abc123.jpg",
|
||||
width=800,
|
||||
height=450,
|
||||
)
|
||||
db.session.add(img)
|
||||
db.session.commit()
|
||||
|
||||
assert img.id is not None
|
||||
assert img.article.guid == "img-test"
|
||||
|
||||
|
||||
def test_create_issue(db):
|
||||
issue = Issue(
|
||||
week_start=date(2026, 4, 6),
|
||||
week_end=date(2026, 4, 12),
|
||||
cover_method="text",
|
||||
cover_path="data/issues/cover.jpg",
|
||||
epub_path="data/issues/test.epub",
|
||||
article_ids=json.dumps([1, 2, 3]),
|
||||
excluded_article_ids=json.dumps([]),
|
||||
status="published",
|
||||
)
|
||||
db.session.add(issue)
|
||||
db.session.commit()
|
||||
assert issue.id is not None
|
||||
assert issue.created_at is not None
|
||||
|
||||
|
||||
def test_setting_crud(db):
|
||||
Setting.set("fetch_interval", 2)
|
||||
assert Setting.get("fetch_interval") == 2
|
||||
assert Setting.get("nonexistent", default="fallback") == "fallback"
|
||||
|
||||
Setting.set("fetch_interval", 4)
|
||||
assert Setting.get("fetch_interval") == 4
|
||||
Reference in New Issue
Block a user