Skip to content

Commit

Permalink
Merge branch '2.x' into sort-cats-analysisspec
Browse files Browse the repository at this point in the history
  • Loading branch information
ramonski authored Jan 4, 2023
2 parents 38bc708 + ea8189a commit e24f862
Show file tree
Hide file tree
Showing 29 changed files with 892 additions and 384 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,33 @@ jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
runs-on: 'ubuntu-latest'

# Steps represent a sequence of tasks that will be executed as part of the job
steps:

- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2

- name: Show workspace
run: echo ${{ github.workspace }}

- name: Checkout senaite.docker
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: "senaite/senaite.docker"
path: "senaite.docker"

- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: ${{ github.workspace }}/senaite.docker/latest
file: ${{ github.workspace }}/senaite.docker/latest/Dockerfile
Expand Down
15 changes: 15 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ Changelog
------------------

- #2191 Apply category sort order on analysis specifications
- #2219 Make `UIDReferenceField` to not keep back-references by default
- #2209 Migrate AnalysisRequest's ReferenceField fields to UIDReferenceField
- #2218 Improve performance of legacy AT `UIDReferenceField`'s setter
- #2214 Remove `DefaultContainerType` field (stale) from AnalysisRequest
- #2215 Fix ParseError when search term contains parenthesis in widget search
- #2213 Purge ComputedField fields from AnalysisRequest related with Profiles
- #2212 Improve performance of legacy AT `UIDReferenceField`'s getter
- #2211 Remove `Profile` field (stale) from AnalysisRequest
- #2207 Support for file upload on analysis (pre) conditions
- #2208 Remove `default_method` from AnalysisRequest's Contact field
- #2204 Fix traceback when retracting an analysis with a detection limit
- #2202 Fix detection limit set manually is not displayed on result save
- #2203 Fix empty date sampled in samples listing when sampling workflow is enabled
- #2197 Use portal as relative path for sticker icons
- #2196 Order sample analyses by sortable title on get per default
- #2193 Fix analyst cannot import results from instruments
- #2190 Fix sample actions without translation
- #2189 Fix auto-print of barcode labels when auto-receive is enabled
Expand Down
3 changes: 3 additions & 0 deletions src/bika/lims/adapters/referencewidgetvocabulary.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def search_term(self):
"""Returns the search term
"""
search_term = _c(self.request.get("searchTerm", ""))
# Normalize the search term
special = "*.!$%&/()=#+:'`´^"
search_term = filter(lambda it: it not in special, search_term)
return search_term.lower().strip()

@property
Expand Down
47 changes: 36 additions & 11 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,14 @@ def _folder_item_result(self, analysis_brain, item):
if self.is_result_edition_allowed(analysis_brain):
item["allow_edit"].append("Result")

# Display the DL operand (< or >) in the results entry field if
# the manual entry of DL is set, but DL selector is hidden
allow_manual = obj.getAllowManualDetectionLimit()
selector = obj.getDetectionLimitSelector()
if allow_manual and not selector:
operand = obj.getDetectionLimitOperand()
item["Result"] = "{} {}".format(operand, result).strip()

# Prepare result options
choices = obj.getResultOptions()
if choices:
Expand Down Expand Up @@ -1138,12 +1146,12 @@ def _folder_item_attachments(self, obj, item):
attachments_names = []
attachments_html = []
analysis = self.get_object(obj)
for at in analysis.getAttachment():
at_file = at.getAttachmentFile()
url = "{}/at_download/AttachmentFile".format(api.get_url(at))
link = get_link(url, at_file.filename, tabindex="-1")
for attachment in analysis.getRawAttachment():
attachment = self.get_object(attachment)
link = self.get_attachment_link(attachment)
attachments_html.append(link)
attachments_names.append(at_file.filename)
filename = attachment.getFilename()
attachments_names.append(filename)

if attachments_html:
item["replace"]["Attachments"] = "<br/>".join(attachments_html)
Expand All @@ -1153,6 +1161,14 @@ def _folder_item_attachments(self, obj, item):
img = get_image("warning.png", title=_("Attachment required"))
item["replace"]["Attachments"] = img

def get_attachment_link(self, attachment):
"""Returns a well-formed link for the attachment passed in
"""
filename = attachment.getFilename()
att_url = api.get_url(attachment)
url = "{}/at_download/AttachmentFile".format(att_url)
return get_link(url, filename, tabindex="-1")

def _folder_item_uncertainty(self, analysis_brain, item):
"""Fills the analysis' uncertainty to the item passed in.
Expand Down Expand Up @@ -1500,12 +1516,21 @@ def _folder_item_conditions(self, analysis_brain, item):
return

conditions = analysis.getConditions()
if conditions:
conditions = map(lambda it: ": ".join([it["title"], it["value"]]),
conditions)
conditions = "<br/>".join(conditions)
service = item["replace"].get("Service") or item["Service"]
item["replace"]["Service"] = "{}<br/>{}".format(service, conditions)
if not conditions:
return

def to_str(condition):
title = condition.get("title")
value = condition.get("value", "")
if condition.get("type") == "file" and api.is_uid(value):
att = self.get_object(value)
value = self.get_attachment_link(att)
return ": ".join([title, str(value)])

# Display the conditions properly formatted
conditions = "<br/>".join([to_str(cond) for cond in conditions])
service = item["replace"].get("Service") or item["Service"]
item["replace"]["Service"] = "<br/>".join([service, conditions])

def is_method_required(self, analysis):
"""Returns whether the render of the selection list with methods is
Expand Down
86 changes: 75 additions & 11 deletions src/bika/lims/browser/analysisrequest/add2.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
from bika.lims.interfaces import IAddSampleObjectInfo
from bika.lims.interfaces import IAddSampleRecordsValidator
from bika.lims.interfaces import IGetDefaultFieldValueARAddHook
from bika.lims.utils import tmpID
from bika.lims.utils.analysisrequest import create_analysisrequest as crar
from bika.lims.workflow import ActionHandlerPool
from BTrees.OOBTree import OOBTree
Expand All @@ -45,7 +44,6 @@
from plone.memoize.volatile import DontCache
from plone.memoize.volatile import cache
from plone.protect.interfaces import IDisableCSRFProtection
from Products.CMFPlone.utils import _createObjectByType
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
Expand Down Expand Up @@ -1086,6 +1084,56 @@ def to_field_value(self, obj):
"title": obj and api.get_title(obj) or ""
}

def to_attachment_record(self, fileupload):
"""Returns a dict-like structure with suitable information for the
proper creation of Attachment objects
"""
if not fileupload.filename:
# ZPublisher.HTTPRequest.FileUpload is empty
return None
return {
"AttachmentFile": fileupload,
"AttachmentType": "",
"ReportOption": "",
"AttachmentKeys": "",
"Service": "",
}

def create_attachment(self, sample, attachment_record):
"""Creates an attachment for the given sample with the information
provided in attachment_record
"""
# create the attachment object
client = sample.getClient()
attachment = api.create(client, "Attachment", **attachment_record)
uid = attachment_record.get("Service")
if not uid:
# Link the attachment to the sample
sample.addAttachment(attachment)
return attachment

# Link the attachment to analyses with this service uid
ans = sample.objectValues(spec="Analysis")
ans = filter(lambda an: an.getRawAnalysisService() == uid, ans)
for analysis in ans:
attachments = analysis.getRawAttachment()
analysis.setAttachment(attachments + [attachment])

# Assign the attachment to the given condition
condition_title = attachment_record.get("Condition")
if not condition_title:
return attachment

conditions = sample.getServiceConditions()
for condition in conditions:
is_uid = condition.get("uid") == uid
is_title = condition.get("title") == condition_title
is_file = condition.get("type") == "file"
if all([is_uid, is_title, is_file]):
condition["value"] = api.get_uid(attachment)
sample.setServiceConditions(conditions)
return attachment

def ajax_get_global_settings(self):
"""Returns the global Bika settings
"""
Expand Down Expand Up @@ -1563,7 +1611,8 @@ def ajax_submit(self):
# Extract file uploads (fields ending with _file)
# These files will be added later as attachments
file_fields = filter(lambda f: f.endswith("_file"), record)
attachments[n] = map(lambda f: record.pop(f), file_fields)
uploads = map(lambda f: record.pop(f), file_fields)
attachments[n] = [self.to_attachment_record(f) for f in uploads]

# Required fields and their values
required_keys = [field.getName() for field in fields
Expand Down Expand Up @@ -1606,8 +1655,23 @@ def ajax_submit(self):
# Missing required fields
missing = [f for f in required_fields if not record.get(f, None)]

# Handle required fields from Service conditions
# Handle fields from Service conditions
for condition in record.get("ServiceConditions", []):
if condition.get("type") == "file":
# Add the file as an attachment
file_upload = condition.get("value")
att = self.to_attachment_record(file_upload)
if att:
# Add the file as an attachment
att.update({
"Service": condition.get("uid"),
"Condition": condition.get("title"),
})
attachments[n].append(att)
# Reset the condition value
filename = file_upload and file_upload.filename or ""
condition.value = filename

if condition.get("required") == "on":
if not condition.get("value"):
title = condition.get("title")
Expand Down Expand Up @@ -1670,16 +1734,16 @@ def ajax_submit(self):
errors["message"] = str(e)
logger.error(e, exc_info=True)
return {"errors": errors}

# We keep the title to check if AR is newly created
# and UID to print stickers
ARs[ar.Title()] = ar.UID()
for attachment in attachments.get(n, []):
if not attachment.filename:
continue
att = _createObjectByType("Attachment", client, tmpID())
att.setAttachmentFile(attachment)
att.processForm()
ar.addAttachment(att)

# Create the attachments
ar_attachments = filter(None, attachments.get(n, []))
for attachment_record in ar_attachments:
self.create_attachment(ar, attachment_record)

actions.resume()

level = "info"
Expand Down
12 changes: 6 additions & 6 deletions src/bika/lims/browser/fields/aranalysesfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ def get(self, instance, **kwargs):
:param kwargs: Keyword arguments to inject in the search query
:returns: A list of Analysis Objects/Catalog Brains
"""
# Do we need to return objects or brains
full_objects = kwargs.get("full_objects", False)

# Bail out parameters from kwargs that don't match with indexes
# Filter out parameters from kwargs that don't match with indexes
catalog = api.get_tool(ANALYSIS_CATALOG)
indexes = catalog.indexes()
query = dict([(k, v) for k, v in kwargs.items() if k in indexes])

# Do the search against the catalog
query["portal_type"] = "Analysis"
query["getAncestorsUIDs"] = api.get_uid(instance)
query["sort_on"] = kwargs.get("sort_on", "sortable_title")
query["sort_order"] = kwargs.get("sort_order", "ascending")

# Do the search against the catalog
brains = catalog(query)
if full_objects:
if kwargs.get("full_objects", False):
return map(api.get_object, brains)
return brains

Expand Down
Loading

0 comments on commit e24f862

Please sign in to comment.