398 строки
12 KiB
Python
398 строки
12 KiB
Python
from copy import copy
|
|
from datetime import datetime
|
|
import json
|
|
import string
|
|
|
|
from django.db import models
|
|
|
|
import commonware.log
|
|
import jinja2
|
|
from tower import ugettext as _
|
|
from uuidfield.fields import UUIDField
|
|
|
|
import amo
|
|
import amo.models
|
|
from addons.models import Addon
|
|
from bandwagon.models import Collection
|
|
from mkt.webapps.models import Webapp
|
|
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
|
|
|
|
log = commonware.log.getLogger('devhub')
|
|
|
|
|
|
class RssKey(models.Model):
|
|
key = UUIDField(db_column='rsskey', auto=True, unique=True)
|
|
addon = models.ForeignKey(Addon, null=True, unique=True)
|
|
user = models.ForeignKey(UserProfile, null=True, unique=True)
|
|
created = models.DateField(default=datetime.now)
|
|
|
|
class Meta:
|
|
db_table = 'hubrsskeys'
|
|
|
|
|
|
class BlogPost(amo.models.ModelBase):
|
|
title = models.CharField(max_length=255)
|
|
date_posted = models.DateField(default=datetime.now)
|
|
permalink = models.CharField(max_length=255)
|
|
|
|
class Meta:
|
|
db_table = 'blogposts'
|
|
|
|
|
|
class HubPromo(amo.models.ModelBase):
|
|
VISIBILITY_CHOICES = (
|
|
(0, 'Nobody'),
|
|
(1, 'Visitors'),
|
|
(2, 'Developers'),
|
|
(3, 'Visitors and Developers'),
|
|
)
|
|
|
|
heading = TranslatedField()
|
|
body = TranslatedField()
|
|
visibility = models.SmallIntegerField(choices=VISIBILITY_CHOICES)
|
|
|
|
class Meta:
|
|
db_table = 'hubpromos'
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.heading)
|
|
|
|
def flush_urls(self):
|
|
return ['*/developers*']
|
|
|
|
|
|
class HubEvent(amo.models.ModelBase):
|
|
name = models.CharField(max_length=255, default='')
|
|
url = models.URLField(max_length=255, default='', verify_exists=False)
|
|
location = models.CharField(max_length=255, default='')
|
|
date = models.DateField(default=datetime.now)
|
|
|
|
class Meta:
|
|
db_table = 'hubevents'
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
def flush_urls(self):
|
|
return ['*/developers*']
|
|
|
|
|
|
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 AppLog(amo.models.ModelBase):
|
|
"""
|
|
This table is for indexing the activity log by app.
|
|
"""
|
|
addon = models.ForeignKey(Webapp)
|
|
activity_log = models.ForeignKey('ActivityLog')
|
|
|
|
class Meta:
|
|
db_table = 'log_activity_app'
|
|
ordering = ('-created',)
|
|
|
|
|
|
class CommentLog(amo.models.ModelBase):
|
|
"""
|
|
This table is for indexing the activity log by comment.
|
|
"""
|
|
activity_log = models.ForeignKey('ActivityLog')
|
|
comments = models.CharField(max_length=255)
|
|
|
|
class Meta:
|
|
db_table = 'log_activity_comment'
|
|
ordering = ('-created',)
|
|
|
|
|
|
class VersionLog(amo.models.ModelBase):
|
|
"""
|
|
This table is for indexing the activity log by version.
|
|
"""
|
|
activity_log = models.ForeignKey('ActivityLog')
|
|
version = models.ForeignKey(Version)
|
|
|
|
class Meta:
|
|
db_table = 'log_activity_version'
|
|
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_addons(self, addons):
|
|
if isinstance(addons, Addon):
|
|
addons = (addons,)
|
|
|
|
vals = (AddonLog.objects.filter(addon__in=addons)
|
|
.values_list('activity_log', flat=True))
|
|
|
|
if vals:
|
|
return self.filter(pk__in=list(vals))
|
|
else:
|
|
return self.none()
|
|
|
|
def for_apps(self, apps):
|
|
if isinstance(apps, Webapp):
|
|
apps = (apps,)
|
|
|
|
vals = (AppLog.objects.filter(addon__in=apps)
|
|
.values_list('activity_log', flat=True))
|
|
|
|
if vals:
|
|
return self.filter(pk__in=list(vals))
|
|
else:
|
|
return self.none()
|
|
|
|
def for_version(self, version):
|
|
vals = (VersionLog.objects.filter(version=version)
|
|
.values_list('activity_log', flat=True))
|
|
return self.filter(pk__in=list(vals))
|
|
|
|
def for_user(self, user):
|
|
vals = (UserLog.objects.filter(user=user)
|
|
.values_list('activity_log', flat=True))
|
|
return self.filter(pk__in=list(vals))
|
|
|
|
def for_developer(self):
|
|
return self.exclude(action__in=amo.LOG_ADMINS + amo.LOG_HIDE_DEVELOPER)
|
|
|
|
def admin_events(self):
|
|
return self.filter(action__in=amo.LOG_ADMINS)
|
|
|
|
def editor_events(self):
|
|
return self.filter(action__in=amo.LOG_EDITORS)
|
|
|
|
def review_queue(self, webapp=False):
|
|
qs = self._by_type(webapp)
|
|
return qs.filter(action__in=amo.LOG_REVIEW_QUEUE)
|
|
|
|
def total_reviews(self, webapp=False):
|
|
qs = self._by_type(webapp)
|
|
"""Return the top users, and their # of reviews."""
|
|
return (qs.values('user', 'user__display_name', 'user__username')
|
|
.filter(action__in=amo.LOG_REVIEW_QUEUE)
|
|
.annotate(approval_count=models.Count('id'))
|
|
.order_by('-approval_count'))
|
|
|
|
def monthly_reviews(self, webapp=False):
|
|
"""Return the top users for the month, and their # of reviews."""
|
|
qs = self._by_type(webapp)
|
|
now = datetime.now()
|
|
created_date = datetime(now.year, now.month, 1)
|
|
return (qs.values('user', 'user__display_name', 'user__username')
|
|
.filter(created__gte=created_date,
|
|
action__in=amo.LOG_REVIEW_QUEUE)
|
|
.annotate(approval_count=models.Count('id'))
|
|
.order_by('-approval_count'))
|
|
|
|
def _by_type(self, webapp=False):
|
|
qs = super(ActivityLogManager, self).get_query_set()
|
|
table = 'log_activity_app' if webapp else 'log_activity_addon'
|
|
return qs.extra(
|
|
tables=[table],
|
|
where=['%s.activity_log_id=log_activity.id' % table])
|
|
|
|
|
|
class SafeFormatter(string.Formatter):
|
|
"""A replacement for str.format that escapes interpolated values."""
|
|
|
|
def get_field(self, *args, **kw):
|
|
# obj is the value getting interpolated into the string.
|
|
obj, used_key = super(SafeFormatter, self).get_field(*args, **kw)
|
|
return jinja2.escape(obj), used_key
|
|
|
|
|
|
class ActivityLog(amo.models.ModelBase):
|
|
TYPES = sorted([(value.id, 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')
|
|
_details = models.TextField(blank=True, db_column='details')
|
|
objects = ActivityLogManager()
|
|
|
|
formatter = SafeFormatter()
|
|
|
|
def f(self, *args, **kw):
|
|
"""Calls SafeFormatter.format and returns a Markup string."""
|
|
# SafeFormatter escapes everything so this is safe.
|
|
return jinja2.Markup(self.formatter.format(*args, **kw))
|
|
|
|
@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 in ('str', 'int', 'null'):
|
|
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, 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
|
|
serialize_me.append(dict(((unicode(arg[0]._meta), arg[1]),)))
|
|
else:
|
|
serialize_me.append(dict(((unicode(arg._meta), arg.pk),)))
|
|
|
|
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)
|
|
|
|
@property
|
|
def log(self):
|
|
return amo.LOG_BY_ID[self.action]
|
|
|
|
# TODO(davedash): Support other types.
|
|
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.
|
|
arguments = copy(self.arguments)
|
|
addon = None
|
|
review = None
|
|
version = None
|
|
collection = None
|
|
tag = None
|
|
|
|
for arg in self.arguments:
|
|
if isinstance(arg, Addon) and not addon:
|
|
addon = self.f(u'<a href="{0}">{1}</a>',
|
|
arg.get_url_path(), arg.name)
|
|
arguments.remove(arg)
|
|
if isinstance(arg, Review) and not review:
|
|
review = self.f(u'<a href="{0}">{1}</a>',
|
|
arg.get_url_path(), _('Review'))
|
|
arguments.remove(arg)
|
|
if isinstance(arg, Version) and not version:
|
|
text = _('Version %s') % arg.version
|
|
version = self.f(u'<a href="{0}">{1}</a>',
|
|
arg.get_url_path(), text)
|
|
arguments.remove(arg)
|
|
if isinstance(arg, Collection) and not collection:
|
|
collection = self.f(u'<a href="{0}">{1}</a>',
|
|
arg.get_url_path(), arg.name)
|
|
arguments.remove(arg)
|
|
if isinstance(arg, Tag) and not tag:
|
|
if arg.can_reverse():
|
|
tag = self.f(u'<a href="{0}">{1}</a>',
|
|
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, 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.'
|
|
|
|
def __unicode__(self):
|
|
return self.to_string()
|
|
|
|
def __html__(self):
|
|
return self
|
|
|
|
class Meta:
|
|
db_table = 'log_activity'
|
|
ordering = ('-created',)
|
|
|
|
|
|
# TODO(davedash): Remove after we finish the import.
|
|
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)
|
|
type = models.SmallIntegerField(choices=TYPES)
|
|
object1_id = models.IntegerField(null=True, blank=True)
|
|
object2_id = models.IntegerField(null=True, blank=True)
|
|
name1 = models.CharField(max_length=255, default='', blank=True)
|
|
name2 = models.CharField(max_length=255, default='', blank=True)
|
|
notes = models.TextField(blank=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'addonlogs'
|
|
ordering = ('id',)
|
|
|
|
|
|
class SubmitStep(models.Model):
|
|
addon = models.ForeignKey(Addon)
|
|
step = models.IntegerField()
|
|
|
|
class Meta:
|
|
db_table = 'submit_step'
|