зеркало из https://github.com/mozilla/kitsune.git
Remove tweets button for Army of Awesome. Bug 624464.
This commit is contained in:
Родитель
391ac6fa39
Коммит
8dac105b96
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче