diff --git a/CHANGES.rst b/CHANGES.rst index b9c38fac62..eb32a383bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,7 @@ Changelog **Changed** +- #1486 Clean-up of indexes and metadata from `setup_catalog` **Removed** diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md index 9cac866a87..b536a23c86 100644 --- a/CODE_CONVENTIONS.md +++ b/CODE_CONVENTIONS.md @@ -10,8 +10,33 @@ any add-ons for `senaite` too. Names for catalog indexes don't follow the `CamelCase` naming convention, rather all in lowercase and words separated by `_`: -- Wrong: `getSampleTypeUID` -- Good: `sampletype_uids` +- Bad: `getSampleTypeUID` +- Good: `sampletype_uid` + +#### Plural and singular forms + +Indexes will be written in singular form. There are a few exceptions, mostly +those that come from `Plone` or `Zope` (e.g. `allowedRolesAndUsers`). Quite +frequently, the plural form is used wrongly because the name of the meta-type +index to use leads us to think about the plural form: e.g. `KeywordIndex`, that +indexes a sequence of keywords. Is better to think about how the searches +against the index work. + +For instance, for a given object, a `KeywordIndex` will store a sequence of +keywords, but if we do a search for single or multiple keywords against this +index, the search will only return those items that have at least one of the +keywords. And if there are multiple keyword matches for same item, the system +will only return the item once. Since we can query for any index (`FieldIndex`, +`KeywordIndex`, etc.) using a list, it does not make sense to use the plural +form. In fact, if you are forced to add an index in plural form because a given +index with same name, but in singular already exists, probably the index in +singular is a `FieldIndex`, that don't allow you to store multiple values. In +such case, the best approach is to change the meta-type of the existing index +from `FieldIndex` to `KeywordIndex`. + +- Bad: `sampletype_titles` +- Good: `sampletype_title` + ### Metadata fields naming @@ -22,6 +47,10 @@ convention. Since one would expect the name of the metadata field to match with the name of the function from the object, we keep same convention. This might change in future when we move to `Dexterity`. -- Wrong: `SampleTypeUID` +- Bad: `SampleTypeUID` - Good: `getSampleTypeUID` +#### Plural and singular forms + +For metadata fields, use plural forms when the field returns a list and use +singular when the field returns a single value. diff --git a/bika/lims/api/__init__.py b/bika/lims/api/__init__.py index aa4d63a2d9..5e248920c1 100644 --- a/bika/lims/api/__init__.py +++ b/bika/lims/api/__init__.py @@ -1320,13 +1320,15 @@ def to_searchable_text_metadata(value): if isinstance(value, (bool)): return u"" if isinstance(value, (list, tuple)): - for v in value: - return to_searchable_text_metadata(v) - if isinstance(value, (dict)): - for k, v in value.items(): - return to_searchable_text_metadata(v) + values = map(to_searchable_text_metadata, value) + values = filter(None, values) + return " ".join(values) + if isinstance(value, dict): + return to_searchable_text_metadata(value.values()) if is_date(value): return value.strftime("%Y-%m-%d") + if is_at_content(value): + return to_searchable_text_metadata(get_title(value)) if not isinstance(value, basestring): value = str(value) return safe_unicode(value) diff --git a/bika/lims/browser/accreditation.py b/bika/lims/browser/accreditation.py index a98da71538..b3f0239290 100644 --- a/bika/lims/browser/accreditation.py +++ b/bika/lims/browser/accreditation.py @@ -19,11 +19,13 @@ # Some rights reserved, see README and LICENSE. from Products.CMFPlone.utils import safe_unicode +from plone.app.content.browser.interfaces import IFolderContentsView +from zope.interface import implements + +from bika.lims import api from bika.lims import bikaMessageFactory as _ from bika.lims.controlpanel.bika_analysisservices import AnalysisServicesView from bika.lims.utils import t -from plone.app.content.browser.interfaces import IFolderContentsView -from zope.interface import implements class AccreditationView(AnalysisServicesView): @@ -34,7 +36,6 @@ def __init__(self, context, request): self.contentFilter = { 'portal_type': 'AnalysisService', 'sort_on': 'sortable_title', - 'getAccredited': True, 'is_active': True} self.context_actions = {} self.title = self.context.translate(_("Accreditation")) @@ -102,3 +103,17 @@ def selected_cats(self, items): if 'category' in item and item['category'] not in cats: cats.append(item['category']) return cats + + def search(self, searchterm="", ignorecase=True): + # Boil out those that are not accredited + # There is no need to keep a `getAccredited` index in the catalog only + # for this view. Since we don't expect a lot of items from this content + # type (AnalysisService), is fine to wake-up them + brains = super(AccreditationView, self).search(searchterm, ignorecase) + + def is_accredited(brain): + obj = api.get_object(brain) + return obj.getAccredited() + + return filter(is_accredited, brains) + diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 5760dd976b..00ab0d637c 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -538,7 +538,7 @@ def get_services(self, poc="lab"): bsc = api.get_tool("bika_setup_catalog") query = { "portal_type": "AnalysisService", - "getPointOfCapture": poc, + "point_of_capture": poc, "is_active": True, "sort_on": "sortable_title", } @@ -1018,18 +1018,18 @@ def get_sampletype_info(self, obj): # Display Sample Points that have this sample type assigned plus # those that do not have a sample type assigned "SamplePoint": { - "sampletype_uids": [sample_type_uid, None], + "sampletype_uid": [sample_type_uid, None], "getClientUID": [client_uid, ""], }, # Display Specifications that have this sample type assigned only "Specification": { - "sampletype_uids": sample_type_uid, + "sampletype_uid": sample_type_uid, "getClientUID": [client_uid, ""], }, # Display AR Templates that have this sample type assigned plus # those that do not have a sample type assigned "Template": { - "sampletype_uids": [sample_type_uid, None], + "sampletype_uid": [sample_type_uid, None], "getClientUID": [client_uid, ""], } } diff --git a/bika/lims/browser/analysisrequest/templates/ar_add2.pt b/bika/lims/browser/analysisrequest/templates/ar_add2.pt index 5e2aebce05..c5aaabe3ba 100644 --- a/bika/lims/browser/analysisrequest/templates/ar_add2.pt +++ b/bika/lims/browser/analysisrequest/templates/ar_add2.pt @@ -412,7 +412,6 @@ - - diff --git a/bika/lims/browser/department/labcontacts.py b/bika/lims/browser/department/labcontacts.py index b482da7795..da4f975e21 100644 --- a/bika/lims/browser/department/labcontacts.py +++ b/bika/lims/browser/department/labcontacts.py @@ -41,12 +41,12 @@ def __init__(self, context, request): self.context_actions = {} self.contentFilter = { 'portal_type': 'LabContact', - 'sort_on': 'getFirstname', + 'sort_on': 'sortable_title', 'sort_order': 'ascending' } self.columns = { 'Fullname': {'title': _('Name'), - 'index': 'getFullname'}, + 'index': 'sortable_title'}, 'Department': {'title': _('Department')}, 'BusinessPhone': {'title': _('Phone')}, 'MobilePhone': {'title': _('Mobile Phone')}, diff --git a/bika/lims/browser/js/bika.lims.loader.js b/bika/lims/browser/js/bika.lims.loader.js index 1837f1ce3d..e650b05f31 100644 --- a/bika/lims/browser/js/bika.lims.loader.js +++ b/bika/lims/browser/js/bika.lims.loader.js @@ -131,9 +131,6 @@ window.bika.lims.controllers = { ".portaltype-worksheetfolder": ['WorksheetFolderView'], - ".portaltype-worksheet.template-add_analyses": - ['WorksheetAddAnalysesView'], - ".portaltype-worksheet.template-add_blank": ['WorksheetAddQCAnalysesView'], diff --git a/bika/lims/browser/js/bika.lims.site.js b/bika/lims/browser/js/bika.lims.site.js index ea565da7a0..de056db9d3 100644 --- a/bika/lims/browser/js/bika.lims.site.js +++ b/bika/lims/browser/js/bika.lims.site.js @@ -1,85 +1,63 @@ -(function() { - /* Please use this command to compile this file into the parent `js` directory: - coffee --no-header -w -o ../ -c bika.lims.site.coffee - */ - window.SiteView = class SiteView { - constructor() { - this.load = this.load.bind(this); - /* INITIALIZERS */ - this.bind_eventhandler = this.bind_eventhandler.bind(this); - this.init_client_add_overlay = this.init_client_add_overlay.bind(this); - // here is where we'd populate the form controls, if we cared to. - this.init_spinner = this.init_spinner.bind(this); - this.init_client_combogrid = this.init_client_combogrid.bind(this); - this.init_datepickers = this.init_datepickers.bind(this); - this.init_referencedefinition = this.init_referencedefinition.bind(this); - /* METHODS */ - this.get_portal_url = this.get_portal_url.bind(this); - this.get_authenticator = this.get_authenticator.bind(this); - this.portalAlert = this.portalAlert.bind(this); - this.portal_alert = this.portal_alert.bind(this); - this.log = this.log.bind(this); - this.readCookie = this.readCookie.bind(this); - this.read_cookie = this.read_cookie.bind(this); - this.setCookie = this.setCookie.bind(this); - this.set_cookie = this.set_cookie.bind(this); - this.notificationPanel = this.notificationPanel.bind(this); - this.notify_in_panel = this.notify_in_panel.bind(this); - this.start_spinner = this.start_spinner.bind(this); - this.stop_spinner = this.stop_spinner.bind(this); - /* EVENT HANDLER */ - this.on_date_range_start_change = this.on_date_range_start_change.bind(this); - this.on_date_range_end_change = this.on_date_range_end_change.bind(this); - this.on_autocomplete_keydown = this.on_autocomplete_keydown.bind(this); - this.on_at_integer_field_keyup = this.on_at_integer_field_keyup.bind(this); - this.on_at_float_field_keyup = this.on_at_float_field_keyup.bind(this); - this.on_numeric_field_paste = this.on_numeric_field_paste.bind(this); - this.on_numeric_field_keypress = this.on_numeric_field_keypress.bind(this); - this.on_reference_definition_list_change = this.on_reference_definition_list_change.bind(this); - this.on_service_info_click = this.on_service_info_click.bind(this); - this.on_ajax_start = this.on_ajax_start.bind(this); - this.on_ajax_end = this.on_ajax_end.bind(this); - this.on_ajax_error = this.on_ajax_error.bind(this); - } - load() { +/* Please use this command to compile this file into the parent `js` directory: + coffee --no-header -w -o ../ -c bika.lims.site.coffee + */ + +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + window.SiteView = (function() { + function SiteView() { + this.on_ajax_error = bind(this.on_ajax_error, this); + this.on_ajax_end = bind(this.on_ajax_end, this); + this.on_ajax_start = bind(this.on_ajax_start, this); + this.on_service_info_click = bind(this.on_service_info_click, this); + this.on_reference_definition_list_change = bind(this.on_reference_definition_list_change, this); + this.on_numeric_field_keypress = bind(this.on_numeric_field_keypress, this); + this.on_numeric_field_paste = bind(this.on_numeric_field_paste, this); + this.on_at_float_field_keyup = bind(this.on_at_float_field_keyup, this); + this.on_at_integer_field_keyup = bind(this.on_at_integer_field_keyup, this); + this.on_autocomplete_keydown = bind(this.on_autocomplete_keydown, this); + this.on_date_range_end_change = bind(this.on_date_range_end_change, this); + this.on_date_range_start_change = bind(this.on_date_range_start_change, this); + this.stop_spinner = bind(this.stop_spinner, this); + this.start_spinner = bind(this.start_spinner, this); + this.notify_in_panel = bind(this.notify_in_panel, this); + this.notificationPanel = bind(this.notificationPanel, this); + this.set_cookie = bind(this.set_cookie, this); + this.setCookie = bind(this.setCookie, this); + this.read_cookie = bind(this.read_cookie, this); + this.readCookie = bind(this.readCookie, this); + this.log = bind(this.log, this); + this.portal_alert = bind(this.portal_alert, this); + this.portalAlert = bind(this.portalAlert, this); + this.get_authenticator = bind(this.get_authenticator, this); + this.get_portal_url = bind(this.get_portal_url, this); + this.init_referencedefinition = bind(this.init_referencedefinition, this); + this.init_datepickers = bind(this.init_datepickers, this); + this.init_spinner = bind(this.init_spinner, this); + this.init_client_add_overlay = bind(this.init_client_add_overlay, this); + this.bind_eventhandler = bind(this.bind_eventhandler, this); + this.load = bind(this.load, this); + } + + SiteView.prototype.load = function() { console.debug("SiteView::load"); - // load translations jarn.i18n.loadCatalog('senaite.core'); this._ = window.jarn.i18n.MessageFactory("senaite.core"); - // initialze the loading spinner this.init_spinner(); - // initialze the client add overlay this.init_client_add_overlay(); - // initialze the client combogrid - this.init_client_combogrid(); - // initialze datepickers this.init_datepickers(); - // initialze reference definition selection this.init_referencedefinition(); - // bind the event handler to the elements this.bind_eventhandler(); - // allowed keys for numeric fields - return this.allowed_keys = [ - 8, // backspace - 9, // tab - 13, // enter - 35, // end - 36, // home - 37, // left arrow - 39, // right arrow - 46, // delete - We don't support the del key in Opera because del == . == 46. - 44, // , - 60, // < - 62, // > - 45, // - - 69, // E - 101, // e, - 61 // = - ]; - } + return this.allowed_keys = [8, 9, 13, 35, 36, 37, 39, 46, 44, 60, 62, 45, 69, 101, 61]; + }; + + + /* INITIALIZERS */ + + SiteView.prototype.bind_eventhandler = function() { - bind_eventhandler() { /* * Binds callbacks on elements * @@ -87,28 +65,22 @@ * delegate the event: https://learn.jquery.com/events/event-delegation/ */ console.debug("SiteView::bind_eventhandler"); - // ReferenceSample selection changed $("body").on("change", "#ReferenceDefinition\\:list", this.on_reference_definition_list_change); - // Numeric field events $("body").on("keypress", ".numeric", this.on_numeric_field_keypress); $("body").on("paste", ".numeric", this.on_numeric_field_paste); - // AT field events $("body").on("keyup", "input[name*='\\:int\'], .ArchetypesIntegerWidget input", this.on_at_integer_field_keyup); $("body").on("keyup", "input[name*='\\:float\'], .ArchetypesDecimalWidget input", this.on_at_float_field_keyup); - // Autocomplete events - // XXX Where is this used? $("body").on("keydown", "input.autocomplete", this.on_autocomplete_keydown); - // Date Range Filtering $("body").on("change", ".date_range_start", this.on_date_range_start_change); $("body").on("change", ".date_range_end", this.on_date_range_end_change); $("body").on("click", "a.service_info", this.on_service_info_click); - // handle Ajax events $(document).on("ajaxStart", this.on_ajax_start); $(document).on("ajaxStop", this.on_ajax_end); return $(document).on("ajaxError", this.on_ajax_error); - } + }; + + SiteView.prototype.init_client_add_overlay = function() { - init_client_add_overlay() { /* * Initialize Client Overlay */ @@ -123,70 +95,35 @@ config: { closeOnEsc: false, onLoad: function() { - // manually remove remarks this.getOverlay().find('.ArchetypesRemarksWidget').remove(); }, onClose: function() {} } }); - } + }; + + SiteView.prototype.init_spinner = function() { - init_spinner() { /* * Initialize Spinner Overlay */ console.debug("SiteView::init_spinner"); - // unbind default Plone loader $(document).unbind('ajaxStart'); $(document).unbind('ajaxStop'); $('#ajax-spinner').remove(); - // counter of active spinners this.counter = 0; - // crate a spinner and append it to the body - this.spinner = $(`
`); + this.spinner = $("
"); return this.spinner.appendTo('body').hide(); - } + }; - init_client_combogrid() { - /* - * Initialize client combogrid, e.g. on the Client Add View - */ - console.debug("SiteView::init_client_combogrid"); - return $("input[id*='ClientID']").combogrid({ - colModel: [ - { - 'columnName': 'ClientUID', - 'hidden': true - }, - { - 'columnName': 'ClientID', - 'width': '20', - 'label': _('Client ID') - }, - { - 'columnName': 'Title', - 'width': '80', - 'label': _('Title') - } - ], - showOn: true, - width: '450px', - url: `${this.get_portal_url()}/getClients?_authenticator=${this.get_authenticator()}`, - select: function(event, ui) { - $(this).val(ui.item.ClientID); - $(this).change(); - return false; - } - }); - } + SiteView.prototype.init_datepickers = function() { - init_datepickers() { - var curDate, dateFormat, limitString, y; /* * Initialize date pickers * * XXX Where are these event handlers used? */ + var curDate, dateFormat, limitString, y; console.debug("SiteView::init_datepickers"); curDate = new Date; y = curDate.getFullYear(); @@ -196,11 +133,12 @@ dateFormat = 'yy-mm-dd'; } $('input.datepicker_range').datepicker({ + /** This function defines a datepicker for a date range. Both input elements should be siblings and have the class 'date_range_start' and 'date_range_end'. - */ + */ showOn: 'focus', showAnim: '', changeMonth: true, @@ -270,9 +208,10 @@ $(this).attr('value', ''); }).focus(); }); - } + }; + + SiteView.prototype.init_referencedefinition = function() { - init_referencedefinition() { /* * Initialize reference definition selection * XXX: When is this used? @@ -282,68 +221,77 @@ console.warn("SiteView::init_referencedefinition: Refactor this method!"); return $('#ReferenceDefinition:list').change(); } - } + }; + + + /* METHODS */ + + SiteView.prototype.get_portal_url = function() { - get_portal_url() { /* * Return the portal url */ return window.portal_url; - } + }; + + SiteView.prototype.get_authenticator = function() { - get_authenticator() { /* * Get the authenticator value */ return $("input[name='_authenticator']").val(); - } + }; + + SiteView.prototype.portalAlert = function(html) { - portalAlert(html) { /* * BBB: Use portal_alert */ console.warn("SiteView::portalAlert: Please use portal_alert method instead."); return this.portal_alert(html); - } + }; + + SiteView.prototype.portal_alert = function(html) { - portal_alert(html) { - var alerts; /* * Display a portal alert box */ + var alerts; console.debug("SiteView::portal_alert"); alerts = $('#portal-alert'); if (alerts.length === 0) { - $('#portal-header').append(``); + $('#portal-header').append(""); } else { - alerts.append(`
${html}
`); + alerts.append("
" + html + "
"); } alerts.fadeIn(); - } + }; + + SiteView.prototype.log = function(message) { - log(message) { /* * Log message via bika.lims.log */ - console.debug(`SiteView::log: message=${message}`); - // XXX: This should actually log via XHR to the server, but seem to not work. + console.debug("SiteView::log: message=" + message); return window.bika.lims.log(message); - } + }; + + SiteView.prototype.readCookie = function(cname) { - readCookie(cname) { /* * BBB: Use read_cookie */ console.warn("SiteView::readCookie: Please use read_cookie method instead."); return this.read_cookie(cname); - } + }; + + SiteView.prototype.read_cookie = function(cname) { - read_cookie(cname) { - var c, ca, i, name; /* * Read cookie value */ - console.debug(`SiteView::read_cookie:${cname}`); + var c, ca, i, name; + console.debug("SiteView::read_cookie:" + cname); name = cname + '='; ca = document.cookie.split(';'); i = 0; @@ -358,72 +306,78 @@ i++; } return null; - } + }; + + SiteView.prototype.setCookie = function(cname, cvalue) { - setCookie(cname, cvalue) { /* * BBB: Use set_cookie */ console.warn("SiteView::setCookie: Please use set_cookie method instead."); return this.set_cookie(cname, cvalue); - } + }; + + SiteView.prototype.set_cookie = function(cname, cvalue) { - set_cookie(cname, cvalue) { - var d, expires; /* * Read cookie value */ - console.debug(`SiteView::set_cookie:cname=${cname}, cvalue=${cvalue}`); + var d, expires; + console.debug("SiteView::set_cookie:cname=" + cname + ", cvalue=" + cvalue); d = new Date; d.setTime(d.getTime() + 1 * 24 * 60 * 60 * 1000); expires = 'expires=' + d.toUTCString(); document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; - } + }; + + SiteView.prototype.notificationPanel = function(data, mode) { - notificationPanel(data, mode) { /* * BBB: Use notify_in_panel */ console.warn("SiteView::notificationPanel: Please use notfiy_in_panel method instead."); return this.notify_in_panel(data, mode); - } + }; + + SiteView.prototype.notify_in_panel = function(data, mode) { - notify_in_panel(data, mode) { - var html; /* * Render an alert inside the content panel, e.g.in autosave of ARView */ - console.debug(`SiteView::notify_in_panel:data=${data}, mode=${mode}`); + var html; + console.debug("SiteView::notify_in_panel:data=" + data + ", mode=" + mode); $('#panel-notification').remove(); - html = ``; + html = ""; $('div#viewlet-above-content-title').append(html); $('#panel-notification').fadeIn('slow', 'linear', function() { setTimeout((function() { $('#panel-notification').fadeOut('slow', 'linear'); }), 3000); }); - } + }; + + SiteView.prototype.start_spinner = function() { - start_spinner() { /* * Start Spinner Overlay */ console.debug("SiteView::start_spinner"); - // increase the counter this.counter++; - this.timer = setTimeout((() => { - if (this.counter > 0) { - this.spinner.show('fast'); - } - }), 500); - } + this.timer = setTimeout(((function(_this) { + return function() { + if (_this.counter > 0) { + _this.spinner.show('fast'); + } + }; + })(this)), 500); + }; + + SiteView.prototype.stop_spinner = function() { - stop_spinner() { /* * Stop Spinner Overlay */ console.debug("SiteView::stop_spinner"); - // decrease the counter this.counter--; if (this.counter < 0) { this.counter = 0; @@ -433,49 +387,53 @@ this.spinner.stop(); this.spinner.hide(); } - } + }; + + + /* EVENT HANDLER */ + + SiteView.prototype.on_date_range_start_change = function(event) { - on_date_range_start_change(event) { - var $el, brother, date_element, el; /* * Eventhandler for Date Range Filtering * * 1. Go to Setup and enable advanced filter bar * 2. Set the start date of adv. filter bar, e.g. in AR listing */ + var $el, brother, date_element, el; console.debug("°°° SiteView::on_date_range_start_change °°°"); el = event.currentTarget; $el = $(el); - // Set the min selectable end date to the start date date_element = $el.datepicker('getDate'); brother = $el.siblings('.date_range_end'); return $(brother).datepicker('option', 'minDate', date_element); - } + }; + + SiteView.prototype.on_date_range_end_change = function(event) { - on_date_range_end_change(event) { - var $el, brother, date_element, el; /* * Eventhandler for Date Range Filtering * * 1. Go to Setup and enable advanced filter bar * 2. Set the start date of adv. filter bar, e.g. in AR listing */ + var $el, brother, date_element, el; console.debug("°°° SiteView::on_date_range_end_change °°°"); el = event.currentTarget; $el = $(el); - // Set the max selectable start date to the end date date_element = $el.datepicker('getDate'); brother = $el.siblings('.date_range_start'); return $(brother).datepicker('option', 'maxDate', date_element); - } + }; + + SiteView.prototype.on_autocomplete_keydown = function(event) { - on_autocomplete_keydown(event) { - var $el, availableTags, el, extractLast, split; /* * Eventhandler for Autocomplete fields * * XXX: Refactor if it is clear where this code is used! */ + var $el, availableTags, el, extractLast, split; console.debug("°°° SiteView::on_autocomplete_keydown °°°"); el = event.currentTarget; $el = $(el); @@ -493,81 +451,77 @@ return $el.autocomplete({ minLength: 0, source: function(request, response) { - // delegate back to autocomplete, but extract the last term response($.ui.autocomplete.filter(availableTags, extractLast(request.term))); }, focus: function() { - // prevent value inserted on focus return false; }, select: function(event, ui) { var terms; terms = split($el.val()); - // remove the current input terms.pop(); - // add the selected item terms.push(ui.item.value); - // add placeholder to get the comma-and-space at the end terms.push(''); this.el.val(terms.join(', ')); return false; } }); - } + }; + + SiteView.prototype.on_at_integer_field_keyup = function(event) { - on_at_integer_field_keyup(event) { - var $el, el; /* * Eventhandler for AT integer fields */ + var $el, el; console.debug("°°° SiteView::on_at_integer_field_keyup °°°"); el = event.currentTarget; $el = $(el); if (/\D/g.test($el.val())) { $el.val($el.val().replace(/\D/g, '')); } - } + }; + + SiteView.prototype.on_at_float_field_keyup = function(event) { - on_at_float_field_keyup(event) { - var $el, el; /* * Eventhandler for AT float fields */ + var $el, el; console.debug("°°° SiteView::on_at_float_field_keyup °°°"); el = event.currentTarget; $el = $(el); if (/[^-.\d]/g.test($el.val())) { $el.val($el.val().replace(/[^.\d]/g, '')); } - } + }; + + SiteView.prototype.on_numeric_field_paste = function(event) { - on_numeric_field_paste(event) { - var $el, el; /* * Eventhandler when the user pasted a value inside a numeric field. */ + var $el, el; console.debug("°°° SiteView::on_numeric_field_paste °°°"); el = event.currentTarget; $el = $(el); - // Wait (next cycle) for value popluation and replace commas. window.setTimeout((function() { $el.val($el.val().replace(',', '.')); }), 0); - } + }; + + SiteView.prototype.on_numeric_field_keypress = function(event) { - on_numeric_field_keypress(event) { - var $el, el, isAllowedKey, key; /* * Eventhandler when the user pressed a key inside a numeric field. */ + var $el, el, isAllowedKey, key; console.debug("°°° SiteView::on_numeric_field_keypress °°°"); el = event.currentTarget; $el = $(el); key = event.which; isAllowedKey = this.allowed_keys.join(',').match(new RegExp(key)); if (!key || 48 <= key && key <= 57 || isAllowedKey) { - // Opera assigns values for control keys. - // Wait (next cycle) for value popluation and replace commas. window.setTimeout((function() { $el.val($el.val().replace(',', '.')); }), 0); @@ -575,10 +529,10 @@ } else { event.preventDefault(); } - } + }; + + SiteView.prototype.on_reference_definition_list_change = function(event) { - on_reference_definition_list_change(event) { - var $el, authenticator, el, option, uid; /* * Eventhandler when the user clicked on the reference defintion dropdown. * @@ -588,6 +542,7 @@ * * The dropdown with the id="ReferenceDefinition:list" is rendered there. */ + var $el, authenticator, el, option, uid; console.debug("°°° SiteView::on_reference_definition_list_change °°°"); el = event.currentTarget; $el = $(el); @@ -595,8 +550,6 @@ uid = $el.val(); option = $el.children(':selected').html(); if (uid === '') { - // No reference definition selected; - // render empty widget. $('#Blank').prop('checked', false); $('#Hazardous').prop('checked', false); $('.bika-listing-table').load('referenceresults', { @@ -618,18 +571,17 @@ '_authenticator': authenticator, 'uid': uid }); - } + }; + + SiteView.prototype.on_service_info_click = function(event) { - on_service_info_click(event) { - var el; /* * Eventhandler when the service info icon was clicked */ + var el; console.debug("°°° SiteView::on_service_info_click °°°"); event.preventDefault(); el = event.currentTarget; - // https://jquerytools.github.io/documentation/overlay - // https://github.com/plone/plone.app.jquerytools/blob/master/plone/app/jquerytools/browser/overlayhelpers.js $(el).prepOverlay({ subtype: "ajax", width: '70%', @@ -643,45 +595,44 @@ return overlay.draggable(); }, onLoad: function(event) { - // manually dispatch the DOMContentLoaded event, so that the ReactJS - // component loads event = new Event("DOMContentLoaded", {}); return window.document.dispatchEvent(event); } } }); - // workaround un-understandable overlay api return $(el).click(); - } + }; + + SiteView.prototype.on_ajax_start = function(event) { - on_ajax_start(event) { /* * Eventhandler if an global Ajax Request started */ console.debug("°°° SiteView::on_ajax_start °°°"); - // start the loading spinner return this.start_spinner(); - } + }; + + SiteView.prototype.on_ajax_end = function(event) { - on_ajax_end(event) { /* * Eventhandler if an global Ajax Request ended */ console.debug("°°° SiteView::on_ajax_end °°°"); - // stop the loading spinner return this.stop_spinner(); - } + }; + + SiteView.prototype.on_ajax_error = function(event, jqxhr, settings, thrownError) { - on_ajax_error(event, jqxhr, settings, thrownError) { /* * Eventhandler if an global Ajax Request error */ console.debug("°°° SiteView::on_ajax_error °°°"); - // stop the loading spinner this.stop_spinner(); - return this.log(`Error at ${settings.url}: ${thrownError}`); - } + return this.log("Error at " + settings.url + ": " + thrownError); + }; + + return SiteView; - }; + })(); }).call(this); diff --git a/bika/lims/browser/js/bika.lims.worksheet.js b/bika/lims/browser/js/bika.lims.worksheet.js index 575d770274..fb4764eb66 100644 --- a/bika/lims/browser/js/bika.lims.worksheet.js +++ b/bika/lims/browser/js/bika.lims.worksheet.js @@ -109,216 +109,6 @@ })(); - window.WorksheetAddAnalysesView = (function() { - function WorksheetAddAnalysesView() { - this.on_search_click = bind(this.on_search_click, this); - this.on_category_change = bind(this.on_category_change, this); - this.filter_service_selector_by_category_uid = bind(this.filter_service_selector_by_category_uid, this); - this.get_listing_form = bind(this.get_listing_form, this); - this.get_listing_form_id = bind(this.get_listing_form_id, this); - this.get_authenticator = bind(this.get_authenticator, this); - this.get_base_url = bind(this.get_base_url, this); - this.ajax_submit = bind(this.ajax_submit, this); - this.bind_eventhandler = bind(this.bind_eventhandler, this); - this.load = bind(this.load, this); - } - - - /* - * Controller class for Worksheet's add analyses view - */ - - WorksheetAddAnalysesView.prototype.load = function() { - console.debug("WorksheetAddanalysesview::load"); - return this.bind_eventhandler(); - }; - - - /* INITIALIZERS */ - - WorksheetAddAnalysesView.prototype.bind_eventhandler = function() { - - /* - * Binds callbacks on elements - * - * N.B. We attach all the events to the form and refine the selector to - * delegate the event: https://learn.jquery.com/events/event-delegation/ - * - */ - console.debug("WorksheetAddanalysesview::bind_eventhandler"); - $("body").on("change", "[name='list_FilterByCategory']", this.on_category_change); - return $("body").on("click", ".ws-analyses-search-button", this.on_search_click); - }; - - - /* METHODS */ - - WorksheetAddAnalysesView.prototype.ajax_submit = function(options) { - var done; - if (options == null) { - options = {}; - } - - /* - * Ajax Submit with automatic event triggering and some sane defaults - */ - console.debug("°°° ajax_submit °°°"); - if (options.type == null) { - options.type = "POST"; - } - if (options.url == null) { - options.url = this.get_base_url(); - } - if (options.context == null) { - options.context = this; - } - console.debug(">>> ajax_submit::options=", options); - $(this).trigger("ajax:submit:start"); - done = (function(_this) { - return function() { - return $(_this).trigger("ajax:submit:end"); - }; - })(this); - return $.ajax(options).done(done); - }; - - WorksheetAddAnalysesView.prototype.get_base_url = function() { - - /* - * Return the current base url - */ - var url; - url = window.location.href; - return url.split('?')[0]; - }; - - WorksheetAddAnalysesView.prototype.get_authenticator = function() { - - /* - * Get the authenticator value - */ - return $("input[name='_authenticator']").val(); - }; - - WorksheetAddAnalysesView.prototype.get_listing_form_id = function() { - - /* - * Returns the CSS ID of the analyses listing - */ - return "list"; - }; - - WorksheetAddAnalysesView.prototype.get_listing_form = function() { - - /* - * Returns the analyses listing form element - */ - var form_id; - form_id = this.get_listing_form_id(); - return $("form[id='" + form_id + "']"); - }; - - WorksheetAddAnalysesView.prototype.filter_service_selector_by_category_uid = function(category_uid) { - - /* - * Filters the service selector by category - */ - var $select, base_url, data, form_id, select_name, url; - console.debug("WorksheetAddanalysesview::filter_service_selector_by_category_uid:" + category_uid); - form_id = this.get_listing_form_id(); - select_name = form_id + "_FilterByService"; - $select = $("[name='" + select_name + "']"); - base_url = this.get_base_url(); - url = base_url.replace("/add_analyses", "/getServices"); - data = { - _authenticator: this.get_authenticator() - }; - if (category_uid !== "any") { - data["getCategoryUID"] = category_uid; - } - return this.ajax_submit({ - url: url, - data: data, - dataType: "json" - }).done(function(data) { - var any_option; - $select.empty(); - any_option = ""; - $select.append(any_option); - return $.each(data, function(index, item) { - var name, option, uid; - uid = item[0]; - name = item[1]; - option = ""; - return $select.append(option); - }); - }); - }; - - - /* EVENT HANDLER */ - - WorksheetAddAnalysesView.prototype.on_category_change = function(event) { - - /* - * Eventhandler for category change - */ - var $el, category_uid; - console.debug("°°° WorksheetAddanalysesview::on_category_change °°°"); - $el = $(event.currentTarget); - category_uid = $el.val(); - return this.filter_service_selector_by_category_uid(category_uid); - }; - - WorksheetAddAnalysesView.prototype.on_search_click = function(event) { - - /* - * Eventhandler for the search button - */ - var filter_indexes, form, form_data, form_id; - console.debug("°°° WorksheetAddanalysesview::on_search_click °°°"); - event.preventDefault(); - form = this.get_listing_form(); - form_id = this.get_listing_form_id(); - filter_indexes = ["FilterByCategory", "FilterByService", "FilterByClient"]; - $.each(filter_indexes, function(index, filter) { - var $el, input, name, value; - name = form_id + "_" + filter; - $el = $("select[name='" + name + "']"); - value = $el.val(); - input = $("input[name='" + name + "']", form); - if (input.length === 0) { - form.append(""); - input = $("input[name='" + name + "']", form); - } - input.val(value); - if (value === "any") { - return input.remove(); - } - }); - form_data = new FormData(form[0]); - form_data.set("table_only", form_id); - return this.ajax_submit({ - data: form_data, - processData: false, - contentType: false - }).done(function(data) { - var $container, $data; - $container = $("div.bika-listing-table-container", form); - $data = $(data); - if ($data.find("tbody").length === 0) { - return $container.html("
0 " + (_('Results')) + "
"); - } else { - $container.html(data); - return window.bika.lims.BikaListingTableView.load_transitions(); - } - }); - }; - - return WorksheetAddAnalysesView; - - })(); - window.WorksheetAddQCAnalysesView = (function() { function WorksheetAddQCAnalysesView() { this.on_referencesample_row_click = bind(this.on_referencesample_row_click, this); diff --git a/bika/lims/browser/js/coffee/bika.lims.site.coffee b/bika/lims/browser/js/coffee/bika.lims.site.coffee index c49feb096c..fea2bfbc1d 100644 --- a/bika/lims/browser/js/coffee/bika.lims.site.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.site.coffee @@ -18,9 +18,6 @@ class window.SiteView # initialze the client add overlay @init_client_add_overlay() - # initialze the client combogrid - @init_client_combogrid() - # initialze datepickers @init_datepickers() @@ -130,34 +127,6 @@ class window.SiteView @spinner.appendTo('body').hide() - init_client_combogrid: => - ### - * Initialize client combogrid, e.g. on the Client Add View - ### - console.debug "SiteView::init_client_combogrid" - - $("input[id*='ClientID']").combogrid - colModel: [ - 'columnName': 'ClientUID' - 'hidden': true - , - 'columnName': 'ClientID' - 'width': '20' - 'label': _('Client ID') - , - 'columnName': 'Title' - 'width': '80' - 'label': _('Title') - ] - showOn: true - width: '450px' - url: "#{@get_portal_url()}/getClients?_authenticator=#{@get_authenticator()}" - select: (event, ui) -> - $(this).val ui.item.ClientID - $(this).change() - false - - init_datepickers: => ### * Initialize date pickers diff --git a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee index 3dfcefdb8b..41890ed40e 100644 --- a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee @@ -100,195 +100,6 @@ class window.WorksheetFolderView bika.lims.SiteView.notify_in_panel message, "error" - -class window.WorksheetAddAnalysesView - ### - * Controller class for Worksheet's add analyses view - ### - - load: => - console.debug "WorksheetAddanalysesview::load" - - # bind the event handler to the elements - @bind_eventhandler() - - - ### INITIALIZERS ### - - bind_eventhandler: => - ### - * Binds callbacks on elements - * - * N.B. We attach all the events to the form and refine the selector to - * delegate the event: https://learn.jquery.com/events/event-delegation/ - * - ### - console.debug "WorksheetAddanalysesview::bind_eventhandler" - - # Category filter changed - $("body").on "change", "[name='list_FilterByCategory']", @on_category_change - - # Search button clicked - $("body").on "click", ".ws-analyses-search-button", @on_search_click - - - ### METHODS ### - - ajax_submit: (options={}) => - ### - * Ajax Submit with automatic event triggering and some sane defaults - ### - console.debug "°°° ajax_submit °°°" - - # some sane option defaults - options.type ?= "POST" - options.url ?= @get_base_url() - options.context ?= this - - console.debug ">>> ajax_submit::options=", options - - $(this).trigger "ajax:submit:start" - done = => - $(this).trigger "ajax:submit:end" - return $.ajax(options).done done - - - get_base_url: => - ### - * Return the current base url - ### - url = window.location.href - return url.split('?')[0] - - - get_authenticator: => - ### - * Get the authenticator value - ### - return $("input[name='_authenticator']").val() - - - get_listing_form_id: () => - ### - * Returns the CSS ID of the analyses listing - ### - return "list" - - - get_listing_form: () => - ### - * Returns the analyses listing form element - ### - form_id = @get_listing_form_id() - return $("form[id='#{form_id}']") - - - filter_service_selector_by_category_uid: (category_uid) => - ### - * Filters the service selector by category - ### - console.debug "WorksheetAddanalysesview::filter_service_selector_by_category_uid:#{category_uid}" - - form_id = @get_listing_form_id() - select_name = "#{form_id}_FilterByService" - $select = $("[name='#{select_name}']") - - base_url = @get_base_url() - url = base_url.replace "/add_analyses", "/getServices" - - data = - _authenticator: @get_authenticator() - - if category_uid isnt "any" - data["getCategoryUID"] = category_uid - - @ajax_submit - url: url - data: data - dataType: "json" - .done (data) -> - $select.empty() - any_option = "" - $select.append any_option - $.each data, (index, item) -> - uid = item[0] - name = item[1] - option = "" - $select.append option - - - ### EVENT HANDLER ### - - on_category_change: (event) => - ### - * Eventhandler for category change - ### - console.debug "°°° WorksheetAddanalysesview::on_category_change °°°" - - # The select element for WS Template - $el = $(event.currentTarget) - - # extract the category UID and filter the services box - category_uid = $el.val() - @filter_service_selector_by_category_uid category_uid - - - on_search_click: (event) => - ### - * Eventhandler for the search button - ### - console.debug "°°° WorksheetAddanalysesview::on_search_click °°°" - - # Prevent form submit - event.preventDefault() - - form = @get_listing_form() - form_id = @get_listing_form_id() - - filter_indexes = [ - "FilterByCategory" - "FilterByService" - "FilterByClient" - ] - - # The filter elements (Category/Service/Client) belong to another form. - # Therefore, we need to inject these values into the listing form as hidden - # input fields. - $.each filter_indexes, (index, filter) -> - name = "#{form_id}_#{filter}" - $el = $("select[name='#{name}']") - value = $el.val() - - # get the corresponding input element of the listing form - input = $("input[name='#{name}']", form) - if input.length == 0 - form.append "" - input = $("input[name='#{name}']", form) - input.val value - - # omit the field if the value is set to any - if value == "any" - input.remove() - - # extract the data of the listing form and post it to the AddAnalysesView - form_data = new FormData form[0] - form_data.set "table_only", form_id - - @ajax_submit - data: form_data - processData: no # do not transform to a query string - contentType: no # do not set any content type header - .done (data) -> - $container = $("div.bika-listing-table-container", form) - $data = $(data) - if $data.find("tbody").length == 0 - $container.html "
0 #{_('Results')}
" - else - $container.html data - window.bika.lims.BikaListingTableView.load_transitions() - - - class window.WorksheetAddQCAnalysesView ### * Controller class for Worksheet's add blank/control views diff --git a/bika/lims/browser/referencesample.py b/bika/lims/browser/referencesample.py index e217b3df74..053475ceed 100644 --- a/bika/lims/browser/referencesample.py +++ b/bika/lims/browser/referencesample.py @@ -266,11 +266,10 @@ def __init__(self, context, request): self.show_search = False # Categories + self.categories = [] if self.show_categories_enabled(): - self.categories = [] self.show_categories = True self.expand_all_categories = True - self.category_index = "getCategoryTitle" self.columns = collections.OrderedDict(( ("Title", { diff --git a/bika/lims/browser/reports/productivity_analysesperservice.py b/bika/lims/browser/reports/productivity_analysesperservice.py index 6933cc5149..18e4f06266 100644 --- a/bika/lims/browser/reports/productivity_analysesperservice.py +++ b/bika/lims/browser/reports/productivity_analysesperservice.py @@ -103,7 +103,7 @@ def __call__(self): 'colspan': 2}, ] datalines.append(dataline) for service in sc(portal_type="AnalysisService", - getCategoryUID=cat.UID, + category_uid=cat.UID, sort_on='sortable_title'): query['getServiceUID'] = service.UID analyses = bc(query) diff --git a/bika/lims/browser/reports/selection_macros/__init__.py b/bika/lims/browser/reports/selection_macros/__init__.py index d5d22e0bda..c23a2f453b 100644 --- a/bika/lims/browser/reports/selection_macros/__init__.py +++ b/bika/lims/browser/reports/selection_macros/__init__.py @@ -156,14 +156,6 @@ def _cache_key_select_reference_service(method, self, style=None): return key -def _cache_key_select_samplepoint(method, self, allow_blank=True, multiselect=False, style=None): - """ - This function returns the key used to decide if method select_samplepoint has to be recomputed - """ - key = update_timer(), allow_blank, multiselect, style - return key - - def _cache_key_select_sample_type(method, self, allow_blank=True, multiselect=False, style=None): """ This function returns the key used to decide if method select_sample_type has to be recomputed @@ -457,29 +449,6 @@ def parse_state(self, request, workflow_id, field_id, field_title): res['titles'] = state_title return res - select_samplepoint_pt = ViewPageTemplateFile("select_samplepoint.pt") - - @ram.cache(_cache_key_select_samplepoint) - def select_samplepoint(self, allow_blank=True, multiselect=False, style=None): - self.style = style - self.allow_blank = allow_blank - self.multiselect = multiselect - self.samplepoints = self.bsc(portal_type='SamplePoint', - is_active=True, - sort_on='sortable_title') - return self.select_samplepoint_pt() - - def parse_samplepoint(self, request): - val = request.form.get("SamplePointUID", "") - if val: - obj = val and self.rc.lookupObject(val) - title = obj.Title() - res = {} - res['contentFilter'] = ('getSamplePointUID', val) - res['parms'] = {'title': _("Sample Point"), 'value': title} - res['titles'] = title - return res - select_sampletype_pt = ViewPageTemplateFile("select_sampletype.pt") @ram.cache(_cache_key_select_sample_type) diff --git a/bika/lims/browser/reports/selection_macros/select_samplepoint.pt b/bika/lims/browser/reports/selection_macros/select_samplepoint.pt deleted file mode 100644 index fee1748199..0000000000 --- a/bika/lims/browser/reports/selection_macros/select_samplepoint.pt +++ /dev/null @@ -1,26 +0,0 @@ -
- - -
- - - -
diff --git a/bika/lims/browser/widgets/analysisprofileanalyseswidget.py b/bika/lims/browser/widgets/analysisprofileanalyseswidget.py index 0bb0d7208f..c7cd1ea270 100644 --- a/bika/lims/browser/widgets/analysisprofileanalyseswidget.py +++ b/bika/lims/browser/widgets/analysisprofileanalyseswidget.py @@ -27,6 +27,7 @@ from bika.lims.api.security import check_permission from bika.lims.browser.bika_listing import BikaListingView from bika.lims.permissions import FieldEditProfiles +from bika.lims.utils import format_supsub from bika.lims.utils import get_image from bika.lims.utils import get_link from plone.memoize import view @@ -79,7 +80,6 @@ def __init__(self, context, request): "sortable": False}), ("Unit", { "title": _("Unit"), - "index": "getUnit", "sortable": False}), ("Price", { "title": _("Price"), @@ -238,6 +238,10 @@ def folderitem(self, obj, item, index): else: item["methods"] = "" + # Unit + unit = obj.getUnit() + item["Unit"] = unit and format_supsub(unit) or "" + # Icons after_icons = "" if obj.getAccredited(): diff --git a/bika/lims/browser/widgets/referenceresultswidget.py b/bika/lims/browser/widgets/referenceresultswidget.py index 9ba7161345..e5d28994bb 100644 --- a/bika/lims/browser/widgets/referenceresultswidget.py +++ b/bika/lims/browser/widgets/referenceresultswidget.py @@ -55,11 +55,10 @@ def __init__(self, context, request, fieldvalue=[], allow_edit=True): self.omit_form = True # Categories + self.categories = [] if self.show_categories_enabled(): - self.categories = [] self.show_categories = True self.expand_all_categories = False - self.category_index = "getCategoryTitle" self.columns = collections.OrderedDict(( ("Title", { diff --git a/bika/lims/browser/widgets/serviceswidget.py b/bika/lims/browser/widgets/serviceswidget.py index c46d46a494..b3c8e5e43a 100644 --- a/bika/lims/browser/widgets/serviceswidget.py +++ b/bika/lims/browser/widgets/serviceswidget.py @@ -49,7 +49,7 @@ def __init__(self, context, request): method_uid = context.getMethodUID() if method_uid: self.contentFilter.update({ - "getAvailableMethodUIDs": method_uid + "method_available_uid": method_uid }) self.context_actions = {} diff --git a/bika/lims/browser/worksheet/ajax.py b/bika/lims/browser/worksheet/ajax.py index ad2592af5c..618eb0aa0b 100644 --- a/bika/lims/browser/worksheet/ajax.py +++ b/bika/lims/browser/worksheet/ajax.py @@ -18,46 +18,17 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -from bika.lims import logger -from bika.lims.utils import t +import json from operator import itemgetter -from Products.Archetypes.config import REFERENCE_CATALOG -from Products.CMFCore.utils import getToolByName import plone import plone.protect -import json +from Products.Archetypes.config import REFERENCE_CATALOG +from Products.CMFCore.utils import getToolByName from bika.lims.workflow import getCurrentState -class GetServices(): - """ When a Category is selected in the add_analyses search screen, this - function returns a list of services from the selected category. - """ - def __init__(self, context, request): - self.context = context - self.request = request - - def __call__(self): - plone.protect.CheckAuthenticator(self.request) - bsc = getToolByName(self.context, 'bika_setup_catalog') - - query = { - "portal_type": 'AnalysisService', - "is_active": True, - "sort_on": 'sortable_title', - } - - getCategoryUID = self.request.get('getCategoryUID', '') - if getCategoryUID: - query["getCategoryUID"] = getCategoryUID - - brains = bsc(query) - voc = [[brain.UID, brain.Title] for brain in brains] - return json.dumps(voc) - - class AttachAnalyses(): """ In attachment add form, the analyses dropdown combo uses this as source. diff --git a/bika/lims/browser/worksheet/configure.zcml b/bika/lims/browser/worksheet/configure.zcml index 2f7187f6d8..cd4ddb6956 100644 --- a/bika/lims/browser/worksheet/configure.zcml +++ b/bika/lims/browser/worksheet/configure.zcml @@ -106,14 +106,6 @@ - - - - - - - - - - - - - - - - - + - - - - + + + - - - - - - + + + + + + + + + + + + + @@ -41,5 +34,22 @@ + + + + + + + + + + + + + + + + + diff --git a/bika/lims/catalog/indexers/contact.py b/bika/lims/catalog/indexers/contact.py new file mode 100644 index 0000000000..3cfff8962f --- /dev/null +++ b/bika/lims/catalog/indexers/contact.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.CORE is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2018-2019 by it's authors. +# Some rights reserved, see README and LICENSE. + +from plone.indexer import indexer + +from bika.lims.interfaces import IContact + + +@indexer(IContact) +def sortable_title(instance): + return instance.getFullname().lower() diff --git a/bika/lims/catalog/indexers/organisation.py b/bika/lims/catalog/indexers/organisation.py new file mode 100644 index 0000000000..c1f1249741 --- /dev/null +++ b/bika/lims/catalog/indexers/organisation.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.CORE is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2018-2019 by it's authors. +# Some rights reserved, see README and LICENSE. + +from plone.indexer import indexer + +from bika.lims.interfaces import IOrganisation + + +@indexer(IOrganisation) +def title(instance): + """Organisation objects does not use the built-in title, rather it uses + Name schema field. We need this type-specific index to simulate the default + behavior for index `title` + """ + name = getattr(instance, "Name", None) + return name or "" diff --git a/bika/lims/content/abstractanalysis.py b/bika/lims/content/abstractanalysis.py index 6edd740e1d..279c9f0452 100644 --- a/bika/lims/content/abstractanalysis.py +++ b/bika/lims/content/abstractanalysis.py @@ -1128,14 +1128,6 @@ def getAttachmentUIDs(self): uids = [att.UID() for att in attachments] return uids - @security.public - def getCalculationTitle(self): - """Used to populate catalog values - """ - calculation = self.getCalculation() - if calculation: - return calculation.Title() - @security.public def getCalculationUID(self): """Used to populate catalog values diff --git a/bika/lims/content/abstractbaseanalysis.py b/bika/lims/content/abstractbaseanalysis.py index 76df20613f..f0bdc2aac6 100644 --- a/bika/lims/content/abstractbaseanalysis.py +++ b/bika/lims/content/abstractbaseanalysis.py @@ -19,21 +19,7 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo -from bika.lims import api -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields import DurationField -from bika.lims.browser.fields import UIDReferenceField -from bika.lims.browser.widgets.durationwidget import DurationWidget -from bika.lims.browser.widgets.recordswidget import RecordsWidget -from bika.lims.browser.widgets.referencewidget import ReferenceWidget -from bika.lims.config import ATTACHMENT_OPTIONS -from bika.lims.config import SERVICE_POINT_OF_CAPTURE -from bika.lims.content.bikaschema import BikaSchema -from bika.lims.interfaces import IBaseAnalysis, ISubmitted -from bika.lims.permissions import FieldEditAnalysisHidden -from bika.lims.permissions import FieldEditAnalysisRemarks -from bika.lims.permissions import FieldEditAnalysisResult -from bika.lims.utils import to_utf8 as _c +from Products.ATExtensions.ateapi import RecordsField from Products.Archetypes.BaseContent import BaseContent from Products.Archetypes.Field import BooleanField from Products.Archetypes.Field import FixedPointField @@ -42,18 +28,37 @@ from Products.Archetypes.Field import StringField from Products.Archetypes.Field import TextField from Products.Archetypes.Schema import Schema -from Products.Archetypes.utils import DisplayList -from Products.Archetypes.utils import IntDisplayList from Products.Archetypes.Widget import BooleanWidget from Products.Archetypes.Widget import DecimalWidget from Products.Archetypes.Widget import IntegerWidget from Products.Archetypes.Widget import SelectionWidget from Products.Archetypes.Widget import StringWidget -from Products.ATExtensions.ateapi import RecordsField +from Products.Archetypes.utils import DisplayList +from Products.Archetypes.utils import IntDisplayList from Products.CMFCore.permissions import View from Products.CMFCore.utils import getToolByName from zope.interface import implements +from bika.lims import api +from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.fields import DurationField +from bika.lims.browser.fields import UIDReferenceField +from bika.lims.browser.widgets.durationwidget import DurationWidget +from bika.lims.browser.widgets.recordswidget import RecordsWidget +from bika.lims.browser.widgets.referencewidget import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG +from bika.lims.config import ATTACHMENT_OPTIONS +from bika.lims.config import SERVICE_POINT_OF_CAPTURE +from bika.lims.content.bikaschema import BikaSchema +from bika.lims.interfaces import IBaseAnalysis +from bika.lims.interfaces import IHaveAnalysisCategory +from bika.lims.interfaces import IHaveDepartment +from bika.lims.interfaces import IHaveInstrument +from bika.lims.permissions import FieldEditAnalysisHidden +from bika.lims.permissions import FieldEditAnalysisRemarks +from bika.lims.permissions import FieldEditAnalysisResult +from bika.lims.utils import to_utf8 as _c + # Anywhere that there just isn't space for unpredictably long names, # this value will be used instead. It's set on the AnalysisService, # but accessed on all analysis objects. @@ -477,13 +482,16 @@ schemata="Description", required=0, allowed_types=('Department',), - vocabulary='getDepartments', widget=ReferenceWidget( - checkbox_bound=0, label=_("Department"), description=_("The laboratory department"), - catalog_name='bika_setup_catalog', - base_query={'is_active': True}, + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending", + ), ) ) @@ -744,7 +752,7 @@ class AbstractBaseAnalysis(BaseContent): # TODO BaseContent? is really needed? - implements(IBaseAnalysis) + implements(IBaseAnalysis, IHaveAnalysisCategory, IHaveDepartment, IHaveInstrument) security = ClassSecurityInfo() schema = schema displayContentsTab = False @@ -848,20 +856,6 @@ def getAnalysisCategories(self): items.sort(lambda x, y: cmp(x[1], y[1])) return DisplayList(list(items)) - @security.public - def getDepartments(self): - """A vocabulary listing available (and activated) departments. - """ - bsc = getToolByName(self, 'bika_setup_catalog') - items = [('', '')] + [(o.UID, o.Title) for o in - bsc(portal_type='Department', - is_active=True)] - o = self.getDepartment() - if o and o.UID() not in [i[0] for i in items]: - items.append((o.UID(), o.Title())) - items.sort(lambda x, y: cmp(x[1], y[1])) - return DisplayList(list(items)) - @security.public def getLowerDetectionLimit(self): """Returns the Lower Detection Limit for this service as a floatable @@ -967,14 +961,6 @@ def getMethodURL(self): if method: return method.absolute_url_path() - @security.public - def getInstrumentTitle(self): - """Used to populate catalog values - """ - instrument = self.getInstrument() - if instrument: - return instrument.Title() - @security.public def getInstrument(self): """Returns the assigned instrument @@ -1022,14 +1008,6 @@ def getCategoryUID(self): if category: return category.UID() - @security.public - def getDepartmentTitle(self): - """Used to populate catalog values - """ - department = self.getDepartment() - if department: - return department.Title() - @security.public def getMaxTimeAllowed(self): """Returns the maximum turnaround time for this analysis. If no TAT is diff --git a/bika/lims/content/analysiscategory.py b/bika/lims/content/analysiscategory.py index 89bc4d5be9..b22b6e2a5f 100644 --- a/bika/lims/content/analysiscategory.py +++ b/bika/lims/content/analysiscategory.py @@ -18,62 +18,83 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -"""Analysis Category - the category of the analysis service -""" - import transaction from AccessControl import ClassSecurityInfo -from Products.Archetypes.public import * +from Products.Archetypes.Field import FloatField +from Products.Archetypes.Field import ReferenceField +from Products.Archetypes.Field import TextField +from Products.Archetypes.Schema import Schema +from Products.Archetypes.Widget import DecimalWidget +from Products.Archetypes.Widget import TextAreaWidget +from Products.Archetypes.public import BaseContent +from Products.Archetypes.public import registerType from Products.Archetypes.references import HoldingReference from Products.CMFCore.WorkflowCore import WorkflowException -from Products.CMFCore.utils import getToolByName +from zope.interface import implements + +from bika.lims import api from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.widgets import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema -from bika.lims.interfaces import IAnalysisCategory, IDeactivable -from zope.interface import implements +from bika.lims.interfaces import IAnalysisCategory +from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import IHaveDepartment -schema = BikaSchema.copy() + Schema(( - TextField( - 'Comments', - default_output_type='text/plain', - allowable_content_types=('text/plain',), - widget=TextAreaWidget( - description=_( - "To be displayed below each Analysis Category section on " - "results reports."), - label=_("Comments")), - ), - ReferenceField( - 'Department', - required=1, - vocabulary='getDepartments', - allowed_types=('Department',), - relationship='AnalysisCategoryDepartment', - referenceClass=HoldingReference, - widget=ReferenceWidget( - checkbox_bound=0, - label=_("Department"), - description=_("The laboratory department"), - ), - ), - FloatField( - 'SortKey', - validators=('SortKeyValidator',), - widget=DecimalWidget( - label=_("Sort Key"), - description=_( - "Float value from 0.0 - 1000.0 indicating the sort " - "order. Duplicate values are ordered alphabetically."), - ), +Comments = TextField( + "Comments", + default_output_type="text/plain", + allowable_content_types=("text/plain",), + widget=TextAreaWidget( + description=_( + "To be displayed below each Analysis Category section on results " + "reports."), + label=_("Comments")), +) + +Department = ReferenceField( + "Department", + required=1, + allowed_types=("Department",), + relationship="AnalysisCategoryDepartment", + referenceClass=HoldingReference, + widget=ReferenceWidget( + label=_("Department"), + description=_("The laboratory department"), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query={ + "is_active": True, + "sort_on": "sortable_title", + "sort_order": "ascending" + }, + ) +) + +SortKey = FloatField( + "SortKey", + validators=("SortKeyValidator",), + widget=DecimalWidget( + label=_("Sort Key"), + description=_( + "Float value from 0.0 - 1000.0 indicating the sort order. " + "Duplicate values are ordered alphabetically."), ), +) + +schema = BikaSchema.copy() + Schema(( + Comments, + Department, + SortKey, )) + schema['description'].widget.visible = True schema['description'].schemata = 'default' class AnalysisCategory(BaseContent): - implements(IAnalysisCategory, IDeactivable) + implements(IAnalysisCategory, IHaveDepartment, IDeactivable) security = ClassSecurityInfo() displayContentsTab = False schema = schema @@ -84,25 +105,12 @@ def _renameAfterCreation(self, check_auto_id=False): from bika.lims.idserver import renameAfterCreation renameAfterCreation(self) - def getDepartments(self): - bsc = getToolByName(self, 'bika_setup_catalog') - deps = [] - for d in bsc(portal_type='Department', - is_active=True): - deps.append((d.UID, d.Title)) - return DisplayList(deps) - - def getDepartmentTitle(self): - field = self.Schema().getField('Department') - dept = field.get(self) - return dept.Title() if dept else '' - def workflow_script_deactivate(self): # A instance cannot be deactivated if it contains services - pu = getToolByName(self, 'plone_utils') - bsc = getToolByName(self, 'bika_setup_catalog') - ars = bsc(portal_type='AnalysisService', getCategoryUID=self.UID()) - if ars: + query = dict(portal_type="AnalysisService", category_uid=self.UID()) + brains = api.search(query, SETUP_CATALOG) + if brains: + pu = api.get_tool("plone_utils") message = _("Category cannot be deactivated because it contains " "Analysis Services") pu.addPortalMessage(message, 'error') diff --git a/bika/lims/content/analysisservice.py b/bika/lims/content/analysisservice.py index 7d90271db9..711f958918 100644 --- a/bika/lims/content/analysisservice.py +++ b/bika/lims/content/analysisservice.py @@ -429,14 +429,6 @@ def _renameAfterCreation(self, check_auto_id=False): return renameAfterCreation(self) - @security.public - def getCalculationTitle(self): - """Used to populate catalog values - """ - calculation = self.getCalculation() - if calculation: - return calculation.Title() - @security.public def getCalculation(self): """Returns the assigned calculation @@ -488,18 +480,15 @@ def getAvailableMethods(self): is unset, only the methods assigned manually to that service are returned. """ - methods = self.getMethods() - muids = [m.UID() for m in methods] - if self.getInstrumentEntryOfResults(): - # Add the methods from the instruments capable to perform - # this analysis service - for ins in self.getInstruments(): - for method in ins.getMethods(): - if method and method.UID() not in muids: - methods.append(method) - muids.append(method.UID()) + if not self.getInstrumentEntryOfResults(): + # No need to go further, just return the manually assigned methods + return self.getMethods() - return methods + # Return the manually assigned methods plus those from instruments + method_uids = self.getAvailableMethodUIDs() + query = dict(portal_type="Method", UID=method_uids) + brains = api.search(query, "bika_setup_catalog") + return map(api.get_object_by_uid, brains) @security.public def getAvailableMethodUIDs(self): @@ -507,7 +496,13 @@ def getAvailableMethodUIDs(self): Returns the UIDs of the available methods. it is used as a vocabulary to fill the selection list of 'Methods' field. """ - return [m.UID() for m in self.getAvailableMethods()] + method_uids = self.getRawMethods() + if self.getInstrumentEntryOfResults(): + for instrument in self.getInstruments(): + method_uids.extend(instrument.getRawMethods()) + method_uids = filter(None, method_uids) + method_uids = list(set(method_uids)) + return method_uids @security.public def getMethods(self): @@ -530,7 +525,7 @@ def getMethodUIDs(self): :returns: List of method UIDs """ - return map(api.get_uid, self.getMethods()) + return self.getRawMethods() @security.public def getInstruments(self): @@ -550,7 +545,7 @@ def getInstrumentUIDs(self): :returns: List of instrument UIDs """ - return map(api.get_uid, self.getInstruments()) + return self.getRawInstruments() @security.public def getAvailableInstruments(self): diff --git a/bika/lims/content/analysisspec.py b/bika/lims/content/analysisspec.py index 4a3fccc4f1..e7c2894e0b 100644 --- a/bika/lims/content/analysisspec.py +++ b/bika/lims/content/analysisspec.py @@ -23,17 +23,16 @@ from Products.ATExtensions.field.records import RecordsField from Products.Archetypes import atapi from Products.Archetypes.public import BaseFolder -from Products.Archetypes.public import ReferenceWidget from Products.Archetypes.public import Schema -from Products.Archetypes.utils import DisplayList from Products.CMFPlone.utils import safe_unicode from zope.i18n import translate from zope.interface import implements -from bika.lims import api from bika.lims import bikaMessageFactory as _ from bika.lims.browser.fields import UIDReferenceField from bika.lims.browser.widgets import AnalysisSpecificationWidget +from bika.lims.browser.widgets import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema from bika.lims.content.clientawaremixin import ClientAwareMixin @@ -45,11 +44,17 @@ UIDReferenceField( 'SampleType', - vocabulary="getSampleTypes", allowed_types=('SampleType',), + required=1, widget=ReferenceWidget( - checkbox_bound=0, label=_("Sample Type"), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending" + ), ), ), @@ -127,7 +132,6 @@ def Title(self): """ Return the title if possible, else return the Sample type. Fall back on the instance's ID if there's no sample type or title. """ - title = '' if self.title: title = self.title else: @@ -141,8 +145,7 @@ def contextual_title(self): else: return self.title + " (" + translate(_("Client")) + ")" - security.declarePublic('getResultsRangeDict') - + @security.public def getResultsRangeDict(self): """Return a dictionary with the specification fields for each service. The keys of the dictionary are the keywords of each @@ -163,27 +166,6 @@ def getResultsRangeDict(self): specs[keyword][key] = spec.get(key, '') return specs - security.declarePublic('getRemainingSampleTypes') - - def getSampleTypes(self, active_only=True): - """Return all sampletypes - """ - catalog = api.get_tool("bika_setup_catalog") - query = { - "portal_type": "SampleType", - # N.B. The `sortable_title` index sorts case sensitive. Since there - # is no sort key for sample types, it makes more sense to sort - # them alphabetically in the selection - "sort_on": "title", - "sort_order": "ascending" - } - results = catalog(query) - if active_only: - results = filter(api.is_active, results) - sampletypes = map( - lambda brain: (brain.UID, brain.Title), results) - return DisplayList(sampletypes) - atapi.registerType(AnalysisSpec, PROJECTNAME) diff --git a/bika/lims/content/client.py b/bika/lims/content/client.py index 31a1b6ec54..79f9c650d5 100644 --- a/bika/lims/content/client.py +++ b/bika/lims/content/client.py @@ -18,33 +18,33 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -import sys - from AccessControl import ClassSecurityInfo from AccessControl import Unauthorized from Products.ATContentTypes.content import schemata from Products.Archetypes.public import BooleanField from Products.Archetypes.public import BooleanWidget from Products.Archetypes.public import ReferenceField -from Products.Archetypes.public import ReferenceWidget from Products.Archetypes.public import Schema from Products.Archetypes.public import SelectionWidget from Products.Archetypes.public import StringField from Products.Archetypes.public import StringWidget from Products.Archetypes.public import registerType -from Products.Archetypes.utils import DisplayList from Products.CMFCore import permissions from Products.CMFCore.PortalFolder import PortalFolderBase as PortalFolder from Products.CMFCore.utils import _checkPermission +from zope.interface import implements + from bika.lims import _ from bika.lims import api +from bika.lims.browser.widgets import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG from bika.lims.config import ARIMPORT_OPTIONS from bika.lims.config import DECIMAL_MARKS from bika.lims.config import PROJECTNAME from bika.lims.content.attachment import Attachment from bika.lims.content.organisation import Organisation -from bika.lims.interfaces import IClient, IDeactivable -from zope.interface import implements +from bika.lims.interfaces import IClient +from bika.lims.interfaces import IDeactivable schema = Organisation.schema.copy() + Schema(( StringField( @@ -93,15 +93,19 @@ schemata="Preferences", required=0, multiValued=1, - vocabulary="getAnalysisCategories", - vocabulary_display_path_bound=sys.maxint, allowed_types=("AnalysisCategory",), relationship="ClientDefaultCategories", widget=ReferenceWidget( - checkbox_bound=0, label=_("Default categories"), description=_( "Always expand the selected categories in client views"), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending", + ), ), ), @@ -110,15 +114,19 @@ schemata="Preferences", required=0, multiValued=1, - vocabulary="getAnalysisCategories", validators=("restrictedcategoriesvalidator",), - vocabulary_display_path_bound=sys.maxint, allowed_types=("AnalysisCategory",), relationship="ClientRestrictedCategories", widget=ReferenceWidget( - checkbox_bound=0, label=_("Restrict categories"), description=_("Show only selected categories in client views"), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending", + ), ), ), @@ -166,11 +174,6 @@ def _renameAfterCreation(self, check_auto_id=False): from bika.lims.idserver import renameAfterCreation renameAfterCreation(self) - def Title(self): - """Return the Organisation's Name as its title - """ - return self.getName() - security.declarePublic("getContactFromUsername") def getContactFromUsername(self, username): @@ -198,19 +201,6 @@ def getContactUIDForUser(self): def getARImportOptions(self): return ARIMPORT_OPTIONS - security.declarePublic("getAnalysisCategories") - - def getAnalysisCategories(self): - """Return all available analysis categories - """ - bsc = api.get_tool("bika_setup_catalog") - cats = [] - for st in bsc(portal_type="AnalysisCategory", - is_active=True, - sort_on="sortable_title"): - cats.append((st.UID, st.Title)) - return DisplayList(cats) - def getContacts(self, only_active=True): """Return an array containing the contacts from this Client """ diff --git a/bika/lims/content/department.py b/bika/lims/content/department.py index 39a42ef0f5..9bfd6e6d61 100644 --- a/bika/lims/content/department.py +++ b/bika/lims/content/department.py @@ -18,58 +18,53 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -"""Department - the department in the laboratory. -""" -from Products.Archetypes.public import * +from AccessControl import ClassSecurityInfo +from Products.Archetypes.public import BaseContent +from Products.Archetypes.public import ReferenceField +from Products.Archetypes.public import Schema +from Products.Archetypes.public import registerType from Products.Archetypes.references import HoldingReference -from Products.CMFCore.utils import getToolByName +from zope.interface import implements + +from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.widgets import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema -from AccessControl import ClassSecurityInfo -import sys -from bika.lims import bikaMessageFactory as _ -from bika.lims.utils import t -from zope.interface import implements -from bika.lims.interfaces import IDepartment, IDeactivable +from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import IDepartment -schema = BikaSchema.copy() + Schema(( - ReferenceField('Manager', - vocabulary = 'getContacts', - vocabulary_display_path_bound = sys.maxint, - allowed_types = ('LabContact',), - referenceClass = HoldingReference, - relationship = 'DepartmentLabContact', - widget = ReferenceWidget( - checkbox_bound = 0, - label=_("Manager"), - description = _( - "Select a manager from the available personnel configured under the " - "'lab contacts' setup item. Departmental managers are referenced on " - "analysis results reports containing analyses by their department."), - ), - ), - ComputedField('ManagerName', - expression = "context.getManager() and context.getManager().getFullname() or ''", - widget = ComputedWidget( - visible = False, - ), - ), - ComputedField('ManagerPhone', - expression = "context.getManager() and context.getManager().getBusinessPhone() or ''", - widget = ComputedWidget( - visible = False, - ), - ), - ComputedField('ManagerEmail', - expression = "context.getManager() and context.getManager().getEmailAddress() or ''", - widget = ComputedWidget( - visible = False, +Manager = ReferenceField( + 'Manager', + required=1, + allowed_types=('LabContact',), + referenceClass=HoldingReference, + relationship="DepartmentLabContact", + widget=ReferenceWidget( + label=_("Manager"), + description=_( + "Select a manager from the available personnel configured under " + "the 'lab contacts' setup item. Departmental managers are " + "referenced on analysis results reports containing analyses by " + "their department."), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending", ), ), +) + +schema = BikaSchema.copy() + Schema(( + Manager, )) + schema['description'].widget.visible = True schema['description'].schemata = 'default' + class Department(BaseContent): implements(IDepartment, IDeactivable) security = ClassSecurityInfo() @@ -77,8 +72,10 @@ class Department(BaseContent): schema = schema _at_rename_after_creation = True + def _renameAfterCreation(self, check_auto_id=False): from bika.lims.idserver import renameAfterCreation renameAfterCreation(self) + registerType(Department, PROJECTNAME) diff --git a/bika/lims/content/instrument.py b/bika/lims/content/instrument.py index f4dd568d4d..4bbe16be2a 100644 --- a/bika/lims/content/instrument.py +++ b/bika/lims/content/instrument.py @@ -28,7 +28,6 @@ from Products.Archetypes.atapi import registerType from bika.lims.api.analysis import is_out_of_range from bika.lims.catalog.analysis_catalog import CATALOG_ANALYSIS_LISTING -from zope.component._api import getAdapters from zope.interface import implements from plone.app.folder.folder import ATFolder @@ -55,10 +54,10 @@ from Products.Archetypes.atapi import ImageWidget from Products.Archetypes.atapi import BooleanWidget from Products.Archetypes.atapi import SelectionWidget -from Products.Archetypes.atapi import ReferenceWidget from Products.Archetypes.atapi import MultiSelectionWidget from bika.lims.browser.widgets import DateTimeWidget from bika.lims.browser.widgets import RecordsWidget +from bika.lims.browser.widgets import ReferenceWidget # bika.lims imports from bika.lims import api @@ -68,7 +67,6 @@ from bika.lims.config import PROJECTNAME from bika.lims.exportimport import instruments from bika.lims.interfaces import IInstrument, IDeactivable -from bika.lims.config import QCANALYSIS_TYPES from bika.lims.content.bikaschema import BikaSchema from bika.lims.content.bikaschema import BikaFolderSchema from bika.lims import bikaMessageFactory as _ @@ -81,36 +79,49 @@ allowed_types=('InstrumentType',), relationship='InstrumentInstrumentType', required=1, - widget=SelectionWidget( - format='select', + widget=ReferenceWidget( label=_("Instrument type"), - visible={'view': 'invisible', 'edit': 'visible'} + showOn=True, + catalog_name='bika_setup_catalog', + base_query={ + "is_active": True, + "sort_on": "sortable_title", + "sort_order": "ascending", + }, ), ), ReferenceField( 'Manufacturer', - vocabulary='getManufacturers', allowed_types=('Manufacturer',), relationship='InstrumentManufacturer', required=1, - widget=SelectionWidget( - format='select', + widget=ReferenceWidget( label=_("Manufacturer"), - visible={'view': 'invisible', 'edit': 'visible'} + showOn=True, + catalog_name='bika_setup_catalog', + base_query={ + "is_active": True, + "sort_on": "sortable_title", + "sort_order": "ascending", + }, ), ), ReferenceField( 'Supplier', - vocabulary='getSuppliers', allowed_types=('Supplier',), relationship='InstrumentSupplier', required=1, - widget=SelectionWidget( - format='select', + widget=ReferenceWidget( label=_("Supplier"), - visible={'view': 'invisible', 'edit': 'visible'} + showOn=True, + catalog_name='bika_setup_catalog', + base_query={ + "is_active": True, + "sort_on": "sortable_title", + "sort_order": "ascending", + }, ), ), @@ -263,46 +274,6 @@ ), ), - # Needed since InstrumentType is sorted by its own object, not by its name. - ComputedField( - 'InstrumentTypeName', - expression='here.getInstrumentType().Title() if here.getInstrumentType() else ""', - widget=ComputedWidget( - label=_('Instrument Type'), - visible=True, - ), - ), - - ComputedField( - 'InstrumentLocationName', - expression='here.getInstrumentLocation().Title() if here.getInstrumentLocation() else ""', - widget=ComputedWidget( - label=_("Instrument Location"), - label_msgid="instrument_location", - description=_("The room and location where the instrument is installed"), - description_msgid="help_instrument_location", - visible=True, - ), - ), - - ComputedField( - 'ManufacturerName', - expression='here.getManufacturer().Title() if here.getManufacturer() else ""', - widget=ComputedWidget( - label=_('Manufacturer'), - visible=True, - ), - ), - - ComputedField( - 'SupplierName', - expression='here.getSupplier().Title() if here.getSupplier() else ""', - widget=ComputedWidget( - label=_('Supplier'), - visible=True, - ), - ), - StringField( 'AssetNumber', widget=StringWidget( @@ -314,17 +285,19 @@ ReferenceField( 'InstrumentLocation', schemata='Additional info.', - vocabulary='getInstrumentLocations', allowed_types=('InstrumentLocation', ), relationship='InstrumentInstrumentLocation', required=0, - widget=SelectionWidget( - format='select', + widget=ReferenceWidget( label=_("Instrument Location"), - label_msgid="instrument_location", description=_("The room and location where the instrument is installed"), - description_msgid="help_instrument_location", - visible={'view': 'invisible', 'edit': 'visible'} + showOn=True, + catalog_name='bika_setup_catalog', + base_query={ + "is_active": True, + "sort_on": "sortable_title", + "sort_order": "ascending", + }, ) ), @@ -358,9 +331,6 @@ )) schema.moveField('AssetNumber', before='description') -schema.moveField('SupplierName', before='Model') -schema.moveField('ManufacturerName', before='SupplierName') -schema.moveField('InstrumentTypeName', before='ManufacturerName') schema['description'].widget.visible = True schema['description'].schemata = 'default' @@ -421,28 +391,12 @@ def getMaintenanceTypesList(self): def getCalibrationAgentsList(self): return getCalibrationAgents(self) - def getManufacturers(self): - bsc = getToolByName(self, 'bika_setup_catalog') - items = [(c.UID, c.Title) - for c in bsc(portal_type='Manufacturer', - is_active=True)] - items.sort(lambda x, y: cmp(x[1], y[1])) - return DisplayList(items) - def getMethodUIDs(self): uids = [] if self.getMethods(): uids = [m.UID() for m in self.getMethods()] return uids - def getSuppliers(self): - bsc = getToolByName(self, 'bika_setup_catalog') - items = [(c.UID, c.getName) - for c in bsc(portal_type='Supplier', - is_active=True)] - items.sort(lambda x, y: cmp(x[1], y[1])) - return DisplayList(items) - def _getAvailableMethods(self): """ Returns the available (active) methods. One method can be done by multiple instruments, but one @@ -456,23 +410,6 @@ def _getAvailableMethods(self): items.insert(0, ('', t(_('None')))) return DisplayList(items) - def getInstrumentTypes(self): - bsc = getToolByName(self, 'bika_setup_catalog') - items = [(c.UID, c.Title) - for c in bsc(portal_type='InstrumentType', - is_active=True)] - items.sort(lambda x, y: cmp(x[1], y[1])) - return DisplayList(items) - - def getInstrumentLocations(self): - bsc = getToolByName(self, 'bika_setup_catalog') - items = [(c.UID, c.Title) - for c in bsc(portal_type='InstrumentLocation', - is_active=True)] - items.sort(lambda x, y: cmp(x[1], y[1])) - items.insert(0, ('', t(_('None')))) - return DisplayList(items) - def getMaintenanceTasks(self): return self.objectValues('InstrumentMaintenanceTask') diff --git a/bika/lims/content/labcontact.py b/bika/lims/content/labcontact.py index 04d7265c76..034c6e6443 100644 --- a/bika/lims/content/labcontact.py +++ b/bika/lims/content/labcontact.py @@ -28,6 +28,7 @@ from bika.lims.content.contact import Contact from bika.lims.content.person import Person from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import IHaveDepartment from bika.lims.interfaces import ILabContact from Products.Archetypes import atapi from Products.Archetypes.Field import ImageField @@ -96,7 +97,7 @@ class LabContact(Contact): """A Lab Contact, which can be linked to a System User """ - implements(ILabContact, IDeactivable) + implements(ILabContact, IHaveDepartment, IDeactivable) schema = schema displayContentsTab = False @@ -112,6 +113,14 @@ def Title(self): """ return safe_unicode(self.getFullname()).encode("utf-8") + @security.public + def getDepartment(self): + """Required by IHaveDepartment. Returns the list of departments this + laboratory contact is assigned to plus the default department + """ + departments = self.getDepartments() + [self.getDefaultDepartment()] + return filter(None, list(set(departments))) + @security.public def getDefaultDepartment(self): """Returns the assigned default department diff --git a/bika/lims/content/laboratory.py b/bika/lims/content/laboratory.py index b93ac0bfa8..91cefa6cd5 100644 --- a/bika/lims/content/laboratory.py +++ b/bika/lims/content/laboratory.py @@ -19,13 +19,6 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo -from bika.lims import api -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields import UIDReferenceField -from bika.lims.config import PROJECTNAME -from bika.lims.config import ManageBika -from bika.lims.content.organisation import Organisation -from bika.lims.interfaces import ILaboratory from Products.Archetypes.public import BooleanField from Products.Archetypes.public import BooleanWidget from Products.Archetypes.public import ImageField @@ -33,17 +26,24 @@ from Products.Archetypes.public import IntegerField from Products.Archetypes.public import IntegerWidget from Products.Archetypes.public import Schema -from Products.Archetypes.public import SelectionWidget from Products.Archetypes.public import StringField from Products.Archetypes.public import StringWidget from Products.Archetypes.public import TextAreaWidget from Products.Archetypes.public import TextField from Products.Archetypes.public import registerType -from Products.Archetypes.utils import DisplayList from Products.CMFCore.utils import UniqueObject from Products.CMFPlone.utils import safe_unicode from zope.interface import implements +from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.fields import UIDReferenceField +from bika.lims.browser.widgets import ReferenceWidget +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG +from bika.lims.config import ManageBika +from bika.lims.config import PROJECTNAME +from bika.lims.content.organisation import Organisation +from bika.lims.interfaces import ILaboratory + DEFAULT_ACCREDITATION_PAGE_HEADER = """${lab_name} has been accredited as ${accreditation_standard} conformant by ${accreditation_body_abbr}, ${accreditation_body_name}

${accreditation_body_abbr} is the single @@ -70,13 +70,17 @@ "Supervisor", required=0, allowed_types=("LabContact",), - vocabulary="_getLabContacts", write_permission=ManageBika, - accessor="getSupervisorUID", - widget=SelectionWidget( - format="select", + widget=ReferenceWidget( label=_("Supervisor"), - description=_("Supervisor of the Lab") + description=_("Supervisor of the Lab"), + showOn=True, + catalog_name=SETUP_CATALOG, + base_query=dict( + is_active=True, + sort_on="sortable_title", + sort_order="ascending", + ), ) ), @@ -194,38 +198,5 @@ def Title(self): title = self.getName() and self.getName() or _("Laboratory") return safe_unicode(title).encode("utf-8") - def _getLabContacts(self): - bsc = api.get_tool("bika_setup_catalog") - # fallback - all Lab Contacts - pairs = [["", ""]] - for contact in bsc(portal_type="LabContact", - is_active=True, - sort_on="sortable_title"): - pairs.append((contact.UID, contact.Title)) - return DisplayList(pairs) - - @security.public - def getSupervisor(self): - """Returns the assigned supervisor - - :returns: Supervisor object - """ - return self.getField("Supervisor").get(self) - - @security.public - def getSupervisorUID(self): - """Returns the UID of the assigned Supervisor - - NOTE: This is the default accessor of the `Supervisor` schema field - and needed for the selection widget to render the selected value - properly in _view_ mode. - - :returns: Supervisor UID - """ - supervisor = self.getSupervisor() - if not supervisor: - return None - return api.get_uid(supervisor) - registerType(Laboratory, PROJECTNAME) diff --git a/bika/lims/content/labproduct.py b/bika/lims/content/labproduct.py index 499aa4b650..e1b04077cc 100644 --- a/bika/lims/content/labproduct.py +++ b/bika/lims/content/labproduct.py @@ -18,16 +18,17 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. +from decimal import Decimal + from AccessControl import ClassSecurityInfo from Products.Archetypes.public import * -from Products.CMFCore.permissions import View, ModifyPortalContent +from zope.interface import implements + +from bika.lims import bikaMessageFactory as _ from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema -from decimal import Decimal -from bika.lims import bikaMessageFactory as _ from bika.lims.interfaces import IDeactivable -from bika.lims.utils import t -from zope.interface import implements +from bika.lims.interfaces import IHavePrice schema = BikaSchema.copy() + Schema(( StringField('Volume', @@ -72,8 +73,9 @@ schema['description'].schemata = 'default' schema['description'].widget.visible = True + class LabProduct(BaseContent): - implements(IDeactivable) + implements(IDeactivable, IHavePrice) security = ClassSecurityInfo() displayContentsTab = False schema = schema @@ -110,4 +112,5 @@ def getVATAmount(self): vatamount = Decimal('0.00') return vatamount.quantize(Decimal('0.00')) + registerType(LabProduct, PROJECTNAME) diff --git a/bika/lims/content/method.py b/bika/lims/content/method.py index ebc2d794af..66111ab00d 100644 --- a/bika/lims/content/method.py +++ b/bika/lims/content/method.py @@ -25,6 +25,7 @@ from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import IHaveInstrument from bika.lims.interfaces import IMethod from bika.lims.utils import t from plone.app.blob.field import FileField as BlobFileField @@ -176,7 +177,7 @@ class Method(BaseFolder): """Method content """ - implements(IMethod, IDeactivable) + implements(IMethod, IDeactivable, IHaveInstrument) security = ClassSecurityInfo() displayContentsTab = False @@ -231,6 +232,12 @@ def _getCalculations(self): items.insert(0, ("", t(_("None")))) return DisplayList(items) + def getInstrument(self): + """Instruments capable to perform this method. + Required by IHaveInstrument + """ + return self.getInstruments() + def getInstruments(self): """Instruments capable to perform this method """ diff --git a/bika/lims/content/organisation.py b/bika/lims/content/organisation.py index 5d61b33443..7214da55bc 100644 --- a/bika/lims/content/organisation.py +++ b/bika/lims/content/organisation.py @@ -19,19 +19,21 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields import AddressField -from bika.lims.browser.widgets import AddressWidget -from bika.lims.config import PROJECTNAME -from bika.lims.content.bikaschema import BikaFolderSchema -from bika.lims.content.bikaschema import BikaSchema -from plone.app.folder.folder import ATFolder from Products.Archetypes.public import ManagedSchema from Products.Archetypes.public import StringField from Products.Archetypes.public import StringWidget from Products.Archetypes.public import registerType from Products.CMFPlone.utils import safe_unicode +from plone.app.folder.folder import ATFolder +from zope.interface import implements +from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.fields import AddressField +from bika.lims.browser.widgets import AddressWidget +from bika.lims.config import PROJECTNAME +from bika.lims.content.bikaschema import BikaFolderSchema +from bika.lims.content.bikaschema import BikaSchema +from bika.lims.interfaces import IOrganisation schema = BikaFolderSchema.copy() + BikaSchema.copy() + ManagedSchema(( @@ -166,6 +168,7 @@ class Organisation(ATFolder): """Base class for Clients, Suppliers and for the Laboratory """ + implements(IOrganisation) security = ClassSecurityInfo() displayContentsTab = False diff --git a/bika/lims/content/person.py b/bika/lims/content/person.py index d03b6f37cf..a23816b6d3 100644 --- a/bika/lims/content/person.py +++ b/bika/lims/content/person.py @@ -253,42 +253,6 @@ def getFullname(self): fullname = '%s %s' % (self.getFirstname(), self.getSurname()) return fullname.strip() - def getListingname(self): - """Person's Fullname as Surname, Firstname - """ - fn = self.getFirstname() - mi = self.getMiddleinitial() - md = self.getMiddlename() - sn = self.getSurname() - fullname = "" - if fn and sn: - fullname = "%s, %s" % ( - self.getSurname(), - self.getFirstname()) - elif fn or sn: - fullname = "%s %s" % ( - self.getSurname(), - self.getFirstname()) - else: - fullname = "" - - if fullname != "": - if mi and md: - fullname = "%s %s %s" % ( - fullname, - self.getMiddleinitial(), - self.getMiddlename()) - elif mi: - fullname = "%s %s" % ( - fullname, - self.getMiddleinitial()) - elif md: - fullname = "%s %s" % ( - fullname, - self.getMiddlename()) - - return fullname.strip() - Title = getFullname @security.protected(CMFCorePermissions.ManagePortal) diff --git a/bika/lims/content/worksheet.py b/bika/lims/content/worksheet.py index 2e2da5b9e0..b606cbb83e 100644 --- a/bika/lims/content/worksheet.py +++ b/bika/lims/content/worksheet.py @@ -1116,17 +1116,6 @@ def applyWorksheetTemplate(self, wst): if method: self.setMethod(method, True) - def getInstrumentTitle(self): - """ - Returns the instrument title - :returns: instrument's title - :rtype: string - """ - instrument = self.getInstrument() - if instrument: - return instrument.Title() - return '' - def getWorksheetTemplateUID(self): """ Returns the template's UID assigned to this worksheet diff --git a/bika/lims/content/worksheettemplate.py b/bika/lims/content/worksheettemplate.py index ff1223155a..cd166fe6fc 100644 --- a/bika/lims/content/worksheettemplate.py +++ b/bika/lims/content/worksheettemplate.py @@ -44,6 +44,9 @@ from bika.lims.interfaces import IDeactivable from zope.interface import implements +from bika.lims.interfaces import IHaveInstrument +from bika.lims.interfaces import IWorksheetTemplate + schema = BikaSchema.copy() + Schema(( RecordsField( "Layout", @@ -159,7 +162,7 @@ class WorksheetTemplate(BaseContent): """Worksheet Templates """ - implements(IDeactivable) + implements(IWorksheetTemplate, IHaveInstrument, IDeactivable) security = ClassSecurityInfo() displayContentsTab = False schema = schema @@ -169,15 +172,6 @@ def _renameAfterCreation(self, check_auto_id=False): from bika.lims.idserver import renameAfterCreation renameAfterCreation(self) - @security.public - def getInstrumentTitle(self): - """Return the instrument title - """ - instrument = self.getInstrument() - if not instrument: - return "" - return api.get_title(instrument) - @security.public def getAnalysisTypes(self): """Return Analysis type displaylist diff --git a/bika/lims/controlpanel/bika_analysiscategories.py b/bika/lims/controlpanel/bika_analysiscategories.py index ef64769555..14d8237e2d 100644 --- a/bika/lims/controlpanel/bika_analysiscategories.py +++ b/bika/lims/controlpanel/bika_analysiscategories.py @@ -80,8 +80,7 @@ def __init__(self, context, request): }), ("Department", { "title": _("Department"), - "index": "getDepartmentTitle", - "attr": "getDepartmentTitle", + "index": "department_title", }), ("SortKey", { "title": _("Sort Key"), diff --git a/bika/lims/controlpanel/bika_analysisservices.py b/bika/lims/controlpanel/bika_analysisservices.py index a1feb6cdb8..c9334701ae 100644 --- a/bika/lims/controlpanel/bika_analysisservices.py +++ b/bika/lims/controlpanel/bika_analysisservices.py @@ -34,6 +34,7 @@ from bika.lims.idserver import renameAfterCreation from bika.lims.interfaces import IAnalysisServices from bika.lims.permissions import AddAnalysisService +from bika.lims.utils import format_supsub from bika.lims.utils import get_image from bika.lims.utils import get_link from bika.lims.utils import tmpID @@ -230,7 +231,6 @@ def __init__(self, context, request): "sortable": self.can_sort}), ("Unit", { "title": _("Unit"), - "attr": "getUnit", "sortable": False}), ("Price", { "title": _("Price"), @@ -390,6 +390,10 @@ def folderitem(self, obj, item, index): item["DuplicateVariation"] = self.format_duplication_variation( dup_variation) + # Unit + unit = obj.getUnit() + item["Unit"] = unit and format_supsub(unit) or "" + # Icons after_icons = "" if obj.getAccredited(): diff --git a/bika/lims/controlpanel/bika_analysisspecs.py b/bika/lims/controlpanel/bika_analysisspecs.py index 77560b2f45..bd8afd3732 100644 --- a/bika/lims/controlpanel/bika_analysisspecs.py +++ b/bika/lims/controlpanel/bika_analysisspecs.py @@ -76,7 +76,7 @@ def __init__(self, context, request): "index": "sortable_title"}), ("SampleType", { "title": _("Sample Type"), - "index": "getSampleTypeTitle"}), + "index": "sampletype_title"}), )) self.review_states = [ diff --git a/bika/lims/controlpanel/bika_departments.py b/bika/lims/controlpanel/bika_departments.py index 3e51b5378e..4ba7697ecb 100644 --- a/bika/lims/controlpanel/bika_departments.py +++ b/bika/lims/controlpanel/bika_departments.py @@ -79,15 +79,12 @@ def __init__(self, context, request): "toggle": True}), ("Manager", { "title": _("Manager"), - "index": "getManagerName", "toggle": True}), ("ManagerPhone", { "title": _("Manager Phone"), - "index": "getManagerPhone", "toggle": True}), ("ManagerEmail", { "title": _("Manager Email"), - "index": "getManagerEmail", "toggle": True}), )) @@ -133,26 +130,22 @@ def folderitem(self, obj, item, index): item["replace"]["Title"] = get_link(url, value=title) item["Description"] = description + item["Manager"] = "" + item["ManagerPhone"] = "" + item["ManagerEmail"] = "" manager = obj.getManager() - manager_name = obj.getManagerName() - manager_phone = obj.getManagerPhone() - manager_email = obj.getManagerEmail() - if manager: + manager_name = manager.getFullname() item["Manager"] = manager_name - item["replace"]["Manager"] = get_link( - manager.absolute_url(), manager_name) - else: - item["Manager"] = "" - if manager_email: - item["ManagerEmail"] = manager_email + manager_url = manager.absolute_url() + item["replace"]["Manager"] = get_link(manager_url, manager_name) + + manager_email = manager.getEmailAddress() item["replace"]["ManagerEmail"] = get_email_link( manager_email, value=manager_email) - else: - item["ManagerEmail"] = "" - item["ManagerPhone"] = manager_phone + item["ManagerPhone"] = manager.getBusinessPhone() return item @@ -165,25 +158,6 @@ class Departments(ATFolder): displayContentsTab = False schema = schema - def getContacts(self, active_only=True): - catalog = api.get_tool("bika_setup_catalog") - query = { - "portal_type": "LabContact", - "sort_on": "sortable_title", - "sort_order": "ascending" - } - results = catalog(query) - - # XXX Better directly filter in the catalog query as soon as we have - # the active/inactive state in the primary workflow - if active_only: - results = filter(api.is_active, results) - - pairs = map( - lambda brain: (brain.UID, brain.Title), results) - - return DisplayList(pairs) - schemata.finalizeATCTSchema(schema, folderish=True, moveDiscussion=False) atapi.registerType(Departments, PROJECTNAME) diff --git a/bika/lims/controlpanel/bika_instruments.py b/bika/lims/controlpanel/bika_instruments.py index 25b4d3ad1e..28a7ac5297 100644 --- a/bika/lims/controlpanel/bika_instruments.py +++ b/bika/lims/controlpanel/bika_instruments.py @@ -72,7 +72,7 @@ def __init__(self, context, request): "index": "sortable_title"}), ("Type", { "title": _("Type"), - "index": "getInstrumentTypeName", + "index": "instrumenttype_title", "toggle": True, "sortable": True}), ("Brand", { @@ -81,7 +81,6 @@ def __init__(self, context, request): "toggle": True}), ("Model", { "title": _("Model"), - "index": "getModel", "toggle": True}), ("ExpiryDate", { "title": _("Expiry Date"), diff --git a/bika/lims/controlpanel/bika_labcontacts.py b/bika/lims/controlpanel/bika_labcontacts.py index a456b6716b..9d22b1c71f 100644 --- a/bika/lims/controlpanel/bika_labcontacts.py +++ b/bika/lims/controlpanel/bika_labcontacts.py @@ -67,10 +67,9 @@ def __init__(self, context, request): self.pagesize = 25 self.columns = collections.OrderedDict(( - # TODO: Better sort by last name (index required!) ("Fullname", { "title": _("Name"), - "index": "getFullname"}), + "index": "sortable_title"}), ("DefaultDepartment", { "title": _("Default Department"), "toggle": False}), diff --git a/bika/lims/controlpanel/bika_labproducts.py b/bika/lims/controlpanel/bika_labproducts.py index a5e7941a69..efae5eda75 100644 --- a/bika/lims/controlpanel/bika_labproducts.py +++ b/bika/lims/controlpanel/bika_labproducts.py @@ -53,19 +53,16 @@ def __init__(self, context, request): 'index': 'sortable_title', 'toggle': True}, 'Volume': {'title': _('Volume'), - 'index': 'getVolume', 'toggle': True}, 'Unit': {'title': _('Unit'), - 'index': 'getUnit', 'toggle': True}, 'Price': {'title': _('Price'), - 'index': 'getPrice', + 'index': 'price', 'toggle': True}, 'VATAmount': {'title': _('VAT Amount'), - 'index': 'getVATAmount', 'toggle': True}, 'TotalPrice': {'title': _('Total Price'), - 'index': 'getTotalPrice', + 'index': 'price_total', 'toggle': True}, } self.review_states = [ diff --git a/bika/lims/controlpanel/bika_samplepoints.py b/bika/lims/controlpanel/bika_samplepoints.py index eb4ea60ca1..b79189ff27 100644 --- a/bika/lims/controlpanel/bika_samplepoints.py +++ b/bika/lims/controlpanel/bika_samplepoints.py @@ -89,7 +89,7 @@ def __init__(self, context, request): "toggle": False}), ("SampleTypes", { "title": _("Sample Types"), - "index": "getSampleTypeTitle", + "index": "sampletype_title", "toggle": True}), )) diff --git a/bika/lims/controlpanel/bika_suppliers.py b/bika/lims/controlpanel/bika_suppliers.py index 5d4aafa61e..6821639f83 100644 --- a/bika/lims/controlpanel/bika_suppliers.py +++ b/bika/lims/controlpanel/bika_suppliers.py @@ -71,7 +71,7 @@ def __init__(self, context, request): self.columns = collections.OrderedDict(( ("Name", { "title": _("Name"), - "index": "getName"}), + "index": "sortable_title"}), ("Email", { "title": _("Email"), "toggle": True}), diff --git a/bika/lims/controlpanel/bika_worksheettemplates.py b/bika/lims/controlpanel/bika_worksheettemplates.py index b19b00a7a7..369ffad314 100644 --- a/bika/lims/controlpanel/bika_worksheettemplates.py +++ b/bika/lims/controlpanel/bika_worksheettemplates.py @@ -82,7 +82,7 @@ def __init__(self, context, request): "toggle": True}), ("Instrument", { "title": _("Instrument"), - "index": "getInstrumentTitle", + "index": "instrument_title", "toggle": True, }), )) diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index b74fc273d7..a05fc7b3b5 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -454,6 +454,9 @@ class ILabProducts(Interface): """Marker interface for Lab Products """ +class ILabProduct(Interface): + """Marker interface for a LabProduct + """ class ISamplePoint(Interface): """Marker interface for a Sample Point @@ -539,6 +542,9 @@ class IWorksheetTemplates(Interface): """Marker interface for Worksheet Templates """ +class IWorksheetTemplate(Interface): + """Marker interface for Worksheet Template + """ class IBikaCatalog(Interface): """Marker interface for bika_catalog @@ -1017,3 +1023,60 @@ def getSampleTypeUID(self): def getSampleTypeTitle(self): """Returns the title or a comma separated list of sample type titles """ + + +class IHavePrice(Interface): + """Marker interface for objects that have a Price + """ + + def getPrice(self): + """Returns the price of the instance + """ + + def getTotalPrice(self): + """Returns the total price of the instance + """ + + +class IHaveInstrument(Interface): + """Marker interface for objects that have Instrument(s) assigned + """ + + def getInstrument(self): + """Returns the instrument or instruments the instance is assigned to + """ + + +class IHaveDepartment(Interface): + """Marker interface for objects that have Department(s) assigned + """ + + def getDepartment(self): + """Returns the department or departments the instance is assigned to + """ + + +class IOrganisation(Interface): + """Marker interface for IOrganisation object + """ + + def getName(self): + """Returns the name of the organisation. Masks Title() + """ + + +class IHaveAnalysisCategory(Interface): + """Marker interface for objects that have AnalysisCategory(ies) assigned + """ + + def getCategory(self): + """Returns the category(ies) assigned to this instance + """ + + def getCategoryUID(self): + """Returns the UID of the category(ies) assigned to this instance + """ + + def getCategoryTitle(self): + """Returns the title of the category(ies) assigned to this instance + """ diff --git a/bika/lims/setuphandlers.py b/bika/lims/setuphandlers.py index 489dae7fc2..fd1256aaee 100644 --- a/bika/lims/setuphandlers.py +++ b/bika/lims/setuphandlers.py @@ -166,55 +166,27 @@ ("bika_setup_catalog", "Type", "", "FieldIndex"), ("bika_setup_catalog", "UID", "", "FieldIndex"), ("bika_setup_catalog", "allowedRolesAndUsers", "", "KeywordIndex"), + ("bika_setup_catalog", "category_uid", "", "KeywordIndex"), ("bika_setup_catalog", "created", "", "DateIndex"), - ("bika_setup_catalog", "getAccredited", "", "FieldIndex"), - ("bika_setup_catalog", "getAnalyst", "", "FieldIndex"), - ("bika_setup_catalog", "getAvailableMethodUIDs", "", "KeywordIndex"), - ("bika_setup_catalog", "getBlank", "", "FieldIndex"), - ("bika_setup_catalog", "getCalculationTitle", "", "FieldIndex"), - ("bika_setup_catalog", "getCalculationUID", "", "FieldIndex"), - ("bika_setup_catalog", "getCalibrationExpiryDate", "", "FieldIndex"), - ("bika_setup_catalog", "getCategoryTitle", "", "FieldIndex"), - ("bika_setup_catalog", "getCategoryUID", "", "FieldIndex"), + ("bika_setup_catalog", "department_title", "", "KeywordIndex"), + ("bika_setup_catalog", "department_uid", "", "KeywordIndex"), ("bika_setup_catalog", "getClientUID", "", "FieldIndex"), - ("bika_setup_catalog", "getDepartmentTitle", "", "FieldIndex"), - ("bika_setup_catalog", "getDocumentID", "", "FieldIndex"), - ("bika_setup_catalog", "getDuplicateVariation", "", "FieldIndex"), - ("bika_setup_catalog", "getFormula", "", "FieldIndex"), - ("bika_setup_catalog", "getFullname", "", "FieldIndex"), - ("bika_setup_catalog", "getHazardous", "", "FieldIndex"), ("bika_setup_catalog", "getId", "", "FieldIndex"), - ("bika_setup_catalog", "getInstrumentLocationName", "", "FieldIndex"), - ("bika_setup_catalog", "getInstrumentTitle", "", "FieldIndex"), - ("bika_setup_catalog", "getInstrumentType", "", "FieldIndex"), - ("bika_setup_catalog", "getInstrumentTypeName", "", "FieldIndex"), ("bika_setup_catalog", "getKeyword", "", "FieldIndex"), - ("bika_setup_catalog", "getManagerEmail", "", "FieldIndex"), - ("bika_setup_catalog", "getManagerName", "", "FieldIndex"), - ("bika_setup_catalog", "getManagerPhone", "", "FieldIndex"), - ("bika_setup_catalog", "getMaxTimeAllowed", "", "FieldIndex"), - ("bika_setup_catalog", "getMethodID", "", "FieldIndex"), - ("bika_setup_catalog", "getModel", "", "FieldIndex"), - ("bika_setup_catalog", "getName", "", "FieldIndex"), - ("bika_setup_catalog", "getPointOfCapture", "", "FieldIndex"), - ("bika_setup_catalog", "getPrice", "", "FieldIndex"), - ("bika_setup_catalog", "getSamplePointTitle", "", "KeywordIndex"), - ("bika_setup_catalog", "getSamplePointUID", "", "FieldIndex"), - # Sorting of listings: Sample Points, Specifications - ("bika_setup_catalog", "getSampleTypeTitle", "", "FieldIndex"), - # Filter in Add2: Sample Points, Specifications, Templates - ("bika_setup_catalog", "sampletype_uids", "", "KeywordIndex"), - ("bika_setup_catalog", "getServiceUID", "", "FieldIndex"), - ("bika_setup_catalog", "getServiceUIDs", "", "KeywordIndex"), - ("bika_setup_catalog", "getTotalPrice", "", "FieldIndex"), - ("bika_setup_catalog", "getUnit", "", "FieldIndex"), - ("bika_setup_catalog", "getVATAmount", "getVATAmount", "FieldIndex"), - ("bika_setup_catalog", "getVolume", "", "FieldIndex"), ("bika_setup_catalog", "id", "getId", "FieldIndex"), + ("bika_setup_catalog", "instrument_title", "", "KeywordIndex"), + ("bika_setup_catalog", "instrumenttype_title", "", "KeywordIndex"), ("bika_setup_catalog", "is_active", "", "BooleanIndex"), + ("bika_setup_catalog", "listing_searchable_text", "", "TextIndexNG3"), + ("bika_setup_catalog", "method_available_uid", "", "KeywordIndex"), ("bika_setup_catalog", "path", "getPhysicalPath", "ExtendedPathIndex"), + ("bika_setup_catalog", "point_of_capture", "", "FieldIndex"), ("bika_setup_catalog", "portal_type", "", "FieldIndex"), + ("bika_setup_catalog", "price", "", "FieldIndex"), + ("bika_setup_catalog", "price_total", "", "FieldIndex"), ("bika_setup_catalog", "review_state", "", "FieldIndex"), + ("bika_setup_catalog", "sampletype_title", "", "KeywordIndex"), + ("bika_setup_catalog", "sampletype_uid", "", "KeywordIndex"), ("bika_setup_catalog", "sortable_title", "", "FieldIndex"), ("bika_setup_catalog", "title", "", "FieldIndex"), @@ -253,39 +225,10 @@ ("bika_setup_catalog", "sortable_title"), ("bika_setup_catalog", "description"), ("bika_setup_catalog", "review_state"), - ("bika_setup_catalog", "getAccredited"), - ("bika_setup_catalog", "getInstrumentType"), - ("bika_setup_catalog", "getInstrumentTypeName"), - ("bika_setup_catalog", "getInstrumentLocationName"), - ("bika_setup_catalog", "getBlank"), - ("bika_setup_catalog", "getCalculationTitle"), - ("bika_setup_catalog", "getCalculationUID"), - ("bika_setup_catalog", "getCalibrationExpiryDate"), ("bika_setup_catalog", "getCategoryTitle"), ("bika_setup_catalog", "getCategoryUID"), ("bika_setup_catalog", "getClientUID"), - ("bika_setup_catalog", "getDepartmentTitle"), - ("bika_setup_catalog", "getDuplicateVariation"), - ("bika_setup_catalog", "getFormula"), - ("bika_setup_catalog", "getFullname"), - ("bika_setup_catalog", "getHazardous"), - ("bika_setup_catalog", "getInstrumentTitle"), ("bika_setup_catalog", "getKeyword"), - ("bika_setup_catalog", "getManagerName"), - ("bika_setup_catalog", "getManagerPhone"), - ("bika_setup_catalog", "getManagerEmail"), - ("bika_setup_catalog", "getMaxTimeAllowed"), - ("bika_setup_catalog", "getModel"), - ("bika_setup_catalog", "getName"), - ("bika_setup_catalog", "getPointOfCapture"), - ("bika_setup_catalog", "getPrice"), - ("bika_setup_catalog", "getSamplePointTitle"), - ("bika_setup_catalog", "getSamplePointUID"), - ("bika_setup_catalog", "getServiceUID"), - ("bika_setup_catalog", "getTotalPrice"), - ("bika_setup_catalog", "getUnit"), - ("bika_setup_catalog", "getVATAmount"), - ("bika_setup_catalog", "getVolume"), ("portal_catalog", "Analyst"), ) diff --git a/bika/lims/skins/bika/ploneCustom.css.dtml b/bika/lims/skins/bika/ploneCustom.css.dtml index b020c7d4ed..09fad1c8fa 100644 --- a/bika/lims/skins/bika/ploneCustom.css.dtml +++ b/bika/lims/skins/bika/ploneCustom.css.dtml @@ -1394,9 +1394,4 @@ div.bika-listing-table-top-hooks div.listing-hook-top-wide { margin-bottom: 0; } -.ws-analyses-search-button { - clear: left; - float: left; -} - diff --git a/bika/lims/subscribers/objectmodified.py b/bika/lims/subscribers/objectmodified.py index 1802458ca8..5b09123961 100644 --- a/bika/lims/subscribers/objectmodified.py +++ b/bika/lims/subscribers/objectmodified.py @@ -20,6 +20,9 @@ from Products.CMFCore.utils import getToolByName +from bika.lims import api +from bika.lims.catalog import CATALOG_ANALYSIS_LISTING + def ObjectModifiedEventHandler(obj, event): """ Various types need automation on edit. @@ -59,9 +62,9 @@ def ObjectModifiedEventHandler(obj, event): elif obj.portal_type == 'AnalysisCategory': # If the analysis category's Title is modified, we must # re-index all services and analyses that refer to this title. - for i in [['Analysis', 'bika_analysis_catalog'], - ['AnalysisService', 'bika_setup_catalog']]: - cat = getToolByName(obj, i[1]) - brains = cat(portal_type=i[0], getCategoryUID=obj.UID()) - for brain in brains: - brain.getObject().reindexObject(idxs=['getCategoryTitle']) + query = dict(getCategoryUID=obj.UID()) + brains = api.search(query, CATALOG_ANALYSIS_LISTING) + for brain in brains: + obj = api.get_object(brain) + obj.reindexObject(idxs=['getCategoryTitle']) + diff --git a/bika/lims/upgrade/v01_03_003.py b/bika/lims/upgrade/v01_03_003.py index 07a0ec8d6b..2d861f4926 100644 --- a/bika/lims/upgrade/v01_03_003.py +++ b/bika/lims/upgrade/v01_03_003.py @@ -22,6 +22,7 @@ from bika.lims import api from bika.lims import logger +from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG from bika.lims.config import PROJECTNAME as product from bika.lims.setuphandlers import setup_form_controller_actions from bika.lims.upgrade import upgradestep @@ -31,15 +32,48 @@ profile = "profile-{0}:default".format(product) INDEXES_TO_ADD = [ - # We changed the type of this index from FieldIndex to KeywordIndex - # https://github.com/senaite/senaite.core/pull/1481 - ("bika_setup_catalog", "sampletype_uids", "KeywordIndex"), + # Replaces getSampleTypeUIDs + ("bika_setup_catalog", "sampletype_uid", "KeywordIndex"), + + # Replaces getSampleTypeTitle + ("bika_setup_catalog", "sampletype_title", "KeywordIndex"), + + # Replaces getAvailableMethodUIDs + # Used to filter services in Worksheet's Add Analyses View for when the + # Worksheet Template being used has a Method assigned + ("bika_setup_catalog", "method_available_uid", "KeywordIndex"), + + # Replaces getInstrumentTitle + # Used for sorting Worksheet Templates listing by Instrument + ("bika_setup_catalog", "instrument_title", "KeywordIndex"), + + # Replaces getPrice, getTotalPrice, getVolume + # Used for sorting LabProducts listing + ("bika_setup_catalog", "price", "FieldIndex"), + ("bika_setup_catalog", "price_total", "FieldIndex"), + + # Replaces getInstrumentTypeName + ("bika_setup_catalog", "instrumenttype_title", "KeywordIndex"), + + # Replaces getDepartmentTitle + ("bika_setup_catalog", "department_title", "KeywordIndex"), + + # Replaces getPointOfCapture + ("bika_setup_catalog", "point_of_capture", "FieldIndex"), + + # Replaces getDepartmentUID + ("bika_setup_catalog", "department_uid", "KeywordIndex"), + + # Default listing_searchable_text index adapter for setup_catalog + ("bika_setup_catalog", "listing_searchable_text", "TextIndexNG3"), + + # Default listing_searchable_text index adapter for setup_catalog + ("bika_setup_catalog", "category_uid", "KeywordIndex"), ] INDEXES_TO_REMOVE = [ # Only used in add2 to filter Sample Points by Sample Type when a Sample # Type was selected. Now, getSampleTypeUID is used instead because of - # https://github.com/senaite/senaite.core/pull/1481 ("bika_setup_catalog", "getSampleTypeTitles"), # Only used for when Sample and SamplePartition objects @@ -52,9 +86,91 @@ # stored in bika_catalog (Batch, BatchFolder and ReferenceSample) ("bika_catalog", "getSampleTypeUID"), - # We remove this index because we changed it's type to KeywordIndex - # https://github.com/senaite/senaite.core/pull/1481 - ("bika_setup_catalog", "getSampleTypeUID") + # getAccredited was only used in the "hidden" view accreditation to filter + # services labeled as "accredited". Since we don't expect that listing to + # contain too many items, they are now filtered by waking-up the object + ("bika_setup_catalog", "getAccredited"), + + # getAnalyst index is used in Analyses (Duplicates and Reference included) + # and Worksheets. None of the types stored in setup_catalog support Analyst + ("bika_setup_catalog", "getAnalyst"), + + # getBlank index is not used in setup_catalog, but in bika_catalog, where + # is used in AddControl and AddBlank views (Worksheet) + ("bika_setup_catalog", "getBlank"), + + # Only used in analyses listing, but from analysis_catalog + ("bika_setup_catalog", "getCalculationUID"), + + # Only used for sorting in LabContacts listing. Replaced by sortable_title + ("bika_setup_catalog", "getFullname"), + + # Used in analysis_catalog, but not in setup_catalog + ("bika_setup_catalog", "getServiceUID"), + + # Not used anywhere + ("bika_setup_catalog", "getDocumentID"), + ("bika_setup_catalog", "getDuplicateVariation"), + ("bika_setup_catalog", "getFormula"), + ("bika_setup_catalog", "getInstrumentLocationName"), + ("bika_setup_catalog", "getInstrumentType"), + ("bika_setup_catalog", "getHazardous"), + ("bika_setup_catalog", "getManagerEmail"), + ("bika_setup_catalog", "getManagerPhone"), + ("bika_setup_catalog", "getManagerName"), + ("bika_setup_catalog", "getMethodID"), + ("bika_setup_catalog", "getMaxTimeAllowed"), + ("bika_setup_catalog", "getModel"), + ("bika_setup_catalog", "getCalculationTitle"), + ("bika_setup_catalog", "getCalibrationExpiryDate"), + ("bika_setup_catalog", "getVATAmount"), + ("bika_setup_catalog", "getUnit"), + ("bika_setup_catalog", "getSamplePointTitle"), + ("bika_setup_catalog", "getVolume"), + ("bika_setup_catalog", "getSamplePointUID"), + ("bika_setup_catalog", "getCategoryTitle"), + ("bika_setup_catalog", "cancellation_state"), + ("bika_setup_catalog", "getName"), + ("bika_setup_catalog", "getServiceUIDs"), + ("bika_setup_catalog", "SearchableText"), + + + # REPLACEMENTS (indexes to be removed because of a replacement) + + # getSampleTypeUID --> sampletype_uid (FieldIndex --> KeywordIndex) + ("bika_setup_catalog", "getSampleTypeUID"), + ("bika_setup_catalog", "sampletype_uids"), + + # getSampleTypeTitle --> sampletype_title + ("bika_setup_catalog", "getSampleTypeTitle"), + + # getAvailableMethodUIDs --> method_available_uid + ("bika_setup_catalog", "getAvailableMethodUIDs"), + + # getInstrumentTitle --> instrument_title + ("bika_setup_catalog", "getInstrumentTitle"), + + # getPrice --> price + ("bika_setup_catalog", "getPrice"), + + # getTotalPrice --> price_total + ("bika_setup_catalog", "getTotalPrice"), + + # getInstrumentTypeName --> instrumenttype_title + ("bika_setup_catalog", "getInstrumentTypeName"), + + # getDepartmentTitle --> department_title + ("bika_setup_catalog", "getDepartmentTitle"), + + # getPointOfCapture --> point_of_capture + ("bika_setup_catalog", "getPointOfCapture"), + + # getDepartmentUID --> department_uid + ("bika_setup_catalog", "getDepartmentUID"), + + # getCategoryUID --> category_uid + ("bika_setup_catalog", "getCategoryUID"), + ] METADATA_TO_REMOVE = [ @@ -70,7 +186,40 @@ # The following are the portal types stored in bika_catalog: # Batch, BatchFolder and ReferenceSample # and "getSampleTypeTitle" metadata is not used for none of them - ("bika_catalog", "getSampleTypeTitle") + ("bika_catalog", "getSampleTypeTitle"), + + # Not used anywhere + ("bika_setup_catalog", "getAccredited"), + + # Not used anywhere + ("bika_setup_catalog", "getBlank"), + ("bika_setup_catalog", "getDuplicateVariation"), + ("bika_setup_catalog", "getFormula"), + ("bika_setup_catalog", "getInstrumentLocationName"), + ("bika_setup_catalog", "getInstrumentTitle"), + ("bika_setup_catalog", "getPrice"), + ("bika_setup_catalog", "getTotalPrice"), + ("bika_setup_catalog", "getVolume"), + ("bika_setup_catalog", "getInstrumentTypeName"), + ("bika_setup_catalog", "getInstrumentType"), + ("bika_setup_catalog", "getHazardous"), + ("bika_setup_catalog", "getManagerEmail"), + ("bika_setup_catalog", "getManagerPhone"), + ("bika_setup_catalog", "getManagerName"), + ("bika_setup_catalog", "getMaxTimeAllowed"), + ("bika_setup_catalog", "getModel"), + ("bika_setup_catalog", "getCalculationTitle"), + ("bika_setup_catalog", "getCalculationUID"), + ("bika_setup_catalog", "getCalibrationExpiryDate"), + ("bika_setup_catalog", "getDepartmentTitle"), + ("bika_setup_catalog", "getVATAmount"), + ("bika_setup_catalog", "getUnit"), + ("bika_setup_catalog", "getPointOfCapture"), + ("bika_setup_catalog", "getSamplePointUID"), + ("bika_setup_catalog", "getFullname"), + ("bika_setup_catalog", "cancellation_state"), + ("bika_setup_catalog", "getName"), + ("bika_setup_catalog", "getServiceUID"), ] @@ -90,18 +239,15 @@ def upgrade(tool): # -------- ADD YOUR STUFF BELOW -------- + # Fix Site Properties Generic Setup Export Step # https://github.com/senaite/senaite.core/pull/1469 setup.runImportStepFromProfile(profile, "propertiestool") - # Remove stale indexes and metadata - # https://github.com/senaite/senaite.core/pull/1481 - remove_stale_indexes(portal) - remove_stale_metadata(portal) - - # Add new indexes - # https://github.com/senaite/senaite.core/pull/1481 - add_new_indexes(portal) + # Remove, rename and add indexes/metadata + # https://github.com/senaite/senaite.core/pull/1486 + cleanup_indexes_and_metadata(portal) + # Sample edit form (some selection widgets empty) # Reindex client's related fields (getClientUID, getClientTitle, etc.) # https://github.com/senaite/senaite.core/pull/1477 reindex_client_fields(portal) @@ -145,6 +291,36 @@ def reindex_client_fields(portal): logger.info("Reindexing client fields ... [DONE]") +def cleanup_indexes_and_metadata(portal): + # Remove stale indexes and metadata + remove_stale_indexes(portal) + remove_stale_metadata(portal) + + # Add new indexes + add_new_indexes(portal) + + # Some indexes in setup_catalog changed + reindex_labcontact_sortable_title(portal) + reindex_supplier_manufacturers_titles(portal) + + +def reindex_labcontact_sortable_title(portal): + logger.info("Reindexing sortable_title for LabContacts ...") + query = dict(portal_type="LabContact") + for brain in api.search(query, SETUP_CATALOG): + obj = api.get_object(brain) + obj.reindexObject(idxs=["sortable_title"]) + logger.info("Reindexing sortable_title for LabContacts ... [DONE]") + + +def reindex_supplier_manufacturers_titles(portal): + logger.info("Reindexing title indexes for Suppliers and Manufacturers ...") + query = dict(portal_type="Supplier") + for brain in api.search(query, SETUP_CATALOG): + obj = api.get_object(brain) + obj.reindexObject(idxs=["title", "sortable_title"]) + logger.info("Reindexing title indexes for Suppliers and Manufacturers ... [DONE]") + def remove_stale_indexes(portal): logger.info("Removing stale indexes ...") diff --git a/bika/lims/validators.py b/bika/lims/validators.py index ff5124eb73..2dc5088302 100644 --- a/bika/lims/validators.py +++ b/bika/lims/validators.py @@ -658,8 +658,7 @@ def __call__(self, value, *args, **kwargs): for category in value: if not category: continue - services = bsc( - portal_type="AnalysisService", getCategoryUID=category) + services = bsc(portal_type="AnalysisService", category_uid=category) for service in services: service = service.getObject() calc = service.getCalculation() @@ -1322,7 +1321,7 @@ def __call__(self, value, *args, **kwargs): bsc = getToolByName(instance, 'bika_setup_catalog') query = { 'portal_type': 'AnalysisService', - 'getAvailableMethodUIDs': method.UID() + 'method_available_uid': method.UID() } method_ans_uids = [b.UID for b in bsc(query)] rules = instance.getReflexRules()