bug 622202, event log and log detail page
This commit is contained in:
Родитель
adbe9b0ac8
Коммит
234fe3ca96
|
@ -310,8 +310,11 @@ def log(action, *args, **kw):
|
|||
|
||||
al = ActivityLog(user=user, action=action.id)
|
||||
al.arguments = args
|
||||
if 'details' in kw:
|
||||
al.details = kw['details']
|
||||
al.save()
|
||||
|
||||
# TODO(davedash): post-remora this may not be necessary.
|
||||
if 'created' in kw:
|
||||
al.created = kw['created']
|
||||
# Double save necessary since django resets the created date on save.
|
||||
|
@ -331,3 +334,4 @@ def log(action, *args, **kw):
|
|||
|
||||
# Index by every user
|
||||
UserLog(activity_log=al, user=user).save()
|
||||
return al
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
"""Tests for the activitylog."""
|
||||
from datetime import datetime
|
||||
|
||||
import test_utils
|
||||
from nose.tools import eq_
|
||||
|
||||
import amo
|
||||
from addons.models import Addon
|
||||
from users.models import UserProfile
|
||||
|
||||
|
||||
class LogTest(test_utils.TestCase):
|
||||
def setUp(self):
|
||||
u = UserProfile.objects.create(username='foo')
|
||||
amo.set_user(u)
|
||||
|
||||
def test_details(self):
|
||||
"""
|
||||
If we get details, verify they are stored as JSON, and we get out what
|
||||
we put in.
|
||||
"""
|
||||
a = Addon.objects.create(name='kumar is awesome',
|
||||
type=amo.ADDON_EXTENSION)
|
||||
magic = dict(title='no', body='way!')
|
||||
al = amo.log(amo.LOG.DELETE_REVIEW, 1, a, details=magic)
|
||||
|
||||
eq_(al.details, magic)
|
||||
eq_(al._details, '{"body": "way!", "title": "no"}')
|
||||
|
||||
def test_created(self):
|
||||
"""
|
||||
Verify that we preserve the create date.
|
||||
"""
|
||||
al = amo.log(amo.LOG.CUSTOM_TEXT, 'hi', created=datetime(2009, 1, 1))
|
||||
|
||||
eq_(al.created, datetime(2009, 1, 1))
|
||||
|
|
@ -133,6 +133,7 @@ class ActivityLog(amo.models.ModelBase):
|
|||
user = models.ForeignKey('users.UserProfile', null=True)
|
||||
action = models.SmallIntegerField(choices=TYPES, db_index=True)
|
||||
_arguments = models.TextField(blank=True, db_column='arguments')
|
||||
_details = models.TextField(blank=True, db_column='details')
|
||||
objects = ActivityLogManager()
|
||||
|
||||
formatter = SafeFormatter()
|
||||
|
@ -194,6 +195,15 @@ class ActivityLog(amo.models.ModelBase):
|
|||
|
||||
self._arguments = json.dumps(serialize_me)
|
||||
|
||||
@property
|
||||
def details(self):
|
||||
if self._details:
|
||||
return json.loads(self._details)
|
||||
|
||||
@details.setter
|
||||
def details(self, data):
|
||||
self._details = json.dumps(data)
|
||||
|
||||
# TODO(davedash): Support other types.
|
||||
def to_string(self, type=None):
|
||||
log_type = amo.LOG_BY_ID[self.action]
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django import forms
|
||||
|
||||
import happyforms
|
||||
from tower import ugettext_lazy as _lazy
|
||||
|
||||
import amo
|
||||
|
||||
ACTION_FILTERS = (('', ''), ('approved', _lazy('Approved reviews')),
|
||||
('deleted', _lazy('Deleted reviews')))
|
||||
|
||||
ACTION_DICT = dict(approved=amo.LOG.APPROVE_REVIEW,
|
||||
deleted=amo.LOG.DELETE_REVIEW)
|
||||
|
||||
|
||||
class EventLogForm(happyforms.Form):
|
||||
start = forms.DateField(required=False,
|
||||
label=_lazy(u'View entries between'))
|
||||
end = forms.DateField(required=False,
|
||||
label=_lazy(u'and'))
|
||||
filter = forms.ChoiceField(required=False, choices=ACTION_FILTERS,
|
||||
label=_lazy(u'Filter by type/action'))
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
# We want this to be inclusive of the end date.
|
||||
if data['end']:
|
||||
data['end'] += timedelta(days=1)
|
||||
|
||||
if data['filter']:
|
||||
data['filter'] = ACTION_DICT[data['filter']]
|
||||
return data
|
|
@ -0,0 +1,49 @@
|
|||
{% extends "editors/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ _('Event Log') }}</h2>
|
||||
|
||||
<div class="listing results">
|
||||
<div class="results-inner controls">
|
||||
<form action="{{ url('editors.eventlog') }}">
|
||||
<p class="date_range">
|
||||
{{ form.start.label_tag()|safe }}
|
||||
{{ form.start|safe }}
|
||||
{{ form.end.label_tag()|safe }}
|
||||
{{ form.end|safe }}
|
||||
</p><p>
|
||||
{{ form.filter.label_tag()|safe }}
|
||||
{{ form.filter|safe }}
|
||||
<button type="submit">{{ _('Filter') }}</button>
|
||||
</p>
|
||||
</form>
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>{{ _('Date') }}</th>
|
||||
<th>{{ _('Event') }}</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for item in pager.object_list %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ item.created|babel_datetime }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.to_string('editor') }}
|
||||
{% if item.details %}
|
||||
<a href="{{ url('editors.eventlog.detail', item.id) }}">
|
||||
{{ _('More details.') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listing-footer">
|
||||
{{ pager|paginator }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "editors/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="featured">
|
||||
<div class="featured-inner">
|
||||
<h2>{{ _('Log details') }}</h2>
|
||||
<p>{{ log.to_string('editor') }}</p>
|
||||
{% if log.details %}
|
||||
<dl>
|
||||
{% for k, v in log.details.iteritems() %}
|
||||
<dt>{{ k }}</dt>
|
||||
<dd>{{ v }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
import test_utils
|
||||
|
@ -6,6 +8,7 @@ import test_utils
|
|||
import amo
|
||||
from amo.urlresolvers import reverse
|
||||
from addons.models import Addon
|
||||
from devhub.models import ActivityLog
|
||||
from reviews.models import Review
|
||||
from users.models import UserProfile
|
||||
|
||||
|
@ -17,6 +20,62 @@ class EditorTest(test_utils.TestCase):
|
|||
assert self.client.login(username='editor@mozilla.com',
|
||||
password='password')
|
||||
|
||||
def make_review(self, username='a'):
|
||||
u = UserProfile.objects.create(username=username)
|
||||
a = Addon.objects.create(name='yermom', type=amo.ADDON_EXTENSION)
|
||||
return Review.objects.create(user=u, addon=a)
|
||||
|
||||
|
||||
class TestEventLog(EditorTest):
|
||||
def setUp(self):
|
||||
self.login_as_editor()
|
||||
amo.set_user(UserProfile.objects.get(username='editor'))
|
||||
review = self.make_review()
|
||||
for i in xrange(30):
|
||||
amo.log(amo.LOG.APPROVE_REVIEW, review, review.addon)
|
||||
amo.log(amo.LOG.DELETE_REVIEW, review.id, review.addon)
|
||||
|
||||
def test_log(self):
|
||||
r = self.client.get(reverse('editors.eventlog'))
|
||||
eq_(r.status_code, 200)
|
||||
|
||||
def test_start_filter(self):
|
||||
r = self.client.get(reverse('editors.eventlog') + '?start=3011-01-01')
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
assert doc('tbody')
|
||||
eq_(len(doc('tbody tr')), 0)
|
||||
|
||||
def test_enddate_filter(self):
|
||||
"""
|
||||
Make sure that if our end date is 1/1/2011, that we include items from
|
||||
1/1/2011. To not do as such would be dishonorable.
|
||||
"""
|
||||
review = self.make_review(username='b')
|
||||
amo.log(amo.LOG.APPROVE_REVIEW, review, review.addon,
|
||||
created=datetime(2011, 1, 1))
|
||||
|
||||
r = self.client.get(reverse('editors.eventlog') + '?end=2011-01-01')
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
eq_(doc('tbody td').eq(0).text(), 'Jan 1, 2011 12:00:00 AM')
|
||||
|
||||
def test_action_filter(self):
|
||||
"""
|
||||
Based on setup we should only see 30 items if we filter for deleted
|
||||
reviews.
|
||||
"""
|
||||
r = self.client.get(reverse('editors.eventlog') + '?filter=deleted')
|
||||
doc = pq(r.content)
|
||||
eq_(len(doc('tbody tr')), 30)
|
||||
|
||||
|
||||
class TestEventLogDetail(TestEventLog):
|
||||
def test_me(self):
|
||||
id = ActivityLog.objects.editor_events()[0].id
|
||||
r = self.client.get(reverse('editors.eventlog.detail', args=[id]))
|
||||
eq_(r.status_code, 200)
|
||||
|
||||
|
||||
class TestHome(EditorTest):
|
||||
"""Test the page at /editors."""
|
||||
|
@ -24,11 +83,6 @@ class TestHome(EditorTest):
|
|||
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)
|
||||
|
|
|
@ -7,6 +7,9 @@ urlpatterns = patterns('',
|
|||
url(r'^queue$', views.queue, name='editors.queue'),
|
||||
url(r'^queue/pending$', views.queue_pending,
|
||||
name='editors.queue_pending'),
|
||||
url(r'^logs$', views.eventlog, name='editors.eventlog'),
|
||||
url(r'^log/(\d+)$', views.eventlog_detail, name='editors.eventlog.detail'),
|
||||
|
||||
url(r'^review/(?P<version_id>\d+)$', views.review, name='editors.review'),
|
||||
url(r'^$', views.home, name='editors.home'),
|
||||
)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import functools
|
||||
|
||||
from django import http
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
import jingo
|
||||
|
||||
import amo
|
||||
from access import acl
|
||||
from amo.decorators import login_required
|
||||
from devhub.models import ActivityLog
|
||||
from editors import forms
|
||||
from editors.models import ViewEditorQueue
|
||||
from editors.helpers import ViewEditorQueueTable
|
||||
from amo.utils import paginate
|
||||
|
@ -27,6 +29,32 @@ def editor_required(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
@editor_required
|
||||
def eventlog(request):
|
||||
form = forms.EventLogForm(request.GET)
|
||||
eventlog = ActivityLog.objects.editor_events()
|
||||
|
||||
if form.is_valid():
|
||||
if form.cleaned_data['start']:
|
||||
eventlog = eventlog.filter(created__gte=form.cleaned_data['start'])
|
||||
if form.cleaned_data['end']:
|
||||
eventlog = eventlog.filter(created__lt=form.cleaned_data['end'])
|
||||
if form.cleaned_data['filter']:
|
||||
eventlog = eventlog.filter(action=form.cleaned_data['filter'].id)
|
||||
|
||||
pager = amo.utils.paginate(request, eventlog, 50)
|
||||
|
||||
data = dict(form=form, pager=pager)
|
||||
return jingo.render(request, 'editors/eventlog.html', data)
|
||||
|
||||
|
||||
@editor_required
|
||||
def eventlog_detail(request, id):
|
||||
log = get_object_or_404(ActivityLog.objects.editor_events(), pk=id)
|
||||
data = dict(log=log)
|
||||
return jingo.render(request, 'editors/eventlog_detail.html', data)
|
||||
|
||||
|
||||
@editor_required
|
||||
def home(request):
|
||||
data = dict(reviews_total=Approval.total_reviews(),
|
||||
|
|
|
@ -240,3 +240,7 @@ ul.tabnav a:hover {
|
|||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.controls {
|
||||
padding: 15px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE log_activity ADD COLUMN details longtext AFTER arguments;
|
Загрузка…
Ссылка в новой задаче