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(