Skip to content

Commit

Permalink
Merge pull request #171 from Teknologforeningen/feature/list-groupmem…
Browse files Browse the repository at this point in the history
…berships-per-person

Feature: List group memberships per person
  • Loading branch information
filiptypjeu authored Aug 18, 2023
2 parents 388aae5 + 77e089e commit 427386f
Show file tree
Hide file tree
Showing 22 changed files with 348 additions and 178 deletions.
1 change: 1 addition & 0 deletions teknologr/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def filter_name(self, queryset, name, value):
# Split the filter value and compare all individual values against all name columns
queries = []
for v in value.split():
# XXX: The preferred name does not have to be set in the database, so hidden members with no preferred name set will not show up for normal users even if the derived preferred name would match
q = Q(preferred_name__icontains=v) | Q(surname__icontains=v)
# Non-staff users only get to filter on 'given_names' if the Member has allowed publishing of info
if is_staff:
Expand Down
15 changes: 3 additions & 12 deletions teknologr/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,10 @@
from datetime import datetime
from rest_framework.response import Response


def findMembers(query, count=50):

args = []

for word in query.split():
args.append(Q(given_names__icontains=word) | Q(surname__icontains=word))

if not args:
return [] # No words in query (only spaces?)

return Member.objects.filter(*args).order_by('surname', 'given_names')[:count]

members = Member.objects.search_by_name(query.split(), True)[:count]
Member.order_by(members, 'name')
return members

def findApplicants(query, count=50):
args = []
Expand Down
2 changes: 1 addition & 1 deletion teknologr/katalogen/static/js/katalogen.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const sortTable = ({ tbody, column, reverse, from_attribute }) => {
if (from_attribute) {
getData = a => $(selector, a).attr("order-data") || "";
} else {
getData = a => $(selector, a).text();
getData = a => $(selector, a).text();
}

// localeCompare() can compare numbers as strings when the 'numeric' option is set.
Expand Down
2 changes: 1 addition & 1 deletion teknologr/katalogen/templates/functionaries.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h3 class="page-header">
</tr>
</thead>
<tbody>
{% for member, duration in functionary_duration_strings %}
{% for member, duration in functionary_durations %}
<tr>
<td order-data="{{ member.public_full_name_for_sorting }}"><a href="{% url 'katalogen:profile' member.id %}">{{ member.public_full_name }}</a></td>
<td order-data="{{ duration.to_sort_string }}">{{ duration }}</td>
Expand Down
36 changes: 36 additions & 0 deletions teknologr/katalogen/templates/group_memberships.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends "home.html" %}

{% block content %}
<div class="row">
<div class="col-12 col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h3 class="page-header">
Medlemmar av
{% if is_staff %}
<a class="admin-link" href="{% url 'admin:group_type' group_type.id %}">{{ group_type.name }}</a>
{% else %}
{{ group_type.name }}
{% endif %}
</h3>
{{ group_type.comment|linebreaks }}
<table class="table table-sm table-striped">
<thead>
<tr>
<th>
<span class="order-by attribute default-order">Namn</span>
<a class="fas fa-expand-arrows-alt" href="{% url 'katalogen:groups' group_type.id %}" title="Visa gruppmandat per undergrupp"></a>
</th>
<th><span class="order-by attribute">Datum</span></th>
</tr>
</thead>
<tbody>
{% for member, duration in group_membership_durations %}
<tr>
<td order-data="{{ member.public_full_name_for_sorting }}"><a href="{% url 'katalogen:profile' member.id %}">{{ member.public_full_name }}</a></td>
<td order-data="{{ duration.to_sort_string }}">{{ duration }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
2 changes: 1 addition & 1 deletion teknologr/katalogen/templates/group_types.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<tbody>
{% for gt in group_types %}
<tr>
<td class="text-left"><a href="{% url 'katalogen:group_type' gt.id %}">{{ gt.name }}</a></td>
<td class="text-left"><a href="{% url 'katalogen:groups' gt.id %}">{{ gt.name }}</a></td>
<td>{{ gt.count_non_empty }}</td>
<td>{{ gt.count_members_total }}</td>
<td>{{ gt.count_members_unique }}</td>
Expand Down
7 changes: 5 additions & 2 deletions teknologr/katalogen/templates/groups.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="row">
<div class="col-12 col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h3 class="page-header">
Medlemmar av
Undergrupperingar av
{% if is_staff %}
<a class="admin-link" href="{% url 'admin:group_type' group_type.id %}">{{ group_type.name }}</a>
{% else %}
Expand All @@ -16,7 +16,10 @@ <h3 class="page-header">
<thead>
<tr>
<th>Datum</th>
<th>Medlemmar</th>
<th>
Medlemmar
<a class="fas fa-compress-arrows-alt" href="{% url 'katalogen:group_memberships' group_type.id %}" title="Visa gruppmandat per person"></a>
</th>
</tr>
</thead>
<tbody>
Expand Down
6 changes: 3 additions & 3 deletions teknologr/katalogen/templates/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ <h3 class="list-inline-item">Poster</h3>
<a class="list-inline-item fa fa-compress-arrows-alt" href="?combine=1" title="Kombinera poster och grupper, samt sortera dem i alfabetisk ordning"></a>
{% endif %}
<table class="table table-striped table-borderless table-sm">
{% for ft, duration in functionary_duration_strings %}
{% for ft, duration in functionary_type_durations %}
<tr>
<th><a href="{% url 'katalogen:functionary_type' ft.id %}">{{ ft.name }}</a></td>
<td class="text-right">{{ duration }}</td>
Expand All @@ -118,9 +118,9 @@ <h3 class="list-inline-item">Grupper</h3>
<a class="list-inline-item fa fa-compress-arrows-alt" href="?combine=1" title="Kombinera poster och grupper, samt sortera dem i alfabetisk ordning"></a>
{% endif %}
<table class="table table-striped table-borderless table-sm">
{% for gt, duration in group_type_duration_strings %}
{% for gt, duration in group_type_durations %}
<tr>
<th><a href="{% url 'katalogen:group_type' gt.id %}">{{ gt.name }}</a></th>
<th><a href="{% url 'katalogen:groups' gt.id %}">{{ gt.name }}</a></th>
<td class="text-right">{{ duration }}</td>
</tr>
{% empty %}
Expand Down
2 changes: 1 addition & 1 deletion teknologr/katalogen/templates/year.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<tbody>
{% for g in groups %}
<tr>
<td><a href="{% url 'katalogen:group_type' g.grouptype.id %}">{{ g.grouptype.name }}{% if g.duration.to_string != year %} ({{ g.duration.to_string }}){% endif %}</a></td>
<td><a href="{% url 'katalogen:groups' g.grouptype.id %}">{{ g.grouptype.name }}{% if g.duration.to_string != year %} ({{ g.duration.to_string }}){% endif %}</a></td>
<td><i>{{ g.num_members }} {% if g.num_members == 1 %}medlem{% else %}medlemmar{% endif %}</i></td>
</tr>
{% for gm in g.memberships_by_member %}
Expand Down
51 changes: 51 additions & 0 deletions teknologr/katalogen/tests_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

from rest_framework import status
from api.tests import BaseAPITest

class GetPageTests():
def test_get_for_anonymous_users(self):
response = self.get_all()
self.check_status_code(response, status.HTTP_302_FOUND)
self.assertTrue(response.url.startswith('/login/'), response.url)

def test_get_for_user(self):
self.login_user()
response = self.get_all()
self.check_status_code(response, status.HTTP_200_OK)


class SearchTests(BaseAPITest):
path1 = '/search/?q=Svakar'
path2 = '/search/?q=Sverker'

def test_search_for_user(self):
# Test redirect if only one Member was found
self.api_path = self.path1
self.login_user()
response = self.get_all()
self.check_status_code(response, status.HTTP_302_FOUND)
self.assertEqual(f'/members/{self.m1.id}/', response.url)

def test_search_for_superuser(self):
self.api_path = self.path1
self.login_superuser()
response = self.get_all()
self.check_status_code(response, status.HTTP_302_FOUND)
self.assertEqual(f'/members/{self.m1.id}/', response.url)

def test_search_hidden_name_for_user(self):
# Test that hidden Members are not found by their non-preferred given name
self.api_path = self.path2
self.login_user()
response = self.get_all()
self.check_status_code(response, status.HTTP_200_OK)
self.assertNotContains(response, "Svakar")
self.assertNotContains(response, "Sverker")

def test_search_hidden_name_for_superuser(self):
self.api_path = self.path2
self.login_superuser()
response = self.get_all()
self.check_status_code(response, status.HTTP_200_OK)
self.assertNotContains(response, "Svakar")
self.assertNotContains(response, "Sverker")
75 changes: 38 additions & 37 deletions teknologr/katalogen/tests_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import date
from .utils import *

class CreateDurationsStringTest(TestCase):
class DurationTests(TestCase):
def setUp(self):
self.d1 = date(1998, 1, 1)
self.d2 = date(1998, 1, 30)
Expand All @@ -12,68 +12,69 @@ def setUp(self):
self.d6 = date(2002, 12, 31)

def test_same_date(self):
self.assertEqual('1 januari 1998', create_duration_string(self.d1, self.d1))
self.assertEqual('1 januari 1998', Duration(self.d1, self.d1).to_string())

def test_same_month(self):
self.assertEqual('1-30 januari 1998', create_duration_string(self.d1, self.d2))
self.assertEqual('1-30 januari 1998', Duration(self.d1, self.d2).to_string())

def test_same_year(self):
self.assertEqual('30 januari - 31 december 1998', create_duration_string(self.d2, self.d3))
self.assertEqual('30 januari - 31 december 1998', Duration(self.d2, self.d3).to_string())

def test_whole_years(self):
self.assertEqual('1998', create_duration_string(self.d1, self.d3))
self.assertEqual('1998-2000', create_duration_string(self.d1, self.d4))
self.assertEqual('1998', Duration(self.d1, self.d3).to_string())
self.assertEqual('1998-2000', Duration(self.d1, self.d4).to_string())

def test_no_simplification(self):
self.assertEqual('1 januari 1998 - 7 juli 2001', create_duration_string(self.d1, self.d5))
self.assertEqual('7 juli 2001 - 31 december 2002', create_duration_string(self.d5, self.d6))
self.assertEqual('1 januari 1998 - 7 juli 2001', Duration(self.d1, self.d5).to_string())
self.assertEqual('7 juli 2001 - 31 december 2002', Duration(self.d5, self.d6).to_string())

def test_create_duration_strings_by_key(self):
result = create_duration_strings_by_key([
(9, Duration(date(2000, 1, 1), date(2000, 12, 31))),
(1, Duration(date(2000, 1, 1), date(2000, 1, 31))),
(1, Duration(date(2000, 3, 1), date(2000, 3, 31))),
(1, Duration(date(2000, 5, 1), date(2000, 5, 31))),
(1, Duration(date(2000, 7, 1), date(2000, 7, 31))),
(1, Duration(date(2000, 9, 1), date(2000, 9, 30))),
(8, Duration(date(2000, 1, 1), date(2000, 12, 31))),

(1, Duration(date(2000, 1, 5), date(2000, 1, 6))),
(1, Duration(date(2000, 3, 7), date(2000, 7, 7))),
])
self.assertEqual([
(9, '2000'),
(1, '1-31 januari 2000, 1 mars - 31 juli 2000, 1-30 september 2000'),
(8, '2000'),
], result)

class SimplifyDurationsTest(TestCase):
class MultiDurationTests(TestCase):
def test_not_overlapping(self):
dur1 = Duration(date(2000, 1, 1), date(2000, 1, 30))
dur2 = Duration(date(2000, 2, 1), date(2000, 2, 28))
self.assertEqual([dur1, dur2], simplify_durations([dur1, dur2]))
self.assertEqual([dur1, dur2], simplify_durations([dur2, dur1]))
self.assertEqual([dur1, dur2], MultiDuration([dur1, dur2]))
self.assertEqual([dur1, dur2], MultiDuration([dur2, dur1]))

def test_sequetial(self):
dur1 = Duration(date(2000, 1, 1), date(2000, 1, 31))
dur2 = Duration(date(2000, 2, 1), date(2000, 2, 28))
dur3 = Duration(date(2000, 1, 1), date(2000, 2, 28))
self.assertEqual([dur3], simplify_durations([dur1, dur2]))
self.assertEqual([dur3], simplify_durations([dur2, dur1]))
self.assertEqual([dur3], MultiDuration([dur1, dur2]))
self.assertEqual([dur3], MultiDuration([dur2, dur1]))

def test_overlapping(self):
dur1 = Duration(date(2000, 1, 1), date(2000, 2, 28))
dur2 = Duration(date(2000, 2, 1), date(2000, 3, 31))
dur3 = Duration(date(2000, 1, 1), date(2000, 3, 31))
self.assertEqual([dur3], simplify_durations([dur1, dur2]))
self.assertEqual([dur3], simplify_durations([dur2, dur1]))
self.assertEqual([dur3], MultiDuration([dur1, dur2]))
self.assertEqual([dur3], MultiDuration([dur2, dur1]))

def test_containing(self):
dur1 = Duration(date(2000, 1, 1), date(2000, 3, 31))
dur2 = Duration(date(2000, 2, 1), date(2000, 2, 28))
dur3 = Duration(date(2000, 1, 1), date(2000, 3, 31))
self.assertEqual([dur3], simplify_durations([dur1, dur2]))
self.assertEqual([dur3], simplify_durations([dur2, dur1]))
self.assertEqual([dur3], MultiDuration([dur1, dur2]))
self.assertEqual([dur3], MultiDuration([dur2, dur1]))

def test_empty(self):
self.assertEqual([], simplify_durations([]))
self.assertEqual([], MultiDuration([]))

def test_combine_per_key(self):
result = MultiDuration.combine_per_key([
(9, Duration(date(2000, 1, 1), date(2000, 12, 31))),
(1, Duration(date(2000, 1, 1), date(2000, 1, 31))),
(1, Duration(date(2000, 3, 1), date(2000, 3, 31))),
(1, Duration(date(2000, 5, 1), date(2000, 5, 31))),
(1, Duration(date(2000, 7, 1), date(2000, 7, 31))),
(1, Duration(date(2000, 9, 1), date(2000, 9, 30))),
(8, Duration(date(2000, 1, 1), date(2000, 12, 31))),

(1, Duration(date(2000, 1, 5), date(2000, 1, 6))),
(1, Duration(date(2000, 3, 7), date(2000, 7, 7))),
])
result = [(r[0], r[1].to_string()) for r in result]
self.assertEqual([
(9, '2000'),
(1, '1-31 januari 2000, 1 mars - 31 juli 2000, 1-30 september 2000'),
(8, '2000'),
], result)
9 changes: 7 additions & 2 deletions teknologr/katalogen/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ def setUp(self):
self.api_path = ''


class MembersSearchViewTest(BaseAPITest, GetPageTests):
class MembersEmptySearchViewTest(BaseAPITest, GetPageTests):
def setUp(self):
super().setUp()
self.api_path = '/search/?q=test'
self.api_path = '/search/?q='

class MembersStartsWithViewTest(BaseAPITest, GetPageTests):
def setUp(self):
Expand Down Expand Up @@ -74,6 +74,11 @@ def setUp(self):
super().setUp()
self.api_path = f'/groups/{self.gt.id}/'

class GroupMembershipsViewTest(BaseAPITest, GetPageTests):
def setUp(self):
super().setUp()
self.api_path = f'/groupmemberships/{self.gt.id}/'


class YearsViewTest(BaseAPITest, GetPageTests):
def setUp(self):
Expand Down
3 changes: 2 additions & 1 deletion teknologr/katalogen/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
url(r'^functionaries/$', views.functionary_types, name='functionary_types'),
url(r'^functionaries/(\d+)/$', views.functionary_type, name='functionary_type'),
url(r'^groups/$', views.group_types, name='group_types'),
url(r'^groups/(\d+)/$', views.group_type, name='group_type'),
url(r'^groups/(\d+)/$', views.groups, name='groups'),
url(r'^groupmemberships/(\d+)/$', views.group_memberships, name='group_memberships'),
url(r'^years/$', views.years, name='years'),
url(r'^years/(\d+)/$', views.year, name='year'),
]
Loading

0 comments on commit 427386f

Please sign in to comment.