diff --git a/apps/groups/forms.py b/apps/groups/forms.py index 860d36c71e3..e358a7ef2ff 100644 --- a/apps/groups/forms.py +++ b/apps/groups/forms.py @@ -1,6 +1,7 @@ from django import forms from groups.models import GroupProfile +from users.forms import AvatarForm class GroupProfileForm(forms.ModelForm): @@ -9,3 +10,12 @@ class GroupProfileForm(forms.ModelForm): class Meta(object): model = GroupProfile fields = ['information'] + + +# Inherit from user's AvatarForm but override the model. +class GroupAvatarForm(AvatarForm): + """The form for editing the group's avatar.""" + + class Meta(object): + model = GroupProfile + fields = ['avatar'] diff --git a/apps/groups/templates/groups/confirm_avatar_delete.html b/apps/groups/templates/groups/confirm_avatar_delete.html new file mode 100644 index 00000000000..4eb3646a840 --- /dev/null +++ b/apps/groups/templates/groups/confirm_avatar_delete.html @@ -0,0 +1,27 @@ +{# vim: set ts=2 et sts=2 sw=2: #} +{% extends "groups/base.html" %} +{% set title = _('Delete {group} group avatar')|f(group=profile.group.name) %} + +{% block content %} +
+

{{ _('Are you sure you want to delete the {group} group avatar?')|f(group=profile.group.name) }}

+
+ +
+ +
+ {{ csrf() }} +

+ {% trans %} + You are about to permanently delete the avatar. + This cannot be undone! + You can always upload another avatar to replace the current one. + {% endtrans %} +

+ +
+
+{% endblock %} diff --git a/apps/groups/templates/groups/edit_avatar.html b/apps/groups/templates/groups/edit_avatar.html new file mode 100644 index 00000000000..de222010b02 --- /dev/null +++ b/apps/groups/templates/groups/edit_avatar.html @@ -0,0 +1,19 @@ +{# vim: set ts=2 et sts=2 sw=2: #} +{% extends "groups/base.html" %} +{% set title = _('Change {group} group avatar')|f(group=profile.group.name) %} + +{% block content %} +
+

{{ title }}

+
+ {{ csrf() }} + + +
+
+{% endblock %} diff --git a/apps/groups/templates/groups/profile.html b/apps/groups/templates/groups/profile.html index 61ffedc962e..4c1736650d2 100644 --- a/apps/groups/templates/groups/profile.html +++ b/apps/groups/templates/groups/profile.html @@ -6,7 +6,12 @@
- {# TODO: leaders and admins can change avatar #} + {% if user_can_edit %} +

{{ _('Change') }}

+ {% if profile.avatar %} +

{{ _('Delete') }}

+ {% endif %} + {% endif %}
{% if user.is_staff and user.has_perm('groups.change_groupprofile') %} @@ -14,7 +19,7 @@ {% endif %}

{{ profile.group.name }}

- {% if user.has_perm('groups.change_groupprofile') or user in leaders %} + {% if user_can_edit %} {{ _('Edit group profile') }} {% endif %}
diff --git a/apps/groups/tests/test_views.py b/apps/groups/tests/test_views.py index cefae4d93e1..2ef55e856c3 100644 --- a/apps/groups/tests/test_views.py +++ b/apps/groups/tests/test_views.py @@ -1,3 +1,7 @@ +import os + +from django.core.files import File + from nose.tools import eq_ from groups.models import GroupProfile @@ -45,3 +49,50 @@ def test_edit_without_perm(self): args=[slug]), {'information': '=new info='}) eq_(r.status_code, 403) + + +class EditAvatarTests(TestCase): + def setUp(self): + super(EditAvatarTests, self).setUp() + self.user = user(save=True) + add_permission(self.user, GroupProfile, 'change_groupprofile') + self.group_profile = group_profile(group=group(save=True), save=True) + self.client.login(username=self.user.username, password='testpass') + + def tearDown(self): + if self.group_profile.avatar: + self.group_profile.avatar.delete() + super(EditAvatarTests, self).tearDown() + + def test_upload_avatar(self): + """Upload a group avatar.""" + with open('apps/upload/tests/media/test.jpg') as f: + self.group_profile.avatar.save('test_old.jpg', File(f), save=True) + assert self.group_profile.avatar.name.endswith('92b516.jpg') + old_path = self.group_profile.avatar.path + assert os.path.exists(old_path), 'Old avatar is not in place.' + + url = reverse('groups.edit_avatar', locale='en-US', + args=[self.group_profile.slug]) + with open('apps/upload/tests/media/test.jpg') as f: + r = self.client.post(url, {'avatar': f}) + + eq_(302, r.status_code) + url = reverse('groups.profile', args=[self.group_profile.slug]) + eq_('http://testserver/en-US' + url, r['location']) + assert not os.path.exists(old_path), 'Old avatar was not removed.' + + def test_delete_avatar(self): + """Delete a group avatar.""" + self.test_upload_avatar() + + url = reverse('groups.delete_avatar', locale='en-US', + args=[self.group_profile.slug]) + r = self.client.get(url) + eq_(200, r.status_code) + r = self.client.post(url) + eq_(302, r.status_code) + url = reverse('groups.profile', args=[self.group_profile.slug]) + eq_('http://testserver/en-US' + url, r['location']) + gp = GroupProfile.uncached.get(slug=self.group_profile.slug) + eq_('', gp.avatar.name) diff --git a/apps/groups/urls.py b/apps/groups/urls.py index 5c81d242f36..a67202e625a 100644 --- a/apps/groups/urls.py +++ b/apps/groups/urls.py @@ -4,4 +4,8 @@ url(r'^$', 'list', name='groups.list'), url(r'^/(?P[^/]+)$', 'profile', name='groups.profile'), url(r'^/(?P[^/]+)/edit$', 'edit', name='groups.edit'), + url(r'^/(?P[^/]+)/avatar$', 'edit_avatar', + name='groups.edit_avatar'), + url(r'^/(?P[^/]+)/avatar/delete$', 'delete_avatar', + name='groups.delete_avatar'), ) diff --git a/apps/groups/views.py b/apps/groups/views.py index f78fce3c53b..b82b87bbf14 100644 --- a/apps/groups/views.py +++ b/apps/groups/views.py @@ -1,3 +1,6 @@ +import os + +from django.conf import settings from django.contrib import messages from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect @@ -8,8 +11,9 @@ from tower import ugettext as _ from access.decorators import login_required -from groups.forms import GroupProfileForm +from groups.forms import GroupProfileForm, GroupAvatarForm from groups.models import GroupProfile +from upload.tasks import _create_image_thumbnail def list(request): @@ -21,9 +25,10 @@ def profile(request, group_slug): prof = get_object_or_404(GroupProfile, slug=group_slug) leaders = prof.leaders.all() members = prof.group.user_set.all() + user_can_edit = _user_can_edit(request.user, prof) return jingo.render(request, 'groups/profile.html', {'profile': prof, 'leaders': leaders, - 'members': members}) + 'members': members, 'user_can_edit': user_can_edit}) @login_required @@ -31,8 +36,7 @@ def profile(request, group_slug): def edit(request, group_slug): prof = get_object_or_404(GroupProfile, slug=group_slug) - if not (request.user.has_perm('groups.change_groupprofile') or - request.user in prof.leaders.all()): + if not _user_can_edit(request.user, prof): raise PermissionDenied form = GroupProfileForm(request.POST or None, instance=prof) @@ -44,3 +48,64 @@ def edit(request, group_slug): return jingo.render(request, 'groups/edit.html', {'form': form, 'profile': prof}) + + +@login_required +@require_http_methods(['GET', 'POST']) +def edit_avatar(request, group_slug): + """Edit group avatar.""" + prof = get_object_or_404(GroupProfile, slug=group_slug) + + if not _user_can_edit(request.user, prof): + raise PermissionDenied + + form = GroupAvatarForm(request.POST or None, request.FILES or None, + instance=prof) + + old_avatar_path = None + if prof.avatar and os.path.isfile(prof.avatar.path): + # Need to store the path, or else django's + # form.is_valid() messes with it. + old_avatar_path = prof.avatar.path + if request.method == 'POST' and form.is_valid(): + # Upload new avatar and replace old one. + if old_avatar_path: + os.unlink(old_avatar_path) + + prof = form.save() + content = _create_image_thumbnail(prof.avatar.path, + settings.AVATAR_SIZE, pad=True) + # We want everything as .png + name = prof.avatar.name + ".png" + # Delete uploaded avatar and replace with thumbnail. + prof.avatar.delete() + prof.avatar.save(name, content, save=True) + return HttpResponseRedirect(prof.get_absolute_url()) + + return jingo.render(request, 'groups/edit_avatar.html', + {'form': form, 'profile': prof}) + + +@login_required +@require_http_methods(['GET', 'POST']) +def delete_avatar(request, group_slug): + """Delete group avatar.""" + prof = get_object_or_404(GroupProfile, slug=group_slug) + + if not _user_can_edit(request.user, prof): + raise PermissionDenied + + if request.method == 'POST': + # Delete avatar here + if prof.avatar: + prof.avatar.delete() + return HttpResponseRedirect(prof.get_absolute_url()) + + return jingo.render(request, 'groups/confirm_avatar_delete.html', + {'profile': prof}) + + +def _user_can_edit(user, group_profile): + """Can the given user edit the given group profile?""" + return (user.has_perm('groups.change_groupprofile') or + user in group_profile.leaders.all()) diff --git a/apps/sumo/views.py b/apps/sumo/views.py index 7cbc44f94e6..f732d68110f 100644 --- a/apps/sumo/views.py +++ b/apps/sumo/views.py @@ -136,6 +136,7 @@ def monitor(request): (settings.GALLERY_IMAGE_THUMBNAIL_PATH, os.R_OK | os.W_OK, msg), (settings.GALLERY_VIDEO_PATH, os.R_OK | os.W_OK, msg), (settings.GALLERY_VIDEO_THUMBNAIL_PATH, os.R_OK | os.W_OK, msg), + (settings.GROUP_AVATAR_PATH, os.R_OK | os.W_OK, msg), ) filepath_results = [] diff --git a/media/css/groups.css b/media/css/groups.css index 038d0241b74..ab7f52554d1 100644 --- a/media/css/groups.css +++ b/media/css/groups.css @@ -41,6 +41,11 @@ article.main h2 { width: 48px; } +#avatar-area p { + font-size: 12px; + margin: 2px 0; +} + #group-profile a.edit { background: transparent url(../img/icons.actions.png) no-repeat left -25px; float: right; @@ -84,3 +89,40 @@ div.editor { #id_information { height: 350px; } + +/* avatar delete */ +#avatar-delete p { + margin: 10px 0; +} + +/* change avatar */ +#change-avatar form { + margin: 25px 0; +} + +#change-avatar ul { + list-style: none; +} + +#change-avatar div.val-wrap { + display: inline-block; + line-height:35px; + margin-right: 10px; + width: auto; +} + +#change-avatar label { + display: inline-block; + padding: 0 5px 0 0; + text-align: right; + vertical-align: top; + width: 180px; +} + +#change-avatar div.val-wrap input { + display: block; +} + +#change-avatar div.submit { + padding: 10px 0 0 188px; +}