Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1706] Redesign personal info page #851

Merged
merged 2 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/open_inwoner/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def __init__(self, user, *args, **kwargs):
self.fields["infix"].required = False
self.fields["last_name"].required = True

if user.is_digid_and_brp():
if user.is_digid_user_with_brp:
self.fields["first_name"].disabled = True
self.fields["infix"].disabled = True
self.fields["last_name"].disabled = True
Expand Down
11 changes: 8 additions & 3 deletions src/open_inwoner/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def get_active_notifications(self) -> str:
def require_necessary_fields(self) -> bool:
"""returns whether user needs to fill in necessary fields"""
if (
self.is_digid_and_brp()
self.is_digid_user_with_brp
and self.email
and not self.email.endswith("@example.org")
):
Expand Down Expand Up @@ -427,12 +427,17 @@ def get_plan_contact_new_count(self):
def clear_plan_contact_new_count(self):
PlanContact.objects.filter(user=self).update(notify_new=False)

def is_digid_and_brp(self) -> bool:
@property
def is_digid_user(self) -> bool:
return self.login_type == LoginTypeChoices.digid

@property
def is_digid_user_with_brp(self) -> bool:
"""
Returns whether user is logged in with digid and data has
been requested from haal centraal
"""
return self.login_type == LoginTypeChoices.digid and self.is_prepopulated
return self.is_digid_user and self.is_prepopulated


class Document(models.Model):
Expand Down
11 changes: 9 additions & 2 deletions src/open_inwoner/accounts/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import requests_mock
from django_webtest import WebTest
from furl import furl
from pyquery import PyQuery as PQ

from open_inwoner.configurations.models import SiteConfiguration
from open_inwoner.haalcentraal.tests.mixins import HaalCentraalMixin
Expand Down Expand Up @@ -1340,14 +1341,20 @@ def test_password_change_form_done_custom_template_is_rendered(self):

def test_password_change_button_is_rendered_with_default_login_type(self):
response = self.app.get(reverse("profile:detail"), user=self.user)
self.assertContains(response, _("Wijzig wachtwoord"))

doc = PQ(response.content)
link = doc.find("[aria-label='Wachtwoord']")[0]
self.assertTrue(doc(link).is_("a"))

def test_password_change_button_is_not_rendered_with_digid_login_type(self):
digid_user = UserFactory(
login_type=LoginTypeChoices.digid, email="[email protected]"
)
response = self.app.get(reverse("profile:detail"), user=digid_user)
self.assertNotContains(response, _("Wijzig wachtwoord"))

doc = PQ(response.content)
links = doc.find("[aria-label='Wachtwoord']")
self.assertEqual(len(links), 0)

def test_anonymous_user_is_redirected_to_login_page_if_password_change_is_accessed(
self,
Expand Down
36 changes: 24 additions & 12 deletions src/open_inwoner/accounts/tests/test_profile_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,28 @@ def test_login_required(self):
def test_user_information_profile_page(self):
response = self.app.get(self.url, user=self.user)

self.assertContains(response, self.user.get_full_name())
self.assertContains(response, self.user.first_name)
self.assertContains(response, self.user.last_name)
self.assertContains(response, self.user.infix)
self.assertContains(response, self.user.email)
self.assertContains(response, self.user.phonenumber)
self.assertContains(response, self.user.get_address())
self.assertContains(response, self.user.street)
self.assertContains(response, self.user.housenumber)
self.assertContains(response, self.user.city)

def test_get_empty_profile_page(self):
response = self.app.get(self.url, user=self.user)

self.assertEquals(response.status_code, 200)
self.assertContains(response, _("U heeft nog geen contacten."))
self.assertContains(response, "0 acties staan open.")
self.assertContains(response, _("U heeft nog geen contacten"))
self.assertContains(response, "0 acties staan open")
self.assertNotContains(response, reverse("products:questionnaire_list"))
self.assertContains(response, _("messages, plans"))

def test_get_filled_profile_page(self):
ActionFactory(created_by=self.user)
contact = UserFactory()
self.user.user_contacts.add(contact)
category = CategoryFactory()
CategoryFactory()
QuestionnaireStepFactory(published=True)

response = self.app.get(self.url, user=self.user)
Expand All @@ -87,14 +90,13 @@ def test_get_filled_profile_page(self):
response,
f"{contact.first_name} ({contact.get_contact_type_display()})",
)
self.assertContains(response, "1 acties staan open.")
self.assertContains(response, "1 acties staan open")
self.assertContains(response, reverse("products:questionnaire_list"))

def test_only_open_actions(self):
action = ActionFactory(created_by=self.user, status=StatusChoices.closed)
ActionFactory(created_by=self.user, status=StatusChoices.closed)
response = self.app.get(self.url, user=self.user)
self.assertEquals(response.status_code, 200)
self.assertContains(response, "0 acties staan open.")
self.assertIn("0 acties staan open", response)

def test_mydata_shown_with_digid_and_brp(self):
user = UserFactory(
Expand Down Expand Up @@ -123,8 +125,18 @@ def test_mydata_not_shown_without_digid(self):
self.assertNotContains(response, _("My details"))

def test_active_user_notifications_are_shown(self):
response = self.app.get(self.url, user=self.user)
self.assertContains(response, _("messages, plans"))
user = UserFactory(
bsn="999993847",
first_name="name",
last_name="surname",
is_prepopulated=False,
login_type=LoginTypeChoices.digid,
messages_notifications=True,
plans_notifications=True,
cases_notifications=False,
)
response = self.app.get(self.url, user=user)
self.assertContains(response, _("Mijn Berichten, Samenwerken"))

def test_expected_message_is_shown_when_all_notifications_disabled(self):
self.user.cases_notifications = False
Expand Down
78 changes: 51 additions & 27 deletions src/open_inwoner/accounts/views/profile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import date, datetime
from typing import Generator, Union

from django.conf import settings
from django.contrib import messages
Expand Down Expand Up @@ -48,51 +49,74 @@ class MyProfileView(
def crumbs(self):
return [(_("Mijn profiel"), reverse("profile:detail"))]

@staticmethod
def stringify(
items: list, string_func: callable, lump: bool = False
) -> Union[Generator, str]:
"""
Create string representation(s) of `items` for display

:param string_func: the function used to stringify elements in `items`
:param lump: if `True`, `string_func` is applied to `items` collectively
:returns: a `Generator` of strings representing elements in `items`, or a
`str` representing `items` as a whole, depending on whether `lump` is
`True`
"""
if lump:
return string_func(items)
return (string_func(item) for item in items)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
today = date.today()

context["anchors"] = [
("#title", _("Persoonlijke gegevens")),
("#overview", _("Persoonlijk overzicht")),
("#personal-info", _("Persoonlijke gegevens")),
("#notifications", _("Voorkeuren voor meldingen")),
("#overview", _("Overzicht")),
]

user_files = user.get_all_files()

# List of names of 'mentor' users that are a contact of me
mentor_contacts = [
c.get_full_name()
for c in user.user_contacts.filter(
contact_type=ContactTypeChoices.begeleider
)
]

# Mentor contacts + names for display
mentor_contacts = user.user_contacts.filter(
contact_type=ContactTypeChoices.begeleider
)
context["mentor_contacts"] = mentor_contacts
context["mentor_contact_names"] = self.stringify(
mentor_contacts,
string_func=lambda m: m.get_full_name,
)

# Regular contacts + names for display
contacts = user.get_active_contacts()
context["contact_names"] = self.stringify(
contacts,
string_func=lambda c: f"{c.first_name} ({c.get_contact_type_display()})",
)

# Actions
actions = (
Action.objects.visible()
.connected(self.request.user)
.filter(status=StatusChoices.open)
)
context["action_text"] = self.stringify(
actions,
string_func=lambda actions: f"{actions.count()} acties staan open",
lump=True,
)

context["next_action"] = (
Action.objects.visible()
.connected(self.request.user)
.filter(end_date__gte=today, status=StatusChoices.open)
.order_by("end_date")
.first()
)
context["files"] = user_files
context["action_text"] = _(
f"{Action.objects.visible().connected(self.request.user).filter(status=StatusChoices.open).count()} acties staan open."
)
contacts = user.get_active_contacts()
# Invited contacts
contact_names = [
f"{contact.first_name} ({contact.get_contact_type_display()})"
for contact in contacts[:3]
]

if contacts.count() > 0:
context[
"contact_text"
] = f"{', '.join(contact_names)}{'...' if contacts.count() > 3 else ''}"
else:
context["contact_text"] = _("U heeft nog geen contacten.")
context["files"] = user_files

context["questionnaire_exists"] = QuestionnaireStep.objects.filter(
published=True
Expand Down Expand Up @@ -183,7 +207,7 @@ def update_klant_api(self, user_form_data: dict):

def get_form_class(self):
user = self.request.user
if user.is_digid_and_brp():
if user.is_digid_user_with_brp:
return BrpUserForm
return super().get_form_class()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

{% endif %}

<!-- dynamic anchor item for products -->
{# dynamic anchor item for products #}
<div class="anchor-menu__start-inquiry">
{% if not product.has_cta_tag %}
{% if product.form %}
Expand Down
8 changes: 4 additions & 4 deletions src/open_inwoner/configurations/tests/test_show_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ def test_default_enabled(self):
def test_when_enabled_and_user_is_logged_in(self):
response = self.app.get(self.profile_url, user=self.user)

links = response.pyquery(".personal-overview")
self.assertNotEqual(links.find(".personal-overview__actions"), [])
links = response.pyquery(".profile-section")
self.assertNotEqual(links.find("#profile-section-actions"), [])
self.assertNotEqual(links.find(f'a[href="{self.actions_list_url}"]'), [])

def test_when_disabled_and_user_is_logged_in(self):
self.profile_config.actions = False
self.profile_config.save()
response = self.app.get(self.profile_url, user=self.user)

links = response.pyquery(".personal-overview")
self.assertEqual(links.find(".personal-overview__actions"), [])
links = response.pyquery(".profile-section")
self.assertEqual(links.find("#profile-section-actions"), [])
self.assertEqual(links.find(f'a[href="{self.actions_list_url}"]'), [])

def test_action_pages_show_404_when_disabled(self):
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/scss/components/List/_List.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
overflow-y: auto;

.case-list,
.contactmomenten-list {
.contactmomenten-list .profile-list {
text-decoration: none;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.personal-overview {
.profile-section {
h2,
.h2 {
margin: var(--row-height) 0 0 0;
margin: calc(var(--row-height) * 1.5) 0 0 0;
}
}
52 changes: 48 additions & 4 deletions src/open_inwoner/scss/components/Table/Tabled.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
display: flex;
flex-direction: column;

.h2 {
&.title--extra-padding {
padding-bottom: var(--spacing-large);
}
}

&__key {
color: var(--color-gray-90);
}
&__value {
color: var(--color-gray-dark);
}

&__title {
padding-bottom: var(--spacing-extra-large);
}

&.tabled--tiny-end {
.tabled__header,
.tabled__row {
Expand All @@ -24,6 +41,24 @@
margin: var(--row-height) 0 0 0;
}

.card {
.h4 .link {
color: var(--color-black);
}

.profile__link {
text-decoration: none;
}

.list-item {
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}

.tabled__header {
display: grid;
grid-template-columns: 1fr 1fr;
Expand All @@ -41,11 +76,23 @@
}
}

.tabled__section {
padding-bottom: var(--spacing-extra-large);

.title {
border-bottom: 1px solid var(--color-gray-light);
}
}

.tabled__row {
display: grid;
grid-template-columns: 1fr 1fr;
border-bottom: 1px solid var(--color-gray-light);

&--blank {
border-bottom-width: 0;
}

@media (min-width: 768px) {
grid-template-columns: 1fr 1fr 125px 125px;
}
Expand All @@ -54,10 +101,7 @@
.tabled__item {
font-family: var(--font-family-body);
padding: var(--spacing-medium);

&--bold {
font-weight: bold;
}
padding-left: 0;

&--right {
@media (min-width: 768px) {
Expand Down
Loading
Loading