From 02fcc6dde75a964e10c35bbd1d1e6d1191e46169 Mon Sep 17 00:00:00 2001 From: ffont Date: Tue, 13 Feb 2024 16:43:06 +0100 Subject: [PATCH] Move some post-moderation stuff to an async task This will make the approval of sound ticckets significantly faster and make the task easier for moderators. --- general/tasks.py | 63 +++++++++++++++++++++++++++++++++++++++++++++--- tickets/views.py | 47 ++++++++++-------------------------- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/general/tasks.py b/general/tasks.py index d7e0209d6..8cab51855 100644 --- a/general/tasks.py +++ b/general/tasks.py @@ -18,21 +18,18 @@ # See AUTHORS file. # import datetime -import hashlib import json import logging -import os import time import sentry_sdk - from celery import shared_task from django.apps import apps from django.conf import settings from django.contrib.auth.models import User from tickets import TICKET_STATUS_CLOSED -from tickets.models import Ticket +from tickets.models import Ticket, TicketComment from utils.audioprocessing.freesound_audio_processing import set_timeout_alarm, check_if_free_space, \ FreesoundAudioProcessor, WorkerException, cancel_timeout_alarm, FreesoundAudioProcessorBeforeDescription from utils.cache import invalidate_user_template_caches, invalidate_all_moderators_header_cache @@ -41,6 +38,7 @@ workers_logger = logging.getLogger("workers") WHITELIST_USER_TASK_NAME = 'whitelist_user' +POST_MODERATION_ASSIGNED_TICKETS_TASK_NAME = "post_moderation_assigned_tickets" DELETE_USER_TASK_NAME = 'delete_user' VALIDATE_BULK_DESCRIBE_CSV_TASK_NAME = "validate_bulk_describe_csv" BULK_DESCRIBE_TASK_NAME = "bulk_describe" @@ -114,6 +112,63 @@ def whitelist_user(ticket_ids=None, user_id=None): 'work_time': round(time.time() - start_time)})) +@shared_task(name=POST_MODERATION_ASSIGNED_TICKETS_TASK_NAME, queue=settings.CELERY_ASYNC_TASKS_QUEUE_NAME) +def post_moderation_assigned_tickets(ticket_ids=[], notification=None, msg=None, moderator_only=False, users_to_update=None, packs_to_update=None): + # Carry out post-processing tasks for the approved sounds like invlaidating caches, sending packs to process, etc... + # We do that in an async task to avoid moderation requests taking too long when approving sounds + workers_logger.info("Start post moderation assigned tickets (%s)" % json.dumps({ + 'task_name': POST_MODERATION_ASSIGNED_TICKETS_TASK_NAME, + 'n_tickets': len(ticket_ids)})) + start_time = time.time() + tickets = Ticket.objects.filter(id__in=ticket_ids) + + collect_users_and_packs = False + if not users_to_update and not packs_to_update: + collect_users_and_packs = True + users_to_update = set() + packs_to_update = set() + + for ticket in tickets: + if collect_users_and_packs: + # Collect list of users and packls to update + # We only fill here users_to_update and packs_to_update if action is not + # "Delete". See comment in "Delete" action case some lines above + users_to_update.add(ticket.sound.user_id) + if ticket.sound.pack: + packs_to_update.add(ticket.sound.pack_id) + + # Invalidate caches of related objects + invalidate_user_template_caches(ticket.sender.id) + invalidate_all_moderators_header_cache() + + # Add new comments to the ticket + if msg is not None: + tc = TicketComment(sender=ticket.assignee, + text=msg, + ticket=ticket, + moderator_only=moderator_only) + tc.save() + + # Send notification email to users + if notification is not None: + ticket.send_notification_emails(notification, Ticket.USER_ONLY) + + # Update number of sounds for each user + Profile = apps.get_model('accounts.Profile') + for profile in Profile.objects.filter(user_id__in=list(users_to_update)): + profile.update_num_sounds() + + # Process packs + Pack = apps.get_model('sounds.Pack') + for pack in Pack.objects.filter(id__in=list(packs_to_update)): + pack.process() + + workers_logger.info("Finished post moderation assigned tickets (%s)" % json.dumps( + {'task_name': POST_MODERATION_ASSIGNED_TICKETS_TASK_NAME, + 'n_tickets': len(ticket_ids), + 'work_time': round(time.time() - start_time)})) + + @shared_task(name=DELETE_USER_TASK_NAME, queue=settings.CELERY_ASYNC_TASKS_QUEUE_NAME) def delete_user(user_id, deletion_action, deletion_reason): try: diff --git a/tickets/views.py b/tickets/views.py index de9e15cf3..f064833db 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -20,7 +20,6 @@ import datetime -import json from django.conf import settings from django.contrib import messages @@ -33,7 +32,7 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.shortcuts import render -from general.tasks import whitelist_user as whitelist_user_task +from general.tasks import whitelist_user as whitelist_user_task, post_moderation_assigned_tickets as post_moderation_assigned_tickets_task from .models import Ticket, TicketComment, UserAnnotation from sounds.models import Sound @@ -556,9 +555,9 @@ def moderation_assigned(request, user_id): # and sounds_to_update before we delete the sounds and they dissapear # from the ticket (thus losing reference) for ticket in tickets: - users_to_update.add(ticket.sound.user.profile) + users_to_update.add(ticket.sound.user_id) if ticket.sound.pack: - packs_to_update.add(ticket.sound.pack) + packs_to_update.add(ticket.sound.pack_id) Sound.objects.filter(ticket__in=tickets).delete() # After we delete sounds that these tickets are associated with, # we refresh the ticket list so that sound_id is null and this does @@ -578,36 +577,16 @@ def moderation_assigned(request, user_id): page in a few seconds to see the updated list of pending tickets""" % ", ".join(users)) - for ticket in tickets: - if action != "Delete": - # We only fill here users_to_update and packs_to_update if action is not - # "Delete". See comment in "Delete" action case some lines above - users_to_update.add(ticket.sound.user.profile) - if ticket.sound.pack: - packs_to_update.add(ticket.sound.pack) - invalidate_user_template_caches(ticket.sender.id) - invalidate_all_moderators_header_cache() - moderator_only = msg_form.cleaned_data.get("moderator_only", False) - - if msg: - tc = TicketComment(sender=ticket.assignee, - text=msg, - ticket=ticket, - moderator_only=moderator_only) - tc.save() - - # Send emails - if notification: - ticket.send_notification_emails(notification, Ticket.USER_ONLY) - - # Update number of sounds for each user - for profile in users_to_update: - profile.update_num_sounds() - - # Process packs - for pack in packs_to_update: - pack.process() - + # Tirgger some async tasks to update user and pack counts, clear caches, send email notifications, etc. + post_moderation_assigned_tickets_task.delay( + ticket_ids=ticket_ids, + notification=notification, + msg=msg, + moderator_only=msg_form.cleaned_data.get("moderator_only", False), + users_to_update=list(users_to_update), + packs_to_update=list(packs_to_update) + ) + messages.add_message(request, messages.INFO, f"{len(tickets)} ticket(s) successfully updated") else: clear_forms = False