From a198bf0dd58244827e1890b80da59b56fad941fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 20 Dec 2019 23:04:59 +0100 Subject: [PATCH 01/41] Make RemarksField to store historic records in json format --- bika/lims/browser/fields/remarksfield.py | 105 +++++++++++++++++++---- 1 file changed, 89 insertions(+), 16 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 4a590eb0ca..84df7c7ce6 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -18,6 +18,9 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. +import json +import six + from AccessControl import ClassSecurityInfo from AccessControl import getSecurityManager from bika.lims.browser.widgets import RemarksWidget @@ -28,6 +31,39 @@ from Products.Archetypes.Registry import registerField from zope import event from zope.interface import implements +from bika.lims import api +from bika.lims.utils import tmpID + + +class RemarksHistory(list): + """A list containing a remarks history, but __str__ returns the legacy + format from instances prior v1.3.3 + """ + def to_legacy(self): + """Returns the remarks in legacy format + """ + remarks = list() + for record in self: + msg = record.get("content") + if not msg: + continue + date_msg = record.get("date") + user_name = record.get("user_name") + if date_msg and user_name: + # Build the legacy format + msg = "=== {} ({})\n{}".format(date_msg, user_name, msg) + remarks.append(msg) + return "\n".join(remarks) + + def __str__(self): + """Returns the remarks in legacy format + """ + return self.to_legacy() + + def __eq__(self, y): + if isinstance(y, six.string_types): + return str(self) == y + return super(list, self).__eq__(y) class RemarksField(ObjectField): @@ -55,29 +91,66 @@ def set(self, instance, value, **kwargs): """ if not value: return - value = value.strip() - date = DateTime().rfc822() - user = getSecurityManager().getUser() - username = user.getUserName() - divider = "=== {} ({})".format(date, username) - existing_remarks = instance.getRawRemarks() - remarks = '\n'.join([divider, value, existing_remarks]) + + if isinstance(value, RemarksHistory): + # Override the whole history here + history = value + else: + # Create a new history record + record = self.to_history_record(value) + + # Append the new record to the history + history = self.get_history(instance) + history.append(record) + + # Generate the json and store + remarks = json.dumps(history) ObjectField.set(self, instance, remarks) - # reindex the object after save to update all catalog metadata - instance.reindexObject() + # notify object edited event event.notify(ObjectEditedEvent(instance)) - def get_cooked_remarks(self, instance): - text = self.get(instance) - if not text: - return "" - return text.replace('\n', '
') + def to_history_record(self, value): + """Transforms the value to an history record + """ + value = value.strip() + user = getSecurityManager().getUser() + contact = api.get_user_contact(user) + record = { + "id": tmpID(), + "date": DateTime().ISO(), + "user_name": user.getUserName(), + "user_full_name": contact and contact.getFullname() or "", + "content": value, + } + return record + + def get_history(self, instance): + """Returns a RemarksHistory object with the remarks entries + """ + remarks = instance.getRawRemarks() + if not remarks: + return [] + try: + records = json.loads(remarks) + except ValueError as ex: + # This is for backwards compatibility with legacy format < v1.3.3 + records = [{"id": tmpID(), + "date": "", + "user_name": "", + "user_full_name": "", + "content": remarks}] + return RemarksHistory(records) + + def to_html(self, value): + """Convert the value to HTML format + """ + return api.text_to_html(value) def get(self, instance, **kwargs): - """Returns raw field value. + """Returns the value in legacy format """ - return self.getRaw(instance, **kwargs) + return str(self.get_history(instance)) def getRaw(self, instance, **kwargs): """Returns raw field value (possible wrapped in BaseUnit) From 134995c6987bac1283252a1a213d6cea474e119b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 20 Dec 2019 23:08:30 +0100 Subject: [PATCH 02/41] Better styling of the Remarks widget --- .../analysisrequest_manage_results.pt | 15 ----- .../templates/analysisrequest_view.pt | 19 +----- .../browser/worksheet/templates/results.pt | 12 ---- .../skins/bika/bika_widgets/remarkswidget.pt | 62 +++++++++++++++---- 4 files changed, 50 insertions(+), 58 deletions(-) diff --git a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt index 8df7a0b733..ba0ecb0ab3 100644 --- a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt +++ b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt @@ -8,21 +8,6 @@ - - diff --git a/bika/lims/browser/analysisrequest/templates/analysisrequest_view.pt b/bika/lims/browser/analysisrequest/templates/analysisrequest_view.pt index afd27104af..5036901750 100644 --- a/bika/lims/browser/analysisrequest/templates/analysisrequest_view.pt +++ b/bika/lims/browser/analysisrequest/templates/analysisrequest_view.pt @@ -8,21 +8,6 @@ - - @@ -81,9 +66,7 @@ Remarks -
- -
+ diff --git a/bika/lims/browser/worksheet/templates/results.pt b/bika/lims/browser/worksheet/templates/results.pt index c78d9f75de..66723ec04b 100644 --- a/bika/lims/browser/worksheet/templates/results.pt +++ b/bika/lims/browser/worksheet/templates/results.pt @@ -12,18 +12,6 @@ - - diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 4c88bd9051..a07e979831 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -7,7 +7,7 @@ @@ -24,32 +24,68 @@ - - Hide the widget's save button when rendering in portal_factory URLs: - XXX Need some abstracted way to instruct the widget to hide the save button -
-
-
- - content - -
+ +
+ +
+
+
+
+
+
+
+
+
- + From bc4e62cccdbc1005e272d47593a3141a766c7e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 20 Dec 2019 23:10:12 +0100 Subject: [PATCH 03/41] Fix get_include_methods function from jsonapi When include_methods param is used in a read call with a single parameter (instead of a list of more than one), the jsonapi does not handle the value parameter properly and transforms it to a list of characters instead. A call like follows: ``` this.ajax_submit({ url: this.get_portal_url() + "/@@API/read", data: { catalog_name: "uid_catalog", UID: widget.attr('data-uid'), include_methods: ["getSomething"], } }).done(function(data) { return deferred.resolveWith(this, [data.objects[0]["getSomething"]]); }); ``` fails. The function `get_include_methods` returns `getSomething` instead of a list `[getSomething]`. Therefore, the function `load_method_values` tries the following calls to the instance: `g`, `e`, `t`, `S`, `o`, ... If the call is like follows: ``` this.ajax_submit({ url: this.get_portal_url() + "/@@API/read", data: { catalog_name: "uid_catalog", UID: widget.attr('data-uid'), include_methods: ["getSomething", "getId"], } }).done(function(data) { return deferred.resolveWith(this, [data.objects[0]["getSomething"]]); }); ``` the list is resolved properly. --- bika/lims/jsonapi/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bika/lims/jsonapi/__init__.py b/bika/lims/jsonapi/__init__.py index a702cc2f53..5a4fecf02f 100644 --- a/bika/lims/jsonapi/__init__.py +++ b/bika/lims/jsonapi/__init__.py @@ -25,6 +25,7 @@ import json import Missing +import six import sys, traceback @@ -129,9 +130,15 @@ def load_field_values(instance, include_fields): def get_include_methods(request): """Retrieve include_methods values from the request """ - methods = request.get("include_methods", "") - include_methods = [ - x.strip() for x in methods.split(",") if x.strip()] + include_methods = [] + if "include_methods" in request: + methods = request.get("include_methods", "") + include_methods = [ + x.strip() for x in methods.split(",") if x.strip()] + if "include_methods[]" in request: + include_methods = request["include_methods[]"] + if isinstance(include_methods, six.string_types): + include_methods = [include_methods] return include_methods From 6508a996a5a01de9df43174ad01c8726af37dcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 20 Dec 2019 23:31:52 +0100 Subject: [PATCH 04/41] Now we are here, make it a bit better --- bika/lims/jsonapi/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bika/lims/jsonapi/__init__.py b/bika/lims/jsonapi/__init__.py index 5a4fecf02f..28a3744e5d 100644 --- a/bika/lims/jsonapi/__init__.py +++ b/bika/lims/jsonapi/__init__.py @@ -130,16 +130,15 @@ def load_field_values(instance, include_fields): def get_include_methods(request): """Retrieve include_methods values from the request """ - include_methods = [] - if "include_methods" in request: - methods = request.get("include_methods", "") - include_methods = [ - x.strip() for x in methods.split(",") if x.strip()] - if "include_methods[]" in request: - include_methods = request["include_methods[]"] - if isinstance(include_methods, six.string_types): - include_methods = [include_methods] - return include_methods + include_methods = request.get("include_methods[]") + if not include_methods: + include_methods = request.get("include_methods", []) + + if isinstance(include_methods, six.string_types): + include_methods = include_methods.split(",") + include_methods = map(lambda me: me.strip(), include_methods) + + return filter(None, include_methods) def load_method_values(instance, include_methods): From 5a40e59f24fe61ffc4e40a8d61f89d5c31ca9247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 20 Dec 2019 23:32:56 +0100 Subject: [PATCH 05/41] Changelog --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9046383d78..a955510fa6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -28,6 +28,7 @@ Changelog **Fixed** +- #1493 jsonapi.read omits `include_methods` when a single parameter is used - #1477 Sample edit form - some selection widgets empty - #1478 Clients default CC E-Mails missing in Add Sample - #1479 Fixed too many redirects error: Labclerks viewing verified worksheets From 0718a9b431db07a94a3a2f827a8cc6ded80ed84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 00:14:41 +0100 Subject: [PATCH 06/41] Don't use json.dumps, rather handle records with RemarksHistory --- bika/lims/browser/fields/remarksfield.py | 191 +++++++++++++----- .../coffee/bika_widgets/remarkswidget.coffee | 11 +- bika/lims/content/analysisprofile.py | 1 - bika/lims/content/analysisrequest.py | 1 - bika/lims/content/artemplate.py | 1 - bika/lims/content/batch.py | 1 - bika/lims/content/instrumentcertification.py | 1 - bika/lims/content/pricelist.py | 1 - bika/lims/content/referencesample.py | 1 - bika/lims/content/sample.py | 1 - bika/lims/content/supplier.py | 1 - bika/lims/content/supplyorder.py | 1 - bika/lims/content/worksheet.py | 1 - .../skins/bika/bika_widgets/remarkswidget.js | 13 +- .../skins/bika/bika_widgets/remarkswidget.pt | 45 ++--- 15 files changed, 179 insertions(+), 92 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 84df7c7ce6..b043cfefb0 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -19,46 +19,42 @@ # Some rights reserved, see README and LICENSE. import json -import six +import six from AccessControl import ClassSecurityInfo -from AccessControl import getSecurityManager -from bika.lims.browser.widgets import RemarksWidget -from bika.lims.interfaces import IRemarksField from DateTime import DateTime -from Products.Archetypes.event import ObjectEditedEvent from Products.Archetypes.Field import ObjectField from Products.Archetypes.Registry import registerField +from Products.Archetypes.event import ObjectEditedEvent from zope import event from zope.interface import implements + from bika.lims import api +from bika.lims.browser.widgets import RemarksWidget +from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID +from datetime import datetime + +from Products.ATContentTypes.utils import DT2dt +from dateutil.relativedelta import relativedelta +from bika.lims.browser import ulocalized_time +import pytz class RemarksHistory(list): """A list containing a remarks history, but __str__ returns the legacy format from instances prior v1.3.3 """ - def to_legacy(self): - """Returns the remarks in legacy format - """ - remarks = list() - for record in self: - msg = record.get("content") - if not msg: - continue - date_msg = record.get("date") - user_name = record.get("user_name") - if date_msg and user_name: - # Build the legacy format - msg = "=== {} ({})\n{}".format(date_msg, user_name, msg) - remarks.append(msg) - return "\n".join(remarks) + + def html(self): + return api.text_to_html(str(self)) def __str__(self): """Returns the remarks in legacy format """ - return self.to_legacy() + remarks = map(lambda rec: str(rec), self) + remarks = filter(None, remarks) + return "\n".join(remarks) def __eq__(self, y): if isinstance(y, six.string_types): @@ -66,6 +62,84 @@ def __eq__(self, y): return super(list, self).__eq__(y) +class RemarksHistoryRecord(dict): + """A dict implementation that represents a record/entry of Remarks History + """ + + def __init__(self, *arg, **kw): + super(RemarksHistoryRecord, self).__init__(*arg, **kw) + self["id"] = self.id or tmpID() + self["user_id"] = self.user_id + self["user_name"] = self.user_name + self["created"] = self.created or DateTime().ISO() + self["content"] = self.content + + @property + def id(self): + return self.get("id", "") + + @property + def user_id(self): + return self.get("user_id", "") + + @property + def user_name(self): + return self.get("user_name", "") + + @property + def created(self): + return self.get("created", "") + + @property + def content(self): + return self.get("content", "") + + @property + def html_content(self): + return api.text_to_html(self.content) + + @property + def friendly_created(self): + """Formats to `h m s` if the elapsed time between now and the time + passed-in is less than 1 month. Otherwise, returns the date time in + long format + """ + if not self.created: + return "" + + date_from = DT2dt(api.to_date(self.created)).replace(tzinfo=pytz.utc) + date_to = datetime.now().replace(tzinfo=pytz.utc) + diff = relativedelta(date_to, date_from) + if diff.years or diff.months: + return ulocalized_time(self.created, long_format=True, + context=api.get_portal()) + if diff.days > 5: + return ulocalized_time(self.created, long_format=True, + context=api.get_portal()) + if diff.days: + tuples = [(diff.days, "d"), (diff.hours, "h")] + elif diff.hours: + tuples = [(diff.hours, "h"), (diff.minutes, "m")] + else: + tuples = [(diff.minutes, "m"), (diff.seconds, "s")] + + # Boil out empties + tuples = filter(lambda tup: tup[0] > 0, tuples) + tokens = map(lambda tup: "{}{}".format(tup[0], tup[1]), tuples) + return " ".join(tokens) + + def __str__(self): + """Returns a legacy string format of the Remarks record + """ + if not self.content: + return "" + if self.created and self.user_id: + # Build the legacy format + return "=== {} ({})\n{}".format(self.created, self.user_id, + self.content) + return self.content + + class RemarksField(ObjectField): """A field that stores remarks. The value submitted to the setter will always be appended to the actual value of the field. @@ -80,11 +154,17 @@ class RemarksField(ObjectField): }) implements(IRemarksField) - security = ClassSecurityInfo() security.declarePrivate('set') + @property + def searchable(self): + """Returns False, preventing this field to be searchable by AT's + SearcheableText + """ + return False + def set(self, instance, value, **kwargs): """Adds the value to the existing text stored in the field, along with a small divider showing username and date of this entry. @@ -95,7 +175,17 @@ def set(self, instance, value, **kwargs): if isinstance(value, RemarksHistory): # Override the whole history here history = value - else: + + elif isinstance(value, (list, tuple)): + # This is a list, convert to RemarksHistory + history = RemarksHistory(list(value)) + + elif isinstance(value, RemarksHistoryRecord): + # This is a record, append to the history + history = self.get_history(instance) + history.append(value) + + elif isinstance(value, six.string_types): # Create a new history record record = self.to_history_record(value) @@ -103,9 +193,11 @@ def set(self, instance, value, **kwargs): history = self.get_history(instance) history.append(record) - # Generate the json and store - remarks = json.dumps(history) - ObjectField.set(self, instance, remarks) + else: + raise ValueError("Type not supported: {}".format(type(value))) + + # Store the data + ObjectField.set(self, instance, history) # notify object edited event event.notify(ObjectEditedEvent(instance)) @@ -113,44 +205,41 @@ def set(self, instance, value, **kwargs): def to_history_record(self, value): """Transforms the value to an history record """ - value = value.strip() - user = getSecurityManager().getUser() + user = api.get_current_user() contact = api.get_user_contact(user) - record = { - "id": tmpID(), - "date": DateTime().ISO(), - "user_name": user.getUserName(), - "user_full_name": contact and contact.getFullname() or "", - "content": value, - } - return record + full_name = contact and contact.getFullname() or "" + return RemarksHistoryRecord(user_id=user.id, + user_name=full_name, + content=value.strip()) def get_history(self, instance): """Returns a RemarksHistory object with the remarks entries """ remarks = instance.getRawRemarks() if not remarks: - return [] + return RemarksHistory() + + # Backwards compatibility with legacy from < v1.3.3 + if isinstance(remarks, six.string_types): + remarks = self.handle_legacy_value(remarks) + + return RemarksHistory(remarks) + + def handle_legacy_value(self, remarks): + # This is for backwards compatibility with legacy format < v1.3.3 try: + # json.loads does unicode conversion, which will fail in the catalog + # search for some cases. So we need to convert the strings to utf8 records = json.loads(remarks) except ValueError as ex: - # This is for backwards compatibility with legacy format < v1.3.3 - records = [{"id": tmpID(), - "date": "", - "user_name": "", - "user_full_name": "", - "content": remarks}] - return RemarksHistory(records) - - def to_html(self, value): - """Convert the value to HTML format - """ - return api.text_to_html(value) + records = [dict(content=remarks)] + + return map(lambda record: RemarksHistoryRecord(record), records) def get(self, instance, **kwargs): - """Returns the value in legacy format + """Returns a RemarksHistory object """ - return str(self.get_history(instance)) + return self.get_history(instance) def getRaw(self, instance, **kwargs): """Returns raw field value (possible wrapped in BaseUnit) diff --git a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee index 9e208916d1..76da2d8b00 100644 --- a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee +++ b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee @@ -65,7 +65,16 @@ class window.RemarksWidgetView widget = @get_remarks_widget(uid) return if widget is null el = widget.find('.remarks_history') - el.html(@format value) + val = value.pop() + record_header = $("
") + record_header.append $(""+val["user_id"]+"") + record_header.append $(""+val["user_name"]+"") + record_content = $("
") + record_content.html(@format(val["content"])) + record = $("
") + record.append record_header + record.append record_content + el.append record clear_remarks_textarea: (uid) => ### diff --git a/bika/lims/content/analysisprofile.py b/bika/lims/content/analysisprofile.py index 42c4f2a8a6..d7b1a29123 100644 --- a/bika/lims/content/analysisprofile.py +++ b/bika/lims/content/analysisprofile.py @@ -66,7 +66,6 @@ ) ), RemarksField('Remarks', - searchable=True, widget=RemarksWidget( label=_("Remarks") ) diff --git a/bika/lims/content/analysisrequest.py b/bika/lims/content/analysisrequest.py index 227d7ead22..53d5255680 100644 --- a/bika/lims/content/analysisrequest.py +++ b/bika/lims/content/analysisrequest.py @@ -1069,7 +1069,6 @@ RemarksField( 'Remarks', - searchable=True, read_permission=View, write_permission=FieldEditRemarks, widget=RemarksWidget( diff --git a/bika/lims/content/artemplate.py b/bika/lims/content/artemplate.py index 72f8b260ca..38239c3d4f 100644 --- a/bika/lims/content/artemplate.py +++ b/bika/lims/content/artemplate.py @@ -117,7 +117,6 @@ ), RemarksField( "Remarks", - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/batch.py b/bika/lims/content/batch.py index 1d9969af83..8bf8315409 100644 --- a/bika/lims/content/batch.py +++ b/bika/lims/content/batch.py @@ -119,7 +119,6 @@ def BatchDate(instance): RemarksField( 'Remarks', - searchable=True, widget=RemarksWidget( label=_('Remarks'), ) diff --git a/bika/lims/content/instrumentcertification.py b/bika/lims/content/instrumentcertification.py index 1e810f2a40..de5824d5bc 100644 --- a/bika/lims/content/instrumentcertification.py +++ b/bika/lims/content/instrumentcertification.py @@ -196,7 +196,6 @@ RemarksField( 'Remarks', - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/pricelist.py b/bika/lims/content/pricelist.py index 18a0bb83dc..d11e124cdc 100644 --- a/bika/lims/content/pricelist.py +++ b/bika/lims/content/pricelist.py @@ -79,7 +79,6 @@ RemarksField( "Remarks", - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/referencesample.py b/bika/lims/content/referencesample.py index 78c4415947..ac6deb14d7 100644 --- a/bika/lims/content/referencesample.py +++ b/bika/lims/content/referencesample.py @@ -103,7 +103,6 @@ RemarksField( 'Remarks', schemata='Description', - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/sample.py b/bika/lims/content/sample.py index 207a179991..6edb677d42 100644 --- a/bika/lims/content/sample.py +++ b/bika/lims/content/sample.py @@ -343,7 +343,6 @@ ), RemarksField( 'Remarks', - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/supplier.py b/bika/lims/content/supplier.py index 5c6f6a8c07..d7079c5bb5 100644 --- a/bika/lims/content/supplier.py +++ b/bika/lims/content/supplier.py @@ -36,7 +36,6 @@ RemarksField( "Remarks", - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/supplyorder.py b/bika/lims/content/supplyorder.py index 9440883d3f..732f9feae7 100644 --- a/bika/lims/content/supplyorder.py +++ b/bika/lims/content/supplyorder.py @@ -98,7 +98,6 @@ ), RemarksField( 'Remarks', - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/content/worksheet.py b/bika/lims/content/worksheet.py index b606cbb83e..07e5e89200 100644 --- a/bika/lims/content/worksheet.py +++ b/bika/lims/content/worksheet.py @@ -110,7 +110,6 @@ RemarksField( 'Remarks', - searchable=True, widget=RemarksWidget( label=_("Remarks"), ), diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.js b/bika/lims/skins/bika/bika_widgets/remarkswidget.js index 4c1c2daafb..1a551b473d 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.js +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.js @@ -91,13 +91,22 @@ /* * Clear and update the widget's History with the provided value. */ - var el, widget; + var el, record, record_content, record_header, val, widget; widget = this.get_remarks_widget(uid); if (widget === null) { return; } el = widget.find('.remarks_history'); - return el.html(this.format(value)); + val = value.pop(); + record_header = $("
"); + record_header.append($("" + val["user_id"] + "")); + record_header.append($("" + val["user_name"] + "")); + record_content = $("
"); + record_content.html(this.format(val["content"])); + record = $("
"); + record.append(record_header); + record.append(record_content); + return el.append(record); }; RemarksWidgetView.prototype.clear_remarks_textarea = function(uid) { diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index a07e979831..792559134f 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -7,13 +7,12 @@ textarea + tal:replace="structure history/html">textarea @@ -41,41 +40,33 @@ input.saveRemarks { margin: 10px 0 20px; } - .remarks_history div.record { - box-shadow: 1px 1px 4px #AAA; - border-radius: 5px; - margin: 10px 0 10px; + .record { + margin: 20px 0 20px; } - .remarks_history div.record-header { - background-color: antiquewhite; - padding:2px 10px; - border-radius: 5px 5px 0 0; - font-size: 0.8em; + .record-header { + font-size: 0.9em; color: #666; + border-bottom: 1px solid #efefef; + margin: 0 0 5px; + padding: 0 0 3px; } - .record-date { - float: right; + .record-header span { + padding-right:10px; } - .remarks_history div.record .record-content { - clear: both; - padding: 10px; - background-color: #fff; - border-radius: 5px; + .record-header .record-user { + font-weight:bold; }
-
+
-
-
+ + +
+ tal:content="structure record/html_content">
From cdcf635c5f36ad36173cafd80882934cef64f754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 00:22:37 +0100 Subject: [PATCH 07/41] Handle legacy values in an easier way --- bika/lims/browser/fields/remarksfield.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index b043cfefb0..d87b6bf0d7 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -221,20 +221,10 @@ def get_history(self, instance): # Backwards compatibility with legacy from < v1.3.3 if isinstance(remarks, six.string_types): - remarks = self.handle_legacy_value(remarks) + remark = RemarksHistoryRecord(content=remarks.strip()) + remarks = RemarksHistory([remark,]) - return RemarksHistory(remarks) - - def handle_legacy_value(self, remarks): - # This is for backwards compatibility with legacy format < v1.3.3 - try: - # json.loads does unicode conversion, which will fail in the catalog - # search for some cases. So we need to convert the strings to utf8 - records = json.loads(remarks) - except ValueError as ex: - records = [dict(content=remarks)] - - return map(lambda record: RemarksHistoryRecord(record), records) + return remarks def get(self, instance, **kwargs): """Returns a RemarksHistory object From 1782a9b9bb39f77a6468abcc1c1b5ff2c4acfeff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 00:23:08 +0100 Subject: [PATCH 08/41] Optimize imports --- bika/lims/browser/fields/remarksfield.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index d87b6bf0d7..e70425110a 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -18,27 +18,25 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -import json +from datetime import datetime +import pytz import six from AccessControl import ClassSecurityInfo from DateTime import DateTime +from Products.ATContentTypes.utils import DT2dt from Products.Archetypes.Field import ObjectField from Products.Archetypes.Registry import registerField from Products.Archetypes.event import ObjectEditedEvent +from dateutil.relativedelta import relativedelta from zope import event from zope.interface import implements from bika.lims import api +from bika.lims.browser import ulocalized_time from bika.lims.browser.widgets import RemarksWidget from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID -from datetime import datetime - -from Products.ATContentTypes.utils import DT2dt -from dateutil.relativedelta import relativedelta -from bika.lims.browser import ulocalized_time -import pytz class RemarksHistory(list): From b91c272d9188b1b0a29ffdeca0f7be1eca62826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 00:27:38 +0100 Subject: [PATCH 09/41] Changelog --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index a955510fa6..3617b7057e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,7 @@ Changelog **Changed** +- #1495 Better Remarks handling and display - #1490 Support Dexterity Behavior Fields in API - #1488 Support Dexterity Contents in Catalog Indexers - #1486 Clean-up of indexes and metadata from `setup_catalog` From 7a3b192b6464943d10f452b6bd4a56a5d52dc5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 01:18:56 +0100 Subject: [PATCH 10/41] support for markdown in Remarks --- bika/lims/browser/fields/remarksfield.py | 5 +++++ bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 2 +- setup.py | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index e70425110a..abe8aef71f 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -37,6 +37,7 @@ from bika.lims.browser.widgets import RemarksWidget from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID +import markdown class RemarksHistory(list): @@ -96,6 +97,10 @@ def content(self): def html_content(self): return api.text_to_html(self.content) + @property + def markdown_content(self): + return markdown.markdown(self.content) + @property def friendly_created(self): """Formats to `h m s` if the elapsed time between now and the time diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 792559134f..e9d9207c3c 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -66,7 +66,7 @@
+ tal:content="structure record/markdown_content">
diff --git a/setup.py b/setup.py index 5f81fa3d65..5ace184d03 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,7 @@ # Needed for `IPortalCatalogQueueProcessor`, which will be included in # `Products.CMFCore` in Plone 5. Remove after we are on Plone 5! 'collective.indexing', + 'markdown', ], extras_require={ 'test': [ From f1d1e6ad4283db9941bafca55049950e6cc2e569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 16:50:26 +0100 Subject: [PATCH 11/41] Remove datetime formatting --- bika/lims/browser/fields/remarksfield.py | 38 +------------------ .../coffee/bika_widgets/remarkswidget.coffee | 1 + .../skins/bika/bika_widgets/remarkswidget.js | 1 + .../skins/bika/bika_widgets/remarkswidget.pt | 2 +- 4 files changed, 4 insertions(+), 38 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index abe8aef71f..dbc73306e6 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -18,26 +18,20 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. -from datetime import datetime - -import pytz +import markdown import six from AccessControl import ClassSecurityInfo from DateTime import DateTime -from Products.ATContentTypes.utils import DT2dt from Products.Archetypes.Field import ObjectField from Products.Archetypes.Registry import registerField from Products.Archetypes.event import ObjectEditedEvent -from dateutil.relativedelta import relativedelta from zope import event from zope.interface import implements from bika.lims import api -from bika.lims.browser import ulocalized_time from bika.lims.browser.widgets import RemarksWidget from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID -import markdown class RemarksHistory(list): @@ -101,36 +95,6 @@ def html_content(self): def markdown_content(self): return markdown.markdown(self.content) - @property - def friendly_created(self): - """Formats to `h m s` if the elapsed time between now and the time - passed-in is less than 1 month. Otherwise, returns the date time in - long format - """ - if not self.created: - return "" - - date_from = DT2dt(api.to_date(self.created)).replace(tzinfo=pytz.utc) - date_to = datetime.now().replace(tzinfo=pytz.utc) - diff = relativedelta(date_to, date_from) - if diff.years or diff.months: - return ulocalized_time(self.created, long_format=True, - context=api.get_portal()) - if diff.days > 5: - return ulocalized_time(self.created, long_format=True, - context=api.get_portal()) - if diff.days: - tuples = [(diff.days, "d"), (diff.hours, "h")] - elif diff.hours: - tuples = [(diff.hours, "h"), (diff.minutes, "m")] - else: - tuples = [(diff.minutes, "m"), (diff.seconds, "s")] - - # Boil out empties - tuples = filter(lambda tup: tup[0] > 0, tuples) - tokens = map(lambda tup: "{}{}".format(tup[0], tup[1]), tuples) - return " ".join(tokens) - def __str__(self): """Returns a legacy string format of the Remarks record """ diff --git a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee index 76da2d8b00..8f3edb87a0 100644 --- a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee +++ b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee @@ -69,6 +69,7 @@ class window.RemarksWidgetView record_header = $("
") record_header.append $(""+val["user_id"]+"") record_header.append $(""+val["user_name"]+"") + record_header.append $(""+val["created"]+"") record_content = $("
") record_content.html(@format(val["content"])) record = $("
") diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.js b/bika/lims/skins/bika/bika_widgets/remarkswidget.js index 1a551b473d..adf1babb82 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.js +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.js @@ -101,6 +101,7 @@ record_header = $("
"); record_header.append($("" + val["user_id"] + "")); record_header.append($("" + val["user_name"] + "")); + record_header.append($("" + val["created"] + "")); record_content = $("
"); record_content.html(this.format(val["content"])); record = $("
"); diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index e9d9207c3c..8b570d7405 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -63,7 +63,7 @@
- +
From 7e1e4f4f25cf4f5c1233ea83e48418382bca17d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 17:03:12 +0100 Subject: [PATCH 12/41] Fix MarkdownException: UnicodeDecodeError: Markdown only accepts unicode or ascii input --- bika/lims/browser/fields/remarksfield.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index dbc73306e6..f956873c29 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -93,7 +93,8 @@ def html_content(self): @property def markdown_content(self): - return markdown.markdown(self.content) + content = self.content.decode('utf-8') + return markdown.markdown(content) def __str__(self): """Returns a legacy string format of the Remarks record From f67d5ef9cb0445819b8915ac7c2868a912e60d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 17:07:38 +0100 Subject: [PATCH 13/41] Convert plain list to RemarksHistoryRecord --- bika/lims/browser/fields/remarksfield.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index f956873c29..e41e0ef40a 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -146,7 +146,8 @@ def set(self, instance, value, **kwargs): elif isinstance(value, (list, tuple)): # This is a list, convert to RemarksHistory - history = RemarksHistory(list(value)) + remarks = map(lambda item: RemarksHistoryRecord(item), value) + history = RemarksHistory(remarks) elif isinstance(value, RemarksHistoryRecord): # This is a record, append to the history From 8df8665f427d967b31421ff70a92642dd079571b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 17:22:39 +0100 Subject: [PATCH 14/41] Add "record" class to the container of a remarks history entry --- bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 8b570d7405..c56394d03f 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -59,7 +59,7 @@
-
+
From 4a01ca311feff0229d856cd6e45742c31b7bbcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 17:23:02 +0100 Subject: [PATCH 15/41] Remove "well" container from manage results in WS and Sample --- .../templates/analysisrequest_manage_results.pt | 4 +--- bika/lims/browser/worksheet/templates/results.pt | 4 +--- bika/lims/content/worksheet.py | 1 + 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt index ba0ecb0ab3..5c57ecf913 100644 --- a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt +++ b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt @@ -81,9 +81,7 @@ Remarks -
- -
+
diff --git a/bika/lims/browser/worksheet/templates/results.pt b/bika/lims/browser/worksheet/templates/results.pt index 66723ec04b..166f9826ef 100644 --- a/bika/lims/browser/worksheet/templates/results.pt +++ b/bika/lims/browser/worksheet/templates/results.pt @@ -213,9 +213,7 @@ Remarks -
- -
+
diff --git a/bika/lims/content/worksheet.py b/bika/lims/content/worksheet.py index 07e5e89200..cd4180befc 100644 --- a/bika/lims/content/worksheet.py +++ b/bika/lims/content/worksheet.py @@ -111,6 +111,7 @@ RemarksField( 'Remarks', widget=RemarksWidget( + render_own_label=True, label=_("Remarks"), ), ), From a5d5d96fe3801b949562801474375d5ae415827f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 22:01:34 +0100 Subject: [PATCH 16/41] Move css to remarkswidget.css --- .../skins/bika/bika_widgets/remarkswidget.css | 21 ++++++++++++ .../skins/bika/bika_widgets/remarkswidget.pt | 33 ++++--------------- 2 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 bika/lims/skins/bika/bika_widgets/remarkswidget.css diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.css b/bika/lims/skins/bika/bika_widgets/remarkswidget.css new file mode 100644 index 0000000000..cd1a5eacb9 --- /dev/null +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.css @@ -0,0 +1,21 @@ +.ArchetypesRemarksWidget { + margin: 20px 0 20px; } + + .ArchetypesRemarksWidget input.saveRemarks { + margin: 10px 0 20px; } + + .ArchetypesRemarksWidget .record { + margin: 20px 0 20px; } + + .ArchetypesRemarksWidget .record .record-header { + font-size: 0.9em; + color: #666; + border-bottom: 1px solid #efefef; + margin: 0 0 5px; + padding: 0 0 3px; } + + .ArchetypesRemarksWidget .record .record-header span { + padding-right:10px; } + + .ArchetypesRemarksWidget .record .record-header .record-user { + font-weight:bold; } diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index c56394d03f..bab175dd1f 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -17,9 +17,12 @@ - + + + + @@ -33,30 +36,6 @@ -
From b274d7ad0553401eca78ae4a923868ae75dda347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Mon, 23 Dec 2019 22:04:02 +0100 Subject: [PATCH 17/41] fix bad indentation --- bika/lims/skins/bika/bika_widgets/remarkswidget.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.css b/bika/lims/skins/bika/bika_widgets/remarkswidget.css index cd1a5eacb9..91f66f632c 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.css +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.css @@ -4,18 +4,18 @@ .ArchetypesRemarksWidget input.saveRemarks { margin: 10px 0 20px; } - .ArchetypesRemarksWidget .record { + .ArchetypesRemarksWidget .record { margin: 20px 0 20px; } - .ArchetypesRemarksWidget .record .record-header { + .ArchetypesRemarksWidget .record .record-header { font-size: 0.9em; color: #666; border-bottom: 1px solid #efefef; margin: 0 0 5px; padding: 0 0 3px; } - .ArchetypesRemarksWidget .record .record-header span { + .ArchetypesRemarksWidget .record .record-header span { padding-right:10px; } - .ArchetypesRemarksWidget .record .record-header .record-user { + .ArchetypesRemarksWidget .record .record-header .record-user { font-weight:bold; } From 9a9882d778ad119058c735d8d1bdb08f0f0ae633 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 23 Dec 2019 22:49:10 +0100 Subject: [PATCH 18/41] Show remarks in overlay of worksheets --- bika/lims/browser/js/bika.lims.worksheet.js | 4 +++- bika/lims/browser/js/coffee/bika.lims.worksheet.coffee | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.worksheet.js b/bika/lims/browser/js/bika.lims.worksheet.js index fb4764eb66..dc62a5fa4a 100644 --- a/bika/lims/browser/js/bika.lims.worksheet.js +++ b/bika/lims/browser/js/bika.lims.worksheet.js @@ -881,13 +881,15 @@ el = event.currentTarget; $(el).prepOverlay({ subtype: "ajax", - filter: "h1,span.remarks_history", + filter: "h1,div.ArchetypesRemarksWidget", config: { closeOnClick: true, closeOnEsc: true, onBeforeLoad: function(event) { var overlay; overlay = this.getOverlay(); + $("textarea", overlay).remove(); + $("input", overlay).remove(); return overlay.draggable(); }, onLoad: function(event) { diff --git a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee index 41890ed40e..a6294fbbf7 100644 --- a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee @@ -824,12 +824,16 @@ class window.WorksheetManageResultsView # https://github.com/plone/plone.app.jquerytools/blob/master/plone/app/jquerytools/browser/overlayhelpers.js $(el).prepOverlay subtype: "ajax" - filter: "h1,span.remarks_history" + filter: "h1,div.ArchetypesRemarksWidget" config: closeOnClick: yes closeOnEsc: yes onBeforeLoad: (event) -> overlay = this.getOverlay() + # Remove the textarea and button + $("textarea", overlay).remove() + $("input", overlay).remove() + # make the overlay draggable overlay.draggable() onLoad: (event) -> $.mask.close() From efbb541a3c821f16a507d0fba3a0847ff34e643d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Tue, 24 Dec 2019 00:18:42 +0100 Subject: [PATCH 19/41] Do not display record-header when no user_id This makes the record-header to not be displayed for legacy entries --- bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index bab175dd1f..41ccf71eab 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -39,7 +39,7 @@
-
+
From 3248bf4a8bb5a0f71dc57937b241e8c5c4ddbfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Tue, 24 Dec 2019 00:35:47 +0100 Subject: [PATCH 20/41] ulocalized creation time --- bika/lims/browser/fields/remarksfield.py | 9 +++++++++ bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index e41e0ef40a..b0a5cf19ba 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -32,6 +32,7 @@ from bika.lims.browser.widgets import RemarksWidget from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID +from Products.CMFPlone.i18nl10n import ulocalized_time class RemarksHistory(list): @@ -83,6 +84,14 @@ def user_name(self): def created(self): return self.get("created", "") + @property + def created_ulocalized(self): + return ulocalized_time(self.created, + long_format=True, + context=api.get_portal(), + request=api.get_request(), + domain="senaite.core") + @property def content(self): return self.get("content", "") diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 41ccf71eab..0711f50ba8 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -42,7 +42,7 @@
- +
From 30e43ebdcb65ad9b3b724608acdf68cd2bdc2233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 8 Jan 2020 22:27:48 +0100 Subject: [PATCH 21/41] Fix AttributeError: 'super' object has no attribute '__eq__' --- bika/lims/browser/fields/remarksfield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index b0a5cf19ba..cfc7eb10ed 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -53,7 +53,7 @@ def __str__(self): def __eq__(self, y): if isinstance(y, six.string_types): return str(self) == y - return super(list, self).__eq__(y) + return super(RemarksHistory, self).__eq__(y) class RemarksHistoryRecord(dict): From 4a65eb2a32cbd1f97a20fb881750accbf4a910cd Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Thu, 9 Jan 2020 14:29:54 +0100 Subject: [PATCH 22/41] Whitespace only --- bika/lims/browser/fields/remarksfield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index cfc7eb10ed..4994cf7b42 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -200,7 +200,7 @@ def get_history(self, instance): # Backwards compatibility with legacy from < v1.3.3 if isinstance(remarks, six.string_types): remark = RemarksHistoryRecord(content=remarks.strip()) - remarks = RemarksHistory([remark,]) + remarks = RemarksHistory([remark, ]) return remarks From b1d1eab3dd4b3ebecb772a6913c10de2bc0a8492 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Thu, 9 Jan 2020 14:30:50 +0100 Subject: [PATCH 23/41] imports sorted --- bika/lims/browser/fields/remarksfield.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 4994cf7b42..9c2c579577 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -20,19 +20,19 @@ import markdown import six -from AccessControl import ClassSecurityInfo -from DateTime import DateTime -from Products.Archetypes.Field import ObjectField -from Products.Archetypes.Registry import registerField -from Products.Archetypes.event import ObjectEditedEvent -from zope import event -from zope.interface import implements +from AccessControl import ClassSecurityInfo from bika.lims import api from bika.lims.browser.widgets import RemarksWidget from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID +from DateTime import DateTime +from Products.Archetypes.event import ObjectEditedEvent +from Products.Archetypes.Field import ObjectField +from Products.Archetypes.Registry import registerField from Products.CMFPlone.i18nl10n import ulocalized_time +from zope import event +from zope.interface import implements class RemarksHistory(list): From a54af26687ba687a3563a5ce9db6483c66fb1b4c Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Thu, 9 Jan 2020 14:47:36 +0100 Subject: [PATCH 24/41] Avoid empty remarks submission --- .../coffee/bika_widgets/remarkswidget.coffee | 17 ++++++++++++++++- .../skins/bika/bika_widgets/remarkswidget.js | 18 +++++++++++++++++- .../skins/bika/bika_widgets/remarkswidget.pt | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee index 8f3edb87a0..0fef010c78 100644 --- a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee +++ b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee @@ -23,6 +23,7 @@ class window.RemarksWidgetView console.debug "RemarksWidgetView::bind_eventhandler" $("body").on "click", "input.saveRemarks", @on_remarks_submit + $("body").on "keyup", "textarea[name='Remarks']", @on_remarks_change # dev only window.rem = @ @@ -164,9 +165,23 @@ class window.RemarksWidgetView ### EVENT HANDLERS ### + on_remarks_change: (event) => + ### + * Eventhandler for RemarksWidget's textarea changes + * + ### + console.debug "°°° RemarksWidgetView::on_remarks_change °°°" + me = this + + el = event.target + btn = el.parentElement.querySelector("input.saveRemarks") + # Enable the button + btn.disabled = false + + on_remarks_submit: (event) => ### - * Eventhandler for RemarksWidget"s "Save Remarks" button + * Eventhandler for RemarksWidget's "Save Remarks" button * ### console.debug "°°° RemarksWidgetView::on_remarks_submit °°°" diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.js b/bika/lims/skins/bika/bika_widgets/remarkswidget.js index adf1babb82..16db416bbe 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.js +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.js @@ -11,6 +11,7 @@ this.get_portal_url = bind(this.get_portal_url, this); this.ajax_submit = bind(this.ajax_submit, this); this.on_remarks_submit = bind(this.on_remarks_submit, this); + this.on_remarks_change = bind(this.on_remarks_change, this); this.post_remarks = bind(this.post_remarks, this); this.fetch_remarks = bind(this.fetch_remarks, this); this.set_remarks = bind(this.set_remarks, this); @@ -41,6 +42,7 @@ */ console.debug("RemarksWidgetView::bind_eventhandler"); $("body").on("click", "input.saveRemarks", this.on_remarks_submit); + $("body").on("keyup", "textarea[name='Remarks']", this.on_remarks_change); return window.rem = this; }; @@ -214,10 +216,24 @@ /* EVENT HANDLERS */ + RemarksWidgetView.prototype.on_remarks_change = function(event) { + + /* + * Eventhandler for RemarksWidget's textarea changes + * + */ + var btn, el, me; + console.debug("°°° RemarksWidgetView::on_remarks_change °°°"); + me = this; + el = event.target; + btn = el.parentElement.querySelector("input.saveRemarks"); + return btn.disabled = false; + }; + RemarksWidgetView.prototype.on_remarks_submit = function(event) { /* - * Eventhandler for RemarksWidget"s "Save Remarks" button + * Eventhandler for RemarksWidget's "Save Remarks" button * */ var widget; diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 0711f50ba8..1a946948bd 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -30,6 +30,7 @@
From 33bbc9822be5f375fee39d8ac43891e5d759821c Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Thu, 9 Jan 2020 16:10:35 +0100 Subject: [PATCH 25/41] Parse legacy remarks --- bika/lims/browser/fields/remarksfield.py | 58 ++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 9c2c579577..cd4b463190 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -18,6 +18,8 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. +import re + import markdown import six @@ -146,6 +148,7 @@ def set(self, instance, value, **kwargs): """Adds the value to the existing text stored in the field, along with a small divider showing username and date of this entry. """ + if not value: return @@ -185,9 +188,13 @@ def to_history_record(self, value): """ user = api.get_current_user() contact = api.get_user_contact(user) - full_name = contact and contact.getFullname() or "" + fullname = contact and contact.getFullname() or "" + if not contact: + # get the fullname from the user properties + props = api.get_user_properties(user) + fullname = props.get("fullname", "") return RemarksHistoryRecord(user_id=user.id, - user_name=full_name, + user_name=fullname, content=value.strip()) def get_history(self, instance): @@ -199,11 +206,54 @@ def get_history(self, instance): # Backwards compatibility with legacy from < v1.3.3 if isinstance(remarks, six.string_types): - remark = RemarksHistoryRecord(content=remarks.strip()) - remarks = RemarksHistory([remark, ]) + parsed_remarks = self._parse_legacy_remarks(remarks) + if parsed_remarks is None: + remark = RemarksHistoryRecord(content=remarks.strip()) + remarks = RemarksHistory([remark, ]) + else: + remarks = map(lambda r: RemarksHistoryRecord(r), + parsed_remarks) return remarks + def _parse_legacy_remarks(self, remarks): + """Parse legacy remarks + """ + records = [] + groups = re.findall(r"=== (.*) \((.*)\)\n(.*)", remarks) + + for group in groups: + # cancel the whole parsing + if len(group) != 3: + return None + + created, userid, content = group + + # try to get the full name of the user id + fullname = self._get_fullname_from_user_id(userid) + + # append the record + records.append({ + "created": created, + "user_id": userid, + "user_name": fullname, + "content": content, + }) + + return records + + def _get_fullname_from_user_id(self, userid, default=""): + """Try the fullname of the user + """ + fullname = default + user = api.get_user(userid) + if user: + props = api.get_user_properties(user) + fullname = props.get("fullname", fullname) + contact = api.get_user_contact(user) + fullname = contact and contact.getFullname() or fullname + return fullname + def get(self, instance, **kwargs): """Returns a RemarksHistory object """ From 2dc8a373e5f865335336cd0474dc2901dc77d7be Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 10 Jan 2020 19:44:23 +0100 Subject: [PATCH 26/41] Sort remarks with the newest first --- bika/lims/browser/fields/remarksfield.py | 11 +++++------ .../js/coffee/bika_widgets/remarkswidget.coffee | 5 +++-- bika/lims/skins/bika/bika_widgets/remarkswidget.js | 7 +++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index cd4b463190..9c769ca991 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -164,7 +164,7 @@ def set(self, instance, value, **kwargs): elif isinstance(value, RemarksHistoryRecord): # This is a record, append to the history history = self.get_history(instance) - history.append(value) + history.insert(0, value) elif isinstance(value, six.string_types): # Create a new history record @@ -172,7 +172,7 @@ def set(self, instance, value, **kwargs): # Append the new record to the history history = self.get_history(instance) - history.append(record) + history.insert(0, record) else: raise ValueError("Type not supported: {}".format(type(value))) @@ -206,13 +206,12 @@ def get_history(self, instance): # Backwards compatibility with legacy from < v1.3.3 if isinstance(remarks, six.string_types): - parsed_remarks = self._parse_legacy_remarks(remarks) - if parsed_remarks is None: + parsed = self._parse_legacy_remarks(remarks) + if parsed is None: remark = RemarksHistoryRecord(content=remarks.strip()) remarks = RemarksHistory([remark, ]) else: - remarks = map(lambda r: RemarksHistoryRecord(r), - parsed_remarks) + remarks = map(lambda r: RemarksHistoryRecord(r), parsed) return remarks diff --git a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee index 0fef010c78..eafaa852d9 100644 --- a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee +++ b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee @@ -63,10 +63,11 @@ class window.RemarksWidgetView ### * Clear and update the widget's History with the provided value. ### + return if value.length < 1 widget = @get_remarks_widget(uid) return if widget is null el = widget.find('.remarks_history') - val = value.pop() + val = value[0] record_header = $("
") record_header.append $(""+val["user_id"]+"") record_header.append $(""+val["user_name"]+"") @@ -76,7 +77,7 @@ class window.RemarksWidgetView record = $("
") record.append record_header record.append record_content - el.append record + el.prepend record clear_remarks_textarea: (uid) => ### diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.js b/bika/lims/skins/bika/bika_widgets/remarkswidget.js index 16db416bbe..2be97c8935 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.js +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.js @@ -94,12 +94,15 @@ * Clear and update the widget's History with the provided value. */ var el, record, record_content, record_header, val, widget; + if (value.length < 1) { + return; + } widget = this.get_remarks_widget(uid); if (widget === null) { return; } el = widget.find('.remarks_history'); - val = value.pop(); + val = value[0]; record_header = $("
"); record_header.append($("" + val["user_id"] + "")); record_header.append($("" + val["user_name"] + "")); @@ -109,7 +112,7 @@ record = $("
"); record.append(record_header); record.append(record_content); - return el.append(record); + return el.prepend(record); }; RemarksWidgetView.prototype.clear_remarks_textarea = function(uid) { From c355cd4d58dfc04677689698fb85e6ee2ad55610 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 10 Jan 2020 21:10:09 +0100 Subject: [PATCH 27/41] Emit Event for new remarks --- bika/lims/browser/fields/remarksfield.py | 4 ++++ bika/lims/events/__init__.py | 4 ++++ bika/lims/events/remarks.py | 17 +++++++++++++++++ bika/lims/subscribers/configure.zcml | 5 +++++ bika/lims/subscribers/remarks.py | 12 ++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 bika/lims/events/__init__.py create mode 100644 bika/lims/events/remarks.py create mode 100644 bika/lims/subscribers/remarks.py diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 9c769ca991..8130a5fdc5 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -26,6 +26,7 @@ from AccessControl import ClassSecurityInfo from bika.lims import api from bika.lims.browser.widgets import RemarksWidget +from bika.lims.events import RemarksAddedEvent from bika.lims.interfaces import IRemarksField from bika.lims.utils import tmpID from DateTime import DateTime @@ -183,6 +184,9 @@ def set(self, instance, value, **kwargs): # notify object edited event event.notify(ObjectEditedEvent(instance)) + # notify new remarks + event.notify(RemarksAddedEvent(instance, history)) + def to_history_record(self, value): """Transforms the value to an history record """ diff --git a/bika/lims/events/__init__.py b/bika/lims/events/__init__.py new file mode 100644 index 0000000000..1ae2978f09 --- /dev/null +++ b/bika/lims/events/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from remarks import IRemarksAddedEvent # noqa +from remarks import RemarksAddedEvent # noqa diff --git a/bika/lims/events/remarks.py b/bika/lims/events/remarks.py new file mode 100644 index 0000000000..1a0688d9bb --- /dev/null +++ b/bika/lims/events/remarks.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from zope.interface import Interface +from zope.interface import implements + + +class IRemarksAddedEvent(Interface): + """Remarks Added Event + """ + + +class RemarksAddedEvent(object): + implements(IRemarksAddedEvent) + + def __init__(self, context, history): + self.context = context + self.history = history diff --git a/bika/lims/subscribers/configure.zcml b/bika/lims/subscribers/configure.zcml index e39cb3bc71..16bf26c8c4 100644 --- a/bika/lims/subscribers/configure.zcml +++ b/bika/lims/subscribers/configure.zcml @@ -3,6 +3,11 @@ xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" i18n_domain="senaite.core"> + + Date: Fri, 10 Jan 2020 21:33:10 +0100 Subject: [PATCH 28/41] Check for valid input before activating the button --- .../browser/js/coffee/bika_widgets/remarkswidget.coffee | 5 ++--- bika/lims/skins/bika/bika_widgets/remarkswidget.js | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee index eafaa852d9..10ccbaaf6e 100644 --- a/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee +++ b/bika/lims/browser/js/coffee/bika_widgets/remarkswidget.coffee @@ -172,10 +172,9 @@ class window.RemarksWidgetView * ### console.debug "°°° RemarksWidgetView::on_remarks_change °°°" - me = this - el = event.target - btn = el.parentElement.querySelector("input.saveRemarks") + return unless el.value + btn = el.parentElement.querySelector("input.saveRemarks") # Enable the button btn.disabled = false diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.js b/bika/lims/skins/bika/bika_widgets/remarkswidget.js index 2be97c8935..b300edb19a 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.js +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.js @@ -225,10 +225,12 @@ * Eventhandler for RemarksWidget's textarea changes * */ - var btn, el, me; + var btn, el; console.debug("°°° RemarksWidgetView::on_remarks_change °°°"); - me = this; el = event.target; + if (!el.value) { + return; + } btn = el.parentElement.querySelector("input.saveRemarks"); return btn.disabled = false; }; From cd5b73486080a5680d6cbe5fa0ddb12a90119771 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 10 Jan 2020 22:09:36 +0100 Subject: [PATCH 29/41] Changed CSS Styles for Readonly Remarks Widget --- .../analysisrequest_manage_results.pt | 2 +- .../templates/analysisrequest_view.pt | 2 +- .../browser/worksheet/templates/results.pt | 2 +- .../skins/bika/bika_widgets/remarkswidget.css | 30 +++++++++---------- .../skins/bika/bika_widgets/remarkswidget.pt | 21 +++++++++++-- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt index 5c57ecf913..b71bf6a148 100644 --- a/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt +++ b/bika/lims/browser/analysisrequest/templates/analysisrequest_manage_results.pt @@ -72,7 +72,7 @@ https://github.com/senaite/senaite.core/pull/920 -->
- textarea + + + +
+ +
+
+ + + +
+
+
+
+
From 93b2af7080a9f5287a8cd1cbef6c7088619b0439 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 10 Jan 2020 22:10:02 +0100 Subject: [PATCH 30/41] Changed variable name --- bika/lims/browser/fields/remarksfield.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 8130a5fdc5..d1eaa745ba 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -210,12 +210,13 @@ def get_history(self, instance): # Backwards compatibility with legacy from < v1.3.3 if isinstance(remarks, six.string_types): - parsed = self._parse_legacy_remarks(remarks) - if parsed is None: + parsed_remarks = self._parse_legacy_remarks(remarks) + if parsed_remarks is None: remark = RemarksHistoryRecord(content=remarks.strip()) remarks = RemarksHistory([remark, ]) else: - remarks = map(lambda r: RemarksHistoryRecord(r), parsed) + remarks = RemarksHistory( + map(lambda r: RemarksHistoryRecord(r), parsed_remarks)) return remarks From c5af142c25b93cb636f2a9345c4262e70ae124ec Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 10 Jan 2020 22:25:52 +0100 Subject: [PATCH 31/41] Fixed overlay for worksheets --- bika/lims/browser/js/bika.lims.worksheet.js | 4 +++- bika/lims/browser/js/coffee/bika.lims.worksheet.coffee | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.worksheet.js b/bika/lims/browser/js/bika.lims.worksheet.js index dc62a5fa4a..64bcd16cba 100644 --- a/bika/lims/browser/js/bika.lims.worksheet.js +++ b/bika/lims/browser/js/bika.lims.worksheet.js @@ -881,13 +881,15 @@ el = event.currentTarget; $(el).prepOverlay({ subtype: "ajax", - filter: "h1,div.ArchetypesRemarksWidget", + filter: "h1,div.remarks-widget", config: { closeOnClick: true, closeOnEsc: true, onBeforeLoad: function(event) { var overlay; overlay = this.getOverlay(); + $("div.pb-ajax>div", overlay).addClass("container"); + $("h3", overlay).remove(); $("textarea", overlay).remove(); $("input", overlay).remove(); return overlay.draggable(); diff --git a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee index a6294fbbf7..88b13cf95d 100644 --- a/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.worksheet.coffee @@ -824,13 +824,15 @@ class window.WorksheetManageResultsView # https://github.com/plone/plone.app.jquerytools/blob/master/plone/app/jquerytools/browser/overlayhelpers.js $(el).prepOverlay subtype: "ajax" - filter: "h1,div.ArchetypesRemarksWidget" + filter: "h1,div.remarks-widget" config: closeOnClick: yes closeOnEsc: yes onBeforeLoad: (event) -> overlay = this.getOverlay() - # Remove the textarea and button + $("div.pb-ajax>div", overlay).addClass("container") + # Remove editable elements + $("h3", overlay).remove() $("textarea", overlay).remove() $("input", overlay).remove() # make the overlay draggable From 3504e598aedcd7225d728ffc70b2afb10bc85ad3 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 10:08:17 +0100 Subject: [PATCH 32/41] Code restructuring --- bika/lims/browser/fields/remarksfield.py | 31 ++++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index d1eaa745ba..8166485f77 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -136,8 +136,6 @@ class RemarksField(ObjectField): implements(IRemarksField) security = ClassSecurityInfo() - security.declarePrivate('set') - @property def searchable(self): """Returns False, preventing this field to be searchable by AT's @@ -145,6 +143,7 @@ def searchable(self): """ return False + @security.private def set(self, instance, value, **kwargs): """Adds the value to the existing text stored in the field, along with a small divider showing username and date of this entry. @@ -187,6 +186,20 @@ def set(self, instance, value, **kwargs): # notify new remarks event.notify(RemarksAddedEvent(instance, history)) + def get(self, instance, **kwargs): + """Returns a RemarksHistory object + """ + return self.get_history(instance) + + def getRaw(self, instance, **kwargs): + """Returns raw field value (possible wrapped in BaseUnit) + """ + value = ObjectField.get(self, instance, **kwargs) + # getattr(instance, "Remarks") returns a BaseUnit + if callable(value): + value = value() + return value + def to_history_record(self, value): """Transforms the value to an history record """ @@ -258,19 +271,5 @@ def _get_fullname_from_user_id(self, userid, default=""): fullname = contact and contact.getFullname() or fullname return fullname - def get(self, instance, **kwargs): - """Returns a RemarksHistory object - """ - return self.get_history(instance) - - def getRaw(self, instance, **kwargs): - """Returns raw field value (possible wrapped in BaseUnit) - """ - value = ObjectField.get(self, instance, **kwargs) - # getattr(instance, "Remarks") returns a BaseUnit - if callable(value): - value = value() - return value - registerField(RemarksField, title='Remarks', description='') From 380c7a27dca9329c5cb37403f5e949485734dff3 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 13:43:48 +0100 Subject: [PATCH 33/41] Code formatting only --- bika/lims/browser/fields/remarksfield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 8166485f77..8bb2274a50 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -272,4 +272,4 @@ def _get_fullname_from_user_id(self, userid, default=""): return fullname -registerField(RemarksField, title='Remarks', description='') +registerField(RemarksField, title="Remarks", description="") From b60915ab3d1c2340d8c9930b98644381761c70db Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 15:29:09 +0100 Subject: [PATCH 34/41] Fix the legacy parsing to handle newlines in the content --- bika/lims/browser/fields/remarksfield.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 8bb2274a50..04e48a42a2 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -237,9 +237,25 @@ def _parse_legacy_remarks(self, remarks): """Parse legacy remarks """ records = [] - groups = re.findall(r"=== (.*) \((.*)\)\n(.*)", remarks) + # split legacy remarks on the "===" delimiter into lines + lines = remarks.split("===") + for line in lines: + # skip empty lines + if line == "": + continue + + # strip leading and trailing whitespaces + line = line.strip() + + # split the line into date, user and content + groups = re.findall(r"(.*) \((.*)\)\n(.*)", line, re.DOTALL) + + # we should have one tuple in the list + if len(groups) != 1: + continue + + group = groups[0] - for group in groups: # cancel the whole parsing if len(group) != 3: return None From ee41b20a611b2b9d77b25416e1a0d1c953d7f97b Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 16:06:14 +0100 Subject: [PATCH 35/41] Comment only --- bika/lims/browser/fields/remarksfield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index 04e48a42a2..d4007440db 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -183,7 +183,7 @@ def set(self, instance, value, **kwargs): # notify object edited event event.notify(ObjectEditedEvent(instance)) - # notify new remarks + # notify new remarks for e.g. later email notification etc. event.notify(RemarksAddedEvent(instance, history)) def get(self, instance, **kwargs): From 6b15d564eb77b4971c62948bfbf0ad1550cb9469 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 16:22:50 +0100 Subject: [PATCH 36/41] Reindex object after remarks set --- bika/lims/browser/fields/remarksfield.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index d4007440db..f06975f7b4 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -180,6 +180,9 @@ def set(self, instance, value, **kwargs): # Store the data ObjectField.set(self, instance, history) + # N.B. ensure updated catalog metadata for the snapshot + instance.reindexObject() + # notify object edited event event.notify(ObjectEditedEvent(instance)) From 916d993227a8a1622ae042d20bf7bba01f7aba83 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 11 Jan 2020 16:50:14 +0100 Subject: [PATCH 37/41] Removed markdown rendering from remarks --- bika/lims/browser/fields/remarksfield.py | 6 ------ bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 4 ++-- setup.py | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/bika/lims/browser/fields/remarksfield.py b/bika/lims/browser/fields/remarksfield.py index f06975f7b4..3f30876073 100644 --- a/bika/lims/browser/fields/remarksfield.py +++ b/bika/lims/browser/fields/remarksfield.py @@ -20,7 +20,6 @@ import re -import markdown import six from AccessControl import ClassSecurityInfo @@ -103,11 +102,6 @@ def content(self): def html_content(self): return api.text_to_html(self.content) - @property - def markdown_content(self): - content = self.content.decode('utf-8') - return markdown.markdown(content) - def __str__(self): """Returns a legacy string format of the Remarks record """ diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index e30b3c1606..61294cc1e7 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -26,7 +26,7 @@
+ tal:content="structure record/html_content">
@@ -63,7 +63,7 @@
+ tal:content="structure record/html_content">
diff --git a/setup.py b/setup.py index 5ace184d03..5f81fa3d65 100644 --- a/setup.py +++ b/setup.py @@ -87,7 +87,6 @@ # Needed for `IPortalCatalogQueueProcessor`, which will be included in # `Products.CMFCore` in Plone 5. Remove after we are on Plone 5! 'collective.indexing', - 'markdown', ], extras_require={ 'test': [ From aa8cfb1334997b5ea75da8cec980547675a8417f Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sun, 12 Jan 2020 07:33:29 +0100 Subject: [PATCH 38/41] Pin Pillow for Scrutinizer --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 5f81fa3d65..7ab50b2d5b 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,8 @@ # Needed for `IPortalCatalogQueueProcessor`, which will be included in # `Products.CMFCore` in Plone 5. Remove after we are on Plone 5! 'collective.indexing', + # https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#622-2020-01-02 + 'Pillow<7.0.0' ], extras_require={ 'test': [ From 1738748da2fc91abd53b3cb190152a785f6b4509 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sun, 12 Jan 2020 07:40:28 +0100 Subject: [PATCH 39/41] Pinned more versions for Scrutinizer --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7ab50b2d5b..e1a8b819d0 100644 --- a/setup.py +++ b/setup.py @@ -87,8 +87,11 @@ # Needed for `IPortalCatalogQueueProcessor`, which will be included in # `Products.CMFCore` in Plone 5. Remove after we are on Plone 5! 'collective.indexing', + # Fix Scrutinizer (remove after we migrated to Python 3) # https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#622-2020-01-02 - 'Pillow<7.0.0' + 'Pillow<7.0.0', + # https://pypi.org/project/more-itertools/ + 'more-itertools<6.0.0' ], extras_require={ 'test': [ From 6a135b57a02c58534bcd76e4661786813ea6848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Tue, 14 Jan 2020 22:53:59 +0100 Subject: [PATCH 40/41] Replace RemarksWidget by TextAreaWidget for setup content types --- bika/lims/content/analysisprofile.py | 42 ++++++++++++-------- bika/lims/content/artemplate.py | 11 ++--- bika/lims/content/instrumentcertification.py | 9 +++-- bika/lims/content/pricelist.py | 27 +++++++------ bika/lims/content/referencesample.py | 11 ++--- bika/lims/content/supplier.py | 26 ++++++------ bika/lims/content/supplyorder.py | 41 +++++++++++-------- 7 files changed, 94 insertions(+), 73 deletions(-) diff --git a/bika/lims/content/analysisprofile.py b/bika/lims/content/analysisprofile.py index d7b1a29123..b27050dcc8 100644 --- a/bika/lims/content/analysisprofile.py +++ b/bika/lims/content/analysisprofile.py @@ -24,26 +24,32 @@ """ from AccessControl import ClassSecurityInfo +from Products.ATExtensions.field import RecordsField +from Products.Archetypes.public import BaseContent +from Products.Archetypes.public import BooleanField +from Products.Archetypes.public import BooleanWidget +from Products.Archetypes.public import ComputedField +from Products.Archetypes.public import ComputedWidget +from Products.Archetypes.public import DecimalWidget +from Products.Archetypes.public import FixedPointField +from Products.Archetypes.public import ReferenceField +from Products.Archetypes.public import Schema +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.CMFCore.utils import getToolByName +from zope.interface import implements + from bika.lims import api -from bika.lims import PMF, bikaMessageFactory as _ -from bika.lims.browser.fields.remarksfield import RemarksField +from bika.lims import bikaMessageFactory as _ from bika.lims.browser.widgets import AnalysisProfileAnalysesWidget -from bika.lims.browser.widgets import RemarksWidget -from bika.lims.browser.widgets import ServicesWidget from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema -from Products.Archetypes.public import * - from bika.lims.content.clientawaremixin import ClientAwareMixin -from bika.lims.interfaces import IAnalysisProfile, IDeactivable -from Products.Archetypes.references import HoldingReference -from Products.ATExtensions.field import RecordsField -from Products.CMFCore.permissions import View, ModifyPortalContent -from Products.CMFCore.utils import getToolByName -from zope.interface import Interface, implements -import sys from bika.lims.interfaces import IAnalysisProfile -from bika.lims.interfaces import IClient +from bika.lims.interfaces import IDeactivable schema = BikaSchema.copy() + Schema(( StringField('ProfileKey', @@ -65,9 +71,11 @@ description = _("The analyses included in this profile, grouped per category"), ) ), - RemarksField('Remarks', - widget=RemarksWidget( - label=_("Remarks") + TextField( + "Remarks", + allowable_content_types=("text/plain",), + widget=TextAreaWidget( + label=_("Remarks"), ) ), # Custom settings for the assigned analysis services diff --git a/bika/lims/content/artemplate.py b/bika/lims/content/artemplate.py index 38239c3d4f..1d5d425de4 100644 --- a/bika/lims/content/artemplate.py +++ b/bika/lims/content/artemplate.py @@ -30,6 +30,8 @@ from Products.Archetypes.public import DisplayList from Products.Archetypes.public import ReferenceField from Products.Archetypes.public import Schema +from Products.Archetypes.public import TextAreaWidget +from Products.Archetypes.public import TextField from Products.Archetypes.public import registerType from Products.Archetypes.references import HoldingReference from Products.CMFCore.utils import getToolByName @@ -37,11 +39,9 @@ from bika.lims import api from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields.remarksfield import RemarksField from bika.lims.browser.widgets import ARTemplateAnalysesWidget from bika.lims.browser.widgets import ARTemplatePartitionsWidget from bika.lims.browser.widgets import ReferenceWidget -from bika.lims.browser.widgets import RemarksWidget from bika.lims.config import PROJECTNAME from bika.lims.content.bikaschema import BikaSchema from bika.lims.content.clientawaremixin import ClientAwareMixin @@ -115,11 +115,12 @@ description=_("Enable sampling workflow for the created sample") ), ), - RemarksField( + TextField( "Remarks", - widget=RemarksWidget( + allowable_content_types=("text/plain",), + widget=TextAreaWidget( label=_("Remarks"), - ), + ) ), RecordsField( "Partitions", diff --git a/bika/lims/content/instrumentcertification.py b/bika/lims/content/instrumentcertification.py index de5824d5bc..f1509c4bf8 100644 --- a/bika/lims/content/instrumentcertification.py +++ b/bika/lims/content/instrumentcertification.py @@ -194,11 +194,12 @@ ) ), - RemarksField( - 'Remarks', - widget=RemarksWidget( + TextField( + "Remarks", + allowable_content_types=("text/plain",), + widget=TextAreaWidget( label=_("Remarks"), - ), + ) ), )) diff --git a/bika/lims/content/pricelist.py b/bika/lims/content/pricelist.py index d11e124cdc..fe1f30b39b 100644 --- a/bika/lims/content/pricelist.py +++ b/bika/lims/content/pricelist.py @@ -19,14 +19,6 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields.remarksfield import RemarksField -from bika.lims.browser.widgets import RemarksWidget -from bika.lims.config import PRICELIST_TYPES -from bika.lims.config import PROJECTNAME -from bika.lims.content.bikaschema import BikaSchema -from bika.lims.interfaces import IDeactivable -from bika.lims.interfaces import IPricelist from DateTime import DateTime from Products.Archetypes.public import BaseFolder from Products.Archetypes.public import BooleanField @@ -36,9 +28,17 @@ from Products.Archetypes.public import Schema from Products.Archetypes.public import SelectionWidget from Products.Archetypes.public import StringField +from Products.Archetypes.public import TextAreaWidget +from Products.Archetypes.public import TextField from Products.Archetypes.public import registerType from zope.interface import implements +from bika.lims import bikaMessageFactory as _ +from bika.lims.config import PRICELIST_TYPES +from bika.lims.config import PROJECTNAME +from bika.lims.content.bikaschema import BikaSchema +from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import IPricelist schema = BikaSchema.copy() + Schema(( @@ -77,13 +77,14 @@ ), ), - RemarksField( + TextField( "Remarks", - widget=RemarksWidget( + allowable_content_types=("text/plain",), + widget=TextAreaWidget( label=_("Remarks"), - ), - )), -) + ) + ), +)) Field = schema["title"] Field.required = 1 diff --git a/bika/lims/content/referencesample.py b/bika/lims/content/referencesample.py index ac6deb14d7..93b120712b 100644 --- a/bika/lims/content/referencesample.py +++ b/bika/lims/content/referencesample.py @@ -100,12 +100,13 @@ label=_("Lot Number"), ), ), - RemarksField( - 'Remarks', - schemata='Description', - widget=RemarksWidget( + TextField( + "Remarks", + allowable_content_types=("text/plain",), + schemata="Description", + widget=TextAreaWidget( label=_("Remarks"), - ), + ) ), DateTimeField('DateSampled', schemata = 'Dates', diff --git a/bika/lims/content/supplier.py b/bika/lims/content/supplier.py index d7079c5bb5..b06a7b59ad 100644 --- a/bika/lims/content/supplier.py +++ b/bika/lims/content/supplier.py @@ -19,26 +19,28 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields.remarksfield import RemarksField -from bika.lims.browser.widgets import RemarksWidget -from bika.lims.config import PROJECTNAME -from bika.lims.content.organisation import Organisation -from bika.lims.interfaces import ISupplier, IDeactivable -from zope.interface import implements -from Products.Archetypes.public import registerType -from Products.Archetypes.public import StringField from Products.Archetypes.public import ManagedSchema +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 zope.interface import implements +from bika.lims import bikaMessageFactory as _ +from bika.lims.config import PROJECTNAME +from bika.lims.content.organisation import Organisation +from bika.lims.interfaces import IDeactivable +from bika.lims.interfaces import ISupplier schema = Organisation.schema.copy() + ManagedSchema(( - RemarksField( + TextField( "Remarks", - widget=RemarksWidget( + allowable_content_types=("text/plain",), + widget=TextAreaWidget( label=_("Remarks"), - ), + ) ), StringField( diff --git a/bika/lims/content/supplyorder.py b/bika/lims/content/supplyorder.py index 732f9feae7..21293bb120 100644 --- a/bika/lims/content/supplyorder.py +++ b/bika/lims/content/supplyorder.py @@ -19,30 +19,36 @@ # Some rights reserved, see README and LICENSE. import sys - -from Products.Archetypes.public import * +from decimal import Decimal from AccessControl import ClassSecurityInfo -from bika.lims import bikaMessageFactory as _ -from bika.lims.browser.fields.remarksfield import RemarksField -from bika.lims.browser.widgets import DateTimeWidget -from bika.lims.browser.widgets import RemarksWidget -from bika.lims.browser.widgets import ReferenceWidget as BikaReferenceWidget -from bika.lims.config import PROJECTNAME -from bika.lims.content.bikaschema import BikaSchema -from bika.lims.interfaces import ISupplyOrder, ICancellable -from bika.lims.utils import t from DateTime import DateTime -from persistent.mapping import PersistentMapping -from decimal import Decimal from Products.Archetypes import atapi +from Products.Archetypes.public import BaseFolder +from Products.Archetypes.public import ComputedField +from Products.Archetypes.public import ComputedWidget +from Products.Archetypes.public import DateTimeField +from Products.Archetypes.public import ReferenceField +from Products.Archetypes.public import Schema +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.references import HoldingReference from Products.CMFCore.permissions import View from Products.CMFPlone.interfaces import IConstrainTypes from Products.CMFPlone.utils import safe_unicode +from persistent.mapping import PersistentMapping from zope.component import getAdapter from zope.interface import implements +from bika.lims import bikaMessageFactory as _ +from bika.lims.browser.widgets import DateTimeWidget +from bika.lims.browser.widgets import ReferenceWidget as BikaReferenceWidget +from bika.lims.config import PROJECTNAME +from bika.lims.content.bikaschema import BikaSchema +from bika.lims.interfaces import ICancellable +from bika.lims.interfaces import ISupplyOrder schema = BikaSchema.copy() + Schema(( ReferenceField( @@ -96,11 +102,12 @@ label=_("Date Dispatched"), ), ), - RemarksField( - 'Remarks', - widget=RemarksWidget( + TextField( + "Remarks", + allowable_content_types=("text/plain",), + widget=TextAreaWidget( label=_("Remarks"), - ), + ) ), ComputedField('ClientUID', expression = 'here.aq_parent.UID()', From b1a31be1cf526e736e7a923366a79539f74fa57d Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Wed, 15 Jan 2020 08:51:35 +0100 Subject: [PATCH 41/41] Make add-button blue --- bika/lims/skins/bika/bika_widgets/remarkswidget.pt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt index 61294cc1e7..8639208510 100644 --- a/bika/lims/skins/bika/bika_widgets/remarkswidget.pt +++ b/bika/lims/skins/bika/bika_widgets/remarkswidget.pt @@ -40,11 +40,9 @@ - +
-