From b812fd3c6566423b4cf69f88c6f714ec435f00c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89der=20Porto?= Date: Fri, 10 Nov 2023 15:46:47 -0300 Subject: [PATCH] Adding new functionality of visualizing which reports are related to a specific metric --- .../metrics/list_metrics_per_project.html | 4 +- .../metrics/list_metrics_reports.html | 69 ++++++++ metrics/tests.py | 68 ++++++++ metrics/urls.py | 3 +- metrics/views.py | 162 ++++++++++++------ 5 files changed, 249 insertions(+), 57 deletions(-) create mode 100644 metrics/templates/metrics/list_metrics_reports.html diff --git a/metrics/templates/metrics/list_metrics_per_project.html b/metrics/templates/metrics/list_metrics_per_project.html index fb5d489..9a17e62 100644 --- a/metrics/templates/metrics/list_metrics_per_project.html +++ b/metrics/templates/metrics/list_metrics_per_project.html @@ -33,6 +33,7 @@

{{ project_value.project }}

data-filter-control="true"> + {% trans "Actions" %} {% trans "ID" %} {% trans "Metric" %} {% trans "Done" %} @@ -42,10 +43,11 @@

{{ project_value.project }}

{% for activity in project_value.project_metrics|dictsort:"activity_id" %} - {{ activity.activity }} + {{ activity.activity }} {% for metric_key, metric_value in activity.activity_metrics.items %} {% for numeric_key, numeric_value in metric_value.metrics.items %} + {{ metric_key }} {{ metric_value.title }}
({% translate numeric_key %}) {{ numeric_value.done|bool_yesno }} diff --git a/metrics/templates/metrics/list_metrics_reports.html b/metrics/templates/metrics/list_metrics_reports.html new file mode 100644 index 0000000..4e759bc --- /dev/null +++ b/metrics/templates/metrics/list_metrics_reports.html @@ -0,0 +1,69 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} +{% block title %}{{ title }}{% endblock %} +{% load metricstags %} + +{% block scripts %} + + + +{% endblock %} +{% block styles %} + + + +{% endblock %} +{% block banner %}{% endblock %} +{% block content %} +
+
+

{{ metric.text }}

+ {% for metric_goal in values %} +

{{ metric_goal.text }}

+
+

Goal: {{ metric_goal.goal }}

+

Done: {{ metric_goal.done }}

+
+ + + + + + + + + + + {% for report in metric_goal.reports %} + + + + + + + {% endfor %} + +
{% trans "ID" %}{% trans "Report" %}{% trans "Done" %}{% trans "Goal" %}
{{ report.id }}{{ report.description }}
({{ report.initial_date }} - {{ report.end_date }})
{{ report.done }}{{ report.done|perc:metric_goal.goal }}
+ {% endfor %} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/metrics/tests.py b/metrics/tests.py index 4c13b80..6fba4dc 100644 --- a/metrics/tests.py +++ b/metrics/tests.py @@ -253,6 +253,74 @@ def test_update_metrics(self): self.assertRedirects(response, reverse('metrics:per_project')) + def test_do_not_show_reports_associated_to_a_metric_to_users_without_permission(self): + self.user.user_permissions.remove(self.view_metrics_permission) + self.client.login(username=self.username, password=self.password) + + area = Area.objects.create(text="Area") + activity = Activity.objects.create(text="Activity", area=area) + metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10) + + url = reverse("metrics:metrics_reports", args=[metric.id]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, f"{reverse('login')}?next={url}") + + def test_show_reports_associated_to_a_metric_only_to_users_with_permission(self): + self.client.login(username=self.username, password=self.password) + + area = Area.objects.create(text="Area") + activity = Activity.objects.create(text="Activity", area=area) + metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10) + team_area = TeamArea.objects.create(text="Area") + report_1 = Report.objects.create( + created_by=self.user_profile, + modified_by=self.user_profile, + activity_associated=activity, + area_responsible=team_area, + initial_date=datetime.now().date(), + end_date=datetime.now().date() + timedelta(days=1), + description="Report 1", + links="https://testlink.com", + learning="Learning" * 60, + ) + report_1.metrics_related.add(metric) + report_1.save() + + url = reverse("metrics:metrics_reports", args=[metric.id]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "metrics/list_metrics_reports.html") + + def test_redirects_to_list_of_metrics_per_project_if_metric_id_does_not_exist(self): + self.client.login(username=self.username, password=self.password) + + area = Area.objects.create(text="Area") + activity = Activity.objects.create(text="Activity", area=area) + metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10) + team_area = TeamArea.objects.create(text="Area") + report_1 = Report.objects.create( + created_by=self.user_profile, + modified_by=self.user_profile, + activity_associated=activity, + area_responsible=team_area, + initial_date=datetime.now().date(), + end_date=datetime.now().date() + timedelta(days=1), + description="Report 1", + links="https://testlink.com", + learning="Learning" * 60, + ) + report_1.metrics_related.add(metric) + report_1.save() + + url = reverse("metrics:metrics_reports", args=[123456789]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, f"{reverse('metrics:per_project')}") + class MetricFunctionsTests(TestCase): def setUp(self): diff --git a/metrics/urls.py b/metrics/urls.py index cf23312..fc475de 100644 --- a/metrics/urls.py +++ b/metrics/urls.py @@ -10,5 +10,6 @@ path('activities_plan', views.show_activities_plan, name='show_activities'), path('metrics', views.show_metrics, name='metrics'), path('metrics_per_project', views.show_metrics_per_project, name='per_project'), - path('update_metrics', views.update_metrics_relations, name='update_metrics') + path('update_metrics', views.update_metrics_relations, name='update_metrics'), + path('metrics_reports/', views.metrics_reports, name='metrics_reports'), ] diff --git a/metrics/views.py b/metrics/views.py index 1dbde94..f9e9be6 100644 --- a/metrics/views.py +++ b/metrics/views.py @@ -1,6 +1,7 @@ import calendar from django.shortcuts import render, redirect, reverse, HttpResponse from django.utils.translation import gettext as _ +from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q, Count, Sum, F from .models import Activity, Area, Metric from report.models import Report, Editor, Organizer, Partner, Project, Funding @@ -70,61 +71,7 @@ def get_metrics_and_aggregate_per_project(): else: q_filter = Q(project=project) for metric in Metric.objects.filter(q_filter): - reports = Report.objects.filter(metrics_related__in=[metric]) - goal = { - "Wikipedia": metric.wikipedia_created + metric.wikipedia_edited, - "Wikimedia Commons": metric.commons_created + metric.commons_edited, - "Wikidata": metric.wikidata_created + metric.wikidata_edited, - "Wikiversity": metric.wikiversity_created + metric.wikiversity_edited, - "Wikibooks": metric.wikibooks_created + metric.wikibooks_edited, - "Wikisource": metric.wikisource_created + metric.wikisource_edited, - "Wikinews": metric.wikinews_created + metric.wikinews_edited, - "Wikiquote": metric.wikiquote_created + metric.wikiquote_edited, - "Wiktionary": metric.wiktionary_created + metric.wiktionary_edited, - "Wikivoyage": metric.wikivoyage_created + metric.wikivoyage_edited, - "Wikispecies": metric.wikispecies_created + metric.wikispecies_edited, - "MetaWiki": metric.metawiki_created + metric.metawiki_edited, - "MediaWiki": metric.mediawiki_created + metric.mediawiki_edited, - "Number of participants": metric.number_of_participants, - "Number of resources": metric.number_of_resources, - "Number of feedbacks": metric.number_of_feedbacks, - "Number of events": metric.number_of_events, - "Number of editors": metric.number_of_editors, - "Number of editors retained": metric.number_of_editors_retained, - "Number of new editors": metric.number_of_new_editors, - "Number of partnerships": metric.number_of_partnerships, - "Number of organizers": metric.number_of_organizers, - "Number of organizers retained": metric.number_of_organizers_retained, - "Number of people reached through social media": metric.number_of_people_reached_through_social_media, - "Occurence": metric.boolean_type, - } - done = { - "Wikipedia": reports.aggregate(total=Sum(F("wikipedia_created") + F("wikipedia_edited")))["total"] or 0, - "Wikimedia Commons": reports.aggregate(total=Sum(F("commons_created") + F("commons_edited")))["total"] or 0, - "Wikidata": reports.aggregate(total=Sum(F("wikidata_created") + F("wikidata_edited")))["total"] or 0, - "Wikiversity": reports.aggregate(total=Sum(F("wikiversity_created") + F("wikiversity_edited")))["total"] or 0, - "Wikibooks": reports.aggregate(total=Sum(F("wikibooks_created") + F("wikibooks_edited")))["total"] or 0, - "Wikisource": reports.aggregate(total=Sum(F("wikisource_created") + F("wikisource_edited")))["total"] or 0, - "Wikinews": reports.aggregate(total=Sum(F("wikinews_created") + F("wikinews_edited")))["total"] or 0, - "Wikiquote": reports.aggregate(total=Sum(F("wikiquote_created") + F("wikiquote_edited")))["total"] or 0, - "Wiktionary": reports.aggregate(total=Sum(F("wiktionary_created") + F("wiktionary_edited")))["total"] or 0, - "Wikivoyage": reports.aggregate(total=Sum(F("wikivoyage_created") + F("wikivoyage_edited")))["total"] or 0, - "Wikispecies": reports.aggregate(total=Sum(F("wikispecies_created") + F("wikispecies_edited")))["total"] or 0, - "MetaWiki": reports.aggregate(total=Sum(F("metawiki_created") + F("metawiki_edited")))["total"] or 0, - "MediaWiki": reports.aggregate(total=Sum(F("mediawiki_created") + F("mediawiki_edited")))["total"] or 0, - "Number of participants": reports.aggregate(total=Sum("participants"))["total"] or 0, - "Number of resources": reports.aggregate(total=Sum("resources"))["total"] or 0, - "Number of feedbacks": reports.aggregate(total=Sum("feedbacks"))["total"] or 0, - "Number of events": reports.count() or 0, - "Number of editors": Editor.objects.filter(editors__in=reports).distinct().count() or 0, - "Number of editors retained": Editor.objects.filter(retained=True, editors__in=reports).distinct().count() or 0, - "Number of new editors": Editor.objects.filter(editors__in=reports, account_creation_date__gte=F('editors__initial_date')).count() or 0, - "Number of partnerships": Partner.objects.filter(partners__in=reports).distinct().count() or 0, - "Number of organizers": Organizer.objects.filter(organizers__in=reports).distinct().count() or 0, - "Number of organizers retained": Organizer.objects.filter(retained=True, organizers__in=reports).distinct().count() or 0, - "Number of people reached through social media": reports.aggregate(total=Sum(F("number_of_people_reached_through_social_media")))["total"] or 0, - "Occurence": reports.filter(metrics_related__boolean_type=True).exists() or False, - } + goal, done = get_goal_and_done_for_metric(metric) result_metrics = {key: {"goal": value, "done": done[key]} for key, value in goal.items() if value != 0} activity_metrics[metric.id] = { @@ -147,6 +94,74 @@ def get_metrics_and_aggregate_per_project(): return aggregated_metrics_and_results +def get_goal_and_done_for_metric(metric): + reports = Report.objects.filter(metrics_related__in=[metric]) + goal = get_goal_for_metric(metric) + done = get_done_for_report(reports) + + return goal, done + + +def get_goal_for_metric(metric): + return { + "Wikipedia": metric.wikipedia_created + metric.wikipedia_edited, + "Wikimedia Commons": metric.commons_created + metric.commons_edited, + "Wikidata": metric.wikidata_created + metric.wikidata_edited, + "Wikiversity": metric.wikiversity_created + metric.wikiversity_edited, + "Wikibooks": metric.wikibooks_created + metric.wikibooks_edited, + "Wikisource": metric.wikisource_created + metric.wikisource_edited, + "Wikinews": metric.wikinews_created + metric.wikinews_edited, + "Wikiquote": metric.wikiquote_created + metric.wikiquote_edited, + "Wiktionary": metric.wiktionary_created + metric.wiktionary_edited, + "Wikivoyage": metric.wikivoyage_created + metric.wikivoyage_edited, + "Wikispecies": metric.wikispecies_created + metric.wikispecies_edited, + "MetaWiki": metric.metawiki_created + metric.metawiki_edited, + "MediaWiki": metric.mediawiki_created + metric.mediawiki_edited, + "Number of participants": metric.number_of_participants, + "Number of resources": metric.number_of_resources, + "Number of feedbacks": metric.number_of_feedbacks, + "Number of events": metric.number_of_events, + "Number of editors": metric.number_of_editors, + "Number of editors retained": metric.number_of_editors_retained, + "Number of new editors": metric.number_of_new_editors, + "Number of partnerships": metric.number_of_partnerships, + "Number of organizers": metric.number_of_organizers, + "Number of organizers retained": metric.number_of_organizers_retained, + "Number of people reached through social media": metric.number_of_people_reached_through_social_media, + "Occurence": metric.boolean_type, + } + + +def get_done_for_report(reports): + return { + "Wikipedia": reports.aggregate(total=Sum(F("wikipedia_created") + F("wikipedia_edited")))["total"] or 0, + "Wikimedia Commons": reports.aggregate(total=Sum(F("commons_created") + F("commons_edited")))["total"] or 0, + "Wikidata": reports.aggregate(total=Sum(F("wikidata_created") + F("wikidata_edited")))["total"] or 0, + "Wikiversity": reports.aggregate(total=Sum(F("wikiversity_created") + F("wikiversity_edited")))["total"] or 0, + "Wikibooks": reports.aggregate(total=Sum(F("wikibooks_created") + F("wikibooks_edited")))["total"] or 0, + "Wikisource": reports.aggregate(total=Sum(F("wikisource_created") + F("wikisource_edited")))["total"] or 0, + "Wikinews": reports.aggregate(total=Sum(F("wikinews_created") + F("wikinews_edited")))["total"] or 0, + "Wikiquote": reports.aggregate(total=Sum(F("wikiquote_created") + F("wikiquote_edited")))["total"] or 0, + "Wiktionary": reports.aggregate(total=Sum(F("wiktionary_created") + F("wiktionary_edited")))["total"] or 0, + "Wikivoyage": reports.aggregate(total=Sum(F("wikivoyage_created") + F("wikivoyage_edited")))["total"] or 0, + "Wikispecies": reports.aggregate(total=Sum(F("wikispecies_created") + F("wikispecies_edited")))["total"] or 0, + "MetaWiki": reports.aggregate(total=Sum(F("metawiki_created") + F("metawiki_edited")))["total"] or 0, + "MediaWiki": reports.aggregate(total=Sum(F("mediawiki_created") + F("mediawiki_edited")))["total"] or 0, + "Number of participants": reports.aggregate(total=Sum("participants"))["total"] or 0, + "Number of resources": reports.aggregate(total=Sum("resources"))["total"] or 0, + "Number of feedbacks": reports.aggregate(total=Sum("feedbacks"))["total"] or 0, + "Number of events": reports.count() or 0, + "Number of editors": Editor.objects.filter(editors__in=reports).distinct().count() or 0, + "Number of editors retained": Editor.objects.filter(retained=True, editors__in=reports).distinct().count() or 0, + "Number of new editors": Editor.objects.filter(editors__in=reports, account_creation_date__gte=F('editors__initial_date')).count() or 0, + "Number of partnerships": Partner.objects.filter(partners__in=reports).distinct().count() or 0, + "Number of organizers": Organizer.objects.filter(organizers__in=reports).distinct().count() or 0, + "Number of organizers retained": Organizer.objects.filter(retained=True, organizers__in=reports).distinct().count() or 0, + "Number of people reached through social media": reports.aggregate(total=Sum(F("number_of_people_reached_through_social_media")))["total"] or 0, + "Occurence": reports.filter(metrics_related__boolean_type=True).exists() or False, + } + + def get_aggregated_metrics_data(project=None): total_sum = {} @@ -377,3 +392,40 @@ def update_metrics_relations(request): report.save() return redirect(reverse("metrics:per_project")) + + +@login_required +@permission_required("metrics.view_metric") +def metrics_reports(request, metric_id): + try: + metric = Metric.objects.get(pk=metric_id) + reports = Report.objects.filter(metrics_related=metric_id).order_by("pk") + + goals = get_goal_for_metric(metric) + filtered_goals = {key: value for key, value in goals.items() if goals[key] > 0} + + values = [] + for goal_key, goal_value in filtered_goals.items(): + report_values = [] + for report in reports: + done = get_done_for_report(Report.objects.filter(pk=report.id)) + report_values.append({ + "id": report.id, + "description": report.description, + "initial_date": report.initial_date, + "end_date": report.end_date, + "done": done[goal_key], + }) + values.append({ + "text": goal_key, + "goal": goal_value, + "done": sum([report_aux["done"] for report_aux in report_values]), + "reports": report_values + }) + + context = {"metric": metric, "values": values} + + return render(request, "metrics/list_metrics_reports.html", context) + except ObjectDoesNotExist: + return redirect(reverse('metrics:per_project')) +