diff --git a/README.md b/README.md index a6babf2d45d8b8fb38759ff195aecb6f07ba523c..b9b8f371b6c9c7021a05801e0c3b8572979ff899 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Greg + [](https://git.app.uib.no/it-bott-integrasjoner/greg/-/commits/master) [](https://git.app.uib.no/it-bott-integrasjoner/greg/-/commits/master) @@ -50,6 +51,13 @@ Use pytest with the pytest-django library to run unit tests. pytest +There are two scripts for adding data to the database: + +- greg/tests/populate_fixtures.py +- greg/tests/populate_database.py + +where the former uses randomized data, and the latter uses specific data useful in combination with the frontend. See the respective files for how to use them. + ## Static type analysis Use [mypy](http://mypy-lang.org/) to run static type checks using type hints. diff --git a/frontend/src/routes/sponsor/frontpage/index.tsx b/frontend/src/routes/sponsor/frontpage/index.tsx index feddce802ad856f8d4bf189caacb72b6898168f4..225481b539d3a7d3589651b0441addfb05383845 100644 --- a/frontend/src/routes/sponsor/frontpage/index.tsx +++ b/frontend/src/routes/sponsor/frontpage/index.tsx @@ -18,7 +18,8 @@ import Page from 'components/page' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import { Guest, Role } from 'interfaces' -import format from 'date-fns/format' +import { isBefore, format } from 'date-fns' + import SponsorGuestButtons from '../../components/sponsorGuestButtons' interface GuestProps { @@ -43,7 +44,8 @@ const PersonLine = ({ cancelCallback, }: PersonLineProps) => { const [t, i18n] = useTranslation(['common']) - + const today = new Date() + today.setHours(0, 0, 0, 0) return ( <TableRow key={`${person.first} ${person.last}`} @@ -57,7 +59,7 @@ const PersonLine = ({ </TableCell> {showStatusColumn && - (person.active ? ( + (!isBefore(role.end_date, today) ? ( <TableCell sx={{ color: 'green' }} align="left"> {t('common:active')} </TableCell> diff --git a/greg/tests/populate_fixtures.py b/greg/tests/populate_fixtures.py new file mode 100644 index 0000000000000000000000000000000000000000..d7e03f4ed11a58a702c93944b76be8d384b513c7 --- /dev/null +++ b/greg/tests/populate_fixtures.py @@ -0,0 +1,343 @@ +""" +Adds a few more specific models for testing purposes. Alternative to the similiar +script for adding random data to various fields. + +WARNING: This script removes all entries in most tables. Do not execute it unless you +are absolutely certain you know what you are doing. + +There are 4 guests: + - One has been invited and not done anything yet + - One has followed the invite link and provided a passport number, and is waiting for + the sponsor to act + - One has had their passport number confirmed by the sponsor and the role is active + - One like the previous but the role has ended + +There is an OU tree with 4 OUs on three levels, with respective OUIdentifiers. +There is an invitation object related to each guest with InvitationLinks that are +expired for all but they invited guest that has not responded. + +There are Consent Types, and the active guests have consented to the mandatory one, but +one of them has denied the other one. + +""" + +import datetime +import logging + +from django.db import connection +from django.conf import settings +from django.utils import timezone + +from greg.models import ( + Consent, + ConsentType, + Identity, + Invitation, + InvitationLink, + OrganizationalUnit, + OuIdentifier, + Person, + Role, + RoleType, + Sponsor, + SponsorOrganizationalUnit, +) + + +ROLE_TYPE_EXT_SCI = "extsci" +ROLE_TYPE_EMERITUS = "emeritus" +TESTDATA_SOURCE = "testsource" +SPONSOR_FEIDEID = "sponsor@feide.no" +OU_EUROPE_NAME_EN = "Europe" +CONSENT_IDENT_MANDATORY = "mandatory" +CONSENT_IDENT_OPTIONAL = "optional" + +logger = logging.getLogger(__name__) + + +class DatabasePopulation: + """ + Helper class for populating database with specific data + + Run the file in the Django shell: exec(open('greg/tests/populate_fixtures.py').read()) + """ + + def truncate_tables(self): + logger.info("truncating tables...") + with connection.cursor() as cursor: + for table in ( + "greg_consent", + "greg_consenttype", + "greg_notification", + "greg_identity", + "greg_invitationlink", + "greg_invitation", + "greg_role", + "greg_sponsororganizationalunit", + "greg_roletype", + "greg_ouidentifier", + "greg_organizationalunit", + "greg_person", + "gregui_greguserprofile", + "greg_sponsor", + ): + logging.info("purging table %s", table) + cursor.execute(f"DELETE FROM {table}") + logger.info("...tables purged") + + def _add_consenttypes(self): + ConsentType.objects.create( + identifier=CONSENT_IDENT_MANDATORY, + name_en="Mandatory consent type", + name_nb="Påkrevd samtykketype", + user_allowed_to_change=False, + mandatory=True, + ) + ConsentType.objects.create( + identifier=CONSENT_IDENT_OPTIONAL, + name_en="Optional consent type", + name_nb="Valgfri samtykketype", + user_allowed_to_change=False, + ) + + def _add_ous_with_identifiers(self): + """ + Create a simple tree + + earth - america + - europe - norway + """ + earth = OrganizationalUnit.objects.create( + name_nb="Universitetet i Jorden", name_en="University of Earth" + ) + OuIdentifier.objects.create( + name=settings.ORGREG_NAME, + source=settings.ORGREG_SOURCE, + value="2", + orgunit=earth, + ) + europe = OrganizationalUnit.objects.create( + name_nb="Europa", name_en=OU_EUROPE_NAME_EN, parent=earth + ) + OuIdentifier.objects.create( + name=settings.ORGREG_NAME, + source=settings.ORGREG_SOURCE, + value="3", + orgunit=europe, + ) + america = OrganizationalUnit.objects.create( + name_nb="Amerika", name_en="America", parent=earth + ) + OuIdentifier.objects.create( + name=settings.ORGREG_NAME, + source=settings.ORGREG_SOURCE, + value="4", + orgunit=america, + ) + norway = OrganizationalUnit.objects.create( + name_nb="Norge", name_en="Norway", parent=europe + ) + OuIdentifier.objects.create( + name=settings.ORGREG_NAME, + source=settings.ORGREG_SOURCE, + value="5", + orgunit=norway, + ) + + def _add_roletypes(self): + RoleType.objects.create( + identifier=ROLE_TYPE_EXT_SCI, + name_nb="Gjesteforsker", + name_en="Guest researcher", + description_nb="Gjesteforsker som ikke skal ha lønn", + description_en="Guest reasearcher without payment", + ) + RoleType.objects.create( + identifier=ROLE_TYPE_EMERITUS, + name_nb="Emeritus", + name_en="Emeritus", + description_nb="Emeritus", + description_en="Emeritus", + max_days=700, + ) + + def _add_sponsors(self): + """Add a sponsor connected to the Europe unit""" + sam = Sponsor.objects.create( + feide_id=SPONSOR_FEIDEID, first_name="Sam", last_name="Sponsorson" + ) + SponsorOrganizationalUnit.objects.create( + sponsor=sam, + organizational_unit=OrganizationalUnit.objects.get( + name_en=OU_EUROPE_NAME_EN + ), + hierarchical_access=False, + ) + + def _add_invited_person(self): + """Person that has been invited and has not followed their invite""" + iggy = Person.objects.create(first_name="Iggy", last_name="Invited") + role = Role.objects.create( + person=iggy, + type=RoleType.objects.get(identifier=ROLE_TYPE_EXT_SCI), + orgunit=OrganizationalUnit.objects.get(name_en=OU_EUROPE_NAME_EN), + start_date=datetime.date.today() + datetime.timedelta(days=2), + end_date=datetime.date.today() + datetime.timedelta(days=100), + sponsor=Sponsor.objects.get(feide_id=SPONSOR_FEIDEID), + ) + invitation = Invitation.objects.create( + role=role, + ) + InvitationLink.objects.create( + invitation=invitation, + expire=timezone.now() + datetime.timedelta(days=30), + ) + + def _add_waiting_person(self): + """ + A person with an active role but missing a verified identity of type national + id or passport. + """ + walter = Person.objects.create( + first_name="Walter", + last_name="Waiting", + registration_completed_date=datetime.date.today() + - datetime.timedelta(days=10), + ) + role = Role.objects.create( + person=walter, + type=RoleType.objects.get(identifier=ROLE_TYPE_EXT_SCI), + orgunit=OrganizationalUnit.objects.get(name_en=OU_EUROPE_NAME_EN), + start_date=datetime.date.today() - datetime.timedelta(days=30), + end_date=datetime.date.today() + datetime.timedelta(days=100), + sponsor=Sponsor.objects.get(feide_id=SPONSOR_FEIDEID), + ) + Identity.objects.create( + person=walter, + type=Identity.IdentityType.PASSPORT_NUMBER, + source=TESTDATA_SOURCE, + value="SE-123456789", + ) + invitation = Invitation.objects.create( + role=role, + ) + InvitationLink.objects.create( + invitation=invitation, + expire=timezone.now() - datetime.timedelta(days=35), + ) + + def _add_active_person(self): + """ + A person with an active role and a verified identity of type national id or + passport. + """ + adam = Person.objects.create( + first_name="Adam", + last_name="Active", + registration_completed_date=datetime.date.today() + - datetime.timedelta(days=10), + ) + role = Role.objects.create( + person=adam, + type=RoleType.objects.get(identifier=ROLE_TYPE_EXT_SCI), + orgunit=OrganizationalUnit.objects.get(name_en=OU_EUROPE_NAME_EN), + start_date=datetime.date.today() - datetime.timedelta(days=30), + end_date=datetime.date.today() + datetime.timedelta(days=100), + sponsor=Sponsor.objects.get(feide_id=SPONSOR_FEIDEID), + ) + Identity.objects.create( + person=adam, + type=Identity.IdentityType.PASSPORT_NUMBER, + source=TESTDATA_SOURCE, + value="NO-123456789", + verified_at=timezone.now() - datetime.timedelta(days=31), + ) + Identity.objects.create( + person=adam, + type=Identity.IdentityType.PRIVATE_MOBILE_NUMBER, + source=TESTDATA_SOURCE, + value="+4792492412", + verified_at=timezone.now() - datetime.timedelta(days=205), + ) + invitation = Invitation.objects.create( + role=role, + ) + InvitationLink.objects.create( + invitation=invitation, + expire=timezone.now() - datetime.timedelta(days=32), + ) + Consent.objects.create( + person=adam, + type=ConsentType.objects.get(identifier=CONSENT_IDENT_MANDATORY), + consent_given_at=datetime.date.today() - datetime.timedelta(days=10), + ) + Consent.objects.create( + person=adam, + type=ConsentType.objects.get(identifier=CONSENT_IDENT_OPTIONAL), + consent_given_at=datetime.date.today() - datetime.timedelta(days=10), + ) + + def _add_expired_person(self): + """ + A person with an inactive role, and a verified identity of type national id or + passport. + """ + esther = Person.objects.create( + first_name="Esther", + last_name="Expired", + registration_completed_date=timezone.now() - datetime.timedelta(days=206), + ) + role = Role.objects.create( + person=esther, + type=RoleType.objects.get(identifier=ROLE_TYPE_EXT_SCI), + orgunit=OrganizationalUnit.objects.get(name_en=OU_EUROPE_NAME_EN), + start_date=datetime.date.today() - datetime.timedelta(days=200), + end_date=datetime.date.today() - datetime.timedelta(days=100), + sponsor=Sponsor.objects.get(feide_id=SPONSOR_FEIDEID), + ) + Identity.objects.create( + person=esther, + type=Identity.IdentityType.PASSPORT_NUMBER, + source=TESTDATA_SOURCE, + value="DK-123456789", + verified_at=timezone.now() - datetime.timedelta(days=205), + ) + Identity.objects.create( + person=esther, + type=Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER, + source=TESTDATA_SOURCE, + value="12345678901", + verified_at=timezone.now() - datetime.timedelta(days=205), + ) + invitation = Invitation.objects.create( + role=role, + ) + InvitationLink.objects.create( + invitation=invitation, + expire=timezone.now() - datetime.timedelta(days=204), + ) + Consent.objects.create( + person=esther, + type=ConsentType.objects.get(identifier=CONSENT_IDENT_MANDATORY), + consent_given_at=datetime.date.today() - datetime.timedelta(days=206), + ) + + def populate_database(self): + logger.info("populating db...") + # Add the types, sponsors and ous + self._add_consenttypes() + self._add_ous_with_identifiers() + self._add_roletypes() + self._add_sponsors() + # Add the four guests + self._add_active_person() + self._add_waiting_person() + self._add_invited_person() + self._add_expired_person() + logger.info("...done populating db") + + +if __name__ == "__main__": + database_population = DatabasePopulation() + database_population.truncate_tables() + database_population.populate_database()