Skip to content

Commit

Permalink
[Bug 790798] Front end for locale announcements.
Browse files Browse the repository at this point in the history
Users that have permission can create locale announcements from the
locale dashboard.
  • Loading branch information
mythmon committed Dec 28, 2012
1 parent 0e7b4cc commit f67ea61
Show file tree
Hide file tree
Showing 11 changed files with 416 additions and 6 deletions.
22 changes: 22 additions & 0 deletions apps/announcements/forms.py
@@ -0,0 +1,22 @@
from datetime import date

from django import forms

from tower import ugettext as _


class AnnouncementForm(forms.Form):
"""Form for collecting information about an announcement.
This is not a ModelForm, and does not include the group or locale fields,
because it should only be used in a context where the group or locale is
implicit, and should not be user controllable. If you need a user
controllable locale or group, use the admin interface.
"""
content = forms.CharField(label=_('Content'), max_length=10000,
widget=forms.Textarea)
show_after = forms.DateField(label=_('Show after'), initial=date.today,
input_formats=['%Y-%m-%d'])
show_until = forms.DateField(label=_('Show until'), required=False,
input_formats=['%Y-%m-%d'])
98 changes: 98 additions & 0 deletions apps/announcements/tests/test_views.py
@@ -0,0 +1,98 @@
from datetime import datetime

from nose.tools import eq_

from announcements.models import Announcement
from announcements.tests import announcement
from sumo.tests import TestCase
from sumo.urlresolvers import reverse
from users.tests import user, add_permission
from wiki.tests import locale


class TestCreateLocaleAnnouncement(TestCase):

def setUp(self):
self.locale = locale(save=True, locale='es')

def _create_test(self, status, count):
"""Login, or other setup, then call this."""
url = reverse('announcements.create_for_locale', locale='es')
resp = self.client.post(url, {
'content': 'Look at me!',
'show_after': '2012-01-01',
})
eq_(resp.status_code, status)
eq_(Announcement.objects.count(), count)

def test_create(self):
u = user(save=True, is_superuser=1)
self.client.login(username=u.username, password='testpass')
self._create_test(200, 1)

def test_leader(self):
u = user(save=True)
self.locale.leaders.add(u)
self.locale.save()
self.client.login(username=u.username, password='testpass')
self._create_test(200, 1)

def test_has_permission(self):
u = user(save=True)
add_permission(u, Announcement, 'add_announcement')
self.client.login(username=u.username, password='testpass')
self._create_test(200, 1)

def test_no_perms(self):
u = user(save=True)
self.client.login(username=u.username, password='testpass')
self._create_test(403, 0)

def test_anon(self):
self._create_test(302, 0)


class TestDeleteAnnouncement(TestCase):

def setUp(self):
self.locale = locale(save=True, locale='es')

self.u = user(save=True)

self.locale.leaders.add(self.u)
self.locale.save()

self.announcement = announcement(save=True, creator=self.u,
locale=self.locale, content="Look at me!",
show_after=datetime(2012, 01, 01, 0, 0, 0))

def _delete_test(self, id, status, count):
"""Login, or other setup, then call this."""
url = reverse('announcements.delete', locale='es', args=(id,))
resp = self.client.post(url)
eq_(resp.status_code, status)
eq_(Announcement.objects.count(), count)

def test_delete(self):
u = user(save=True, is_superuser=1)
self.client.login(username=u.username, password='testpass')
self._delete_test(self.announcement.id, 204, 0)

def test_leader(self):
# Use the user that was created in setUp.
self.client.login(username=self.u.username, password='testpass')
self._delete_test(self.announcement.id, 204, 0)

def test_has_permission(self):
u = user(save=True)
add_permission(u, Announcement, 'add_announcement')
self.client.login(username=u.username, password='testpass')
self._delete_test(self.announcement.id, 204, 0)

def test_no_perms(self):
u = user(save=True)
self.client.login(username=u.username, password='testpass')
self._delete_test(self.announcement.id, 403, 1)

def test_anon(self):
self._delete_test(self.announcement.id, 302, 1)
9 changes: 9 additions & 0 deletions apps/announcements/urls.py
@@ -0,0 +1,9 @@
from django.conf.urls.defaults import patterns, url


urlpatterns = patterns('announcements.views',
url(r'^/create/locale$', 'create_for_locale',
name='announcements.create_for_locale'),
url(r'^/(?P<announcement_id>\d+)/delete$', 'delete',
name='announcements.delete'),
)
55 changes: 55 additions & 0 deletions apps/announcements/views.py
@@ -0,0 +1,55 @@
import json

from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST
from access.decorators import login_required

from announcements.forms import AnnouncementForm
from announcements.models import Announcement
from wiki.models import Locale


@require_POST
@login_required
def create_for_locale(request):
"""An ajax view to create a new announcement for the current locale."""
user = request.user
locale = Locale.objects.get(locale=request.locale)

if not user_can_announce(user, locale):
return HttpResponseForbidden()

form = AnnouncementForm(request.POST)

if form.is_valid():
a = Announcement(creator=user, locale=locale, **form.cleaned_data)
a.save()
return HttpResponse(json.dumps({'id': a.id}),
content_type="application/json")
else:
return HttpResponse(json.dumps(form.errors), status=400,
content_type="application/json")


@require_POST
@login_required
def delete(request, announcement_id):
"""An ajax view to delete an announcement."""
user = request.user
locale = Locale.objects.get(locale=request.locale)

if not user_can_announce(user, locale):
return HttpResponseForbidden()

a = get_object_or_404(Announcement, id=announcement_id)
a.delete()

return HttpResponse("", status=204)


def user_can_announce(user, locale):
if user.is_anonymous():
return False
return (user.has_perm('announcements.add_announcement') or
user in locale.leaders.all())
33 changes: 32 additions & 1 deletion apps/dashboards/templates/dashboards/localization.html
Expand Up @@ -18,12 +18,43 @@ <h1>{{ title }}</h1>
{% for a in announcements %}
<li>
{{ a.content|wiki_to_html }}
<p>{{ datetimeformat(a.show_after) }}</p>
<p>
{{ datetimeformat(a.show_after, 'date') }}
{% if user_can_announce %}
<a href="{{ url('announcements.delete', a.id) }}" class="delete">
{{ _('Delete') }}
</a>
{% endif %}
</p>
</li>
{% endfor %}
</ul>
{% endif %}

{% if user_can_announce %}
<div id="create-announcement">
<button class="btn">
<img src="{{ MEDIA_URL }}img/blank.png" />
{{ _('Create announcement') }}
</button>
<span class="success">{{ _('Created successfully') }}</span>
<div class="kbox" data-target="#create-announcement > .btn" data-position="none" data-close-on-out-click="true">
<form action="{{ url('announcements.create_for_locale') }}" method="POST">
{{ csrf() }}
<ul>
{{ announce_form.as_ul() }}
<li>
<button type="submit" class="btn btn-submit" value="Submit">
{{ _('Create') }}
</button>
<img src="{{ MEDIA_URL }}img/wait-trans.gif" class="spinner" />
</i>
</ul>
</form>
</div>
</div>
{% endif %}

<div class="choice-list">
<label>{{ _('Show information for:') }}</label>
<ul>
Expand Down
44 changes: 42 additions & 2 deletions apps/dashboards/tests/test_views.py
@@ -1,19 +1,20 @@
from datetime import timedelta
from datetime import timedelta, datetime
import json

from django.conf import settings

from nose import SkipTest
from nose.tools import eq_

from announcements.tests import announcement
from dashboards.cron import cache_most_unhelpful_kb_articles
from dashboards.readouts import CONTRIBUTOR_READOUTS
from sumo.tests import TestCase
from sumo.urlresolvers import reverse
from sumo.redis_utils import redis_client, RedisError
from users.tests import user, group
from wiki.models import HelpfulVote
from wiki.tests import revision
from wiki.tests import revision, locale


class LocalizationDashTests(TestCase):
Expand All @@ -26,6 +27,45 @@ def test_redirect_to_contributor_dash(self):
locale='en-US'))


def LocalizationDashAnnouncementsTests(TestCase):

def setUp(self):
self.locale1 = locale(save=True, locale='es')

self.u1 = user(save=True)
self.u2 = user(save=True)
self.u3 = user(save=True)

self.u1.is_superuser = 1
self.u1.save()

self.locale1.leaders.add(self.u2)
self.locale1.save()

self.announcement = announcement(save=True, creator=self.u2,
locale=self.locale1, content="Look at me!",
show_after=datetime(2012, 01, 01, 0, 0, 0))

def test_show_create(self):
self.client.login(username=self.u1.username, password='testpass')
resp = self.client.get(reverse('dashboards.localization'))
self.assertContains(resp, 'id="create-announcement"')

def test_show_for_authed(self):
self.client.login(username=self.u2.username, password='testpass')
resp = self.client.get(reverse('dashboards.localization'))
self.assertContains(resp, 'id="create-announcement"')

def test_hide_for_not_authed(self):
self.client.login(username=self.u3.username, password='testpass')
resp = self.client.get(reverse('dashboards.localization'))
self.assertNotContains(resp, 'id="create-announcement"')

def test_hide_for_anon(self):
resp = self.client.get(reverse('dashboards.localization'))
self.assertNotContains(resp, 'id="create-announcement"')


class ContributorDashTests(TestCase):
def test_main_view(self):
"""Assert the top page of the contributor dash resolves, renders."""
Expand Down
2 changes: 2 additions & 0 deletions apps/dashboards/utils.py
Expand Up @@ -5,6 +5,7 @@
import jingo

from announcements.models import Announcement
from announcements.forms import AnnouncementForm
from dashboards import ACTIONS_PER_PAGE
from sumo_locales import LOCALES
from sumo.utils import paginate
Expand Down Expand Up @@ -55,6 +56,7 @@ def render_readouts(request, readouts, template, locale=None, extra_data=None):
'is_watching_default_ready':
ReadyRevisionEvent.is_notifying(request.user),
'on_default_locale': on_default_locale,
'announce_form': AnnouncementForm(),
'announcements': Announcement.get_for_locale_name(current_locale),
}
if extra_data:
Expand Down
14 changes: 12 additions & 2 deletions apps/dashboards/views.py
@@ -1,5 +1,4 @@
import colorsys
from functools import partial
import json
import logging
import math
Expand All @@ -13,6 +12,7 @@
from tower import ugettext as _

from access.decorators import login_required
from announcements.views import user_can_announce
from announcements.models import Announcement
from dashboards.personal import GROUP_DASHBOARDS
from dashboards.readouts import (overview_rows, READOUTS, L10N_READOUTS,
Expand All @@ -23,6 +23,7 @@
from sumo.redis_utils import redis_client, RedisError
from sumo.urlresolvers import reverse
from sumo.utils import paginate, smart_int
from wiki.models import Locale


log = logging.getLogger('k.dashboards')
Expand Down Expand Up @@ -69,7 +70,16 @@ def localization(request):
"""Render aggregate data about articles in a non-default locale."""
if request.locale == settings.WIKI_DEFAULT_LANGUAGE:
return HttpResponseRedirect(reverse('dashboards.contributors'))
data = {'overview_rows': overview_rows(request.locale)}
locales = Locale.objects.filter(locale=request.locale)
if locales:
permission = user_can_announce(request.user, locales[0])
else:
permission = False

data = {
'overview_rows': overview_rows(request.locale),
'user_can_announce': permission
}
return render_readouts(request, L10N_READOUTS, 'localization.html',
extra_data=data)

Expand Down

0 comments on commit f67ea61

Please sign in to comment.