diff --git a/src/api/views/metrics.py b/src/api/views/metrics.py
index b59b981f448f77299000b3f1b8ca19c1eb2ea087..aa6bbe96573a5b1bcec679768ed29eb999dd146d 100644
--- a/src/api/views/metrics.py
+++ b/src/api/views/metrics.py
@@ -51,6 +51,11 @@ class MetricsView(TemplateView):
                 'type': 'counter',
                 'values': {}
             },
+            'hub_conference_members_themes': {
+                'help': 'used themes by members in the conference',
+                'type': 'gauge',
+                'values': {}
+            },
             'hub_conference_tickets': {
                 'help': 'registered tickets',
                 'type': 'counter',
@@ -160,6 +165,11 @@ class MetricsView(TemplateView):
             metrics['hub_conference_members_staff']['values'][f'{{conference="{slug}"}}'] = \
                 ConferenceMember.objects.filter(conference=conference, is_staff=True).count()
 
+            # hub_conference_members_themes
+            for theme in PlatformUser.Theme.values:
+                metrics['hub_conference_members_themes']['values'][f'{{conference="{slug}",theme="{theme}"}}'] = \
+                    ConferenceMember.objects.filter(conference=conference, user__theme=theme).count()
+
             # hub_conference_tickets
             metrics['hub_conference_tickets']['values'][f'{{conference="{slug}"}}'] = \
                 ConferenceMemberTicket.objects.filter(conference=conference).count()
diff --git a/src/core/models/schedules.py b/src/core/models/schedules.py
index 7b95740a684034c712507cafaddceba2d7afb567..dc6faaad140072beeb0f1b911eadb65e2f57ea0d 100644
--- a/src/core/models/schedules.py
+++ b/src/core/models/schedules.py
@@ -181,6 +181,12 @@ class ScheduleSource(models.Model):
                 'source_id': item_source_id,
                 'local_id': None,
             })
+
+            # if we have the item locally but shall skip it, handle it as 'handled' anyway
+            # so that it is not picked up again as e.g. 'missing_events'
+            if not new_mapping and mapping.local_id and mapping.local_id in expected_items:
+                expected_items.remove(mapping.local_id)
+
             return
 
         if new_mapping:
@@ -254,7 +260,7 @@ class ScheduleSource(models.Model):
 
         items[item_source_id] = mapping.local_object
         activity.append({
-            'action': 'changed',  # TODO: actually check if data was changed
+            'action': 'seen',  # TODO: set to 'changed' if data was changed (returned by .from_dict()?)
             'type': item_type,
             'source_id': item_source_id,
             'local_id': str(mapping.local_id),
@@ -270,9 +276,14 @@ class ScheduleSource(models.Model):
         events = {}
         rooms = {}
 
+        # derive some flags
+        missing_events = (self.import_configuration.get('missing_events') if self.import_configuration else None) or 'ignore'
+
         # note down all existing rooms and events so that we can call out the missing ones
         expected_rooms = list(self.assembly.rooms.values_list('id', flat=True))
-        expected_events = list(self.assembly.events.values_list('id', flat=True))
+        expected_events = list(self.mappings.filter(
+            mapping_type=ScheduleSourceMapping.MappingType.EVENT,
+        ).values_list('local_id', flat=True))
 
         # first, load the rooms (as they're needed for events)
         for r_id, r in data['rooms'].items():
@@ -331,14 +342,23 @@ class ScheduleSource(models.Model):
                 'local_id': str(room_id),
             })
 
-        # flag the non-loaded rooms as 'missing'
+        # flag the non-loaded events as 'missing'
         for event_id in expected_events:
-            activity.append({
+            act = {
                 'action': 'missing',
                 'type': 'event',
                 'source_id': None,
                 'local_id': str(event_id),
-            })
+            }
+
+            # check if we should do something about the missing event
+            if missing_events == 'depublish':
+                Event.objects.filter(pk=event_id).update(is_public=False)
+            elif missing_events == 'delete':
+                Event.objects.filter(pk=event_id).delete()
+                act['action'] = 'deleted'
+
+            activity.append(act)
 
         return activity
 
@@ -543,7 +563,7 @@ class ScheduleSourceImport(models.Model):
 
             self.data['_activity'] = activity
             if errors:
-                # create list of unique erros for summary
+                # create list of unique errors for summary
                 msgs = list(set([x['message'].split('\n')[0] for x in errors]))
 
             stats = ', '.join(
diff --git a/src/core/tests/schedules.py b/src/core/tests/schedules.py
index 6ddc280886022950c799249282876bb413da35da..516223f942da75f287fde92247dd5c906b0ba69f 100644
--- a/src/core/tests/schedules.py
+++ b/src/core/tests/schedules.py
@@ -4,6 +4,7 @@ import os
 
 from django.test import TestCase, override_settings
 
+from ..models import Event
 from ..models.assemblies import Assembly
 from ..models.conference import Conference, ConferenceMember
 from ..models.users import PlatformUser
@@ -86,6 +87,8 @@ class ScheduleTests(TestCase):
         self.assertEqual(1, self.assembly.rooms.count())
         self.assertEqual(1, self.assembly.events.count())
         self.assertIn('_activity', job.data)
+        job_activity = job.data['_activity']
+        self.assertEqual(2, len(job_activity))
         self.assertTrue(all(a['action'] == 'added' for a in job.data['_activity']))
 
         # check that room and event have been created
@@ -93,6 +96,7 @@ class ScheduleTests(TestCase):
         e1 = self.assembly.events.get(slug='minkorrekt')
         self.assertIsNotNone(r1)
         self.assertIsNotNone(e1)
+        self.assertEqual('Methodisch Inkorrekt', e1.name)
 
         # check that mappings exist
         self.assertEqual(2, src.mappings.count())
@@ -107,6 +111,81 @@ class ScheduleTests(TestCase):
         self.assertEqual(e1.room, r1)
         self.assertEqual(timedelta(minutes=90), e1.schedule_duration)
 
+        # change the event's name and flag it to be skipped
+        e1.name = 'Fnord'
+        e1.save()
+        e1_m.skip = True
+        e1_m.save()
+
+        # add a new event locally which was not imported
+        e2 = self.assembly.events.create(conference=self.conference, slug='fnordnewsshow', name='Fnord News Show', room=r1, is_public=True)
+
+        # redo the import
+        job2 = src.imports.create(state=ScheduleSourceImport.State.PREPARED)
+        job2.do_import()
+        job2_activity = job2.data.get('_activity')
+        self.assertIsNotNone(job2_activity)
+        self.assertEqual(2, len(job2_activity))
+
+        # check that the event has not been changed
+        e1.refresh_from_db()
+        self.assertEqual('Fnord', e1.name, 'name of skipped event has been changed')
+
+        # check that the event was flagged
+        job2_e1_activity_items = [x for x in job2_activity if x['type'] == 'event' and x['source_id'] == e1_m.source_id]
+        self.assertEqual(1, len(job2_e1_activity_items))
+        self.assertEqual('skipped', job2_e1_activity_items[0]['action'])
+
+        # fake a previous mapping for the local event (which is not present in the import data)
+        e2_m = src.mappings.create(mapping_type=ScheduleSourceMapping.MappingType.EVENT, local_id=e2.pk, source_id='foobar')
+
+        # redo the import
+        job3 = src.imports.create(state=ScheduleSourceImport.State.PREPARED)
+        job3.do_import()
+        job3_activity = job3.data.get('_activity')
+        self.assertEqual(3, len(job3_activity))
+
+        # check that the local (and fake-mapped) event was marked 'missing'
+        job3_e2_activity_items = [x for x in job3_activity if x['type'] == 'event' and x['local_id'] == str(e2.id)]
+        self.assertEqual(1, len(job3_e2_activity_items))
+        self.assertEqual('missing', job3_e2_activity_items[0]['action'])
+
+        # redo the import with missing_event='depublish'
+        self.assertTrue(e2.is_public)
+        src.import_configuration = {'missing_events': 'depublish'}
+        src.save()
+        job4 = src.imports.create(state=ScheduleSourceImport.State.PREPARED)
+        job4.do_import()
+        self.assertEqual(ScheduleSourceImport.State.COMPLETED, job4.state)
+        job4_activity = job4.data.get('_activity')
+
+        # check that the local (and fake-mapped) event was marked 'missing'
+        job4_e2_activity_items = [x for x in job4_activity if x['type'] == 'event' and x['local_id'] == str(e2.id)]
+        self.assertEqual(1, len(job4_e2_activity_items))
+        self.assertEqual('missing', job4_e2_activity_items[0]['action'])
+
+        # but now the event should have been de-published
+        e2.refresh_from_db()
+        self.assertFalse(e2.is_public)
+
+        # redo the import with missing_event='delete'
+        src.import_configuration = {'missing_events': 'delete'}
+        src.save()
+        job5 = src.imports.create(state=ScheduleSourceImport.State.PREPARED)
+        job5.do_import()
+        self.assertEqual(ScheduleSourceImport.State.COMPLETED, job5.state)
+        job5_activity = job5.data.get('_activity')
+
+        # check that the local (and fake-mapped) event was marked 'missing'
+        job5_e2_activity_items = [x for x in job5_activity if x['type'] == 'event' and x['local_id'] == str(e2.id)]
+        self.assertEqual(1, len(job5_e2_activity_items))
+        self.assertEqual('deleted', job5_e2_activity_items[0]['action'])
+
+        # but now the event should have been de-published
+        with self.assertRaises(Event.DoesNotExist):
+            e2.refresh_from_db()
+
+
     @override_settings(SCHEDULES_SUPPORT_FILE_PROTOCOL=True)
     def test_xml(self):
         src = ScheduleSource.objects.create(