diff --git a/pdf/generate.py b/pdf/generate.py index bddf610..4a3df26 100644 --- a/pdf/generate.py +++ b/pdf/generate.py @@ -2,22 +2,25 @@ import locale import logging import os +import re import tempfile +from datetime import datetime from math import ceil +from time import time import weasyprint +from rq import Retry from weasyprint.logger import PROGRESS_LOGGER from django.conf import settings from django.core.files import File from django.template.loader import render_to_string from django.urls import reverse from django.utils import translation -from django_rq import job +from django_rq import job, get_queue from django_weasyprint.utils import django_url_fetcher from pdf.locales import changed_locale, lang_to_locale from pdf.models.request import PDFRequest, Status -from pdf.utils import Timer, ProgressFilter logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -88,3 +91,47 @@ def generate_pdf(request: PDFRequest): def generate_pdf_job(request: PDFRequest): """Generates PDF from request in the background""" generate_pdf(request) + + +def schedule_generation(request: PDFRequest, schedule_time: datetime): + """Schedules generation of a request at a specific time""" + queue = get_queue('default') + created_job = queue.enqueue_at( + schedule_time, + generate_pdf, + request, + retry = Retry(max=5, interval=120) + ) + logger.info("Schedule PDF generation of request %s at %s", request.id, schedule_time) + return created_job + + +class ProgressFilter(logging.Filter): + """Filters Weasyprint progress messages, highly dependent on implementation!""" + STEP_NUMBER = re.compile(r"(?<=Step\s)\d") + + def __init__(self, request): + super().__init__() + self.request = request + + def filter(self, record): + step = self.STEP_NUMBER.search(record.getMessage()).group(0) + if self.request.progress != step: + self.request.progress = step + self.request.save() + return True + + +class Timer: + """Context manager for measuring time""" + def __init__(self): + self.duration = 0 + self.start = 0 + self.end = 0 + + def __enter__(self): + self.start = time() + + def __exit__(self, exit_type, value, traceback): + self.end = time() + self.duration = self.end - self.start diff --git a/pdf/management/commands/generate_pdf.py b/pdf/management/commands/generate_pdf.py index 5ec6a49..387c5a0 100644 --- a/pdf/management/commands/generate_pdf.py +++ b/pdf/management/commands/generate_pdf.py @@ -42,7 +42,7 @@ def handle(self, *args, **options): if all_requests: objects = [generate_new_pdf_request(category) for category in Category.objects.filter(generate_pdf=True)] else: - objects = PDFRequest.objects.filter(status=Status.QUEUED) + objects = PDFRequest.objects.filter(status__in={Status.QUEUED, Status.SCHEDULED}) if requests: objects = objects[:requests] diff --git a/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py b/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py new file mode 100644 index 0000000..e45ad9c --- /dev/null +++ b/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.1.10 on 2023-09-09 18:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pdf', '0019_pdfrequest_public'), + ] + + operations = [ + migrations.RemoveField( + model_name='pdfrequest', + name='created_date', + ), + migrations.AddField( + model_name='pdfrequest', + name='scheduled_at', + field=models.DateTimeField(null=True), + ), + migrations.AlterField( + model_name='pdfrequest', + name='status', + field=models.CharField(choices=[('QU', 'Queued'), ('SC', 'Scheduled'), ('PR', 'In progress'), ('DO', 'Done'), ('FA', 'Failed')], default='QU', max_length=2), + ), + ] diff --git a/pdf/models/request.py b/pdf/models/request.py index a7f116f..7674397 100644 --- a/pdf/models/request.py +++ b/pdf/models/request.py @@ -3,7 +3,7 @@ from django.core.validators import MinValueValidator from django.db.models import Model, DateField, DateTimeField, IntegerField, FileField, CharField, TextChoices, \ - ManyToManyField, ForeignKey, CASCADE, SET_NULL, CheckConstraint, Q, PositiveIntegerField + ManyToManyField, ForeignKey, CASCADE, SET_NULL, CheckConstraint, Q, PositiveIntegerField, TextField from django.utils.translation import gettext_lazy as _ from backend.models import Song @@ -23,6 +23,7 @@ class RequestType(TextChoices): class Status(TextChoices): """Status of PDF Request""" QUEUED = "QU", _('Queued') + SCHEDULED = "SC", _('Scheduled') IN_PROGRESS = "PR", _('In progress') DONE = "DO", _("Done") FAILED = "FA", _("Failed") @@ -30,7 +31,7 @@ class Status(TextChoices): class PDFRequest(PDFOptions): """Request for PDF generation""" - created_date = DateField(auto_now_add=True, editable=False) +# created_date = DateField(auto_now_add=True, editable=False) update_date = DateTimeField(auto_now=True) type = CharField( max_length=2, @@ -47,6 +48,7 @@ class PDFRequest(PDFOptions): file = FileField(null=True, storage=fs) songs = ManyToManyField(Song, through="PDFSong") category = ForeignKey(Category, null=True, on_delete=SET_NULL) + scheduled_at = DateTimeField(null=True) def get_songs(self) -> List[Song]: """Returns all songs for request""" diff --git a/pdf/templates/pdf/requests/list.html b/pdf/templates/pdf/requests/list.html index de9f117..f9070e0 100644 --- a/pdf/templates/pdf/requests/list.html +++ b/pdf/templates/pdf/requests/list.html @@ -6,12 +6,22 @@ {% block header %} {% trans "PDF Requests" %} {% endblock %} {% block extra_head %} - - + + {% endblock %} {% block framed_body %} -