Projet

Général

Profil

0001-profile-add-a-new-user-profile-cell-25633.patch

Frédéric Péters, 10 août 2018 17:12

Télécharger (9,8 ko)

Voir les différences:

Subject: [PATCH] profile: add a new "user profile" cell (#25633)

 combo/data/models.py                       |  7 ++-
 combo/profile/__init__.py                  | 26 +++++++++++
 combo/profile/models.py                    | 37 +++++++++++++++
 combo/profile/templates/combo/profile.html | 13 ++++++
 combo/public/templatetags/combo.py         | 14 ++++++
 tests/settings.py                          | 18 ++++++++
 tests/test_profile.py                      | 52 ++++++++++++++++++++++
 7 files changed, 165 insertions(+), 2 deletions(-)
 create mode 100644 combo/profile/templates/combo/profile.html
 create mode 100644 tests/test_profile.py
combo/data/models.py
1040 1040
        #  },
1041 1041
        #  ...
1042 1042
        # ]
1043
    first_data_key = 'json'
1043 1044

  
1044 1045
    _json_content = None
1045 1046

  
......
1060 1061
                    context[varname] = context['request'].GET[varname]
1061 1062
        self._json_content = None
1062 1063

  
1063
        data_urls = [{'key': 'json', 'url': self.url, 'cache_duration': self.cache_duration,
1064
        context['concerned_user'] = self.get_concerned_user(context)
1065

  
1066
        data_urls = [{'key': self.first_data_key, 'url': self.url, 'cache_duration': self.cache_duration,
1064 1067
                      'log_errors': self.log_errors, 'timeout': self.timeout}]
1065 1068
        data_urls.extend(self.additional_data or [])
1066 1069

  
......
1127 1130

  
1128 1131
        # keep cache of first response as it may be used to find the
1129 1132
        # appropriate template.
1130
        self._json_content = extra_context['json']
1133
        self._json_content = extra_context[self.first_data_key]
1131 1134

  
1132 1135
        return extra_context
1133 1136

  
combo/profile/__init__.py
1
# combo - content management system
2
# Copyright (C) 2014-2018  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17

  
18
import django.apps
19
from django.utils.translation import ugettext_lazy as _
20

  
21

  
22
class AppConfig(django.apps.AppConfig):
23
    name = 'combo.profile'
24
    verbose_name = _('Profile')
25

  
26
default_app_config = 'combo.profile.AppConfig'
combo/profile/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from collections import OrderedDict
18
import copy
19

  
17 20
from django.conf import settings
18 21
from django.db import models
22
from django.utils.dateparse import parse_date
23
from django.utils.translation import ugettext_lazy as _
24

  
25
from combo.data.models import JsonCellBase
26
from combo.data.library import register_cell_class
19 27

  
20 28

  
21 29
class Profile(models.Model):
22 30
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
23 31
    initial_login_view_timestamp = models.DateTimeField(null=True)
32

  
33

  
34
@register_cell_class
35
class ProfileCell(JsonCellBase):
36
    template_name = 'combo/profile.html'
37
    first_data_key = 'profile'
38

  
39
    class Meta:
40
        verbose_name = _('Profile')
41

  
42
    @property
43
    def url(self):
44
        idp = settings.KNOWN_SERVICES.get('authentic').values()[0]
45
        return '{%% load combo %%}%sapi/users/{{ concerned_user|name_id }}' % idp.get('url')
46

  
47
    def get_cell_extra_context(self, context):
48
        extra_context = super(ProfileCell, self).get_cell_extra_context(context)
49
        extra_context['profile_fields'] = OrderedDict()
50
        if extra_context.get('profile') is not None:
51
            for attribute in settings.USER_PROFILE_CONFIG.get('fields'):
52
                extra_context['profile_fields'][attribute['name']] = copy.copy(attribute)
53
                value = extra_context['profile'].get(attribute['name'])
54
                if value:
55
                    if attribute['kind'] in ('birthdate', 'date'):
56
                        value = parse_date(value)
57
                extra_context['profile_fields'][attribute['name']]['value'] = value
58
        else:
59
            extra_context['error'] = 'unknown user'
60
        return extra_context
combo/profile/templates/combo/profile.html
1
{% load i18n %}
2
{% block cell-content %}
3
<div class="profile">
4
<h2>{% trans "Profile" %}</h2>
5
{% for key, details in profile_fields.items %}
6
  {% if details.value and details.user_visible %}
7
    <p><span class="label">{{ details.label }}</span> <span class="value">{{ details.value }}</span></p>
8
  {% endif %}
9
{% endfor %}
10
{% if error == 'unknown user' %}
11
<p>{% trans 'Unknown User' %}</p>
12
{% endif %}
13
{% endblock %}
combo/public/templatetags/combo.py
19 19
import datetime
20 20

  
21 21
from django import template
22
from django.conf import settings
22 23
from django.core import signing
23 24
from django.core.exceptions import PermissionDenied
25
from django.template import VariableDoesNotExist
24 26
from django.template.base import TOKEN_BLOCK, TOKEN_VAR
25 27
from django.template.defaultfilters import stringfilter
26 28
from django.utils import dateparse
......
30 32
from combo.utils import NothingInCacheException, flatten_context
31 33
from combo.apps.dashboard.models import DashboardCell, Tile
32 34

  
35
if 'mellon' in settings.INSTALLED_APPS:
36
    from mellon.models import UserSAMLIdentifier
37

  
33 38
register = template.Library()
34 39

  
35 40
def skeleton_text(context, placeholder_name, content=''):
......
229 234
@register.filter
230 235
def signed(obj):
231 236
    return signing.dumps(obj)
237

  
238
@register.filter
239
def name_id(user):
240
    saml_id = UserSAMLIdentifier.objects.filter(user=user).last()
241
    if saml_id:
242
        return saml_id.name_id
243
    # it is important to raise this so get_templated_url is aborted and no call
244
    # is tried with a missing user argument.
245
    raise VariableDoesNotExist('name_id')
tests/settings.py
62 62

  
63 63
BOOKING_CALENDAR_CELL_ENABLED = True
64 64
NEWSLETTERS_CELL_ENABLED = True
65

  
66
USER_PROFILE_CONFIG = {
67
    'fields': [
68
        {
69
            'name': 'first_name',
70
            'kind': 'string',
71
            'label': 'First Name',
72
            'user_visible': True,
73
        },
74
        {
75
            'name': 'birthdate',
76
            'kind': 'birthdate',
77
            'label': 'Birth Date',
78
            'user_visible': True,
79
        }
80
    ]
81
}
82

  
tests/test_profile.py
1
import datetime
2
import json
3
import mock
4
import pytest
5

  
6
from django.test import override_settings
7

  
8
from combo.data.models import Page
9
from combo.profile.models import ProfileCell
10

  
11
pytestmark = pytest.mark.django_db
12

  
13
def test_profile_cell(app, admin_user):
14
    page = Page()
15
    page.save()
16

  
17
    cell = ProfileCell(page=page, order=0)
18
    cell.save()
19

  
20
    with override_settings(
21
                KNOWN_SERVICES={'authentic': {'idp': {
22
                    'title': 'IdP', 'url': 'http://example.org/'}}}), \
23
            mock.patch('combo.utils.requests.get') as requests_get, \
24
            mock.patch('combo.public.templatetags.combo.UserSAMLIdentifier') as user_saml:
25

  
26
        data = {'first_name': 'Foo', 'birthdate': '2018-08-10'}
27
        requests_get.return_value = mock.Mock(
28
                content=json.dumps(data),
29
                json=lambda: data,
30
                status_code=200)
31

  
32
        def filter_mock(user=None):
33
            assert user is admin_user
34
            return mock.Mock(last=lambda: mock.Mock(name_id='123456'))
35

  
36
        mocked_objects = mock.Mock()
37
        mocked_objects.filter = mock.Mock(side_effect=filter_mock)
38
        user_saml.objects = mocked_objects
39

  
40
        context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
41
        assert context['profile_fields']['first_name']['value'] == 'Foo'
42
        assert context['profile_fields']['birthdate']['value'] == datetime.date(2018, 8, 10)
43
        assert requests_get.call_args[0][0] == 'http://example.org/api/users/123456'
44

  
45
        def filter_mock_missing(user=None):
46
            return mock.Mock(last=lambda: None)
47

  
48
        mocked_objects.filter = mock.Mock(side_effect=filter_mock_missing)
49

  
50
        context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
51
        assert context['error'] == 'unknown user'
52
        assert requests_get.call_count == 1  # no new call was made
0
-