From 447ed3eaf7b000790067dfe6808bbfe639b174b8 Mon Sep 17 00:00:00 2001 From: Joel Addison Date: Fri, 3 Jan 2025 12:00:06 +0000 Subject: [PATCH] Reinstate Markdown support for symposion Allow Markdown within the speaker bio and proposal abstract, with a restricted set of tags for safety. Render direct to markdown instead of storing plain and HTML in the database. Fix issue in timetable when a day had no slots created. --- pinaxcon/settings.py | 26 +++++++++++++ .../symposion/proposals/_proposal_fields.html | 37 +++++++++---------- .../symposion/reviews/review_review.html | 4 +- .../schedule/presentation_detail.html | 7 ++-- .../symposion/speakers/speaker_profile.html | 3 +- requirements.txt | 4 +- static/src/scss/app.scss | 2 +- vendor/symposion/constants.py | 8 ++-- vendor/symposion/proposals/models.py | 6 +-- vendor/symposion/schedule/timetable.py | 5 ++- vendor/symposion/speakers/models.py | 4 +- vendor/symposion/text_parser.py | 3 +- 12 files changed, 67 insertions(+), 42 deletions(-) diff --git a/pinaxcon/settings.py b/pinaxcon/settings.py index 5930a8cf..4d448872 100644 --- a/pinaxcon/settings.py +++ b/pinaxcon/settings.py @@ -230,6 +230,7 @@ INSTALLED_APPS = [ "django_jsonfield_backport", "pinax.eventlog", "timezone_field", + 'markdownify.apps.MarkdownifyConfig', # symposion "symposion", @@ -347,6 +348,7 @@ LOGGING = { 'level': 'DEBUG' }, } + FIXTURE_DIRS = [ os.path.join(PROJECT_ROOT, "fixtures"), ] @@ -360,6 +362,30 @@ AUTHENTICATION_BACKENDS = [ LOGIN_URL = '/saml2/login/' SESSION_EXPIRE_AT_BROWSER_CLOSE = True +MARKDOWNIFY = { + "default": { + "WHITELIST_TAGS": [ + 'a', + 'abbr', + 'acronym', + 'b', + 'blockquote', + 'code', + 'em', + 'i', + 'li', + 'ol', + 'p', + 'pre', + 'strong', + 'ul', + ], + "MARKDOWN_EXTENSIONS": [ + "markdown.extensions.fenced_code", + ] + } +} + CONFERENCE_ID = 1 PROPOSAL_FORMS = { "talk": "pinaxcon.proposals.forms.TalkProposalForm", diff --git a/pinaxcon/templates/symposion/proposals/_proposal_fields.html b/pinaxcon/templates/symposion/proposals/_proposal_fields.html index 9667d371..054c8629 100644 --- a/pinaxcon/templates/symposion/proposals/_proposal_fields.html +++ b/pinaxcon/templates/symposion/proposals/_proposal_fields.html @@ -1,5 +1,6 @@ {% load i18n %} {% load lca2018_tags %} +{% load markdownify %}
@@ -98,26 +99,24 @@
-
{{ proposal.abstract_html|safe }} 
-

+
{{ proposal.abstract | markdownify }}
-
{{ proposal.private_abstract_html|safe }} 
-

+
{{ proposal.private_abstract | markdownify }}
- {% if proposal.content_warning_html %} -
{{ proposal.content_warning_html|safe }}
+ {% if proposal.content_warning %} +
{{ proposal.content_warning | markdownify }}
{% else %} -
No Content Warning Provided
+

None Provided

{% endif %}
@@ -137,7 +136,7 @@
{% if proposal.project_url %} -

{{ proposal.project_url|safe }} 

+

{{ proposal.project_url|safe }}

{% else %}

None Provided

{% endif %} @@ -148,7 +147,7 @@
{% if proposal.video_url %} -

{{ proposal.video_url|safe }} 

+

{{ proposal.video_url|safe }}

{% else %}

None Provided

{% endif %} @@ -158,10 +157,10 @@
- {% if proposal.technical_requirements_html %} -
{{ proposal.technical_requirements_html|safe }}
+ {% if proposal.technical_requirements %} +
{{ proposal.technical_requirements | markdownify }}
{% else %} -
No Special Talk Requirements Requested
+

None Provided

{% endif %}
@@ -169,21 +168,21 @@
-

{{ proposal.require_approval }} 

+

{{ proposal.require_approval }}

-

{{ proposal.recording_release }} 

+

{{ proposal.recording_release }}

-

{{ proposal.materials_release }} 

+

{{ proposal.materials_release }}

@@ -210,16 +209,16 @@
-
{{ speaker.biography_html|safe }} 
+
{{ speaker.biography | markdownify }}
-
{{ speaker.experience_html|safe }} 
+
{{ speaker.experience | markdownify }}
- {% if speaker.accessibility_html %} + {% if speaker.accessibility %}
-
{{ speaker.accessibility_html|safe }} 
+
{{ speaker.accessibility | markdownify }}
{% endif %}
diff --git a/pinaxcon/templates/symposion/reviews/review_review.html b/pinaxcon/templates/symposion/reviews/review_review.html index 93096a10..e5db903d 100644 --- a/pinaxcon/templates/symposion/reviews/review_review.html +++ b/pinaxcon/templates/symposion/reviews/review_review.html @@ -1,6 +1,6 @@ {% extends "site_base.html" %} {% load static %} - +{% load markdownify %} {% load bootstrap %} {% block body_class %}review{% endblock %} @@ -28,7 +28,7 @@

Abstract

- {{ proposal.abstract_html|safe }} + {{ proposal.abstract | markdownify }}

Audience level: {{ proposal.get_audience_level_display }}

diff --git a/pinaxcon/templates/symposion/schedule/presentation_detail.html b/pinaxcon/templates/symposion/schedule/presentation_detail.html index f8bd2d4f..62c01cc7 100644 --- a/pinaxcon/templates/symposion/schedule/presentation_detail.html +++ b/pinaxcon/templates/symposion/schedule/presentation_detail.html @@ -5,6 +5,7 @@ {% load sitetree %} {% load static %} {% load thumbnail %} +{% load markdownify %} {% block head_title %}Presentation: {{ presentation.title }}{% endblock %} {% block page_title %}{{ presentation.title }}{% endblock %} @@ -42,7 +43,7 @@ {{ speaker.homepage }} {% endif %}

-
{{ speaker.biography_html|safe}}
+
{{ speaker.biography | markdownify }}

{% endfor %} @@ -51,9 +52,7 @@

Abstract

- {% autoescape off %} -

{{ presentation.abstract_html|safe|clean_text|urlize }}

- {% endautoescape %} +
{{ presentation.abstract | markdownify }}
{% endblock %} diff --git a/pinaxcon/templates/symposion/speakers/speaker_profile.html b/pinaxcon/templates/symposion/speakers/speaker_profile.html index 666401cc..245e808f 100644 --- a/pinaxcon/templates/symposion/speakers/speaker_profile.html +++ b/pinaxcon/templates/symposion/speakers/speaker_profile.html @@ -4,6 +4,7 @@ {% load lca2018_tags %} {% load lca2019_tags %} {% load thumbnail %} +{% load markdownify %} {% block head_title %}Speaker - {{ speaker.name }}{% endblock %} {% block page_title %}Speaker - {{ speaker.name }}{% endblock %} @@ -26,7 +27,7 @@

Biography

-
{{ speaker.biography_html|safe }}
+
{{ speaker.biography | markdownify }}

Presentations

diff --git a/requirements.txt b/requirements.txt index 80e6abe1..ef0e0896 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,9 +30,11 @@ django-sitetree==1.16.0 django-taggit==1.3.0 django-timezone-field==4.1.2 easy-thumbnails==2.8.5 -bleach==3.2.1 +bleach==6.1.0 pytz>=2020.1 django-ical==1.7.1 +django-markdownify==0.9.5 +markdown==3.7 # Registrasion reqs django-nested-admin==3.3.2 diff --git a/static/src/scss/app.scss b/static/src/scss/app.scss index d34c68d0..3953b05d 100644 --- a/static/src/scss/app.scss +++ b/static/src/scss/app.scss @@ -111,7 +111,7 @@ h3, .h3 { label.label-required:after { content: ' *'; } -.abstract, .bio, .monospace-text { +.monospace-text { font-family: Hack, monospace; white-space: pre-wrap; } diff --git a/vendor/symposion/constants.py b/vendor/symposion/constants.py index 762f32cf..4736f863 100644 --- a/vendor/symposion/constants.py +++ b/vendor/symposion/constants.py @@ -1,5 +1,3 @@ -TEXT_FIELD_MONOSPACE_NOTE=( - "This field is rendered with the monospace font " - "Hack with " - "whitespace preserved") - +TEXT_FIELD_FORMAT_NOTE=( + "This field supports Markdown, " + + "with limitations on allowed elements.") diff --git a/vendor/symposion/proposals/models.py b/vendor/symposion/proposals/models.py index bc3f295e..62c297c5 100644 --- a/vendor/symposion/proposals/models.py +++ b/vendor/symposion/proposals/models.py @@ -100,7 +100,7 @@ class ProposalBase(models.Model): abstract = models.TextField( _("Abstract"), help_text=_("This will appear in the conference programme. Up to about " - "500 words. " + constants.TEXT_FIELD_MONOSPACE_NOTE) + "500 words. " + constants.TEXT_FIELD_FORMAT_NOTE) ) abstract_html = models.TextField(blank=True) @@ -109,7 +109,7 @@ class ProposalBase(models.Model): help_text=_("This will only be shown to organisers and reviewers. You " "should provide any details about your proposal that you " "don't want to be public here. " + - constants.TEXT_FIELD_MONOSPACE_NOTE) + constants.TEXT_FIELD_FORMAT_NOTE) ) private_abstract_html = models.TextField(blank=True) @@ -121,7 +121,7 @@ class ProposalBase(models.Model): "please list your technical requirements here. Such as: a " "static IP address, A/V equipment or will be demonstrating " "security-related techniques on the conference network. " + - constants.TEXT_FIELD_MONOSPACE_NOTE) + constants.TEXT_FIELD_FORMAT_NOTE) ) technical_requirements_html = models.TextField(blank=True) diff --git a/vendor/symposion/schedule/timetable.py b/vendor/symposion/schedule/timetable.py index 893851aa..dbff8c8f 100644 --- a/vendor/symposion/schedule/timetable.py +++ b/vendor/symposion/schedule/timetable.py @@ -34,9 +34,10 @@ class TimeTable(object): return self._rooms def __iter__(self): - slots = self.slots() + if not self._times: + return - row = [] + slots = self.slots() total_room_count = self.rooms().count() for time, next_time in pairwise(self._times): row = {"time": time, "end": next_time, "slots": []} diff --git a/vendor/symposion/speakers/models.py b/vendor/symposion/speakers/models.py index e43add90..74b25469 100644 --- a/vendor/symposion/speakers/models.py +++ b/vendor/symposion/speakers/models.py @@ -41,7 +41,7 @@ class Speaker(models.Model): help_text=_("This will appear on the conference website and in the " "programme. Please write in the third person, eg 'Alice " "is a Moblin hacker...', 150-200 words. " + - constants.TEXT_FIELD_MONOSPACE_NOTE), + constants.TEXT_FIELD_FORMAT_NOTE), verbose_name=_("Biography"), ) biography_html = models.TextField(blank=True) @@ -51,7 +51,7 @@ class Speaker(models.Model): "we'd like to know. Anything you put here will only be " "seen by the organisers and reviewers; use it to convince " "them why they should accept your proposal. " + - constants.TEXT_FIELD_MONOSPACE_NOTE), + constants.TEXT_FIELD_FORMAT_NOTE), verbose_name=_("Speaking experience"), ) experience_html = models.TextField(blank=True) diff --git a/vendor/symposion/text_parser.py b/vendor/symposion/text_parser.py index 939d449e..841c49ae 100644 --- a/vendor/symposion/text_parser.py +++ b/vendor/symposion/text_parser.py @@ -1,7 +1,6 @@ import bleach -tags = bleach.sanitizer.ALLOWED_TAGS[:] -tags.extend(['p', 'pre']) +tags = bleach.sanitizer.ALLOWED_TAGS | {'p', 'pre'} def parse(text):