diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py
index 6ef1d727fd..30fab561e2 100644
--- a/src/pretix/base/models/event.py
+++ b/src/pretix/base/models/event.py
@@ -365,6 +365,26 @@ class Event(EventMixin, LoggedModel):
providers[pp.identifier] = pp
return providers
+ @property
+ def event_microdata(self):
+ import json
+
+ eventdict = {"@context": "http://schema.org", "@type": "Event"}
+ eventdict["location"] = {"@type": "Place",
+ "address": str(self.location)}
+ if self.settings.show_times:
+ eventdict["startDate"] = self.date_from.isoformat()
+ if self.settings.show_date_to and self.date_to is not None:
+ eventdict["endDate"] = self.date_to.isoformat()
+ else:
+ eventdict["startDate"] = self.date_from.date().isoformat()
+ if self.settings.show_date_to and self.date_to is not None:
+ eventdict["endDate"] = self.date_to.date().isoformat()
+
+ eventdict["name"] = str(self.name)
+
+ return json.dumps(eventdict)
+
def get_invoice_renderers(self) -> dict:
"""
Returns a dictionary of initialized invoice renderers mapped by their identifiers.
diff --git a/src/pretix/presale/templates/pretixpresale/base.html b/src/pretix/presale/templates/pretixpresale/base.html
index 669ee7aed1..094bec1f95 100644
--- a/src/pretix/presale/templates/pretixpresale/base.html
+++ b/src/pretix/presale/templates/pretixpresale/base.html
@@ -30,6 +30,13 @@
{% endcompress %}
+ {% if event %}
+ {% autoescape off %}
+
+ {% endautoescape %}
+ {% endif %}
{{ html_head|safe }}
diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py
index 6f6cd2fece..5f5463cfd6 100644
--- a/src/tests/presale/test_event.py
+++ b/src/tests/presale/test_event.py
@@ -1,6 +1,7 @@
import datetime
import re
from decimal import Decimal
+from json import loads
from django.conf import settings
from django.core import mail
@@ -939,6 +940,49 @@ class EventIcalDownloadTest(EventTestMixin, SoupTest):
self.assertIn('LOCATION:Heeeeeere', ical, 'incorrect location')
+class EventMicrodataTest(EventTestMixin, SoupTest):
+ def setUp(self):
+ super().setUp()
+ self.event.settings.show_date_to = True
+ self.event.settings.show_times = True
+ self.event.location = 'DUMMY ARENA'
+ self.event.date_from = datetime.datetime(2013, 12, 26, 21, 57, 58, tzinfo=datetime.timezone.utc)
+ self.event.date_to = self.event.date_from + datetime.timedelta(days=2)
+ self.event.settings.timezone = 'UTC'
+ self.event.save()
+
+ def _get_json(self):
+ doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug))
+ microdata = loads(doc.find(type="application/ld+json").string)
+ return microdata
+
+ def test_name(self):
+ md = self._get_json()
+ self.assertEqual(self.event.name, md['name'], msg='Name not present')
+
+ def test_location(self):
+ md = self._get_json()
+ self.assertEqual(self.event.location, md['location']['address'], msg='Location not present')
+
+ def test_date_to(self):
+ md = self._get_json()
+ self.assertEqual(self.event.date_to.isoformat(), md['endDate'], msg='Date To not present')
+ self.event.settings.show_date_to = False
+ md = self._get_json()
+ self.assertNotIn(self.event.date_to.isoformat(), md,
+ msg='Date To present when show date to setting is false')
+
+ def test_no_times(self):
+ self.event.settings.show_times = False
+ md = self._get_json()
+ self.assertNotEqual(self.event.date_from.isoformat(), md['startDate'], msg='Date including time present')
+ self.assertEqual(self.event.date_from.date().isoformat(), md['startDate'], msg='Date not present at all')
+
+ def test_date_from(self):
+ md = self._get_json()
+ self.assertEqual(self.event.date_from.isoformat(), md['startDate'], msg='Date From not present')
+
+
class EventSlugBlacklistValidatorTest(EventTestMixin, SoupTest):
def test_slug_validation(self):
event = Event(