Skip to content

Commit

Permalink
Added UI for changing and deleting group avatars [bug 663805]
Browse files Browse the repository at this point in the history
  • Loading branch information
rlr committed Jun 14, 2011
1 parent 61cca2f commit df41c52
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 6 deletions.
10 changes: 10 additions & 0 deletions 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):
Expand All @@ -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']
27 changes: 27 additions & 0 deletions 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 %}
<article id="avatar-delete" class="main">
<h1>{{ _('Are you sure you want to delete the {group} group avatar?')|f(group=profile.group.name) }}</h1>
<div id="avatar-preview">
<img src="{{ group_avatar(profile) }}" alt="">
</div>

<form action="" method="post">
{{ csrf() }}
<p>
{% trans %}
You are about to permanently delete the avatar.
<strong>This cannot be undone!</strong>
You can always upload another avatar to replace the current one.
{% endtrans %}
</p>
<div class="form-actions">
<input type="submit" class="btn" value="{{ _('Delete avatar') }}" />
<a href="{{ url('groups.profile', profile.slug) }}">{{ _('Cancel') }}</a>
</div>
</form>
</article>
{% endblock %}
19 changes: 19 additions & 0 deletions 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 %}
<article id="change-avatar" class="main">
<h1>{{ title }}</h1>
<form method="post" action="" enctype="multipart/form-data">
{{ csrf() }}
<ul>
{{ form.as_ul()|safe }}
</ul>
<div class="submit">
<input type="submit" value="{{ _('Upload', 'avatar') }}" />
<a href="{{ url('groups.profile', profile.slug) }}">{{ _('Cancel') }}</a>
</div>
</form>
</article>
{% endblock %}
9 changes: 7 additions & 2 deletions apps/groups/templates/groups/profile.html
Expand Up @@ -6,15 +6,20 @@
<article id="group-profile" class="main">
<section id="avatar-area">
<img src="{{ group_avatar(profile) }}" alt="" />
{# TODO: leaders and admins can change avatar #}
{% if user_can_edit %}
<p><a href="{{ url('groups.edit_avatar', profile.slug) }}" title="{{ _('Change avatar') }}">{{ _('Change') }}</a></p>
{% if profile.avatar %}
<p><a href="{{ url('groups.delete_avatar', profile.slug) }}" title="{{ _('Delete avatar') }}">{{ _('Delete') }}</a></p>
{% endif %}
{% endif %}
</section>
<section id="main-area">
{% if user.is_staff and user.has_perm('groups.change_groupprofile') %}
<a class="edit" href="{{ url('admin:groups_groupprofile_change', profile.id) }}">{{ _('Edit in admin') }}</a>
{% endif %}
<h1>{{ profile.group.name }}</h1>

{% if user.has_perm('groups.change_groupprofile') or user in leaders %}
{% if user_can_edit %}
<a class="edit" href="{{ url('groups.edit', profile.slug) }}">{{ _('Edit group profile') }}</a>
{% endif %}
<div id="doc-content">
Expand Down
51 changes: 51 additions & 0 deletions 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
Expand Down Expand Up @@ -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)
4 changes: 4 additions & 0 deletions apps/groups/urls.py
Expand Up @@ -4,4 +4,8 @@
url(r'^$', 'list', name='groups.list'),
url(r'^/(?P<group_slug>[^/]+)$', 'profile', name='groups.profile'),
url(r'^/(?P<group_slug>[^/]+)/edit$', 'edit', name='groups.edit'),
url(r'^/(?P<group_slug>[^/]+)/avatar$', 'edit_avatar',
name='groups.edit_avatar'),
url(r'^/(?P<group_slug>[^/]+)/avatar/delete$', 'delete_avatar',
name='groups.delete_avatar'),
)
73 changes: 69 additions & 4 deletions 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
Expand All @@ -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):
Expand All @@ -21,18 +25,18 @@ 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
@require_http_methods(['GET', 'POST'])
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)
Expand All @@ -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())
1 change: 1 addition & 0 deletions apps/sumo/views.py
Expand Up @@ -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 = []
Expand Down
42 changes: 42 additions & 0 deletions media/css/groups.css
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

0 comments on commit df41c52

Please sign in to comment.