profile: add a new "user profile" cell (#25633)
This commit is contained in:
parent
80fcdb2f67
commit
cb4ccd0579
|
@ -1043,6 +1043,7 @@ class JsonCellBase(CellBase):
|
|||
# },
|
||||
# ...
|
||||
# ]
|
||||
first_data_key = 'json'
|
||||
|
||||
_json_content = None
|
||||
|
||||
|
@ -1063,7 +1064,9 @@ class JsonCellBase(CellBase):
|
|||
context[varname] = context['request'].GET[varname]
|
||||
self._json_content = None
|
||||
|
||||
data_urls = [{'key': 'json', 'url': self.url, 'cache_duration': self.cache_duration,
|
||||
context['concerned_user'] = self.get_concerned_user(context)
|
||||
|
||||
data_urls = [{'key': self.first_data_key, 'url': self.url, 'cache_duration': self.cache_duration,
|
||||
'log_errors': self.log_errors, 'timeout': self.timeout}]
|
||||
data_urls.extend(self.additional_data or [])
|
||||
|
||||
|
@ -1128,7 +1131,7 @@ class JsonCellBase(CellBase):
|
|||
|
||||
# keep cache of first response as it may be used to find the
|
||||
# appropriate template.
|
||||
self._json_content = extra_context['json']
|
||||
self._json_content = extra_context[self.first_data_key]
|
||||
|
||||
return extra_context
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014-2018 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import django.apps
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class AppConfig(django.apps.AppConfig):
|
||||
name = 'combo.profile'
|
||||
verbose_name = _('Profile')
|
||||
|
||||
default_app_config = 'combo.profile.AppConfig'
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-08-10 13:52
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('data', '0035_page_related_cells'),
|
||||
('profile', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProfileCell',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('placeholder', models.CharField(max_length=20)),
|
||||
('order', models.PositiveIntegerField()),
|
||||
('slug', models.SlugField(blank=True, verbose_name='Slug')),
|
||||
('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
|
||||
('public', models.BooleanField(default=True, verbose_name='Public')),
|
||||
('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
|
||||
('last_update_timestamp', models.DateTimeField(auto_now=True)),
|
||||
('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')),
|
||||
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data.Page')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Profile',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -14,10 +14,47 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.dateparse import parse_date
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from combo.data.models import JsonCellBase
|
||||
from combo.data.library import register_cell_class
|
||||
|
||||
|
||||
class Profile(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
||||
initial_login_view_timestamp = models.DateTimeField(null=True)
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class ProfileCell(JsonCellBase):
|
||||
template_name = 'combo/profile.html'
|
||||
first_data_key = 'profile'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Profile')
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
idp = settings.KNOWN_SERVICES.get('authentic').values()[0]
|
||||
return '{%% load combo %%}%sapi/users/{{ concerned_user|name_id }}/' % idp.get('url')
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
extra_context = super(ProfileCell, self).get_cell_extra_context(context)
|
||||
extra_context['profile_fields'] = OrderedDict()
|
||||
if extra_context.get('profile') is not None:
|
||||
for attribute in settings.USER_PROFILE_CONFIG.get('fields'):
|
||||
extra_context['profile_fields'][attribute['name']] = copy.copy(attribute)
|
||||
value = extra_context['profile'].get(attribute['name'])
|
||||
if value:
|
||||
if attribute['kind'] in ('birthdate', 'date'):
|
||||
value = parse_date(value)
|
||||
extra_context['profile_fields'][attribute['name']]['value'] = value
|
||||
else:
|
||||
extra_context['error'] = 'unknown user'
|
||||
return extra_context
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}
|
||||
{% block cell-content %}
|
||||
<div class="profile">
|
||||
<h2>{% trans "Profile" %}</h2>
|
||||
{% for key, details in profile_fields.items %}
|
||||
{% if details.value and details.user_visible %}
|
||||
<p><span class="label">{{ details.label }}</span> <span class="value">{{ details.value }}</span></p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if error == 'unknown user' %}
|
||||
<p>{% trans 'Unknown User' %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -19,8 +19,10 @@ from __future__ import absolute_import
|
|||
import datetime
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.core import signing
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.template import VariableDoesNotExist
|
||||
from django.template.base import TOKEN_BLOCK, TOKEN_VAR
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django.utils import dateparse
|
||||
|
@ -30,6 +32,9 @@ from combo.public.menu import get_menu_context
|
|||
from combo.utils import NothingInCacheException, flatten_context
|
||||
from combo.apps.dashboard.models import DashboardCell, Tile
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
from mellon.models import UserSAMLIdentifier
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def skeleton_text(context, placeholder_name, content=''):
|
||||
|
@ -229,3 +234,12 @@ def as_list(obj):
|
|||
@register.filter
|
||||
def signed(obj):
|
||||
return signing.dumps(obj)
|
||||
|
||||
@register.filter
|
||||
def name_id(user):
|
||||
saml_id = UserSAMLIdentifier.objects.filter(user=user).last()
|
||||
if saml_id:
|
||||
return saml_id.name_id
|
||||
# it is important to raise this so get_templated_url is aborted and no call
|
||||
# is tried with a missing user argument.
|
||||
raise VariableDoesNotExist('name_id')
|
||||
|
|
|
@ -184,6 +184,13 @@ div.cell {
|
|||
}
|
||||
}
|
||||
|
||||
div.profile {
|
||||
span.value {
|
||||
display: block;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1586px) {
|
||||
div#page-content div.cubesbarchart {
|
||||
width: 49.5%;
|
||||
|
|
|
@ -62,3 +62,20 @@ FAMILY_SERVICE = {'root': '/'}
|
|||
|
||||
BOOKING_CALENDAR_CELL_ENABLED = True
|
||||
NEWSLETTERS_CELL_ENABLED = True
|
||||
|
||||
USER_PROFILE_CONFIG = {
|
||||
'fields': [
|
||||
{
|
||||
'name': 'first_name',
|
||||
'kind': 'string',
|
||||
'label': 'First Name',
|
||||
'user_visible': True,
|
||||
},
|
||||
{
|
||||
'name': 'birthdate',
|
||||
'kind': 'birthdate',
|
||||
'label': 'Birth Date',
|
||||
'user_visible': True,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import datetime
|
||||
import json
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
from combo.data.models import Page
|
||||
from combo.profile.models import ProfileCell
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@override_settings(
|
||||
KNOWN_SERVICES={'authentic': {'idp': {'title': 'IdP', 'url': 'http://example.org/'}}})
|
||||
@mock.patch('combo.public.templatetags.combo.UserSAMLIdentifier')
|
||||
@mock.patch('combo.utils.requests.get')
|
||||
def test_profile_cell(requests_get, user_saml, app, admin_user):
|
||||
page = Page()
|
||||
page.save()
|
||||
|
||||
cell = ProfileCell(page=page, order=0)
|
||||
cell.save()
|
||||
|
||||
data = {'first_name': 'Foo', 'birthdate': '2018-08-10'}
|
||||
requests_get.return_value = mock.Mock(
|
||||
content=json.dumps(data),
|
||||
json=lambda: data,
|
||||
status_code=200)
|
||||
|
||||
def filter_mock(user=None):
|
||||
assert user is admin_user
|
||||
return mock.Mock(last=lambda: mock.Mock(name_id='123456'))
|
||||
|
||||
mocked_objects = mock.Mock()
|
||||
mocked_objects.filter = mock.Mock(side_effect=filter_mock)
|
||||
user_saml.objects = mocked_objects
|
||||
|
||||
context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
|
||||
assert context['profile_fields']['first_name']['value'] == 'Foo'
|
||||
assert context['profile_fields']['birthdate']['value'] == datetime.date(2018, 8, 10)
|
||||
assert requests_get.call_args[0][0] == 'http://example.org/api/users/123456/'
|
||||
|
||||
def filter_mock_missing(user=None):
|
||||
return mock.Mock(last=lambda: None)
|
||||
|
||||
mocked_objects.filter = mock.Mock(side_effect=filter_mock_missing)
|
||||
|
||||
context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
|
||||
assert context['error'] == 'unknown user'
|
||||
assert requests_get.call_count == 1 # no new call was made
|
Loading…
Reference in New Issue