-
+{% has_perms_for_tmg_role as may_access_page %}
+{% if may_access_page %}
+
+
+ {% if not results %}
+
{{ empty_message }}
+ {% endif %}
+ {% has_perms_for_tmg_role as may_access_page %}
+ {% if may_access_page %}
+ {% for result in results %}
+
+
{{ forloop.counter }} {{ result.subject_identifier }} {% copy_string_to_clipboard_button result.subject_identifier %} {{ utc_date|timeuntil:result.report_datetime.date }} ago
+
+
+
-
- {% render_tmg_panel action_item=result change=True by_user_created_only=True counter=forloop.counter report_status=result.report_status %}
-
+
+ {# AE TMG #}
+ {% render_tmg_panel action_item=result by_user_created_only=True counter=forloop.counter report_status=result.reference_obj.report_status %}
+
- {% death_report_tmg_queryset action_item=result as qs %}
- {% for death_report_tmg in qs %}
- {% render_tmg_panel reference_obj=death_report_tmg change=True by_user_created_only=True counter=forloop.counter %}
- {% endfor %}
+ {% death_report_tmg_queryset action_item=result as qs %}
+ {% for death_report_tmg in qs %}
+ {% render_tmg_panel reference_obj=death_report_tmg by_user_created_only=True counter=forloop.counter %}
+ {% endfor %}
- {% death_report_tmg2_queryset action_item=result as qs %}
- {% for death_report_tmg2 in qs %}
- {% render_tmg_panel reference_obj=death_report_tmg2 change=True by_user_created_only=True counter=forloop.counter %}
- {% endfor %}
+ {% death_report_tmg2_queryset action_item=result as qs %}
+ {% for death_report_tmg2 in qs %}
+ {% render_tmg_panel reference_obj=death_report_tmg2 by_user_created_only=True counter=forloop.counter %}
+ {% endfor %}
-
+
-
-
- {% render_tmg_panel action_item=result.parent_action_item view_only=True counter=forloop.counter %}
- {% if result.parent_action_item.reference_obj.action_identifier != result.related_action_item.reference_obj.action_identifier %}
- {% render_tmg_panel action_item=result.related_action_item view_only=True counter=forloop.counter %}
- {% endif %}
+
+
+ {# AE INTIAL #}
+ {% render_tmg_panel action_item=result.parent_action_item counter=forloop.counter %}
+ {% if result.parent_action_item.reference_obj.action_identifier != result.related_action_item.reference_obj.action_identifier %}
+ {% render_tmg_panel action_item=result.related_action_item counter=forloop.counter %}
+ {% endif %}
+
-
-
+
- {% ae_followup_queryset ae_initial=result.parent_action_item.reference_obj as qs %}
- {% if qs.count == 0 %}
There are no follow-ups to this AE
{% endif %}
- {% for ae_followup in qs %}
- {% render_tmg_panel reference_obj=ae_followup view_only=True counter=forloop.counter %}
- {% endfor %}
+ {% ae_followup_queryset ae_initial=result.parent_action_item.reference_obj as qs %}
+ {% if qs.count == 0 %}
There are no follow-ups to this AE
{% endif %}
+ {% for ae_followup in qs %}
+ {% render_tmg_panel reference_obj=ae_followup counter=forloop.counter %}
+ {% endfor %}
- {% death_report_queryset subject_identifier=result.subject_identifier as qs %}
- {% for death_report in qs %}
- {% render_tmg_panel reference_obj=death_report view_only=True counter=forloop.counter %}
- {% endfor %}
+ {% death_report_queryset subject_identifier=result.subject_identifier as qs %}
+ {% for death_report in qs %}
+ {% render_tmg_panel reference_obj=death_report counter=forloop.counter %}
+ {% endfor %}
-
+
-
+
+
+
-
+ {% endfor %}
+ {% else %}
+
+ You do not have permissions to view this page. Only users with the TMG Role are allowed access. Check with your administrator.
- {% endfor %}
- {% else %}
-
- You do not have permissions to view this page. Only users with the TMG Role are allowed access. Check with your administrator.
-
- {% endif %}
-
{% paginator_row %}
+ {% endif %}
+
{% paginator_row %}
+
-
+{% endif %}
diff --git a/edc_adverse_event/templatetags/edc_adverse_event_extras.py b/edc_adverse_event/templatetags/edc_adverse_event_extras.py
index 62c8d66..3e4e7a0 100644
--- a/edc_adverse_event/templatetags/edc_adverse_event_extras.py
+++ b/edc_adverse_event/templatetags/edc_adverse_event_extras.py
@@ -6,19 +6,20 @@
from django import template
from django.conf import settings
-from django.contrib.auth import get_permission_codename
+from django.contrib.messages import ERROR
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import select_template
from django.utils.html import format_html
+from django.utils.translation import gettext as _
from edc_action_item.utils import get_reference_obj
-from edc_constants.constants import OPEN, OTHER, YES
+from edc_constants.constants import CLOSED, OPEN, OTHER, YES
from edc_dashboard.utils import get_bootstrap_version
+from edc_model_admin.utils import add_to_messages_once
from edc_utils import get_utcnow
-from .. import get_ae_model
-from ..auth_objects import TMG_ROLE
-from ..constants import AE_WITHDRAWN
-from ..utils import get_adverse_event_app_label
+from ..constants import AE_WITHDRAWN, TMG_ROLE
+from ..utils import get_adverse_event_app_label, get_ae_model, has_valid_tmg_perms
+from ..view_utils import TmgButton
if TYPE_CHECKING:
from django.db.models import QuerySet
@@ -28,19 +29,23 @@
from ..model_mixins import (
AeFollowupModelMixin,
AeInitialModelMixin,
+ DeathReportModelMixin,
DeathReportTmgModelMixin,
)
- class DeathReportTmg(DeathReportTmgModelMixin, BaseUuidModel):
+ class DeathReportTmgModel(DeathReportTmgModelMixin, BaseUuidModel):
...
- class DeathReportTmgSecond(DeathReportTmgModelMixin, BaseUuidModel):
+ class DeathReportTmgSecondModel(DeathReportTmgModelMixin, BaseUuidModel):
...
- class AeInitial(AeInitialModelMixin, BaseUuidModel):
+ class AeInitialModel(AeInitialModelMixin, BaseUuidModel):
...
- class AeFollowup(AeFollowupModelMixin, BaseUuidModel):
+ class AeFollowupModel(AeFollowupModelMixin, BaseUuidModel):
+ ...
+
+ class DeathReportModel(DeathReportModelMixin, BaseUuidModel):
...
@@ -70,19 +75,6 @@ def select_description_template(model):
return select_ae_template(f"{model}_description.html").template.name
-@register.inclusion_tag(
- f"edc_adverse_event/bootstrap{get_bootstrap_version()}/"
- f"tmg/tmg_ae_listboard_result.html",
- takes_context=True,
-)
-def tmg_listboard_results(
- context, results: [ActionItem], empty_message: str | None = None
-) -> dict:
- context["results"] = results
- context["empty_message"] = empty_message
- return context
-
-
@register.inclusion_tag(select_description_template("aeinitial"), takes_context=True)
def format_ae_description(context, ae_initial, wrap_length):
context["utc_date"] = get_utcnow().date()
@@ -137,6 +129,19 @@ def format_ae_susar_description(context, ae_susar, wrap_length):
return context
+@register.inclusion_tag(
+ f"edc_adverse_event/bootstrap{get_bootstrap_version()}/"
+ f"tmg/tmg_ae_listboard_result.html",
+ takes_context=True,
+)
+def tmg_listboard_results(
+ context, results: [ActionItem], empty_qs_message: str | None = None
+) -> dict:
+ context["results"] = results
+ context["empty_message"] = empty_qs_message
+ return context
+
+
@register.inclusion_tag(
f"edc_adverse_event/bootstrap{get_bootstrap_version()}/tmg/death_report_tmg_panel.html",
takes_context=True,
@@ -146,7 +151,7 @@ def render_death_report_tmg_panel(context, action_item: ActionItem = None):
@register.simple_tag
-def death_report_tmg_queryset(action_item: ActionItem = None) -> QuerySet[DeathReportTmg]:
+def death_report_tmg_queryset(action_item: ActionItem = None) -> QuerySet[DeathReportTmgModel]:
return get_ae_model("deathreporttmg").objects.filter(
subject_identifier=action_item.subject_identifier
)
@@ -155,7 +160,7 @@ def death_report_tmg_queryset(action_item: ActionItem = None) -> QuerySet[DeathR
@register.simple_tag
def death_report_tmg2_queryset(
action_item: ActionItem = None,
-) -> QuerySet[DeathReportTmgSecond]:
+) -> QuerySet[DeathReportTmgSecondModel]:
return get_ae_model("deathreporttmgsecond").objects.filter(
subject_identifier=action_item.subject_identifier
)
@@ -164,12 +169,14 @@ def death_report_tmg2_queryset(
@register.simple_tag
def death_report_queryset(
subject_identifier: str = None,
-) -> QuerySet[DeathReportTmgSecond]:
+) -> QuerySet[DeathReportTmgSecondModel]:
return get_ae_model("deathreport").objects.filter(subject_identifier=subject_identifier)
@register.simple_tag
-def ae_followup_queryset(ae_initial: AeInitial = None) -> QuerySet[AeFollowup] | None:
+def ae_followup_queryset(
+ ae_initial: AeInitialModel = None,
+) -> QuerySet[AeFollowupModel] | None:
if ae_initial:
return get_ae_model("aefollowup").objects.filter(ae_initial_id=ae_initial.id)
return None
@@ -182,115 +189,71 @@ def ae_followup_queryset(ae_initial: AeInitial = None) -> QuerySet[AeFollowup] |
def render_tmg_panel(
context,
action_item: ActionItem = None,
- reference_obj: DeathReportTmg = None,
- change: bool | None = None,
+ reference_obj: DeathReportTmgModel = None,
view_only: bool | None = None,
by_user_created_only: bool | None = None,
counter: int = None,
report_status: str | None = None,
) -> dict:
- # must have either action item or reference_obj
reference_obj = reference_obj or get_reference_obj(action_item)
if not action_item and reference_obj:
action_item = reference_obj.action_item
- may_access_tmg_obj = has_perms_for_obj(
- context,
- reference_obj=reference_obj,
- change=change,
- view_only=view_only,
- by_user_created_only=by_user_created_only,
+ disable_all = True if not has_valid_tmg_perms(request=context["request"]) else False
+ btn = TmgButton(
+ user=context["request"].user,
+ model_obj=reference_obj,
+ model_cls=action_item.action_cls.reference_model_cls(),
+ request=context["request"],
+ only_user_created_may_access=by_user_created_only,
+ forloop_counter=counter,
+ current_site=context["request"].site,
+ disable_all=disable_all,
)
- disabled = "disabled" if not may_access_tmg_obj else ""
-
if view_only:
- label = "View"
- fa_icon = "fa-eye"
+ panel_color = "info"
elif not reference_obj:
- label = "Add"
- fa_icon = "fa-plus"
+ panel_color = "warning"
else:
- label = "Edit" if reference_obj else "view"
- fa_icon = "fa-pencil" if reference_obj else "fa-plus"
+ panel_color = "success"
+
+ # panel_label
+ display_name = action_item.display_name.replace("Submit", "").replace("pending", "")
+ identifier = action_item.identifier or "New"
+ panel_label = _(f"{display_name} {identifier}")
- if view_only:
- panel_color = "info"
- else:
- panel_color = "success" if reference_obj else "warning"
return dict(
+ btn=btn,
+ panel_color=panel_color,
reference_obj=reference_obj,
action_item=action_item,
- may_access_tmg_obj=may_access_tmg_obj,
- counter=counter,
- panel_color=panel_color,
- disabled=disabled,
- label=label,
- fa_icon=fa_icon,
- view_only=view_only,
- report_status=report_status,
OPEN=OPEN,
- )
-
-
-@register.inclusion_tag(
- f"edc_adverse_event/bootstrap{get_bootstrap_version()}/tmg/ae_tmg_panel.html",
- takes_context=True,
-)
-def render_ae_initial_panel(
- context,
- action_item: ActionItem = None,
- counter: int = None,
-) -> dict:
- may_access_tmg_obj = has_perms_for_obj(
- context,
- action_item=action_item,
- view_only=True,
- )
- return dict(
- action_item=action_item,
- may_access_tmg_obj=may_access_tmg_obj,
- counter=counter,
- panel_color="info",
+ CLOSED=CLOSED,
+ report_status=report_status,
+ panel_label=panel_label,
)
-@register.simple_tag(takes_context=True)
-def has_perms_for_obj(
- context,
- action_item: ActionItem | None = None,
- reference_obj: DeathReportTmg = None,
- change: bool | None = None,
- view_only: bool | None = None,
- by_user_created_only: bool | None = None,
-) -> bool:
- has_perms = False
-
- reference_obj = reference_obj or get_reference_obj(action_item)
-
- if reference_obj:
- app_label = reference_obj._meta.app_label
- add_codename = get_permission_codename("add", reference_obj._meta)
- change_codename = get_permission_codename("change", reference_obj._meta)
- view_codename = get_permission_codename("view", reference_obj._meta)
- has_change_perms = context["request"].user.has_perms(
- [f"{app_label}.{add_codename}", f"{app_label}.{change_codename}"]
- )
- has_view_perms = context["request"].user.has_perm(f"{app_label}.{view_codename}")
- if change:
- has_perms = has_change_perms
- elif view_only:
- has_perms = not has_change_perms and has_view_perms
- if by_user_created_only:
- has_perms = (
- has_perms and reference_obj.user_created == context["request"].user.username
- )
- return has_perms
-
-
@register.simple_tag(takes_context=True)
def has_perms_for_tmg_role(context):
has_perms = False
try:
has_perms = context["request"].user.userprofile.roles.get(name=TMG_ROLE)
except ObjectDoesNotExist:
- pass
+ add_to_messages_once(
+ context["request"],
+ ERROR,
+ _(
+ "Access disabled. User has not been granted a TMG role. "
+ "Contact your administrator."
+ ),
+ )
+
return has_perms
+
+
+@register.simple_tag()
+def get_empty_qs_message(status: str, search_term: str):
+ msg = f"There are no {status} reports."
+ if search_term:
+ msg = f"{msg[:-1]} for your search criteria"
+ return _(msg)
diff --git a/edc_adverse_event/utils.py b/edc_adverse_event/utils.py
index c4e75fc..59c0177 100644
--- a/edc_adverse_event/utils.py
+++ b/edc_adverse_event/utils.py
@@ -5,9 +5,18 @@
from django import forms
from django.apps import apps as django_apps
from django.conf import settings
+from django.contrib.auth import get_permission_codename
+from django.contrib.messages import ERROR
+from django.core.exceptions import ObjectDoesNotExist
+from django.utils.translation import gettext as _
+from edc_model_admin.utils import add_to_messages_once
from edc_utils import convert_php_dateformat
+from .constants import TMG_ROLE
+
if TYPE_CHECKING:
+ from django.core.handlers.wsgi import WSGIRequest
+
from edc_adverse_event.model_mixins import (
AeFollowupModelMixin,
AeInitialModelMixin,
@@ -62,3 +71,51 @@ def get_ae_model(
def get_ae_model_name(model_name: str) -> str:
return f"{get_adverse_event_app_label()}.{model_name}"
+
+
+def has_valid_tmg_perms(request: WSGIRequest, add_message: bool = None):
+ """Checks if user has TMG_ROLE but not granted add/change
+ perms to any non-TMG AE models.
+
+ add_message: if True, adds a message to message context.
+ """
+ non_tmg_ae_models = ["aeinitial", "aefollowup", "deathreport"]
+ # check role
+ try:
+ has_valid_perms = request.user.userprofile.roles.get(name=TMG_ROLE)
+ except ObjectDoesNotExist:
+ has_valid_perms = False
+ if add_message:
+ add_to_messages_once(
+ request,
+ ERROR,
+ (
+ "Access disabled. User has not been granted a TMG role. "
+ "Contact your administrator."
+ ),
+ )
+ # check AE model perms
+ if has_valid_tmg_perms:
+ codenames = {}
+ for model_name in non_tmg_ae_models:
+ model_cls = get_ae_model(model_name)
+ codename = get_permission_codename("change", model_cls._meta)
+ codenames.update({model_cls: f"{model_cls._meta.app_label}.{codename}"})
+ for model_cls, codename in codenames.items():
+ if request.user.has_perm(codename):
+ if add_message:
+ add_to_messages_once(
+ request,
+ ERROR,
+ (
+ _(
+ "Access disabled. A TMG user may not have change "
+ "permission for any adverse event form. Contact your "
+ "administrator. Got %(verbose_name)s"
+ )
+ % {"verbose_name": model_cls._meta.verbose_name}
+ ),
+ )
+ has_valid_perms = False
+ break
+ return has_valid_perms
diff --git a/edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py b/edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py
index d44451d..404c652 100644
--- a/edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py
+++ b/edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py
@@ -11,7 +11,7 @@
from edc_utils import get_utcnow
from ...constants import AE_TMG_ACTION
-from ...utils import get_adverse_event_app_label
+from ...utils import get_adverse_event_app_label, has_valid_tmg_perms
if TYPE_CHECKING:
from django.db.models import Q
@@ -73,6 +73,7 @@ class StatusTmgAeListboardView(TmgAeListboardViewMixin):
status = None
def get_context_data(self, **kwargs) -> dict[str, Any]:
+ has_valid_tmg_perms(self.request, add_message=True)
kwargs.update(status=self.status)
return super().get_context_data(**kwargs)
diff --git a/edc_adverse_event/view_utils/__init__.py b/edc_adverse_event/view_utils/__init__.py
new file mode 100644
index 0000000..e0fea1a
--- /dev/null
+++ b/edc_adverse_event/view_utils/__init__.py
@@ -0,0 +1 @@
+from .tmg_button import TmgButton
diff --git a/edc_adverse_event/view_utils/tmg_button.py b/edc_adverse_event/view_utils/tmg_button.py
new file mode 100644
index 0000000..33eaa93
--- /dev/null
+++ b/edc_adverse_event/view_utils/tmg_button.py
@@ -0,0 +1,93 @@
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING
+
+from django.utils.translation import gettext as _
+from edc_subject_dashboard.view_utils import ModelButton
+
+if TYPE_CHECKING:
+ from edc_model.models import BaseUuidModel
+
+ from edc_adverse_event.model_mixins import (
+ AeFollowupModelMixin,
+ AeInitialModelMixin,
+ DeathReportModelMixin,
+ DeathReportTmgModelMixin,
+ )
+
+ class AeInitialModel(AeInitialModelMixin, BaseUuidModel):
+ ...
+
+ class AeFollowupModel(AeFollowupModelMixin, BaseUuidModel):
+ ...
+
+ class DeathReportTmgModel(DeathReportTmgModelMixin, BaseUuidModel):
+ ...
+
+ class DeathReportModel(DeathReportModelMixin, BaseUuidModel):
+ ...
+
+
+@dataclass
+class TmgButton(ModelButton):
+ model_obj: DeathReportTmgModel | DeathReportModel | AeFollowupModel | AeInitialModel = None
+ next_url_name: str = "open_tmg_ae_listboard_url"
+ only_user_created_may_access: bool | None = None
+ forloop_counter: int | None = None
+ colors: tuple[str, str, str] = field(default=("warning", "success", "success"))
+ titles: tuple[str, str, str] = field(default=(_("Add"), _("Change"), _("View")))
+
+ disable_all: bool = False
+
+ @property
+ def disabled(self) -> str:
+ if self.disable_all:
+ disabled = "disabled"
+ else:
+ disabled = super().disabled
+ if (
+ self.only_user_created_may_access
+ and self.model_obj.user_created != self.user.username
+ ):
+ disabled = "disabled"
+ return disabled
+
+ @property
+ def fa_icon(self) -> str:
+ if self.disabled:
+ return "fa-eye-slash"
+ return super().fa_icon
+
+ @property
+ def btn_id(self) -> str:
+ if self.forloop_counter is not None:
+ return str(self.forloop_counter)
+ return super().btn_id
+
+ @property
+ def title(self) -> str:
+ if (
+ self.model_obj
+ and self.only_user_created_may_access
+ and self.model_obj.user_created != self.user.username
+ ):
+ title = _("May only be edited by %(user)s") % {"user": self.model_obj.user_created}
+ if self.model_obj.site.id != self.request.site.id:
+ title = _("%(title)s when logged into site %(site)s") % {
+ "title": title,
+ "site": self.model_obj.site.id,
+ }
+ else:
+ title = super().title
+ return title
+
+ @property
+ def label(self) -> str:
+ if (
+ self.model_obj
+ and self.only_user_created_may_access
+ and self.model_obj.user_created != self.user.username
+ ):
+ return _("View")
+ return _(super().label)