diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | core/html.py | 3 | ||||
-rw-r--r-- | core/ld.py | 17 | ||||
-rw-r--r-- | takahe/settings/base.py | 6 | ||||
-rw-r--r-- | takahe/urls.py | 4 | ||||
-rw-r--r-- | templates/activities/_post.html | 4 | ||||
-rw-r--r-- | templates/identity/view.html | 6 | ||||
-rw-r--r-- | users/models/identity.py | 22 |
8 files changed, 54 insertions, 9 deletions
@@ -1,5 +1,6 @@ *.psql *.sqlite3 .venv +/*.env /media/ notes.md diff --git a/core/html.py b/core/html.py index fd41a50..5045b16 100644 --- a/core/html.py +++ b/core/html.py @@ -20,12 +20,13 @@ def sanitize_post(post_html: str) -> str: Only allows a, br, p and span tags, and class attributes. """ cleaner = bleach.Cleaner( - tags=["a", "br", "p", "span"], + tags=["br", "p"], attributes={ # type:ignore "a": allow_a, "p": ["class"], "span": ["class"], }, filters=[LinkifyFilter], + strip=True, ) return mark_safe(cleaner.clean(post_html)) @@ -1,4 +1,5 @@ import datetime +import os import urllib.parse as urllib_parse from typing import Dict, List, Optional, Union @@ -436,3 +437,19 @@ def parse_ld_date(value: Optional[str]) -> Optional[datetime.datetime]: return datetime.datetime.strptime(value, DATETIME_FORMAT).replace( tzinfo=datetime.timezone.utc ) + + +def media_type_from_filename(filename): + _, extension = os.path.splitext(filename) + if extension == ".png": + return "image/png" + elif extension in [".jpg", ".jpeg"]: + return "image/png" + elif extension == ".gif": + return "image/gif" + elif extension == ".apng": + return "image/apng" + elif extension == ".webp": + return "image/webp" + else: + return "application/octet-stream" diff --git a/takahe/settings/base.py b/takahe/settings/base.py index b98b9a0..dd89818 100644 --- a/takahe/settings/base.py +++ b/takahe/settings/base.py @@ -108,5 +108,7 @@ STATICFILES_DIRS = [ ALLOWED_HOSTS = ["*"] -MEDIA_ROOT = BASE_DIR / "media" -MEDIA_URL = "/media/" + +# Note that this MUST be a fully qualified URL in production +MEDIA_URL = os.environ.get("TAKAHE_MEDIA_URL", "/media/") +MEDIA_ROOT = os.environ.get("TAKAHE_MEDIA_ROOT", BASE_DIR / "media") diff --git a/takahe/urls.py b/takahe/urls.py index c2d9d6b..0b23d7d 100644 --- a/takahe/urls.py +++ b/takahe/urls.py @@ -1,5 +1,3 @@ -import re - from django.conf import settings as djsettings from django.contrib import admin as djadmin from django.urls import path, re_path @@ -99,7 +97,7 @@ urlpatterns = [ path("djadmin/", djadmin.site.urls), # Media files re_path( - r"^%s(?P<path>.*)$" % re.escape(djsettings.MEDIA_URL.lstrip("/")), + r"^media/(?P<path>.*)$", serve, kwargs={"document_root": djsettings.MEDIA_ROOT}, ), diff --git a/templates/activities/_post.html b/templates/activities/_post.html index 5de8bc7..3d455ea 100644 --- a/templates/activities/_post.html +++ b/templates/activities/_post.html @@ -2,7 +2,9 @@ {% load activity_tags %} <div class="post" data-takahe-id="{{ post.id }}"> - <img src="{{ post.author.local_icon_url }}" class="icon"> + <a href="{{ post.author.urls.view }}"> + <img src="{{ post.author.local_icon_url }}" class="icon"> + </a> <time> {% if post.visibility == 0 %} diff --git a/templates/identity/view.html b/templates/identity/view.html index 223c2bb..f877f59 100644 --- a/templates/identity/view.html +++ b/templates/identity/view.html @@ -28,6 +28,12 @@ {{ identity.name_or_handle }} <small>@{{ identity.handle }}</small> </h1> + {% if identity.summary %} + <div class="summary"> + {{ identity.safe_summary }} + </div> + {% endif %} + {% if not identity.local %} {% if identity.outdated and not identity.name %} <p class="system-note"> diff --git a/users/models/identity.py b/users/models/identity.py index d4ab720..21912ac 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -8,10 +8,12 @@ from asgiref.sync import async_to_sync, sync_to_async from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from django.db import models +from django.template.defaultfilters import linebreaks_filter from django.templatetags.static import static from django.utils import timezone -from core.ld import canonicalise +from core.html import sanitize_post +from core.ld import canonicalise, media_type_from_filename from core.uploads import upload_namer from stator.models import State, StateField, StateGraph, StatorModel from users.models.domain import Domain @@ -139,6 +141,10 @@ class Identity(StatorModel): elif self.image_uri: return self.image_uri + @property + def safe_summary(self): + return sanitize_post(self.summary) + ### Alternate constructors/fetchers ### @classmethod @@ -223,7 +229,19 @@ class Identity(StatorModel): if self.name: response["name"] = self.name if self.summary: - response["summary"] = self.summary + response["summary"] = str(linebreaks_filter(self.summary)) + if self.icon: + response["icon"] = { + "type": "Image", + "mediaType": media_type_from_filename(self.icon.name), + "url": self.icon.url, + } + if self.image: + response["image"] = { + "type": "Image", + "mediaType": media_type_from_filename(self.image.name), + "url": self.image.url, + } return response ### ActivityPub (inbound) ### |