implement report abuse for Marketplace detail page(bug 740283)

This commit is contained in:
Chris Van 2012-04-16 18:30:04 -07:00
Родитель ef1f3b71d8
Коммит dbaabe5f01
17 изменённых файлов: 266 добавлений и 21 удалений

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

@ -19,10 +19,8 @@
<div id="recaptcha_image"></div>
<p>
<input type="text" name="recaptcha_response_field"
id="recaptcha_response_field" size="30" />
id="recaptcha_response_field" size="30">
</p>
</div>
</li>
{{ form.recaptcha.errors }}

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

@ -1193,6 +1193,7 @@ RECAPTCHA_PUBLIC_KEY = ''
RECAPTCHA_PRIVATE_KEY = ''
RECAPTCHA_URL = ('https://www.google.com/recaptcha/api/challenge?k=%s' %
RECAPTCHA_PUBLIC_KEY)
RECAPTCHA_AJAX_URL = 'http://www.google.com/recaptcha/api/js/recaptcha_ajax.js'
# Send Django signals asynchronously on a background thread.
ASYNC_SIGNALS = True

55
media/css/mkt/abuse.less Normal file
Просмотреть файл

@ -0,0 +1,55 @@
@import 'lib';
.recaptcha-container {
position: relative;
}
#abuse {
ol {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
#recap-container {
margin-top: 15px;
}
#label {
display: block;
font-weight: normal;
margin-bottom: 15px;
}
}
#recap-container {
.border-radius(5px);
background: @yellow;
border: 1px solid darken(@yellow, 60%);
padding: 1em;
margin-bottom: 1em;
position: relative;
label {
color: @gold;
font-weight: bold;
}
input {
width: 300px;
}
}
#recaptcha_help {
color: @gray;
float: right;
font-size: 11px;
}
#recaptcha_widget_div {
display: none !important;
}
@media (max-width: @4col) {
#recaptcha_image {
width: 100% !important;
}
}

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

@ -164,6 +164,9 @@ p.req {
display: inline-block;
margin-bottom: 5px;
}
.errorlist {
margin-top: 5px;
}
}
form {

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

@ -50,6 +50,7 @@
@bg: #dee3e6;
@pale-bg: #eff1f3;
@yellow: #ffe;
@gold: darken(@yellow, 75%);
@btn-b: #44A5E1;
@btn-b-dark: #267CC2;

15
media/js/mkt/abuse.js Normal file
Просмотреть файл

@ -0,0 +1,15 @@
(function() {
z.page.on('fragmentloaded', function() {
var $abuse = $('#abuse');
if ($abuse.find('form legend a').length) {
var $ol = $abuse.find('ol');
$ol.hide();
}
var RecaptchaOptions = {theme: 'custom'};
z.page.on('click', '#recaptcha_different', _pd(function() {
Recaptcha.reload();
})).on('click', '#recaptcha_audio', _pd(function() {
Recaptcha.switch_type('audio');
}));
});
})();

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

@ -11,6 +11,7 @@
if (e.metaKey || e.ctrlKey || e.button !== 0) return;
if (!href || href.substr(0,4) == 'http' ||
href.substr(0,7) === 'mailto:' ||
href.substr(0,11) === 'javascript:' ||
href.substr(0,1) === '#' ||
href.indexOf('/developers/') !== -1 ||
href.indexOf('/statistics/') !== -1 ||

12
media/js/mkt/recaptcha.js Normal file
Просмотреть файл

@ -0,0 +1,12 @@
z.page.on('fragmentloaded', function() {
if ($('#recaptcha_div').length) {
var recaptcha = $('body').data('recaptcha');
if (recaptcha) {
Recaptcha.create(recaptcha, 'recaptcha_div', {
tabindex: 1,
theme: 'red',
callback: Recaptcha.focus_response_field
});
}
}
});

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

@ -83,6 +83,7 @@ CSS = {
'css/mkt/detail.less',
'css/mkt/ratings.less',
'css/mkt/device.less',
'css/mkt/abuse.less',
'css/mkt/slider.less',
'css/mkt/promo-grid.less',
'css/mkt/overlay.less',
@ -189,6 +190,7 @@ JS = {
'js/common/keys.js',
'js/mkt/capabilities.js',
'js/mkt/fragments.js',
'js/mkt/recaptcha.js',
'js/mkt/overlay.js',
'js/mkt/slider.js',
'js/mkt/login.js',
@ -197,6 +199,7 @@ JS = {
'js/mkt/buttons.js',
'js/mkt/search.js',
'js/mkt/apps.js',
'js/mkt/abuse.js',
'js/zamboni/outgoing_links.js',
'js/common/upload-image.js',
'js/impala/serializers.js',

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

@ -0,0 +1,59 @@
{% extends 'mkt/base.html' %}
{% set title = _('Report Abuse') %}
{% block title %}
{# L10n: {0} is the name of the app. #}
{{ mkt_page_title(_('Report abuse for {0}')|f(product.name)) }}
{% endblock %}
{% block content %}
{{ mkt_breadcrumbs(product, [(None, title)]) }}
<section id="abuse" class="friendly">
<div>
<h2>{{ title }}</h2>
<p>
<label id="label" for="{{ abuse_form.text.auto_id }}">
{% trans policies_url='https://developer.mozilla.org/en/Apps/'
'Marketplace_Review' %}
If you suspect this app violates
<a href="{{ policies_url }}">our policies</a> or has security or
privacy issues, please use the form below to describe your
concerns. Please do not use this form for any other reason.
{% endtrans %}
</label>
</p>
<form method="post">
{{ csrf() }}
{{ grid_field(abuse_form.text, req=False, label='') }}
{% if abuse_form.has_recaptcha %}
{% from 'includes/forms.html' import required %}
<div id="recap-container">
<label for="recaptcha_response_field">
{{ _('Are you human?') }} {{ required() }}
<a href="#" id="recaptcha_help">{{ _("What's this?") }}</a>
</label>
<div class="recaptcha-container">
{% trans %}
<p>
Please enter <strong>both words</strong> below,
<strong>separated by a space</strong>.
</p>
<p>
If this is hard to read, you can
<a href="#" id="recaptcha_different">try different words</a> or
<a href="#" id="recaptcha_audio">listen to something</a> instead.
</p>
{% endtrans %}
<div id="recaptcha_div"></div>
</div>
</div>
{{ abuse_form.recaptcha.errors }}
{% endif %}
<p class="form-footer">
<button type="submit">{{ _('Send Report') }}</button> {{ _('or') }}
<a href="{{ product.get_detail_url() }}">{{ _('Cancel') }}</a>
</p>
</form>
</div>
</section>
{% endblock %}

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

@ -228,6 +228,22 @@
<p>
{{ product.created|timelabel }}
</p>
{% endif %}
</li>
<li class="published">
<h3>{{ _('Published') }}</h3>
<p>
<time isotime="{{ product.created|isotime }}"
title="{{ product.created|isotime }}">
{{ product.created|timesince }}
</time>
</p>
</li>
{% if abuse_form %}
<li class="abuse">
<h3>{{ _('Report Abuse') }}</h3>
<p><a href="{{ product.get_detail_url('abuse') }}">
{{ _('Report') }}</a></p>
</li>
{% endif %}
</ul>

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

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import json
from django.conf import settings
from django.core import mail
from django.utils.html import strip_tags
import mock
@ -10,6 +12,7 @@ from pyquery import PyQuery as pq
from tower import strip_whitespace
import waffle
from abuse.models import AbuseReport
import amo
from amo.helpers import external_url, numberfmt
import amo.tests
@ -626,22 +629,52 @@ class TestPrivacy(amo.tests.TestCase):
eq_(get_clean(doc('ol li:first')), '<li>papparapara2</li>')
class TestReportAbuse(amo.tests.TestCase):
@mock.patch.object(settings, 'RECAPTCHA_PRIVATE_KEY', 'something')
class TestReportAbuse(DetailBase):
fixtures = ['base/users', 'webapps/337141-steamcube']
def setUp(self):
self.webapp = Webapp.objects.get(id=337141)
super(TestReportAbuse, self).setUp()
self.url = self.webapp.get_detail_url('abuse')
def test_get(self):
# TODO: Uncomment Report Abuse gets ported to mkt.
raise SkipTest
r = self.client.get(self.url)
eq_(r.status_code, 200)
def log_in(self):
assert self.client.login(username='regular@mozilla.com',
password='password')
def test_submit(self):
# TODO: Uncomment Report Abuse gets ported to mkt.
raise SkipTest
self.client.login(username='regular@mozilla.com', password='password')
r = self.client.post(self.url, {'text': 'this is some rauncy ish'})
self.assertRedirects(r, self.webapp.get_detail_url())
def test_recaptcha_shown_for_anonymous(self):
eq_(self.get_pq()('#recap-container').length, 1)
def test_no_recaptcha_for_authenticated(self):
self.log_in()
eq_(self.get_pq()('#recap-container').length, 0)
@mock.patch('captcha.fields.ReCaptchaField.clean', new=mock.Mock)
def test_abuse_anonymous(self):
self.client.post(self.url, {'text': 'spammy'})
eq_(len(mail.outbox), 1)
assert 'spammy' in mail.outbox[0].body
report = AbuseReport.objects.get(addon=337141)
eq_(report.message, 'spammy')
eq_(report.reporter, None)
def test_abuse_anonymous_fails(self):
r = self.client.post(self.url, {'text': 'spammy'})
assert 'recaptcha' in r.context['abuse_form'].errors
def test_abuse_authenticated(self):
self.log_in()
self.client.post(self.url, {'text': 'spammy'})
eq_(len(mail.outbox), 1)
assert 'spammy' in mail.outbox[0].body
report = AbuseReport.objects.get(addon=337141)
eq_(report.message, 'spammy')
eq_(report.reporter.email, 'regular@mozilla.com')
def test_abuse_name(self):
self.webapp.name = 'Bmrk.ru Социальные закладки'
self.webapp.save()
self.log_in()
self.client.post(self.url, {'text': 'spammy'})
assert 'spammy' in mail.outbox[0].body
assert AbuseReport.objects.get(addon=self.webapp)

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

@ -7,10 +7,12 @@ from . import views
urlpatterns = patterns('',
url('^$', views.detail, name='detail'),
url('^abuse$', views.abuse, name='detail.abuse'),
url('^abuse/recaptcha$', views.abuse_recaptcha,
name='detail.abuse.recaptcha'),
url('^record$', views.record, name='detail.record'),
url('^privacy$', views.privacy, name='detail.privacy'),
# Submission.
('^purchase/', include('mkt.purchase.urls')),
# Statistics.

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

@ -2,15 +2,23 @@ import jingo
from tower import ugettext as _
from django import http
from django.shortcuts import redirect
from commonware.response.decorators import xframe_sameorigin
from session_csrf import anonymous_csrf_exempt
from tower import ugettext as _
from abuse.models import send_abuse_report
from access import acl
from addons.decorators import addon_view_factory
from amo.decorators import json_view, login_required, post_required, write
from amo.forms import AbuseForm
from amo.utils import memoize_get
from lib.metrics import send_request
from lib.crypto.receipt import cef, SigningError
from mkt.ratings.models import Rating
from mkt.site import messages
from mkt.webapps.models import create_receipt, Installed, Webapp
addon_view = addon_view_factory(qs=Webapp.objects.valid)
@ -20,15 +28,19 @@ addon_all_view = addon_view_factory(qs=Webapp.objects.all)
@addon_all_view
def detail(request, addon):
"""Product details page."""
ratings = Rating.objects.latest().filter(addon=addon).order_by('-created')
positive_ratings = ratings.filter(score=1)[:5]
negative_ratings = ratings.filter(score=-1)[:5]
return jingo.render(request, 'detail/app.html', {
ctx = {
'product': addon,
'ratings': ratings,
'positive_ratings': positive_ratings,
'negative_ratings': negative_ratings,
})
}
if addon.is_public():
ctx['abuse_form'] = AbuseForm(request=request)
return jingo.render(request, 'detail/app.html', ctx)
@addon_all_view
@ -42,6 +54,32 @@ def privacy(request, addon):
return jingo.render(request, 'detail/privacy.html', {'product': addon})
@anonymous_csrf_exempt
@addon_view
def abuse(request, addon):
form = AbuseForm(request.POST or None, request=request)
if request.method == 'POST' and form.is_valid():
send_abuse_report(request, addon, form.cleaned_data['text'])
messages.success(request, _('Abuse reported.'))
return redirect(addon.get_url_path())
else:
return jingo.render(request, 'detail/abuse.html',
{'product': addon, 'abuse_form': form})
@anonymous_csrf_exempt
@addon_view
def abuse_recaptcha(request, addon):
form = AbuseForm(request.POST or None, request=request)
if request.method == 'POST' and form.is_valid():
send_abuse_report(request, addon, form.cleaned_data['text'])
messages.success(request, _('Abuse reported.'))
return redirect(addon.get_url_path())
else:
return jingo.render(request, 'detail/abuse_recaptcha.html',
{'product': addon, 'abuse_form': form})
@json_view
@addon_all_view
@login_required

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

@ -29,6 +29,7 @@ MKT_SUPPORT_EMAIL = 'marketplace-developer-support@mozilla.org'
MARKETPLACE_EMAIL = 'amo-marketplace@mozilla.org'
ABUSE_EMAIL = 'Mozilla Marketplace <marketplace-abuse@mozilla.org>'
NOBODY_EMAIL = 'Mozilla Marketplace <nobody@mozilla.org>'
DEFAULT_FROM_EMAIL = 'Mozilla Marketplace <nobody@mozilla.org>'
ROOT_URLCONF = 'mkt.urls'

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

@ -3,7 +3,7 @@
{% macro simple_label(field, label, opt, req, tooltip, hint) %}
<label class="{{ 'choice' if choice }}" for="{{ field.auto_id }}">
{{ label or field.label }}
{{ label }}
</label>
{% if field.field.required and req != False %}{{ required() -}}{% endif %}
@ -24,7 +24,12 @@
{% if choice %}{{ field.as_widget() }}{% endif %}
{% endif %}
{{ simple_label(field, label, opt, req, tooltip, hint) }}
{% if not label %}
{% set label = field.label %}
{% endif %}
{% if label %}
{{ simple_label(field, label, opt, req, tooltip, hint) }}
{% endif %}
{% if grid %}
</div>

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

@ -39,6 +39,7 @@
data-media-url="{{ MEDIA_URL }}"
data-login-url="{{ url('users.browserid_login') }}"
data-collect-timings="{{ url('mkt.timing.record') }}:{{ collect_timings_percent }}"
data-recaptcha="{{ settings.RECAPTCHA_PUBLIC_KEY }}"
{% block bodyattrs %}{% endblock %}>
{% block siteheader %}
@ -159,6 +160,7 @@
{% if not logged %}
{# We need Persona for only non-authenticated users. #}
<script async defer src="https://browserid.org/include.js"></script>
<script type="text/javascript" src="{{ settings.RECAPTCHA_AJAX_URL }}"></script>
{% endif %}
{{ js('mkt/consumer') }}
{% endblock %}