bug 592590, activity log fun for all ages

This commit is contained in:
Dave Dash 2010-10-19 11:14:29 -07:00
Родитель e6eabb946f
Коммит 3e84bc7a07
8 изменённых файлов: 491 добавлений и 76 удалений

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

@ -7,6 +7,7 @@ import commonware.log
from product_details import firefox_versions, thunderbird_versions
from tower import ugettext_lazy as _
from .log import LOG, LOG_BY_ID, LOG_KEEP
# Every app should have its own logger.
log = commonware.log.getLogger('z.amo')

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

@ -10,7 +10,7 @@ import commonware.log
import amo
from bandwagon.models import Collection
from cake.models import Session
from devhub.models import AddonLog, LOG as ADDONLOG
from devhub.models import ActivityLog
from files.models import TestResult, TestResultCache
from sharing import SERVICES_LIST
from stats.models import AddonShareCount, Contribution
@ -91,24 +91,9 @@ def gc(test_result=True):
created__lt=six_days_ago).delete()
log.debug('Removing old entries from add-on news feeds.')
keep = (
ADDONLOG['Create Add-on'],
ADDONLOG['Add User with Role'],
ADDONLOG['Remove User with Role'],
ADDONLOG['Set Inactive'],
ADDONLOG['Unset Inactive'],
ADDONLOG['Change Status'],
ADDONLOG['Add Version'],
ADDONLOG['Delete Version'],
ADDONLOG['Approve Version'],
ADDONLOG['Retain Version'],
ADDONLOG['Escalate Version'],
ADDONLOG['Request Version'],
ADDONLOG['Add Recommended'],
ADDONLOG['Remove Recommended'],
)
AddonLog.objects.filter(created__lt=three_months_ago).exclude(
type__in=keep).delete()
ActivityLog.objects.filter(created__lt=three_months_ago).exclude(
action__in=amo.LOG_KEEP).delete()
log.debug('Cleaning up anonymous collections.')
Collection.objects.filter(created__lt=two_days_ago,

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

@ -66,11 +66,10 @@
},
{
"pk": 1,
"model": "devhub.addonlog",
"model": "devhub.activitylog",
"fields": {
"created": "2000-01-01",
"addon": 59,
"type": 99
"action": 99
}
},
{

234
apps/amo/log.py Normal file
Просмотреть файл

@ -0,0 +1,234 @@
from collections import namedtuple
from celery.datastructures import AttributeDict
from tower import ugettext as _
__all__ = ('LOG', 'LOG_BY_ID', 'LOG_KEEP',)
_LOG = namedtuple('LOG', 'id format')
class CREATE_ADDON:
id = 1
format = _('{user.name} created addon {addon.name}')
keep = True
class EDIT_PROPERTIES:
id = 2
format = _('{user.name} edited addon {addon.name} properties')
class EDIT_DESCRIPTIONS:
id = 3
format = _('{user.name} edited addon {addon.name} description')
class EDIT_CATEGORIES:
id = 4
format = _('{user.name} edited categories for {addon.name}')
class ADD_USER_WITH_ROLE:
id = 5
format = _('{user.name} added {0.name} to '
'addon {addon.name} with role {1}')
keep = True
class REMOVE_USER_WITH_ROLE:
id = 6
# L10n: {0} is the user being removed, {1} is their role.
format = _('{user.name} removed {0} with role {1}')
keep = True
class EDIT_CONTRIBUTIONS:
id = 7
format = _('{user.name} edited contributions for {addon.name}')
class SET_INACTIVE:
id = 8
format = _('{user.name} set addon {addon.name} inactive')
keep = True
class UNSET_INACTIVE:
id = 9
format = _('{user.name} activated addon {addon.name}')
keep = True
class SET_PUBLIC_STATS:
id = 10
format = _('{user.name} set stats public for {addon}')
keep = True
class UNSET_PUBLIC_STATS:
id = 11
format = _('{user.name} set stats private for {addon}')
keep = True
class CHANGE_STATUS:
id = 12
# L10n: {0} is the status
format = _('{user.name} changed {addon} status to {0}')
keep = True
class ADD_PREVIEW:
id = 13
format = _('{user.name} added preview to {addon}')
class EDIT_PREVIEW:
id = 14
format = _('{user.name} edited preview for {addon}')
class DELETE_PREVIEW:
id = 15
format = _('{user.name} deleted preview from {addon}')
class ADD_VERSION:
id = 16
format = _('{user.name} added version {0.version} to {addon}')
keep = True
class EDIT_VERSION:
id = 17
format = _('{user.name} edited version {0.version} of {addon}')
class DELETE_VERSION:
id = 18
format = _('{user.name} deleted version {0.version} from {addon}')
keep = True
class ADD_FILE_TO_VERSION:
id = 19
format = _('{user.name} added file {0.name} to '
'version {0.version} of {addon}')
class DELETE_FILE_FROM_VERSION:
id = 20
format = _('{user.name} deleted file {0.name} '
'from {addon} version {0.version}')
class APPROVE_VERSION:
id = 21
format = _('Version {0.version} of {addon} approved')
keep = True
class RETAIN_VERSION:
id = 22
format = _('{user.name} retained version {0.version} of {addon}')
keep = True
class ESCALATE_VERSION:
id = 23
# L10n: {0.version} is the version of an addon.
format = _('{user.name} escalated review of {addon} {0.version}')
keep = True
class REQUEST_VERSION:
id = 24
# L10n: {0.version} is the version of an addon.
format = _('{user.name} requested more information regarding '
'{addon} {0.version}')
keep = True
class ADD_TAG:
id = 25
# L10n: {0} is the tag name.
format = _('{user.name} added tag {0} to {addon}')
class REMOVE_TAG:
id = 26
# L10n: {0} is the tag name.
format = _('{user.name} removed tag {0} from {addon}')
class ADD_TO_COLLECTION:
id = 27
format = _('{user.name} added addon {addon} to a collection {0.name}')
class REMOVE_FROM_COLLECTION:
id = 28
forma = _('{user.name} removed addon {addon} from a collection {0.name}')
class ADD_REVIEW:
id = 29
format = _('{user.name} wrote a review about {addon}')
class ADD_RECOMMENDED_CATEGORY:
id = 31
# L10n: {0} is a category name.
format = _('{addon} featured in {0}')
class REMOVE_RECOMMENDED_CATEGORY:
id = 32
# L10n: {0} is a category name.
format = _('{addon} no longer featured in {0}')
class ADD_RECOMMENDED:
id = 33
format = _('{addon} is now featured')
keep = True
class REMOVE_RECOMMENDED:
id = 34
format = _('{addon} is no longer featured')
keep = True
class ADD_APPVERSION:
id = 35
# L10n: {0} is the application, {1.min/max} is the min/max version of the
# app
format = _('addon now supports {0} {1.min}-{1.max}')
class CUSTOM_TEXT:
id = 98
format = '{0}'
class CUSTOM_HTML:
id = 99
format = '{0}'
LOGS = (CREATE_ADDON, EDIT_PROPERTIES, EDIT_DESCRIPTIONS, EDIT_CATEGORIES,
ADD_USER_WITH_ROLE, REMOVE_USER_WITH_ROLE, EDIT_CONTRIBUTIONS,
SET_INACTIVE, UNSET_INACTIVE, SET_PUBLIC_STATS, UNSET_PUBLIC_STATS,
CHANGE_STATUS, ADD_PREVIEW, EDIT_PREVIEW, DELETE_PREVIEW,
ADD_VERSION, EDIT_VERSION, DELETE_VERSION, ADD_FILE_TO_VERSION,
DELETE_FILE_FROM_VERSION, APPROVE_VERSION, RETAIN_VERSION,
ESCALATE_VERSION, REQUEST_VERSION, ADD_TAG, REMOVE_TAG,
ADD_TO_COLLECTION, REMOVE_FROM_COLLECTION, ADD_REVIEW,
ADD_RECOMMENDED_CATEGORY, REMOVE_RECOMMENDED_CATEGORY, ADD_RECOMMENDED,
REMOVE_RECOMMENDED, ADD_APPVERSION, CUSTOM_TEXT, CUSTOM_HTML,
)
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'))

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

@ -3,7 +3,7 @@ import test_utils
from bandwagon.models import Collection
from cake.models import Session
from devhub.models import AddonLog
from devhub.models import ActivityLog
from files.models import TestResult, TestResultCache
from stats.models import AddonShareCount, Contribution
from amo.cron import gc
@ -16,7 +16,7 @@ class GarbageTest(test_utils.TestCase):
"This fixture is expired data that should just get cleaned up."
eq_(Collection.objects.all().count(), 1)
eq_(Session.objects.all().count(), 1)
eq_(AddonLog.objects.all().count(), 1)
eq_(ActivityLog.objects.all().count(), 1)
eq_(TestResult.objects.all().count(), 1)
eq_(TestResultCache.objects.all().count(), 1)
eq_(AddonShareCount.objects.all().count(), 1)
@ -24,7 +24,7 @@ class GarbageTest(test_utils.TestCase):
gc(test_result=False)
eq_(Collection.objects.all().count(), 0)
eq_(Session.objects.all().count(), 0)
eq_(AddonLog.objects.all().count(), 0)
eq_(ActivityLog.objects.all().count(), 0)
# XXX(davedash): this isn't working in testing.
# eq_(TestResult.objects.all().count(), 0)
eq_(TestResultCache.objects.all().count(), 0)

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

@ -1,60 +1,18 @@
from datetime import datetime
import json
from django.db import models
import commonware.log
import amo
import amo.models
from addons.models import Addon
from users.models import UserProfile
from translations.fields import TranslatedField
LOG = {
'Create Add-on': 1,
'Edit Properties': 2,
'Edit Descriptions': 3,
'Edit Categories': 4,
'Add User with Role': 5,
'Remove User with Role': 6,
'Edit Contributions': 7,
'Set Inactive': 8,
'Unset Inactive': 9,
'Set Public Stats': 10,
'Unset Public Stats': 11,
'Change Status': 12,
'Add Preview': 13,
'Edit Preview': 14,
'Delete Preview': 15,
'Add Version': 16,
'Edit Version': 17,
'Delete Version': 18,
'Add File to Version': 19,
'Delete File from Version': 20,
'Approve Version': 21,
'Retain Version': 22,
'Escalate Version': 23,
'Request Version': 24,
'Add Tag': 25,
'Remove Tag': 26,
'Add to Collection': 27,
'Remove from Collection': 28,
'Add Review': 29,
'Add Recommended Category': 31,
'Remove Recommended Category': 32,
'Add Recommended': 33,
'Remove Recommended': 34,
'Add Appversion': 35,
'Custom Text': 98,
'Custom HTML': 99,
}
log = commonware.log.getLogger('devhub')
class HubPromo(amo.models.ModelBase):
@ -95,8 +53,139 @@ class HubEvent(amo.models.ModelBase):
return ['*/developers*']
class AddonLog(models.Model):
TYPES = [(value, key) for key, value in LOG.items()]
class AddonLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by addon.
"""
addon = models.ForeignKey(Addon)
activity_log = models.ForeignKey('ActivityLog')
class Meta:
db_table = 'log_activity_addon'
ordering = ('-created',)
class UserLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by user.
Note: This includes activity performed unto the user.
"""
activity_log = models.ForeignKey('ActivityLog')
user = models.ForeignKey(UserProfile)
class Meta:
db_table = 'log_activity_user'
ordering = ('-created',)
class ActivityLogManager(amo.models.ManagerBase):
def for_addon(self, addon, limit=20, offset=0):
vals = (AddonLog.objects.filter(addon=addon)[offset:limit]
.values_list('activity_log', flat=True))
return self.filter(pk__in=list(vals))
def for_user(self, user, limit=20, offset=0):
vals = (UserLog.objects.filter(user=user)[offset:limit]
.values_list('activity_log', flat=True))
return self.filter(pk__in=list(vals))
class ActivityLog(amo.models.ModelBase):
TYPES = [(value, key) for key, value in amo.LOG.items()]
user = models.ForeignKey('users.UserProfile', null=True)
action = models.SmallIntegerField(choices=TYPES, db_index=True)
_arguments = models.TextField(blank=True, db_column='arguments')
objects = ActivityLogManager()
@property
def arguments(self):
try:
# d is a structure:
# ``d = [{'addons.addon'=12}, {'addons.addon'=1}, ... ]``
d = json.loads(self._arguments)
except:
log.debug('unserializing data from addon_log failed: %s' % self.id)
return None
objs = []
for item in d:
# item has only one element.
model_name, pk = item.items()[0]
if model_name == 'str':
objs.append(pk)
else:
(app_label, model_name) = model_name.split('.')
model = models.loading.get_model(app_label, model_name)
objs.extend(model.objects.filter(pk=pk))
return objs
@arguments.setter
def arguments(self, args=[]):
"""
Takes an object or a tuple of objects and serializes them and stores it
in the db as a json string.
"""
if args is None:
args = []
if not isinstance(args, (list, tuple)):
args = (args,)
serialize_me = []
for arg in args:
if isinstance(arg, str):
serialize_me.append({'str': arg})
else:
serialize_me.append(dict(((unicode(arg._meta), arg.pk),)))
self._arguments = json.dumps(serialize_me)
@classmethod
def log(cls, request, action, arguments=None):
"""
e.g. ActivityLog.log(request, amo.LOG.CREATE_ADDON, []),
ActivityLog.log(request, amo.LOG.ADD_FILE_TO_VERSION,
(file, version))
"""
al = cls(user=request.amo_user, action=action.id)
al.arguments = arguments
al.save()
if not isinstance(arguments, (list, tuple)):
arguments = (arguments,)
for arg in arguments:
if isinstance(arg, Addon):
AddonLog(addon=arg, activity_log=al).save()
elif isinstance(arg, UserProfile):
# Index by any user who is mentioned as an argument.
UserLog(activity_log=al, user=arg).save()
# Index by every request user
UserLog(activity_log=al, user=request.amo_user).save()
# TODO(davedash): Support other types.
def to_string(self, type='default'):
log_type = amo.LOG_BY_ID[self.action]
arguments = self.arguments
addon = None
for arg in arguments:
if isinstance(arg, Addon) and not addon:
addon = arg
break
return log_type.format.format(*arguments, user=self.user, addon=addon)
def __unicode__(self):
return self.to_string()
class Meta:
db_table = 'log_activity'
class LegacyAddonLog(models.Model):
TYPES = [(value, key) for key, value in amo.LOG.items()]
addon = models.ForeignKey('addons.Addon', null=True, blank=True)
user = models.ForeignKey('users.UserProfile', null=True)

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

@ -0,0 +1,71 @@
from nose.tools import eq_
from mock import Mock
import test_utils
import amo
from addons.models import Addon
from devhub.models import ActivityLog
from users.models import UserProfile
class TestActivityLog(test_utils.TestCase):
fixtures = ('base/addon_3615',)
def setUp(self):
self.request = Mock()
u = UserProfile(username='Joe CamelCase')
u.save()
self.request.amo_user = u
def test_basic(self):
request = self.request
a = Addon.objects.get()
ActivityLog.log(request, amo.LOG['CREATE_ADDON'], a)
entries = ActivityLog.objects.for_addon(a)
eq_(len(entries), 1)
eq_(entries[0].arguments[0], a)
eq_(unicode(entries[0]),
'Joe CamelCase created addon Delicious Bookmarks')
def test_json_failboat(self):
request = self.request
a = Addon.objects.get()
ActivityLog.log(request, amo.LOG['CREATE_ADDON'], a)
entry = ActivityLog.objects.get()
entry._arguments = 'failboat?'
entry.save()
eq_(entry.arguments, None)
def test_no_arguments(self):
request = self.request
ActivityLog.log(request, amo.LOG['CUSTOM_HTML'])
entry = ActivityLog.objects.get()
eq_(entry.arguments, [])
def test_output(self):
request = self.request
ActivityLog.log(request, amo.LOG['CUSTOM_TEXT'], 'hi there')
entry = ActivityLog.objects.get()
eq_(unicode(entry), 'hi there')
def test_user_log(self):
request = self.request
ActivityLog.log(request, amo.LOG['CUSTOM_TEXT'], 'hi there')
entries = ActivityLog.objects.for_user(request.amo_user)
eq_(len(entries), 1)
def test_user_log_as_argument(self):
"""
Tests that a user that has something done to them gets into the user
log.
"""
request = self.request
u = UserProfile(username='Marlboro Manatee')
u.save()
ActivityLog.log(request, amo.LOG['ADD_USER_WITH_ROLE'],
(u, 'developer', Addon.objects.get()))
entries = ActivityLog.objects.for_user(request.amo_user)
eq_(len(entries), 1)
entries = ActivityLog.objects.for_user(u)
eq_(len(entries), 1)

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

@ -0,0 +1,36 @@
CREATE TABLE `log_activity_addon` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`addon_id` integer NOT NULL,
`activity_log_id` integer NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
;
ALTER TABLE `log_activity_addon` ADD CONSTRAINT `addon_id_refs_id_5bfa17d1` FOREIGN KEY (`addon_id`) REFERENCES `addons` (`id`);
CREATE TABLE `log_activity_user` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`activity_log_id` integer NOT NULL,
`user_id` integer NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
;
ALTER TABLE `log_activity_user` ADD CONSTRAINT `user_id_refs_id_e987c199` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`);
CREATE TABLE `log_activity` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`user_id` integer,
`action` smallint NOT NULL,
`arguments` longtext NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
;
ALTER TABLE `log_activity` ADD CONSTRAINT `user_id_refs_id_3fa7a30a` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`);
ALTER TABLE `log_activity_addon` ADD CONSTRAINT `activity_log_id_refs_id_9c20a926` FOREIGN KEY (`activity_log_id`) REFERENCES `log_activity` (`id`);
ALTER TABLE `log_activity_user` ADD CONSTRAINT `activity_log_id_refs_id_4f8d99d4` FOREIGN KEY (`activity_log_id`) REFERENCES `log_activity` (`id`);
CREATE INDEX `log_activity_addon_cc3d5937` ON `log_activity_addon` (`addon_id`);
CREATE INDEX `log_activity_addon_3bf68f54` ON `log_activity_addon` (`activity_log_id`);
CREATE INDEX `log_activity_user_3bf68f54` ON `log_activity_user` (`activity_log_id`);
CREATE INDEX `log_activity_user_fbfc09f1` ON `log_activity_user` (`user_id`);
CREATE INDEX `log_activity_fbfc09f1` ON `log_activity` (`user_id`);
CREATE INDEX `log_activity_1bd4707b` ON `log_activity` (`action`);