Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge suggestions #1968

Merged
merged 14 commits into from
Jan 22, 2024
72 changes: 72 additions & 0 deletions evap/development/fixtures/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -133085,6 +133085,78 @@
"cc_users": []
}
},
{
"model": "evaluation.userprofile",
"fields": {
"password": "eZAyFmtqHydCIFtGdbevAxiVjiRpqMtmaVUCrmkcfXdoJDigmGWPVNHeoYYyRojokKUJjsgPSPvZkjiiIHSIQlBfOKtQFDbZlPEyKnrQRrHdPtEhUYHqJauIlyIkYpBM",
"last_login": null,
"is_superuser": false,
"email": "vincenzo.boston@student.institution.example.com",
"title": "",
"first_name_given": "",
richardebeling marked this conversation as resolved.
Show resolved Hide resolved
"first_name_chosen": "",
"last_name": "",
"language": "",
"is_proxy_user": false,
"login_key": null,
"login_key_valid_until": null,
"is_active": true,
"notes": "",
"startpage": "DE",
"groups": [],
"user_permissions": [],
"delegates": [],
"cc_users": []
}
},
{
"model": "evaluation.userprofile",
"fields": {
"password": "utAhMBbTpirVqtaoPpadEHdamaehnXWbEsliMMSnwDBYJcTnHluinAxkTeEupPoBzpuDBMYeXbpwmockMtQNYegbMuxkUBEBKqWGkOEFAWxzUFjdxevtIwYzvAgHCAwD",
"last_login": null,
"is_superuser": false,
"email": "bud.ledbetter@student.institution.example.com",
"title": "",
"first_name_given": "",
"first_name_chosen": "",
"last_name": "",
"language": "",
"is_proxy_user": false,
"login_key": null,
"login_key_valid_until": null,
"is_active": true,
"notes": "",
"startpage": "DE",
"groups": [],
"user_permissions": [],
"delegates": [],
"cc_users": []
}
},
{
"model": "evaluation.userprofile",
"fields": {
"password": "naFmzOVrFhXrVVLsIGFYceDAarTGwDRFZKGJwBvKhNFCpupezBrwhorUHsyQSpUxLFKSQuOurcIyoBBYRjARXjzcJCbqYRiKRMOwvdTqwNjAbYDhUKbopBPDYhANXUkI",
"last_login": null,
"is_superuser": false,
"email": "melody.large@student.institution.example.com",
"title": "",
"first_name_given": "",
"first_name_chosen": "",
"last_name": "",
"language": "",
"is_proxy_user": false,
"login_key": null,
"login_key_valid_until": null,
"is_active": true,
"notes": "",
"startpage": "DE",
"groups": [],
"user_permissions": [],
"delegates": [],
"cc_users": []
}
},
{
"model": "evaluation.emailtemplate",
"pk": 1,
Expand Down
2 changes: 1 addition & 1 deletion evap/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@

# email domains for the internal users of the hosting institution used to
# figure out who is an internal user
INSTITUTION_EMAIL_DOMAINS = ["institution.example.com"]
INSTITUTION_EMAIL_DOMAINS = ["institution.example.com", "student.institution.example.com"]

# List of tuples defining email domains that should be replaced on saving UserProfiles.
# Emails ending on the first value will have this part replaced by the second value.
Expand Down
54 changes: 42 additions & 12 deletions evap/staff/templates/staff_user_merge_selection.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,50 @@

{% block content %}
{{ block.super }}
<h3>{% trans 'Merge users' %}</h3>

<form id="user-selection-form" method="POST" class="form-horizontal">
{% csrf_token %}
<div class="card mb-3">
<div class="card-body">
<p>{% trans 'Select the users you want to merge.' %}</p>
{% include 'bootstrap_form.html' with form=form wide=True %}
<div class="row row-cols-1 row-cols-md-2">
janno42 marked this conversation as resolved.
Show resolved Hide resolved
<div class="col">
<div class="card">
<div class="card-body">
<h4 class="card-title">{% trans 'Merge users' %}</h4>
<form id="user-selection-form" method="POST" class="form-horizontal">
{% csrf_token %}
<p>{% trans 'Select the users you want to merge.' %}</p>
{% include 'bootstrap_form.html' with form=form wide=True %}
</form>
</div>
<div class="card-footer text-center card-submit-area d-flex">
<button type="submit" form="user-selection-form" class="btn btn-light mx-auto my-auto">{% trans 'Show merge info' %}</button>
</div>
</div>
</div>
<div class="card card-submit-area text-center mb-3">
<div class="card-body">
<button type="submit" class="btn btn-primary">{% trans 'Show merge info' %}</button>
<div class="col">
<div class="card">
<div class="card-body">
<h4 class="card-title">{% trans 'Merge suggestions' %}</h4>
<table class="table table-striped table-narrow table-vertically-aligned">
<tbody>
{% for main_user, merge_candidate in suggested_merges %}
<tr>
<td>
<b>{{ main_user.full_name }}</b> <br />
<b>{{ merge_candidate.full_name }}</b>
</td>
<td>
{{ main_user.email }} <br />
{{ merge_candidate.email }}
</td>
<td class="text-end">
<a href="{% url 'staff:user_merge' main_user.id merge_candidate.id %}" class="btn btn-sm btn-light" data-bs-toggle="tooltip" data-bs-placement="top" title="{% trans 'Show merge info' %}">
<span class="fas fa-object-group"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</form>
</div>

{% endblock %}
43 changes: 42 additions & 1 deletion evap/staff/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.db import IntegrityError, transaction
from django.db.models import BooleanField, Case, Count, ExpressionWrapper, IntegerField, Prefetch, Q, Sum, When
from django.db.models import (
BooleanField,
Case,
Count,
ExpressionWrapper,
Func,
IntegerField,
OuterRef,
Prefetch,
Q,
Sum,
When,
)
from django.dispatch import receiver
from django.forms import BaseForm, formset_factory
from django.forms.models import inlineformset_factory, modelformset_factory
Expand Down Expand Up @@ -2294,6 +2306,35 @@ class UserMergeSelectionView(FormView):
form_class = UserMergeSelectionForm
template_name = "staff_user_merge_selection.html"

def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)

class UserNameFromEmail(Func):
# django docs support our usage here:
# https://docs.djangoproject.com/en/5.0/ref/models/expressions/#func-expressions
# pylint: disable=abstract-method
template = "split_part(%(expressions)s, '@', 1)"

query = UserProfile.objects.annotate(username_part_of_email=UserNameFromEmail("email"))

users_with_merge_candidates = query.annotate(
merge_candidate_pk=query.filter(username_part_of_email=UserNameFromEmail(OuterRef("email")))
.filter(pk__lt=OuterRef("pk"))
.values("pk")[:1]
).exclude(merge_candidate_pk=None)

merge_candidate_ids = [user.merge_candidate_pk for user in users_with_merge_candidates]
merge_candidates_by_id = {user.pk: user for user in UserProfile.objects.filter(pk__in=merge_candidate_ids)}

suggested_merges = [
(user, merge_candidates_by_id[user.merge_candidate_pk])
for user in users_with_merge_candidates
if not user.is_external and not merge_candidates_by_id[user.merge_candidate_pk].is_external
]

context["suggested_merges"] = suggested_merges
return context

def form_valid(self, form: UserMergeSelectionForm) -> HttpResponse:
return redirect(
"staff:user_merge",
Expand Down