Skip to content

Commit

Permalink
Merge branch 'qa-report-with-linked-column' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanWillitts committed Aug 14, 2024
2 parents 0b27f15 + 0e969fd commit 15f536a
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 127 deletions.
4 changes: 2 additions & 2 deletions edc_qareports/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .list_filters import QaNoteStatusListFilter
from .list_filters import NoteStatusListFilter
from .note_modeladmin_mixin import NoteModelAdminMixin
from .qa_report_log_admin import QaReportLogAdmin
from .qa_report_log_summary_admin import QaReportLogSummaryAdmin
from .qa_report_note_admin import QaReportNoteAdmin
from .qa_report_with_note_modeladmin_mixin import QaReportWithNoteModelAdminMixin
24 changes: 15 additions & 9 deletions edc_qareports/admin/list_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
from django.db.models import Count, QuerySet
from edc_constants.constants import FEEDBACK, NEW

from ..choices import QA_NOTE_STATUS
from ..models import QaReportNote
from ..choices import NOTE_STATUSES


class QaNoteStatusListFilter(SimpleListFilter):
class NoteStatusListFilter(SimpleListFilter):
title = "QA Status"
parameter_name = "qa_note_status"
parameter_name = "note_status"

note_model_cls = None

def __init__(self, request, params, model, model_admin):
self.note_model_cls = model_admin.note_model_cls
super().__init__(request, params, model, model_admin)

def lookups(self, request, model_admin):
status_dict = {tpl[0]: tpl[1] for tpl in QA_NOTE_STATUS}
status_dict = {tpl[0]: tpl[1] for tpl in NOTE_STATUSES}
names = [(NEW, status_dict[NEW])]
qs = (
QaReportNote.objects.values("status")
self.note_model_cls.objects.values("status")
.order_by("status")
.annotate(cnt=Count("status"))
)
Expand All @@ -37,15 +42,16 @@ def queryset(self, request, queryset):
if self.value() and self.value() != "none":
if report_model := self.report_model(queryset):
if self.value() == FEEDBACK:
qs = QaReportNote.objects.values("subject_identifier").filter(
qs = self.note_model_cls.objects.values("subject_identifier").filter(
report_model=report_model, status=FEEDBACK
)
queryset = queryset.filter(
subject_identifier__in=[obj.get("subject_identifier") for obj in qs]
)
elif self.value() == NEW:
qs = QaReportNote.objects.values("subject_identifier").filter(
report_model=report_model, status=FEEDBACK
qs = self.note_model_cls.objects.values("subject_identifier").filter(
report_model=report_model,
status=FEEDBACK,
)
queryset = queryset.exclude(
subject_identifier__in=[obj.get("subject_identifier") for obj in qs]
Expand Down
109 changes: 109 additions & 0 deletions edc_qareports/admin/note_modeladmin_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from django.apps import apps as django_apps
from django.contrib import admin
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from django.urls import reverse
from edc_constants.constants import NEW

from ..models import QaReportLog
from ..utils import truncate_string
from .list_filters import NoteStatusListFilter


class NoteModelAdminMixin:
"""A mixin to link a data management report to a note (with status)
on each report item.
note_model_cls/template can be overridden in concrete classes.
"""

qa_report_log_enabled = True
qa_report_list_display_insert_pos = 3

note_model_cls = django_apps.get_model("edc_qareports.qareportnote")
note_template = "edc_qareports/columns/notes_column.html"

def update_qa_report_log(self, request) -> None:
QaReportLog.objects.create(
username=request.user.username,
site=request.site,
report_model=self.model._meta.label_lower,
)

def changelist_view(self, request, extra_context=None):
if self.qa_report_log_enabled:
self.update_qa_report_log(request)
return super().changelist_view(request, extra_context=extra_context)

def get_list_display(self, request):
list_display = super().get_list_display(request)
list_display = list(list_display)
list_display.insert(self.qa_report_list_display_insert_pos, "notes")
list_display.insert(self.qa_report_list_display_insert_pos, "status")
return tuple(list_display)

def get_list_filter(self, request):
list_filter = super().get_list_filter(request)
list_filter = list(list_filter)
list_filter.insert(0, NoteStatusListFilter)
return tuple(list_filter)

def get_note_model_obj_or_raise(self, obj=None):
try:
note_model_obj = self.note_model_cls.objects.get(
report_model=obj.report_model, subject_identifier=obj.subject_identifier
)
except ObjectDoesNotExist:
raise
return note_model_obj

@admin.display(description="Status")
def status(self, obj) -> str:
try:
note_model_obj = self.get_note_model_obj_or_raise(obj)
except ObjectDoesNotExist:
status = NEW
else:
status = note_model_obj.status
return status.title()

@admin.display(description="Notes")
def notes(self, obj=None) -> str:
"""Returns url to add or edit qa_report note model."""
note_app_label, note_model_name = self.note_model_cls._meta.label_lower.split(".")
note_url_name = f"{note_app_label}_{note_model_name}"

report_app_label, report_model_name = self.model._meta.label_lower.split(".")
next_url_name = "_".join([report_app_label, report_model_name, "changelist"])
next_url_name = f"{report_app_label}_admin:{next_url_name}"

try:
note_model_obj = self.get_note_model_obj_or_raise(obj)
except ObjectDoesNotExist:
note_model_obj = None
url = reverse(f"{note_app_label}_admin:{note_url_name}_add")
title = "Add"
else:
url = reverse(
f"{note_app_label}_admin:{note_url_name}_change",
args=(note_model_obj.id,),
)
title = "Edit"

url = (
f"{url}?next={next_url_name},subject_identifier,q"
f"&subject_identifier={obj.subject_identifier}"
f"&report_model={obj.report_model}&q={obj.subject_identifier}"
)
label = self.get_notes_label(note_model_obj)
context = dict(title=title, url=url, label=label)
return render_to_string(self.note_template, context=context)

def get_notes_label(self, obj, field_name=None):
if not obj:
label = "Add"
elif not obj.note:
label = "Edit"
else:
label = truncate_string(obj.note, max_length=35)
return label
87 changes: 0 additions & 87 deletions edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py

This file was deleted.

2 changes: 1 addition & 1 deletion edc_qareports/choices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from edc_constants.constants import FEEDBACK, NEW

QA_NOTE_STATUS = (
NOTE_STATUSES = (
(NEW, "New"),
(FEEDBACK, "Feedback"),
)
9 changes: 7 additions & 2 deletions edc_qareports/forms/qa_report_note_form.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from django import forms
from edc_form_validators import FormValidatorMixin
from edc_form_validators import FormValidator, FormValidatorMixin
from edc_model_form.mixins import BaseModelFormMixin
from edc_sites.modelform_mixins import SiteModelFormMixin

from ..models import QaReportNote


class QaReportNoteFormValidator(FormValidator):
def clean(self):
self.required_if_true(True, field_required="note")


class QaReportNoteForm(
SiteModelFormMixin,
BaseModelFormMixin,
Expand All @@ -14,7 +19,7 @@ class QaReportNoteForm(
):

report_datetime_field_attr = "report_datetime"
form_validator_cls = None
form_validator_cls = QaReportNoteFormValidator

class Meta:
model = QaReportNote
Expand Down
18 changes: 18 additions & 0 deletions edc_qareports/migrations/0012_alter_qareportnote_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-08-14 15:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("edc_qareports", "0011_auto_20240813_0219"),
]

operations = [
migrations.AlterField(
model_name="qareportnote",
name="note",
field=models.TextField(blank=True, null=True),
),
]
2 changes: 2 additions & 0 deletions edc_qareports/model_mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .note_model_mixin import NoteModelMixin
from .qa_report_model_mixin import QaReportModelMixin, qa_reports_permissions
36 changes: 36 additions & 0 deletions edc_qareports/model_mixins/note_model_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django.apps import apps as django_apps
from django.db import models
from edc_constants.constants import FEEDBACK, NEW
from edc_model.models import BaseUuidModel
from edc_sites.model_mixins import SiteModelMixin
from edc_utils import get_utcnow

from ..choices import NOTE_STATUSES


class NoteModelMixin(SiteModelMixin, BaseUuidModel):
"""Model mixin to link form (e.g. note) to a data query report,
such as, unmanaged views.
See also, NoteModelAdminMixin
"""

report_model = models.CharField(max_length=150)

report_datetime = models.DateTimeField(default=get_utcnow)

note = models.TextField(null=True, blank=True)

status = models.CharField(max_length=25, default=NEW, choices=NOTE_STATUSES)

def save(self, *args, **kwargs):
if self.status == NEW:
self.status = FEEDBACK
super().save(*args, **kwargs)

@property
def report_model_cls(self):
return django_apps.get_model(self.report_model)

class Meta:
abstract = True
1 change: 0 additions & 1 deletion edc_qareports/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .dbviews import QaReportLogSummary
from .edc_permissions import EdcPermissions
from .qa_report_model_mixin import QaReportModelMixin, qa_reports_permissions
from .qa_report_note import QaReportNote
from .qa_reports_log import QaReportLog
2 changes: 1 addition & 1 deletion edc_qareports/models/dbviews/unmanaged_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.db import models
from django_db_views.db_view import DBView

from ..qa_report_model_mixin import qa_reports_permissions
from ...model_mixins import qa_reports_permissions
from .view_definition import get_view_definition


Expand Down
Loading

0 comments on commit 15f536a

Please sign in to comment.