bug 627543, Recent editor activity.
This commit is contained in:
Родитель
a4782421bb
Коммит
22015c47a2
|
@ -9,7 +9,7 @@ from product_details import firefox_versions, thunderbird_versions
|
|||
from tower import ugettext_lazy as _
|
||||
|
||||
from licenses import license_text
|
||||
from .log import LOG, LOG_BY_ID, LOG_KEEP, log
|
||||
from .log import LOG, LOG_BY_ID, LOG_EDITORS, LOG_KEEP, log
|
||||
from django.utils.translation import trans_real as translation
|
||||
|
||||
logger_log = commonware.log.getLogger('z.amo')
|
||||
|
|
|
@ -246,6 +246,24 @@ class CHANGE_ICON:
|
|||
format = _(u'{addon} icon changed.')
|
||||
|
||||
|
||||
class APPROVE_REVIEW:
|
||||
id = 40
|
||||
format = _(u'{review} for {addon} approved.')
|
||||
editor_format = _(u'{user} approved {review} for {addon}.')
|
||||
keep = True
|
||||
editor_event = True
|
||||
|
||||
|
||||
class DELETE_REVIEW:
|
||||
"""Requires review.id and add-on objects."""
|
||||
id = 41
|
||||
format = _(u'{review} for {addon} deleted.')
|
||||
# TODO(davedash): a {more} link will need to go somewhere
|
||||
editor_format = _(u'{user} deleted review {0}.')
|
||||
keep = True
|
||||
editor_event = True
|
||||
|
||||
|
||||
class CUSTOM_TEXT:
|
||||
id = 98
|
||||
format = '{0}'
|
||||
|
@ -266,11 +284,12 @@ LOGS = (CREATE_ADDON, EDIT_PROPERTIES, EDIT_DESCRIPTIONS, EDIT_CATEGORIES,
|
|||
ADD_TO_COLLECTION, REMOVE_FROM_COLLECTION, ADD_REVIEW,
|
||||
ADD_RECOMMENDED_CATEGORY, REMOVE_RECOMMENDED_CATEGORY, ADD_RECOMMENDED,
|
||||
REMOVE_RECOMMENDED, ADD_APPVERSION, CUSTOM_TEXT, CUSTOM_HTML,
|
||||
CHANGE_USER_WITH_ROLE, CHANGE_LICENSE, CHANGE_POLICY, CHANGE_ICON
|
||||
)
|
||||
CHANGE_USER_WITH_ROLE, CHANGE_LICENSE, CHANGE_POLICY, CHANGE_ICON,
|
||||
APPROVE_REVIEW, DELETE_REVIEW,)
|
||||
LOG_BY_ID = dict((l.id, l) for l in LOGS)
|
||||
LOG = AttributeDict((l.__name__, l) for l in LOGS)
|
||||
LOG_KEEP = [l.id for l in LOGS if hasattr(l, 'keep')]
|
||||
LOG_EDITORS = [l.id for l in LOGS if hasattr(l, 'editor_event')]
|
||||
|
||||
|
||||
def log(action, *args, **kw):
|
||||
|
@ -292,6 +311,7 @@ def log(action, *args, **kw):
|
|||
al = ActivityLog(user=user, action=action.id)
|
||||
al.arguments = args
|
||||
al.save()
|
||||
|
||||
if 'created' in kw:
|
||||
al.created = kw['created']
|
||||
# Double save necessary since django resets the created date on save.
|
||||
|
|
|
@ -17,6 +17,7 @@ from bandwagon.models import Collection
|
|||
from reviews.models import Review
|
||||
from tags.models import Tag
|
||||
from translations.fields import TranslatedField
|
||||
from users.helpers import user_link
|
||||
from users.models import UserProfile
|
||||
from versions.models import Version
|
||||
|
||||
|
@ -114,6 +115,9 @@ class ActivityLogManager(amo.models.ManagerBase):
|
|||
.values_list('activity_log', flat=True))
|
||||
return self.filter(pk__in=list(vals))
|
||||
|
||||
def editor_events(self):
|
||||
return self.filter(action__in=amo.LOG_EDITORS)
|
||||
|
||||
|
||||
class SafeFormatter(string.Formatter):
|
||||
"""A replacement for str.format that escapes interpolated values."""
|
||||
|
@ -153,7 +157,7 @@ class ActivityLog(amo.models.ModelBase):
|
|||
for item in d:
|
||||
# item has only one element.
|
||||
model_name, pk = item.items()[0]
|
||||
if model_name == 'str':
|
||||
if model_name in ('str', 'int'):
|
||||
objs.append(pk)
|
||||
else:
|
||||
(app_label, model_name) = model_name.split('.')
|
||||
|
@ -179,6 +183,8 @@ class ActivityLog(amo.models.ModelBase):
|
|||
for arg in args:
|
||||
if isinstance(arg, basestring):
|
||||
serialize_me.append({'str': arg})
|
||||
elif isinstance(arg, (int, long)):
|
||||
serialize_me.append({'int': arg})
|
||||
elif isinstance(arg, tuple):
|
||||
# Instead of passing an addon instance you can pass a tuple:
|
||||
# (Addon, 3) for Addon with pk=3
|
||||
|
@ -189,8 +195,12 @@ class ActivityLog(amo.models.ModelBase):
|
|||
self._arguments = json.dumps(serialize_me)
|
||||
|
||||
# TODO(davedash): Support other types.
|
||||
def to_string(self, type='default'):
|
||||
def to_string(self, type=None):
|
||||
log_type = amo.LOG_BY_ID[self.action]
|
||||
if type and hasattr(log_type, '%s_format' % type):
|
||||
format = getattr(log_type, '%s_format' % type)
|
||||
else:
|
||||
format = log_type.format
|
||||
|
||||
# We need to copy arguments so we can remove elements from it
|
||||
# while we loop over self.arguments.
|
||||
|
@ -225,10 +235,13 @@ class ActivityLog(amo.models.ModelBase):
|
|||
arg.get_url_path(), arg.tag_text)
|
||||
else:
|
||||
tag = self.f('{0}', arg.tag_text)
|
||||
|
||||
user = user_link(self.user)
|
||||
|
||||
try:
|
||||
kw = dict(addon=addon, review=review, version=version,
|
||||
collection=collection, tag=tag)
|
||||
return self.f(log_type.format, *arguments, **kw)
|
||||
collection=collection, tag=tag, user=user)
|
||||
return self.f(format, *arguments, **kw)
|
||||
except (AttributeError, KeyError, IndexError):
|
||||
log.warning('%d contains garbage data' % (self.id or 0))
|
||||
return 'Something magical happened.'
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
<div class="editor-stats-table">
|
||||
<div>
|
||||
<table>
|
||||
{# TODO(dd): ActivityLog stuff #}
|
||||
{# TODO(gkoberger): MOAR Stats #}
|
||||
{% for i in range(5) %}
|
||||
<tr>
|
||||
<td>John Smith</td>
|
||||
|
@ -62,33 +62,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{# TODO(dd): ActivityLog stuff (this is a placeholder) #}
|
||||
<div class="listing results">
|
||||
<div class="results-inner">
|
||||
{% for item in eventlog %}
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/en-US/firefox/addon/test_addon2/">Kris Maglione</a> deleted review <a href="#">263753</a>.
|
||||
<time title="Jan 6, 2011 3:35:43 PM" datetime="2011-01-06T23:35:43Z">1 week, 6 days ago</time>
|
||||
{{ item.to_string('editor') }}
|
||||
{% trans ago=item.created|timesince, iso=item.created|isotime,
|
||||
pretty=item.created|babel_datetime %}
|
||||
<time datetime="{{ iso }}" title="{{ pretty }}">{{ ago }}</time>
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
# -*- coding: utf8 -*-
|
||||
import re
|
||||
|
||||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
import test_utils
|
||||
|
||||
import amo
|
||||
from amo.urlresolvers import reverse
|
||||
from addons.models import Addon
|
||||
from reviews.models import Review
|
||||
from users.models import UserProfile
|
||||
|
||||
|
||||
class EditorTest(test_utils.TestCase):
|
||||
fixtures = ('base/users', 'editors/pending-queue')
|
||||
|
||||
def login_as_editor(self):
|
||||
assert self.client.login(username='editor@mozilla.com',
|
||||
password='password')
|
||||
|
||||
class TestPendingQueue(EditorTest):
|
||||
fixtures = ['base/users', 'editors/pending-queue']
|
||||
|
||||
class TestHome(EditorTest):
|
||||
"""Test the page at /editors."""
|
||||
def setUp(self):
|
||||
self.login_as_editor()
|
||||
amo.set_user(UserProfile.objects.get(username='editor'))
|
||||
|
||||
def make_review(self):
|
||||
u = UserProfile.objects.create(username='a')
|
||||
a = Addon.objects.create(name='yermom', type=amo.ADDON_EXTENSION)
|
||||
return Review.objects.create(user=u, addon=a)
|
||||
|
||||
def test_approved_review(self):
|
||||
review = self.make_review()
|
||||
amo.log(amo.LOG.APPROVE_REVIEW, review, review.addon)
|
||||
r = self.client.get(reverse('editors.home'))
|
||||
doc = pq(r.content)
|
||||
eq_(doc('.row').eq(0).text().strip().split('.')[0],
|
||||
'editor approved Review for yermom ')
|
||||
|
||||
def test_deleted_review(self):
|
||||
review = self.make_review()
|
||||
amo.log(amo.LOG.DELETE_REVIEW, review.id, review.addon)
|
||||
r = self.client.get(reverse('editors.home'))
|
||||
doc = pq(r.content)
|
||||
eq_(doc('.row').eq(0).text().strip().split('.')[0],
|
||||
'editor deleted review %d' % review.id)
|
||||
|
||||
|
||||
class TestPendingQueue(EditorTest):
|
||||
def setUp(self):
|
||||
super(TestPendingQueue, self).setUp()
|
||||
self.login_as_editor()
|
||||
|
@ -31,7 +60,7 @@ class TestPendingQueue(EditorTest):
|
|||
|
||||
def test_invalid_page(self):
|
||||
r = self.client.get(reverse('editors.queue_pending'),
|
||||
data={'page':999})
|
||||
data={'page': 999})
|
||||
eq_(r.status_code, 200)
|
||||
eq_(r.context['page'].number, 1)
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ import functools
|
|||
|
||||
from django import http
|
||||
from django.shortcuts import redirect
|
||||
from django.core.paginator import Paginator
|
||||
import jingo
|
||||
|
||||
from access import acl
|
||||
from amo.decorators import login_required
|
||||
from devhub.models import ActivityLog
|
||||
from editors.models import ViewEditorQueue
|
||||
from editors.helpers import ViewEditorQueueTable
|
||||
from amo.utils import paginate
|
||||
|
@ -14,6 +14,7 @@ from amo.urlresolvers import reverse
|
|||
from files.models import Approval
|
||||
from zadmin.models import get_config
|
||||
|
||||
|
||||
def editor_required(func):
|
||||
"""Requires the user to be logged in as an editor or admin."""
|
||||
@functools.wraps(func)
|
||||
|
@ -28,9 +29,11 @@ def editor_required(func):
|
|||
|
||||
@editor_required
|
||||
def home(request):
|
||||
data = {'reviews_total': Approval.total_reviews(),
|
||||
'reviews_monthly': Approval.monthly_reviews(),
|
||||
'motd': get_config('editors_review_motd')}
|
||||
data = dict(reviews_total=Approval.total_reviews(),
|
||||
reviews_monthly=Approval.monthly_reviews(),
|
||||
motd=get_config('editors_review_motd'),
|
||||
eventlog=ActivityLog.objects.editor_events()[:6],
|
||||
)
|
||||
|
||||
return jingo.render(request, 'editors/home.html', data)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче