From: Radek Czajka Date: Tue, 20 Mar 2012 16:48:25 +0000 (+0100) Subject: basic waiter support; needs polishing X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/6e0b282242bf227d1dba35b2748b7cadb48741db basic waiter support; needs polishing --- diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 556a8a1b8..722539ade 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -227,7 +227,7 @@ def get_customized_pdf_path(book, customizations): """ h = customizations_hash(customizations) pdf_name = '%s-custom-%s' % (book.slug, h) - pdf_file = get_dynamic_path(None, pdf_name, ext='pdf') + pdf_file = pdf_name + '.pdf' return pdf_file diff --git a/apps/catalogue/templates/catalogue/book_wide.html b/apps/catalogue/templates/catalogue/book_wide.html index c683fce61..9c5c85697 100644 --- a/apps/catalogue/templates/catalogue/book_wide.html +++ b/apps/catalogue/templates/catalogue/book_wide.html @@ -52,7 +52,6 @@
- {% if related.media.mp3 or related.media.ogg %}

{% trans "Download" %}

- {% endif %}
{% endblock %} diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 7ead47115..c8549c26f 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -7,7 +7,7 @@ import itertools from django.conf import settings from django.template import RequestContext -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, get_object_or_404, redirect from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect from django.core.urlresolvers import reverse from django.db.models import Q @@ -30,6 +30,7 @@ from suggest.forms import PublishingSuggestForm from picture.models import Picture from os import path +from waiter.models import WaitedFile staff_required = user_passes_test(lambda user: user.is_staff) @@ -540,10 +541,11 @@ def download_custom_pdf(request, slug, method='GET'): cust = form.customizations pdf_file = models.get_customized_pdf_path(book, cust) - if not path.exists(pdf_file): - result = async_build_pdf.delay(book.id, cust, pdf_file) - result.wait() - return AttachmentHttpResponse(file_name=("%s.pdf" % book.slug), file_path=pdf_file, mimetype="application/pdf") + url = WaitedFile.order(pdf_file, + lambda p: async_build_pdf.delay(book.id, cust, p), + "%s: %s" % (book.pretty_title(), ", ".join(cust)) + ) + return redirect(url) else: raise Http404(_('Incorrect customization options for PDF')) else: diff --git a/apps/waiter/__init__.py b/apps/waiter/__init__.py new file mode 100644 index 000000000..5aabbd62d --- /dev/null +++ b/apps/waiter/__init__.py @@ -0,0 +1,5 @@ +""" +Waiting for and serving files generated in Celery to the user. + +Author: Radek Czajka +""" \ No newline at end of file diff --git a/apps/waiter/migrations/0001_initial.py b/apps/waiter/migrations/0001_initial.py new file mode 100644 index 000000000..062d6f895 --- /dev/null +++ b/apps/waiter/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'WaitedFile' + db.create_table('waiter_waitedfile', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, db_index=True)), + ('task', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)), + ('description', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + )) + db.send_create_signal('waiter', ['WaitedFile']) + + + def backwards(self, orm): + + # Deleting model 'WaitedFile' + db.delete_table('waiter_waitedfile') + + + models = { + 'waiter.waitedfile': { + 'Meta': {'object_name': 'WaitedFile'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}) + } + } + + complete_apps = ['waiter'] diff --git a/apps/waiter/migrations/__init__.py b/apps/waiter/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/waiter/models.py b/apps/waiter/models.py new file mode 100644 index 000000000..fd9730131 --- /dev/null +++ b/apps/waiter/models.py @@ -0,0 +1,53 @@ +from os.path import join, abspath, exists +from django.db import models +from waiter.settings import WAITER_ROOT, WAITER_URL +from django.core.urlresolvers import reverse + +class WaitedFile(models.Model): + path = models.CharField(max_length=255, unique=True, db_index=True) + task = models.CharField(max_length=64, null=True, editable=False) + description = models.CharField(max_length=255, null=True, blank=True) + + @staticmethod + def abspath(path): + abs_path = abspath(join(WAITER_ROOT, path)) + if not abs_path.startswith(WAITER_ROOT): + raise ValueError('Path not inside WAITER_ROOT.') + return abs_path + + @classmethod + def exists(cls, path): + """Returns opened file or None. + + `path` is relative to WAITER_ROOT. + Won't open a path leading outside of WAITER_ROOT. + """ + abs_path = cls.abspath(path) + # Pre-fetch objects to avoid minor race condition + relevant = [o.id for o in cls.objects.filter(path=path)] + print abs_path + if exists(abs_path): + cls.objects.filter(id__in=relevant).delete() + return True + else: + return False + + @classmethod + def order(cls, path, task_creator, description=None): + """ + Returns an URL for the user to follow. + If the file is ready, returns download URL. + If not, starts preparing it and returns waiting URL. + """ + already = cls.exists(path) + if not already: + waited, created = cls.objects.get_or_create(path=path) + if created: + # TODO: makedirs + waited.task = task_creator(cls.abspath(path)) + print waited.task + waited.description = description + waited.save() + # TODO: it the task exists, if stale delete, send some mail and restart + return reverse("waiter", args=[path]) + return join(WAITER_URL, path) diff --git a/apps/waiter/settings.py b/apps/waiter/settings.py new file mode 100644 index 000000000..f75edfe50 --- /dev/null +++ b/apps/waiter/settings.py @@ -0,0 +1,12 @@ +from os.path import join +from django.conf import settings + +try: + WAITER_ROOT = settings.WAITER_ROOT +except AttributeError: + WAITER_ROOT = join(settings.MEDIA_ROOT, 'waiter') + +try: + WAITER_URL = settings.WAITER_URL +except AttributeError: + WAITER_URL = join(settings.MEDIA_URL, 'waiter') diff --git a/apps/waiter/templates/waiter/wait.html b/apps/waiter/templates/waiter/wait.html new file mode 100644 index 000000000..7c5a88428 --- /dev/null +++ b/apps/waiter/templates/waiter/wait.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block titleextra %}Prosię ciekać{% endblock %} + + +{% block extrahead %} + {# TODO: Not like that! What are you, caveman?! #} + +{% endblock %} + + +{% block body %} + {% if file_url %} +

Plik jest gotowy! Jeśli pobieranie nie zacznie się automatycznie, + oto bezpośredni link.

+ {% else %} +

Idź na kawę. To trochę potrwa.

+ +

{{ waiting_for.description }}

+ {% endif %} +{% endblock %} diff --git a/apps/waiter/urls.py b/apps/waiter/urls.py new file mode 100644 index 000000000..484ef3e44 --- /dev/null +++ b/apps/waiter/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('waiter.views', + url(r'^(?P.*)$', 'wait', name='waiter'), +) diff --git a/apps/waiter/views.py b/apps/waiter/views.py new file mode 100644 index 000000000..81cd3b49d --- /dev/null +++ b/apps/waiter/views.py @@ -0,0 +1,12 @@ +from os.path import join +from waiter.models import WaitedFile +from waiter.settings import WAITER_URL +from django.shortcuts import get_object_or_404, render, redirect + +def wait(request, path): + if WaitedFile.exists(path): + file_url = join(WAITER_URL, path) + else: + waiting_for = get_object_or_404(WaitedFile, path=path) + # TODO: check if not stale, inform the user and send some mail if so. + return render(request, "waiter/wait.html", locals()) diff --git a/wolnelektury/settings/__init__.py b/wolnelektury/settings/__init__.py index 4d8b81b9c..fe091a1b2 100644 --- a/wolnelektury/settings/__init__.py +++ b/wolnelektury/settings/__init__.py @@ -92,6 +92,7 @@ INSTALLED_APPS = [ 'picture', 'search', 'social', + 'waiter', ] # Load localsettings, if they exist diff --git a/wolnelektury/urls.py b/wolnelektury/urls.py index ce51801c5..5323ada9b 100644 --- a/wolnelektury/urls.py +++ b/wolnelektury/urls.py @@ -34,6 +34,7 @@ urlpatterns += patterns('', url(r'^info/', include('infopages.urls')), url(r'^ludzie/', include('social.urls')), url(r'^uzytkownik/', include('allauth.urls')), + url(r'^czekaj/', include('waiter.urls')), # Admin panel url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),