214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
import uuid
|
|
from datetime import datetime, timedelta
|
|
|
|
from django.conf import settings
|
|
from django.contrib.humanize.templatetags.humanize import naturaltime
|
|
from django.db import models
|
|
from django.db.models import JSONField
|
|
from slugify import slugify
|
|
|
|
from boards.icons import DOMAIN_ICONS, DOMAIN_FAVICONS
|
|
|
|
|
|
class Board(models.Model):
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
slug = models.SlugField(unique=True)
|
|
|
|
name = models.CharField(max_length=120, db_index=True)
|
|
avatar = models.URLField(max_length=512, null=True)
|
|
|
|
curator_name = models.CharField(max_length=120)
|
|
curator_title = models.CharField(max_length=120, null=True)
|
|
curator_url = models.TextField(null=True)
|
|
curator_bio = models.TextField(null=True)
|
|
curator_footer = models.TextField(null=True)
|
|
|
|
schema = models.TextField(null=True)
|
|
|
|
created_at = models.DateTimeField(db_index=True)
|
|
updated_at = models.DateTimeField()
|
|
refreshed_at = models.DateTimeField(null=True)
|
|
|
|
is_visible = models.BooleanField(default=True)
|
|
is_private = models.BooleanField(default=True)
|
|
index = models.PositiveIntegerField(default=0)
|
|
|
|
class Meta:
|
|
db_table = "boards"
|
|
ordering = ["index", "name"]
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.created_at:
|
|
self.created_at = datetime.utcnow()
|
|
|
|
if not self.slug:
|
|
self.slug = slugify(self.name).lower()
|
|
|
|
self.updated_at = datetime.utcnow()
|
|
|
|
return super().save(*args, **kwargs)
|
|
|
|
def board_name(self):
|
|
return self.name or self.curator_name
|
|
|
|
def natural_refreshed_at(self):
|
|
if not self.refreshed_at:
|
|
return "now..."
|
|
return naturaltime(self.refreshed_at)
|
|
|
|
|
|
class BoardBlock(models.Model):
|
|
DEFAULT_VIEW = "blocks/three.html"
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
board = models.ForeignKey(Board, related_name="blocks", on_delete=models.CASCADE, db_index=True)
|
|
name = models.CharField(max_length=512, null=True)
|
|
slug = models.SlugField()
|
|
|
|
view = models.CharField(max_length=256, default=DEFAULT_VIEW, null=False)
|
|
|
|
created_at = models.DateTimeField(db_index=True)
|
|
updated_at = models.DateTimeField()
|
|
|
|
index = models.PositiveIntegerField(default=0)
|
|
|
|
class Meta:
|
|
db_table = "board_blocks"
|
|
ordering = ["index"]
|
|
|
|
@property
|
|
def template(self):
|
|
if self.view and self.view.endswith(".html"):
|
|
return self.view
|
|
return self.DEFAULT_VIEW
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.created_at:
|
|
self.created_at = datetime.utcnow()
|
|
|
|
if not self.slug:
|
|
self.slug = slugify(self.name).lower()
|
|
|
|
self.updated_at = datetime.utcnow()
|
|
|
|
return super().save(*args, **kwargs)
|
|
|
|
|
|
class BoardFeed(models.Model):
|
|
DEFAULT_VIEW = "feeds/simple.html"
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
board = models.ForeignKey(Board, related_name="feeds", on_delete=models.CASCADE, db_index=True)
|
|
block = models.ForeignKey(BoardBlock, related_name="feeds", on_delete=models.CASCADE, db_index=True)
|
|
name = models.CharField(max_length=512, null=True)
|
|
comment = models.TextField(null=True)
|
|
url = models.TextField()
|
|
icon = models.URLField(max_length=512, null=True)
|
|
rss = models.URLField(max_length=512, null=True)
|
|
mix = JSONField(null=True)
|
|
|
|
view = models.CharField(max_length=256, default=DEFAULT_VIEW, null=False)
|
|
|
|
created_at = models.DateTimeField(db_index=True)
|
|
last_article_at = models.DateTimeField(null=True)
|
|
refreshed_at = models.DateTimeField(null=True)
|
|
|
|
frequency = models.FloatField(default=0.0) # per week
|
|
columns = models.SmallIntegerField(default=1)
|
|
articles_per_column = models.SmallIntegerField(default=15)
|
|
index = models.PositiveIntegerField(default=0)
|
|
|
|
conditions = JSONField(null=True)
|
|
filters = JSONField(null=True)
|
|
is_parsable = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
db_table = "board_feeds"
|
|
ordering = ["index"]
|
|
|
|
@property
|
|
def template(self):
|
|
if self.view and self.view.endswith(".html"):
|
|
return self.view
|
|
return self.DEFAULT_VIEW
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.created_at:
|
|
self.created_at = datetime.utcnow()
|
|
self.updated_at = datetime.utcnow()
|
|
return super().save(*args, **kwargs)
|
|
|
|
def last_articles(self):
|
|
return self.articles.all()[:15 * self.columns]
|
|
|
|
def natural_last_article_at(self):
|
|
if not self.last_article_at:
|
|
return None
|
|
return naturaltime(self.last_article_at)
|
|
|
|
|
|
class Article(models.Model):
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
uniq_id = models.TextField(db_index=True)
|
|
board = models.ForeignKey(Board, related_name="articles", on_delete=models.CASCADE, db_index=True)
|
|
feed = models.ForeignKey(BoardFeed, related_name="articles", on_delete=models.CASCADE, db_index=True)
|
|
url = models.URLField(max_length=2048)
|
|
type = models.CharField(max_length=16)
|
|
domain = models.CharField(max_length=256, null=True)
|
|
title = models.CharField(max_length=256)
|
|
image = models.URLField(max_length=512, null=True)
|
|
description = models.TextField(null=True)
|
|
summary = models.TextField(null=True)
|
|
|
|
created_at = models.DateTimeField(db_index=True)
|
|
updated_at = models.DateTimeField()
|
|
|
|
class Meta:
|
|
db_table = "articles"
|
|
ordering = ["-created_at"]
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.created_at:
|
|
self.created_at = datetime.utcnow()
|
|
self.updated_at = datetime.utcnow()
|
|
return super().save(*args, **kwargs)
|
|
|
|
def icon(self):
|
|
article_icon = DOMAIN_ICONS.get(self.domain)
|
|
if not article_icon:
|
|
return ""
|
|
|
|
if article_icon.startswith("fa:"):
|
|
return f"""<i class="{article_icon[3:]}"></i> """
|
|
|
|
return f"""<img src="{article_icon}" alt="{self.domain}" class="icon"> """
|
|
|
|
def favicon(self):
|
|
if not self.domain:
|
|
return None
|
|
|
|
favicon_domain = self.domain.lower().replace("www.", "")
|
|
return DOMAIN_FAVICONS.get(favicon_domain)
|
|
|
|
def natural_created_at(self):
|
|
if not self.created_at:
|
|
return None
|
|
return naturaltime(self.created_at)
|
|
|
|
def is_fresh(self):
|
|
frequency = self.feed.frequency
|
|
now = datetime.utcnow()
|
|
|
|
if frequency <= 1:
|
|
# low frequency feed — any post this week is new
|
|
return self.created_at > now - timedelta(days=7)
|
|
elif frequency <= 20:
|
|
# average frequency — mark today posts
|
|
return self.created_at > now - timedelta(days=1)
|
|
elif frequency >= 100:
|
|
# extra high frequency — mark newest posts
|
|
return self.created_at > now - timedelta(hours=4)
|
|
|
|
# normal frequency
|
|
return self.created_at > now - timedelta(hours=8)
|