Passed
Push — master ( 0e02ba...4def5b )
by Jordi
04:46
created

bika.lims.browser.bika_listing   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 35
eloc 138
dl 0
loc 226
rs 9.6
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A WorkflowAction.redirect() 0 8 3
A WorkflowAction.__init__() 0 10 1
A WorkflowAction.workflow_action_print_stickers() 0 21 2
A WorkflowAction._get_selected_items() 0 11 3
A WorkflowAction.get_selected_uids() 0 3 1
A WorkflowAction.workflow_action_copy_to_new() 0 19 2
A WorkflowAction.workflow_action_default() 0 23 5
A WorkflowAction.add_status_message() 0 4 1
B WorkflowAction.__call__() 0 29 7
A WorkflowAction.submitTransition() 0 28 5
A WorkflowAction._get_form_workflow_action() 0 25 5
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
import collections
9
10
from bika.lims import PMF
11
from bika.lims import api
12
from bika.lims import bikaMessageFactory as _
13
from bika.lims.api import get_object_by_uid
14
from bika.lims.browser.listing.view import ListingView
15
from bika.lims.workflow import ActionHandlerPool
16
from bika.lims.workflow import doActionFor
17
18
19
class WorkflowAction:
20
    """Workflow actions taken in any Bika contextAnalysisRequest context
21
22
    This function provides the default behaviour for workflow actions
23
    invoked from bika_listing tables.
24
25
    Some actions (eg, AR copy_to_new) can be invoked from multiple contexts.
26
    In that case, I will begin to register their handlers here.
27
    XXX WorkflowAction handlers should be simple adapters.
28
    """
29
30
    def __init__(self, context, request):
31
        self.destination_url = ""
32
        self.context = context
33
        self.request = request
34
        self.back_url = self.context.absolute_url()
35
        # Save context UID for benefit of event subscribers.
36
        self.request['context_uid'] = hasattr(self.context, 'UID') and \
37
            self.context.UID() or ''
38
        self.portal = api.get_portal()
39
        self.addPortalMessage = self.context.plone_utils.addPortalMessage
40
41
    def _get_form_workflow_action(self):
42
        """Retrieve the workflow action from the submitted form
43
            - "workflow_action" is the edit border transition
44
            - "workflow_action_button" is the bika_listing table buttons
45
        """
46
        request = self.request
47
        form = request.form
48
        came_from = "workflow_action"
49
        action = form.get(came_from, '')
50
51
        if not action:
52
            came_from = "workflow_action_button"
53
            action = form.get('workflow_action_id', '')
54
            if not action:
55
                if self.destination_url == "":
56
                    url = self.context.absolute_url()
57
                    self.destination_url = request.get_header("referer", url)
58
                request.response.redirect(self.destination_url)
59
                return None, None
60
61
        # A condition in the form causes Plone to sometimes send two actions
62
        if type(action) in (list, tuple):
63
            action = action[0]
64
65
        return action, came_from
66
67
    def get_selected_uids(self):
68
        """Returns a list of selected form uids"""
69
        return self.request.form.get('uids', None) or list()
70
71
    def _get_selected_items(self):
72
        """return a list of selected form objects
73
           full_objects defaults to True
74
        """
75
        uids = self.get_selected_uids()
76
        selected_items = collections.OrderedDict()
77
        for uid in uids:
78
            obj = get_object_by_uid(uid)
79
            if obj:
80
                selected_items[uid] = obj
81
        return selected_items
82
83
    def redirect(self, redirect_url=None, message=None, level="info"):
84
        """Redirect with a message
85
        """
86
        if redirect_url is None:
87
            redirect_url = self.back_url
88
        if message is not None:
89
            self.add_status_message(message, level)
90
        return self.request.response.redirect(redirect_url)
91
92
    def add_status_message(self, message, level="info"):
93
        """Set a portal status message
94
        """
95
        return self.context.plone_utils.addPortalMessage(message, level)
96
97
    def workflow_action_default(self, action, came_from):
98
        if came_from in ['workflow_action', 'edit']:
99
            # If a single item was acted on we will create the item list
100
            # manually from this item itself.  Otherwise, bika_listing will
101
            # pass a list of selected items in the requyest.
102
            items = [self.context, ]
103
        else:
104
            # normal bika_listing.
105
            items = self._get_selected_items().values()
106
107
        if items:
108
            trans, dest = self.submitTransition(action, came_from, items)
109
            if trans:
110
                message = PMF('Changes saved.')
111
                self.addPortalMessage(message, 'info')
112
            if dest:
113
                self.request.response.redirect(dest)
114
                return
115
        else:
116
            message = _('No items selected')
117
            self.addPortalMessage(message, 'warn')
118
        self.request.response.redirect(self.destination_url)
119
        return
120
121
    def workflow_action_copy_to_new(self):
122
        """Invoke the ar_add form in the current context, passing the UIDs of
123
        the source ARs as request parameters.
124
        """
125
        objects = self._get_selected_items()
126
        if not objects:
127
            message = self.context.translate(
128
                _("No analyses have been selected"))
129
            self.addPortalMessage(message, 'info')
130
            self.destination_url = self.context.absolute_url() + "/batchbook"
131
            self.request.response.redirect(self.destination_url)
132
            return
133
134
        url = self.context.absolute_url() + "/ar_add" + \
135
            "?ar_count={0}".format(len(objects)) + \
136
            "&copy_from={0}".format(",".join(objects.keys()))
137
138
        self.request.response.redirect(url)
139
        return
140
141
    def workflow_action_print_stickers(self):
142
        """Invoked from AR or Sample listings in the current context, passing
143
        the uids of the selected items and default sticker template as request
144
        parameters to the stickers rendering machinery, that generates the PDF
145
        """
146
        uids = self.request.form.get("uids", [])
147
        if not uids:
148
            message = self.context.translate(
149
                _("No ARs have been selected"))
150
            self.context.plone_utils.addPortalMessage(message, 'info')
151
            self.destination_url = self.context.absolute_url()
152
            self.request.response.redirect(self.destination_url)
153
            return
154
155
        url = '{0}/sticker?autoprint=1&template={1}&items={2}'.format(
156
            self.context.absolute_url(),
157
            self.portal.bika_setup.getAutoStickerTemplate(),
158
            ','.join(uids)
159
        )
160
        self.destination_url = url
161
        self.request.response.redirect(url)
162
163
    def __call__(self):
164
        request = self.request
165
        form = request.form
166
167
        if self.destination_url == "":
168
            self.destination_url = request.get_header(
169
                "referer", self.context.absolute_url())
170
171
        action, came_from = self._get_form_workflow_action()
172
173
        if action:
174
            # bika_listing sometimes gives us a list of items?
175
            if type(action) == list:
176
                action = action[0]
177
            # Call out to the workflow action method
178
            # Use default bika_listing.py/WorkflowAction for other transitions
179
            method_name = 'workflow_action_' + action
180
            method = getattr(self, method_name, False)
181
            if method and not callable(method):
182
                raise Exception("Shouldn't Happen: %s.%s not callable." %
183
                                (self, method_name))
184
            if method:
185
                method()
186
            else:
187
                self.workflow_action_default(action, came_from)
188
        else:
189
            # Do nothing
190
            self.request.response.redirect(self.destination_url)
191
            return
192
193
    # noinspection PyUnusedLocal
194
    def submitTransition(self, action, came_from, items):
195
        """Performs the action's transition for the specified items
196
197
        Returns (numtransitions, destination), where:
198
        - numtransitions: the number of objects successfully transitioned.
199
            If no objects have been successfully transitioned, gets 0 value
200
        - destination: the destination url to be loaded immediately
201
        """
202
        transitioned = []
203
        actions = ActionHandlerPool.get_instance()
204
        actions.queue_pool()
205
        for item in items:
206
            success, message = doActionFor(item, action)
207
            if success:
208
                transitioned.append(item.UID())
209
            else:
210
                self.addPortalMessage(message, 'error')
211
        actions.resume()
212
213
        # automatic label printing
214
        dest = None
215
        auto_stickers_action = self.portal.bika_setup.getAutoPrintStickers()
216
        if transitioned and action == auto_stickers_action:
217
            self.request.form['uids'] = transitioned
218
            self.workflow_action_print_stickers()
219
            dest = self.destination_url
220
221
        return len(transitioned), dest
222
223
224
class BikaListingView(ListingView):
225
    """BBB: Base View for Table Listings
226
227
    Please use `bika.lims.browser.listing.view.ListingView` instead
228
    """
229