addons-server/apps/editors/models.py

336 строки
12 KiB
Python

import copy
from django.conf import settings
from django.db import models
from django.template import Context, loader
from django.utils.datastructures import SortedDict
import amo
import amo.models
from amo.helpers import absolutify
from amo.urlresolvers import reverse
from amo.utils import send_mail
from addons.models import Addon
from editors.sql_model import RawSQLModel
from translations.fields import TranslatedField
from users.models import UserProfile
from versions.models import version_uploaded
import commonware.log
user_log = commonware.log.getLogger('z.users')
class CannedResponse(amo.models.ModelBase):
name = TranslatedField()
response = TranslatedField()
sort_group = models.CharField(max_length=255)
class Meta:
db_table = 'cannedresponses'
def __unicode__(self):
return unicode(self.name)
class EventLog(models.Model):
type = models.CharField(max_length=60)
action = models.CharField(max_length=120)
field = models.CharField(max_length=60, blank=True)
user = models.ForeignKey(UserProfile)
changed_id = models.IntegerField()
added = models.CharField(max_length=765, blank=True)
removed = models.CharField(max_length=765, blank=True)
notes = models.TextField(blank=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = u'eventlog'
@staticmethod
def new_editors():
items = (EventLog.objects.values('added', 'created')
.filter(type='admin',
action='group_addmember',
changed_id=2)
.order_by('-created')[:5])
users = UserProfile.objects.filter(id__in=[i['added'] for i in items])
names = dict((u.id, u.display_name) for u in users)
return [dict(display_name=names[int(i['added'])], **i) for i in items]
class ViewQueue(RawSQLModel):
id = models.IntegerField()
addon_name = models.CharField(max_length=255)
addon_slug = models.CharField(max_length=30)
addon_status = models.IntegerField()
addon_type_id = models.IntegerField()
admin_review = models.BooleanField()
is_site_specific = models.BooleanField()
external_software = models.BooleanField()
binary = models.BooleanField()
premium_type = models.IntegerField()
_no_restart = models.CharField(max_length=255)
_jetpack_versions = models.CharField(max_length=255)
_latest_versions = models.CharField(max_length=255)
_latest_version_ids = models.CharField(max_length=255)
_file_platform_ids = models.CharField(max_length=255)
_file_platform_vers = models.CharField(max_length=255)
_info_request_vers = models.CharField(max_length=255)
_editor_comment_vers = models.CharField(max_length=255)
_application_ids = models.CharField(max_length=255)
waiting_time_days = models.IntegerField()
waiting_time_hours = models.IntegerField()
waiting_time_min = models.IntegerField()
is_version_specific = False
_latest_version_id = None
def base_query(self):
return {
'select': SortedDict([
('id', 'addons.id'),
('addon_name', 'tr.localized_string'),
('addon_status', 'addons.status'),
('addon_type_id', 'addons.addontype_id'),
('addon_slug', 'addons.slug'),
('admin_review', 'addons.adminreview'),
('is_site_specific', 'addons.sitespecific'),
('external_software', 'addons.externalsoftware'),
('binary', 'addons.binary'),
('premium_type', 'addons.premium_type'),
('_latest_version_ids', """GROUP_CONCAT(versions.id
ORDER BY versions.created DESC)"""),
('_latest_versions', """GROUP_CONCAT(versions.version
ORDER BY versions.created
DESC SEPARATOR '&&&&')"""),
('_editor_comment_vers', """GROUP_CONCAT(DISTINCT CONCAT(CONCAT(
versions.has_editor_comment, '-'),
versions.id))"""),
('_info_request_vers', """GROUP_CONCAT(DISTINCT CONCAT(CONCAT(
versions.has_info_request, '-'),
versions.id))"""),
('_file_platform_vers', """GROUP_CONCAT(DISTINCT CONCAT(CONCAT(
files.platform_id, '-'),
files.version_id))"""),
('_file_platform_ids', """GROUP_CONCAT(DISTINCT
files.platform_id)"""),
('_jetpack_versions', """GROUP_CONCAT(DISTINCT
files.jetpack_version)"""),
('_no_restart', """GROUP_CONCAT(DISTINCT files.no_restart)"""),
('_application_ids', """GROUP_CONCAT(DISTINCT
apps.application_id)"""),
]),
'from': [
'files',
'JOIN versions ON (files.version_id = versions.id)',
'JOIN addons ON (versions.addon_id = addons.id)',
"""JOIN files AS version_files ON (
version_files.version_id = versions.id)""",
"""LEFT JOIN applications_versions as apps
ON versions.id = apps.version_id""",
# Translations
"""JOIN translations AS tr ON (
tr.id = addons.name
AND tr.locale = addons.defaultlocale)"""
],
'where': [
'NOT addons.inactive', # disabled_by_user
],
'group_by': 'id'}
@property
def latest_version(self):
return self._explode_concat(self._latest_versions, sep='&&&&',
cast=unicode)[0]
@property
def latest_version_id(self):
if not self._latest_version_id:
ids = self._explode_concat(self._latest_version_ids)
self._latest_version_id = ids[0]
return self._latest_version_id
@property
def is_restartless(self):
return any(self._explode_concat(self._no_restart))
@property
def is_jetpack(self):
return bool(self._jetpack_versions)
@property
def is_premium(self):
return self.premium_type == amo.ADDON_PREMIUM
@property
def file_platform_vers(self):
return self._explode_concat(self._file_platform_vers, cast=str)
@property
def has_info_request(self):
return self.for_latest_version(self._info_request_vers)
@property
def has_editor_comment(self):
return self.for_latest_version(self._editor_comment_vers)
@property
def file_platform_ids(self):
return self._explode_concat(self._file_platform_ids)
@property
def application_ids(self):
return self._explode_concat(self._application_ids)
def for_latest_version(self, vals, cast=int, default=0):
split = self._explode_concat(vals, cast=str)
for s in split:
val, version_id = s.split('-')
if int(version_id) == self.latest_version_id:
return cast(val)
return default
class ViewFullReviewQueue(ViewQueue):
def base_query(self):
q = super(ViewFullReviewQueue, self).base_query()
q['select'].update({
'waiting_time_days':
'TIMESTAMPDIFF(DAY, MAX(versions.nomination), NOW())',
'waiting_time_hours':
'TIMESTAMPDIFF(HOUR, MAX(versions.nomination), NOW())',
'waiting_time_min':
'TIMESTAMPDIFF(MINUTE, MAX(versions.nomination), NOW())',
})
q['where'].extend(['files.status <> %s' % amo.STATUS_BETA,
'addons.status IN (%s, %s)' % (
amo.STATUS_NOMINATED,
amo.STATUS_LITE_AND_NOMINATED)])
return q
class VersionSpecificQueue(ViewQueue):
is_version_specific = True
def base_query(self):
q = copy.deepcopy(super(VersionSpecificQueue, self).base_query())
q['select'].update({
'waiting_time_days':
'TIMESTAMPDIFF(DAY, MAX(files.created), NOW())',
'waiting_time_hours':
'TIMESTAMPDIFF(HOUR, MAX(files.created), NOW())',
'waiting_time_min':
'TIMESTAMPDIFF(MINUTE, MAX(files.created), NOW())',
})
return q
class ViewPendingQueue(VersionSpecificQueue):
def base_query(self):
q = super(ViewPendingQueue, self).base_query()
q['where'].extend(['files.status = %s' % amo.STATUS_UNREVIEWED,
'addons.status = %s' % amo.STATUS_PUBLIC])
return q
class ViewPreliminaryQueue(VersionSpecificQueue):
def base_query(self):
q = super(ViewPreliminaryQueue, self).base_query()
q['where'].extend(['files.status = %s' % amo.STATUS_UNREVIEWED,
'addons.status IN (%s, %s)' % (
amo.STATUS_LITE,
amo.STATUS_UNREVIEWED)])
return q
class ViewFastTrackQueue(VersionSpecificQueue):
def base_query(self):
q = super(ViewFastTrackQueue, self).base_query()
q['where'].extend(['files.no_restart = 1', # is jetpack
'files.requires_chrome = 0',
'files.status = %s' % amo.STATUS_UNREVIEWED,
'addons.status IN (%s, %s, %s, %s)' % (
amo.STATUS_LITE,
amo.STATUS_UNREVIEWED,
amo.STATUS_NOMINATED,
amo.STATUS_LITE_AND_NOMINATED)])
return q
class PerformanceGraph(ViewQueue):
id = models.IntegerField()
yearmonth = models.CharField(max_length=7)
approval_created = models.DateTimeField()
user_id = models.IntegerField()
total = models.IntegerField()
def base_query(self):
request_ver = amo.LOG.REQUEST_VERSION.id
review_ids = [str(r) for r in amo.LOG_REVIEW_QUEUE if r != request_ver]
return {
'select': SortedDict([
('yearmonth',
"DATE_FORMAT(`log_activity`.`created`, '%%Y-%%m')"),
('approval_created', '`log_activity`.`created`'),
('user_id', '`users`.`id`'),
('total', 'COUNT(*)')]),
'from': [
'log_activity',
'LEFT JOIN `users` ON (`users`.`id`=`log_activity`.`user_id`)'],
'where': ['log_activity.action in (%s)' % ','.join(review_ids)],
'group_by': 'yearmonth, user_id'
}
class EditorSubscription(amo.models.ModelBase):
user = models.ForeignKey(UserProfile)
addon = models.ForeignKey(Addon)
class Meta:
db_table = 'editor_subscriptions'
def send_notification(self, version):
user_log.info('Sending addon update notice to %s for %s' %
(self.user.email, self.addon.pk))
context = Context({
'name': self.addon.name,
'url': absolutify(reverse('addons.detail', args=[self.addon.pk])),
'number': version.version,
'review': absolutify(reverse('editors.review',
args=[self.addon.pk])),
'SITE_URL': settings.SITE_URL,
})
# Not being localised because we don't know the editors locale.
subject = 'Mozilla Add-ons: %s Updated' % self.addon.name
template = loader.get_template('editors/emails/notify_update.ltxt')
send_mail(subject, template.render(Context(context)),
recipient_list=[self.user.email],
from_email=settings.EDITORS_EMAIL,
use_blacklist=False)
def send_notifications(signal=None, sender=None, **kw):
if sender.is_beta:
return
subscribers = sender.addon.editorsubscription_set.all()
if not subscribers:
return
for subscriber in subscribers:
subscriber.send_notification(sender)
subscriber.delete()
version_uploaded.connect(send_notifications, dispatch_uid='send_notifications')