Skip to content
Snippets Groups Projects
Commit c7a00c17 authored by Tore.Brede's avatar Tore.Brede
Browse files

Merge branch 'master' into GREG-9_audit_log

parents d87e4450 d4c6f053
No related branches found
No related tags found
1 merge request!9GREG-9 Audit log
Showing
with 307 additions and 28 deletions
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
.settings/ .settings/
.venv/ .venv/
.vscode/ .vscode/
venv/ venv*/
gregsite/db.sqlite3 gregsite/db.sqlite3
gregsite/settings/local.py gregsite/settings/local.py
gregsite/static/
...@@ -5,6 +5,7 @@ django-settings-module=gregsite.settings ...@@ -5,6 +5,7 @@ django-settings-module=gregsite.settings
[MESSAGES CONTROL] [MESSAGES CONTROL]
disable= disable=
duplicate-code,
fixme, fixme,
import-outside-toplevel, import-outside-toplevel,
invalid-name, invalid-name,
...@@ -13,6 +14,7 @@ disable= ...@@ -13,6 +14,7 @@ disable=
missing-function-docstring, missing-function-docstring,
missing-module-docstring, missing-module-docstring,
no-self-use, no-self-use,
redefined-outer-name,
too-few-public-methods, too-few-public-methods,
too-many-ancestors, too-many-ancestors,
unused-argument, unused-argument,
DJANGO_SETTINGS_MODULE ?= gregsite.settings.dev
BLACK ?= black -q BLACK ?= black -q
MYPY ?= mypy MYPY ?= mypy
PIP ?= pip -q PIP ?= pip -q
POETRY ?= poetry POETRY ?= poetry
PYLINT ?= pylint -sn PYLINT ?= pylint -sn
PYTEST ?= pytest -v -s --no-header
PYTHON ?= python3.9 PYTHON ?= python3.9
VENV ?= venv VENV ?= venv
mypy = $(MYPY) --config-file mypy.ini mypy = $(MYPY) --config-file mypy.ini
pip = python -m $(PIP) pip = python -m $(PIP)
poetry = python -m $(POETRY) poetry = python -m $(POETRY)
pytest = DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) python -m $(PYTEST)
venv = . $(VENV)/bin/activate && venv = . $(VENV)/bin/activate &&
PACKAGES = greg/ gregsite/ PACKAGES = greg/ gregsite/
...@@ -30,7 +34,7 @@ $(VENV)/touchfile: ...@@ -30,7 +34,7 @@ $(VENV)/touchfile:
.PHONY: test .PHONY: test
test: $(VENV) test: $(VENV)
$(venv) $(mypy) -p greg $(venv) $(mypy) -p greg
$(venv) python manage.py test $(venv) $(pytest)
.PHONY: lint .PHONY: lint
lint: $(VENV) lint: $(VENV)
......
...@@ -12,6 +12,10 @@ class PersonRoleFilter(filters.FilterSet): ...@@ -12,6 +12,10 @@ class PersonRoleFilter(filters.FilterSet):
class PersonFilter(filters.FilterSet): class PersonFilter(filters.FilterSet):
verified = filters.BooleanFilter(
field_name="person__verified_by_id", lookup_expr="isnull", exclude=True
)
class Meta: class Meta:
model = Person model = Person
fields = ["first_name"] fields = ["first_name", "last_name", "verified"]
from rest_framework.serializers import ModelSerializer
from greg.models import Consent
class ConsentSerializer(ModelSerializer):
class Meta:
model = Consent
fields = "__all__"
from rest_framework.serializers import ModelSerializer
from greg.models import OrganizationalUnit
class OrganizationalUnitSerializer(ModelSerializer):
class Meta:
model = OrganizationalUnit
fields = "__all__"
...@@ -6,7 +6,14 @@ from greg.models import Person, PersonRole, Role ...@@ -6,7 +6,14 @@ from greg.models import Person, PersonRole, Role
class PersonSerializer(serializers.ModelSerializer): class PersonSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Person model = Person
fields = ("id", "first_name", "last_name", "date_of_birth", "email", "mobile_phone") fields = [
"id",
"first_name",
"last_name",
"date_of_birth",
"email",
"mobile_phone",
]
class PersonRoleSerializer(serializers.ModelSerializer): class PersonRoleSerializer(serializers.ModelSerializer):
...@@ -16,6 +23,10 @@ class PersonRoleSerializer(serializers.ModelSerializer): ...@@ -16,6 +23,10 @@ class PersonRoleSerializer(serializers.ModelSerializer):
model = PersonRole model = PersonRole
fields = [ fields = [
"id", "id",
"start_date",
"end_date",
"registered_by",
"unit",
"created", "created",
"updated", "updated",
"role", "role",
......
from rest_framework import serializers
from greg.models import Sponsor
class SponsorSerializer(serializers.ModelSerializer):
class Meta:
model = Sponsor
fields = ["id", "feide_id"]
from django.conf.urls import url from django.urls import (
from django.urls import path path,
re_path,
)
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView from drf_spectacular.views import (
SpectacularAPIView,
SpectacularSwaggerView,
)
from greg.api.views.person import PersonViewSet, PersonRoleViewSet from greg.api.views.consent import ConsentViewSet
from greg.api.views.organizational_unit import OrganizationalUnitViewSet
from greg.api.views.person import (
PersonRoleViewSet,
PersonViewSet,
)
from greg.api.views.role import RoleViewSet from greg.api.views.role import RoleViewSet
from greg.api.views.health import Health from greg.api.views.health import Health
from greg.api.views.sponsor import SponsorViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r"persons", PersonViewSet, basename="person") router.register(r"persons", PersonViewSet, basename="person")
router.register(r"roles", RoleViewSet, basename="role") router.register(r"roles", RoleViewSet, basename="role")
router.register(r"consents", ConsentViewSet, basename="consent")
router.register(r"sponsors", SponsorViewSet, basename="sponsor")
router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit")
urlpatterns = router.urls urlpatterns = router.urls
...@@ -21,12 +36,12 @@ urlpatterns += [ ...@@ -21,12 +36,12 @@ urlpatterns += [
name="swagger-ui", name="swagger-ui",
), ),
path("health/", Health.as_view()), path("health/", Health.as_view()),
url( re_path(
r"^persons/(?P<person_id>[0-9]+)/roles/$", r"^persons/(?P<person_id>[0-9]+)/roles/$",
PersonRoleViewSet.as_view({"get": "list"}), PersonRoleViewSet.as_view({"get": "list", "post": "create"}),
name="person_role-list", name="person_role-list",
), ),
url( re_path(
r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/$", r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/$",
PersonRoleViewSet.as_view({"get": "retrieve"}), PersonRoleViewSet.as_view({"get": "retrieve"}),
name="person_role-detail", name="person_role-detail",
......
from rest_framework import viewsets
from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers.consent import ConsentSerializer
from greg.models import Consent
class ConsentViewSet(viewsets.ModelViewSet):
"""Consent API"""
queryset = Consent.objects.all().order_by("id")
serializer_class = ConsentSerializer
pagination_class = PrimaryKeyCursorPagination
lookup_field = "id"
from rest_framework import viewsets
from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer
from greg.models import OrganizationalUnit
class OrganizationalUnitViewSet(viewsets.ModelViewSet):
"""OrganizationalUnit API"""
queryset = OrganizationalUnit.objects.all().order_by("id")
serializer_class = OrganizationalUnitSerializer
pagination_class = PrimaryKeyCursorPagination
lookup_field = "id"
from django.core.exceptions import ValidationError
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework import viewsets from rest_framework import viewsets
from greg.api.filters import PersonFilter, PersonRoleFilter
from greg.api.pagination import PrimaryKeyCursorPagination from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers.person import PersonSerializer, PersonRoleSerializer from greg.api.serializers.person import PersonSerializer, PersonRoleSerializer
from greg.api.filters import PersonFilter, PersonRoleFilter
from greg.models import Person, PersonRole from greg.models import Person, PersonRole
...@@ -18,6 +19,20 @@ class PersonViewSet(viewsets.ModelViewSet): ...@@ -18,6 +19,20 @@ class PersonViewSet(viewsets.ModelViewSet):
filter_backends = (filters.DjangoFilterBackend,) filter_backends = (filters.DjangoFilterBackend,)
filterset_class = PersonFilter filterset_class = PersonFilter
@extend_schema(
parameters=[
OpenApiParameter(
name="verified",
description="Only include verified or only not verified. When nothing is set, "
"both verified and not verified are returned",
required=False,
type=bool,
)
]
)
def list(self, request, *args, **kwargs):
return super().list(request)
class PersonRoleViewSet(viewsets.ModelViewSet): class PersonRoleViewSet(viewsets.ModelViewSet):
"""Person role API""" """Person role API"""
...@@ -38,3 +53,12 @@ class PersonRoleViewSet(viewsets.ModelViewSet): ...@@ -38,3 +53,12 @@ class PersonRoleViewSet(viewsets.ModelViewSet):
if person_role_id: if person_role_id:
qs = qs.filter(id=person_role_id) qs = qs.filter(id=person_role_id)
return qs return qs
def perform_create(self, serializer):
person_id = self.kwargs["person_id"]
if person_id is None:
# Should not happen, the person ID is part of the API path
raise ValidationError("No person id")
serializer.save(person_id=person_id)
from rest_framework.viewsets import ReadOnlyModelViewSet
from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers.sponsor import SponsorSerializer
from greg.models import Sponsor
class SponsorViewSet(ReadOnlyModelViewSet):
"""Sponsor API"""
queryset = Sponsor.objects.all().order_by("id")
serializer_class = SponsorSerializer
pagination_class = PrimaryKeyCursorPagination
lookup_field = "id"
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
class GregAppConfig(AppConfig): class GregAppConfig(AppConfig):
......
# Generated by Django 3.2.5 on 2021-07-14 12:28 # Generated by Django 3.2.5 on 2021-07-15 13:31
import datetime import datetime
import dirtyfields.dirtyfields import dirtyfields.dirtyfields
...@@ -147,6 +147,9 @@ class Migration(migrations.Migration): ...@@ -147,6 +147,9 @@ class Migration(migrations.Migration):
('role', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='person_roles', to='greg.role')), ('role', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='person_roles', to='greg.role')),
('unit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='unit_person_role', to='greg.organizationalunit')), ('unit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='unit_person_role', to='greg.organizationalunit')),
], ],
options={
'abstract': False,
},
bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model), bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -198,10 +201,6 @@ class Migration(migrations.Migration): ...@@ -198,10 +201,6 @@ class Migration(migrations.Migration):
model_name='sponsor', model_name='sponsor',
constraint=models.UniqueConstraint(fields=('feide_id',), name='unique_feide_id'), constraint=models.UniqueConstraint(fields=('feide_id',), name='unique_feide_id'),
), ),
migrations.AddConstraint(
model_name='personrole',
constraint=models.UniqueConstraint(fields=('person', 'role', 'unit'), name='personrole_person_role_unit_unique'),
),
migrations.AddConstraint( migrations.AddConstraint(
model_name='personconsent', model_name='personconsent',
constraint=models.UniqueConstraint(fields=('person', 'consent'), name='person_consent_unique'), constraint=models.UniqueConstraint(fields=('person', 'consent'), name='person_consent_unique'),
......
...@@ -103,14 +103,6 @@ class PersonRole(BaseModel): ...@@ -103,14 +103,6 @@ class PersonRole(BaseModel):
"Sponsor", on_delete=models.PROTECT, related_name="sponsor_role" "Sponsor", on_delete=models.PROTECT, related_name="sponsor_role"
) )
class Meta:
constraints = [
models.UniqueConstraint(
fields=["person", "role", "unit"],
name="personrole_person_role_unit_unique",
)
]
def __repr__(self): def __repr__(self):
return "{}(id={!r}, person={!r}, role={!r})".format( return "{}(id={!r}, person={!r}, role={!r})".format(
self.__class__.__name__, self.pk, self.person, self.role self.__class__.__name__, self.pk, self.person, self.role
......
import pytest
from rest_framework import status
from rest_framework.reverse import reverse
from greg.models import Consent
@pytest.fixture
def consent_foo() -> Consent:
return Consent.objects.create(
type="test_consent",
consent_name_en="Test1",
consent_name_nb="Test2",
consent_description_en="Test description",
consent_description_nb="Test beskrivelse",
consent_link_en="https://example.org",
consent_link_nb="https://example.org",
valid_from="2018-01-20",
user_allowed_to_change=True,
)
@pytest.mark.django_db
def test_get_consent(client, consent_foo):
resp = client.get(reverse("consent-detail", kwargs={"id": consent_foo.id}))
assert resp.status_code == status.HTTP_200_OK
data = resp.json()
assert data.get("id") == consent_foo.id
assert data.get("type") == consent_foo.type
assert data.get("consent_name_en") == consent_foo.consent_name_en
import pytest
from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.status import HTTP_200_OK
from greg.models import Person
@pytest.fixture
def person_foo() -> Person:
return Person.objects.create(
first_name="Foo",
last_name="Foo",
date_of_birth="2001-01-27",
email="test@example.org",
mobile_phone="123456788",
)
@pytest.fixture
def person_bar() -> Person:
return Person.objects.create(
first_name="Bar",
last_name="Bar",
date_of_birth="2000-07-01",
email="test2@example.org",
mobile_phone="123456789",
)
@pytest.mark.django_db
def test_get_person(client, person_foo):
resp = client.get(reverse("person-detail", kwargs={"id": person_foo.id}))
assert resp.status_code == HTTP_200_OK
data = resp.json()
assert data.get("id") == person_foo.id
assert data.get("first_name") == person_foo.first_name
assert data.get("last_name") == person_foo.last_name
@pytest.mark.django_db
def test_persons(client, person_foo, person_bar):
resp = client.get(reverse("person-list"))
assert resp.status_code == HTTP_200_OK
data = resp.json()
assert len(data["results"]) == 2
@pytest.mark.django_db
def test_persons_verified_filter_include(client, setup_db_test_data):
url = reverse("person-list")
response = client.get(url, {"verified": "true"})
results = response.json()["results"]
assert len(results) == 1
# The following person will have a verified identity set up for him
# in the test data
assert results[0]["first_name"] == "Christopher"
assert results[0]["last_name"] == "Flores"
@pytest.mark.django_db
def test_persons_verified_filter_exclude(client, setup_db_test_data):
url = reverse("person-list")
response = client.get(url, {"verified": "false"})
results = response.json()["results"]
names = [(result["first_name"], result["last_name"]) for result in results]
assert len(results) == 9
assert ("Christopher", "Flores") not in names
@pytest.mark.django_db
def test_add_role(client, person_foo):
url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
roles_for_person = client.get(url).json()["results"]
# Check that there are no roles for the person, and then add one
assert len(roles_for_person) == 0
role_data = {
"role": "Visiting Professor",
"start_date": "2021-06-10",
"end_date": "2021-08-10",
"registered_by": "1",
"unit": "1",
}
response = client.post(url, role_data)
assert response.status_code == status.HTTP_201_CREATED
response_data = response.json()
roles_for_person = client.get(url).json()["results"]
# Check that the role shows up when listing roles for the person now
assert len(roles_for_person) == 1
assert roles_for_person[0]["id"] == response_data["id"]
import logging
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from django.contrib.auth import get_user_model
# faker spams the logs with localisation warnings
# see https://github.com/joke2k/faker/issues/753
import pytest
from greg.tests.populate_database import DatabasePopulation
logging.getLogger("faker").setLevel(logging.ERROR)
# The database is populated once when scope is session.
# If the scope is changed to function some additional
# logic is needed to make sure the old data is cleaned
# before the seeding is run again
@pytest.fixture(scope="session")
def setup_db_test_data(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
database_seeder = DatabasePopulation()
database_seeder.populate_database()
@pytest.fixture
def client() -> APIClient:
user, _ = get_user_model().objects.get_or_create(username="test")
token, _ = Token.objects.get_or_create(user=user)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}")
return client
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment