Passed
Push — 2.x ( e94308...699219 )
by Jordi
09:28
created

senaite.core.api.label   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 37
eloc 130
dl 0
loc 261
rs 9.44
c 0
b 0
f 0

18 Functions

Rating   Name   Duplication   Size   Complexity  
A add_obj_labels() 0 15 2
A set_obj_labels() 0 13 2
B to_labels() 0 17 6
A disable_labels_for_type() 0 10 2
A list_labels() 0 8 1
A catalog_object() 0 5 1
A uncatalog_object() 0 5 1
A get_obj_labels() 0 8 1
A del_obj_labels() 0 15 2
A set_storage() 0 9 2
A is_label_object() 0 8 1
A get_storage() 0 8 1
A get_klass() 0 17 4
A create_label() 0 12 3
A enable_labels_for_type() 0 10 2
A search_objects_by_label() 0 14 1
A get_label_by_name() 0 13 3
A query_labels() 0 14 2
1
# -*- coding: utf-8 -*-
2
3
from bika.lims import api
4
from bika.lims import logger
5
from bika.lims.api import create
6
from bika.lims.api import get_object
7
from bika.lims.api import get_senaite_setup
8
from bika.lims.api import is_string
9
from bika.lims.api import search
10
from plone.dexterity.utils import resolveDottedName
11
from Products.Archetypes.atapi import listTypes
12
from Products.CMFPlone.utils import classDoesNotImplement
13
from Products.CMFPlone.utils import classImplements
14
from senaite.core.catalog import LABEL_CATALOG
15
from senaite.core.catalog import SETUP_CATALOG
16
from senaite.core.interfaces import ICanHaveLabels
17
from senaite.core.interfaces import IHaveLabels
18
from senaite.core.interfaces import ILabel
19
from zope.interface import alsoProvides
20
from zope.interface import noLongerProvides
21
22
LABEL_STORAGE = "senaite.core.labels"
23
BEHAVIOR_ID = ICanHaveLabels.__identifier__
24
25
26
def get_storage(obj, default=None):
27
    """Get label storage for the given object
28
29
    :param obj: Content object
30
    :param default: default value to return
31
    :returns: tuple
32
    """
33
    return getattr(obj, LABEL_STORAGE, default)
34
35
36
def set_storage(obj, value):
37
    """Set the label stroage for the given object
38
39
    :param obj: The object to store the labels
40
    :param value: Tuple of labels
41
    """
42
    if not isinstance(value, tuple):
43
        raise TypeError("Expected type tuple, got %s" % type(value))
44
    setattr(obj, LABEL_STORAGE, value)
45
46
47
def query_labels(inactive=False, **kw):
48
    """Fetch all labels by a catalog query
49
    """
50
    catalog = SETUP_CATALOG
51
    query = {
52
        "portal_type": "Label",
53
        "is_active": True,
54
        "sort_on": "title",
55
    }
56
    # Allow to update the query with the keywords
57
    query.update(kw)
58
    if inactive:
59
        del query["is_active"]
60
    return search(query, catalog)
61
62
63
def get_label_by_name(name, inactive=True):
64
    """Fetch a label object by name
65
66
    :param name: Name of the label
67
    :returns: Label object or None
68
    """
69
    found = query_labels(title=name)
70
    if len(found) == 0:
71
        return None
72
    elif len(found) > 1:
73
        logger.warn("Found more than one label for '%s'"
74
                    "Returning the first label only" % name)
75
    return api.get_object(found[0])
76
77
78
def list_labels(inactive=False, **kw):
79
    """List the titles of all global labels
80
81
    :returns: List of label titles
82
    """
83
    brains = query_labels(inactive=inactive, **kw)
84
    labels = map(api.get_title, brains)
85
    return list(labels)
86
87
88
def create_label(label, **kw):
89
    """Create a new label
90
    """
91
    if not api.is_string(label):
92
        return None
93
    # Do not create duplicate labels
94
    existing = get_label_by_name(label, inactive=True)
95
    if existing:
96
        return existing
97
    # Create a new labels object
98
    setup = get_senaite_setup()
99
    return create(setup.labels, "Label", title=label, **kw)
100
101
102
def is_label_object(obj):
103
    """Checks if the given object is a label object
104
105
    :param obj: Object to check
106
    :returns: True if the object is a label
107
    """
108
    obj = api.get_object(obj, default=None)
109
    return ILabel.providedBy(obj)
110
111
112
def to_labels(labels):
113
    """Convert labels into a list of strings
114
115
    :returns: List of label strings
116
    """
117
    if not isinstance(labels, (tuple, list, set)):
118
        labels = tuple((labels, ))
119
    out = set()
120
    for label in labels:
121
        if is_label_object(label):
122
            out.add(api.get_title(label))
123
        elif label and is_string(label):
124
            out.add(label)
125
        else:
126
            # ignore the rest
127
            continue
128
    return tuple(out)
129
130
131
def catalog_object(obj):
132
    """Catalog the object in the label catalog
133
    """
134
    catalog = api.get_tool(LABEL_CATALOG)
135
    catalog.catalog_object(obj)
136
137
138
def uncatalog_object(obj):
139
    """Uncatalog the object from the label catalog
140
    """
141
    catalog = api.get_tool(LABEL_CATALOG)
142
    catalog.uncatalog_object(obj)
143
144
145
def get_obj_labels(obj):
146
    """Get assigned labels of the given object
147
148
    :returns: tuple of string labels
149
    """
150
    obj = get_object(obj)
151
    labels = get_storage(obj, default=tuple())
152
    return labels
153
154
155
def set_obj_labels(obj, labels):
156
    """Set the given labels to the object label storage
157
    """
158
    obj = api.get_object(obj)
159
    # always sort the labels before setting it to the storage
160
    set_storage(obj, tuple(sorted(labels)))
161
    # mark the object with the proper interface
162
    if not labels:
163
        noLongerProvides(obj, IHaveLabels)
164
        uncatalog_object(obj)
165
    else:
166
        alsoProvides(obj, IHaveLabels)
167
        catalog_object(obj)
168
169
170
def add_obj_labels(obj, labels):
171
    """Add one ore more labels to the object
172
173
    :param obj: the object to label
174
    :param labels: string or list of labels to add
175
    :returns: The new labels
176
    """
177
    obj = api.get_object(obj)
178
    # prepare the set of new labels
179
    new_labels = set(get_obj_labels(obj))
180
    for label in to_labels(labels):
181
        new_labels.add(label)
182
    # set the new labels
183
    set_obj_labels(obj, new_labels)
184
    return get_obj_labels(obj)
185
186
187
def del_obj_labels(obj, labels):
188
    """Remove labels from the object
189
190
    :param obj: the object where the labels should be removed
191
    :param labels: string or list of labels to remove
192
    :returns: The new labels
193
    """
194
    obj = api.get_object(obj)
195
    # prepare the set of new labels
196
    new_labels = set(get_obj_labels(obj))
197
    for label in to_labels(labels):
198
        new_labels.discard(label)
199
    # set the new labels
200
    set_obj_labels(obj, new_labels)
201
    return get_obj_labels(obj)
202
203
204
def search_objects_by_label(label, **kw):
205
    """Search for objects having one or more of the given labels
206
207
    :param label: string or list of labels to search
208
    :returns: Catalog results (brains)
209
    """
210
    labels = to_labels(label)
211
    query = {
212
        "labels": map(api.safe_unicode, labels),
213
        "sort_on": "title",
214
    }
215
    # Allow to update the query with the keywords
216
    query.update(kw)
217
    return search(query, catalog=LABEL_CATALOG)
218
219
220
def get_klass(portal_type):
221
    """Returns the implementation class of the given portal type
222
223
    :param portal_type: The portal_type to lookup the class for
224
    :returns: Class object
225
    """
226
    portal_types = api.get_tool("portal_types")
227
    fti = portal_types.getTypeInfo(portal_type)
228
229
    if fti.product:
230
        at_types = listTypes(fti.product)
231
        for t in at_types:
232
            if not t.get("portal_type") == portal_type:
233
                continue
234
            return t.get("klass")
235
    else:
236
        return resolveDottedName(fti.klass)
237
238
239
def enable_labels_for_type(portal_type):
240
    """Enable labels for all objects of the given type
241
242
    :param portal_type: The portal_type to enable labeling
243
    """
244
    klass = get_klass(portal_type)
245
    classImplements(klass, ICanHaveLabels)
246
    # enable behavior for DX types
247
    if api.is_dx_type(portal_type):
248
        api.enable_behavior(portal_type, BEHAVIOR_ID)
249
250
251
def disable_labels_for_type(portal_type):
252
    """Disable labels for all objects of the given type
253
254
    :param portal_type: The portal_type to disable labeling
255
    """
256
    klass = get_klass(portal_type)
257
    classDoesNotImplement(klass, ICanHaveLabels)
258
    # disable behavior
259
    if api.is_dx_type(portal_type):
260
        api.disable_behavior(portal_type, BEHAVIOR_ID)
261