enabling ajax review flagging
This commit is contained in:
Родитель
48e722729a
Коммит
371c1d26d7
|
@ -0,0 +1,51 @@
|
|||
[
|
||||
{
|
||||
"pk": 4043307,
|
||||
"model": "auth.user",
|
||||
"fields": {
|
||||
"username": "jbalogh",
|
||||
"first_name": "Jeff",
|
||||
"last_name": "Balogh",
|
||||
"is_active": 1,
|
||||
"is_superuser": 1,
|
||||
"is_staff": 1,
|
||||
"last_login": "2010-01-13 17:17:23",
|
||||
"groups": [],
|
||||
"user_permissions": [],
|
||||
"password": "sha512$7b5436061f8c0902088c292c057be69fdb17312e2f71607c9c51641f5d876522$08d1d370d89e2ae92755fd03464a7276ca607c431d04a52d659f7a184f3f9918073637d82fc88981c7099c7c46a1137b9fdeb675304eb98801038905a9ee0600",
|
||||
"email": "jbalogh@mozilla.com",
|
||||
"date_joined": "2009-02-02 11:50:31"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4043307,
|
||||
"model": "users.userprofile",
|
||||
"fields": {
|
||||
"sandboxshown": 1,
|
||||
"display_collections_fav": 1,
|
||||
"display_collections": 1,
|
||||
"occupation": "",
|
||||
"confirmationcode": "",
|
||||
"location": "",
|
||||
"picture_type": "",
|
||||
"averagerating": "",
|
||||
"homepage": "http://jeffbalogh.org",
|
||||
"email": "jbalogh@mozilla.com",
|
||||
"notifycompat": 1,
|
||||
"bio": null,
|
||||
"firstname": "Jeff",
|
||||
"deleted": 0,
|
||||
"lastname": "Balogh",
|
||||
"emailhidden": 0,
|
||||
"user": 4043307,
|
||||
"password": "sha512$7b5436061f8c0902088c292c057be69fdb17312e2f71607c9c51641f5d876522$08d1d370d89e2ae92755fd03464a7276ca607c431d04a52d659f7a184f3f9918073637d82fc88981c7099c7c46a1137b9fdeb675304eb98801038905a9ee0600",
|
||||
"nickname": "jbalogh",
|
||||
"resetcode_expires": "2010-01-12 15:28:07",
|
||||
"resetcode": "",
|
||||
"created": "2009-02-02 11:50:31",
|
||||
"notes": "",
|
||||
"modified": "2010-01-12 17:01:41",
|
||||
"notifyevents": 1
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
from django import forms
|
||||
|
||||
from .models import ReviewFlag
|
||||
|
||||
|
||||
class ReviewFlagForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ReviewFlag
|
||||
|
||||
def clean(self):
|
||||
data = super(ReviewFlagForm, self).clean()
|
||||
if 'note' in data and data['note'].strip():
|
||||
data['flag'] = 'other'
|
||||
return data
|
|
@ -3,6 +3,8 @@ import itertools
|
|||
from django.db import models
|
||||
from django.utils import translation
|
||||
|
||||
from tower import ugettext_lazy as _
|
||||
|
||||
import amo.models
|
||||
from translations.fields import TranslatedField
|
||||
from translations.models import Translation
|
||||
|
@ -123,9 +125,22 @@ models.signals.post_save.connect(Review.post_save, sender=Review)
|
|||
models.signals.post_delete.connect(Review.post_delete, sender=Review)
|
||||
|
||||
|
||||
# TODO: translate old flags.
|
||||
class ReviewFlag(amo.models.ModelBase):
|
||||
FLAGS = (
|
||||
('spam', _('Spam or otherwise non-review content')),
|
||||
('language', _('Inappropriate language/dialog')),
|
||||
('bug_support', _('Misplaced bug report or support request')),
|
||||
('other', _('Other (please specify)')),
|
||||
)
|
||||
|
||||
review = models.ForeignKey(Review)
|
||||
user = models.ForeignKey('users.UserProfile')
|
||||
name = models.CharField(max_length=64, default='review_flag_reason_other',
|
||||
db_column='flag_name')
|
||||
notes = models.CharField(max_length=100, db_column='flag_notes')
|
||||
flag = models.CharField(max_length=64, default='other',
|
||||
choices=FLAGS, db_column='flag_name')
|
||||
note = models.CharField(max_length=100, db_column='flag_notes', blank=True,
|
||||
default='')
|
||||
|
||||
class Meta:
|
||||
db_table = 'reviews_moderation_flags'
|
||||
unique_together = (('review', 'user'),)
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
{% endif %}
|
||||
{% if request.user.is_authenticated() %}
|
||||
<ul>
|
||||
<li><a href="#TODO">{{ _('Report this review') }}</a></li>
|
||||
<li><a class="flag-review" href="{{ url('reviews.flag', addon.id, review.id) }}">
|
||||
{{ _('Report this review') }}</a></li>
|
||||
{% if perms.is_author or perms.is_admin %}
|
||||
<li><a href="#TODO">{{ _('Reply to review') }}</a></li>
|
||||
{% endif %}
|
||||
|
|
|
@ -72,4 +72,20 @@
|
|||
{{ _('Write a New Review') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden">
|
||||
<div class="install-note review-reason">
|
||||
<strong>{{ _('Please select a reason:') }}</strong>
|
||||
<ul>
|
||||
{% for flag, text in ReviewFlag.FLAGS %}
|
||||
<li><a href="#{{ flag }}">{{ text }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{# Using a fake form so we get submission on keyboard-enter. #}
|
||||
<form class="other-note" method="POST" action="">
|
||||
{{ flag_form.note|safe }}
|
||||
<input type="submit" value="{{ _('Submit') }}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
from django import http
|
||||
|
||||
from nose.tools import eq_
|
||||
import test_utils
|
||||
|
||||
from amo.urlresolvers import reverse
|
||||
from reviews.models import Review, ReviewFlag
|
||||
|
||||
|
||||
class TestViews(test_utils.TestCase):
|
||||
|
@ -9,3 +13,49 @@ class TestViews(test_utils.TestCase):
|
|||
def test_dev_reply(self):
|
||||
url = reverse('reviews.detail', args=[1865, 218468])
|
||||
r = self.client.get(url)
|
||||
|
||||
|
||||
class TestFlag(test_utils.TestCase):
|
||||
fixtures = ['reviews/dev-reply.json', 'base/admin']
|
||||
|
||||
def setUp(self):
|
||||
self.url = reverse('reviews.flag', args=[1865, 218468])
|
||||
self.client.login(username='jbalogh@mozilla.com', password='password')
|
||||
|
||||
def test_no_login(self):
|
||||
self.client.logout()
|
||||
response = self.client.post(self.url)
|
||||
assert isinstance(response, http.HttpResponseRedirect)
|
||||
|
||||
def test_new_flag(self):
|
||||
response = self.client.post(self.url, {'flag': 'spam'})
|
||||
eq_(response.status_code, 200)
|
||||
eq_(response.content, '{"msg": "Thanks; this review has been '
|
||||
'flagged for editor approval."}')
|
||||
eq_(ReviewFlag.objects.filter(flag='spam').count(), 1)
|
||||
eq_(Review.objects.filter(editorreview=True).count(), 1)
|
||||
|
||||
def test_update_flag(self):
|
||||
response = self.client.post(self.url, {'flag': 'spam'})
|
||||
eq_(response.status_code, 200)
|
||||
eq_(ReviewFlag.objects.filter(flag='spam').count(), 1)
|
||||
eq_(Review.objects.filter(editorreview=True).count(), 1)
|
||||
|
||||
response = self.client.post(self.url, {'flag': 'language'})
|
||||
eq_(response.status_code, 200)
|
||||
eq_(ReviewFlag.objects.filter(flag='language').count(), 1)
|
||||
eq_(ReviewFlag.objects.count(), 1)
|
||||
eq_(Review.objects.filter(editorreview=True).count(), 1)
|
||||
|
||||
def test_flag_with_note(self):
|
||||
response = self.client.post(self.url, {'flag': 'spam', 'note': 'xxx'})
|
||||
eq_(response.status_code, 200)
|
||||
eq_(ReviewFlag.objects.filter(flag='other').count(), 1)
|
||||
eq_(ReviewFlag.objects.count(), 1)
|
||||
eq_(ReviewFlag.objects.get(flag='other').note, 'xxx')
|
||||
eq_(Review.objects.filter(editorreview=True).count(), 1)
|
||||
|
||||
def test_bad_flag(self):
|
||||
response = self.client.post(self.url, {'flag': 'xxx'})
|
||||
eq_(response.status_code, 400)
|
||||
eq_(Review.objects.filter(editorreview=True).count(), 0)
|
||||
|
|
|
@ -5,5 +5,6 @@ from . import views
|
|||
urlpatterns = patterns('',
|
||||
url('^$', views.review_list, name='reviews.list'),
|
||||
url('^(?P<review_id>\d+)$', views.review_list, name='reviews.detail'),
|
||||
url('^(?P<review_id>\d+)/flag$', views.flag, name='reviews.flag'),
|
||||
url('^user:(?P<user_id>\d+)$', views.review_list, name='reviews.user'),
|
||||
)
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
import jingo
|
||||
from tower import ugettext as _
|
||||
|
||||
import amo.utils
|
||||
from amo.decorators import post_required, json_view
|
||||
from access import acl
|
||||
from addons.models import Addon
|
||||
from versions.models import Version
|
||||
|
||||
from .models import Review
|
||||
from .models import Review, ReviewFlag
|
||||
from .forms import ReviewFlagForm
|
||||
|
||||
|
||||
def review_list(request, addon_id, review_id=None, user_id=None):
|
||||
|
@ -15,7 +18,9 @@ def review_list(request, addon_id, review_id=None, user_id=None):
|
|||
q = (Review.objects.valid().filter(addon=addon)
|
||||
.order_by('-created'))
|
||||
|
||||
ctx = {'addon': addon}
|
||||
ctx = {'addon': addon, 'ReviewFlag': ReviewFlag,
|
||||
'flag_form': ReviewFlagForm()}
|
||||
|
||||
if review_id is not None:
|
||||
ctx['page'] = 'detail'
|
||||
# If this is a dev reply, find the first msg for context.
|
||||
|
@ -49,3 +54,23 @@ def get_replies(reviews):
|
|||
reviews = [r.id for r in reviews]
|
||||
qs = Review.objects.filter(reply_to__in=reviews)
|
||||
return dict((r.reply_to_id, r) for r in qs)
|
||||
|
||||
|
||||
@post_required
|
||||
@login_required # TODO: return a 401?
|
||||
@json_view
|
||||
def flag(request, addon_id, review_id):
|
||||
d = dict(review=review_id, user=request.user.id)
|
||||
try:
|
||||
instance = ReviewFlag.objects.get(**d)
|
||||
except ReviewFlag.DoesNotExist:
|
||||
instance = None
|
||||
data = dict(request.POST.items(), **d)
|
||||
form = ReviewFlagForm(data, instance=instance)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
Review.objects.filter(id=review_id).update(editorreview=True)
|
||||
return {'msg': _('Thanks; this review has been flagged '
|
||||
'for editor approval.')}
|
||||
else:
|
||||
return json_view.error(unicode(form.errors))
|
||||
|
|
|
@ -116,3 +116,7 @@
|
|||
form.go button {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.flag-review {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -2118,10 +2118,6 @@ form .error .note.error {
|
|||
border-top-color: #0471ed;
|
||||
}
|
||||
|
||||
.primary > .warning {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefox Cup promo styles
|
||||
* TODO remove these when promo is pulled
|
||||
|
@ -2173,4 +2169,10 @@ form .error .note.error {
|
|||
* TODO remove these when promo is pulled
|
||||
**/
|
||||
|
||||
.other-note {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.other .other-note {
|
||||
display: inherit;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
$(document).ready(function() {
|
||||
var report = $('.review-reason').parent().html();
|
||||
$('.flag-review').addPopup(report)
|
||||
.bind('newPopup', function(e, popup) {
|
||||
// If there's a click on one of the flag links, submit it.
|
||||
// If they pick other, show the extra text field.
|
||||
$(popup).click(function(e) {
|
||||
var parent = $(this).parent(),
|
||||
url = parent.find('.flag-review').attr('href');
|
||||
if ($(e.target).filter('a').length) {
|
||||
e.preventDefault();
|
||||
var flag = $(e.target).attr('href').slice(1)
|
||||
if (flag == 'other') {
|
||||
// Show the Other form and bind the submit.
|
||||
parent.addClass('other');
|
||||
$(this).find('form').submit(function(e){
|
||||
e.preventDefault();
|
||||
var note = parent.find('#id_note').val();
|
||||
addFlag(url, 'other', note);
|
||||
});
|
||||
} else {
|
||||
addFlag(url, flag, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var addFlag = function(url, flag, note) {
|
||||
$.ajax({type: 'POST',
|
||||
url: url,
|
||||
data: {flag: flag, note: note},
|
||||
success: function(){ alert('success'); },
|
||||
error: function(){ alert('error'); },
|
||||
dataType: 'json'
|
||||
});
|
||||
};
|
||||
});
|
|
@ -358,6 +358,7 @@ MINIFY_BUNDLES = {
|
|||
'js/get-satisfaction-v2.js',
|
||||
'js/zamboni/contributions.js',
|
||||
'js/zamboni/addon_details.js',
|
||||
'js/zamboni/reviews.js',
|
||||
|
||||
# Personas
|
||||
'js/zamboni/jquery.hoverIntent.min.js',
|
||||
|
|
Загрузка…
Ссылка в новой задаче