From aadb4cea9978025b650d466aa950080f46750369 Mon Sep 17 00:00:00 2001 From: Jonathan Willitts Date: Tue, 13 Aug 2024 13:22:28 +0100 Subject: [PATCH 1/4] Add QaReportWithLinkedColumn mixins, based on QaReportWithNote Use new mixins for QaReportWithNote --- edc_qareports/admin/__init__.py | 5 +- edc_qareports/admin/list_filters.py | 25 ++-- ...ort_with_linked_column_modeladmin_mixin.py | 117 ++++++++++++++++++ .../qa_report_with_note_modeladmin_mixin.py | 87 ++----------- edc_qareports/choices.py | 2 +- edc_qareports/forms/qa_report_note_form.py | 9 +- .../0011_alter_qareportnote_note.py | 18 +++ edc_qareports/model_mixins/__init__.py | 1 + .../qa_report_linked_column_model_mixin.py | 38 ++++++ edc_qareports/models/qa_report_note.py | 27 +--- .../{notes_column.html => linked_column.html} | 0 edc_qareports/utils.py | 10 ++ 12 files changed, 225 insertions(+), 114 deletions(-) create mode 100644 edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py create mode 100644 edc_qareports/migrations/0011_alter_qareportnote_note.py create mode 100644 edc_qareports/model_mixins/__init__.py create mode 100644 edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py rename edc_qareports/templates/edc_qareports/columns/{notes_column.html => linked_column.html} (100%) diff --git a/edc_qareports/admin/__init__.py b/edc_qareports/admin/__init__.py index bcbb5cf..bf49d14 100644 --- a/edc_qareports/admin/__init__.py +++ b/edc_qareports/admin/__init__.py @@ -1,5 +1,8 @@ -from .list_filters import QaNoteStatusListFilter +from .list_filters import QaReportLinkedColumnStatusListFilter 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_linked_column_modeladmin_mixin import ( + QaReportWithLinkedColumnModelAdminMixin, +) from .qa_report_with_note_modeladmin_mixin import QaReportWithNoteModelAdminMixin diff --git a/edc_qareports/admin/list_filters.py b/edc_qareports/admin/list_filters.py index 09f8ddb..4aeb72b 100644 --- a/edc_qareports/admin/list_filters.py +++ b/edc_qareports/admin/list_filters.py @@ -2,20 +2,24 @@ from django.db.models import Count, QuerySet from edc_constants.constants import FEEDBACK, NEW -from edc_qareports.models import QaReportNote +from ..choices import QA_REPORT_LINKED_COLUMN_STATUSES -from ..choices import QA_NOTE_STATUS - -class QaNoteStatusListFilter(SimpleListFilter): +class QaReportLinkedColumnStatusListFilter(SimpleListFilter): title = "QA Status" - parameter_name = "qa_note_status" + parameter_name = "qa_linked_column_status" + + linked_model_cls = None + + def __init__(self, request, params, model, model_admin): + self.linked_model_cls = model_admin.linked_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 QA_REPORT_LINKED_COLUMN_STATUSES} names = [(NEW, status_dict[NEW])] qs = ( - QaReportNote.objects.values("status") + self.linked_model_cls.objects.values("status") .order_by("status") .annotate(cnt=Count("status")) ) @@ -38,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.linked_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.linked_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] diff --git a/edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py b/edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py new file mode 100644 index 0000000..2f3de04 --- /dev/null +++ b/edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py @@ -0,0 +1,117 @@ +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 QaReportLinkedColumnStatusListFilter + + +class QaReportWithLinkedColumnModelAdminMixin: + """A mixin to link a data management report to a linked column + (with status) on each report item. + """ + + qa_report_log_enabled = True + qa_report_list_display_insert_pos = 3 + + linked_model_cls = None + linked_column_field_name = None + + linked_column_template = "edc_qareports/columns/linked_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, "linked_column") + 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, QaReportLinkedColumnStatusListFilter) + return tuple(list_filter) + + @admin.display(description="Status") + def status(self, obj) -> str: + try: + linked_model_obj = self.get_linked_model_obj_or_raise(obj) + except ObjectDoesNotExist: + status = NEW + else: + status = linked_model_obj.status + return status.title() + + def get_linked_model_obj_or_raise(self, obj=None): + try: + linked_model_obj = self.linked_model_cls.objects.get( + report_model=obj.report_model, subject_identifier=obj.subject_identifier + ) + except ObjectDoesNotExist: + raise + return linked_model_obj + + def linked_column(self, obj=None) -> str: + """Returns url to add or edit qa_report linked model + instance. + """ + return self.render_linked_column_to_string( + field_name=self.linked_column_field_name, obj=obj + ) + + def render_linked_column_to_string(self, field_name: str, obj=None) -> str: + """Returns url to add or edit qa_report linked model.""" + linked_app_label, linked_model_name = self.linked_model_cls._meta.label_lower.split( + "." + ) + linked_url_name = f"{linked_app_label}_{linked_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: + linked_model_obj = self.get_linked_model_obj_or_raise(obj) + except ObjectDoesNotExist: + linked_model_obj = None + url = reverse(f"{linked_app_label}_admin:{linked_url_name}_add") + title = "Add" + else: + url = reverse( + f"{linked_app_label}_admin:{linked_url_name}_change", + args=(linked_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_linked_column_label(linked_model_obj, field_name) + context = dict(title=title, url=url, label=label) + return render_to_string(self.linked_column_template, context=context) + + def get_linked_column_label(self, obj, field_name=None): + if not obj: + label = "Add" + else: + label = getattr(obj, field_name) or "Edit" + if isinstance(label, str): + label = truncate_string(label, max_length=35) + return label diff --git a/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py b/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py index 26f7f92..60747c8 100644 --- a/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py +++ b/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py @@ -1,87 +1,20 @@ +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, QaReportNote -from .list_filters import QaNoteStatusListFilter +from . import QaReportWithLinkedColumnModelAdminMixin -class QaReportWithNoteModelAdminMixin: +class QaReportWithNoteModelAdminMixin(QaReportWithLinkedColumnModelAdminMixin): """A mixin to link a data management report to notes and status on each report item. - See also, model ReportNote. + See also, model QaReportNote. """ - qa_report_log_enabled = True - qa_report_list_display_insert_pos = 3 + linked_model_cls = django_apps.get_model("edc_qareports.qareportnote") + linked_column_field_name = "note" - 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, QaNoteStatusListFilter) - return tuple(list_filter) - - @admin.display(description="Status", ordering="subject_identifier") - def status(self, obj): - try: - report_note = self.get_qa_report_note_or_raise(obj) - except ObjectDoesNotExist: - return NEW - return report_note.status - - @staticmethod - def get_qa_report_note_or_raise(obj=None): - try: - qa_report_note = QaReportNote.objects.get( - report_model=obj.report_model, subject_identifier=obj.subject_identifier - ) - except ObjectDoesNotExist: - raise - return qa_report_note - - @admin.display(description="Notes", ordering="subject_identifier") - def notes(self, obj=None): - """Returns url to add or edit qa_report note""" - url_name = "_".join(QaReportNote._meta.label_lower.split(".")) - app_label, model_name = self.model._meta.label_lower.split(".") - next_url_name = "_".join([app_label, model_name, "changelist"]) - next_url_name = f"{app_label}_admin:{next_url_name}" - try: - qa_report_note = self.get_qa_report_note_or_raise(obj) - except ObjectDoesNotExist: - url = reverse(f"edc_qareports_admin:{url_name}_add") - label = "Add" - title = "Add report note" - else: - url = reverse(f"edc_qareports_admin:{url_name}_change", args=(qa_report_note.id,)) - label = qa_report_note.note[0:35] or "Edit" - 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}" - ) - context = dict(title=title, url=url, label=label) - return render_to_string("edc_qareports/columns/notes_column.html", context=context) + @admin.display(description="Notes") + def linked_column(self, obj=None): + """Returns url to add or edit qa_report model note""" + return super().linked_column(obj=obj) diff --git a/edc_qareports/choices.py b/edc_qareports/choices.py index 4938dec..dc47d65 100644 --- a/edc_qareports/choices.py +++ b/edc_qareports/choices.py @@ -1,6 +1,6 @@ from edc_constants.constants import FEEDBACK, NEW -QA_NOTE_STATUS = ( +QA_REPORT_LINKED_COLUMN_STATUSES = ( (NEW, "New"), (FEEDBACK, "Feedback"), ) diff --git a/edc_qareports/forms/qa_report_note_form.py b/edc_qareports/forms/qa_report_note_form.py index 21a7cf7..fea106b 100644 --- a/edc_qareports/forms/qa_report_note_form.py +++ b/edc_qareports/forms/qa_report_note_form.py @@ -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, @@ -14,7 +19,7 @@ class QaReportNoteForm( ): report_datetime_field_attr = "report_datetime" - form_validator_cls = None + form_validator_cls = QaReportNoteFormValidator class Meta: model = QaReportNote diff --git a/edc_qareports/migrations/0011_alter_qareportnote_note.py b/edc_qareports/migrations/0011_alter_qareportnote_note.py new file mode 100644 index 0000000..d85b6d5 --- /dev/null +++ b/edc_qareports/migrations/0011_alter_qareportnote_note.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-08-12 11:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("edc_qareports", "0010_auto_20240619_0559"), + ] + + operations = [ + migrations.AlterField( + model_name="qareportnote", + name="note", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/edc_qareports/model_mixins/__init__.py b/edc_qareports/model_mixins/__init__.py new file mode 100644 index 0000000..4e73d43 --- /dev/null +++ b/edc_qareports/model_mixins/__init__.py @@ -0,0 +1 @@ +from .qa_report_linked_column_model_mixin import QaReportWithLinkedColumnModelMixin diff --git a/edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py b/edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py new file mode 100644 index 0000000..92cd2b0 --- /dev/null +++ b/edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py @@ -0,0 +1,38 @@ +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 edc_qareports.choices import QA_REPORT_LINKED_COLUMN_STATUSES + + +class QaReportWithLinkedColumnModelMixin(SiteModelMixin, BaseUuidModel): + """Model mixin to link form to a data query report, such as, + unmanaged views. + + See also, QaReportWithLinkedColumnModelAdminMixin + """ + + 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=QA_REPORT_LINKED_COLUMN_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 diff --git a/edc_qareports/models/qa_report_note.py b/edc_qareports/models/qa_report_note.py index 41d2b2c..0509286 100644 --- a/edc_qareports/models/qa_report_note.py +++ b/edc_qareports/models/qa_report_note.py @@ -1,16 +1,11 @@ -from django.apps import apps as django_apps -from django.db import models from django.db.models import UniqueConstraint -from edc_constants.constants import FEEDBACK, NEW from edc_identifier.model_mixins import NonUniqueSubjectIdentifierFieldMixin from edc_model.models import BaseUuidModel -from edc_sites.model_mixins import SiteModelMixin -from edc_utils import get_utcnow -from ..choices import QA_NOTE_STATUS +from ..model_mixins import QaReportWithLinkedColumnModelMixin -class QaReportNote(NonUniqueSubjectIdentifierFieldMixin, SiteModelMixin, BaseUuidModel): +class QaReportNote(NonUniqueSubjectIdentifierFieldMixin, QaReportWithLinkedColumnModelMixin): """A model class to capture user / dm notes linked to a data query report, such as, unmanaged views. @@ -19,22 +14,8 @@ class QaReportNote(NonUniqueSubjectIdentifierFieldMixin, SiteModelMixin, BaseUui See also, QaReportWithNoteModelAdminMixin """ - report_model = models.CharField(max_length=150) - - report_datetime = models.DateTimeField(default=get_utcnow) - - note = models.TextField(null=True) - - status = models.CharField(max_length=25, default=NEW, choices=QA_NOTE_STATUS) - - 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) + def __str__(self) -> str: + return f"{self._meta.verbose_name}: {self.subject_identifier}" class Meta(BaseUuidModel.Meta): verbose_name = "QA Report Note" diff --git a/edc_qareports/templates/edc_qareports/columns/notes_column.html b/edc_qareports/templates/edc_qareports/columns/linked_column.html similarity index 100% rename from edc_qareports/templates/edc_qareports/columns/notes_column.html rename to edc_qareports/templates/edc_qareports/columns/linked_column.html diff --git a/edc_qareports/utils.py b/edc_qareports/utils.py index b9df36c..d965a13 100644 --- a/edc_qareports/utils.py +++ b/edc_qareports/utils.py @@ -24,3 +24,13 @@ def read_unmanaged_model_sql( parsed_sql.append(line) return " ".join(parsed_sql) + + +def truncate_string(string: str, max_length: int) -> str: + """Strips string of leading/trailing whitespace and truncates + if > `max_length`. + """ + string = string.strip() + if len(string) > max_length: + return string[: max_length - 1].strip() + "…" + return string From 91ce5ce77b32d6829e803a8683fdb33ee043524c Mon Sep 17 00:00:00 2001 From: Jonathan Willitts Date: Wed, 14 Aug 2024 15:29:57 +0100 Subject: [PATCH 2/4] Refactor NoteModel(Admin)Mixin ...instantly usable, or can be overridden. Also simplified implementation --- edc_qareports/admin/__init__.py | 7 +- edc_qareports/admin/list_filters.py | 18 ++-- ...dmin_mixin.py => note_modeladmin_mixin.py} | 84 +++++++++---------- .../qa_report_with_note_modeladmin_mixin.py | 20 ----- edc_qareports/choices.py | 2 +- edc_qareports/model_mixins/__init__.py | 3 +- ...umn_model_mixin.py => note_model_mixin.py} | 14 ++-- .../qa_report_model_mixin.py | 0 edc_qareports/models/__init__.py | 1 - edc_qareports/models/qa_report_note.py | 6 +- .../{linked_column.html => notes_column.html} | 0 11 files changed, 61 insertions(+), 94 deletions(-) rename edc_qareports/admin/{qa_report_with_linked_column_modeladmin_mixin.py => note_modeladmin_mixin.py} (55%) delete mode 100644 edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py rename edc_qareports/model_mixins/{qa_report_linked_column_model_mixin.py => note_model_mixin.py} (64%) rename edc_qareports/{models => model_mixins}/qa_report_model_mixin.py (100%) rename edc_qareports/templates/edc_qareports/columns/{linked_column.html => notes_column.html} (100%) diff --git a/edc_qareports/admin/__init__.py b/edc_qareports/admin/__init__.py index bf49d14..264666d 100644 --- a/edc_qareports/admin/__init__.py +++ b/edc_qareports/admin/__init__.py @@ -1,8 +1,5 @@ -from .list_filters import QaReportLinkedColumnStatusListFilter +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_linked_column_modeladmin_mixin import ( - QaReportWithLinkedColumnModelAdminMixin, -) -from .qa_report_with_note_modeladmin_mixin import QaReportWithNoteModelAdminMixin diff --git a/edc_qareports/admin/list_filters.py b/edc_qareports/admin/list_filters.py index 4aeb72b..f7cbb54 100644 --- a/edc_qareports/admin/list_filters.py +++ b/edc_qareports/admin/list_filters.py @@ -2,24 +2,24 @@ from django.db.models import Count, QuerySet from edc_constants.constants import FEEDBACK, NEW -from ..choices import QA_REPORT_LINKED_COLUMN_STATUSES +from ..choices import NOTE_STATUSES -class QaReportLinkedColumnStatusListFilter(SimpleListFilter): +class NoteStatusListFilter(SimpleListFilter): title = "QA Status" - parameter_name = "qa_linked_column_status" + parameter_name = "note_status" - linked_model_cls = None + note_model_cls = None def __init__(self, request, params, model, model_admin): - self.linked_model_cls = model_admin.linked_model_cls + 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_REPORT_LINKED_COLUMN_STATUSES} + status_dict = {tpl[0]: tpl[1] for tpl in NOTE_STATUSES} names = [(NEW, status_dict[NEW])] qs = ( - self.linked_model_cls.objects.values("status") + self.note_model_cls.objects.values("status") .order_by("status") .annotate(cnt=Count("status")) ) @@ -42,14 +42,14 @@ def queryset(self, request, queryset): if self.value() and self.value() != "none": if report_model := self.report_model(queryset): if self.value() == FEEDBACK: - qs = self.linked_model_cls.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 = self.linked_model_cls.objects.values("subject_identifier").filter( + qs = self.note_model_cls.objects.values("subject_identifier").filter( report_model=report_model, status=FEEDBACK, ) diff --git a/edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py b/edc_qareports/admin/note_modeladmin_mixin.py similarity index 55% rename from edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py rename to edc_qareports/admin/note_modeladmin_mixin.py index 2f3de04..35fc6cc 100644 --- a/edc_qareports/admin/qa_report_with_linked_column_modeladmin_mixin.py +++ b/edc_qareports/admin/note_modeladmin_mixin.py @@ -1,3 +1,4 @@ +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 @@ -6,21 +7,21 @@ from ..models import QaReportLog from ..utils import truncate_string -from .list_filters import QaReportLinkedColumnStatusListFilter +from .list_filters import NoteStatusListFilter -class QaReportWithLinkedColumnModelAdminMixin: - """A mixin to link a data management report to a linked column - (with status) on each report item. +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 - linked_model_cls = None - linked_column_field_name = None - - linked_column_template = "edc_qareports/columns/linked_column.html" + 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( @@ -37,64 +38,55 @@ def changelist_view(self, request, extra_context=None): 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, "linked_column") + 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, QaReportLinkedColumnStatusListFilter) + 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: - linked_model_obj = self.get_linked_model_obj_or_raise(obj) + note_model_obj = self.get_note_model_obj_or_raise(obj) except ObjectDoesNotExist: status = NEW else: - status = linked_model_obj.status + status = note_model_obj.status return status.title() - def get_linked_model_obj_or_raise(self, obj=None): - try: - linked_model_obj = self.linked_model_cls.objects.get( - report_model=obj.report_model, subject_identifier=obj.subject_identifier - ) - except ObjectDoesNotExist: - raise - return linked_model_obj - - def linked_column(self, obj=None) -> str: - """Returns url to add or edit qa_report linked model - instance. - """ - return self.render_linked_column_to_string( - field_name=self.linked_column_field_name, obj=obj - ) - - def render_linked_column_to_string(self, field_name: str, obj=None) -> str: - """Returns url to add or edit qa_report linked model.""" - linked_app_label, linked_model_name = self.linked_model_cls._meta.label_lower.split( - "." - ) - linked_url_name = f"{linked_app_label}_{linked_model_name}" + @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: - linked_model_obj = self.get_linked_model_obj_or_raise(obj) + note_model_obj = self.get_note_model_obj_or_raise(obj) except ObjectDoesNotExist: - linked_model_obj = None - url = reverse(f"{linked_app_label}_admin:{linked_url_name}_add") + note_model_obj = None + url = reverse(f"{note_app_label}_admin:{note_url_name}_add") title = "Add" else: url = reverse( - f"{linked_app_label}_admin:{linked_url_name}_change", - args=(linked_model_obj.id,), + f"{note_app_label}_admin:{note_url_name}_change", + args=(note_model_obj.id,), ) title = "Edit" @@ -103,15 +95,15 @@ def render_linked_column_to_string(self, field_name: str, obj=None) -> str: f"&subject_identifier={obj.subject_identifier}" f"&report_model={obj.report_model}&q={obj.subject_identifier}" ) - label = self.get_linked_column_label(linked_model_obj, field_name) + label = self.get_notes_label(note_model_obj) context = dict(title=title, url=url, label=label) - return render_to_string(self.linked_column_template, context=context) + return render_to_string(self.note_template, context=context) - def get_linked_column_label(self, obj, field_name=None): + def get_notes_label(self, obj, field_name=None): if not obj: label = "Add" + elif not obj.note: + label = "Edit" else: - label = getattr(obj, field_name) or "Edit" - if isinstance(label, str): - label = truncate_string(label, max_length=35) + label = truncate_string(obj.note, max_length=35) return label diff --git a/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py b/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py deleted file mode 100644 index 60747c8..0000000 --- a/edc_qareports/admin/qa_report_with_note_modeladmin_mixin.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.apps import apps as django_apps -from django.contrib import admin - -from . import QaReportWithLinkedColumnModelAdminMixin - - -class QaReportWithNoteModelAdminMixin(QaReportWithLinkedColumnModelAdminMixin): - """A mixin to link a data management report to notes and status - on each report item. - - See also, model QaReportNote. - """ - - linked_model_cls = django_apps.get_model("edc_qareports.qareportnote") - linked_column_field_name = "note" - - @admin.display(description="Notes") - def linked_column(self, obj=None): - """Returns url to add or edit qa_report model note""" - return super().linked_column(obj=obj) diff --git a/edc_qareports/choices.py b/edc_qareports/choices.py index dc47d65..72afe7e 100644 --- a/edc_qareports/choices.py +++ b/edc_qareports/choices.py @@ -1,6 +1,6 @@ from edc_constants.constants import FEEDBACK, NEW -QA_REPORT_LINKED_COLUMN_STATUSES = ( +NOTE_STATUSES = ( (NEW, "New"), (FEEDBACK, "Feedback"), ) diff --git a/edc_qareports/model_mixins/__init__.py b/edc_qareports/model_mixins/__init__.py index 4e73d43..8eba501 100644 --- a/edc_qareports/model_mixins/__init__.py +++ b/edc_qareports/model_mixins/__init__.py @@ -1 +1,2 @@ -from .qa_report_linked_column_model_mixin import QaReportWithLinkedColumnModelMixin +from .note_model_mixin import NoteModelMixin +from .qa_report_model_mixin import QaReportModelMixin diff --git a/edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py b/edc_qareports/model_mixins/note_model_mixin.py similarity index 64% rename from edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py rename to edc_qareports/model_mixins/note_model_mixin.py index 92cd2b0..8276b49 100644 --- a/edc_qareports/model_mixins/qa_report_linked_column_model_mixin.py +++ b/edc_qareports/model_mixins/note_model_mixin.py @@ -5,14 +5,14 @@ from edc_sites.model_mixins import SiteModelMixin from edc_utils import get_utcnow -from edc_qareports.choices import QA_REPORT_LINKED_COLUMN_STATUSES +from ..choices import NOTE_STATUSES -class QaReportWithLinkedColumnModelMixin(SiteModelMixin, BaseUuidModel): - """Model mixin to link form to a data query report, such as, - unmanaged views. +class NoteModelMixin(SiteModelMixin, BaseUuidModel): + """Model mixin to link form (e.g. note) to a data query report, + such as, unmanaged views. - See also, QaReportWithLinkedColumnModelAdminMixin + See also, NoteModelAdminMixin """ report_model = models.CharField(max_length=150) @@ -21,9 +21,7 @@ class QaReportWithLinkedColumnModelMixin(SiteModelMixin, BaseUuidModel): note = models.TextField(null=True, blank=True) - status = models.CharField( - max_length=25, default=NEW, choices=QA_REPORT_LINKED_COLUMN_STATUSES - ) + status = models.CharField(max_length=25, default=NEW, choices=NOTE_STATUSES) def save(self, *args, **kwargs): if self.status == NEW: diff --git a/edc_qareports/models/qa_report_model_mixin.py b/edc_qareports/model_mixins/qa_report_model_mixin.py similarity index 100% rename from edc_qareports/models/qa_report_model_mixin.py rename to edc_qareports/model_mixins/qa_report_model_mixin.py diff --git a/edc_qareports/models/__init__.py b/edc_qareports/models/__init__.py index d85efcc..e6090ce 100644 --- a/edc_qareports/models/__init__.py +++ b/edc_qareports/models/__init__.py @@ -1,5 +1,4 @@ from .edc_permissions import EdcPermissions -from .qa_report_model_mixin import QaReportModelMixin from .qa_report_note import QaReportNote from .qa_reports_log import QaReportLog from .unmanaged import QaReportLogSummary diff --git a/edc_qareports/models/qa_report_note.py b/edc_qareports/models/qa_report_note.py index 0509286..ec7a3f1 100644 --- a/edc_qareports/models/qa_report_note.py +++ b/edc_qareports/models/qa_report_note.py @@ -2,16 +2,16 @@ from edc_identifier.model_mixins import NonUniqueSubjectIdentifierFieldMixin from edc_model.models import BaseUuidModel -from ..model_mixins import QaReportWithLinkedColumnModelMixin +from ..model_mixins import NoteModelMixin -class QaReportNote(NonUniqueSubjectIdentifierFieldMixin, QaReportWithLinkedColumnModelMixin): +class QaReportNote(NonUniqueSubjectIdentifierFieldMixin, NoteModelMixin): """A model class to capture user / dm notes linked to a data query report, such as, unmanaged views. Unique constraint is on subject_identifier and the report model. - See also, QaReportWithNoteModelAdminMixin + See also, NoteModelAdminMixin """ def __str__(self) -> str: diff --git a/edc_qareports/templates/edc_qareports/columns/linked_column.html b/edc_qareports/templates/edc_qareports/columns/notes_column.html similarity index 100% rename from edc_qareports/templates/edc_qareports/columns/linked_column.html rename to edc_qareports/templates/edc_qareports/columns/notes_column.html From 9a6c88d346aefd02d72e7c5aed9d5628cda2cd63 Mon Sep 17 00:00:00 2001 From: Jonathan Willitts Date: Wed, 14 Aug 2024 17:00:39 +0100 Subject: [PATCH 3/4] Fixes further to merge in of 'develop' --- edc_qareports/model_mixins/__init__.py | 2 +- edc_qareports/models/__init__.py | 1 - edc_qareports/models/dbviews/unmanaged_model.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/edc_qareports/model_mixins/__init__.py b/edc_qareports/model_mixins/__init__.py index 8eba501..e619de7 100644 --- a/edc_qareports/model_mixins/__init__.py +++ b/edc_qareports/model_mixins/__init__.py @@ -1,2 +1,2 @@ from .note_model_mixin import NoteModelMixin -from .qa_report_model_mixin import QaReportModelMixin +from .qa_report_model_mixin import QaReportModelMixin, qa_reports_permissions diff --git a/edc_qareports/models/__init__.py b/edc_qareports/models/__init__.py index 73522b3..9581254 100644 --- a/edc_qareports/models/__init__.py +++ b/edc_qareports/models/__init__.py @@ -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 diff --git a/edc_qareports/models/dbviews/unmanaged_model.py b/edc_qareports/models/dbviews/unmanaged_model.py index 5a3b960..0fc0d66 100644 --- a/edc_qareports/models/dbviews/unmanaged_model.py +++ b/edc_qareports/models/dbviews/unmanaged_model.py @@ -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 From 0e969fd550b47b843dc5b41b9ac3541200b0ce40 Mon Sep 17 00:00:00 2001 From: Jonathan Willitts Date: Wed, 14 Aug 2024 17:03:51 +0100 Subject: [PATCH 4/4] Add migration --- .../migrations/0012_alter_qareportnote_note.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 edc_qareports/migrations/0012_alter_qareportnote_note.py diff --git a/edc_qareports/migrations/0012_alter_qareportnote_note.py b/edc_qareports/migrations/0012_alter_qareportnote_note.py new file mode 100644 index 0000000..a375087 --- /dev/null +++ b/edc_qareports/migrations/0012_alter_qareportnote_note.py @@ -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), + ), + ]