--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/interfaces/profile.py Thu Oct 08 09:28:33 2015 +0200
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from pyams_file.schema import ThumbnailImageField
+from zope.interface import Interface
+
+from pyams_security import _
+
+
+PUBLIC_PROFILE_KEY = 'pyams_security.public_profile'
+
+
+class IPublicProfile(Interface):
+ """User public profile preferences"""
+
+ avatar = ThumbnailImageField(title=_("Profile's avatar"),
+ description=_("This picture will be associated to your user profile"),
+ required=False)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/profile.py Thu Oct 08 09:28:33 2015 +0200
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IPrincipalInfo
+from pyams_security.interfaces.profile import PUBLIC_PROFILE_KEY, IPublicProfile
+from pyams_utils.interfaces.site import ISiteRoot
+from pyams_utils.interfaces.tales import ITALESExtension
+from zope.annotation.interfaces import IAnnotations, IAttributeAnnotatable
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from persistent import Persistent
+from pyams_file.property import FileProperty
+from pyams_utils.adapter import adapter_config, ContextRequestAdapter
+from pyams_utils.request import check_request
+from pyams_utils.traversing import get_parent
+from pyramid.threadlocal import get_current_registry, get_current_request
+from zope.container.contained import Contained
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.interface import implementer, Interface
+from zope.location import locate
+
+
+@implementer(IPublicProfile, IAttributeAnnotatable)
+class PublicProfile(Persistent, Contained):
+ """Public profile persistent class"""
+
+ avatar = FileProperty(IPublicProfile['avatar'])
+
+
+@adapter_config(context=Interface, provides=IPublicProfile)
+def PublicProfileFactory(context):
+ request = check_request()
+ return IPublicProfile(request.principal)
+
+
+@adapter_config(context=IPrincipalInfo, provides=IPublicProfile)
+def PrincipalPublicProfileFactory(principal):
+ """Principal public profile factory adapter"""
+ annotations = IAnnotations(principal)
+ profile = annotations.get(PUBLIC_PROFILE_KEY)
+ if profile is None:
+ profile = annotations[PUBLIC_PROFILE_KEY] = PublicProfile()
+ get_current_registry().notify(ObjectCreatedEvent(profile))
+ request = get_current_request()
+ if request is not None:
+ root = get_parent(request.context, ISiteRoot)
+ locate(profile, root, '++profile++')
+ return profile
+
+
+@adapter_config(name='profile', context=(Interface, Interface), provides=ITraversable)
+class ProfileTraverser(ContextRequestAdapter):
+ """++profile++ namespace traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ return IPublicProfile(self.request)
+
+
+@adapter_config(name='public_profile', context=(Interface, Interface), provides=ITALESExtension)
+class PublicProfileExtension(ContextRequestAdapter):
+ """public_profile TALES extension"""
+
+ def render(self, request=None):
+ if request is None:
+ request = self.request
+ return IPublicProfile(request)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/zmi/profile.py Thu Oct 08 09:28:33 2015 +0200
@@ -0,0 +1,90 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IInnerTabForm
+from pyams_security.interfaces.profile import IPublicProfile
+from pyams_skin.interfaces.viewlet import IShortcutsViewletManager
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_form.form import InnerEditForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_skin.viewlet.shortcuts import Shortcut
+from pyams_utils.adapter import adapter_config
+from pyams_zmi.form import AdminDialogEditForm
+from pyramid.view import view_config
+from z3c.form import field
+from zope.interface import Interface
+
+from pyams_security import _
+
+
+@viewlet_config(name='profile', layer=IPyAMSLayer, manager=IShortcutsViewletManager, weight=10)
+class UserProfileShortcut(Shortcut):
+ """User profile shortcut"""
+
+ label = _("User profile")
+ bg_color_class = 'bg-color-greenLight'
+ icon_class = 'fa-user'
+ url = 'user-profile.html'
+ modal_target = True
+ checked = 'selected'
+
+
+@pagelet_config(name='user-profile.html', layer=IPyAMSLayer)
+class UserProfileEditForm(AdminDialogEditForm):
+ """User profile edit form"""
+
+ legend = _("Edit user profile")
+ fields = field.Fields(Interface)
+
+ ajax_handler = 'user-profile.json'
+ edit_permission = None
+
+ dialog_class = 'modal-large'
+
+ @property
+ def title(self):
+ return self.request.principal.title
+
+
+@view_config(name='user-profile.json', request_type=IPyAMSLayer, renderer='json', xhr=True)
+class UserProfileAJAXEditForm(AJAXEditForm, UserProfileEditForm):
+ """User profile edit form, JSON renderer"""
+
+
+@adapter_config(name='public_profile',
+ context=(Interface, IPyAMSLayer, UserProfileEditForm),
+ provides=IInnerTabForm)
+class PublicProfileTabForm(InnerEditForm):
+ """Public profile tab form"""
+
+ tab_label = _("Public profile")
+ legend = None
+
+ fields = field.Fields(IPublicProfile)
+ edit_permission = None
+
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
+ weight = 10
+
+ def getContent(self):
+ return IPublicProfile(self.request.principal)