From ab4e5ecf12ae44076e6b489f99bf8537085541d2 Mon Sep 17 00:00:00 2001
From: Saurabh Kumar <theskumar@users.noreply.github.com>
Date: Wed, 1 May 2024 11:43:15 +0530
Subject: [PATCH] Refractor primary nav and dashboard, hide projects info if
 not enabled (#3898)

Also, refractor and allow for importing permission functions from
anywhere and not just limit to be imported from the
`hypha.users.decorators` module

This PR also doesn't display project info on the dashboard if the
projects is not enabled

For the stats block at the top of the dashboard, if the projects is not
abled displays the count of flagged submissions and the count of
previously reviewed submissions

Simplify the nav generator logic.
---
 .../templates/dashboard/dashboard.html        | 46 +++++++----
 hypha/apply/utils/templatetags/apply_tags.py  |  7 ++
 hypha/core/navigation.py                      | 81 ++++++++++++++-----
 .../core/navigation/primarynav-apply.html     |  5 +-
 hypha/core/templatetags/applynav_tags.py      | 40 ---------
 5 files changed, 103 insertions(+), 76 deletions(-)
 delete mode 100644 hypha/core/templatetags/applynav_tags.py

diff --git a/hypha/apply/dashboard/templates/dashboard/dashboard.html b/hypha/apply/dashboard/templates/dashboard/dashboard.html
index 6f228a04e..512e79bb3 100644
--- a/hypha/apply/dashboard/templates/dashboard/dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/dashboard.html
@@ -36,20 +36,35 @@
                         <div class="stat-block__view">{% trans "View" %}</div>
                     {% endif %}
                 </a>
-                <a href="#active-projects" class="stat-block__item border shadow-sm">
-                    <p class="stat-block__number">{{ projects.count }}</p>
-                    <p class="stat-block__text">{% trans "Live projects under your management" %}</p>
-                    {% if projects.count %}
-                        <div class="stat-block__view">{% trans "View" %}</div>
+                {% if PROJECTS_ENABLED %}
+                    <a href="#active-projects" class="stat-block__item border shadow-sm">
+                        <p class="stat-block__number">{{ projects.count }}</p>
+                        <p class="stat-block__text">{% trans "Live projects under your management" %}</p>
+                        {% if projects.count %}
+                            <div class="stat-block__view">{% trans "View" %}</div>
+                        {% endif %}
+                    </a>
+                    <a href="#active-invoices" class="stat-block__item border shadow-sm">
+                        <p class="stat-block__number">{{ active_invoices.count }}</p>
+                        <p class="stat-block__text">{% trans "Requests for invoices requiring your attention" %}</p>
+                        {% if active_invoices.count %}
+                            <div class="stat-block__view">{% trans "View" %}</div>
+                        {% endif %}
+                    </a>
+                {% else %}
+                    {% if my_flagged.table.data %}
+                        <a href="#submissions-flagged" class="stat-block__item border shadow-sm">
+                            <p class="stat-block__number">{{ my_flagged.table.rows|length }}</p>
+                            <p class="stat-block__text">Your flagged submissions</p>
+                        </a>
                     {% endif %}
-                </a>
-                <a href="#active-invoices" class="stat-block__item border shadow-sm">
-                    <p class="stat-block__number">{{ active_invoices.count }}</p>
-                    <p class="stat-block__text">{% trans "Requests for invoices requiring your attention" %}</p>
-                    {% if active_invoices.count %}
-                        <div class="stat-block__view">{% trans "View" %}</div>
+                    {% if my_reviewed.table.data %}
+                        <a href="#my-review" class="stat-block__item border shadow-sm">
+                            <p class="stat-block__number">{{ my_reviewed.table.rows|length }}</p>
+                            <p class="stat-block__text">Your previous reviews</p>
+                        </a>
                     {% endif %}
-                </a>
+                {% endif %}
             </div>
         </div>
 
@@ -67,14 +82,14 @@
             {% include "funds/includes/round-block.html" with can_export=can_export closed_rounds=rounds.closed open_rounds=rounds.open title="Your rounds and labs" page_type='dashboard' %}
         {% endif %}
 
-        {% if paf_for_review.count %}
+        {% if PROJECTS_ENABLED and paf_for_review.count %}
             <div id="paf_for_review" class="wrapper wrapper--bottom-space">
                 <h4 class="heading heading--normal">{% trans "PAFs for review" %}</h4>
                 {% render_table paf_for_review.table %}
             </div>
         {% endif %}
 
-        {% if projects.table.data %}
+        {% if PROJECTS_ENABLED and projects.table.data %}
             <div id="active-projects" class="wrapper wrapper--bottom-space">
                 {% trans "Your projects" as project_heading %}
                 {% include "funds/includes/table_filter_and_search.html" with filter=projects.filterset filter_action=projects.url search_term=search_term search_action=projects.url search_placeholder="projects" use_search=True use_batch_actions=False heading="Your projects" %}
@@ -85,11 +100,10 @@
                         <a href="{{ projects.url }}?lead={{ request.user.pk }}">{% trans "Show all" %}</a>
                     </div>
                 {% endif %}
-
             </div>
         {% endif %}
 
-        {% if active_invoices.count %}
+        {% if PROJECTS_ENABLED and active_invoices.count %}
             <div id="active-invoices" class="wrapper wrapper--bottom-space">
                 <h4 class="heading heading--normal">{% trans "Active Invoices" %}</h4>
                 {% render_table active_invoices.table %}
diff --git a/hypha/apply/utils/templatetags/apply_tags.py b/hypha/apply/utils/templatetags/apply_tags.py
index 06b19b841..726f21037 100644
--- a/hypha/apply/utils/templatetags/apply_tags.py
+++ b/hypha/apply/utils/templatetags/apply_tags.py
@@ -3,6 +3,8 @@ from django import template
 from django.conf import settings
 from django.template.defaultfilters import stringfilter
 
+from hypha.core.navigation import get_primary_navigation_items
+
 register = template.Library()
 
 
@@ -38,3 +40,8 @@ def truncatechars_middle(value, arg):
         return value
     else:
         return "{}...{}".format(value[: ln // 2], value[-((ln + 1) // 2) :])
+
+
+@register.simple_tag
+def primary_navigation_items(user):
+    return get_primary_navigation_items(user)
diff --git a/hypha/core/navigation.py b/hypha/core/navigation.py
index 2e77ea268..d951d782e 100644
--- a/hypha/core/navigation.py
+++ b/hypha/core/navigation.py
@@ -1,43 +1,48 @@
+import copy
+import functools
+import importlib
+
 from django.conf import settings
+from django.core.exceptions import PermissionDenied
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 
-nav_items = [
+DEFAULT_NAV_ITEMS = [
     {
         "title": _("My Dashboard"),
         "url": reverse_lazy("dashboard:dashboard"),
-        "permission_method": "has_dashboard_access",
+        "permission_method": "hypha.apply.users.decorators.has_dashboard_access",
     },
     {
         "title": _("Submissions"),
         # kind of basic url to figure out active tab
         "url": reverse_lazy("apply:submissions:overview"),
-        "permission_method": "is_apply_staff_or_reviewer_required",
+        "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_reviewer_required",
         "sub_items": [
             {
                 "title": _("All Submissions"),
                 "url": reverse_lazy("apply:submissions:list"),
-                "permission_method": "is_apply_staff_or_reviewer_required",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_reviewer_required",
             },
             {
                 "title": _("Staff Assignments"),
                 "url": reverse_lazy("apply:submissions:staff_assignments"),
-                "permission_method": "is_apply_staff",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff",
             },
             {
                 "title": _("Reviews"),
                 "url": reverse_lazy("apply:submissions:reviewer_leaderboard"),
-                "permission_method": "is_apply_staff",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff",
             },
             {
                 "title": _("Results"),
                 "url": reverse_lazy("apply:submissions:result"),
-                "permission_method": "is_apply_staff",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff",
             },
             {
                 "title": _("Staff flagged"),
                 "url": reverse_lazy("apply:submissions:staff_flagged"),
-                "permission_method": "is_apply_staff",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff",
             },
         ],
     },
@@ -45,33 +50,73 @@ nav_items = [
         "title": _("Projects"),
         # kind of basic url to figure out active tab
         "url": reverse_lazy("apply:projects:overview"),
-        "permission_method": "is_apply_staff_or_finance_or_contracting",
+        "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance_or_contracting",
         "sub_items": [
             {
                 "title": _("All Projects"),
                 "url": reverse_lazy("apply:projects:all"),
-                "permission_method": "is_apply_staff_or_finance_or_contracting",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance_or_contracting",
             },
             {
                 "title": _("Invoices"),
                 "url": reverse_lazy("apply:projects:invoices"),
-                "permission_method": "is_apply_staff_or_finance",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance",
             },
             {
                 "title": _("Reports"),
                 "url": reverse_lazy("apply:projects:reports:all"),
-                "permission_method": "is_apply_staff_or_finance",
+                "permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance",
             },
         ],
     },
 ]
 
 
-if settings.APPLY_NAV_MENU_ITEMS:
-    nav_items = settings.APPLY_NAV_MENU_ITEMS
+def _check_permission(user, method: str) -> bool:
+    """Resolve the method path and check if the user has permission.
+
+    Args:
+        user: user object
+        method: import path to the method to check permission
+
+    Returns:
+        bool: True if user has permission, False otherwise
+    """
+    module = importlib.import_module(method.rsplit(".", 1)[0])
+    method = method.rsplit(".", 1)[1]
+    try:
+        return getattr(module, method)(user)
+    except PermissionDenied:
+        return False
+
+
+@functools.cache
+def get_primary_navigation_items(user):
+    """Get the primary navigation items based on user permissions."""
+    original_nav_items = copy.deepcopy(
+        settings.APPLY_NAV_MENU_ITEMS or DEFAULT_NAV_ITEMS
+    )
+
+    nav_items = []
+
+    for item in original_nav_items:
+        nav_item = item.copy()
+
+        if item["title"] == "Projects" and not settings.PROJECTS_ENABLED:
+            continue
+
+        if item["title"] == "Submissions" and settings.APPLY_NAV_SUBMISSIONS_ITEMS:
+            nav_item["sub_items"] = settings.APPLY_NAV_SUBMISSIONS_ITEMS
 
-if settings.APPLY_NAV_SUBMISSIONS_ITEMS:
-    nav_items[1]["sub_items"] = settings.APPLY_NAV_SUBMISSIONS_ITEMS
+        if not _check_permission(user, nav_item["permission_method"]):
+            continue
+        if sub_items := nav_item.get("sub_items"):
+            nav_item["sub_items"] = list(
+                filter(
+                    lambda x: _check_permission(user, x["permission_method"]),
+                    sub_items,
+                )
+            )
+        nav_items.append(nav_item)
 
-if settings.APPLY_NAV_PROJECTS_ITEMS:
-    nav_items[2]["sub_items"] = settings.APPLY_NAV_PROJECTS_ITEMS
+    return nav_items
diff --git a/hypha/core/templates/core/navigation/primarynav-apply.html b/hypha/core/templates/core/navigation/primarynav-apply.html
index 176b4e8f1..e7fec3ba7 100644
--- a/hypha/core/templates/core/navigation/primarynav-apply.html
+++ b/hypha/core/templates/core/navigation/primarynav-apply.html
@@ -1,8 +1,9 @@
-{% load i18n applynav_tags heroicons %}
+{% load i18n apply_tags heroicons %}
+
 {% if request.user.is_authenticated %}
     <nav role="navigation" aria-label="Primary" class="w-full">
         <ul class="nav nav--primary" role="menubar">
-            {% apply_nav_items request.user as nav_items %}
+            {% primary_navigation_items request.user as nav_items %}
             {% for item in nav_items %}
                 <li class="nav__item"
                     role="presentation"
diff --git a/hypha/core/templatetags/applynav_tags.py b/hypha/core/templatetags/applynav_tags.py
deleted file mode 100644
index 64e6fe0b6..000000000
--- a/hypha/core/templatetags/applynav_tags.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import copy
-
-from django import template
-from django.core.exceptions import PermissionDenied
-
-from hypha.apply.users import decorators
-
-from ..navigation import nav_items
-
-register = template.Library()
-
-
-def has_permission(user, method):
-    try:
-        if getattr(decorators, method)(user):
-            return True
-        return False
-    except PermissionDenied:
-        return False
-    except Exception:
-        # just to handle unknown exceptions
-        return False
-
-
-@register.simple_tag
-def apply_nav_items(user):
-    temp_nav = copy.deepcopy(nav_items)
-    item_count = 0
-    for item in nav_items:
-        item_count = +1
-        removed = False
-        if not has_permission(user, item["permission_method"]):
-            temp_nav.remove(item)
-            removed = True
-            item_count = -1
-        if not removed and "sub_items" in item.keys():
-            for sub_item in item["sub_items"]:
-                if not has_permission(user, sub_item["permission_method"]):
-                    temp_nav[item_count]["sub_items"].remove(sub_item)
-    return temp_nav
-- 
GitLab