Passed
Push — master ( 059385...87984f )
by Jordi
04:29
created

build.bika.lims.browser.contact   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 51
eloc 185
dl 0
loc 286
rs 7.92
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A ContactLoginDetailsView.is_labcontact() 0 6 2
A ContactLoginDetailsView.is_contact() 0 6 2
A ContactLoginDetailsView._link_user() 0 14 3
B ContactLoginDetailsView.__call__() 0 23 6
A ContactLoginDetailsView.get_users() 0 8 1
A ContactLoginDetailsView.add_status_message() 0 4 1
A ContactLoginDetailsView.get_contact_properties() 0 8 1
C ContactLoginDetailsView.linkable_users() 0 50 11
A ContactLoginDetailsView.get_user_properties() 0 26 3
A ContactLoginDetailsView._unlink_user() 0 5 1
A ContactLoginDetailsView.tabindex() 0 5 2
F ContactLoginDetailsView._create_user() 0 87 18

How to fix   Complexity   

Complexity

Complex classes like build.bika.lims.browser.contact 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
import re
9
10
import transaction
11
from bika.lims import PMF
12
from bika.lims import api
13
from bika.lims import bikaMessageFactory as _
14
from bika.lims import logger
15
from bika.lims.api import security
16
from bika.lims.browser import BrowserView
17
from bika.lims.content.contact import Contact
18
from bika.lims.content.labcontact import LabContact
19
from plone.app.controlpanel.usergroups import UsersOverviewControlPanel
20
from plone.memoize import view
21
from plone.protect import CheckAuthenticator
22
from Products.CMFCore.utils import getToolByName
23
from Products.CMFPlone.utils import safe_unicode
24
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
25
26
27
class ContactLoginDetailsView(BrowserView):
28
    """Contact Login View
29
    """
30
    template = ViewPageTemplateFile("templates/login_details.pt")
31
32
    def __call__(self):
33
        request = self.request
34
        form = request.form
35
        CheckAuthenticator(form)
36
37
        self.newSearch = False
38
        self.searchstring = form.get("searchstring", "")
39
40
        if form.get("submitted"):
41
            logger.debug("Form Submitted: {}".format(form))
42
            if form.get("unlink_button", False):
43
                self._unlink_user()
44
            elif form.get("search_button", False):
45
                logger.debug("Search User")
46
                self.newSearch = True
47
            elif form.get("link_button", False):
48
                logger.debug("Link User")
49
                self._link_user(form.get("userid"))
50
            elif form.get("save_button", False):
51
                logger.debug("Create User")
52
                self._create_user()
53
54
        return self.template()
55
56
    @view.memoize
57
    def get_users(self):
58
        """Get all users of the portal
59
        """
60
        # We make use of the existing controlpanel `@@usergroup-userprefs`
61
        # view logic to make sure we get all users from all plugins (e.g. LDAP)
62
        users_view = UsersOverviewControlPanel(self.context, self.request)
63
        return users_view.doSearch("")
64
65
    def get_user_properties(self):
66
        """Return the properties of the User
67
        """
68
69
        user = self.context.getUser()
70
71
        # No User linked, nothing to do
72
        if user is None:
73
            return {}
74
75
        out = {}
76
        plone_user = user.getUser()
77
        userid = plone_user.getId()
78
        for sheet in plone_user.listPropertysheets():
79
            ps = plone_user.getPropertysheet(sheet)
80
            out.update(dict(ps.propertyItems()))
81
82
        portal = api.get_portal()
83
        mtool = getToolByName(self.context, 'portal_membership')
84
85
        out["id"] = userid
86
        out["portrait"] = mtool.getPersonalPortrait(id=userid)
87
        out["edit_url"] = "{}/@@user-information?userid={}".format(
88
            portal.absolute_url(), userid)
89
90
        return out
91
92
    def get_contact_properties(self):
93
        """Return the properties of the Contact
94
        """
95
        contact = self.context
96
97
        return {
98
            "fullname": contact.getFullname(),
99
            "username": contact.getUsername(),
100
        }
101
102
    def linkable_users(self):
103
        """Search Plone users which are not linked to a contact or lab contact
104
        """
105
106
        # Only users with at nost these roles are displayed
107
        linkable_roles = {"Authenticated", "Member", "Client"}
108
109
        out = []
110
        for user in self.get_users():
111
            userid = user.get("id", None)
112
113
            if userid is None:
114
                continue
115
116
            # Skip users which are already linked to a Contact
117
            contact = Contact.getContactByUsername(userid)
118
            labcontact = LabContact.getContactByUsername(userid)
119
120
            if contact or labcontact:
121
                continue
122
            if self.is_contact():
123
                # Checking Plone user belongs to Client group only. Otherwise,
124
                # weird things could happen (a client contact assigned to a
125
                # user with labman privileges, different contacts from
126
                # different clients assigned to the same user, etc.)
127
                user_roles = security.get_roles(user=userid)
128
                if not linkable_roles.issuperset(set(user_roles)):
129
                    continue
130
            userdata = {
131
                "userid": userid,
132
                "email": user.get("email"),
133
                "fullname": user.get("title"),
134
            }
135
136
            # filter out users which do not match the searchstring
137
            if self.searchstring:
138
                s = self.searchstring.lower()
139
                if not any(
140
                        map(lambda v: re.search(s, str(v).lower()),
0 ignored issues
show
introduced by
The variable s does not seem to be defined for all execution paths.
Loading history...
141
                            userdata.values())):
142
                    continue
143
144
            # update data (maybe for later use)
145
            userdata.update(user)
146
147
            # Append the userdata for the results
148
            out.append(userdata)
149
150
        out.sort(lambda x, y: cmp(x["fullname"], y["fullname"]))
151
        return out
152
153
    def is_contact(self):
154
        """Check if the current context is a Contact
155
        """
156
        if self.context.portal_type == "Contact":
157
            return True
158
        return False
159
160
    def is_labcontact(self):
161
        """Check if the current context is a LabContact
162
        """
163
        if self.context.portal_type == "LabContact":
164
            return True
165
        return False
166
167
    def _link_user(self, userid):
168
        """Link an existing user to the current Contact
169
        """
170
        # check if we have a selected user from the search-list
171
        if userid:
172
            try:
173
                self.context.setUser(userid)
174
                self.add_status_message(
175
                    _("User linked to this Contact"), "info")
176
            except ValueError, e:
177
                self.add_status_message(e, "error")
178
        else:
179
            self.add_status_message(
180
                _("Please select a User from the list"), "info")
181
182
    def _unlink_user(self):
183
        """Unlink and delete the User from the current Contact
184
        """
185
        self.context.unlinkUser()
186
        self.add_status_message(_("Unlinked User"), "info")
187
188
    def add_status_message(self, message, severity="info"):
189
        """Set a portal message
190
        """
191
        self.context.plone_utils.addPortalMessage(message, severity)
192
193
    def _create_user(self):
194
        """Create a new user
195
        """
196
197
        def error(field, message):
198
            if field:
199
                message = "%s: %s" % (field, message)
200
            self.context.plone_utils.addPortalMessage(message, 'error')
201
            return self.request.response.redirect(
202
                self.context.absolute_url() + "/login_details")
203
204
        form = self.request.form
205
        contact = self.context
206
207
        password = safe_unicode(form.get('password', '')).encode('utf-8')
208
        username = safe_unicode(form.get('username', '')).encode('utf-8')
209
        confirm = form.get('confirm', '')
210
        email = safe_unicode(form.get('email', '')).encode('utf-8')
211
212
        if not username:
213
            return error('username', PMF("Input is required but not given."))
214
215
        if not email:
216
            return error('email', PMF("Input is required but not given."))
217
218
        reg_tool = self.context.portal_registration
219
        # properties = self.context.portal_properties.site_properties
220
        # if properties.validate_email:
221
        #     password = reg_tool.generatePassword()
222
        # else:
223
        if password != confirm:
224
            return error('password', PMF("Passwords do not match."))
225
226
        if not password:
227
            return error('password', PMF("Input is required but not given."))
228
229
        if not confirm:
230
            return error('password', PMF("Passwords do not match."))
231
232
        if len(password) < 5:
233
            return error('password', PMF("Passwords must contain at least 5 "
234
                                         "characters."))
235
        for user in self.get_users():
236
            userid = user.get("id", None)
237
            if userid is None:
238
                continue
239
            user_obj = api.get_user(userid)
240
            if user_obj.getUserName() == username:
241
                msg = "Username {} already exists, please, choose " \
242
                      "another one.".format(username)
243
                return error(None, msg)
244
245
        try:
246
            reg_tool.addMember(username,
247
                               password,
248
                               properties={
249
                                   'username': username,
250
                                   'email': email,
251
                                   'fullname': username})
252
        except ValueError, msg:
253
            return error(None, msg)
254
255
        # set the user to the contact
256
        contact.setUser(username)
257
258
        # Additional groups for LabContact users only!
259
        # -> This is not visible in the Client Contact Form
260
        if "groups" in self.request and self.request["groups"]:
261
            groups = self.request["groups"]
262
            if not type(groups) in (list, tuple):
263
                groups = [groups, ]
264
            for group in groups:
265
                group = self.portal_groups.getGroupById(group)
266
                group.addMember(username)
267
268
        if self.request.get('mail_me', 0):
269
            try:
270
                reg_tool.registeredNotify(username)
271
            except Exception:
272
                transaction.abort()
273
                message = _("SMTP server disconnected. User creation aborted.")
274
                return error(None, message)
275
276
        message = _("Member registered and linked to the current Contact.")
277
        self.context.plone_utils.addPortalMessage(message, 'info')
278
        return self.request.response.redirect(
279
            self.context.absolute_url() + "/login_details")
280
281
    def tabindex(self):
282
        i = 0
283
        while True:
284
            i += 1
285
            yield i
286