diff --git a/CHANGES.rst b/CHANGES.rst
index 83f82c4c0f..eefb9c4e9e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -35,6 +35,7 @@ Changelog
**Fixed**
+- #1512 QC Analyses listing appears empty in Sample view
- #1510 Error when viewing a Sample w/o Batch as client contact
- #1511 Links to partitions for Internal Use are displayed in partitions viewlet
- #1505 Manage Analyses Form re-applies partitioned Analyses back to the Root
diff --git a/bika/lims/browser/analyses/qc.py b/bika/lims/browser/analyses/qc.py
index e9e76cdc3a..89e98cdc36 100644
--- a/bika/lims/browser/analyses/qc.py
+++ b/bika/lims/browser/analyses/qc.py
@@ -18,11 +18,15 @@
# Copyright 2018-2019 by it's authors.
# Some rights reserved, see README and LICENSE.
-from operator import itemgetter
+from collections import OrderedDict
+from bika.lims import api
from bika.lims import bikaMessageFactory as _
from bika.lims.browser.analyses.view import AnalysesView
from bika.lims.config import QCANALYSIS_TYPES
+from bika.lims.interfaces import IDuplicateAnalysis
+from bika.lims.utils import get_image
+from bika.lims.utils import get_link
class QCAnalysesView(AnalysesView):
@@ -36,81 +40,89 @@ class QCAnalysesView(AnalysesView):
"""
def __init__(self, context, request, **kwargs):
- AnalysesView.__init__(self, context, request, **kwargs)
- self.columns['getReferenceAnalysesGroupID'] = {
- 'title': _('QC Sample ID'),
- 'sortable': False}
- self.columns['Worksheet'] = {'title': _('Worksheet'),
- 'sortable': False}
- self.review_states[0]['columns'] = ['Service',
- 'Worksheet',
- 'getReferenceAnalysesGroupID',
- 'Partition',
- 'Method',
- 'Instrument',
- 'Result',
- 'Uncertainty',
- 'CaptureDate',
- 'DueDate',
- 'state_title']
-
- qcanalyses = context.getQCAnalyses()
- asuids = [an.UID() for an in qcanalyses]
- self.contentFilter = {'UID': asuids,
- 'sort_on': 'getId'}
- self.icon = self.portal_url + \
- "/++resource++bika.lims.images/referencesample.png"
-
- # TODO-performance: Do not use object. Using brain, use meta_type in
- # order to get the object's type
- def folderitem(self, obj, item, index):
- """Prepare a data item for the listing.
+ super(QCAnalysesView, self).__init__(context, request, **kwargs)
+
+ icon_path = "/++resource++bika.lims.images/referencesample.png"
+ self.icon = "{}{}".format(self.portal_url, icon_path)
+
+ # Add Worksheet and QC Sample ID columns
+ new_columns = OrderedDict((
+ ("Worksheet", {
+ "title": _("Worksheet"),
+ "sortable": True,
+ }),
+ ("getReferenceAnalysesGroupID", {
+ "title": _('QC Sample ID'),
+ "sortable": False,
+ }),
+ ("Parent", {
+ "title": _("Source"),
+ "sortable": False,
+ })
+ ))
+ self.columns.update(new_columns)
+
+ # Remove unnecessary columns
+ if "Hidden" in self.columns:
+ del(self.columns["Hidden"])
+
+ # Remove filters (Valid, Invalid, All)
+ self.review_states = [self.review_states[0]]
- :param obj: The catalog brain or content object
- :param item: Listing item (dictionary)
- :param index: Index of the listing item
- :returns: Augmented listing data item
+ # Apply the columns to all review_states
+ for review_state in self.review_states:
+ review_state.update({"columns": self.columns.keys()})
+
+ def update(self):
+ """Update hook
"""
+ super(AnalysesView, self).update()
+ # Update the query with the QC Analyses uids
+ qc_uids = map(api.get_uid, self.context.getQCAnalyses())
+ self.contentFilter.update({
+ "UID": qc_uids,
+ "portal_type": ["DuplicateAnalysis", "ReferenceAnalysis"],
+ "sort_on": "sortable_title"
+ })
- obj = obj.getObject()
- # Group items by RefSample - Worksheet - Position
- ws = obj.getWorksheet()
- wsid = ws and ws.id or ''
- wshref = ws.absolute_url() or None
- if wshref:
- item['replace']['Worksheet'] = "%s" % (
- wshref, wsid)
-
- imgtype = ""
- if obj.portal_type == 'ReferenceAnalysis':
- antype = QCANALYSIS_TYPES.getValue(obj.getReferenceType())
- if obj.getReferenceType() == 'c':
- imgtype = "
" % (
- antype, self.context.absolute_url())
- if obj.getReferenceType() == 'b':
- imgtype = "
" % (
- antype, self.context.absolute_url())
- item['replace']['Partition'] = "%s" % (
- obj.aq_parent.absolute_url(), obj.aq_parent.id)
- elif obj.portal_type == 'DuplicateAnalysis':
- antype = QCANALYSIS_TYPES.getValue('d')
- imgtype = "
" % (
- antype, self.context.absolute_url())
- item['sortcode'] = '%s_%s' % (obj.getRequestID(), obj.getKeyword())
-
- item['before']['Service'] = imgtype
- item['sortcode'] = '%s_%s' % (obj.getReferenceAnalysesGroupID(),
- obj.getKeyword())
- return item
+ def is_analysis_edition_allowed(self, analysis_brain):
+ """Overwrite this method to ensure the table is recognized as readonly
+
+ XXX: why is the super method not recognizing `self.allow_edit`?
+ """
+ return False
- def folderitems(self):
- items = AnalysesView.folderitems(self)
- # Sort items
- items = sorted(items, key=itemgetter('sortcode'))
- return items
+ def folderitem(self, obj, item, index):
+ item = super(QCAnalysesView, self).folderitem(obj, item, index)
+
+ obj = self.get_object(obj)
+
+ # Fill Worksheet cell
+ worksheet = obj.getWorksheet()
+ if not worksheet:
+ return item
+
+ # Fill the Worksheet cell
+ ws_id = api.get_id(worksheet)
+ ws_url = api.get_url(worksheet)
+ item["replace"]["Worksheet"] = get_link(ws_url, value=ws_id)
+
+ if IDuplicateAnalysis.providedBy(obj):
+ an_type = "d"
+ img_name = "duplicate.png"
+ parent = obj.getRequest()
+ else:
+ an_type = obj.getReferenceType()
+ img_name = an_type == "c" and "control.png" or "blank.png"
+ parent = obj.aq_parent
+
+ # Render the image
+ an_type = QCANALYSIS_TYPES.getValue(an_type)
+ item['before']['Service'] = get_image(img_name, title=an_type)
+
+ # Fill the Parent cell
+ parent_url = api.get_url(parent)
+ parent_id = api.get_id(parent)
+ item["replace"]["Parent"] = get_link(parent_url, value=parent_id)
+
+ return item
diff --git a/bika/lims/content/analysisrequest.py b/bika/lims/content/analysisrequest.py
index 8f97cc101c..8e4c82ba23 100644
--- a/bika/lims/content/analysisrequest.py
+++ b/bika/lims/content/analysisrequest.py
@@ -45,6 +45,7 @@
from bika.lims.browser.widgets.durationwidget import DurationWidget
from bika.lims.catalog import CATALOG_ANALYSIS_LISTING
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
+from bika.lims.catalog import CATALOG_WORKSHEET_LISTING
from bika.lims.catalog.bika_catalog import BIKA_CATALOG
from bika.lims.config import PRIORITIES
from bika.lims.config import PROJECTNAME
@@ -1804,59 +1805,50 @@ def current_date(self):
# noinspection PyCallingNonCallable
return DateTime()
- def getQCAnalyses(self, qctype=None, review_state=None):
- """return the QC analyses performed in the worksheet in which, at
- least, one sample of this AR is present.
-
- Depending on qctype value, returns the analyses of:
+ def getWorksheets(self, full_objects=False):
+ """Returns the worksheets that contains analyses from this Sample
+ """
+ # Get the Analyses UIDs of this Sample
+ analyses_uids = map(api.get_uid, self.getAnalyses())
+ if not analyses_uids:
+ return []
- - 'b': all Blank Reference Samples used in related worksheet/s
- - 'c': all Control Reference Samples used in related worksheet/s
- - 'd': duplicates only for samples contained in this AR
+ # Get the worksheets that contain any of these analyses
+ query = dict(getAnalysesUIDs=analyses_uids)
+ worksheets = api.search(query, CATALOG_WORKSHEET_LISTING)
+ if full_objects:
+ worksheets = map(api.get_object, worksheets)
+ return worksheets
- If qctype==None, returns all type of qc analyses mentioned above
+ def getQCAnalyses(self, review_state=None):
+ """Returns the Quality Control analyses assigned to worksheets that
+ contains analyses from this Sample
"""
- qcanalyses = []
- suids = []
- ans = self.getAnalyses()
- wf = getToolByName(self, 'portal_workflow')
- for an in ans:
- an = an.getObject()
- if an.getServiceUID() not in suids:
- suids.append(an.getServiceUID())
+ # Get the worksheet uids
+ worksheet_uids = map(api.get_uid, self.getWorksheets())
+ if not worksheet_uids:
+ return []
- def valid_dup(wan):
- if wan.portal_type == 'ReferenceAnalysis':
- return False
- an_state = wf.getInfoFor(wan, 'review_state')
- return \
- wan.portal_type == 'DuplicateAnalysis' \
- and wan.getRequestID() == self.id \
- and (review_state is None or an_state in review_state)
-
- def valid_ref(wan):
- if wan.portal_type != 'ReferenceAnalysis':
- return False
- an_state = wf.getInfoFor(wan, 'review_state')
- an_reftype = wan.getReferenceType()
- return wan.getServiceUID() in suids \
- and wan not in qcanalyses \
- and (qctype is None or an_reftype == qctype) \
- and (review_state is None or an_state in review_state)
-
- for an in ans:
- an = an.getObject()
- ws = an.getWorksheet()
- if not ws:
- continue
- was = ws.getAnalyses()
- for wa in was:
- if valid_dup(wa):
- qcanalyses.append(wa)
- elif valid_ref(wa):
- qcanalyses.append(wa)
-
- return qcanalyses
+ # Get reference qc analyses from these worksheets
+ query = dict(portal_type="ReferenceAnalysis",
+ getWorksheetUID=worksheet_uids)
+ qc_analyses = api.search(query, CATALOG_ANALYSIS_LISTING)
+
+ # Extend with duplicate qc analyses from these worksheets and Sample
+ query = dict(portal_type="DuplicateAnalysis",
+ getWorksheetUID=worksheet_uids,
+ getAncestorsUIDs=[api.get_uid(self)])
+ qc_analyses += api.search(query, CATALOG_ANALYSIS_LISTING)
+
+ # Bail out analyses with a different review_state
+ if review_state:
+ qc_analyses = filter(
+ lambda an: api.get_review_status(an) in review_state,
+ qc_analyses
+ )
+
+ # Return the objects
+ return map(api.get_object, qc_analyses)
def isInvalid(self):
"""return if the Analysis Request has been invalidated