This commit is contained in:
Jeff Balogh 2010-06-28 17:52:32 -07:00
Родитель 48e722729a
Коммит 371c1d26d7
12 изменённых файлов: 229 добавлений и 11 удалений

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

@ -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
}
}
]

15
apps/reviews/forms.py Normal file
Просмотреть файл

@ -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',