diff --git a/src/backoffice/templates/backoffice/schedule_source_import-detail.html b/src/backoffice/templates/backoffice/schedule_source_import-detail.html index bd3d829fa05babe9b0318c08ecde58692cec5e1f..831850a1fb54d02e65186a09547f67f8659e5188 100644 --- a/src/backoffice/templates/backoffice/schedule_source_import-detail.html +++ b/src/backoffice/templates/backoffice/schedule_source_import-detail.html @@ -8,8 +8,8 @@ <div class="card mb-3"> <div class="card-header"> - Import ID <code>{{ object.id }}</code>, - Source <a href="{% url 'backoffice:schedulesource-detail' pk=object.schedule_source_id %}"><code>{{ object.schedule_source_id }}</code></a> + Import ID <code>{{ object.id }}</code>, + Source <a href="{% url 'backoffice:schedulesource-detail' pk=object.schedule_source_id %}"><code>{{ object.schedule_source_id }}</code></a> </div> <div class="card-body"> <p>State: <strong class="{{ object.text_color_class }}">{{ object.state }}</strong></p> @@ -21,22 +21,57 @@ {% if object.errors %} <div class="card mb-3 border-warning"> - <div class="card-header bg-warning text-white"> + <div class="card-header text-bg-warning"> Errors </div> - <div class="card-body"> - <code><pre>{{ object.errors|json_indent }}</pre></code> - </div> + <table class="card-body table table-sm table-hover"> + <thead> + <tr><th>Type</th><th>Action</th><th>Source ID</th><th>Local ID</th><th>Message</th></tr> + </thead> + <tbody> + {% for error in object.errors %} + <tr> + <td>{{ error.type|default:"???" }}</td> + <td>{{ error.action|default:"???" }}</td> + <td>{{ error.source_id|default:"-/-" }}</td> + <td>{{ error.local_id|default:"-/-" }}</td> + <td>{{ error.message }}</td> + </tr> + {% endfor %} + </tbody> + </table> </div> {%endif%} -<div class="card mb-3{% if object.data %} border-primary{% endif %}"> - <div class="card-header{% if object.data %} bg-primary text-white{% endif %}"> - Data - </div> - <div class="card-body"> - <code><pre>{{ object.data|json_indent }}</pre></code> +{% if activity %} +<div class="card mb-3 border-primary"> + <div class="card-header text-bg-primary"> + Activity </div> + <table class="card-body table table-sm table-hover"> + <thead> + <tr><th>Type</th><th>Action</th><th>Source ID</th><th>Local ID</th></tr> + </thead> + <tbody> + {% for item in activity %} + <tr{% if item.action != "seen" %} class="fw-bold"{% endif %}> + <td class="{% if item.action == "seen" %}text-muted{% elif item.action == "error" %}text-danger{% endif %}">{{ item.type|default:"???" }}</td> + <td class="{% if item.action == "seen" %}text-muted{% elif item.action == "error" %}text-danger{% endif %}">{{ item.action|default:"???" }}</td> + <td class="{% if item.action == "seen" %}text-muted{% elif item.action == "error" %}text-danger{% endif %}">{{ item.source_id|default:"-/-" }}</td> + <td class="{% if item.action == "seen" %}text-muted{% elif item.action == "error" %}text-danger{% endif %}">{{ item.local_id|default:"-/-" }}</td> + </tr> + {% endfor %} + </tbody> + </table> +</div> +{% endif %} + +{% if object.data %} +<div class="text-center"> + <a href="{% url 'backoffice:schedulesourceimport-detail-data' pk=object.pk %}" class="btn btn-outline-primary"> + data as JSON + </a> </div> +{% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/backoffice/templates/backoffice/schedules_index.html b/src/backoffice/templates/backoffice/schedules_index.html index d3f899751982df2d412472643c01844b90544932..f7ee8dc6dfd725e34b4fd33687e3b9cca68c2ae6 100644 --- a/src/backoffice/templates/backoffice/schedules_index.html +++ b/src/backoffice/templates/backoffice/schedules_index.html @@ -13,8 +13,8 @@ {% for source in sources %} {% with source.latest_import as latest %} <div class="col"> - <div class="card{% if source.has_running_import %} border-primary{% elif source.is_due %} border-warning{% endif %}"> - <div class="card-header{% if source.has_running_import %} text-bg-primary{% elif source.is_due %} text-bg-warning{% endif %}"> + <div class="card{% if source.has_running_import %} border-primary{% elif source.is_due %} border-warning{% elif latest.state == 'finished' %} border-success{% endif %}"> + <div class="card-header{% if source.has_running_import %} text-bg-primary{% elif source.is_due %} text-bg-warning{% elif latest.state == 'finished' %} text-bg-success{% endif %}"> <a href="{% url 'backoffice:schedulesource-detail' pk=source.pk %}" class="float-end ms-3">Details</a> {% if source.assembly %} <abbr title="{% trans "Assembly" %}: {{ source.assembly.name }}">{{ source.assembly.slug }}</abbr> @@ -51,9 +51,10 @@ </p> {% endif %} </div> + {% if latest %} <div class="card-footer"> <p> - <span class="text-muted small fw-bold">{% trans "ScheduleSource__latest_import" %}</span><br> + <a class="text-muted small fw-bold" href="{% url 'backoffice:schedulesourceimport-detail' pk=latest.pk %}">{% trans "ScheduleSource__latest_import" %}</a><br> <span class="text-muted small">{% trans "start" %}:</span> <abbr title="{{ latest.start }}">{{ latest.start|naturaltime|default:"-/-" }}</abbr><br> <span class="text-muted small">{% trans "end" %}:</span> @@ -62,6 +63,7 @@ {{ latest.get_state_display }}<br> </p> </div> + {% endif %} </div> </div> {% endwith %} diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py index 583392d966a80e586aa795d8a765604e1786c0df..e4b583c81fd42c99c87f1aed20066c1d1ccb8d4d 100644 --- a/src/backoffice/urls.py +++ b/src/backoffice/urls.py @@ -124,6 +124,7 @@ urlpatterns = [ path('schedule/source/<uuid:pk>/import', schedules.ScheduleSourcesDoImportView.as_view(), name='schedulesource-import'), path('schedule/source/<uuid:pk>/update', schedules.ScheduleSourcesUpdateView.as_view(), name='schedulesource-edit'), path('schedule/import/<int:pk>', schedules.ScheduleSourceImportDetailView.as_view(), name='schedulesourceimport-detail'), + path('schedule/import/<int:pk>.json', schedules.ScheduleSourceImportDataView.as_view(), name='schedulesourceimport-detail-data'), path('sos/', RedirectView.as_view(pattern_name='backoffice:sos', permanent=True)), path('self-organized/', events.SoSIndexView.as_view(), name='sos'), path('self-organized/projects/create', projects.CreateProjectView.as_view(), name='so-create-project'), diff --git a/src/backoffice/views/schedules.py b/src/backoffice/views/schedules.py index 34d7c5755ac60f95d6d267cdec7173b5fdaa375c..6cf5223869beb9fbe69929917b8616c8eb996085 100644 --- a/src/backoffice/views/schedules.py +++ b/src/backoffice/views/schedules.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.http import JsonResponse from django.shortcuts import redirect from django.urls import reverse from django.views import View @@ -26,7 +27,7 @@ class SchedulesIndexView(ScheduleAdminMixin, TemplateView): def get_context_data(self, *args, **kwargs): ctx = super().get_context_data(*args, **kwargs) - ctx['sources'] = self.conference.schedule_sources.select_related('assembly').all() + ctx['sources'] = self.conference.schedule_sources.select_related('assembly').all().order_by('assembly__name') return ctx @@ -119,3 +120,23 @@ class ScheduleSourcesDeleteView(ScheduleAdminMixin, DeleteView): class ScheduleSourceImportDetailView(ScheduleAdminMixin, DetailView): template_name = 'backoffice/schedule_source_import-detail.html' model = ScheduleSourceImport + + def get_context_data(self, *args, **kwargs): + action2sort = {'error': '01', 'removed': '02', 'added': '03', 'seen': '08'} + type2sort = {'room': '10', 'event': '20', 'speaker': '50'} + ctx = super().get_context_data(*args, **kwargs) + if data := ctx['object'].data: + ctx['activity'] = sorted( + data.get('_activity', []), + key=lambda item: f'{action2sort.get(item["action"], "99")}__{type2sort.get(item["type"], "99")}__{item["source_id"]:>10}', + ) + else: + ctx['activity'] = None + return ctx + + +class ScheduleSourceImportDataView(ScheduleAdminMixin, DetailView): + model = ScheduleSourceImport + + def get(self, *args, **kwargs): + return JsonResponse(self.get_object().data, json_dumps_params={'indent': 2}) diff --git a/src/core/models/schedules.py b/src/core/models/schedules.py index b6e321540fdc925887983ce2631eff731e2af01d..271700e8d1771b604c348414e1fc0e2a2c17169d 100644 --- a/src/core/models/schedules.py +++ b/src/core/models/schedules.py @@ -162,7 +162,7 @@ class ScheduleSource(models.Model): # the very bad case: we found too many if len(candidates) > 1: - raise ValueError('Multiple candidate speakers found: ' + '; '.join(x.pk for x in candidates)) + raise ValueError('Multiple candidate speakers found: ' + '; '.join(str(x.pk) for x in candidates)) # hail mary attempt: see if we have an imported speaker with the same name candidates = self.conference.users.select_related('user').filter(user__user_type=PlatformUser.Type.SPEAKER, user__display_name=name).all() @@ -770,6 +770,8 @@ class ScheduleSourceImport(models.Model): @property def errors(self): + if not self.data: + return None errors = [x for x in self.data.get('_activity', []) if x['action'] == 'error'] return errors @@ -787,6 +789,7 @@ class ScheduleSourceImport(models.Model): return False self.start = timezone.now() + self.end = None self.state = self.State.STARTED self.save(update_fields=['start', 'state']) logger.info('[job %s] starting import', self.pk)