See #170: add a description field on tracks, albums, tracks

This commit is contained in:
Eliot Berriot 2020-01-14 14:00:08 +01:00
parent 424b9f133a
commit 2bc71eecfd
38 changed files with 653 additions and 59 deletions

View file

@ -26,3 +26,12 @@ class AttachmentFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
class Meta:
model = "common.Attachment"
@registry.register
class CommonFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
text = factory.Faker("paragraph")
content_type = "text/plain"
class Meta:
model = "common.Content"

View file

@ -0,0 +1,21 @@
# Generated by Django 2.2.7 on 2020-01-13 10:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0005_auto_20191125_1421'),
]
operations = [
migrations.CreateModel(
name='Content',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.CharField(blank=True, max_length=5000, null=True)),
('content_type', models.CharField(max_length=100)),
],
),
]

View file

@ -24,6 +24,14 @@ from . import utils
from . import validators
CONTENT_TEXT_MAX_LENGTH = 5000
CONTENT_TEXT_SUPPORTED_TYPES = [
"text/html",
"text/markdown",
"text/plain",
]
@Field.register_lookup
class NotEqual(Lookup):
lookup_name = "ne"
@ -273,6 +281,15 @@ class MutationAttachment(models.Model):
unique_together = ("attachment", "mutation")
class Content(models.Model):
"""
A text content that can be associated to other models, like a description, a summary, etc.
"""
text = models.CharField(max_length=CONTENT_TEXT_MAX_LENGTH, blank=True, null=True)
content_type = models.CharField(max_length=100)
@receiver(models.signals.post_save, sender=Attachment)
def warm_attachment_thumbnails(sender, instance, **kwargs):
if not instance.file or not settings.CREATE_IMAGE_THUMBNAILS:
@ -302,3 +319,18 @@ def trigger_mutation_post_init(sender, instance, created, **kwargs):
except AttributeError:
return
handler(instance)
CONTENT_FKS = {
"music.Track": ["description"],
"music.Album": ["description"],
"music.Artist": ["description"],
}
@receiver(models.signals.post_delete, sender=None)
def remove_attached_content(sender, instance, **kwargs):
fk_fields = CONTENT_FKS.get(instance._meta.label, [])
for field in fk_fields:
if getattr(instance, "{}_id".format(field)):
getattr(instance, field).delete()

View file

@ -86,7 +86,6 @@ class MutationSerializer(serializers.Serializer):
class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
serialized_relations = {}
previous_state_handlers = {}
def __init__(self, *args, **kwargs):
# we force partial mode, because update mutations are partial
@ -141,9 +140,12 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
obj,
*list(validated_data.keys()),
serialized_relations=self.serialized_relations,
handlers=self.previous_state_handlers,
handlers=self.get_previous_state_handlers(),
)
def get_previous_state_handlers(self):
return {}
def get_update_previous_state(obj, *fields, serialized_relations={}, handlers={}):
if not fields:

View file

@ -11,6 +11,7 @@ from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from . import models
from . import utils
class RelatedField(serializers.RelatedField):
@ -308,3 +309,12 @@ class AttachmentSerializer(serializers.Serializer):
return models.Attachment.objects.create(
file=validated_data["file"], actor=validated_data["actor"]
)
class ContentSerializer(serializers.Serializer):
text = serializers.CharField(max_length=models.CONTENT_TEXT_MAX_LENGTH)
content_type = serializers.ChoiceField(choices=models.CONTENT_TEXT_SUPPORTED_TYPES,)
html = serializers.SerializerMethodField()
def get_html(self, o):
return utils.render_html(o.text, o.content_type)

View file

@ -1,5 +1,7 @@
from django.utils.deconstruct import deconstructible
import bleach.sanitizer
import markdown
import os
import shutil
import uuid
@ -241,3 +243,65 @@ def join_queries_or(left, right):
return left | right
else:
return right
def render_markdown(text):
return markdown.markdown(text, extensions=["nl2br"])
HTMl_CLEANER = bleach.sanitizer.Cleaner(
strip=True,
tags=[
"p",
"a",
"abbr",
"acronym",
"b",
"blockquote",
"code",
"em",
"i",
"li",
"ol",
"strong",
"ul",
],
)
HTML_LINKER = bleach.linkifier.Linker()
def clean_html(html):
return HTMl_CLEANER.clean(html)
def render_html(text, content_type):
rendered = render_markdown(text)
if content_type == "text/html":
rendered = text
elif content_type == "text/markdown":
rendered = render_markdown(text)
else:
rendered = render_markdown(text)
rendered = HTML_LINKER.linkify(rendered)
return clean_html(rendered).strip().replace("\n", "")
@transaction.atomic
def attach_content(obj, field, content_data):
from . import models
existing = getattr(obj, "{}_id".format(field))
if existing:
getattr(obj, field).delete()
if not content_data:
return
content_obj = models.Content.objects.create(
text=content_data["text"][: models.CONTENT_TEXT_MAX_LENGTH],
content_type=content_data["content_type"],
)
setattr(obj, field, content_obj)
obj.save(update_fields=[field])