diff --git a/src/onegov/agency/theme/styles/agency.scss b/src/onegov/agency/theme/styles/agency.scss index fea20cc3a1..a65fc74fea 100644 --- a/src/onegov/agency/theme/styles/agency.scss +++ b/src/onegov/agency/theme/styles/agency.scss @@ -189,3 +189,18 @@ ul.agency-nav { text-overflow: clip; white-space: normal; } + + +/* + Agency Form +*/ + +// hide the organisation field +.row.field-organisation { + display: none; +} + +// hide the sub organisation field +.row.field-sub_organisation { + display: none; +} diff --git a/src/onegov/org/forms/person.py b/src/onegov/org/forms/person.py index f67b9effe8..41466de65a 100644 --- a/src/onegov/org/forms/person.py +++ b/src/onegov/org/forms/person.py @@ -17,6 +17,8 @@ class PersonForm(Form): last_name = StringField(_("Last name"), [InputRequired()]) function = StringField(_("Function")) + organisation = StringField(_("Organisation")) + sub_organisation = StringField(_("Sub organisation")) email = EmailField(_("E-Mail")) phone = StringField(_("Phone")) diff --git a/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po index c3990ced7b..3275814321 100644 --- a/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po +++ b/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po @@ -4127,6 +4127,9 @@ msgstr "Referenz Anfrage" msgid "No people added yet." msgstr "Es wurden noch keine Personen hinzugefügt." +msgid "No people found for current filter selection." +msgstr "Keine Personen für aktuelle Filterauswahl gefunden." + msgid "Export a vCard of this person" msgstr "Elektronische Visitenkarte (vCard)" @@ -5792,6 +5795,18 @@ msgstr "Ein Konto wurde für Sie erstellt" msgid "The user was created successfully" msgstr "Der Benutzer wurde erfolgreich erstellt" +msgid "Organisation" +msgstr "Organisation" + +msgid "Sub organisation" +msgstr "Unterorganisation" + +msgid "Select organisation" +msgstr "Organisation wählen" + +msgid "Select sub organisation" +msgstr "Unterorganisation wählen" + #~ msgid "A new entry has been added to the \"${directory.title}\" directory:" #~ msgstr "" #~ "Ein neuer Eintrag wurde im Verzeichnis \"${directory.title}\" hinzugefügt:" diff --git a/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po index b39fa87652..e0f10e6b30 100644 --- a/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po +++ b/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po @@ -4139,6 +4139,9 @@ msgstr "Référence" msgid "No people added yet." msgstr "Aucune personne n'a encore été ajoutée." +msgid "No people found for current filter selection." +msgstr "Aucune personne n'a été trouvée pour la sélection de filtre actuelle." + msgid "Export a vCard of this person" msgstr "Exporter une vCard de cette personne" @@ -5816,6 +5819,18 @@ msgstr "Un compte a été créé pour vous" msgid "The user was created successfully" msgstr "L'utilisateur a bien été créé" +msgid "Organisation" +msgstr "Organisation" + +msgid "Sub organisation" +msgstr "Sous-organisation" + +msgid "Select organisation" +msgstr "Sélectionner une organisation" + +msgid "Select sub organisation" +msgstr "Sélectionner une sous-organisation" + #~ msgid "A new entry has been added to the \"${directory.title}\" directory:" #~ msgstr "" #~ "Une nouvelle entrée a été ajoutée au répertoire « ${directory.title} » :" diff --git a/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po index e57ca377b9..d8e20a1fc8 100644 --- a/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po +++ b/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po @@ -4129,6 +4129,9 @@ msgstr "Riferimento della richiesta" msgid "No people added yet." msgstr "Nessuna persona aggiunta ancora." +msgid "No people found for current filter selection." +msgstr "Non sono state trovate persone per la selezione del filtro corrente." + msgid "Export a vCard of this person" msgstr "Esporta una vCard di questa persona" @@ -5807,6 +5810,18 @@ msgstr "È stato creato un account per te" msgid "The user was created successfully" msgstr "Utente creato correttamente" +msgid "Organisation" +msgstr "Organizzazione" + +msgid "Sub organisation" +msgstr "Sotto organizzazione" + +msgid "Select organisation" +msgstr "Selezionare l'organizzazione" + +msgid "Select sub organisation" +msgstr "Selezionare la sotto-organizzazione" + #~ msgid "A new entry has been added to the \"${directory.title}\" directory:" #~ msgstr "" #~ "Un nuovo elemento è stato aggiunto alla cartella \"${directory.title}\":" diff --git a/src/onegov/org/templates/macros.pt b/src/onegov/org/templates/macros.pt index 4b098703a0..d1e9dc9d3c 100644 --- a/src/onegov/org/templates/macros.pt +++ b/src/onegov/org/templates/macros.pt @@ -748,6 +748,10 @@
  • ${person.function}
  • +
  • + ${person.organisation} + ${person.sub_organisation} +
  • ${person.email}
  • @@ -775,6 +779,10 @@
  • ${person.function}
  • +
  • + ${person.organisation} + ${person.sub_organisation} +
  • ${person.born} ${person.academic_title} diff --git a/src/onegov/org/templates/people.pt b/src/onegov/org/templates/people.pt index 4641ea19ba..ba81b00155 100644 --- a/src/onegov/org/templates/people.pt +++ b/src/onegov/org/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/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')