From e1e47ea823914eb12ee0e6181ad0c8b0f76fa1d2 Mon Sep 17 00:00:00 2001 From: George Katsikas <giorgakis.katsikas@gmail.com> Date: Mon, 7 Oct 2024 12:15:28 +0200 Subject: [PATCH] add organization-affiliated profile email export add view permission checks for data export --- .../organizations/_organization_card.html | 7 +- scipost_django/organizations/urls.py | 5 + scipost_django/organizations/views.py | 93 ++++++++++++++++++- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/scipost_django/organizations/templates/organizations/_organization_card.html b/scipost_django/organizations/templates/organizations/_organization_card.html index 8d3988944..e1715d7c7 100644 --- a/scipost_django/organizations/templates/organizations/_organization_card.html +++ b/scipost_django/organizations/templates/organizations/_organization_card.html @@ -135,9 +135,10 @@ aria-labelledby="publications-{{ org.id }}-tab"> {% if perms.can_manage_organizations %} - <div class="row float-end"> - <a href="{% url "organizations:download_associated_publications_tex" pk=org.id %}">Download associated publications as LaTeX</a> - </div> + <ul class="p-0 list-unstyled float-end"> + <li><a href="{% url "organizations:download_associated_publications_tex" pk=org.id %}">Download associated publications as LaTeX</a></li> + <li><a href="#" hx-swap="outerHTML" hx-get="{% url "organizations:_hx_export_associated_profile_emails" pk=org.id %}">Export associated profile emails</a></li> + </ul> {% endif %} <h3> {% if org.cf_nr_associated_publications %}{{ org.cf_nr_associated_publications }}{% else %}No{% endif %} diff --git a/scipost_django/organizations/urls.py b/scipost_django/organizations/urls.py index ba2c9d21b..fe9373766 100644 --- a/scipost_django/organizations/urls.py +++ b/scipost_django/organizations/urls.py @@ -39,6 +39,11 @@ urlpatterns = [ views.download_associated_publications_tex, name="download_associated_publications_tex", ), + path( + "<int:pk>/_hx_associated_profile_emails", + views._hx_export_associated_profile_emails, + name="_hx_export_associated_profile_emails", + ), path( "get_organization_detail", views.get_organization_detail, diff --git a/scipost_django/organizations/views.py b/scipost_django/organizations/views.py index 2838fd09a..f023f0203 100644 --- a/scipost_django/organizations/views.py +++ b/scipost_django/organizations/views.py @@ -7,15 +7,17 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import UserPassesTestMixin from django.core.exceptions import PermissionDenied -from django.http import QueryDict +from django.http import HttpResponse +from django.db.models.functions import Lower from django.template.response import TemplateResponse from django.urls import reverse_lazy from django.db import transaction -from django.db.models import Q, Exists, OuterRef, Prefetch +from django.db.models import Q, Count, Exists, OuterRef from django.shortcuts import get_object_or_404, render, redirect from django.urls import reverse from django.utils import timezone from django.utils.html import format_html +from django.utils.timezone import timedelta from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView @@ -23,8 +25,9 @@ from django.views.generic.list import ListView from dal import autocomplete from guardian.decorators import permission_required -from finances.constants import SUBSIDY_WITHDRAWN -from finances.models.subsidy import Subsidy +from journals.models.publication import PublicationAuthorsTable +from profiles.models import Profile, ProfileEmail +from submissions.models.submission import SubmissionAuthorProfile from .constants import ( ORGTYPE_PRIVATE_BENEFACTOR, @@ -301,6 +304,87 @@ def get_organization_detail(request): return redirect(reverse("organizations:organizations")) +@permission_required_htmx("scipost.can_manage_organizations") +def _hx_export_associated_profile_emails(request, pk): + """ + Export a list of associated profile emails. + The list includes all non-deprecated emails of profiles that have: + - accepted (unsolicited) SciPost emails + - been active in the last year (logged in, published, submitted, or reported) + - are affiliated with the organization (via direct declaration or publication/submission metadata) + """ + organization = get_object_or_404(Organization, pk=pk) + + year_ago = timezone.now() - timedelta(days=365) + recently_active_profiles = ( + Profile.objects.all() + .annotate( + nr_publications=Count( + "publicationauthorstable", + distinct=True, + filter=Q( + publicationauthorstable__publication__publication_date__gte=year_ago + ), + ), + nr_submissions=Count( + "contributor__submissions", + distinct=True, + filter=Q(contributor__submissions__submission_date__gte=year_ago), + ), + nr_reports=Count( + "contributor__reports", + distinct=True, + filter=Q(contributor__reports__created__gte=year_ago), + ), + has_published_with_organization=Exists( + PublicationAuthorsTable.objects.filter( + profile=OuterRef("pk"), + affiliations__organization=organization, + ) + ), + has_submitted_with_organization=Exists( + SubmissionAuthorProfile.objects.filter( + profile=OuterRef("pk"), + affiliations__organization=organization, + ) + ), + logged_in=Q(contributor__user__last_login__gte=year_ago), + ) + .filter( + Q(accepts_SciPost_emails=True) + & ( + Q(nr_publications__gt=0) + | Q(nr_submissions__gt=0) + | Q(nr_reports__gt=0) + | Q(logged_in=True) + ) + & ( + Q(affiliations__organization=organization) + | Q(has_published_with_organization=True) + | Q(has_submitted_with_organization=True) + ), + ) + .order_by() + .distinct() + ) + + # Remove duplicate emails before exporting + profile_emails = ( + ProfileEmail.objects.filter( + profile__in=recently_active_profiles, still_valid=True + ) + .annotate(lower_email=Lower("email")) + .values_list("lower_email", flat=True) + .order_by("lower_email") + .distinct() + ) + + return HttpResponse( + f'<textarea rows="5" style="width: 300px;">{", ".join(profile_emails)}</textarea>' + ) + + +@permission_required_htmx("scipost.can_manage_organizations") def download_associated_publications_tex(request, pk): """ Download a .tex file with the associated publications of an organization. @@ -320,6 +404,7 @@ def download_associated_publications_tex(request, pk): return response +@permission_required_htmx("scipost.can_manage_organizations") def download_associated_authors_tex(request, pk): """ Download a .tex file with the associated authors of an organization. -- GitLab