| Total Complexity | 53 |
| Total Lines | 551 |
| Duplicated Lines | 7.44 % |
| 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.content.analysisprofile 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-2024 by it's authors. |
||
| 19 | # Some rights reserved, see README and LICENSE. |
||
| 20 | |||
| 21 | from AccessControl import ClassSecurityInfo |
||
| 22 | from bika.lims import api |
||
| 23 | from bika.lims import senaiteMessageFactory as _ |
||
| 24 | from bika.lims.interfaces import IDeactivable |
||
| 25 | from plone.autoform import directives |
||
| 26 | from plone.supermodel import model |
||
| 27 | from Products.CMFCore import permissions |
||
| 28 | from senaite.core.catalog import SETUP_CATALOG |
||
| 29 | from senaite.core.content.base import Container |
||
| 30 | from senaite.core.content.mixins import ClientAwareMixin |
||
| 31 | from senaite.core.interfaces import IAnalysisProfile |
||
| 32 | from senaite.core.schema import UIDReferenceField |
||
| 33 | from senaite.core.schema.fields import DataGridRow |
||
| 34 | from senaite.core.z3cform.widgets.listing.widget import ListingWidgetFactory |
||
| 35 | from senaite.core.z3cform.widgets.uidreference import UIDReferenceWidgetFactory |
||
| 36 | from zope import schema |
||
| 37 | from zope.interface import Interface |
||
| 38 | from zope.interface import Invalid |
||
| 39 | from zope.interface import implementer |
||
| 40 | from zope.interface import invariant |
||
| 41 | |||
| 42 | |||
| 43 | class IAnalysisProfileRecord(Interface): |
||
| 44 | """Record schema for selected services |
||
| 45 | """ |
||
| 46 | uid = schema.TextLine(title=u"Profile UID") |
||
| 47 | hidden = schema.Bool(title=u"Hidden") |
||
| 48 | |||
| 49 | |||
| 50 | class IAnalysisProfileSchema(model.Schema): |
||
| 51 | """Schema interface |
||
| 52 | """ |
||
| 53 | |||
| 54 | model.fieldset( |
||
| 55 | "analyses", |
||
| 56 | label=_(u"Analyses"), |
||
| 57 | fields=[ |
||
| 58 | "services", |
||
| 59 | ] |
||
| 60 | ) |
||
| 61 | |||
| 62 | model.fieldset( |
||
| 63 | "accounting", |
||
| 64 | label=_(u"Accounting"), |
||
| 65 | fields=[ |
||
| 66 | "commercial_id", |
||
| 67 | "use_analysis_profile_price", |
||
| 68 | "analysis_profile_price", |
||
| 69 | "analysis_profile_vat", |
||
| 70 | ] |
||
| 71 | ) |
||
| 72 | |||
| 73 | title = schema.TextLine( |
||
| 74 | title=_( |
||
| 75 | u"title_analysisprofile_title", |
||
| 76 | default=u"Name" |
||
| 77 | ), |
||
| 78 | required=True, |
||
| 79 | ) |
||
| 80 | |||
| 81 | description = schema.Text( |
||
| 82 | title=_( |
||
| 83 | u"title_analysisprofile_description", |
||
| 84 | default=u"Description" |
||
| 85 | ), |
||
| 86 | required=False, |
||
| 87 | ) |
||
| 88 | |||
| 89 | profile_key = schema.TextLine( |
||
| 90 | title=_( |
||
| 91 | u"title_analysisprofile_profile_key", |
||
| 92 | default=u"Profile Keyword" |
||
| 93 | ), |
||
| 94 | description=_( |
||
| 95 | u"description_analysisprofile_profile_key", |
||
| 96 | default=u"Please provide a unique profile keyword" |
||
| 97 | ), |
||
| 98 | required=False, |
||
| 99 | default=u"" |
||
| 100 | ) |
||
| 101 | |||
| 102 | directives.widget("services", |
||
| 103 | ListingWidgetFactory, |
||
| 104 | listing_view="analysisprofile_services_widget") |
||
| 105 | services = schema.List( |
||
| 106 | title=_( |
||
| 107 | u"title_analysisprofile_services", |
||
| 108 | default=u"Profile Analyses" |
||
| 109 | ), |
||
| 110 | description=_( |
||
| 111 | u"description_analysisprofile_services", |
||
| 112 | default=u"Select the included analyses for this profile" |
||
| 113 | ), |
||
| 114 | value_type=DataGridRow(schema=IAnalysisProfileRecord), |
||
| 115 | default=[], |
||
| 116 | required=True, |
||
| 117 | ) |
||
| 118 | |||
| 119 | # Commecrial ID |
||
| 120 | commercial_id = schema.TextLine( |
||
| 121 | title=_( |
||
| 122 | u"title_analysisprofile_commercial_id", |
||
| 123 | default=u"Commercial ID" |
||
| 124 | ), |
||
| 125 | description=_( |
||
| 126 | u"description_analysisprofile_commercial_id", |
||
| 127 | default=u"Commercial ID used for accounting" |
||
| 128 | ), |
||
| 129 | required=False, |
||
| 130 | ) |
||
| 131 | |||
| 132 | use_analysis_profile_price = schema.Bool( |
||
| 133 | title=_( |
||
| 134 | u"title_analysisprofile_use_profile_price", |
||
| 135 | default=u"Use analysis profile price" |
||
| 136 | ), |
||
| 137 | description=_( |
||
| 138 | u"description_analysisprofile_use_profile_price", |
||
| 139 | default=u"Use profile price instead of single analyses prices" |
||
| 140 | ), |
||
| 141 | required=False, |
||
| 142 | ) |
||
| 143 | |||
| 144 | analysis_profile_price = schema.Decimal( |
||
| 145 | title=_( |
||
| 146 | u"title_analysisprofile_profile_price", |
||
| 147 | default=u"Price (excluding VAT)" |
||
| 148 | ), |
||
| 149 | description=_( |
||
| 150 | u"description_analysisprofile_profile_price", |
||
| 151 | default=u"Please provide the price excluding VAT" |
||
| 152 | ), |
||
| 153 | required=False, |
||
| 154 | ) |
||
| 155 | |||
| 156 | analysis_profile_vat = schema.Decimal( |
||
| 157 | title=_( |
||
| 158 | u"title_analysisprofile_profile_vat", |
||
| 159 | default=u"VAT %" |
||
| 160 | ), |
||
| 161 | description=_( |
||
| 162 | u"description_analysisprofile_profile_vat", |
||
| 163 | default=u"Please provide the VAT in percent that is added to the " |
||
| 164 | u"profile price" |
||
| 165 | ), |
||
| 166 | required=False, |
||
| 167 | ) |
||
| 168 | |||
| 169 | directives.widget( |
||
| 170 | "sample_types", |
||
| 171 | UIDReferenceWidgetFactory, |
||
| 172 | catalog=SETUP_CATALOG, |
||
| 173 | query={ |
||
| 174 | "is_active": True, |
||
| 175 | "sort_on": "title", |
||
| 176 | "sort_order": "ascending", |
||
| 177 | }, |
||
| 178 | ) |
||
| 179 | sample_types = UIDReferenceField( |
||
| 180 | title=_( |
||
| 181 | u"label_analysisprofile_sampletypes", |
||
| 182 | default=u"Sample types" |
||
| 183 | ), |
||
| 184 | description=_( |
||
| 185 | u"description_analysisprofile_sampletypes", |
||
| 186 | default=u"Sample types for which this analysis profile is " |
||
| 187 | u"supported. This profile won't be available for " |
||
| 188 | u"selection in sample creation and edit forms unless the " |
||
| 189 | u"selected sample type is one of these. If no sample type " |
||
| 190 | u"is set here, this profile will always be available for " |
||
| 191 | u"selection, regardless of the sample type of the sample." |
||
| 192 | ), |
||
| 193 | allowed_types=("SampleType", ), |
||
| 194 | multi_valued=True, |
||
| 195 | required=False, |
||
| 196 | ) |
||
| 197 | |||
| 198 | @invariant |
||
| 199 | def validate_profile_key(data): |
||
| 200 | """Checks if the profile keyword is unique |
||
| 201 | """ |
||
| 202 | profile_key = data.profile_key |
||
| 203 | if not profile_key: |
||
| 204 | # no further checks required |
||
| 205 | return |
||
| 206 | context = getattr(data, "__context__", None) |
||
| 207 | if context and context.profile_key == profile_key: |
||
| 208 | # nothing changed |
||
| 209 | return |
||
| 210 | query = { |
||
| 211 | "portal_type": "AnalysisProfile", |
||
| 212 | "profile_key": profile_key, |
||
| 213 | } |
||
| 214 | results = api.search(query, catalog=SETUP_CATALOG) |
||
| 215 | if len(results) > 0: |
||
| 216 | raise Invalid(_("Profile keyword must be unique")) |
||
| 217 | |||
| 218 | |||
| 219 | @implementer(IAnalysisProfile, IAnalysisProfileSchema, IDeactivable) |
||
| 220 | class AnalysisProfile(Container, ClientAwareMixin): |
||
| 221 | """AnalysisProfile |
||
| 222 | """ |
||
| 223 | # Catalogs where this type will be catalogued |
||
| 224 | _catalogs = [SETUP_CATALOG] |
||
| 225 | |||
| 226 | security = ClassSecurityInfo() |
||
| 227 | |||
| 228 | @security.protected(permissions.View) |
||
| 229 | def getProfileKey(self): |
||
| 230 | accessor = self.accessor("profile_key") |
||
| 231 | value = accessor(self) or "" |
||
| 232 | return api.to_utf8(value) |
||
| 233 | |||
| 234 | @security.protected(permissions.ModifyPortalContent) |
||
| 235 | def setProfileKey(self, value): |
||
| 236 | mutator = self.mutator("profile_key") |
||
| 237 | mutator(self, api.safe_unicode(value)) |
||
| 238 | |||
| 239 | # BBB: AT schema field property |
||
| 240 | ProfileKey = property(getProfileKey, setProfileKey) |
||
| 241 | |||
| 242 | @security.protected(permissions.View) |
||
| 243 | def getRawServices(self): |
||
| 244 | """Return the raw value of the services field |
||
| 245 | |||
| 246 | >>> self.getRawServices() |
||
| 247 | [{'uid': '...', 'hidden': False}, {'uid': '...', 'hidden': True}, ...] |
||
| 248 | |||
| 249 | :returns: List of dictionaries containing `uid` and `hidden` |
||
| 250 | """ |
||
| 251 | accessor = self.accessor("services") |
||
| 252 | return accessor(self) or [] |
||
| 253 | |||
| 254 | @security.protected(permissions.View) |
||
| 255 | def getServices(self, active_only=True): |
||
| 256 | """Returns a list of service objects |
||
| 257 | |||
| 258 | >>> self.getServices() |
||
| 259 | [<AnalysisService at ...>, <AnalysisService at ...>, ...] |
||
| 260 | |||
| 261 | :returns: List of analysis service objects |
||
| 262 | """ |
||
| 263 | services = map(api.get_object, self.getRawServiceUIDs()) |
||
| 264 | if active_only: |
||
| 265 | # filter out inactive services |
||
| 266 | services = filter(api.is_active, services) |
||
| 267 | return list(services) |
||
| 268 | |||
| 269 | @security.protected(permissions.ModifyPortalContent) |
||
| 270 | def setServices(self, value, keep_inactive=True): |
||
| 271 | """Set services for the profile |
||
| 272 | |||
| 273 | This method accepts either a list of analysis service objects, a list |
||
| 274 | of analysis service UIDs or a list of analysis profile service records |
||
| 275 | containing the keys `uid` and `hidden`: |
||
| 276 | |||
| 277 | >>> self.setServices([<AnalysisService at ...>, ...]) |
||
| 278 | >>> self.setServices(['353e1d9bd45d45dbabc837114a9c41e6', '...', ...]) |
||
| 279 | >>> self.setServices([{'hidden': False, 'uid': '...'}, ...]) |
||
| 280 | |||
| 281 | Raises a TypeError if the value does not match any allowed type. |
||
| 282 | """ |
||
| 283 | if not isinstance(value, list): |
||
| 284 | value = [value] |
||
| 285 | records = [] |
||
| 286 | for v in value: |
||
| 287 | uid = None |
||
| 288 | hidden = False |
||
| 289 | if isinstance(v, dict): |
||
| 290 | uid = v.get("uid") |
||
| 291 | hidden = v.get("hidden", False) |
||
| 292 | elif api.is_object(v): |
||
| 293 | uid = api.get_uid(v) |
||
| 294 | elif api.is_uid(v): |
||
| 295 | uid = v |
||
| 296 | else: |
||
| 297 | raise TypeError( |
||
| 298 | "Expected object, uid or record, got %r" % type(v)) |
||
| 299 | records.append({"uid": uid, "hidden": hidden}) |
||
| 300 | |||
| 301 | View Code Duplication | if keep_inactive: |
|
|
|
|||
| 302 | # keep inactive services so they come up again when reactivated |
||
| 303 | uids = [record.get("uid") for record in records] |
||
| 304 | for record in self.getRawServices(): |
||
| 305 | uid = record.get("uid") |
||
| 306 | if uid in uids: |
||
| 307 | continue |
||
| 308 | obj = api.get_object(uid) |
||
| 309 | if not api.is_active(obj): |
||
| 310 | records.append(record) |
||
| 311 | |||
| 312 | mutator = self.mutator("services") |
||
| 313 | mutator(self, records) |
||
| 314 | |||
| 315 | # BBB: AT schema field property |
||
| 316 | Service = Services = property(getServices, setServices) |
||
| 317 | |||
| 318 | @security.protected(permissions.View) |
||
| 319 | def getServiceUIDs(self, active_only=True): |
||
| 320 | """Returns a list of UIDs for the referenced AnalysisService objects |
||
| 321 | |||
| 322 | :param active_only: If True, only UIDs of active services are returned |
||
| 323 | :returns: A list of unique identifiers (UIDs) |
||
| 324 | """ |
||
| 325 | if active_only: |
||
| 326 | services = self.getServices(active_only=active_only) |
||
| 327 | return list(map(api.get_uid, services)) |
||
| 328 | return self.getRawServiceUIDs() |
||
| 329 | |||
| 330 | @security.protected(permissions.View) |
||
| 331 | def getRawServiceUIDs(self): |
||
| 332 | """Returns the list of UIDs stored as raw data in the 'Services' field |
||
| 333 | |||
| 334 | :returns: A list of UIDs extracted from the raw 'Services' data. |
||
| 335 | """ |
||
| 336 | services = self.getRawServices() |
||
| 337 | return list(map(lambda record: record.get("uid"), services)) |
||
| 338 | |||
| 339 | @security.protected(permissions.View) |
||
| 340 | def getCommercialID(self): |
||
| 341 | accessor = self.accessor("commercial_id") |
||
| 342 | value = accessor(self) or "" |
||
| 343 | return api.to_utf8(value) |
||
| 344 | |||
| 345 | @security.protected(permissions.ModifyPortalContent) |
||
| 346 | def setCommercialID(self, value): |
||
| 347 | mutator = self.mutator("commercial_id") |
||
| 348 | mutator(self, api.safe_unicode(value)) |
||
| 349 | |||
| 350 | # BBB: AT schema field property |
||
| 351 | CommercialID = property(getCommercialID, setCommercialID) |
||
| 352 | |||
| 353 | @security.protected(permissions.View) |
||
| 354 | def getUseAnalysisProfilePrice(self): |
||
| 355 | accessor = self.accessor("use_analysis_profile_price") |
||
| 356 | value = accessor(self) |
||
| 357 | return bool(value) |
||
| 358 | |||
| 359 | @security.protected(permissions.ModifyPortalContent) |
||
| 360 | def setUseAnalysisProfilePrice(self, value): |
||
| 361 | mutator = self.mutator("use_analysis_profile_price") |
||
| 362 | mutator(self, bool(value)) |
||
| 363 | |||
| 364 | # BBB: AT schema field property |
||
| 365 | UseAnalysisProfilePrice = property( |
||
| 366 | getUseAnalysisProfilePrice, setUseAnalysisProfilePrice) |
||
| 367 | |||
| 368 | @security.protected(permissions.View) |
||
| 369 | def getAnalysisProfilePrice(self): |
||
| 370 | accessor = self.accessor("analysis_profile_price") |
||
| 371 | value = accessor(self) or "" |
||
| 372 | return api.to_float(value, 0.0) |
||
| 373 | |||
| 374 | @security.protected(permissions.ModifyPortalContent) |
||
| 375 | def setAnalysisProfilePrice(self, value): |
||
| 376 | mutator = self.mutator("analysis_profile_price") |
||
| 377 | mutator(self, value) |
||
| 378 | |||
| 379 | # BBB: AT schema field property |
||
| 380 | AnalysisProfilePrice = property( |
||
| 381 | getAnalysisProfilePrice, setAnalysisProfilePrice) |
||
| 382 | |||
| 383 | @security.protected(permissions.View) |
||
| 384 | def getAnalysisProfileVAT(self): |
||
| 385 | accessor = self.accessor("analysis_profile_vat") |
||
| 386 | value = accessor(self) or "" |
||
| 387 | return api.to_float(value, 0.0) |
||
| 388 | |||
| 389 | @security.protected(permissions.ModifyPortalContent) |
||
| 390 | def setAnalysisProfileVAT(self, value): |
||
| 391 | mutator = self.mutator("analysis_profile_vat") |
||
| 392 | mutator(self, value) |
||
| 393 | |||
| 394 | # BBB: AT schema field property |
||
| 395 | AnalysisProfileVAT = property(getAnalysisProfileVAT, setAnalysisProfileVAT) |
||
| 396 | |||
| 397 | @security.protected(permissions.View) |
||
| 398 | def getAnalysisServiceSettings(self, uid): |
||
| 399 | """Returns the hidden seettings for the given service UID |
||
| 400 | """ |
||
| 401 | uid = api.get_uid(uid) |
||
| 402 | by_uid = self.get_services_by_uid() |
||
| 403 | record = by_uid.get(uid, {"uid": uid, "hidden": False}) |
||
| 404 | return record |
||
| 405 | |||
| 406 | @security.protected(permissions.ModifyPortalContent) |
||
| 407 | def setAnalysisServicesSettings(self, settings): |
||
| 408 | """BBB: Update settings for selected service UIDs |
||
| 409 | |||
| 410 | This method expects a list of dictionaries containing the service `uid` |
||
| 411 | and the `hidden` setting. |
||
| 412 | |||
| 413 | This is basically the same format as stored in the `services` field! |
||
| 414 | |||
| 415 | However, we want to just update the settings for selected service UIDs" |
||
| 416 | |||
| 417 | >>> settings = [{'uid': '...', 'hidden': False}, ...] |
||
| 418 | >>> setAnalysisServicesSettings(settings) |
||
| 419 | """ |
||
| 420 | if not isinstance(settings, list): |
||
| 421 | settings = [settings] |
||
| 422 | |||
| 423 | by_uid = self.get_services_by_uid() |
||
| 424 | |||
| 425 | for setting in settings: |
||
| 426 | if not isinstance(setting, dict): |
||
| 427 | raise TypeError( |
||
| 428 | "Expected a record containing `uid` and `hidden`, got %s" |
||
| 429 | % type(setting)) |
||
| 430 | uid = setting.get("uid") |
||
| 431 | hidden = setting.get("hidden", False) |
||
| 432 | |||
| 433 | if not uid: |
||
| 434 | raise ValueError("UID is missing in setting %r" % setting) |
||
| 435 | |||
| 436 | record = by_uid.get(uid) |
||
| 437 | if not record: |
||
| 438 | continue |
||
| 439 | record["hidden"] = hidden |
||
| 440 | |||
| 441 | # set back the new services |
||
| 442 | self.setServices(by_uid.values()) |
||
| 443 | |||
| 444 | @security.protected(permissions.View) |
||
| 445 | def getAnalysisServicesSettings(self): |
||
| 446 | """BBB: Return hidden settings for selected services |
||
| 447 | |||
| 448 | :returns: List of dictionaries containing `uid` and `hidden` settings |
||
| 449 | """ |
||
| 450 | # Note: We store the selected service UIDs and the hidden setting in |
||
| 451 | # the `services` field. Therefore, we can just return the raw value. |
||
| 452 | return self.getRawServices() |
||
| 453 | |||
| 454 | # BBB: AT schema (computed) field property |
||
| 455 | AnalysisServicesSettings = property(getAnalysisServicesSettings) |
||
| 456 | |||
| 457 | @security.protected(permissions.View) |
||
| 458 | def get_services_by_uid(self): |
||
| 459 | """Return the selected services grouped by UID |
||
| 460 | """ |
||
| 461 | records = {} |
||
| 462 | for record in self.services: |
||
| 463 | records[record.get("uid")] = record |
||
| 464 | return records |
||
| 465 | |||
| 466 | def isAnalysisServiceHidden(self, service): |
||
| 467 | """Check if the service is configured as hidden |
||
| 468 | """ |
||
| 469 | obj = api.get_object(service) |
||
| 470 | uid = api.get_uid(service) |
||
| 471 | services = self.get_services_by_uid() |
||
| 472 | record = services.get(uid) |
||
| 473 | if not record: |
||
| 474 | return obj.getRawHidden() |
||
| 475 | return record.get("hidden", False) |
||
| 476 | |||
| 477 | @security.protected(permissions.View) |
||
| 478 | def getPrice(self): |
||
| 479 | """Returns the price of the profile, without VAT |
||
| 480 | """ |
||
| 481 | return self.getAnalysisProfilePrice() |
||
| 482 | |||
| 483 | @security.protected(permissions.View) |
||
| 484 | def getVATAmount(self): |
||
| 485 | """Compute VAT amount |
||
| 486 | """ |
||
| 487 | price = self.getPrice() |
||
| 488 | vat = self.getAnalysisProfileVAT() |
||
| 489 | return float(price) * float(vat) / 100 |
||
| 490 | |||
| 491 | # BBB: AT schema (computed) field property |
||
| 492 | VATAmount = property(getVATAmount) |
||
| 493 | |||
| 494 | @security.protected(permissions.View) |
||
| 495 | def getTotalPrice(self): |
||
| 496 | """Calculate the final price using the VAT and the subtotal price |
||
| 497 | """ |
||
| 498 | price = self.getPrice() |
||
| 499 | vat = self.getVATAmount() |
||
| 500 | return float(price) + float(vat) |
||
| 501 | |||
| 502 | # BBB: AT schema (computed) field property |
||
| 503 | TotalPrice = property(getTotalPrice) |
||
| 504 | |||
| 505 | View Code Duplication | def remove_service(self, service): |
|
| 506 | """Remove the service from the profile |
||
| 507 | |||
| 508 | If the service is not selected in the profile, returns False. |
||
| 509 | |||
| 510 | NOTE: This method is used when an Analysis Service was deactivated. |
||
| 511 | |||
| 512 | :param service: The service to be removed from this profile |
||
| 513 | :type service: AnalysisService |
||
| 514 | :return: True if the AnalysisService has been removed successfully |
||
| 515 | """ |
||
| 516 | # get the UID of the service that should be removed |
||
| 517 | uid = api.get_uid(service) |
||
| 518 | # get the current raw value of the services field. |
||
| 519 | current_services = self.getRawServices() |
||
| 520 | # filter out the UID of the service |
||
| 521 | new_services = filter( |
||
| 522 | lambda record: record.get("uid") != uid, current_services) |
||
| 523 | |||
| 524 | # check if the service was removed or not |
||
| 525 | current_services_count = len(current_services) |
||
| 526 | new_services_count = len(new_services) |
||
| 527 | |||
| 528 | if current_services_count == new_services_count: |
||
| 529 | # service was not part of the profile |
||
| 530 | return False |
||
| 531 | |||
| 532 | # set the new services |
||
| 533 | self.setServices(new_services) |
||
| 534 | |||
| 535 | return True |
||
| 536 | |||
| 537 | @security.protected(permissions.View) |
||
| 538 | def getRawSampleTypes(self): |
||
| 539 | accessor = self.accessor("sample_types", raw=True) |
||
| 540 | return accessor(self) or [] |
||
| 541 | |||
| 542 | @security.protected(permissions.View) |
||
| 543 | def getSampleTypes(self): |
||
| 544 | accessor = self.accessor("sample_types") |
||
| 545 | return accessor(self) or [] |
||
| 546 | |||
| 547 | @security.protected(permissions.ModifyPortalContent) |
||
| 548 | def setSampleTypes(self, value): |
||
| 549 | mutator = self.mutator("sample_types") |
||
| 550 | mutator(self, value) |
||
| 551 |