Passed
Push — master ( d8e2ec...90ae0b )
by Jordi
10:07 queued 04:19
created

build.bika.lims.jsonapi.read.read()   F

Complexity

Conditions 22

Size

Total Lines 111
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 111
rs 0
c 0
b 0
f 0
cc 22
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like build.bika.lims.jsonapi.read.read() 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
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
from Products.CMFPlone.utils import safe_unicode
9
from bika.lims import logger, to_utf8
10
from bika.lims.interfaces import IJSONReadExtender
11
from bika.lims.jsonapi import get_include_fields
12
from plone.jsonapi.core import router
13
from plone.jsonapi.core.interfaces import IRouteProvider
14
from plone.protect.authenticator import AuthenticatorView
15
from bika.lims.jsonapi import load_brain_metadata
16
from bika.lims.jsonapi import load_field_values
17
from bika.lims.jsonapi import get_include_methods
18
from bika.lims.jsonapi import load_method_values
19
from Products.CMFCore.utils import getToolByName
20
from zope import interface
21
from zope.component import getAdapters
22
import re
23
import App
24
25
26
def read(context, request):
27
    tag = AuthenticatorView(context, request).authenticator()
28
    pattern = '<input .*name="(\w+)".*value="(\w+)"'
29
    _authenticator = re.match(pattern, tag).groups()[1]
30
31
    ret = {
32
        "url": router.url_for("read", force_external=True),
33
        "success": True,
34
        "error": False,
35
        "objects": [],
36
        "_authenticator": _authenticator,
37
    }
38
    debug_mode = App.config.getConfiguration().debug_mode
39
    catalog_name = request.get("catalog_name", "portal_catalog")
40
    if not catalog_name:
41
        raise ValueError("bad or missing catalog_name: " + catalog_name)
42
    catalog = getToolByName(context, catalog_name)
43
    indexes = catalog.indexes()
44
45
    contentFilter = {}
46
    for index in indexes:
47
        if index in request:
48
            if index == 'UID' and safe_unicode(request[index]) == "":
49
                msg = 'Request with no UID for %s catalog. Dismissing UID ' \
50
                      'while filtering' % catalog_name
51
                logger.warning(msg)
52
            if index == 'review_state' and "{" in request[index]:
53
                continue
54
            contentFilter[index] = safe_unicode(request[index])
55
        if "%s[]"%index in request:
56
            value = request["%s[]"%index]
57
            if type(value) in (list, tuple):
58
                contentFilter[index] = [safe_unicode(v) for v in value]
59
            else:
60
                contentFilter[index] = value
61
62
    if 'limit' in request:
63
        try:
64
            contentFilter['sort_limit'] = int(request["limit"])
65
        except ValueError:
66
            pass
67
    sort_on = request.get('sort_on', 'id')
68
    contentFilter['sort_on'] = sort_on
69
    # sort order
70
    sort_order = request.get('sort_order', '')
71
    if sort_order:
72
        contentFilter['sort_order'] = sort_order
73
    else:
74
        contentFilter['sort_order'] = 'ascending'
75
76
    include_fields = get_include_fields(request)
77
78
    include_methods = get_include_methods(request)
79
80
    # Get matching objects from catalog
81
    proxies = catalog(**contentFilter)
82
83
    if debug_mode:
84
        if len(proxies) == 0:
85
            logger.info("contentFilter {} returned zero objects"
86
                        .format(contentFilter))
87
        elif len(proxies) == 1:
88
            logger.info("contentFilter {} returned {} ({})".format(
89
                contentFilter, proxies[0].portal_type, proxies[0].UID))
90
        else:
91
            types = ','.join(set([p.portal_type for p in proxies]))
92
            logger.info("contentFilter {} returned {} items (types: {})"
93
                        .format(contentFilter, len(proxies), types))
94
95
    # batching items
96
    page_nr = int(request.get("page_nr", 0))
97
    try:
98
        page_size = int(request.get("page_size", 10))
99
    except ValueError:
100
        page_size = 10
101
    # page_size == 0: show all
102
    if page_size == 0:
103
        page_size = len(proxies)
104
    first_item_nr = page_size * page_nr
105
    if first_item_nr > len(proxies):
106
        first_item_nr = 0
107
    page_proxies = proxies[first_item_nr:first_item_nr + page_size]
108
    for proxy in page_proxies:
109
        obj_data = {}
110
111
        # Place all proxy attributes into the result.
112
        obj_data.update(load_brain_metadata(proxy, include_fields))
113
114
        # Place all schema fields ino the result.
115
        obj = proxy.getObject()
116
        obj_data.update(load_field_values(obj, include_fields))
117
        # Add methods results
118
        obj_data.update(load_method_values(obj, include_methods))
119
120
        obj_data['path'] = "/".join(obj.getPhysicalPath())
121
122
        # call any adapters that care to modify this data.
123
        adapters = getAdapters((obj, ), IJSONReadExtender)
124
        for name, adapter in adapters:
125
            adapter(request, obj_data)
126
127
        ret['objects'].append(obj_data)
128
129
    ret['total_objects'] = len(proxies)
130
    ret['first_object_nr'] = first_item_nr
131
    last_object_nr = first_item_nr + len(page_proxies)
132
    if last_object_nr > ret['total_objects']:
133
        last_object_nr = ret['total_objects']
134
    ret['last_object_nr'] = last_object_nr
135
136
    return ret
137
138
139
class Read(object):
140
    interface.implements(IRouteProvider)
141
142
    def initialize(self, context, request):
143
        pass
144
145
    @property
146
    def routes(self):
147
        return (
148
            ("/read", "read", self.read, dict(methods=['GET', 'POST'])),
149
        )
150
151
    def read(self, context, request):
152
        """/@@API/read: Search the catalog and return data for all objects found
153
154
        Optional parameters:
155
156
            - catalog_name: uses portal_catalog if unspecified
157
            - limit  default=1
158
            - All catalog indexes are searched for in the request.
159
160
        {
161
            runtime: Function running time.
162
            error: true or string(message) if error. false if no error.
163
            success: true or string(message) if success. false if no success.
164
            objects: list of dictionaries, containing catalog metadata
165
        }
166
        """
167
168
        return read(context, request)
169