209 строки
7.8 KiB
Python
209 строки
7.8 KiB
Python
import datetime
|
|
|
|
from django.conf import settings
|
|
|
|
import commonware.log
|
|
import phpserialize
|
|
from celeryutils import task
|
|
from hera.contrib.django_utils import flush_urls
|
|
|
|
import amo
|
|
from abuse.models import AbuseReport
|
|
from addons.models import Addon
|
|
from amo.decorators import set_task_user
|
|
from applications.models import Application, AppVersion
|
|
from bandwagon.models import Collection
|
|
from devhub.models import ActivityLog, AppLog, LegacyAddonLog
|
|
from editors.models import EscalationQueue, EventLog
|
|
from market.models import Refund
|
|
from reviews.models import Review
|
|
from stats.models import Contribution
|
|
|
|
|
|
log = commonware.log.getLogger('z.task')
|
|
|
|
|
|
@task
|
|
def flush_front_end_cache_urls(urls, **kw):
|
|
"""Accepts a list of urls which will be sent through Hera to the front end
|
|
cache. This does no checking for success or failure or whether the URLs
|
|
were in the cache to begin with."""
|
|
|
|
if not urls:
|
|
return
|
|
|
|
log.info(u"Flushing %d URLs from front end cache: (%s)" % (len(urls),
|
|
urls))
|
|
|
|
# Zeus is only interested in complete URLs. We can't just pass a
|
|
# prefix to Hera because some URLs will be on SAMO.
|
|
for index, url in enumerate(urls):
|
|
if not url.startswith('http'):
|
|
if '/api/' in url:
|
|
urls[index] = u"%s%s" % (settings.SERVICES_URL, url)
|
|
else:
|
|
urls[index] = u"%s%s" % (settings.SITE_URL, url)
|
|
|
|
flush_urls(urls)
|
|
|
|
|
|
@task
|
|
def set_modified_on_object(obj, **kw):
|
|
"""Sets modified on one object at a time."""
|
|
try:
|
|
log.info('Setting modified on object: %s, %s' %
|
|
(obj.__class__.__name__, obj.pk))
|
|
obj.update(modified=datetime.datetime.now())
|
|
except Exception, e:
|
|
log.error('Failed to set modified on: %s, %s - %s' %
|
|
(obj.__class__.__name__, obj.pk, e))
|
|
|
|
|
|
@task
|
|
def delete_logs(items, **kw):
|
|
log.info('[%s@%s] Deleting logs' % (len(items), delete_logs.rate_limit))
|
|
ActivityLog.objects.filter(pk__in=items).exclude(
|
|
action__in=amo.LOG_KEEP).delete()
|
|
|
|
|
|
@task
|
|
def delete_stale_contributions(items, **kw):
|
|
log.info('[%s@%s] Deleting stale contributions' %
|
|
(len(items), delete_stale_contributions.rate_limit))
|
|
Contribution.objects.filter(
|
|
transaction_id__isnull=True, pk__in=items).delete()
|
|
|
|
|
|
@task
|
|
def delete_anonymous_collections(items, **kw):
|
|
log.info('[%s@%s] Deleting anonymous collections' %
|
|
(len(items), delete_anonymous_collections.rate_limit))
|
|
Collection.objects.filter(type=amo.COLLECTION_ANONYMOUS,
|
|
pk__in=items).delete()
|
|
|
|
|
|
@task
|
|
def delete_incomplete_addons(items, **kw):
|
|
log.info('[%s@%s] Deleting incomplete add-ons' %
|
|
(len(items), delete_incomplete_addons.rate_limit))
|
|
for addon in Addon.objects.filter(
|
|
highest_status=0, status=0, pk__in=items):
|
|
try:
|
|
addon.delete('Deleted for incompleteness')
|
|
except Exception as e:
|
|
log.error("Couldn't delete add-on %s: %s" % (addon.id, e))
|
|
|
|
|
|
@task
|
|
def migrate_admin_logs(items, **kw):
|
|
print 'Processing: %d..%d' % (items[0], items[-1])
|
|
for item in LegacyAddonLog.objects.filter(pk__in=items):
|
|
kw = dict(user=item.user, created=item.created)
|
|
amo.log(amo.LOG.ADD_APPVERSION, (Application, item.object1_id),
|
|
(AppVersion, item.object2_id), **kw)
|
|
|
|
|
|
@task
|
|
def migrate_editor_eventlog(items, **kw):
|
|
log.info('[%s@%s] Migrating eventlog items' %
|
|
(len(items), migrate_editor_eventlog.rate_limit))
|
|
for item in EventLog.objects.filter(pk__in=items):
|
|
kw = dict(user=item.user, created=item.created)
|
|
if item.action == 'review_delete':
|
|
details = None
|
|
try:
|
|
details = phpserialize.loads(item.notes)
|
|
except ValueError:
|
|
pass
|
|
amo.log(amo.LOG.DELETE_REVIEW, item.changed_id, details=details,
|
|
**kw)
|
|
elif item.action == 'review_approve':
|
|
try:
|
|
r = Review.objects.get(pk=item.changed_id)
|
|
amo.log(amo.LOG.ADD_REVIEW, r, r.addon, **kw)
|
|
except Review.DoesNotExist:
|
|
log.warning("Couldn't find review for %d" % item.changed_id)
|
|
|
|
|
|
@task
|
|
@set_task_user
|
|
def find_abuse_escalations(addon_id, **kw):
|
|
weekago = datetime.date.today() - datetime.timedelta(days=7)
|
|
|
|
for abuse in AbuseReport.recent_high_abuse_reports(1, weekago, addon_id):
|
|
if EscalationQueue.objects.filter(addon=abuse.addon).exists():
|
|
# App is already in the queue, no need to re-add it.
|
|
# TODO: If not in queue b/c of abuse reports, add an
|
|
# amo.LOG.ESCALATED_HIGH_ABUSE for reviewers.
|
|
log.info(u'[addon:%s] High abuse reports, but already escalated' %
|
|
(abuse.addon,))
|
|
continue
|
|
|
|
# We have an abuse report and this add-on isn't currently in the
|
|
# escalated queue... let's see if it has been detected and dealt with
|
|
# already.
|
|
logs = (AppLog.objects.filter(
|
|
activity_log__action=amo.LOG.ESCALATED_HIGH_ABUSE.id,
|
|
addon=abuse.addon).order_by('-created'))
|
|
if logs:
|
|
abuse_since_log = AbuseReport.recent_high_abuse_reports(
|
|
1, logs[0].created, addon_id)
|
|
# If no abuse reports have happened since the last logged abuse
|
|
# report, do not add to queue.
|
|
if not abuse_since_log:
|
|
log.info(u'[addon:%s] High abuse reports, but none since last '
|
|
'escalation' % abuse.addon)
|
|
continue
|
|
|
|
# If we haven't bailed out yet, escalate this app.
|
|
msg = u'High number of abuse reports detected'
|
|
EscalationQueue.objects.create(addon=abuse.addon)
|
|
amo.log(amo.LOG.ESCALATED_HIGH_ABUSE, abuse.addon,
|
|
abuse.addon.current_version, details={'comments': msg})
|
|
log.info(u'[addon:%s] %s' % (abuse.addon, msg))
|
|
|
|
|
|
@task
|
|
@set_task_user
|
|
def find_refund_escalations(addon_id, **kw):
|
|
try:
|
|
addon = Addon.objects.get(pk=addon_id)
|
|
except Addon.DoesNotExist:
|
|
log.info(u'[addon:%s] Task called but no addon found.' % addon_id)
|
|
return
|
|
|
|
refund_threshold = 0.05
|
|
weekago = datetime.date.today() - datetime.timedelta(days=7)
|
|
|
|
ratio = Refund.recent_refund_ratio(addon.id, weekago)
|
|
if ratio > refund_threshold:
|
|
if EscalationQueue.objects.filter(addon=addon).exists():
|
|
# App is already in the queue, no need to re-add it.
|
|
# TODO: If not in queue b/c of refunds, add an
|
|
# amo.LOG.ESCALATED_HIGH_REFUNDS for reviewers.
|
|
log.info(u'[addon:%s] High refunds, but already escalated' % addon)
|
|
return
|
|
|
|
# High refunds and app isn't in the escalation queue... let's see if
|
|
# it has been detected and dealt with already.
|
|
logs = (AppLog.objects.filter(
|
|
activity_log__action=amo.LOG.ESCALATED_HIGH_REFUNDS.id,
|
|
addon=addon).order_by('-created', '-id'))
|
|
if logs:
|
|
since_ratio = Refund.recent_refund_ratio(addon.id, logs[0].created)
|
|
# If not high enough ratio since the last logged, do not add to
|
|
# the queue.
|
|
if not since_ratio > refund_threshold:
|
|
log.info(u'[addon:%s] High refunds, but not enough since last '
|
|
'escalation. Ratio: %.0f%%' % (addon,
|
|
since_ratio * 100))
|
|
return
|
|
|
|
# If we haven't bailed out yet, escalate this app.
|
|
msg = u'High number of refund requests (%.0f%%) detected.' % (
|
|
(ratio * 100),)
|
|
EscalationQueue.objects.create(addon=addon)
|
|
amo.log(amo.LOG.ESCALATED_HIGH_REFUNDS, addon,
|
|
addon.current_version, details={'comments': msg})
|
|
log.info(u'[addon:%s] %s' % (addon, msg))
|