Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QC Analyses listing appears empty in Sample view #1512

Merged
merged 9 commits into from
Jan 28, 2020
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
162 changes: 87 additions & 75 deletions bika/lims/browser/analyses/qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do it the other way around. Put this into the __init__ and flush it eventually when there are no QC analyses in update.

("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'] = "<a href='%s'>%s</a>" % (
wshref, wsid)

imgtype = ""
if obj.portal_type == 'ReferenceAnalysis':
antype = QCANALYSIS_TYPES.getValue(obj.getReferenceType())
if obj.getReferenceType() == 'c':
imgtype = "<img title='%s' " \
"src='%s/++resource++bika.lims.images/control.png" \
"'/>&nbsp;" % (
antype, self.context.absolute_url())
if obj.getReferenceType() == 'b':
imgtype = "<img title='%s' " \
"src='%s/++resource++bika.lims.images/blank.png" \
"'/>&nbsp;" % (
antype, self.context.absolute_url())
item['replace']['Partition'] = "<a href='%s'>%s</a>" % (
obj.aq_parent.absolute_url(), obj.aq_parent.id)
elif obj.portal_type == 'DuplicateAnalysis':
antype = QCANALYSIS_TYPES.getValue('d')
imgtype = "<img title='%s' " \
"src='%s/++resource++bika.lims.images/duplicate.png" \
"'/>&nbsp;" % (
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
90 changes: 41 additions & 49 deletions bika/lims/content/analysisrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down