Skip to content

Commit 57d15fe

Browse files
committed
[#1657] add option to hide categories from anonymous users
- added model field (bool) for hiding categories - added `LoginMaybeRequiredMixin` to categories views in order to conditionally restrict access to authenticated users - refactored rendering of nav bar (removed inclusion tags)
1 parent 141896e commit 57d15fe

File tree

12 files changed

+240
-68
lines changed

12 files changed

+240
-68
lines changed

src/open_inwoner/components/templates/components/Header/Header.html

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{% get_solo "configurations.SiteConfiguration" as config %}
44

55
{% accessibility_header request=request %}
6+
67
<header class="header" aria-label="Navigatie header">
78
<div class="header__container">
89
<div class="header__menu">
@@ -45,17 +46,17 @@
4546
{% link text=link_text href="/" icon="grid_view" icon_position="before" %}
4647
</li>
4748

48-
{% if cms_apps.products and categories %}
49+
{% if cms_apps.products %}
4950
<li class="primary-navigation__list-item dropdown-nav__toggle">
5051
<a href="#" class="link link--toggle link--icon link--icon-position-before" aria-label="{% trans "Onderwerpen" %}" title="{% trans "Onderwerpen" %}" aria-expanded="false">
5152
<span >{% trans "Onderwerpen" %}</span>
5253
<span aria-hidden="true" class="material-icons-outlined ">description</span>
5354
{% icon icon="expand_more" icon_position="after" icon_outlined=True %}
5455
</a>
5556

56-
{% if categories %}
57+
{% if menu_categories %}
5758
<ul class="primary-navigation__list subpage-list">
58-
{% for category in categories %}
59+
{% for category in menu_categories %}
5960
<li class="primary-navigation__list-item">
6061
{% url 'products:category_detail' slug=category.slug as category_href %}
6162
{% link text=category.name href=category_href %}
@@ -103,7 +104,7 @@
103104
{% firstof config.logo.default_alt_text config.name as logo_alt_text %}
104105
<div class="logo__desktop">{% logo src=config.logo.file.url alt="Homepage "|add:logo_alt_text svg_height=75 %}</div>
105106

106-
{% primary_navigation categories=categories request=request has_general_faq_questions=has_general_faq_questions cms_apps=cms_apps show_plans=show_plans %}
107+
{% include "components/Header/PrimaryNavigation.html" %}
107108

108109
{% if cms_apps.products %}
109110
<nav class="header__actions" aria-label="Zoek navigatie desktop">
@@ -115,7 +116,7 @@
115116
</nav>
116117
{% endif %}
117118

118-
{% navigation_authenticated categories=categories request=request has_general_faq_questions=has_general_faq_questions %}
119+
{% include "components/Header/NavigationAuthenticated.html" %}
119120
</div>
120121
</header>
121122

src/open_inwoner/components/templates/components/Header/PrimaryNavigation.html

+12-13
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,21 @@
44

55
<nav class="primary-navigation primary-navigation__main" aria-label="Hoofd navigatie">
66
<ul class="primary-navigation__list">
7-
87
{% if cms_apps.products and categories %}
9-
<li class="primary-navigation__list-item">
10-
{% button text=_('Onderwerpen') type="button" icon="expand_more" icon_position="after" icon_outlined=True transparent=True extra_classes="primary-navigation--toggle" %}
8+
{% if request.user.is_authenticated or not config.hide_categories_from_anonymous_users %}
9+
<li class="primary-navigation__list-item">
10+
{% button text=_('Onderwerpen') type="button" icon="expand_more" icon_position="after" icon_outlined=True transparent=True extra_classes="primary-navigation--toggle" %}
1111

12-
{% if categories %}
13-
<ul class="primary-navigation__list subpage-list">
14-
{% for category in categories %}
15-
<li class="primary-navigation__list-item">
16-
{% url 'products:category_detail' slug=category.slug as category_href %}
17-
{% link text=category.name href=category_href %}
18-
</li>
19-
{% endfor %}
20-
</ul>
12+
<ul class="primary-navigation__list subpage-list">
13+
{% for category in categories %}
14+
<li class="primary-navigation__list-item">
15+
{% url 'products:category_detail' slug=category.slug as category_href %}
16+
{% link text=category.name href=category_href %}
17+
</li>
18+
{% endfor %}
19+
</ul>
20+
</li>
2121
{% endif %}
22-
</li>
2322
{% endif %}
2423
</ul>
2524
</nav>

src/open_inwoner/components/templatetags/header_tags.py

-46
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from django import template
22

33
from open_inwoner.configurations.models import SiteConfiguration
4-
from open_inwoner.questionnaire.models import QuestionnaireStep
54

65
register = template.Library()
76

@@ -38,51 +37,6 @@ def header(categories, request, **kwargs):
3837
+ request: Request | the django request object.
3938
- has_general_faq_questions: boolean | If the FAQ menu item should be shown.
4039
"""
41-
return {
42-
**kwargs,
43-
"categories": categories,
44-
"request": request,
45-
}
46-
47-
48-
@register.inclusion_tag("components/Header/PrimaryNavigation.html")
49-
def primary_navigation(categories, request, **kwargs):
50-
"""
51-
Displaying the primary navigation
52-
53-
Usage:
54-
{% primary_navigation categories=Category.objects.all request=request %}
55-
56-
Variables:
57-
+ categories: Category[] | The categories that should be displayed in the theme dropdown.
58-
+ request: Request | The django request object.
59-
+ questionnaire: QuestionnaireStep | The default QuestionnaireStep, if any.
60-
- has_general_faq_questions: boolean | If the FAQ menu item should be shown.
61-
- show_plans: boolean | If the Plan item should be shown.
62-
"""
63-
64-
return {
65-
**kwargs,
66-
"categories": categories,
67-
"request": request,
68-
}
69-
70-
71-
@register.inclusion_tag("components/Header/NavigationAuthenticated.html")
72-
def navigation_authenticated(categories, request, **kwargs):
73-
"""
74-
Displaying the desktop navigation when user is authenticated
75-
76-
Usage:
77-
{% navigation_authenticated categories=Category.objects.all request=request %}
78-
79-
Variables:
80-
+ categories: Category[] | The categories that should be displayed in the theme dropdown.
81-
+ request: Request | The django request object.
82-
+ questionnaire: QuestionnaireStep | The default QuestionnaireStep, if any.
83-
- has_general_faq_questions: boolean | If the FAQ menu item should be shown.
84-
- show_plans: boolean | If the Plan item should be shown.
85-
"""
8640

8741
return {
8842
**kwargs,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from django.test import TestCase
2+
3+
from pyquery import PyQuery
4+
5+
from open_inwoner.accounts.tests.factories import UserFactory
6+
from open_inwoner.cms.products.cms_apps import ProductsApphook
7+
from open_inwoner.cms.tests import cms_tools
8+
from open_inwoner.cms.tests.cms_tools import create_apphook_page
9+
from open_inwoner.configurations.models import SiteConfiguration
10+
from open_inwoner.pdc.tests.factories import CategoryFactory
11+
12+
13+
class HeaderTest(TestCase):
14+
@classmethod
15+
def setUpTestData(cls):
16+
cls.user = UserFactory()
17+
cls.user.set_password("12345")
18+
cls.user.email = "[email protected]"
19+
cls.user.save()
20+
21+
cms_tools.create_homepage()
22+
23+
# PrimaryNavigation.html requires apphook + categories
24+
create_apphook_page(ProductsApphook)
25+
cls.published1 = CategoryFactory(
26+
path="0001", name="First one", slug="first-one"
27+
)
28+
cls.published2 = CategoryFactory(
29+
path="0002", name="Second one", slug="second-one"
30+
)
31+
32+
def test_categories_hidden_from_anonymous_users(self):
33+
config = SiteConfiguration.get_solo()
34+
config.hide_categories_for_anonymous_users = True
35+
config.save()
36+
37+
response = self.client.get("/")
38+
39+
doc = PyQuery(response.content)
40+
41+
categories = doc.find("[title='Onderwerpen']")
42+
self.assertEqual(len(categories), 0)
43+
44+
def test_categories_not_hidden_from_anonymous_users(self):
45+
config = SiteConfiguration.get_solo()
46+
config.hide_categories_for_anonymous_users = False
47+
config.save()
48+
49+
response = self.client.get("/")
50+
51+
doc = PyQuery(response.content)
52+
53+
categories = doc.find("[title='Onderwerpen']")
54+
self.assertEqual(len(categories), 2)
55+
self.assertEqual(categories[0].tag, "a")
56+
self.assertEqual(categories[1].tag, "button")

src/open_inwoner/configurations/admin.py

+4
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ class SiteConfigurarionAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
189189
),
190190
},
191191
),
192+
(
193+
_("Display options for anonymous users"),
194+
{"fields": ("hide_categories_from_anonymous_users",)},
195+
),
192196
)
193197
inlines = [SiteConfigurationPageInline]
194198
form = SiteConfigurarionAdminForm
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 3.2.15 on 2023-08-15 12:53
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("configurations", "0046_siteconfiguration_cookie_consent"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="siteconfiguration",
15+
name="hide_categories_from_anonymous_users",
16+
field=models.BooleanField(
17+
default=False,
18+
help_text="If checked, categories will be hidden for users who are not logged in.",
19+
verbose_name="Hide categories for anonymouns users",
20+
),
21+
),
22+
]

src/open_inwoner/configurations/models.py

+7
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,13 @@ class SiteConfiguration(SingletonModel):
445445
default=True,
446446
help_text=_("Whether file sharing via the messages is allowed or not"),
447447
)
448+
hide_categories_from_anonymous_users = models.BooleanField(
449+
verbose_name=_("Hide categories from anonymouns users"),
450+
default=False,
451+
help_text=_(
452+
"If checked, categories will be hidden from users who are not logged in."
453+
),
454+
)
448455

449456
class Meta:
450457
verbose_name = _("Site Configuration")
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from django.test import TestCase, override_settings
2+
from django.urls import reverse
3+
4+
from open_inwoner.accounts.tests.factories import UserFactory
5+
from open_inwoner.configurations.models import SiteConfiguration
6+
7+
from .factories import CategoryFactory
8+
9+
10+
@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls")
11+
class TestCategoryListView(TestCase):
12+
@classmethod
13+
def setUpTestData(cls):
14+
cls.user = UserFactory()
15+
cls.user.set_password("12345")
16+
cls.user.email = "[email protected]"
17+
cls.user.save()
18+
19+
def test_access_restricted(self):
20+
config = SiteConfiguration.get_solo()
21+
config.display_categories_for_anonymous_users = False
22+
config.save()
23+
24+
url = reverse("products:category_list")
25+
26+
# request with anonymous user
27+
response = self.client.get(url)
28+
29+
self.assertEqual(response.status_code, 302)
30+
self.assertEqual(response.url, "/accounts/login/?next=/products/")
31+
32+
# request with user logged in
33+
self.client.login(email=self.user.email, password="12345")
34+
35+
response = self.client.get(url)
36+
37+
self.assertEqual(response.status_code, 200)
38+
39+
def test_access_not_restricted(self):
40+
config = SiteConfiguration.get_solo()
41+
config.display_categories_for_anonymous_users = True
42+
config.save()
43+
44+
url = reverse("products:category_list")
45+
46+
# request with anonymous user
47+
response = self.client.get(url)
48+
49+
self.assertEqual(response.status_code, 200)
50+
51+
52+
@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls")
53+
class TestCategoryDetailView(TestCase):
54+
@classmethod
55+
def setUpTestData(cls):
56+
cls.user = UserFactory()
57+
cls.user.set_password("12345")
58+
cls.user.email = "[email protected]"
59+
cls.user.save()
60+
61+
cls.category = CategoryFactory.create(name="test cat")
62+
63+
def test_access_restricted(self):
64+
config = SiteConfiguration.get_solo()
65+
config.display_categories_for_anonymous_users = False
66+
config.save()
67+
68+
url = reverse("products:category_detail", kwargs={"slug": self.category.slug})
69+
70+
# request with anonymous user
71+
response = self.client.get(url)
72+
73+
self.assertEqual(response.status_code, 302)
74+
self.assertEqual(response.url, "/accounts/login/?next=/products/test-cat/")
75+
76+
# request with user logged in
77+
self.client.login(email=self.user.email, password="12345")
78+
79+
response = self.client.get(url)
80+
81+
self.assertEqual(response.status_code, 200)
82+
83+
def test_access_not_restricted(self):
84+
config = SiteConfiguration.get_solo()
85+
config.display_categories_for_anonymous_users = True
86+
config.save()
87+
88+
url = reverse("products:category_detail", kwargs={"slug": self.category.slug})
89+
90+
# request with anonymous user
91+
response = self.client.get(url)
92+
93+
self.assertEqual(response.status_code, 200)

src/open_inwoner/pdc/views.py

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
from open_inwoner.configurations.models import SiteConfiguration
1111
from open_inwoner.pdc.models.product import ProductCondition
12-
from open_inwoner.plans.models import Plan
1312
from open_inwoner.questionnaire.models import QuestionnaireStep
13+
from open_inwoner.utils.views import LoginMaybeRequiredMixin
1414

1515
from ..utils.views import CommonPageMixin
1616
from .choices import YesNo
@@ -112,7 +112,9 @@ def get_context_data(self, **kwargs):
112112
return super().get_context_data(**kwargs)
113113

114114

115-
class CategoryListView(CommonPageMixin, ListBreadcrumbMixin, ListView):
115+
class CategoryListView(
116+
LoginMaybeRequiredMixin, CommonPageMixin, ListBreadcrumbMixin, ListView
117+
):
116118
template_name = "pages/category/list.html"
117119
model = Category
118120

@@ -124,9 +126,21 @@ def crumbs(self):
124126
config = SiteConfiguration.get_solo()
125127
return [(config.theme_title, reverse("products:category_list"))]
126128

129+
@property
130+
def display_restricted(self):
131+
config = SiteConfiguration.get_solo()
132+
import pdb
133+
134+
pdb.set_trace()
135+
return config.hide_categories_from_anonymous_users is True
136+
127137

128138
class CategoryDetailView(
129-
CommonPageMixin, BaseBreadcrumbMixin, CategoryBreadcrumbMixin, DetailView
139+
LoginMaybeRequiredMixin,
140+
CommonPageMixin,
141+
BaseBreadcrumbMixin,
142+
CategoryBreadcrumbMixin,
143+
DetailView,
130144
):
131145
template_name = "pages/category/detail.html"
132146
model = Category
@@ -166,6 +180,11 @@ def get_context_data(self, **kwargs):
166180
def get_breadcrumb_name(self):
167181
return self.object.name
168182

183+
@property
184+
def display_restricted(self):
185+
config = SiteConfiguration.get_solo()
186+
return config.hide_categories_from_anonymous_users is True
187+
169188

170189
class ProductDetailView(
171190
CommonPageMixin, BaseBreadcrumbMixin, CategoryBreadcrumbMixin, DetailView

0 commit comments

Comments
 (0)