diff --git a/src/onegov/org/theme/styles/org.scss b/src/onegov/org/theme/styles/org.scss
index 94880cacb8..2ae6c0fe0e 100644
--- a/src/onegov/org/theme/styles/org.scss
+++ b/src/onegov/org/theme/styles/org.scss
@@ -2450,6 +2450,14 @@ $person-email-icon: '\f003';
margin-bottom: 1em;
}
+ .person-card-organisation {
+ margin-bottom: 1em;
+
+ span:not(:last-child)::after {
+ content: ' -';
+ }
+ }
+
.person-card-personal {
margin-bottom: 1em;
diff --git a/src/onegov/org/views/people.py b/src/onegov/org/views/people.py
index 34eda4c10e..60705dba8e 100644
--- a/src/onegov/org/views/people.py
+++ b/src/onegov/org/views/people.py
@@ -26,7 +26,12 @@ def view_people(
layout: PersonCollectionLayout | None = None
) -> 'RenderData':
- people = self.query().order_by(Person.last_name, Person.first_name).all()
+ selected_org = str(request.params.get('organisation', ''))
+ selected_sub_org = str(request.params.get('sub_organisation', ''))
+
+ people = self.people_by_organisation(selected_org, selected_sub_org)
+ orgs = self.unique_organisations()
+ sub_orgs = self.unique_sub_organisations(selected_org)
class AtoZPeople(AtoZ[Person]):
@@ -38,8 +43,13 @@ def get_items(self) -> list[Person]:
return {
'title': _("People"),
+ 'count': len(people),
'people': AtoZPeople(request).get_items_by_letter().items(),
- 'layout': layout or PersonCollectionLayout(self, request)
+ 'layout': layout or PersonCollectionLayout(self, request),
+ 'organisations': orgs,
+ 'sub_organisations': sub_orgs,
+ 'selected_organisation': selected_org,
+ 'selected_sub_organisation': selected_sub_org
}
diff --git a/src/onegov/people/collections/people.py b/src/onegov/people/collections/people.py
index 8cb7fff0ab..bd99c0e3ec 100644
--- a/src/onegov/people/collections/people.py
+++ b/src/onegov/people/collections/people.py
@@ -2,7 +2,6 @@
from onegov.core.collection import GenericCollection
from onegov.people.models import Person
-
from typing import Any
from typing import TypeVar
from typing import TYPE_CHECKING
@@ -24,7 +23,6 @@ def add( # type:ignore[override]
last_name: str,
**optional: Any
) -> PersonT:
-
person = self.model_class(
first_name=first_name,
last_name=last_name,
@@ -55,3 +53,39 @@ class PersonCollection(BasePersonCollection[Person]):
@property
def model_class(self) -> type[Person]:
return Person
+
+ def people_by_organisation(
+ self,
+ org: str | None,
+ sub_org: str | None
+ ) -> list[Person]:
+ """
+ Returns all persons of a given organisation and sub-organisation.
+
+ If organisation and sub-organisation are both None, all persons are
+ returned.
+ """
+ query = self.session.query(Person).order_by(Person.last_name,
+ Person.first_name)
+ if org:
+ query = query.filter(Person.organisation == org)
+ if sub_org:
+ query = query.filter(Person.sub_organisation == sub_org)
+ return query.all()
+
+ def unique_organisations(self) -> tuple[str | None, ...]:
+ query = self.session.query(Person.organisation)
+ query = query.filter(Person.organisation.isnot(None)).distinct()
+ query = query.order_by(Person.organisation)
+ return tuple(org[0] for org in query if org[0] != '')
+
+ def unique_sub_organisations(
+ self,
+ of_org: str | None = None
+ ) -> tuple[str | None, ...]:
+ query = self.session.query(Person.sub_organisation)
+ if of_org:
+ query = query.filter(Person.organisation == of_org)
+ query = query.filter(Person.sub_organisation.isnot(None)).distinct()
+ query = query.order_by(Person.sub_organisation)
+ return tuple(s_org[0] for s_org in query if s_org[0] != '')
diff --git a/src/onegov/people/models/person.py b/src/onegov/people/models/person.py
index 3c0299e0ff..49b7b1bfae 100644
--- a/src/onegov/people/models/person.py
+++ b/src/onegov/people/models/person.py
@@ -103,6 +103,12 @@ def spoken_title(self) -> str:
#: the function of the person
function: 'Column[str | None]' = Column(Text, nullable=True)
+ #: an organisation the person belongs to
+ organisation: 'Column[str | None]' = Column(Text, nullable=True)
+
+ # a sub organisation the person belongs to
+ sub_organisation: 'Column[str | None]' = Column(Text, nullable=True)
+
#: the political party the person belongs to
political_party: 'Column[str | None]' = Column(Text, nullable=True)
diff --git a/src/onegov/people/upgrade.py b/src/onegov/people/upgrade.py
index a867663d40..c329109f45 100644
--- a/src/onegov/people/upgrade.py
+++ b/src/onegov/people/upgrade.py
@@ -246,3 +246,15 @@ def extend_agency_and_person_with_more_fields(context: UpgradeContext) -> None:
Column(column, Text, nullable=True),
default=lambda x: ''
)
+
+
+@upgrade_task('Add organisation columns to people')
+def add_organisation_columns_to_people(context: UpgradeContext) -> None:
+ if not context.has_column('people', 'organisation'):
+ context.operations.add_column('people', Column(
+ 'organisation', Text, nullable=True
+ ))
+ if not context.has_column('people', 'sub_organisation'):
+ context.operations.add_column('people', Column(
+ 'sub_organisation', Text, nullable=True
+ ))
diff --git a/src/onegov/town6/templates/macros.pt b/src/onegov/town6/templates/macros.pt
index 618a0d5b61..b07d0f5039 100644
--- a/src/onegov/town6/templates/macros.pt
+++ b/src/onegov/town6/templates/macros.pt
@@ -969,6 +969,10 @@
${person.function}
+
+ ${person.organisation}
+ ${person.sub_organisation}
+
${person.email}
@@ -995,6 +999,10 @@
${person.function}
+
+ ${person.organisation}
+ ${person.sub_organisation}
+
${person.born}
${person.academic_title}
diff --git a/src/onegov/town6/templates/people.pt b/src/onegov/town6/templates/people.pt
index 5e816bbf27..785754745b 100644
--- a/src/onegov/town6/templates/people.pt
+++ b/src/onegov/town6/templates/people.pt
@@ -3,7 +3,23 @@
${title}
- No people added yet.
+ No people added yet.
+ No people found for current filter selection.
+
+
diff --git a/src/onegov/town6/theme/styles/person.scss b/src/onegov/town6/theme/styles/person.scss
index 972384d2b7..e76ada6476 100644
--- a/src/onegov/town6/theme/styles/person.scss
+++ b/src/onegov/town6/theme/styles/person.scss
@@ -49,6 +49,14 @@ $person-email-icon: '\f0e0';
margin-bottom: 1em;
}
+ .person-card-organisation {
+ margin-bottom: 1em;
+
+ span:not(:last-child)::after {
+ content: ' -';
+ }
+ }
+
.person-card-personal {
margin-bottom: 1em;
@@ -63,6 +71,9 @@ $person-email-icon: '\f0e0';
span {
white-space: pre-line;
}
+ span:not(:last-child)::after {
+ content: ',';
+ }
}
.person-card-notes {
diff --git a/tests/onegov/org/test_views_people.py b/tests/onegov/org/test_views_people.py
index 43d93a07df..80b2a16ddf 100644
--- a/tests/onegov/org/test_views_people.py
+++ b/tests/onegov/org/test_views_people.py
@@ -83,12 +83,12 @@ def test_with_people(client):
assert 'Gordon Flash' in new_page
assert 'Ming Merciless' in new_page
- gordon = client.app.session().query(Person)\
- .filter(Person.last_name == 'Gordon')\
+ gordon = client.app.session().query(Person) \
+ .filter(Person.last_name == 'Gordon') \
.one()
- ming = client.app.session().query(Person)\
- .filter(Person.last_name == 'Ming')\
+ ming = client.app.session().query(Person) \
+ .filter(Person.last_name == 'Ming') \
.one()
new_page.form['title'] = 'About Flash'
@@ -98,12 +98,181 @@ def test_with_people(client):
assert edit_page.form['people_' + gordon.id.hex].value == 'y'
assert edit_page.form['people_' + gordon.id.hex + '_function'].value \
- == 'Astronaut'
+ == 'Astronaut'
assert edit_page.form['people_' + ming.id.hex].value is None
assert edit_page.form['people_' + ming.id.hex + '_function'].value == ''
+def test_people_view_organisation_fiter(client):
+ org_1 = 'The Nexus'
+ sub_org_11 = 'Nexus Innovators'
+ sub_org_12 = 'Nexus Guardians'
+ sub_org_13 = 'Nexus Diplomats'
+ org_2 = 'The Vanguard'
+ sub_org_21 = 'Vanguard Tech'
+ sub_org_22 = 'Vanguard Capital'
+
+ client.login_editor()
+
+ def add_person(first_name, last_name, function, org, sub_org):
+ people = client.get('/people')
+ new_person = people.click('Person')
+ new_person.form['first_name'] = first_name
+ new_person.form['last_name'] = last_name
+ new_person.form['function'] = function
+ new_person.form['organisation'] = org
+ new_person.form['sub_organisation'] = sub_org
+ new_person.form.submit()
+
+ add_person('Aria', 'Chen',
+ 'brilliant robotics engineer and team leader',
+ org_1, sub_org_11)
+ add_person('Max', 'Holloway',
+ 'young prodigy in artificial intelligence',
+ org_1, sub_org_11)
+ add_person('Olivia', 'Greenwood',
+ 'leading climate scientists', org_1, sub_org_12)
+ add_person('Sofia', 'Mendoza',
+ 'charismatic public speaker', org_1, sub_org_12)
+ add_person('James', 'Thornton',
+ 'seasoned diplomat', org_1, sub_org_13)
+ add_person('Zack', 'Torres',
+ 'tech-savvy intern, eager to learn from all divisions',
+ org_1, '')
+ add_person('John', 'Doe', 'CEO and tech mogul',
+ org_2, sub_org_21)
+ add_person('Victoria', 'Smith',
+ 'Shrewd investment banker and division head',
+ org_2, sub_org_22)
+
+ # no filter
+ people = client.get('/people')
+ assert 'Chen Aria' in people
+ assert 'Holloway Max' in people
+ assert 'Greenwood Olivia' in people
+ assert 'Mendoza Sofia' in people
+ assert 'Thornton James' in people
+ assert 'Torres Zack' in people
+ assert 'Doe John' in people
+ assert 'Smith Victoria' in people
+
+ # filter for organization 'Nexus'
+ people = client.get('/people?organisation=The+Nexus')
+ assert 'Chen Aria' in people
+ assert 'Holloway Max' in people
+ assert 'Greenwood Olivia' in people
+ assert 'Mendoza Sofia' in people
+ assert 'Thornton James' in people
+ assert 'Torres Zack' in people
+ assert 'Doe John' not in people
+ assert 'Smith Victoria' not in people
+
+ # filter for sub organization 'Innovators'
+ people = client.get('/people?organisation=The+Nexus&sub_organisation'
+ '=Nexus+Innovators')
+ assert 'Chen Aria' in people
+ assert 'Holloway Max' in people
+ assert 'Greenwood Olivia' not in people
+ assert 'Mendoza Sofia' not in people
+ assert 'Thornton James' not in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' not in people
+ assert 'Smith Victoria' not in people
+
+ # filter for sub organization 'Guardians'
+ people = client.get('/people?organisation=The+Nexus&sub_organisation'
+ '=Nexus+Guardians')
+ assert 'Chen Aria' not in people
+ assert 'Holloway Max' not in people
+ assert 'Greenwood Olivia' in people
+ assert 'Mendoza Sofia' in people
+ assert 'Thornton James' not in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' not in people
+ assert 'Smith Victoria' not in people
+
+ # filter for sub organization 'Diplomats'
+ people = client.get('/people?organisation=The+Nexus&sub_organisation'
+ '=Nexus+Diplomats')
+ assert 'Chen Aria' not in people
+ assert 'Holloway Max' not in people
+ assert 'Greenwood Olivia' not in people
+ assert 'Mendoza Sofia' not in people
+ assert 'Thornton James' in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' not in people
+ assert 'Smith Victoria' not in people
+
+ # filter for organization 'The Vanguard'
+ people = client.get('/people?organisation=The+Vanguard')
+ assert 'Chen Aria' not in people
+ assert 'Holloway Max' not in people
+ assert 'Greenwood Olivia' not in people
+ assert 'Mendoza Sofia' not in people
+ assert 'Thornton James' not in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' in people
+ assert 'Smith Victoria' in people
+
+ # filter for sub organization 'Vanguard Tech'
+ people = client.get('/people?organisation=The+Vanguard&sub_organisation'
+ '=Vanguard+Tech')
+ assert 'Chen Aria' not in people
+ assert 'Holloway Max' not in people
+ assert 'Greenwood Olivia' not in people
+ assert 'Mendoza Sofia' not in people
+ assert 'Thornton James' not in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' in people
+ assert 'Smith Victoria' not in people
+
+ # filter for sub organization 'Vanguard Capital'
+ people = client.get('/people?organisation=The+Vanguard&sub_organisation'
+ '=Vanguard+Capital')
+ assert 'Chen Aria' not in people
+ assert 'Holloway Max' not in people
+ assert 'Greenwood Olivia' not in people
+ assert 'Mendoza Sofia' not in people
+ assert 'Thornton James' not in people
+ assert 'Torres Zack' not in people
+ assert 'Doe John' not in people
+ assert 'Smith Victoria' in people
+
+ # mix filters - no results
+ people = client.get('/people?organisation=The+Vanguard&sub_organisation'
+ '=The+Innovators')
+ assert 'Keine Personen für aktuelle Filterauswahl gefunden' in people
+
+ # test select options
+ people = client.get('/people')
+ assert 'The Nexus' in people
+ assert 'The Vanguard' in people
+ assert 'Nexus Innovators' in people
+ assert 'Nexus Guardians' in people
+ assert 'Nexus Diplomats' in people
+ assert 'Vanguard Tech' in people
+ assert 'Vanguard Capital' in people
+
+ people = client.get('/people?organisation=The+Nexus')
+ assert 'The Nexus' in people
+ assert 'The Vanguard' in people
+ assert 'Nexus Innovators' in people
+ assert 'Nexus Guardians' in people
+ assert 'Nexus Diplomats' in people
+ assert 'Vanguard Tech' not in people
+ assert 'Vanguard Capital' not in people
+
+ people = client.get('/people?organisation=The+Vanguard')
+ assert 'The Nexus' in people
+ assert 'The Vanguard' in people
+ assert 'Nexus Innovators' not in people
+ assert 'Nexus Guardians' not in people
+ assert 'Nexus Diplomats' not in people
+ assert 'Vanguard Tech' in people
+ assert 'Vanguard Capital' in people
+
+
def test_delete_linked_person_issue_149(client):
client.login_editor()
@@ -114,8 +283,8 @@ def test_delete_linked_person_issue_149(client):
new_person.form['last_name'] = 'Gordon'
new_person.form.submit()
- gordon = client.app.session().query(Person)\
- .filter(Person.last_name == 'Gordon')\
+ gordon = client.app.session().query(Person) \
+ .filter(Person.last_name == 'Gordon') \
.one()
new_page = client.get('/topics/organisation').click('Thema')