268 строки
9.8 KiB
Python
268 строки
9.8 KiB
Python
from django import http
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.template import Context, loader
|
|
|
|
import commonware.log
|
|
import jingo
|
|
from tower import ugettext as _
|
|
from mobility.decorators import mobile_template
|
|
|
|
import amo
|
|
from amo import messages
|
|
from amo.decorators import (json_view, login_required, post_required,
|
|
restricted_content)
|
|
from amo.helpers import absolutify, shared_url
|
|
import amo.utils
|
|
from access import acl
|
|
from addons.decorators import addon_view_factory, has_purchased
|
|
from addons.models import Addon
|
|
|
|
from .helpers import user_can_delete_review
|
|
from .models import Review, ReviewFlag, GroupedRating, Spam
|
|
from . import forms
|
|
|
|
log = commonware.log.getLogger('z.reviews')
|
|
addon_view = addon_view_factory(qs=Addon.objects.valid)
|
|
|
|
|
|
def send_mail(template, subject, emails, context, perm_setting):
|
|
template = loader.get_template(template)
|
|
amo.utils.send_mail(subject, template.render(Context(context,
|
|
autoescape=False)),
|
|
recipient_list=emails, perm_setting=perm_setting)
|
|
|
|
|
|
@addon_view
|
|
@mobile_template('reviews/{mobile/}review_list.html')
|
|
def review_list(request, addon, review_id=None, user_id=None, template=None):
|
|
q = (Review.objects.valid().filter(addon=addon)
|
|
.order_by('-created'))
|
|
|
|
ctx = {'addon': addon,
|
|
'grouped_ratings': GroupedRating.get(addon.id)}
|
|
|
|
ctx['form'] = forms.ReviewForm(None)
|
|
|
|
if review_id is not None:
|
|
ctx['page'] = 'detail'
|
|
# If this is a dev reply, find the first msg for context.
|
|
review = get_object_or_404(Review.objects.all(), pk=review_id)
|
|
if review.reply_to_id:
|
|
review_id = review.reply_to_id
|
|
ctx['reply'] = review
|
|
q = q.filter(pk=review_id)
|
|
elif user_id is not None:
|
|
ctx['page'] = 'user'
|
|
q = q.filter(user=user_id)
|
|
if not q:
|
|
raise http.Http404()
|
|
else:
|
|
ctx['page'] = 'list'
|
|
q = q.filter(is_latest=True)
|
|
|
|
ctx['reviews'] = reviews = amo.utils.paginate(request, q)
|
|
ctx['replies'] = Review.get_replies(reviews.object_list)
|
|
if request.user.is_authenticated():
|
|
ctx['review_perms'] = {
|
|
'is_admin': acl.action_allowed(request, 'Addons', 'Edit'),
|
|
'is_editor': acl.check_reviewer(request),
|
|
'is_author': acl.check_addon_ownership(request, addon, viewer=True,
|
|
dev=True, support=True),
|
|
}
|
|
ctx['flags'] = get_flags(request, reviews.object_list)
|
|
else:
|
|
ctx['review_perms'] = {}
|
|
return jingo.render(request, template, ctx)
|
|
|
|
|
|
def get_flags(request, reviews):
|
|
reviews = [r.id for r in reviews]
|
|
qs = ReviewFlag.objects.filter(review__in=reviews, user=request.user.id)
|
|
return dict((r.review_id, r) for r in qs)
|
|
|
|
|
|
@addon_view
|
|
@post_required
|
|
@login_required(redirect=False)
|
|
@json_view
|
|
def flag(request, addon, review_id):
|
|
review = get_object_or_404(Review, pk=review_id, addon=addon)
|
|
if review.user_id == request.user.id:
|
|
raise http.Http404()
|
|
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 = forms.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))
|
|
|
|
|
|
@addon_view
|
|
@post_required
|
|
@login_required(redirect=False)
|
|
def delete(request, addon, review_id):
|
|
review = get_object_or_404(Review.objects, pk=review_id, addon=addon)
|
|
if not user_can_delete_review(request, review):
|
|
raise PermissionDenied
|
|
review.delete()
|
|
|
|
log.info(u'Review deleted: %s deleted id:%s by %s ("%s": "%s")' %
|
|
(request.amo_user.name, review_id, review.user.name, review.title,
|
|
review.body))
|
|
return http.HttpResponse()
|
|
|
|
|
|
def _review_details(request, addon, form):
|
|
version = addon.current_version and addon.current_version.id
|
|
d = dict(addon_id=addon.id, user_id=request.user.id,
|
|
version_id=version,
|
|
ip_address=request.META.get('REMOTE_ADDR', ''))
|
|
d.update(**form.cleaned_data)
|
|
return d
|
|
|
|
|
|
@addon_view
|
|
@login_required
|
|
def reply(request, addon, review_id):
|
|
is_admin = acl.action_allowed(request, 'Addons', 'Edit')
|
|
is_author = acl.check_addon_ownership(request, addon, dev=True)
|
|
if not (is_admin or is_author):
|
|
raise PermissionDenied
|
|
|
|
review = get_object_or_404(Review.objects, pk=review_id, addon=addon)
|
|
form = forms.ReviewReplyForm(request.POST or None)
|
|
if request.method == 'POST' and form.is_valid():
|
|
d = dict(reply_to=review, addon=addon,
|
|
defaults=dict(user=request.amo_user))
|
|
reply, new = Review.objects.get_or_create(**d)
|
|
for key, val in _review_details(request, addon, form).items():
|
|
setattr(reply, key, val)
|
|
reply.save()
|
|
action = 'New' if new else 'Edited'
|
|
log.debug('%s reply to %s: %s' % (action, review_id, reply.id))
|
|
|
|
if new:
|
|
reply_url = shared_url('reviews.detail', addon, review.id,
|
|
add_prefix=False)
|
|
data = {'name': addon.name,
|
|
'reply_title': reply.title,
|
|
'reply': reply.body,
|
|
'reply_url': absolutify(reply_url)}
|
|
emails = [review.user.email]
|
|
sub = u'Mozilla Add-on Developer Reply: %s' % addon.name
|
|
send_mail('reviews/emails/reply_review.ltxt',
|
|
sub, emails, Context(data), 'reply')
|
|
|
|
return redirect(shared_url('reviews.detail', addon, review_id))
|
|
ctx = dict(review=review, form=form, addon=addon)
|
|
return jingo.render(request, 'reviews/reply.html', ctx)
|
|
|
|
|
|
@addon_view
|
|
@mobile_template('reviews/{mobile/}add.html')
|
|
@login_required
|
|
@restricted_content
|
|
@has_purchased
|
|
def add(request, addon, template=None):
|
|
if addon.has_author(request.user):
|
|
raise PermissionDenied
|
|
form = forms.ReviewForm(request.POST or None)
|
|
if (request.method == 'POST' and form.is_valid() and
|
|
not request.POST.get('detailed')):
|
|
details = _review_details(request, addon, form)
|
|
review = Review.objects.create(**details)
|
|
if 'flag' in form.cleaned_data and form.cleaned_data['flag']:
|
|
rf = ReviewFlag(review=review,
|
|
user_id=request.user.id,
|
|
flag=ReviewFlag.OTHER,
|
|
note='URLs')
|
|
rf.save()
|
|
|
|
amo.log(amo.LOG.ADD_REVIEW, addon, review)
|
|
log.debug('New review: %s' % review.id)
|
|
|
|
reply_url = shared_url('reviews.reply', addon, review.id,
|
|
add_prefix=False)
|
|
data = {'name': addon.name,
|
|
'rating': '%s out of 5 stars' % details['rating'],
|
|
'review': details['body'],
|
|
'reply_url': absolutify(reply_url)}
|
|
|
|
emails = [a.email for a in addon.authors.all()]
|
|
send_mail('reviews/emails/add_review.ltxt',
|
|
u'Mozilla Add-on User Review: %s' % addon.name,
|
|
emails, Context(data), 'new_review')
|
|
|
|
return redirect(shared_url('reviews.list', addon))
|
|
return jingo.render(request, template, dict(addon=addon, form=form))
|
|
|
|
|
|
@addon_view
|
|
@json_view
|
|
@login_required(redirect=False)
|
|
@post_required
|
|
def edit(request, addon, review_id):
|
|
review = get_object_or_404(Review, pk=review_id, addon=addon)
|
|
is_admin = acl.action_allowed(request, 'Addons', 'Edit')
|
|
if not (request.user.id == review.user.id or is_admin):
|
|
raise PermissionDenied
|
|
cls = forms.ReviewReplyForm if review.reply_to else forms.ReviewForm
|
|
form = cls(request.POST)
|
|
if form.is_valid():
|
|
for field in form.fields:
|
|
if field in form.cleaned_data:
|
|
setattr(review, field, form.cleaned_data[field])
|
|
amo.log(amo.LOG.EDIT_REVIEW, addon, review)
|
|
review.save()
|
|
return http.HttpResponse()
|
|
else:
|
|
return json_view.error(form.errors)
|
|
|
|
|
|
@login_required
|
|
def spam(request):
|
|
if not acl.action_allowed(request, 'Spam', 'Flag'):
|
|
raise PermissionDenied
|
|
spam = Spam()
|
|
|
|
if request.method == 'POST':
|
|
review = Review.objects.get(pk=request.POST['review'])
|
|
if 'del_review' in request.POST:
|
|
log.info('SPAM: %s' % review.id)
|
|
delete(request, request.POST['addon'], review.id)
|
|
messages.success(request, 'Deleted that review.')
|
|
elif 'del_user' in request.POST:
|
|
user = review.user
|
|
log.info('SPAMMER: %s deleted %s' %
|
|
(request.amo_user.username, user.username))
|
|
if not user.is_developer:
|
|
Review.objects.filter(user=user).delete()
|
|
user.anonymize()
|
|
messages.success(request, 'Deleted that dirty spammer.')
|
|
|
|
for reason in spam.reasons():
|
|
spam.redis.srem(reason, review.id)
|
|
return http.HttpResponseRedirect(request.path)
|
|
|
|
buckets = {}
|
|
for reason in spam.reasons():
|
|
ids = spam.redis.smembers(reason)
|
|
key = reason.split(':')[-1]
|
|
buckets[key] = Review.objects.no_cache().filter(id__in=ids)
|
|
reviews = dict((review.addon_id, review) for bucket in buckets.values()
|
|
for review in bucket)
|
|
for addon in Addon.objects.no_cache().filter(id__in=reviews):
|
|
reviews[addon.id].addon = addon
|
|
return jingo.render(request, 'reviews/spam.html',
|
|
dict(buckets=buckets,
|
|
review_perms=dict(is_admin=True)))
|