Passed
Push — 2.x ( 7b411d...b48eec )
by Jordi
06:47
created

ContactLinkWidgetFactory()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 2
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-2025 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from bika.lims import api
22
from bika.lims import senaiteMessageFactory as _
23
from bika.lims.interfaces import IContact
24
from bika.lims.interfaces import ILabContact
25
from bika.lims.utils import get_link_for
26
from plone.app.users.browser.account import getSchema
27
from plone.app.users.browser.userdatapanel import UserDataPanel as Base
28
from plone.app.users.browser.userdatapanel import UserDataPanelAdapter
29
from plone.app.users.schema import ProtectedEmail
30
from plone.app.users.schema import ProtectedTextLine
31
from plone.app.users.schema import checkEmailAddress
32
from plone.autoform import directives
33
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
34
from senaite.core.catalog import CONTACT_CATALOG
35
from z3c.form.browser.text import TextWidget
36
from z3c.form.interfaces import DISPLAY_MODE
37
from z3c.form.interfaces import IFieldWidget
38
from z3c.form.widget import FieldWidget
39
from zope.interface import Interface
40
from zope.interface import implementer
41
from zope.schema import TextLine
42
43
44
class ContactLinkWidget(TextWidget):
45
    """Widget to render contact UID as a clickable link
46
    """
47
    def render(self):
48
        if not self.value:
49
            return None
50
        return get_link_for(self.value)
51
52
53
@implementer(IFieldWidget)
54
def ContactLinkWidgetFactory(field, request):
55
    return FieldWidget(field, ContactLinkWidget(request))
56
57
58
class IUserDataSchema(Interface):
59
    """Custom User Data Schema
60
    """
61
62
    fullname = ProtectedTextLine(
63
        title=_(u"label_user_full_name", default=u"Full Name"),
64
        description=_(u"help_full_name_creation",
65
                      default=u"Enter full name, e.g. John Smith."),
66
        required=False)
67
68
    email = ProtectedEmail(
69
        title=_(u"label_user_email", default=u"Email"),
70
        description=u"We will use this address if you need to recover your "
71
                    u"password",
72
        required=True,
73
        constraint=checkEmailAddress,
74
    )
75
76
    # Field behaves like a readonly field
77
    directives.mode(contact=DISPLAY_MODE)
78
    directives.widget("contact", ContactLinkWidgetFactory)
79
    contact = TextLine(
80
        title=_(u"label_user_contact", default=u"Contact"),
81
        description=_(u"description_user_contact",
82
                      default=u"User is linked to a contact. "
83
                      u"Please change your settings in the contact profile."),
84
        required=False,
85
    )
86
87
88
def getUserDataSchema():
89
    form_name = u"In User Profile"
90
    # This is needed on Plone 6, but has a bad side effect on Plone 5:
91
    # as Manager you go to a member and then to your own personal-information
92
    # form and you see the data of the member you just visited.
93
    # I keep the code here commented out as warning in case someone compares
94
    # the code.
95
    # if getSecurityManager().checkPermission('Manage portal', portal):
96
    #     form_name = None
97
    schema = getSchema(
98
        IUserDataSchema, UserDataPanelAdapter, form_name=form_name)
99
    return schema
100
101
102
class UserDataPanel(Base):
103
    """Provides user properties in the profile
104
    """
105
    template = ViewPageTemplateFile("templates/account-panel.pt")
106
107
    def __init__(self, context, request):
108
        super(UserDataPanel, self).__init__(context, request)
109
110
    def __call__(self):
111
        submitted = self.request.form.get("Save", False)
112
        if not submitted:
113
            self.notify_linked_user()
114
        return super(UserDataPanel, self).__call__()
115
116
    def _on_save(self, data=None):
117
        contact = api.get_user_contact(self.member)
118
        if not contact:
119
            return
120
        # update the fullname
121
        fullname = data.get("fullname")
122
        if fullname:
123
            contact.setFullname(fullname)
124
        # update the email
125
        email = data.get("email")
126
        if email:
127
            contact.setEmailAddress(email)
128
129
    @property
130
    def label(self):
131
        fullname = self.member.getProperty("fullname")
132
        username = self.member.getUserName()
133
        if fullname:
134
            # username/fullname are already encoded in UTF8
135
            return "%s (%s)" % (fullname, username)
136
        return username
137
138
    @property
139
    def schema(self):
140
        schema = getUserDataSchema()
141
        return schema
142
143
    def updateWidgets(self, prefix=None):
144
        super(UserDataPanel, self).updateWidgets(prefix=prefix)
145
        # check if we are linked to a contact
146
        contact = self.get_linked_contact()
147
        if not contact:
148
            # remove the widget and return
149
            return self.remove_widgets("contact")
150
        # update the contact widget and remove the email/fullname widgets
151
        widget = self.widgets.get("contact")
152
        if widget:
153
            widget.value = api.safe_unicode(api.get_uid(contact))
154
            # remove the email/fullname widgets
155
            self.remove_widgets("email", "fullname")
156
157
    def remove_widgets(self, *names):
158
        """Removes the widgets from the form
159
        """
160
        for name in names:
161
            widget = self.widgets.get(name)
162
            if not widget:
163
                continue
164
            del self.widgets[name]
165
166
    def get_linked_contact(self):
167
        """Returns the linked contact object
168
        """
169
        query = {"getUsername": self.member.getId()}
170
        brains = api.search(query, CONTACT_CATALOG)
171
        if len(brains) == 0:
172
            return None
173
        return api.get_object(brains[0])
174
175
    def notify_linked_user(self):
176
        """Add notification message if user is linked to a contact
177
        """
178
        contact = self.get_linked_contact()
179
        if ILabContact.providedBy(contact):
180
            self.add_status_message(_(
181
                "User is linked to lab contact '${fullname}'",
182
                mapping={"fullname": api.safe_unicode(contact.getFullname())}
183
            ))
184
185
        elif IContact.providedBy(contact):
186
            fullname = contact.getFullname()
187
            client_name = contact.aq_parent.getName()
188
            self.add_status_message(_(
189
                "User is linked to the client contact '${fullname}' "
190
                "(${client_name})",
191
                mapping={
192
                    "fullname": api.safe_unicode(fullname),
193
                    "client_name": api.safe_unicode(client_name)
194
                }
195
            ))
196
197
    def add_status_message(self, message, level="info"):
198
        """Add a portal status message
199
        """
200
        plone_utils = api.get_tool("plone_utils")
201
        return plone_utils.addPortalMessage(message, level)
202
203
204
class UserDataConfiglet(UserDataPanel):
205
    """Control panel version of the userdata panel
206
    """
207
    template = ViewPageTemplateFile("templates/account-configlet.pt")
208