diff options
author | Matthew Lemon <lemon@matthewlemon.com> | 2020-05-28 16:12:13 +0100 |
---|---|---|
committer | Matthew Lemon <lemon@matthewlemon.com> | 2020-05-28 16:12:13 +0100 |
commit | acf875c2bca1727306cb70aaa37e6f4595209786 (patch) | |
tree | 67390297bcd02b2481f5673629227dfea0884215 /ctrack/users | |
parent | 4aaa68109434e04d1103710d675efd075b2f744a (diff) | |
parent | 520a9deb13f93c689443c0339b1c96a67f0c15a1 (diff) |
Merge branch 'stakeholder-profile'
Diffstat (limited to 'ctrack/users')
-rw-r--r-- | ctrack/users/admin.py | 6 | ||||
-rw-r--r-- | ctrack/users/migrations/0005_delete_userprofile.py | 16 | ||||
-rw-r--r-- | ctrack/users/migrations/0006_user_stakeholder.py | 20 | ||||
-rw-r--r-- | ctrack/users/models.py | 22 | ||||
-rw-r--r-- | ctrack/users/stakeholder.py | 4 | ||||
-rw-r--r-- | ctrack/users/tests/test_functional.py | 124 | ||||
-rw-r--r-- | ctrack/users/tests/test_models.py | 10 | ||||
-rw-r--r-- | ctrack/users/tests/test_views.py | 142 | ||||
-rw-r--r-- | ctrack/users/views.py | 12 |
9 files changed, 329 insertions, 27 deletions
diff --git a/ctrack/users/admin.py b/ctrack/users/admin.py index 120cc64..7e930d8 100644 --- a/ctrack/users/admin.py +++ b/ctrack/users/admin.py @@ -12,6 +12,8 @@ class UserAdmin(auth_admin.UserAdmin): form = UserChangeForm add_form = UserCreationForm - fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets - list_display = ["username", "name", "is_superuser"] + fieldsets = ( + ("User", {"fields": ("name", "stakeholder")}), + ) + auth_admin.UserAdmin.fieldsets + list_display = ["username", "name", "is_superuser", "stakeholder"] search_fields = ["name"] diff --git a/ctrack/users/migrations/0005_delete_userprofile.py b/ctrack/users/migrations/0005_delete_userprofile.py new file mode 100644 index 0000000..42f62bb --- /dev/null +++ b/ctrack/users/migrations/0005_delete_userprofile.py @@ -0,0 +1,16 @@ +# Generated by Django 3.0.5 on 2020-05-25 14:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_auto_20200524_1945'), + ] + + operations = [ + migrations.DeleteModel( + name='UserProfile', + ), + ] diff --git a/ctrack/users/migrations/0006_user_stakeholder.py b/ctrack/users/migrations/0006_user_stakeholder.py new file mode 100644 index 0000000..d8a3089 --- /dev/null +++ b/ctrack/users/migrations/0006_user_stakeholder.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-05-25 15:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('organisations', '0005_auto_20200525_1502'), + ('users', '0005_delete_userprofile'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='stakeholder', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Stakeholder'), + ), + ] diff --git a/ctrack/users/models.py b/ctrack/users/models.py index 8f07b15..052efd6 100644 --- a/ctrack/users/models.py +++ b/ctrack/users/models.py @@ -1,14 +1,28 @@ from django.contrib.auth.models import AbstractUser -from django.db.models import CharField +from django.db import models from django.urls import reverse from django.utils.translation import ugettext_lazy as _ +from ctrack.organisations.models import Stakeholder + class User(AbstractUser): - # First Name and Last Name do not cover name patterns - # around the globe. - name = CharField(_("Name of User"), blank=True, max_length=255) + name = models.CharField(_("Name of User"), blank=True, max_length=255) + stakeholder = models.OneToOneField( + Stakeholder, on_delete=models.CASCADE, null=True, blank=True + ) def get_absolute_url(self): return reverse("users:detail", kwargs={"username": self.username}) + + @property + def is_stakeholder(self): + if self.stakeholder is not None: + return True + else: + return False + + def get_organisation_name(self): + if self.is_stakeholder: + return self.stakeholder.person.organisation.name diff --git a/ctrack/users/stakeholder.py b/ctrack/users/stakeholder.py index d15dd39..a8b4329 100644 --- a/ctrack/users/stakeholder.py +++ b/ctrack/users/stakeholder.py @@ -1,7 +1,3 @@ from django.db import models from ctrack.organisations.models import Person - - -class Stakeholder(models.Model): - person = models.ForeignKey(Person, on_delete=models.CASCADE) diff --git a/ctrack/users/tests/test_functional.py b/ctrack/users/tests/test_functional.py new file mode 100644 index 0000000..5622ab3 --- /dev/null +++ b/ctrack/users/tests/test_functional.py @@ -0,0 +1,124 @@ +""" +Functional tests. Are probably SLOW thanks to using Selenium to load a browser instance. + +The use case being tested here is related to a user being able to log in and hit +the correct page, containing their details. Those details depend on whether they are +a regular user or a stakeholder user. +""" + +import time + +import pytest +from django.contrib.auth.models import Permission + +from ctrack.users.models import User + +pytestmark = pytest.mark.django_db + + +def test_regular_user_can_log_in(browser, live_server): + + # Toss McBride is an OES user. He logs into the system... + User.objects.create_user(username="toss", password="knob") + browser.get(live_server + "/accounts/login") + browser.find_element_by_id("id_login").send_keys("toss") + browser.find_element_by_id("id_password").send_keys("knob") + browser.find_element_by_id("sign_in_button").submit() + time.sleep(1) + current_url = browser.current_url + assert current_url == live_server + "/" + + type_user_message = browser.find_elements_by_tag_name("p") + assert "THIS IS A TEMPLATE FOR A REGULAR USER" in [ + m.text for m in type_user_message + ] + + +def test_stakeholder_can_log_in_and_see_their_home(browser, live_server, stakeholder): + # Toss McBride is an OES user. He logs into the system... + + user = User.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + org = user.stakeholder.person.get_organisation_name() + user.save() + browser.get(live_server + "/accounts/login") + browser.find_element_by_id("id_login").send_keys("toss") + browser.find_element_by_id("id_password").send_keys("knob") + browser.find_element_by_id("sign_in_button").submit() + time.sleep(1) + current_url = browser.current_url + assert current_url == live_server + "/" + + p_tags = browser.find_elements_by_tag_name("p") + h2_tags = browser.find_elements_by_tag_name("h2") + assert "THIS IS A TEMPLATE FOR A STAKEHOLDER USER" in [m.text for m in p_tags] + assert org in [m.text for m in h2_tags] + assert ( + f"{user.stakeholder.person.first_name} {user.stakeholder.person.last_name}" + in [m.text for m in p_tags] + ) + + +def test_stakeholder_can_log_in_but_receieved_permisson_denied_when_off_piste( + browser, live_server, stakeholder +): + user = User.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + browser.get(live_server + "/accounts/login") + browser.find_element_by_id("id_login").send_keys("toss") + browser.find_element_by_id("id_password").send_keys("knob") + browser.find_element_by_id("sign_in_button").submit() + time.sleep(1) + # Try to browser to Organisations list + browser.get(live_server + "/organisations") + assert "Sorry. You do not have permission to view this page." in [ + x.text for x in browser.find_elements_by_tag_name("p") + ] + + +def test_stakeholder_user_with_permissions_can_view_page( + browser, live_server, stakeholder +): + user = User.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + org_list_permission = Permission.objects.get(name="Can view organisation") + + # Add the permission to view an Organisation, which is set on OrganisationListView + assert user.user_permissions.count() == 0 + user.user_permissions.add(org_list_permission) + assert user.user_permissions.count() == 1 + user.save() + + browser.get(live_server + "/accounts/login") + browser.find_element_by_id("id_login").send_keys("toss") + browser.find_element_by_id("id_password").send_keys("knob") + browser.find_element_by_id("sign_in_button").submit() + time.sleep(1) + # Try to browser to Organisations list + browser.get(live_server + "/organisations") + assert "Organisations" in browser.title + + +def test_stakeholder_user_can_see_requisite_subtitles_on_home_page( + browser, live_server, stakeholder +): + user = User.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + browser.get(live_server + "/accounts/login") + browser.find_element_by_id("id_login").send_keys("toss") + browser.find_element_by_id("id_password").send_keys("knob") + browser.find_element_by_id("sign_in_button").submit() + time.sleep(1) + current_url = browser.current_url + assert current_url == live_server + "/" + + # On the other side, he sees some basic details about himself. + assert "ctrack - Department for Transport" in browser.title + + h2 = browser.find_elements_by_tag_name("h2") + assert "Incident Reporting" in [x.text for x in h2] + assert "Audits and Inspections" in [x.text for x in h2] + assert "NIS systems" in [x.text for x in h2] + assert "DfT Engagement" in [x.text for x in h2] diff --git a/ctrack/users/tests/test_models.py b/ctrack/users/tests/test_models.py index 2d45cca..368be34 100644 --- a/ctrack/users/tests/test_models.py +++ b/ctrack/users/tests/test_models.py @@ -1,6 +1,6 @@ import pytest -from ctrack.users.stakeholder import Stakeholder +from ctrack.organisations.models import Stakeholder pytestmark = pytest.mark.django_db @@ -16,10 +16,14 @@ def test_user_is_person_object(user): assert user -def test_stakeholder_model(person): +def test_stakeholder_model(person, user): """ A stakeholder is someone who is part of the regime but also has user access to the the system. """ stakeholder = Stakeholder(person=person) - assert stakeholder + org = person.organisation.name + user.stakeholder = stakeholder + assert user.stakeholder.person.first_name == "Toss" + assert user.is_stakeholder is True + assert user.get_organisation_name() == org diff --git a/ctrack/users/tests/test_views.py b/ctrack/users/tests/test_views.py index 3299cd6..8dc4825 100644 --- a/ctrack/users/tests/test_views.py +++ b/ctrack/users/tests/test_views.py @@ -1,7 +1,9 @@ import pytest -from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission from django.test import RequestFactory +from ctrack.core.views import home_page +from ctrack.organisations.views import OrganisationListView from ctrack.users.models import User from ctrack.users.views import UserDetailView, UserRedirectView, UserUpdateView @@ -44,23 +46,137 @@ class TestUserRedirectView: view.request = request - assert view.get_redirect_url() == f"/users/{user.username}/" + assert view.get_redirect_url() == "/" -def test_profile_view_contains_organisation_information(): - """url: users/username - This is where users are redirected to when they log in and where I want to capture - information about the user - particularly if they are an OES user. +def test_profile_view_contains_organisation_information( + person, user, request_factory, stakeholder +): """ - user = get_user_model().objects.create_user( - username="testy", email="testy@test.com", password="test1020" - ) - factory = RequestFactory() - request = factory.get(f"/users/{user.username}") + This tests the context_data - not the rendered page... We'll do that in the + next test. + """ + org_name = person.organisation.name + user.stakeholder = stakeholder + user.save() + request = request_factory.get(f"/users/{user.username}") + # we have to do the following to simulate logged-in user # Django Advanced Testing Topics request.user = user + + # We pass 'username' rather than 'slug' here because we are setting 'slug_url_kwarg' in our CBV. response = UserDetailView.as_view()(request, username=user.username) + + assert response.status_code == 200 + assert response.context_data["user"].username == user.username + assert response.context_data["user"].is_stakeholder is True + assert response.context_data["user"].stakeholder.person.first_name == "Toss" + + # Two ways of getting the organisaton name + assert ( + response.context_data["user"].stakeholder.person.get_organisation_name() + == org_name + ) + assert response.context_data["user"].get_organisation_name() == org_name + assert response.context_data["user"].stakeholder.person.first_name == "Toss" + + +def test_home_page_h1_tag_with_client(client, django_user_model): + """ + Basic test of HTML from the home page. + """ + django_user_model.objects.create_user(username="toss", password="knob") + client.login(username="toss", password="knob") + response = client.get("/") + assert response.status_code == 200 + assert response.content[:15] == b"<!DOCTYPE html>" + assert b"<title>ctrack - Department for Transport</title>" in response.content + assert b"<h1>Welcome to ctrack - Department for Transport</h1>" in response.content + assert b"</html>" in response.content + + +def test_regular_user_redirected_to_their_template_on_login( + django_user_model, request_factory: RequestFactory +): + """ + When a user logs in without a stakeholder mapping, they get sent to the regular user + template. + """ + user = django_user_model.objects.create_user(username="toss", password="knob") + request = request_factory.get("/") + request.user = user + response = home_page(request) + assert response.status_code == 200 + assert b"<p>THIS IS A TEMPLATE FOR A REGULAR USER</p>" in response.content + + +def test_stakeholder_redirected_to_their_template_on_login( + django_user_model, request_factory: RequestFactory, stakeholder +): + """ + When a user logs in WITH a stakeholder mapping, they get sent to the stakehoder user + template. + """ + user = django_user_model.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + request = request_factory.get("/") + request.user = user + response = home_page(request) + assert response.status_code == 200 + assert b"THIS IS A TEMPLATE FOR A STAKEHOLDER USER" in response.content + + +def test_stakeholder_returns_is_stakeholder( + django_user_model, request_factory, stakeholder +): + user = django_user_model.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + request = request_factory.get("/") + request.user = user + assert request.user.is_stakeholder is True + + +def test_stakeholder_user_is_not_staff(django_user_model, stakeholder): + user = django_user_model.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + assert user.is_staff is False + + +def test_stakeholder_user_gets_301_when_trying_to_access_view_with_perm_set( + django_user_model, client, stakeholder +): + """ + No permissions are set when a regular user is created. This test knows that a suitable + permission is set on the ctrack.organisations.view.OrganisationListView, and therefore we + would expect a redirect/403 persmission denied response when trying to reach it with a + regular user. + """ + user = django_user_model.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + user.save() + client.login(username="toss", password="knob") + response = client.get(path="https://localhost:8000/organisations") + assert ( + response.status_code == 301 + ) # This page redirects to 403.html, hence why its a 301 (I think) + + +@pytest.mark.skip("Explore why this does not pass - it passess in functional style") +def test_staff_user_gets_200_when_trying_to_access_view_with_perm_set( + django_user_model, client, stakeholder +): + user = django_user_model.objects.create_user(username="toss", password="knob") + user.stakeholder = stakeholder + org_list_permission = Permission.objects.get(name="Can view organisation") + assert user.user_permissions.count() == 0 + user.user_permissions.add(org_list_permission) + assert user.has_perm("organisations.view_organisation") + user.save() + logged_in = client.login(username="toss", password="knob") + assert logged_in is True + response = client.get("/organisations") assert response.status_code == 200 - # TODO - work out how we can attach an organisation to the User model - assert False, "This does nothing yet" diff --git a/ctrack/users/views.py b/ctrack/users/views.py index a292191..8e504e4 100644 --- a/ctrack/users/views.py +++ b/ctrack/users/views.py @@ -11,7 +11,13 @@ User = get_user_model() class UserDetailView(LoginRequiredMixin, DetailView): model = User + + # This names the field in the model that contains the slug. Want it to be thise so that is a good + # citizen to be used in a URL slug_field = "username" + + # the name of the URLConf keyword argument that contains the slug. By default, slug_url_kwarg is 'slug'. + # we have to pass 'username' as the argument when testing UserDetailView because of this. slug_url_kwarg = "username" @@ -44,7 +50,11 @@ class UserRedirectView(LoginRequiredMixin, RedirectView): permanent = False def get_redirect_url(self): - return reverse("users:detail", kwargs={"username": self.request.user.username}) + return reverse("core:home") + + +# def get_redirect_url(self): +# return reverse("users:detail", kwargs={"username": self.request.user.username}) user_redirect_view = UserRedirectView.as_view() |