| Total Complexity | 50 |
| Total Lines | 263 |
| Duplicated Lines | 9.89 % |
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like senaite.core.z3cform.widgets.remarks.widget often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # -*- coding: utf-8 -*- |
||
| 2 | # |
||
| 3 | # This file is part of SENAITE.CORE. |
||
| 4 | # |
||
| 5 | # SENAITE.CORE is free software: you can redistribute it and/or modify it under |
||
| 6 | # the terms of the GNU General Public License as published by the Free Software |
||
| 7 | # Foundation, version 2. |
||
| 8 | # |
||
| 9 | # This program is distributed in the hope that it will be useful, but WITHOUT |
||
| 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
||
| 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
||
| 12 | # details. |
||
| 13 | # |
||
| 14 | # You should have received a copy of the GNU General Public License along with |
||
| 15 | # this program; if not, write to the Free Software Foundation, Inc., 51 |
||
| 16 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||
| 17 | # |
||
| 18 | # Copyright 2018-2025 by it's authors. |
||
| 19 | # Some rights reserved, see README and LICENSE. |
||
| 20 | |||
| 21 | import copy |
||
| 22 | import six |
||
| 23 | |||
| 24 | from Acquisition import aq_inner |
||
| 25 | from Acquisition import aq_parent |
||
| 26 | from Products.CMFPlone.utils import safe_unicode |
||
| 27 | from Products.Five.browser import BrowserView |
||
| 28 | from bika.lims import api |
||
| 29 | from bika.lims.decorators import returns_json |
||
| 30 | from senaite.core.api.dtime import to_localized_time |
||
| 31 | from senaite.core.schema.interfaces import IRemarksField |
||
| 32 | from senaite.core.schema.remarksfield import fill_remark_object |
||
| 33 | from senaite.core.interfaces import ISenaiteFormLayer |
||
| 34 | from senaite.core.p3compat import cmp |
||
| 35 | from senaite.core.permissions import FieldEditRemarks |
||
| 36 | from senaite.core.z3cform.interfaces import IRemarksWidget |
||
| 37 | from z3c.form.browser import widget |
||
| 38 | from z3c.form.converter import BaseDataConverter |
||
| 39 | from z3c.form.interfaces import INPUT_MODE |
||
| 40 | from z3c.form.interfaces import DISPLAY_MODE |
||
| 41 | from z3c.form.interfaces import IAddForm |
||
| 42 | from z3c.form.interfaces import IFieldWidget |
||
| 43 | from z3c.form.interfaces import IWidget |
||
| 44 | from z3c.form.widget import FieldWidget |
||
| 45 | from z3c.form.widget import Widget |
||
| 46 | from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile |
||
| 47 | from zope.component import adapter |
||
| 48 | from zope.interface import implementer |
||
| 49 | |||
| 50 | |||
| 51 | def check_permission_edit_remark(context): |
||
| 52 | """Check is can add remark |
||
| 53 | """ |
||
| 54 | tool = api.get_tool("portal_membership") |
||
| 55 | return tool.checkPermission(FieldEditRemarks, context) |
||
| 56 | |||
| 57 | |||
| 58 | @adapter(IRemarksField, IWidget) |
||
| 59 | class RemarksDataConverter(BaseDataConverter): |
||
| 60 | """Value conversion between field and widget |
||
| 61 | """ |
||
| 62 | def to_list_of_dicts(self, value): |
||
| 63 | if not isinstance(value, list): |
||
| 64 | value = [value] |
||
| 65 | value = filter(None, value) |
||
| 66 | return map(self.to_dict, value) |
||
| 67 | |||
| 68 | def to_dict(self, value): |
||
| 69 | if not isinstance(value, dict): |
||
| 70 | return {} |
||
| 71 | return value |
||
| 72 | |||
| 73 | def toWidgetValue(self, value): |
||
| 74 | """Returns the field value with encoded string |
||
| 75 | """ |
||
| 76 | values = self.to_list_of_dicts(value) |
||
| 77 | values = self.to_utf8(values) |
||
| 78 | values.sort(lambda x, y: cmp(y["created"], x["created"])) |
||
| 79 | return values |
||
| 80 | |||
| 81 | def toFieldValue(self, value): |
||
| 82 | """Converts from widget value to safe_unicode |
||
| 83 | """ |
||
| 84 | if api.is_string(value): |
||
| 85 | value = fill_remark_object(value) |
||
| 86 | values = self.to_list_of_dicts(value) |
||
| 87 | return self.to_safe_unicode(values) |
||
| 88 | |||
| 89 | View Code Duplication | def to_safe_unicode(self, data): |
|
|
|
|||
| 90 | """Converts the data to unicode |
||
| 91 | """ |
||
| 92 | if isinstance(data, unicode): |
||
| 93 | return data |
||
| 94 | if isinstance(data, list): |
||
| 95 | return [self.to_safe_unicode(item) for item in data] |
||
| 96 | if isinstance(data, dict): |
||
| 97 | return { |
||
| 98 | self.to_safe_unicode(key): self.to_safe_unicode(value) |
||
| 99 | for key, value in six.iteritems(data) |
||
| 100 | } |
||
| 101 | return safe_unicode(data) |
||
| 102 | |||
| 103 | View Code Duplication | def to_utf8(self, data): |
|
| 104 | """Encodes the data to utf-8 |
||
| 105 | """ |
||
| 106 | if isinstance(data, unicode): |
||
| 107 | return data.encode("utf-8") |
||
| 108 | if isinstance(data, list): |
||
| 109 | return [self.to_utf8(item) for item in data] |
||
| 110 | if isinstance(data, dict): |
||
| 111 | return { |
||
| 112 | self.to_utf8(key): self.to_utf8(value) |
||
| 113 | for key, value in six.iteritems(data) |
||
| 114 | } |
||
| 115 | return data |
||
| 116 | |||
| 117 | |||
| 118 | @implementer(IRemarksWidget) |
||
| 119 | class RemarksWidget(widget.HTMLFormElement, Widget): |
||
| 120 | """SENAITE Remarks Widget |
||
| 121 | """ |
||
| 122 | klass = u"senaite-remarks-widget" |
||
| 123 | |||
| 124 | def update(self): |
||
| 125 | widget.HTMLFormElement.update(self) |
||
| 126 | Widget.update(self) |
||
| 127 | widget.addFieldClass(self) |
||
| 128 | |||
| 129 | def render(self): |
||
| 130 | if self.is_add_form(): |
||
| 131 | return ViewPageTemplateFile("add.pt")(self) |
||
| 132 | return Widget.render(self) |
||
| 133 | |||
| 134 | def is_add_form(self): |
||
| 135 | """Check if we are in an add form |
||
| 136 | :returns: True if in add form, False if in edit form |
||
| 137 | """ |
||
| 138 | # Check if the parent form implements IAddForm |
||
| 139 | form = getattr(self, "form", None) |
||
| 140 | if form is None: |
||
| 141 | return False |
||
| 142 | is_add = IAddForm.providedBy(form) |
||
| 143 | |||
| 144 | # form maybe includes into group(fieldset) |
||
| 145 | # check that recursively |
||
| 146 | parent = aq_parent(aq_inner(form)) |
||
| 147 | while not is_add and parent is not None: |
||
| 148 | is_add = IAddForm.providedBy(parent) |
||
| 149 | parent = aq_parent(aq_inner(parent)) |
||
| 150 | return is_add |
||
| 151 | |||
| 152 | def localized_time(self, value): |
||
| 153 | return to_localized_time(value, |
||
| 154 | long_format=True, |
||
| 155 | context=self.context, |
||
| 156 | request=self.request) |
||
| 157 | |||
| 158 | def html_content(self, value): |
||
| 159 | return api.text_to_html(value) |
||
| 160 | |||
| 161 | @property |
||
| 162 | def portal(self): |
||
| 163 | """Return the portal object |
||
| 164 | """ |
||
| 165 | return api.get_portal() |
||
| 166 | |||
| 167 | @property |
||
| 168 | def portal_url(self): |
||
| 169 | """Return the portal object URL |
||
| 170 | """ |
||
| 171 | return api.get_url(self.portal) |
||
| 172 | |||
| 173 | @property |
||
| 174 | def can_add_remark(self): |
||
| 175 | """Check is can add remark |
||
| 176 | """ |
||
| 177 | modes = [INPUT_MODE, DISPLAY_MODE] |
||
| 178 | can_edit_field = check_permission_edit_remark(self.context) |
||
| 179 | return self.mode in modes and can_edit_field |
||
| 180 | |||
| 181 | |||
| 182 | @adapter(IRemarksField, ISenaiteFormLayer) |
||
| 183 | @implementer(IFieldWidget) |
||
| 184 | def RemarksWidgetFactory(field, request): |
||
| 185 | """Widget factory for Address Widget |
||
| 186 | """ |
||
| 187 | return FieldWidget(field, RemarksWidget(request)) |
||
| 188 | |||
| 189 | |||
| 190 | class AjaxAddRemark(BrowserView): |
||
| 191 | """Endpoint for the add remark for object |
||
| 192 | """ |
||
| 193 | |||
| 194 | @returns_json |
||
| 195 | def __call__(self): |
||
| 196 | """Returns a json with the result about of added remark for object |
||
| 197 | """ |
||
| 198 | field_name = self.request.form.get("fieldName", None) |
||
| 199 | uid = self.request.form.get("uid", None) |
||
| 200 | value = self.request.form.get("value", None) |
||
| 201 | if not field_name or not uid or not value: |
||
| 202 | return {"success": False} |
||
| 203 | obj = api.get_object_by_uid(uid, None) |
||
| 204 | if not obj: |
||
| 205 | return {"success": False} |
||
| 206 | new_remark = self.add_remark_to_obj(obj, field_name, value) |
||
| 207 | if not new_remark: |
||
| 208 | return {"success": False} |
||
| 209 | return {"success": True} |
||
| 210 | |||
| 211 | def add_remark_to_obj(self, obj, field_name, value): |
||
| 212 | """Add new remark to `obj` by `field_name` with `value` |
||
| 213 | """ |
||
| 214 | fields = api.get_fields(obj) |
||
| 215 | if field_name not in fields: |
||
| 216 | return False |
||
| 217 | field = fields.get(field_name) |
||
| 218 | if not IRemarksField.providedBy(field): |
||
| 219 | return False |
||
| 220 | if not check_permission_edit_remark(obj): |
||
| 221 | self.request.response.setStatus(403) |
||
| 222 | return False |
||
| 223 | field.add(obj, value) |
||
| 224 | return True |
||
| 225 | |||
| 226 | |||
| 227 | class AjaxFetchRemarks(BrowserView): |
||
| 228 | """Endpoint for fetching remarks for object |
||
| 229 | """ |
||
| 230 | @returns_json |
||
| 231 | def __call__(self): |
||
| 232 | uid = self.request.form.get("uid", None) |
||
| 233 | field_name = self.request.form.get("fieldName", None) |
||
| 234 | if not uid or not field_name: |
||
| 235 | return {"success": False} |
||
| 236 | obj = api.get_object_by_uid(uid, None) |
||
| 237 | if not obj: |
||
| 238 | return {"success": False} |
||
| 239 | |||
| 240 | value = self.get_remarks_by_field(obj, field_name) |
||
| 241 | if not value: |
||
| 242 | return {"success": False} |
||
| 243 | remarks = copy.deepcopy(value) |
||
| 244 | remarks.sort(lambda x, y: cmp(y["created"], x["created"])) |
||
| 245 | for r in remarks: |
||
| 246 | r["created"] = to_localized_time(r["created"], |
||
| 247 | long_format=True, |
||
| 248 | context=self.context, |
||
| 249 | request=self.request) |
||
| 250 | return { |
||
| 251 | "success": True, |
||
| 252 | "remarks": remarks, |
||
| 253 | } |
||
| 254 | |||
| 255 | def get_remarks_by_field(self, obj, field_name): |
||
| 256 | fields = api.get_fields(obj) |
||
| 257 | if field_name not in fields: |
||
| 258 | return False |
||
| 259 | field = fields.get(field_name) |
||
| 260 | if not IRemarksField.providedBy(field): |
||
| 261 | return False |
||
| 262 | return field.get(obj) |
||
| 263 |