aboutsummaryrefslogtreecommitdiffstats
path: root/ctrack/users
diff options
context:
space:
mode:
authorMatthew Lemon <lemon@matthewlemon.com>2020-05-28 16:12:13 +0100
committerMatthew Lemon <lemon@matthewlemon.com>2020-05-28 16:12:13 +0100
commitacf875c2bca1727306cb70aaa37e6f4595209786 (patch)
tree67390297bcd02b2481f5673629227dfea0884215 /ctrack/users
parent4aaa68109434e04d1103710d675efd075b2f744a (diff)
parent520a9deb13f93c689443c0339b1c96a67f0c15a1 (diff)
Merge branch 'stakeholder-profile'
Diffstat (limited to 'ctrack/users')
-rw-r--r--ctrack/users/admin.py6
-rw-r--r--ctrack/users/migrations/0005_delete_userprofile.py16
-rw-r--r--ctrack/users/migrations/0006_user_stakeholder.py20
-rw-r--r--ctrack/users/models.py22
-rw-r--r--ctrack/users/stakeholder.py4
-rw-r--r--ctrack/users/tests/test_functional.py124
-rw-r--r--ctrack/users/tests/test_models.py10
-rw-r--r--ctrack/users/tests/test_views.py142
-rw-r--r--ctrack/users/views.py12
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()