Remove tweets button for Army of Awesome. Bug 624464.

This commit is contained in:
Fred Wenzel 2011-02-17 14:29:21 -08:00
Родитель 391ac6fa39
Коммит 8dac105b96
11 изменённых файлов: 176 добавлений и 12 удалений

Просмотреть файл

@ -6,7 +6,7 @@ from .models import Tweet, CannedCategory, CannedResponse, CategoryMembership
class TweetAdmin(admin.ModelAdmin):
date_hierarchy = 'created'
list_display = ('tweet_id', '__unicode__', 'created', 'locale')
list_filter = ('locale',)
list_filter = ('locale', 'hidden')
search_fields = ('raw_json',)
admin.site.register(Tweet, TweetAdmin)

Просмотреть файл

@ -15,6 +15,7 @@ class Tweet(ModelBase):
created = models.DateTimeField(default=datetime.now, db_index=True)
reply_to = models.BigIntegerField(blank=True, null=True, default=None,
db_index=True)
hidden = models.BooleanField(default=False, db_index=True)
class Meta:
get_latest_by = 'created'

Просмотреть файл

@ -45,6 +45,7 @@
<br style="clear:both; height: 1px" />
<div id="tweets-wrap">
{{ csrf() }}{# CSRF token for AJAX actions. #}
{% if not tweets %}
<div class="warning-box">
{% trans language=settings.LOCALES[request.locale].native %}

Просмотреть файл

@ -21,6 +21,12 @@
{% elif settings.CC_SHOW_REPLIES %}
<span class="reply_count">{{ _('Reply now') }}</span>
{% endif %}
{% if settings.CC_ALLOW_REMOVE and not (tweet.reply_to or tweet.replies) %}
<a class="remove_tweet" href="{{ url('customercare.hide_tweet') }}">{{
_('Remove') }}</a>
{% endif %}
<p class="text">{{ tweet.text|safe }}</p>
</div>
<div id="replies_{{ tweet.id }}" class="replies" data-tweet-id="{{ tweet.id }}">

Просмотреть файл

@ -1,7 +1,12 @@
from django.conf import settings
from mock import patch_object
from nose.tools import eq_
from customercare.models import Tweet
from customercare.views import _get_tweets
from sumo.tests import TestCase
from sumo.urlresolvers import reverse
class TweetListTestCase(TestCase):
@ -28,3 +33,53 @@ class TweetListTestCase(TestCase):
# max_id.
for tweet in tweets_2:
assert tweet['id'] < max_id
def test_hide_tweets(self):
"""Try hiding tweets."""
hide_tweet = lambda id: self.client.post(
reverse('customercare.hide_tweet', locale='en-US'),
{'id': id})
tw = Tweet.objects.no_cache().filter(reply_to=None, hidden=False)[0]
r = hide_tweet(tw.tweet_id)
eq_(r.status_code, 200)
# Re-fetch from database. Should be hidden.
tw = Tweet.objects.no_cache().get(tweet_id=tw.tweet_id)
eq_(tw.hidden, True)
# Hiding it again should work.
r = hide_tweet(tw.tweet_id)
eq_(r.status_code, 200)
def test_hide_tweets_with_replies(self):
"""Hiding tweets with replies is not allowed."""
tw = Tweet.objects.filter(reply_to=None)[0]
tw.reply_to = 123
tw.save()
r = self.client.post(
reverse('customercare.hide_tweet', locale='en-US'),
{'id': tw.tweet_id})
eq_(r.status_code, 400)
def test_hide_tweets_invalid_id(self):
"""Invalid tweet IDs shouldn't break anything."""
hide_tweet = lambda id: self.client.post(
reverse('customercare.hide_tweet', locale='en-US'),
{'id': id})
r = hide_tweet(123)
eq_(r.status_code, 404)
r = hide_tweet('cheesecake')
eq_(r.status_code, 400)
@patch_object(settings._wrapped, 'CC_ALLOW_REMOVE', False)
def test_hide_tweets_disabled(self):
"""Do not allow hiding tweets if feature is disabled."""
tw = Tweet.objects.filter(reply_to=None)[0]
r = self.client.post(
reverse('customercare.hide_tweet', locale='en-US'),
{'id': tw.tweet_id})
eq_(r.status_code, 418) # Don't tell a teapot to brew coffee.

Просмотреть файл

@ -1,7 +1,8 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('customercare.views',
url(r'/more_tweets', 'more_tweets', name="customercare.more_tweets"),
url(r'/twitter_post', 'twitter_post', name="customercare.twitter_post"),
url(r'', 'landing', name='customercare.landing'),
url(r'^/more_tweets$', 'more_tweets', name="customercare.more_tweets"),
url(r'^/twitter_post$', 'twitter_post', name="customercare.twitter_post"),
url(r'^/hide_tweet$', 'hide_tweet', name="customercare.hide_tweet"),
url(r'^$', 'landing', name='customercare.landing'),
)

Просмотреть файл

@ -6,7 +6,9 @@ import logging
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponseBadRequest
from django.http import (HttpResponse, HttpResponseBadRequest,
HttpResponseNotFound, HttpResponseServerError)
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST, require_GET
from babel.numbers import format_number
@ -15,7 +17,7 @@ import jingo
from tower import ugettext as _
import tweepy
from .models import CannedCategory, Tweet
from customercare.models import CannedCategory, Tweet
import twitter
@ -58,7 +60,7 @@ def _get_tweets(locale=settings.LANGUAGE_CODE,
max_id will only return tweets with the status ids less than the given id.
"""
locale = settings.LOCALES[locale].iso639_1
q = Tweet.objects.filter(locale=locale, reply_to=reply_to)
q = Tweet.objects.filter(locale=locale, reply_to=reply_to, hidden=False)
if max_id:
q = q.filter(tweet_id__lt=max_id)
if limit:
@ -188,3 +190,39 @@ def twitter_post(request):
# We could optimize by not encoding and then decoding JSON.
return jingo.render(request, 'customercare/tweets.html',
{'tweets': [_tweet_for_template(tweet)]})
@require_POST
def hide_tweet(request):
"""
Hide the tweet with a given ID. Only hides tweets that are not replies
and do not have replies.
Returns proper HTTP status codes.
"""
# If feature disabled, bail.
if not settings.CC_ALLOW_REMOVE:
return HttpResponse(status=418) # I'm a teapot.
try:
id = int(request.POST.get('id'))
except (ValueError, TypeError):
return HttpResponseBadRequest(_('Invalid ID.'))
try:
tweet = Tweet.objects.get(tweet_id=id)
except Tweet.DoesNotExist:
return HttpResponseNotFound(_('Invalid ID.'))
if tweet.reply_to or Tweet.objects.filter(reply_to=id).count():
return HttpResponseBadRequest(_('Tweets that are replies or have '
'replies must not be hidden.'))
try:
tweet.hidden = True
tweet.save(force_update=True)
except Exception, e:
return HttpResponseServerError(
_('An error occured: {message}').format(message=e))
return HttpResponse('ok')

Просмотреть файл

@ -155,6 +155,10 @@ div.warning-box {
border-top: 1px solid #fef1ad;
}
#tweets li {
position: relative;
}
#tweets li img {
border: solid 1px #cacccb;
float: left;
@ -193,7 +197,23 @@ div.warning-box {
width: 550px;
}
#tweets li .reply_count {
#tweets .remove_tweet {
display: none;
}
html.js #tweets li:hover .remove_tweet,
#tweets li .remove_tweet.clicked {
display: inline;
position: absolute;
right: 10px;
bottom: 10px;
}
#tweets li .remove_tweet.clicked {
padding-left: 20px;
background: url('../img/customercare/spinner.gif') left top no-repeat;
}
#tweets li .reply_count,
#tweets li .remove_tweet {
clear: right;
float: right;
font-family: Verdana, sans-serif;
@ -206,14 +226,20 @@ div.warning-box {
color: #0ba643;
}
#tweets li a.reply_count:before {
content: '\25B6\A0';
content: '\25B6\A0'; /* rightarrow, space */
}
#tweets li a.reply_count.opened:before {
content: '\25BC\A0';
content: '\25BC\A0'; /* downarrow, space */
}
#tweets li a.reply_count:hover {
#tweets li .reply_count:hover,
#tweets li .remove_tweet:hover {
text-decoration: underline;
}
#tweets li .remove_tweet:before {
color: #e45d49;
content: '\2716\A0'; /* big X, space */
font-size: 110%;
}
.tweets-buttons {
float: right;

Просмотреть файл

@ -1,6 +1,7 @@
(function($){
// Tweet IDs are too high. Using .data('tweet-id') returns incorrect
// results. See jQuery bug 7579 - http://bugs.jquery.com/ticket/7579
// results. Use .attr('data-tweet-id') instead.
// See jQuery bug 7579 - http://bugs.jquery.com/ticket/7579
function Memory(name) {
this._id = null;
@ -373,6 +374,37 @@
e.preventDefault();
});
/* Remove tweet functionality */
$('#tweets a.remove_tweet').live('click', function(e) {
if ($(this).hasClass('clicked')) return false;
$(this).addClass('clicked');
var tweet = $(this).closest('li'),
tweet_id = tweet.attr('data-tweet-id');
$.ajax({
url: $(this).attr('href'),
type: 'POST',
data: {
csrfmiddlewaretoken: $('#tweets-wrap input[name=csrfmiddlewaretoken]').val(),
id: tweet_id
},
dataType: 'text',
success: function() {
$(this).removeClass('clicked');
tweet.slideUp('fast', function() {
$(this).remove();
});
},
error: function(err) {
$(this).removeClass('clicked');
alert('Error removing tweet: ' + err.responseText);
}
});
$(this).blur();
e.preventDefault();
});
/* Search box */
$('#side-search input[name="q"]').autoPlaceholderText();

Просмотреть файл

@ -0,0 +1,3 @@
-- Allow tweets to be hidden on Army of Awesome page.
ALTER TABLE `customercare_tweet` ADD `hidden` TINYINT(1) NOT NULL DEFAULT '0';
ALTER TABLE `customercare_tweet` ADD INDEX ( `hidden` ) ;

Просмотреть файл

@ -568,6 +568,7 @@ VIDEO_MAX_FILESIZE = 16777216 # 16 megabytes, in bytes
CC_MAX_TWEETS = 500 # Max. no. of tweets in DB
CC_TWEETS_PERPAGE = 100 # How many tweets to collect in one go. Max: 100.
CC_SHOW_REPLIES = True # Show replies to tweets?
CC_ALLOW_REMOVE = True # Allow users to hide tweets?
CC_TWEET_ACTIVITY_URL = 'https://metrics.mozilla.com/stats/twitter/armyOfAwesomeKillRate.json' # Tweet activity stats
CC_TOP_CONTRIB_URL = 'https://metrics.mozilla.com/stats/twitter/armyOfAwesomeTopSoldiers.json' # Top contributor stats