first working attempt to make gravatar optional

pull/4503/head
Daniel Chimeno 2017-03-03 13:18:46 +01:00 zatwierdzone przez Matt Westcott
rodzic 1849f0d54a
commit 5b8ca7d7e7
23 zmienionych plików z 322 dodań i 76 usunięć

Wyświetl plik

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="612px" height="792px" viewBox="0 0 612 792" enable-background="new 0 0 612 792" xml:space="preserve">
<path fill="#999999" d="M306,181.8c51,0,91.8,40.8,91.8,91.8S357,365.4,306,365.4s-91.8-40.8-91.8-91.8S255,181.8,306,181.8z"/>
<path fill="#999999" d="M306,616.575c-76.5,0-144.075-39.525-183.6-98.175c1.275-61.2,122.4-94.351,183.6-94.351
S488.325,457.2,489.6,518.4C450.075,577.05,382.5,616.575,306,616.575z"/>
</svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 784 B

Wyświetl plik

@ -0,0 +1,27 @@
{% extends "wagtailadmin/base.html" %}
{% load avatar i18n %}
{% block titletag %}{% trans "Change profile picture" %}{% endblock %}
{% block content %}
{% trans "Change profile picture" as change_str %}
{% include "wagtailadmin/shared/header.html" with title=change_str %}
<div class="nice-padding">
<form action="{% url 'wagtailadmin_account_change_avatar' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
</ul>
<li class="submit"><input type="submit" value="{% trans 'Update' %}" class="button" /></li>
</form>
<p>{% trans "Your current profile picture:" %}</p>
<p>
<span class="avatar square"><img src="{% avatar_url request.user size=50 %}"></span>
</p>
</div>
{% endblock %}

Wyświetl plik

@ -1,5 +1,5 @@
{% extends "wagtailadmin/base.html" %}
{% load gravatar staticfiles i18n %}
{% load avatar staticfiles i18n %}
{% block titletag %}{% trans "Dashboard" %}{% endblock %}
{% block bodyclass %}homepage{% endblock %}
@ -13,7 +13,7 @@
<header class="merged nice-padding">
<div class="row row-flush">
<div class="col1">
<div class="avatar"><img src="{% gravatar_url user.email %}" alt="" /></div>
<div class="avatar"><img src="{% avatar_url user.email %}" alt="" /></div>
</div>
<div class="col9">
<h1>{% block branding_welcome %}{% blocktrans %}Welcome to the {{ site_name }} Wagtail CMS{% endblocktrans %}{% endblock %}</h1>

Wyświetl plik

@ -1,6 +1,6 @@
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% load gravatar %}
{% load avatar %}
{% load i18n %}
{% load l10n %}
{% block titletag %}{% blocktrans with title=page.get_admin_display_title page_type=content_type.model_class.get_verbose_name %}Editing {{ page_type }}: {{ title }}{% endblocktrans %}{% endblock %}
@ -89,7 +89,7 @@
{% blocktrans with last_mod=page.get_latest_revision.created_at %}Last modified: {{ last_mod }}{% endblocktrans %}
{% if page.get_latest_revision.user %}
{% blocktrans with modified_by=page.get_latest_revision.user.get_full_name|default:page.get_latest_revision.user.get_username %}by {{ modified_by }}{% endblocktrans %}
<span class="avatar small"><img src="{% gravatar_url page.get_latest_revision.user.email 25 %}" /></span>
<span class="avatar small"><img src="{% avatar_url page.get_latest_revision.user size=25 %}" /></span>
{% endif %}
<a href="{% url 'wagtailadmin_pages:revisions_index' page.id %}" class="underlined">{% trans 'Revisions' %}</a>
{% endif %}

Wyświetl plik

@ -1,4 +1,4 @@
{% load i18n wagtailadmin_tags gravatar %}
{% load i18n wagtailadmin_tags avatar %}
{% load l10n %}
<table class="listing">
@ -17,7 +17,7 @@
<h2>
<a href="{% url 'wagtailadmin_pages:revisions_revert' page.id revision.id %}">{{ revision.created_at }}</a>
<span class="unbold">
{% trans 'by' context 'points to a user who created a revision' %}<span class="avatar small"><img src="{% gravatar_url revision.user.email 25 %}" /></span>{{ revision.user }}
{% trans 'by' context 'points to a user who created a revision' %}<span class="avatar small"><img src="{% avatar_url revision.user size=25 %}" /></span>{{ revision.user }}
</span>
{% if revision == page.get_latest_revision %}({% trans 'Current draft' %}){% endif %}
{% if revision == page.live_revision %}({% trans 'Live version' %}){% endif %}

Wyświetl plik

@ -1,4 +1,4 @@
{% load gravatar wagtailadmin_tags %}
{% load avatar wagtailadmin_tags %}
{% load i18n %}
<nav class="nav-main">
<ul>
@ -7,7 +7,7 @@
<li class="footer" id="footer">
<div class="account" id="account-settings" title="{% trans 'Edit your account' %}">
<span class="avatar square avatar-on-dark">
<img src="{% gravatar_url request.user.email 25 %}" />
<img src="{% avatar_url request.user size=50 %}" />
</span>
<em class="icon icon-arrow-up-after">{{ request.user.first_name|default:request.user.get_username }}</em>
</div>
@ -19,4 +19,4 @@
</li>
</ul>
</nav>
</nav>

Wyświetl plik

@ -1,7 +1,7 @@
{% load gravatar %}
{% load avatar %}
<span class="avatar small">
<img src="{% gravatar_url user.email 25 %}" />
<img src="{% avatar_url user size=25 %}" />
</span>
{{ user }}

Wyświetl plik

@ -0,0 +1,18 @@
from __future__ import absolute_import, unicode_literals
from django import template
from django.contrib.staticfiles.templatetags.staticfiles import static
register = template.Library()
@register.simple_tag(takes_context=True)
def avatar_url(context, user, size=50):
"""
A template tag that receives a user and size and return
the appropiate avatar url for that user.
Example usage: {% avatar_url request.user 50 %}
"""
if hasattr(user, 'wagtail_userprofile'): # A user could not have profile yet, so this is necessay
return user.wagtail_userprofile.get_avatar_url(size=size)
return static('wagtailadmin/images/default-user-avatar.svg')

Wyświetl plik

@ -1,44 +0,0 @@
# place inside a 'templatetags' directory inside the top level of a Django app (not project, must be inside an app)
# at the top of your page template include this:
# {% load gravatar %}
# and to use the url do this:
# <img src="{% gravatar_url 'someone@somewhere.com' %}">
# or
# <img src="{% gravatar_url sometemplatevariable %}">
# just make sure to update the "default" image path below
import hashlib
from urllib.parse import urlencode
from django import template
register = template.Library()
class GravatarUrlNode(template.Node):
def __init__(self, email, size=50):
self.email = template.Variable(email)
self.size = size
def render(self, context):
try:
email = self.email.resolve(context)
except template.VariableDoesNotExist:
return ''
default = "mm"
size = int(self.size) * 2 # requested at retina size by default and scaled down at point of use with css
gravatar_url = "//www.gravatar.com/avatar/{hash}?{params}".format(
hash=hashlib.md5(email.lower().encode('utf-8')).hexdigest(),
params=urlencode({'s': size, 'd': default})
)
return gravatar_url
@register.tag
def gravatar_url(parser, token):
bits = token.split_contents()
return GravatarUrlNode(*bits[1:])

Wyświetl plik

@ -1,3 +1,6 @@
import os
import tempfile
import pytz
from django.contrib.auth import views as auth_views
@ -13,6 +16,8 @@ from wagtail.admin.utils import (
from wagtail.tests.utils import WagtailTestUtils
from wagtail.users.models import UserProfile
TMP_MEDIA_ROOT = tempfile.mktemp()
class TestAuthentication(TestCase, WagtailTestUtils):
"""
@ -468,6 +473,101 @@ class TestAccountSection(TestCase, WagtailTestUtils):
self.assertNotContains(response, 'Set Time Zone')
class TestAvatarSection(TestCase, WagtailTestUtils):
def _create_image(self):
from PIL import Image
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
image = Image.new('RGB', (200, 200), 'white')
image.save(f, 'JPEG')
return open(f.name, mode='rb')
def setUp(self):
self.user = self.login()
self.avatar = self._create_image()
self.other_avatar = self._create_image()
def tearDown(self):
self.avatar.close()
self.other_avatar.close()
def test_avatar_preferences_view(self):
"""
This tests that the change user profile(avatar) view responds with an index page
"""
response = self.client.get(reverse('wagtailadmin_account_change_avatar'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/account/change_avatar.html')
self.assertContains(response, "Change profile picture")
def test_avatar_preferences_post(self):
"""
This tests that the change user profile(avatar) view change the user preferences
"""
post_data = {
'avatar_choice': 'default',
}
response = self.client.post(reverse('wagtailadmin_account_change_avatar'), post_data, follow=True)
self.assertEqual(response.status_code, 200)
profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
self.assertEqual('default', profile.avatar_choice)
def test_get_avatar_returns_default_if_not_changed(self):
profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
self.assertEqual(profile.avatar_choice, 'default')
self.assertIn('default-user-avatar', profile.get_avatar_url())
def test_set_gravatar_returns_gravatar(self):
post_data = {
'avatar_choice': 'gravatar',
}
response = self.client.post(reverse('wagtailadmin_account_change_avatar'), post_data, follow=True)
self.assertEqual(response.status_code, 200)
profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
self.assertEqual(profile.avatar_choice, 'gravatar')
self.assertIn('www.gravatar.com', profile.get_avatar_url())
@override_settings(MEDIA_ROOT=TMP_MEDIA_ROOT)
def test_set_custom_avatar_stores_and_get_custom_avatar(self):
response = self.client.post(reverse('wagtailadmin_account_change_avatar'),
{'avatar_choice': 'custom',
'avatar': self.avatar},
follow=True)
self.assertEqual(response.status_code, 200)
profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
self.assertEqual('custom', profile.avatar_choice)
self.assertIn(os.path.basename(self.avatar.name), profile.get_avatar_url())
@override_settings(MEDIA_ROOT=TMP_MEDIA_ROOT)
def test_user_upload_another_image_removes_previous_one(self):
response = self.client.post(reverse('wagtailadmin_account_change_avatar'),
{'avatar_choice': 'custom',
'avatar': self.avatar},
follow=True)
self.assertEqual(response.status_code, 200)
profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
old_avatar_path = profile.avatar.path
# Upload a new avatar
new_response = self.client.post(reverse('wagtailadmin_account_change_avatar'),
{'avatar_choice': 'custom',
'avatar': self.other_avatar},
follow=True)
self.assertEqual(new_response.status_code, 200)
# Check old avatar doesn't exist anymore in filesystem
with self.assertRaises(FileNotFoundError):
open(old_avatar_path)
class TestAccountManagementForNonModerator(TestCase, WagtailTestUtils):
"""
Tests of reduced-functionality for editors

Wyświetl plik

@ -50,6 +50,7 @@ urlpatterns = [
account.notification_preferences,
name='wagtailadmin_account_notification_preferences'
),
url(r'account/change_avatar/$', account.change_avatar, name='wagtailadmin_account_change_avatar'),
url(
r'^account/language_preferences/$',
account.language_preferences,

Wyświetl plik

@ -12,7 +12,7 @@ from django.utils.translation import activate
from wagtail.admin import forms
from wagtail.core import hooks
from wagtail.users.forms import (
CurrentTimeZoneForm, EmailForm, NotificationPreferencesForm, PreferredLanguageForm)
AvatarPreferencesForm, CurrentTimeZoneForm, EmailForm, NotificationPreferencesForm, PreferredLanguageForm)
from wagtail.users.models import UserProfile
from wagtail.utils.loading import get_custom_form
@ -185,6 +185,20 @@ def current_time_zone(request):
})
def change_avatar(request):
if request.method == 'POST':
user_profile = UserProfile.get_for_user(request.user)
form = AvatarPreferencesForm(request.POST, request.FILES, instance=user_profile)
if form.is_valid():
form.save()
messages.success(request, _("Your preferences have been updated successfully!"))
return redirect('wagtailadmin_account_change_avatar')
else:
form = AvatarPreferencesForm(instance=UserProfile.get_for_user(request.user))
return render(request, 'wagtailadmin/account/change_avatar.html', {'form': form})
class LoginView(auth_views.LoginView):
template_name = 'wagtailadmin/login.html'

Wyświetl plik

@ -194,15 +194,11 @@ def register_viewsets_urls():
@hooks.register('register_account_menu_item')
def register_account_set_gravatar(request):
def register_account_set_profile_picture(request):
return {
'url': 'https://gravatar.com/emails/',
'label': _('Set gravatar'),
'help_text': _(
"Your avatar image is provided by Gravatar and is connected to "
"your email address. With a Gravatar account you can set an "
"avatar for any number of other email addresses you use."
)
'url': reverse('wagtailadmin_account_change_avatar'),
'label': _('Set profile picture'),
'help_text': _("Change your profile picture")
}

Wyświetl plik

@ -1,5 +1,5 @@
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags i18n staticfiles gravatar %}
{% load wagtailadmin_tags i18n staticfiles avatar %}
{% block extra_css %}
{{ block.super }}
@ -582,12 +582,9 @@
<h2>Misc formatters</h2>
<h3>Avatar icons</h3>
<p><span class="avatar"><img src="{% gravatar_url "david@torchbox.com" %}" /></span> Gravatar set (normal)</p>
<p><span class="avatar"><img src="{% gravatar_url "aeon@torchbox.com" %}" /></span> Gravatar not set (normal)</p>
<p><span class="avatar square"><img src="{% gravatar_url "david@torchbox.com" %}" /></span> Gravatar set (square)</p>
<p><span class="avatar square"><img src="{% gravatar_url "aeon@torchbox.com" %}" /></span> Gravatar not set (square)</p>
<p><span class="avatar small"><img src="{% gravatar_url "david@torchbox.com" %}" /></span> Gravatar set (small)</p>
<p><span class="avatar small"><img src="{% gravatar_url "aeon@torchbox.com" %}" /></span> Gravatar not set (small)</p>
<p><span class="avatar"><img src="{% avatar_url user size=50 %}" /></span> Avatar normal</p>
<p><span class="avatar square"><img src="{% avatar_url user size=50 %}" /></span> Avatar square</p>
<p><span class="avatar small"><img src="{% avatar_url user size=25 %}" /></span> Avatar small</p>
<h3>Status tags</h3>
<div class="status-tag primary">Primary tag</div>

Wyświetl plik

@ -1,4 +1,5 @@
from django import forms
from django.contrib.auth.models import User
from django.core.paginator import Paginator
from django.shortcuts import render
from django.utils.translation import ugettext as _
@ -71,8 +72,11 @@ def index(request):
paginator = Paginator(list(range(100)), 10)
page = paginator.page(2)
user = User(email='david@torchbox.com')
return render(request, 'wagtailstyleguide/base.html', {
'search_form': form,
'example_form': example_form,
'example_page': page,
'user': user,
})

Wyświetl plik

@ -418,3 +418,12 @@ class CurrentTimeZoneForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ("current_time_zone",)
class AvatarPreferencesForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ("avatar_choice", "avatar")
widgets = {
'avatar_choice': forms.RadioSelect(),
}

Wyświetl plik

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-11-24 14:18
from __future__ import unicode_literals
from django.db import migrations, models
import wagtail.users.models
class Migration(migrations.Migration):
dependencies = [
('wagtailusers', '0007_userprofile_current_time_zone'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='avatar',
field=models.ImageField(blank=True, upload_to=wagtail.users.models.upload_avatar_to, verbose_name='Upload your custom avatar'),
),
migrations.AddField(
model_name='userprofile',
name='avatar_choice',
field=models.CharField(choices=[('default', 'Default'), ('custom', 'Custom'), ('gravatar', 'Gravatar')], default='default', max_length=10, verbose_name='Select profile picture type'),
),
]

Wyświetl plik

@ -1,9 +1,34 @@
import os
import uuid
from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from wagtail.users.utils import get_gravatar_url
def upload_avatar_to(instance, filename):
filename, ext = os.path.splitext(filename)
return os.path.join(
'avatar_images',
'avatar_{uuid}_{filename}{ext}'.format(
uuid=uuid.uuid4(), filename=filename, ext=ext)
)
class UserProfile(models.Model):
DEFAULT = 'default'
CUSTOM = 'custom'
GRAVATAR = 'gravatar'
AVATAR_CHOICES = (
(DEFAULT, _('Default')),
(CUSTOM, _('Custom')),
(GRAVATAR, 'Gravatar')
)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='wagtail_userprofile'
)
@ -40,6 +65,19 @@ class UserProfile(models.Model):
default=''
)
avatar_choice = models.CharField(
verbose_name=_('Select profile picture type'),
default=DEFAULT,
choices=AVATAR_CHOICES,
max_length=10
)
avatar = models.ImageField(
verbose_name=_('Upload your custom avatar'),
upload_to=upload_avatar_to,
blank=True,
)
@classmethod
def get_for_user(cls, user):
return cls.objects.get_or_create(user=user)[0]
@ -53,5 +91,30 @@ class UserProfile(models.Model):
def __str__(self):
return self.user.get_username()
@cached_property
def default_avatar(self):
return static('wagtailadmin/images/default-user-avatar.svg')
def get_avatar_url(self, size=50):
if self.avatar_choice == self.DEFAULT:
return self.default_avatar
if self.avatar_choice == self.CUSTOM:
try:
return self.avatar.url
except ValueError:
return self.default_avatar
if self.avatar_choice == self.GRAVATAR and self.user.email:
return get_gravatar_url(self.user.email, default=None, size=50)
return self.default_avatar
def save(self, *args, **kwargs):
if self.avatar:
this = UserProfile.objects.get(pk=self.pk)
this.avatar.delete(save=False)
return super(UserProfile, self).save(*args, **kwargs)
class Meta:
verbose_name = _('user profile')

Wyświetl plik

@ -1,6 +1,6 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% load gravatar %}
{% load avatar %}
{% block titletag %}{% trans "groups" %}{% endblock %}
{% block extra_js %}
{{ block.super }}

Wyświetl plik

@ -1,6 +1,6 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% load gravatar %}
{% load avatar %}
{% block titletag %}{% trans "Users" %}{% endblock %}
{% block extra_js %}
{{ block.super }}

Wyświetl plik

@ -1,5 +1,5 @@
{% load i18n wagtailusers_tags %}
{% load gravatar %}
{% load avatar %}
<table class="listing">
<thead>
<tr>
@ -28,7 +28,7 @@
<tr>
<td class="title" valign="top">
<h2>
<span class="avatar small"><img src="{% gravatar_url user.email 25 %}" /></span>
<span class="avatar small"><img src="{% avatar_url user size=25 %}" /></span>
<a href="{% url 'wagtailusers_users:edit' user.pk %}">{{ user.get_full_name|default:user.get_username }}</a>
</h2>
<ul class="actions">

Wyświetl plik

@ -19,6 +19,10 @@ delete_user_perm_codename = "delete_{0}".format(AUTH_USER_MODEL_NAME.lower())
change_user_perm_codename = "change_{0}".format(AUTH_USER_MODEL_NAME.lower())
def test_avatar_provider(user, default, size=50):
return '/nonexistent/path/to/avatar.png'
class CustomUserCreationForm(UserCreationForm):
country = forms.CharField(required=True, label="Country")
attachment = forms.FileField(required=True, label="Attachment")
@ -929,7 +933,7 @@ class TestUserProfileCreation(TestCase, WagtailTestUtils):
self.test_user = get_user_model().objects.create_user(
username='testuser',
email='testuser@email.com',
password='password'
password='password',
)
def test_user_created_without_profile(self):
@ -942,6 +946,16 @@ class TestUserProfileCreation(TestCase, WagtailTestUtils):
# and get it from the db too
self.assertEqual(UserProfile.objects.filter(user=self.test_user).count(), 1)
def test_get_avatar_url_default(self):
user_profile = UserProfile.get_for_user(self.test_user)
self.assertEqual(user_profile.avatar_choice, 'default')
self.assertIn('default-user-avatar', user_profile.get_avatar_url())
def test_get_avatar_url_fallback_default(self):
user_profile = UserProfile.get_for_user(self.test_user)
user_profile.avatar_choice = 'Non-existent'
self.assertIn('default-user-avatar', user_profile.get_avatar_url())
class TestUserEditViewForNonSuperuser(TestCase, WagtailTestUtils):
def setUp(self):

Wyświetl plik

@ -1,3 +1,6 @@
import hashlib
from django.utils.http import urlencode
from wagtail.core.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
delete_user_perm = "{0}.delete_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower())
@ -16,3 +19,12 @@ def user_can_delete_user(current_user, user_to_delete):
return False
return True
def get_gravatar_url(email, default=None, size=50):
params = {'s': str(size)}
if default is not None:
params['default'] = default
gravatar_url = "https://www.gravatar.com/avatar/" + hashlib.md5(email.lower().encode('utf-8')).hexdigest() + "?"
gravatar_url += urlencode(params)
return gravatar_url