Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Barcode and labelling depending on Sample Type #607

Merged
merged 23 commits into from
Feb 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Changelog

**Added**

- #607 Ability to choose sticker template based on sample type
- #480 Sample panel in dashboard
- #617 Instrument import interface: 2-Dimensional-CSV
- #617 Instrument import interface: Agilent Masshunter
Expand All @@ -16,6 +17,7 @@ Changelog
- #617 Instrument import interface: Shimadzu LC MS/MS Nexera X2
- #537 Instrument import interface: Sysmex XT-4000i
- #536 Instrument import interface: Sysmex XT-1800i
- #607 Barcode and labelling depending on Sample Type
- #618 When previewing stickers the number of copies to print for each sticker can be modified.
- #618 The default number of sticker copies can be set and edited in the setup Sticker's tab.

Expand Down
13 changes: 13 additions & 0 deletions bika/lims/adapters/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,17 @@
preserveOriginal="True"
/>

<!-- Stickers -->
<!-- IGetStickerTemplates adapters -->
<adapter
factory="bika.lims.adapters.stickers.GetSampleStickers"
for="bika.lims.interfaces.ISample"
provides="bika.lims.interfaces.IGetStickerTemplates"
/>
<adapter
factory="bika.lims.adapters.stickers.GetSampleStickers"
for="bika.lims.interfaces.IAnalysisRequest"
provides="bika.lims.interfaces.IGetStickerTemplates"
/>

</configure>
65 changes: 65 additions & 0 deletions bika/lims/adapters/stickers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
#
# This file is part of SENAITE.CORE
#
# Copyright 2018 by it's authors.
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.

from zope.interface import implements

from bika.lims import logger
from bika.lims.interfaces import IGetStickerTemplates
from bika.lims.vocabularies import getStickerTemplates


class GetSampleStickers(object):
"""
Returns an array with the templates of stickers available for Sample
object in context.
Each array item is a dictionary with the following structure:
[{'id': <template_id>,
'title': <teamplate_title>,
'selected: True/False'}, ]
"""

implements(IGetStickerTemplates)

def __init__(self, context):
self.context = context
self.request = None
self.sample_type = None

def __call__(self, request):
self.request = request
# Stickers admittance are saved in sample type
if not hasattr(self.context, 'getSampleType'):
logger.warning(
"{} has no attribute 'getSampleType', so no sticker will be "
"returned.". format(self.context.getId())
)
return []
self.sample_type = self.context.getSampleType()
sticker_ids = self.sample_type.getAdmittedStickers()
default_sticker_id = self.get_default_sticker_id()
result = []
# Getting only existing templates and its info
stickers = getStickerTemplates()
for sticker in stickers:
if sticker.get('id') in sticker_ids:
sticker_info = sticker.copy()
sticker_info['selected'] = \
default_sticker_id == sticker.get('id')
result.append(sticker_info)
return result

def get_default_sticker_id(self):
"""
Gets the default sticker for that content type depending on the
requested size.

:return: An sticker ID as string
"""
size = self.request.get('size', '')
if size == 'small':
return self.sample_type.getDefaultSmallSticker()
return self.sample_type.getDefaultLargeSticker()
19 changes: 18 additions & 1 deletion bika/lims/browser/stickers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
from bika.lims import bikaMessageFactory as _, t
from bika.lims import logger
from bika.lims.browser import BrowserView
from zope.component.interfaces import ComponentLookupError
from bika.lims.utils import createPdf, to_int
from bika.lims.vocabularies import getStickerTemplates
from plone.resource.utils import iterDirectoriesOfType, queryResourceDirectory
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
import glob, os, os.path, sys, traceback
from bika.lims.interfaces import IGetStickerTemplates
from zope.component import getAdapters

import os
import App
Expand Down Expand Up @@ -168,8 +171,22 @@ def getAvailableTemplates(self):
'title': <teamplate_title>,
'selected: True/False'}
"""
seltemplate = self.getSelectedTemplate()
# Getting adapters for current context. those adapters will return
# the desired sticker templates for the current context:
try:
adapters = getAdapters((self.context, ), IGetStickerTemplates)
except ComponentLookupError:
logger.info('No IGetStickerTemplates adapters found.')
adapters = None
Copy link
Member

Choose a reason for hiding this comment

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

no need to do the assignment adapters = None here, just return list()

Copy link
Author

@Espurna Espurna Feb 2, 2018

Choose a reason for hiding this comment

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

The method continues as follows: if there aren't adapters nor templates, get all sticker templates in the system. So we can't return at this point.

templates = []
if adapters is not None:
# Gather all templates
for name, adapter in adapters:
templates += adapter(self.request)
if templates:
return templates
Copy link
Member

Choose a reason for hiding this comment

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

Reduce nesting:

if not adapters:
    return list()
templates = list()
for name, adapter in adapters:
    templates.append(adapter(self.request))
if templates:
   ...

Copy link
Author

@Espurna Espurna Feb 2, 2018

Choose a reason for hiding this comment

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

As I said in a previous comment, I can't return until the end of the method.

Moreover, adapters variable can be a generator object or None. There could be the case where the generator contains "nothing", like an empty list. So, if getAdapters doesn't fail, adapters variable will be an object even and consequently it will always be true.

# If there are no adapters, get all sticker templates in the system
seltemplate = self.getSelectedTemplate()
for temp in getStickerTemplates(filter_by_type=self.filter_by_type):
out = temp
out['selected'] = temp.get('id', '') == seltemplate
Expand Down
1 change: 1 addition & 0 deletions bika/lims/browser/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@
from .rejectionwidget import RejectionWidget
from .priorityselectionwidget import PrioritySelectionWidget
from .comboboxwidget import ComboBoxWidget
from .sampletypestickerswidget import SampleTypeStickersWidget
22 changes: 22 additions & 0 deletions bika/lims/browser/widgets/sampletypestickerswidget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from AccessControl import ClassSecurityInfo

from Products.Archetypes.Registry import registerWidget

from bika.lims.browser.widgets import RecordsWidget


class SampleTypeStickersWidget(RecordsWidget):
security = ClassSecurityInfo()
_properties = RecordsWidget._properties.copy()
_properties.update({
'helper_js': (
"bika_widgets/recordswidget.js",
"bika_widgets/sampletypestickerswidget.js",),
})


registerWidget(
SampleTypeStickersWidget,
title="Sample type stickers widget",
description='Defines the available stickers for a sample type.',
)
179 changes: 168 additions & 11 deletions bika/lims/content/sampletype.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,44 @@
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.

from AccessControl import ClassSecurityInfo

from Products.ATContentTypes.lib.historyaware import HistoryAwareMixin
from Products.ATExtensions.ateapi import RecordsField
from Products.Archetypes.public import *
from Products.Archetypes.references import HoldingReference
from Products.CMFCore.permissions import View, ModifyPortalContent
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import safe_unicode
from bika.lims.browser import BrowserView
from magnitude import mg
from zope.interface import implements

from bika.lims import bikaMessageFactory as _
from bika.lims.utils import t
from bika.lims.config import PROJECTNAME
from bika.lims.browser.widgets import DurationWidget
from bika.lims import logger
from bika.lims.browser.fields import DurationField
from bika.lims.browser.widgets import DurationWidget
from bika.lims.browser.widgets import SampleTypeStickersWidget
from bika.lims.browser.widgets.referencewidget import ReferenceWidget as brw
from bika.lims.config import PROJECTNAME
from bika.lims.content.bikaschema import BikaSchema
from bika.lims.interfaces import ISampleType
from magnitude import mg, MagnitudeError
from zope.interface import implements
from bika.lims.browser.widgets.referencewidget import ReferenceWidget as brw
import json
import plone
import sys
from bika.lims.vocabularies import getStickerTemplates

SMALL_DEFAULT_STICKER = 'small_default'
LARGE_DEFAULT_STICKER = 'large_default'


def sticker_templates():
"""
It returns the registered stickers in the system.
:return: a DisplayList object
"""
voc = DisplayList()
stickers = getStickerTemplates()
for sticker in stickers:
voc.add(sticker.get('id'), sticker.get('title'))
if voc.index == 0:
logger.warning('Sampletype: getStickerTemplates is empty!')
return voc


schema = BikaSchema.copy() + Schema((
DurationField('RetentionPeriod',
Expand Down Expand Up @@ -106,6 +124,47 @@
visibile=False,
)
),
RecordsField(
'AdmittedStickerTemplates',
subfields=(
'admitted',
SMALL_DEFAULT_STICKER,
LARGE_DEFAULT_STICKER,
),
subfield_labels={
'admitted': _(
'Admitted stickers for the sample type'),
SMALL_DEFAULT_STICKER: _(
'Default small sticker'),
LARGE_DEFAULT_STICKER: _(
'Default large sticker')},
subfield_sizes={
'admitted': 6,
SMALL_DEFAULT_STICKER: 1,
LARGE_DEFAULT_STICKER: 1},
subfield_types={
'admitted': 'selection',
SMALL_DEFAULT_STICKER: 'selection',
LARGE_DEFAULT_STICKER: 'selection'
},
subfield_vocabularies={
'admitted': sticker_templates(),
SMALL_DEFAULT_STICKER: '_sticker_templates_vocabularies',
LARGE_DEFAULT_STICKER: '_sticker_templates_vocabularies',
},
required_subfields={
'admitted': 1,
SMALL_DEFAULT_STICKER: 1,
LARGE_DEFAULT_STICKER: 1},
default=[{}],
fixedSize=1,
widget=SampleTypeStickersWidget(
label=_("Admitted sticker templates"),
description=_(
"Defines the stickers to use for this sample type."),
allowDelete=False,
),
),
))

schema['description'].schemata = 'default'
Expand Down Expand Up @@ -198,6 +257,104 @@ def ContainerTypesVocabulary(self):
from bika.lims.content.containertype import ContainerTypes
return ContainerTypes(self, allow_blank=True)

def _get_sticker_subfield(self, subfield):
values = self.getField('AdmittedStickerTemplates').get(self)
if not values:
return ''
value = values[0].get(subfield)
return value

def getDefaultSmallSticker(self):
"""
Returns the small sticker ID defined as default.

:return: A string as an sticker ID
"""
return self._get_sticker_subfield(SMALL_DEFAULT_STICKER)

def getDefaultLargeSticker(self):
"""
Returns the large sticker ID defined as default.

:return: A string as an sticker ID
"""
return self._get_sticker_subfield(LARGE_DEFAULT_STICKER)

def getAdmittedStickers(self):
"""
Returns the admitted sticker IDs defined.

:return: An array of sticker IDs
"""
admitted = self._get_sticker_subfield('admitted')
if admitted:
return admitted
return []

def _sticker_templates_vocabularies(self):
"""
Returns the vocabulary to be used in
AdmittedStickerTemplates.small_default

If the object has saved not AdmittedStickerTemplates.admitted stickers,
this method will return an empty DisplayList. Otherwise it returns
the stickers selected in admitted.

:return: A DisplayList
"""
admitted = self.getAdmittedStickers()
if not admitted:
return DisplayList()
voc = DisplayList()
stickers = getStickerTemplates()
for sticker in stickers:
if sticker.get('id') in admitted:
voc.add(sticker.get('id'), sticker.get('title'))
return voc

def setDefaultSmallSticker(self, value):
"""
Sets the small sticker ID defined as default.

:param value: A sticker ID
"""
self._set_sticker_subfield(SMALL_DEFAULT_STICKER, value)

def setDefaultLargeSticker(self, value):
"""
Sets the large sticker ID defined as default.

:param value: A sticker ID
"""
self._set_sticker_subfield(LARGE_DEFAULT_STICKER, value)

def setAdmittedStickers(self, value):
"""
Sets the admitted sticker IDs.

:param value: An array of sticker IDs
"""
self._set_sticker_subfield('admitted', value)

def _set_sticker_subfield(self, subfield, value):
if value is None:
logger.error(
"Setting wrong 'AdmittedStickerTemplates/admitted' value"
" to Sample Type '{}'"
.format(self.getId()))
return
if not isinstance(value, list):
logger.error(
"Setting wrong 'AdmittedStickerTemplates/admitted' value"
" type to Sample Type '{}'"
.format(self.getId()))
return
field = self.getField('AdmittedStickerTemplates')
stickers = field.get(self)
stickers[0][subfield] = value
field.set(self, stickers)


registerType(SampleType, PROJECTNAME)

def SampleTypes(self, instance=None, allow_blank=False):
Expand Down
12 changes: 12 additions & 0 deletions bika/lims/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,3 +823,15 @@ class ITopWideHTMLComponentsHook(Interface):
"""
Marker interface to hook html components in bikalisting
"""


class IGetStickerTemplates(Interface):
"""
Marker interface to get stickers for a specific content type.

An IGetStickerTemplates adapter should return a result with the
following format:

:return: [{'id': <template_id>,
'title': <template_title>}, ...]
"""
Loading