def __call__(self): ar = self.context workflow = getToolByName(ar, 'portal_workflow') # If is a retracted AR, show the link to child AR and show a warn msg if workflow.getInfoFor(ar, 'review_state') == 'invalid': childar = hasattr(ar, 'getChildAnalysisRequest') \ and ar.getChildAnalysisRequest() or None childid = childar and childar.getRequestID() or None message = _('This Analysis Request has been withdrawn and is shown ' 'for trace-ability purposes only. Retest: ${retest_child_id}.', mapping={"retest_child_id":childid if childid else ''}) self.context.plone_utils.addPortalMessage(message, 'warning') # If is an AR automatically generated due to a Retraction, show it's # parent AR information if hasattr(ar, 'getParentAnalysisRequest') \ and ar.getParentAnalysisRequest(): par = ar.getParentAnalysisRequest() message = _( 'This Analysis Request has been generated automatically due to ' 'the retraction of the Analysis Request ${retracted_request_id}.', mapping={"retracted_request_id": par.getRequestID()}) self.context.plone_utils.addPortalMessage(message, 'info') can_do = getSecurityManager().checkPermission(ResultsNotRequested, ar) if workflow.getInfoFor(ar, 'cancellation_state') == "cancelled": self.request.response.redirect(ar.absolute_url()) elif not(can_do): self.request.response.redirect(ar.absolute_url()) else: return self.template()
def __call__(self): ar = self.context workflow = getToolByName(ar, 'portal_workflow') # If is a retracted AR, show the link to child AR and show a warn msg if workflow.getInfoFor(ar, 'review_state') == 'invalid': childar = hasattr(ar, 'getChildAnalysisRequest') \ and ar.getChildAnalysisRequest() or None childid = childar and childar.getRequestID() or None message = _( 'This Analysis Request has been withdrawn and is shown ' 'for trace-ability purposes only. Retest: ${retest_child_id}.', mapping={"retest_child_id": childid if childid else ''}) self.context.plone_utils.addPortalMessage(message, 'warning') # If is an AR automatically generated due to a Retraction, show it's # parent AR information if hasattr(ar, 'getParentAnalysisRequest') \ and ar.getParentAnalysisRequest(): par = ar.getParentAnalysisRequest() message = _( 'This Analysis Request has been generated automatically due to ' 'the retraction of the Analysis Request ${retracted_request_id}.', mapping={"retracted_request_id": par.getRequestID()}) self.context.plone_utils.addPortalMessage(message, 'info') can_do = getSecurityManager().checkPermission(ResultsNotRequested, ar) if workflow.getInfoFor(ar, 'cancellation_state') == "cancelled": self.request.response.redirect(ar.absolute_url()) elif not (can_do): self.request.response.redirect(ar.absolute_url()) else: return self.template()
def render_field_view(self, field): fieldname = field.getName() field = self.context.Schema()[fieldname] ret = {'fieldName': fieldname, 'mode': 'view'} try: adapter = getAdapter(self.context, interface=IHeaderTableFieldRenderer, name=fieldname) except ComponentLookupError: adapter = None if adapter: ret = {'fieldName': fieldname, 'mode': 'structure', 'html': adapter(field)} else: if field.getType().find("ool") > -1: value = field.get(self.context) ret = {'fieldName': fieldname, 'mode': 'structure', 'html': t(_('Yes')) if value else t(_('No')) } elif field.getType().find("Reference") > -1: # Prioritize method retrieval over schema's field targets = None if hasattr(self.context, 'get%s' % fieldname): fieldaccessor = getattr(self.context, 'get%s' % fieldname) if callable(fieldaccessor): targets = fieldaccessor() if not targets: targets = field.get(self.context) if targets: if not type(targets) == list: targets = [targets,] sm = getSecurityManager() if all([sm.checkPermission(view, ta) for ta in targets]): a = ["<a href='%s'>%s</a>" % (target.absolute_url(), target.Title()) for target in targets] ret = {'fieldName': fieldname, 'mode': 'structure', 'html': ", ".join(a)} else: ret = {'fieldName': fieldname, 'mode': 'structure', 'html': ", ".join([ta.Title() for ta in targets])} else: ret = {'fieldName': fieldname, 'mode': 'structure', 'html': ''} elif field.getType().lower().find('datetime') > -1: value = field.get(self.context) ret = {'fieldName': fieldname, 'mode': 'structure', 'html': self.ulocalized_time(value, long_format=True) } return ret
def __call__(self): ar = self.context workflow = getToolByName(ar, 'portal_workflow') if workflow.getInfoFor(ar, 'cancellation_state') == "cancelled": self.request.response.redirect(ar.absolute_url()) elif not (getSecurityManager().checkPermission(EditResults, ar)): self.request.response.redirect(ar.absolute_url()) else: self.tables = {} show_cats = self.context.bika_setup.getCategoriseAnalysisServices() for poc in POINTS_OF_CAPTURE: if self.context.getAnalyses(getPointOfCapture=poc): t = self.createAnalysesView(ar, self.request, getPointOfCapture=poc, sort_on='getServiceTitle', show_categories=show_cats) t.form_id = "ar_manage_results_%s" % poc t.allow_edit = True t.review_states[0]['transitions'] = [{ 'id': 'submit' }, { 'id': 'retract' }, { 'id': 'verify' }] t.show_select_column = True poc_value = POINTS_OF_CAPTURE.getValue(poc) self.tables[poc_value] = t.contents_table() # If a general retracted is done, rise a waring if workflow.getInfoFor(ar, 'review_state') == 'sample_received': allstatus = list() for analysis in ar.getAnalyses(): status = workflow.getInfoFor(analysis.getObject(), 'review_state') if status not in [ 'retracted', 'to_be_verified', 'verified' ]: allstatus = [] break else: allstatus.append(status) if len(allstatus) > 0: message = "General Retract Done" self.context.plone_utils.addPortalMessage( message, 'warning') self.checkInstrumentsValidity() return self.template()
def __call__(self): CheckAuthenticator(self.request) field = self.context.Schema()["Remarks"] value = self.request['value'].strip() + "\n\n" existing = self.context.getRemarks(mimetype='text/x-web-intelligent').strip() date = DateTime().rfc822() user = getSecurityManager().getUser() divider = "=== %s (%s)\n" % (date, user) remarks = convertWebIntelligentPlainTextToHtml(divider) + \ convertWebIntelligentPlainTextToHtml(value) + \ convertWebIntelligentPlainTextToHtml(existing) self.context.setRemarks(divider + value + existing, mimetype='text/x-web-intelligent') return remarks.strip()
def __call__(self): CheckAuthenticator(self.request) field = self.context.Schema()["Remarks"] value = self.request['value'].strip() + "\n\n" existing = self.context.getRemarks( mimetype='text/x-web-intelligent').strip() date = DateTime().rfc822() user = getSecurityManager().getUser() divider = "=== %s (%s)\n" % (date, user) remarks = convertWebIntelligentPlainTextToHtml(divider) + \ convertWebIntelligentPlainTextToHtml(value) + \ convertWebIntelligentPlainTextToHtml(existing) self.context.setRemarks(divider + value + existing, mimetype='text/x-web-intelligent') return remarks.strip()
def __call__(self): ar = self.context workflow = getToolByName(ar, 'portal_workflow') if workflow.getInfoFor(ar, 'cancellation_state') == "cancelled": self.request.response.redirect(ar.absolute_url()) elif not(getSecurityManager().checkPermission(EditResults, ar)): self.request.response.redirect(ar.absolute_url()) else: self.tables = {} show_cats = self.context.bika_setup.getCategoriseAnalysisServices() for poc in POINTS_OF_CAPTURE: if self.context.getAnalyses(getPointOfCapture=poc): t = self.createAnalysesView(ar, self.request, getPointOfCapture=poc, sort_on='getServiceTitle', show_categories=show_cats) t.form_id = "ar_manage_results_%s" % poc t.allow_edit = True t.review_states[0]['transitions'] = [{'id': 'submit'}, {'id': 'retract'}, {'id': 'verify'}] t.show_select_column = True poc_value = POINTS_OF_CAPTURE.getValue(poc) self.tables[poc_value] = t.contents_table() # If a general retracted is done, rise a waring if workflow.getInfoFor(ar, 'review_state') == 'sample_received': allstatus = list() for analysis in ar.getAnalyses(): status = workflow.getInfoFor(analysis.getObject(), 'review_state') if status not in ['retracted','to_be_verified','verified']: allstatus = [] break else: allstatus.append(status) if len(allstatus) > 0: message = "General Retract Done" self.context.plone_utils.addPortalMessage(message, 'warning') self.checkInstrumentsValidity() return self.template()
# Can't do anything to the object if it's cancelled # reference and duplicate analyses don't have cancellation_state #if context.portal_type == "Analysis": if workflow.getInfoFor(context, 'cancellation_state', 'active') == "cancelled": return False # All kinds of analyses get their submitter and verifier compared if context.portal_type in ("ReferenceAnalysis", "DuplicateAnalysis"): # May we verify results that we ourself submitted? if checkPermission(VerifyOwnResults, context): return True # Check for self-submitted Analysis. user_id = getSecurityManager().getUser().getId() self_submitted = False review_history = workflow.getInfoFor(context, 'review_history') review_history = context.reverseList(review_history) for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: return False if context.portal_type == "AnalysisRequest": if not checkPermission(Verify, context): # Allow automatic verify (Disregard permission)
def folderitems(self): """Accumulate a list of all AnalysisRequest objects contained in this Batch, as well as those which are inherited. """ wf = getToolByName(self.context, 'portal_workflow') schema = self.context.Schema() ars = [] for o in schema.getField('InheritedObjects').get(self.context): if o.portal_type == 'AnalysisRequest': if o not in ars: ars.append(o) elif o.portal_type == 'Batch': for ar in o.getAnalysisRequests(): if ar not in ars: ars.append(ar) for ar in self.context.getAnalysisRequests(): if ar not in ars: ars.append(ar) self.categories = [] analyses = {} items = [] services = [] keywords = [] for ar in ars: analyses[ar.id] = [] for analysis in ar.getAnalyses(full_objects=True): analyses[ar.id].append(analysis) service = analysis.getService() if service.getKeyword() not in keywords: # we use a keyword check, because versioned services are !=. keywords.append(service.getKeyword()) services.append(service) batchlink = "" batch = ar.getBatch() if batch: batchlink = "<a href='%s'>%s</a>" % ( batch.absolute_url(), batch.Title()) arlink = "<a href='%s'>%s</a>" % ( ar.absolute_url(), ar.Title()) subgroup = ar.Schema()['SubGroup'].get(ar) sub_title = subgroup.Title() if subgroup else 'No Subgroup' sub_sort = subgroup.getSortKey() if subgroup else '1' sub_class = re.sub(r"[^A-Za-z\w\d\-\_]", '', sub_title) if [sub_sort, sub_title] not in self.categories: self.categories.append([sub_sort, sub_title]) review_state = wf.getInfoFor(ar, 'review_state') state_title = wf.getTitleForStateOnType( review_state, 'AnalysisRequest') item = { 'obj': ar, 'id': ar.id, 'uid': ar.UID(), 'category': sub_title, 'title': ar.Title(), 'type_class': 'contenttype-AnalysisRequest', 'url': ar.absolute_url(), 'relative_url': ar.absolute_url(), 'view_url': ar.absolute_url(), 'created': self.ulocalized_time(ar.created()), 'sort_key': ar.created(), 'replace': { 'Batch': batchlink, 'AnalysisRequest': arlink, }, 'before': {}, 'after': {}, 'choices': {}, 'class': {'Batch': 'Title'}, 'state_class': 'state-active subgroup_{0}'.format(sub_class) if sub_class else 'state-active', 'allow_edit': [], 'Batch': '', 'SamplePoint': ar.getSamplePoint().Title() if ar.getSamplePoint() else '', 'SampleType': ar.getSampleType().Title() if ar.getSampleType() else '', 'ClientOrderNumber': ar.getClientOrderNumber(), 'AnalysisRequest': '', 'state_title': state_title, } items.append(item) unitstr = '<em class="discreet" style="white-space:nowrap;">%s</em>' checkPermission = getSecurityManager().checkPermission # Insert columns for analyses for service in services: keyword = service.getKeyword() self.columns[keyword] = { 'title': service.ShortTitle if service.ShortTitle else service.title, 'sortable': True } self.review_states[0]['columns'].insert( len(self.review_states[0]['columns']) - 1, keyword) # Insert values for analyses for i, item in enumerate(items): for analysis in analyses[item['id']]: if keyword not in items[i]: items[i][keyword] = '' if analysis.getKeyword() != keyword: continue edit = checkPermission(EditResults, analysis) calculation = analysis.getService().getCalculation() if self.allow_edit and edit and not calculation: items[i]['allow_edit'].append(keyword) if not self.insert_submit_button: self.insert_submit_button = True value = analysis.getResult() items[i][keyword] = value items[i]['class'][keyword] = '' if value or (edit and not calculation): unit = unitstr % service.getUnit() items[i]['after'][keyword] = unit if keyword not in items[i]['class']: items[i]['class'][keyword] = 'empty' if self.insert_submit_button: custom_actions = self.review_states[0].get('custom_actions', []) custom_actions.append({'id': 'submit'}) self.review_states[0]['custom_actions'] = custom_actions self.categories.sort() self.categories = [x[1] for x in self.categories] items = sorted(items, key=itemgetter("sort_key")) return items
def folderitems(self, full_objects=False): workflow = getToolByName(self.context, "portal_workflow") items = BikaListingView.folderitems(self) mtool = getToolByName(self.context, 'portal_membership') member = mtool.getAuthenticatedMember() roles = member.getRoles() hideclientlink = 'RegulatoryInspector' in roles \ and 'Manager' not in roles \ and 'LabManager' not in roles \ and 'LabClerk' not in roles for x in range(len(items)): if 'obj' not in items[x]: continue obj = items[x]['obj'] sample = obj.getSample() if getSecurityManager().checkPermission(EditResults, obj): url = obj.absolute_url() + "/manage_results" else: url = obj.absolute_url() items[x]['Client'] = obj.aq_parent.Title() if (hideclientlink is False): items[x]['replace']['Client'] = "<a href='%s'>%s</a>" % \ (obj.aq_parent.absolute_url(), obj.aq_parent.Title()) items[x]['Creator'] = self.user_fullname(obj.Creator()) items[x]['getRequestID'] = obj.getRequestID() items[x]['replace']['getRequestID'] = "<a href='%s'>%s</a>" % \ (url, items[x]['getRequestID']) items[x]['getSample'] = sample items[x]['replace']['getSample'] = \ "<a href='%s'>%s</a>" % (sample.absolute_url(), sample.Title()) if obj.getAnalysesNum(): items[x]['getAnalysesNum'] = str( obj.getAnalysesNum()[0]) + '/' + str( obj.getAnalysesNum()[1]) else: items[x]['getAnalysesNum'] = '' batch = obj.getBatch() if batch: items[x]['BatchID'] = batch.getBatchID() items[x]['replace']['BatchID'] = "<a href='%s'>%s</a>" % \ (batch.absolute_url(), items[x]['BatchID']) else: items[x]['BatchID'] = '' val = obj.Schema().getField('SubGroup').get(obj) items[x]['SubGroup'] = val.Title() if val else '' samplingdate = obj.getSample().getSamplingDate() items[x]['SamplingDate'] = self.ulocalized_time(samplingdate, long_format=1) items[x]['getDateReceived'] = self.ulocalized_time( obj.getDateReceived()) items[x]['getDatePublished'] = self.ulocalized_time( obj.getDatePublished()) deviation = sample.getSamplingDeviation() items[x]['SamplingDeviation'] = deviation and deviation.Title( ) or '' priority = obj.getPriority() items[x]['Priority'] = '' # priority.Title() items[x]['getStorageLocation'] = sample.getStorageLocation( ) and sample.getStorageLocation().Title() or '' items[x]['AdHoc'] = sample.getAdHoc() and True or '' after_icons = "" state = workflow.getInfoFor(obj, 'worksheetanalysis_review_state') if state == 'assigned': after_icons += "<img src='%s/++resource++bika.lims.images/worksheet.png' title='%s'/>" % \ (self.portal_url, t(_("All analyses assigned"))) if workflow.getInfoFor(obj, 'review_state') == 'invalid': after_icons += "<img src='%s/++resource++bika.lims.images/delete.png' title='%s'/>" % \ (self.portal_url, t(_("Results have been withdrawn"))) if obj.getLate(): after_icons += "<img src='%s/++resource++bika.lims.images/late.png' title='%s'>" % \ (self.portal_url, t(_("Late Analyses"))) if samplingdate > DateTime(): after_icons += "<img src='%s/++resource++bika.lims.images/calendar.png' title='%s'>" % \ (self.portal_url, t(_("Future dated sample"))) if obj.getInvoiceExclude(): after_icons += "<img src='%s/++resource++bika.lims.images/invoice_exclude.png' title='%s'>" % \ (self.portal_url, t(_("Exclude from invoice"))) if sample.getSampleType().getHazardous(): after_icons += "<img src='%s/++resource++bika.lims.images/hazardous.png' title='%s'>" % \ (self.portal_url, t(_("Hazardous"))) if after_icons: items[x]['after']['getRequestID'] = after_icons items[x]['Created'] = self.ulocalized_time(obj.created()) contact = obj.getContact() if contact: items[x]['ClientContact'] = contact.Title() items[x]['replace']['ClientContact'] = "<a href='%s'>%s</a>" % \ (contact.absolute_url(), contact.Title()) else: items[x]['ClientContact'] = "" SamplingWorkflowEnabled = sample.getSamplingWorkflowEnabled() if SamplingWorkflowEnabled and not samplingdate > DateTime(): datesampled = self.ulocalized_time(sample.getDateSampled()) if not datesampled: datesampled = self.ulocalized_time(DateTime(), long_format=1) items[x]['class']['getDateSampled'] = 'provisional' sampler = sample.getSampler().strip() if sampler: items[x]['replace']['getSampler'] = self.user_fullname( sampler) if 'Sampler' in member.getRoles() and not sampler: sampler = member.id items[x]['class']['getSampler'] = 'provisional' else: datesampled = '' sampler = '' items[x]['getDateSampled'] = datesampled items[x]['getSampler'] = sampler # sampling workflow - inline edits for Sampler and Date Sampled checkPermission = self.context.portal_membership.checkPermission state = workflow.getInfoFor(obj, 'review_state') if state == 'to_be_sampled' \ and checkPermission(SampleSample, obj) \ and not samplingdate > DateTime(): items[x]['required'] = ['getSampler', 'getDateSampled'] items[x]['allow_edit'] = ['getSampler', 'getDateSampled'] samplers = getUsers(sample, ['Sampler', 'LabManager', 'Manager']) username = member.getUserName() users = [({ 'ResultValue': u, 'ResultText': samplers.getValue(u) }) for u in samplers] items[x]['choices'] = {'getSampler': users} Sampler = sampler and sampler or \ (username in samplers.keys() and username) or '' items[x]['getSampler'] = Sampler # These don't exist on ARs # XXX This should be a list of preservers... items[x]['getPreserver'] = '' items[x]['getDatePreserved'] = '' # inline edits for Preserver and Date Preserved checkPermission = self.context.portal_membership.checkPermission if checkPermission(PreserveSample, obj): items[x]['required'] = ['getPreserver', 'getDatePreserved'] items[x]['allow_edit'] = ['getPreserver', 'getDatePreserved'] preservers = getUsers(obj, ['Preserver', 'LabManager', 'Manager']) username = member.getUserName() users = [({ 'ResultValue': u, 'ResultText': preservers.getValue(u) }) for u in preservers] items[x]['choices'] = {'getPreserver': users} preserver = username in preservers.keys() and username or '' items[x]['getPreserver'] = preserver items[x]['getDatePreserved'] = self.ulocalized_time( DateTime(), long_format=1) items[x]['class']['getPreserver'] = 'provisional' items[x]['class']['getDatePreserved'] = 'provisional' # Submitting user may not verify results if items[x]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): self_submitted = False try: review_history = list( workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == member.getId(): self_submitted = True break if self_submitted: items[x]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ t(_("Cannot verify: Submitted by current user")) except Exception: pass # Hide Preservation/Sampling workflow actions if the edit columns # are not displayed. toggle_cols = self.get_toggle_cols() new_states = [] for i, state in enumerate(self.review_states): if state['id'] == self.review_state: if 'getSampler' not in toggle_cols \ or 'getDateSampled' not in toggle_cols: if 'hide_transitions' in state: state['hide_transitions'].append('sample') else: state['hide_transitions'] = [ 'sample', ] if 'getPreserver' not in toggle_cols \ or 'getDatePreserved' not in toggle_cols: if 'hide_transitions' in state: state['hide_transitions'].append('preserve') else: state['hide_transitions'] = [ 'preserve', ] new_states.append(state) self.review_states = new_states return items
def create(self, context, request): """/@@API/create: Create new object. Required parameters: - obj_type = portal_type of new object. - obj_path = path of new object, from plone site root. - Not required for obj_type=AnalysisRequest Optionally: - obj_id = ID of new object. All other parameters in the request are matched against the object's Schema. If a matching field is found in the schema, then the value is taken from the request and sent to the field's mutator. Reference fields may have their target value(s) specified with a delimited string query syntax, containing the portal_catalog search: <FieldName>=index1:value1|index2:value2 eg to set the Client of a batch: ...@@API/update?obj_path=<path>... ...&Client=title:<client_title>&... And, to set a multi-valued reference, these both work: ...@@API/update?obj_path=<path>... ...&InheritedObjects:list=title:AR1... ...&InheritedObjects:list=title:AR2... ...@@API/update?obj_path=<path>... ...&InheritedObjects[]=title:AR1... ...&InheritedObjects[]=title:AR2... The Analysis_Specification parameter is special, it mimics the format of the python dictionaries, and only service Keyword can be used to reference services. Even if the keyword is not actively required, it must be supplied: <service_keyword>:min:max:error tolerance The function returns a dictionary as a json string: { runtime: Function running time. error: true or string(message) if error. false if no error. success: true or string(message) if success. false if no success. } >>> portal = layer['portal'] >>> portal_url = portal.absolute_url() >>> from plone.app.testing import SITE_OWNER_NAME >>> from plone.app.testing import SITE_OWNER_PASSWORD Simple AR creation, no obj_path parameter is required: >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create", "&".join([ ... "obj_type=AnalysisRequest", ... "Client=portal_type:Client|id:client-1", ... "SampleType=portal_type:SampleType|title:Apple Pulp", ... "Contact=portal_type:Contact|getFullname:Rita Mohale", ... "Services:list=portal_type:AnalysisService|title:Calcium", ... "Services:list=portal_type:AnalysisService|title:Copper", ... "Services:list=portal_type:AnalysisService|title:Magnesium", ... "SamplingDate=2013-09-29", ... "Specification=portal_type:AnalysisSpec|title:Apple Pulp", ... ])) >>> browser.contents '{..."success": true...}' If some parameters are specified and are not located as existing fields or properties of the created instance, the create should fail: >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create?", "&".join([ ... "obj_type=Batch", ... "obj_path=/batches", ... "title=Test", ... "Thing=Fish" ... ])) >>> browser.contents '{...The following request fields were not used: ...Thing...}' Now we test that the AR create also fails if some fields are spelled wrong >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create", "&".join([ ... "obj_type=AnalysisRequest", ... "thing=Fish", ... "Client=portal_type:Client|id:client-1", ... "SampleType=portal_type:SampleType|title:Apple Pulp", ... "Contact=portal_type:Contact|getFullname:Rita Mohale", ... "Services:list=portal_type:AnalysisService|title:Calcium", ... "Services:list=portal_type:AnalysisService|title:Copper", ... "Services:list=portal_type:AnalysisService|title:Magnesium", ... "SamplingDate=2013-09-29" ... ])) >>> browser.contents '{...The following request fields were not used: ...thing...}' """ savepoint = transaction.savepoint() self.context = context self.request = request self.unused = [x for x in self.request.form.keys()] self.used("form.submitted") self.used("__ac_name") self.used("__ac_password") # always require obj_type self.require("obj_type") obj_type = self.request['obj_type'] self.used("obj_type") # AnalysisRequest shortcut: creates Sample, Partition, AR, Analyses. if obj_type == "AnalysisRequest": try: return self._create_ar(context, request) except: savepoint.rollback() raise # Other object types require explicit path as their parent self.require("obj_path") obj_path = self.request['obj_path'] if not obj_path.startswith("/"): obj_path = "/" + obj_path self.used("obj_path") site_path = request['PATH_INFO'].replace("/@@API/create", "") parent = context.restrictedTraverse(str(site_path + obj_path)) # normal permissions still apply for this user if not getSecurityManager().checkPermission(AccessJSONAPI, parent): msg = "You don't have the '{0}' permission on {1}".format( AccessJSONAPI, parent.absolute_url()) raise Unauthorized(msg) obj_id = request.get("obj_id", "") _renameAfterCreation = False if not obj_id: _renameAfterCreation = True obj_id = tmpID() self.used(obj_id) ret = { "url": router.url_for("create", force_external=True), "success": True, "error": False, } try: obj = _createObjectByType(obj_type, parent, obj_id) obj.unmarkCreationFlag() if _renameAfterCreation: renameAfterCreation(obj) ret['obj_id'] = obj.getId() used_fields = set_fields_from_request(obj, request) for field in used_fields: self.used(field) obj.reindexObject() obj.aq_parent.reindexObject() event.notify(ObjectInitializedEvent(obj)) obj.at_post_create_script() except: savepoint.rollback() raise if self.unused: raise BadRequest("The following request fields were not used: %s. Request aborted." % self.unused) return ret
def __call__(self): ar = self.context workflow = getToolByName(self.context, 'portal_workflow') if 'transition' in self.request.form: doActionFor(self.context, self.request.form['transition']) # Contacts get expanded for view contact = self.context.getContact() contacts = [] for cc in self.context.getCCContact(): contacts.append(cc) if contact in contacts: contacts.remove(contact) ccemails = [] for cc in contacts: ccemails.append("%s <<a href='mailto:%s'>%s</a>>" % (cc.Title(), cc.getEmailAddress(), cc.getEmailAddress())) # CC Emails become mailto links emails = self.context.getCCEmails() if isinstance(emails, str): emails = emails and [emails, ] or [] cc_emails = [] cc_hrefs = [] for cc in emails: cc_emails.append(cc) cc_hrefs.append("<a href='mailto:%s'>%s</a>" % (cc, cc)) # render header table self.header_table = HeaderTableView(self.context, self.request)() # Create Partitions View for this ARs sample p = SamplePartitionsView(self.context.getSample(), self.request) p.show_column_toggles = False self.parts = p.contents_table() # Create Field and Lab Analyses tables self.tables = {} for poc in POINTS_OF_CAPTURE: if self.context.getAnalyses(getPointOfCapture=poc): t = self.createAnalysesView(ar, self.request, getPointOfCapture=poc, show_categories=self.context.bika_setup.getCategoriseAnalysisServices()) t.allow_edit = True t.form_id = "%s_analyses" % poc t.review_states[0]['transitions'] = [{'id': 'submit'}, {'id': 'retract'}, {'id': 'verify'}] t.show_workflow_action_buttons = True t.show_select_column = True if getSecurityManager().checkPermission(EditFieldResults, self.context) \ and poc == 'field': t.review_states[0]['columns'].remove('DueDate') self.tables[POINTS_OF_CAPTURE.getValue(poc)] = t.contents_table() # Un-captured field analyses may cause confusion if ar.getAnalyses(getPointOfCapture='field', review_state=['sampled', 'sample_due']): message = _("There are field analyses without submitted results.") self.addMessage(message, 'info') # Create QC Analyses View for this AR show_cats = self.context.bika_setup.getCategoriseAnalysisServices() qcview = self.createQCAnalyesView(ar, self.request, show_categories=show_cats) qcview.allow_edit = False qcview.show_select_column = False qcview.show_workflow_action_buttons = False qcview.form_id = "%s_qcanalyses" qcview.review_states[0]['transitions'] = [{'id': 'submit'}, {'id': 'retract'}, {'id': 'verify'}] self.qctable = qcview.contents_table() # Create the ResultsInterpretation by department view from dependencies.dependency import ARResultsInterpretationView self.riview = ARResultsInterpretationView(ar, self.request) # If a general retracted is done, rise a waring if workflow.getInfoFor(ar, 'review_state') == 'sample_received': allstatus = list() for analysis in ar.getAnalyses(): status = workflow.getInfoFor(analysis.getObject(), 'review_state') if status not in ['retracted','to_be_verified','verified']: allstatus = [] break else: allstatus.append(status) if len(allstatus) > 0: self.addMessage("General Retract Done", 'warning') # If is a retracted AR, show the link to child AR and show a warn msg if workflow.getInfoFor(ar, 'review_state') == 'invalid': childar = hasattr(ar, 'getChildAnalysisRequest') \ and ar.getChildAnalysisRequest() or None message = _('These results have been withdrawn and are ' 'listed here for trace-ability purposes. Please follow ' 'the link to the retest') if childar: message = (message + " %s.") % childar.getRequestID() else: message = message + "." self.addMessage(message, 'warning') # If is an AR automatically generated due to a Retraction, show it's # parent AR information if hasattr(ar, 'getParentAnalysisRequest') \ and ar.getParentAnalysisRequest(): par = ar.getParentAnalysisRequest() message = _('This Analysis Request has been ' 'generated automatically due to ' 'the retraction of the Analysis ' 'Request ${retracted_request_id}.', mapping={'retracted_request_id': par.getRequestID()}) self.addMessage(message, 'info') self.renderMessages() return self.template()
def render_field_view(self, field): fieldname = field.getName() field = self.context.Schema()[fieldname] ret = {'fieldName': fieldname, 'mode': 'view'} try: adapter = getAdapter(self.context, interface=IHeaderTableFieldRenderer, name=fieldname) except ComponentLookupError: adapter = None if adapter: ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': adapter(field) } else: if field.getType().find("ool") > -1: value = field.get(self.context) ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': t(_('Yes')) if value else t(_('No')) } elif field.getType().find("Reference") > -1: # Prioritize method retrieval over schema's field targets = None if hasattr(self.context, 'get%s' % fieldname): fieldaccessor = getattr(self.context, 'get%s' % fieldname) if callable(fieldaccessor): targets = fieldaccessor() if not targets: targets = field.get(self.context) if targets: if not type(targets) == list: targets = [ targets, ] sm = getSecurityManager() if all([sm.checkPermission(view, ta) for ta in targets]): a = [ "<a href='%s'>%s</a>" % (target.absolute_url(), target.Title()) for target in targets ] ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': ", ".join(a) } else: ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': ", ".join([ta.Title() for ta in targets]) } else: ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': '' } elif field.getType().lower().find('datetime') > -1: value = field.get(self.context) ret = { 'fieldName': fieldname, 'mode': 'structure', 'html': self.ulocalized_time(value, long_format=True) } return ret
def folderitems(self, full_objects=False): workflow = getToolByName(self.context, "portal_workflow") items = BikaListingView.folderitems(self) mtool = getToolByName(self.context, 'portal_membership') member = mtool.getAuthenticatedMember() roles = member.getRoles() hideclientlink = 'RegulatoryInspector' in roles \ and 'Manager' not in roles \ and 'LabManager' not in roles \ and 'LabClerk' not in roles for x in range(len(items)): if 'obj' not in items[x]: continue obj = items[x]['obj'] sample = obj.getSample() if getSecurityManager().checkPermission(EditResults, obj): url = obj.absolute_url() + "/manage_results" else: url = obj.absolute_url() items[x]['Client'] = obj.aq_parent.Title() if (hideclientlink is False): items[x]['replace']['Client'] = "<a href='%s'>%s</a>" % \ (obj.aq_parent.absolute_url(), obj.aq_parent.Title()) items[x]['Creator'] = self.user_fullname(obj.Creator()) items[x]['getRequestID'] = obj.getRequestID() items[x]['replace']['getRequestID'] = "<a href='%s'>%s</a>" % \ (url, items[x]['getRequestID']) items[x]['getSample'] = sample items[x]['replace']['getSample'] = \ "<a href='%s'>%s</a>" % (sample.absolute_url(), sample.Title()) if obj.getAnalysesNum(): items[x]['getAnalysesNum'] = str(obj.getAnalysesNum()[0]) + '/' + str(obj.getAnalysesNum()[1]) else: items[x]['getAnalysesNum'] = '' batch = obj.getBatch() if batch: items[x]['BatchID'] = batch.getBatchID() items[x]['replace']['BatchID'] = "<a href='%s'>%s</a>" % \ (batch.absolute_url(), items[x]['BatchID']) else: items[x]['BatchID'] = '' val = obj.Schema().getField('SubGroup').get(obj) items[x]['SubGroup'] = val.Title() if val else '' samplingdate = obj.getSample().getSamplingDate() items[x]['SamplingDate'] = self.ulocalized_time(samplingdate, long_format=1) items[x]['getDateReceived'] = self.ulocalized_time(obj.getDateReceived()) items[x]['getDatePublished'] = self.ulocalized_time(obj.getDatePublished()) deviation = sample.getSamplingDeviation() items[x]['SamplingDeviation'] = deviation and deviation.Title() or '' priority = obj.getPriority() items[x]['Priority'] = '' # priority.Title() items[x]['getStorageLocation'] = sample.getStorageLocation() and sample.getStorageLocation().Title() or '' items[x]['AdHoc'] = sample.getAdHoc() and True or '' after_icons = "" state = workflow.getInfoFor(obj, 'worksheetanalysis_review_state') if state == 'assigned': after_icons += "<img src='%s/++resource++bika.lims.images/worksheet.png' title='%s'/>" % \ (self.portal_url, t(_("All analyses assigned"))) if workflow.getInfoFor(obj, 'review_state') == 'invalid': after_icons += "<img src='%s/++resource++bika.lims.images/delete.png' title='%s'/>" % \ (self.portal_url, t(_("Results have been withdrawn"))) if obj.getLate(): after_icons += "<img src='%s/++resource++bika.lims.images/late.png' title='%s'>" % \ (self.portal_url, t(_("Late Analyses"))) if samplingdate > DateTime(): after_icons += "<img src='%s/++resource++bika.lims.images/calendar.png' title='%s'>" % \ (self.portal_url, t(_("Future dated sample"))) if obj.getInvoiceExclude(): after_icons += "<img src='%s/++resource++bika.lims.images/invoice_exclude.png' title='%s'>" % \ (self.portal_url, t(_("Exclude from invoice"))) if sample.getSampleType().getHazardous(): after_icons += "<img src='%s/++resource++bika.lims.images/hazardous.png' title='%s'>" % \ (self.portal_url, t(_("Hazardous"))) if after_icons: items[x]['after']['getRequestID'] = after_icons items[x]['Created'] = self.ulocalized_time(obj.created()) contact = obj.getContact() if contact: items[x]['ClientContact'] = contact.Title() items[x]['replace']['ClientContact'] = "<a href='%s'>%s</a>" % \ (contact.absolute_url(), contact.Title()) else: items[x]['ClientContact'] = "" SamplingWorkflowEnabled = sample.getSamplingWorkflowEnabled() if SamplingWorkflowEnabled and not samplingdate > DateTime(): datesampled = self.ulocalized_time(sample.getDateSampled()) if not datesampled: datesampled = self.ulocalized_time( DateTime(), long_format=1) items[x]['class']['getDateSampled'] = 'provisional' sampler = sample.getSampler().strip() if sampler: items[x]['replace']['getSampler'] = self.user_fullname(sampler) if 'Sampler' in member.getRoles() and not sampler: sampler = member.id items[x]['class']['getSampler'] = 'provisional' else: datesampled = '' sampler = '' items[x]['getDateSampled'] = datesampled items[x]['getSampler'] = sampler # sampling workflow - inline edits for Sampler and Date Sampled checkPermission = self.context.portal_membership.checkPermission state = workflow.getInfoFor(obj, 'review_state') if state == 'to_be_sampled' \ and checkPermission(SampleSample, obj) \ and not samplingdate > DateTime(): items[x]['required'] = ['getSampler', 'getDateSampled'] items[x]['allow_edit'] = ['getSampler', 'getDateSampled'] samplers = getUsers(sample, ['Sampler', 'LabManager', 'Manager']) username = member.getUserName() users = [({'ResultValue': u, 'ResultText': samplers.getValue(u)}) for u in samplers] items[x]['choices'] = {'getSampler': users} Sampler = sampler and sampler or \ (username in samplers.keys() and username) or '' items[x]['getSampler'] = Sampler # These don't exist on ARs # XXX This should be a list of preservers... items[x]['getPreserver'] = '' items[x]['getDatePreserved'] = '' # inline edits for Preserver and Date Preserved checkPermission = self.context.portal_membership.checkPermission if checkPermission(PreserveSample, obj): items[x]['required'] = ['getPreserver', 'getDatePreserved'] items[x]['allow_edit'] = ['getPreserver', 'getDatePreserved'] preservers = getUsers(obj, ['Preserver', 'LabManager', 'Manager']) username = member.getUserName() users = [({'ResultValue': u, 'ResultText': preservers.getValue(u)}) for u in preservers] items[x]['choices'] = {'getPreserver': users} preserver = username in preservers.keys() and username or '' items[x]['getPreserver'] = preserver items[x]['getDatePreserved'] = self.ulocalized_time( DateTime(), long_format=1) items[x]['class']['getPreserver'] = 'provisional' items[x]['class']['getDatePreserved'] = 'provisional' # Submitting user may not verify results if items[x]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): self_submitted = False try: review_history = list(workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == member.getId(): self_submitted = True break if self_submitted: items[x]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ t(_("Cannot verify: Submitted by current user")) except Exception: pass # Hide Preservation/Sampling workflow actions if the edit columns # are not displayed. toggle_cols = self.get_toggle_cols() new_states = [] for i, state in enumerate(self.review_states): if state['id'] == self.review_state: if 'getSampler' not in toggle_cols \ or 'getDateSampled' not in toggle_cols: if 'hide_transitions' in state: state['hide_transitions'].append('sample') else: state['hide_transitions'] = ['sample', ] if 'getPreserver' not in toggle_cols \ or 'getDatePreserved' not in toggle_cols: if 'hide_transitions' in state: state['hide_transitions'].append('preserve') else: state['hide_transitions'] = ['preserve', ] new_states.append(state) self.review_states = new_states return items
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects = True) # manually skim retracted analyses from the list new_items = [] for i,item in enumerate(items): # self.contentsMethod may return brains or objects. if not ('obj' in items[i]): continue obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue new_items.append(item) items = new_items methods = self.get_methods_vocabulary() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() items[i]['category'] = cat if cat not in self.categories: self.categories.append(cat) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ format_numeric_result(obj, interim_fields[x]['value']) self.interim_fields[obj.UID()] = interim_fields items[i]['service_uid'] = service.UID() items[i]['Service'] = service.Title() items[i]['Keyword'] = keyword items[i]['Unit'] = format_supsub(unit) if unit else '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Remarks'] = obj.getRemarks() items[i]['Uncertainty'] = '' items[i]['DetectionLimit'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) items[i]['calculation'] = calculation and True or False try: items[i]['Partition'] = obj.getSamplePartition().getId() except AttributeError: items[i]['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = self.ulocalized_time(obj.aq_parent.getExpiryDate(), long_format=0) else: items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and self.ulocalized_time(cd, long_format=1) or '' items[i]['Attachments'] = '' item['allow_edit'] = [] client_or_lab = "" tblrowclass = items[i].get('table_row_class'); if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); else: sample = None if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() elif self.context.portal_type == 'Worksheet': if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'): sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() elif self.context.portal_type == 'Sample': sample = self.context st_uid = sample.getSampleType().UID() if sample else '' items[i]['st_uid'] = st_uid if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) allowed_method_states = ['to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned'] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() \ or (obj.portal_type == 'ReferenceAnalysis' \ and item['review_state'] in allowed_method_states)) if can_edit_analysis: items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() \ else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID() if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t(_( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() items[i]['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: items[i]['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % (attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % (self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % (attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % (attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace']['Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() dmk = self.context.bika_setup.getResultsDecimalMark() items[i]['formatted_result'] = obj.getFormattedResult(sciformat=int(scinot),decimalmark=dmk) # LIMS-1379 Allow manual uncertainty value input # https://jira.bikalabs.com/browse/LIMS-1379 fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) fu = fu if fu else '' if can_edit_analysis and service.getAllowManualUncertainty() == True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') items[i]['Uncertainty'] = unc if unc else '' items[i]['before']['Uncertainty'] = '± '; items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit']; elif fu: items[i]['Uncertainty'] = fu items[i]['before']['Uncertainty'] = '± '; items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit']; # LIMS-1700. Allow manual input of Detection Limits # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode # https://jira.bikalabs.com/browse/LIMS-1700 # https://jira.bikalabs.com/browse/LIMS-1775 if can_edit_analysis and \ hasattr(obj, 'getDetectionLimitOperand') and \ hasattr(service, 'getDetectionLimitSelector') and \ service.getDetectionLimitSelector() == True: isldl = obj.isBelowLowerDetectionLimit() isudl = obj.isAboveUpperDetectionLimit() dlval='' if isldl or isudl: dlval = '<' if isldl else '>' item['allow_edit'].append('DetectionLimit') item['DetectionLimit'] = dlval choices=[{'ResultValue': '<', 'ResultText': '<'}, {'ResultValue': '>', 'ResultText': '>'}] item['choices']['DetectionLimit'] = choices self.columns['DetectionLimit']['toggle'] = True srv = obj.getService() defdls = {'min':srv.getLowerDetectionLimit(), 'max':srv.getUpperDetectionLimit(), 'manual':srv.getAllowManualDetectionLimit()} defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>' defin = defin % (obj.UID(), json.dumps(defdls)) item['after']['DetectionLimit'] = defin # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 # Since LDL, UDL, etc. are wildcards that can be used # in calculations, these fields must be loaded always # for 'live' calculations. if can_edit_analysis: dls = {'default_ldl': 'none', 'default_udl': 'none', 'below_ldl': False, 'above_udl': False, 'is_ldl': False, 'is_udl': False, 'manual_allowed': False, 'dlselect_allowed': False} if hasattr(obj, 'getDetectionLimits'): dls['below_ldl'] = obj.isBelowLowerDetectionLimit() dls['above_udl'] = obj.isBelowLowerDetectionLimit() dls['is_ldl'] = obj.isLowerDetectionLimit() dls['is_udl'] = obj.isUpperDetectionLimit() dls['default_ldl'] = service.getLowerDetectionLimit() dls['default_udl'] = service.getUpperDetectionLimit() dls['manual_allowed'] = service.getAllowManualDetectionLimit() dls['dlselect_allowed'] = service.getDetectionLimitSelector() dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>' dlsin = dlsin % (obj.UID(), json.dumps(dls)) item['after']['Result'] = dlsin else: items[i]['Specification'] = "" if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join([x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" items[i]['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get('hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() items[i]['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if items[i]['review_state'] not in ['to_be_sampled', 'to_be_preserved', 'sample_due', 'published']: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) # Submitting user may not verify results (admin can though) if items[i]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): user_id = getSecurityManager().getUser().getId() self_submitted = False try: review_history = list(workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: items[i]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ (t(_("Cannot verify: Submitted by current user"))) except WorkflowException: pass # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = items[i]['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \ (ws.absolute_url(), t(_("Assigned to: ${worksheet_id}", mapping={'worksheet_id': safe_unicode(ws.id)}))) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = {'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False} if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items
def __call__(self): ar = self.context workflow = getToolByName(self.context, 'portal_workflow') if 'transition' in self.request.form: doActionFor(self.context, self.request.form['transition']) # Contacts get expanded for view contact = self.context.getContact() contacts = [] for cc in self.context.getCCContact(): contacts.append(cc) if contact in contacts: contacts.remove(contact) ccemails = [] for cc in contacts: ccemails.append( "%s <<a href='mailto:%s'>%s</a>>" % (cc.Title(), cc.getEmailAddress(), cc.getEmailAddress())) # CC Emails become mailto links emails = self.context.getCCEmails() if isinstance(emails, str): emails = emails and [ emails, ] or [] cc_emails = [] cc_hrefs = [] for cc in emails: cc_emails.append(cc) cc_hrefs.append("<a href='mailto:%s'>%s</a>" % (cc, cc)) # render header table self.header_table = HeaderTableView(self.context, self.request)() # Create Partitions View for this ARs sample p = SamplePartitionsView(self.context.getSample(), self.request) p.show_column_toggles = False self.parts = p.contents_table() # Create Field and Lab Analyses tables self.tables = {} for poc in POINTS_OF_CAPTURE: if self.context.getAnalyses(getPointOfCapture=poc): t = self.createAnalysesView( ar, self.request, getPointOfCapture=poc, show_categories=self.context.bika_setup. getCategoriseAnalysisServices()) t.allow_edit = True t.form_id = "%s_analyses" % poc t.review_states[0]['transitions'] = [{ 'id': 'submit' }, { 'id': 'retract' }, { 'id': 'verify' }] t.show_workflow_action_buttons = True t.show_select_column = True if getSecurityManager().checkPermission(EditFieldResults, self.context) \ and poc == 'field': t.review_states[0]['columns'].remove('DueDate') self.tables[POINTS_OF_CAPTURE.getValue( poc)] = t.contents_table() # Un-captured field analyses may cause confusion if ar.getAnalyses(getPointOfCapture='field', review_state=['sampled', 'sample_due']): message = _("There are field analyses without submitted results.") self.addMessage(message, 'info') # Create QC Analyses View for this AR show_cats = self.context.bika_setup.getCategoriseAnalysisServices() qcview = self.createQCAnalyesView(ar, self.request, show_categories=show_cats) qcview.allow_edit = False qcview.show_select_column = False qcview.show_workflow_action_buttons = False qcview.form_id = "%s_qcanalyses" qcview.review_states[0]['transitions'] = [{ 'id': 'submit' }, { 'id': 'retract' }, { 'id': 'verify' }] self.qctable = qcview.contents_table() # Create the ResultsInterpretation by department view from dependencies.dependency import ARResultsInterpretationView self.riview = ARResultsInterpretationView(ar, self.request) # If a general retracted is done, rise a waring if workflow.getInfoFor(ar, 'review_state') == 'sample_received': allstatus = list() for analysis in ar.getAnalyses(): status = workflow.getInfoFor(analysis.getObject(), 'review_state') if status not in ['retracted', 'to_be_verified', 'verified']: allstatus = [] break else: allstatus.append(status) if len(allstatus) > 0: self.addMessage("General Retract Done", 'warning') # If is a retracted AR, show the link to child AR and show a warn msg if workflow.getInfoFor(ar, 'review_state') == 'invalid': childar = hasattr(ar, 'getChildAnalysisRequest') \ and ar.getChildAnalysisRequest() or None message = _( 'These results have been withdrawn and are ' 'listed here for trace-ability purposes. Please follow ' 'the link to the retest') if childar: message = (message + " %s.") % childar.getRequestID() else: message = message + "." self.addMessage(message, 'warning') # If is an AR automatically generated due to a Retraction, show it's # parent AR information if hasattr(ar, 'getParentAnalysisRequest') \ and ar.getParentAnalysisRequest(): par = ar.getParentAnalysisRequest() message = _( 'This Analysis Request has been ' 'generated automatically due to ' 'the retraction of the Analysis ' 'Request ${retracted_request_id}.', mapping={'retracted_request_id': par.getRequestID()}) self.addMessage(message, 'info') self.renderMessages() return self.template()
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects=True) # manually skim retracted analyses from the list new_items = [] for i, item in enumerate(items): # self.contentsMethod may return brains or objects. if not ('obj' in items[i]): continue obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue new_items.append(item) items = new_items methods = self.get_methods_vocabulary() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() items[i]['category'] = cat if cat not in self.categories: self.categories.append(cat) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ format_numeric_result(obj, interim_fields[x]['value']) self.interim_fields[obj.UID()] = interim_fields items[i]['service_uid'] = service.UID() items[i]['Service'] = service.Title() items[i]['Keyword'] = keyword items[i]['Unit'] = format_supsub(unit) if unit else '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Remarks'] = obj.getRemarks() items[i]['Uncertainty'] = '' items[i]['DetectionLimit'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) items[i]['calculation'] = calculation and True or False try: items[i]['Partition'] = obj.getSamplePartition().getId() except AttributeError: items[i]['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = self.ulocalized_time( obj.aq_parent.getExpiryDate(), long_format=0) else: items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and self.ulocalized_time( cd, long_format=1) or '' items[i]['Attachments'] = '' item['allow_edit'] = [] client_or_lab = "" tblrowclass = items[i].get('table_row_class') if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) else: sample = None if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() elif self.context.portal_type == 'Worksheet': if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'): sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() elif self.context.portal_type == 'Sample': sample = self.context st_uid = sample.getSampleType().UID() if sample else '' items[i]['st_uid'] = st_uid if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) allowed_method_states = [ 'to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned' ] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() \ or (obj.portal_type == 'ReferenceAnalysis' \ and item['review_state'] in allowed_method_states)) if can_edit_analysis: items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() \ else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID( ) if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t( _( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() items[i]['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: items[i]['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % ( attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % ( self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % ( attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % ( attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace'][ 'Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() dmk = self.context.bika_setup.getResultsDecimalMark() items[i]['formatted_result'] = obj.getFormattedResult( sciformat=int(scinot), decimalmark=dmk) # LIMS-1379 Allow manual uncertainty value input # https://jira.bikalabs.com/browse/LIMS-1379 fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) fu = fu if fu else '' if can_edit_analysis and service.getAllowManualUncertainty( ) == True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') items[i]['Uncertainty'] = unc if unc else '' items[i]['before']['Uncertainty'] = '± ' items[i]['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[ i]['Unit'] elif fu: items[i]['Uncertainty'] = fu items[i]['before']['Uncertainty'] = '± ' items[i]['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[ i]['Unit'] # LIMS-1700. Allow manual input of Detection Limits # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode # https://jira.bikalabs.com/browse/LIMS-1700 # https://jira.bikalabs.com/browse/LIMS-1775 if can_edit_analysis and \ hasattr(obj, 'getDetectionLimitOperand') and \ hasattr(service, 'getDetectionLimitSelector') and \ service.getDetectionLimitSelector() == True: isldl = obj.isBelowLowerDetectionLimit() isudl = obj.isAboveUpperDetectionLimit() dlval = '' if isldl or isudl: dlval = '<' if isldl else '>' item['allow_edit'].append('DetectionLimit') item['DetectionLimit'] = dlval choices = [{ 'ResultValue': '<', 'ResultText': '<' }, { 'ResultValue': '>', 'ResultText': '>' }] item['choices']['DetectionLimit'] = choices self.columns['DetectionLimit']['toggle'] = True srv = obj.getService() defdls = { 'min': srv.getLowerDetectionLimit(), 'max': srv.getUpperDetectionLimit(), 'manual': srv.getAllowManualDetectionLimit() } defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>' defin = defin % (obj.UID(), json.dumps(defdls)) item['after']['DetectionLimit'] = defin # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 # Since LDL, UDL, etc. are wildcards that can be used # in calculations, these fields must be loaded always # for 'live' calculations. if can_edit_analysis: dls = { 'default_ldl': 'none', 'default_udl': 'none', 'below_ldl': False, 'above_udl': False, 'is_ldl': False, 'is_udl': False, 'manual_allowed': False, 'dlselect_allowed': False } if hasattr(obj, 'getDetectionLimits'): dls['below_ldl'] = obj.isBelowLowerDetectionLimit() dls['above_udl'] = obj.isBelowLowerDetectionLimit() dls['is_ldl'] = obj.isLowerDetectionLimit() dls['is_udl'] = obj.isUpperDetectionLimit() dls['default_ldl'] = service.getLowerDetectionLimit() dls['default_udl'] = service.getUpperDetectionLimit() dls['manual_allowed'] = service.getAllowManualDetectionLimit( ) dls['dlselect_allowed'] = service.getDetectionLimitSelector( ) dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>' dlsin = dlsin % (obj.UID(), json.dumps(dls)) item['after']['Result'] = dlsin else: items[i]['Specification'] = "" if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join( [x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" items[i]['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get( 'hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() items[i]['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if items[i]['review_state'] not in [ 'to_be_sampled', 'to_be_preserved', 'sample_due', 'published' ]: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) # Submitting user may not verify results (admin can though) if items[i]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): user_id = getSecurityManager().getUser().getId() self_submitted = False try: review_history = list( workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: items[i]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ (t(_("Cannot verify: Submitted by current user"))) except WorkflowException: pass # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = items[i]['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \ (ws.absolute_url(), t(_("Assigned to: ${worksheet_id}", mapping={'worksheet_id': safe_unicode(ws.id)}))) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = { 'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False } if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items