lemoncurry/entries/models.py

241 lines
6.4 KiB
Python

from computed_property import ComputedCharField
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site as DjangoSite
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property
from itertools import groupby
from mf2util import interpret
from slugify import slugify
from textwrap import shorten
from urllib.parse import urljoin, urlparse
from lemonshort.short_url import short_url
from meta.models import ModelMeta
from model_utils.models import TimeStampedModel
from users.models import Site
from . import kinds
from lemoncurry import requests, utils
ENTRY_KINDS = [(k.id, k.id) for k in kinds.all]
class CatManager(models.Manager):
def from_name(self, name):
cat, created = self.get_or_create(name=name, slug=slugify(name))
return cat
class Cat(models.Model):
objects = CatManager()
name = models.CharField(max_length=255, unique=True)
slug = models.CharField(max_length=255, unique=True)
def __str__(self):
return '#' + self.name
@property
def url(self):
return reverse('entries:cat', args=(self.slug,))
class Meta:
ordering = ('name',)
class EntryManager(models.Manager):
def get_queryset(self):
qs = super(EntryManager, self).get_queryset()
return (qs
.select_related('author')
.prefetch_related('cats', 'syndications'))
class Entry(ModelMeta, TimeStampedModel):
objects = EntryManager()
kind = models.CharField(
max_length=30,
choices=ENTRY_KINDS,
db_index=True,
default=ENTRY_KINDS[0][0]
)
name = models.CharField(max_length=100, blank=True)
photo = models.ImageField(blank=True)
content = models.TextField()
cats = models.ManyToManyField(Cat, related_name='entries')
in_reply_to = models.CharField(max_length=255, blank=True)
like_of = models.CharField(max_length=255, blank=True)
repost_of = models.CharField(max_length=255, blank=True)
author = models.ForeignKey(
get_user_model(),
related_name='entries',
on_delete=models.CASCADE,
)
@property
def reply_context(self):
if not self.in_reply_to:
return None
return interpret(
requests.mf2(self.in_reply_to).to_dict(),
self.in_reply_to
)
@property
def published(self):
return self.created
@property
def updated(self):
return self.modified
_metadata = {
'description': 'excerpt',
'image': 'image_url',
'twitter_creator': 'twitter_creator',
'og_profile_id': 'og_profile_id',
}
@property
def title(self):
if self.name:
return self.name
return shorten(
utils.to_plain(self.paragraphs[0]),
width=100,
placeholder=''
)
@property
def excerpt(self):
try:
return utils.to_plain(self.paragraphs[0 if self.name else 1])
except IndexError:
return ' '
@property
def paragraphs(self):
lines = self.content.splitlines()
return [
"\n".join(para) for k, para in groupby(lines, key=bool) if k
]
@property
def twitter_creator(self):
return self.author.twitter_username
@property
def og_profile_id(self):
return self.author.facebook_id
@property
def image_url(self):
return self.photo.url if self.photo else self.author.avatar_url
def __str__(self):
return '{0} {1}: {2}'.format(self.kind, self.id, self.title)
def get_absolute_url(self):
return self.absolute_url
@property
def absolute_url(self):
base = 'https://' + DjangoSite.objects.get_current().domain
return urljoin(base, self.url)
@property
def affected_urls(self):
base = 'https://' + DjangoSite.objects.get_current().domain
kind = kinds.from_id[self.kind]
urls = {
self.url,
reverse('entries:index', kwargs={'kind': kind}),
reverse('entries:atom_by_kind', kwargs={'kind': kind}),
reverse('entries:rss_by_kind', kwargs={'kind': kind}),
} | {cat.url for cat in self.cats.all()}
if kind.on_home:
urls |= {
reverse('home:index'),
reverse('entries:atom'),
reverse('entries:rss')
}
return {urljoin(base, u) for u in urls}
@property
def url(self):
kind = kinds.from_id[self.kind]
args = [kind, self.id]
if kind.slug:
args.append(self.slug)
return reverse('entries:entry', args=args)
@property
def short_url(self):
return short_url(self)
@property
def slug(self):
return slugify(self.name)
@property
def json_ld(self):
base = 'https://' + DjangoSite.objects.get_current().domain
url = urljoin(base, self.url)
posting = {
'@context': 'http://schema.org',
'@type': 'BlogPosting',
'@id': url,
'url': url,
'mainEntityOfPage': url,
'author': {
'@type': 'Person',
'url': urljoin(base, self.author.url),
'name': self.author.name,
},
'headline': self.title,
'description': self.excerpt,
'datePublished': self.created.isoformat(),
'dateModified': self.modified.isoformat(),
}
if self.photo:
posting['image'] = (urljoin(base, self.photo.url), )
return posting
class Meta:
verbose_name_plural = 'entries'
ordering = ['-created']
class Syndication(models.Model):
entry = models.ForeignKey(
Entry,
related_name='syndications',
on_delete=models.CASCADE
)
url = models.CharField(max_length=255)
domain = ComputedCharField(
compute_from='calc_domain', max_length=255,
)
def calc_domain(self):
domain = urlparse(self.url).netloc
if domain.startswith('www.'):
domain = domain[4:]
return domain
@cached_property
def site(self):
d = self.domain
try:
return Site.objects.get(domain=d)
except Site.DoesNotExist:
return Site(name=d, domain=d, icon='fas fa-newspaper')
class Meta:
ordering = ['domain']