basic waiter support; needs polishing
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Tue, 20 Mar 2012 16:48:25 +0000 (17:48 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Tue, 20 Mar 2012 16:48:25 +0000 (17:48 +0100)
13 files changed:
apps/catalogue/models.py
apps/catalogue/templates/catalogue/book_wide.html
apps/catalogue/views.py
apps/waiter/__init__.py [new file with mode: 0644]
apps/waiter/migrations/0001_initial.py [new file with mode: 0644]
apps/waiter/migrations/__init__.py [new file with mode: 0644]
apps/waiter/models.py [new file with mode: 0644]
apps/waiter/settings.py [new file with mode: 0644]
apps/waiter/templates/waiter/wait.html [new file with mode: 0644]
apps/waiter/urls.py [new file with mode: 0644]
apps/waiter/views.py [new file with mode: 0644]
wolnelektury/settings/__init__.py
wolnelektury/urls.py

index 556a8a1..722539a 100644 (file)
@@ -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
 
index c683fce..9c5c856 100644 (file)
@@ -52,7 +52,6 @@
     </ul>
   </div>
   <div class="other-download">
-       {% if related.media.mp3 or related.media.ogg %}
     <h2 class="mono">{% trans "Download" %}</h2>
     <ul class="plain">
       <li>
        {% if related.media.ogg %}<a href="{% url download_zip_ogg book.slug %}">OGG</a>{% endif %}.
        {% endif %}
       </li>
-      {% comment %}
       <li>
        <a href="{% url custom_pdf_form %}?slug={{book.slug}}" id="custom-pdf" class="ajaxable">{% trans "Download a custom PDF" %}</a>
       </li>
-      {% endcomment %}
     </ul>
-    {% endif %}
   </div>
 </div>
 {% endblock %}
index 7ead471..c8549c2 100644 (file)
@@ -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 (file)
index 0000000..5aabbd6
--- /dev/null
@@ -0,0 +1,5 @@
+"""
+Waiting for and serving files generated in Celery to the user.
+
+Author: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
+"""
\ 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 (file)
index 0000000..062d6f8
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/apps/waiter/models.py b/apps/waiter/models.py
new file mode 100644 (file)
index 0000000..fd97301
--- /dev/null
@@ -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 (file)
index 0000000..f75edfe
--- /dev/null
@@ -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 (file)
index 0000000..7c5a884
--- /dev/null
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+
+{% block titleextra %}Prosię ciekać{% endblock %}
+
+
+{% block extrahead %}
+       {# TODO: Not like that! What are you, caveman?! #}
+       <meta http-equiv="refresh" content="{% if file_url %}0; url={{ file_url }}{% else %}5{% endif %}" />
+{% endblock %}
+
+
+{% block body %}
+       {% if file_url %}
+       <p>Plik jest gotowy! Jeśli pobieranie nie zacznie się automatycznie,
+               oto <a href="{{ file_url }}">bezpośredni link</a>.</p>
+       {% else %}
+               <p>Idź na kawę. To trochę potrwa.</p>
+
+               <p>{{ waiting_for.description }}</p>
+       {% endif %}
+{% endblock %}
diff --git a/apps/waiter/urls.py b/apps/waiter/urls.py
new file mode 100644 (file)
index 0000000..484ef3e
--- /dev/null
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('waiter.views',
+    url(r'^(?P<path>.*)$', 'wait', name='waiter'),
+)
diff --git a/apps/waiter/views.py b/apps/waiter/views.py
new file mode 100644 (file)
index 0000000..81cd3b4
--- /dev/null
@@ -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())
index 4d8b81b..fe091a1 100644 (file)
@@ -92,6 +92,7 @@ INSTALLED_APPS = [
     'picture',
     'search',
     'social',
+    'waiter',
 ]
 
 # Load localsettings, if they exist
index ce51801..5323ada 100644 (file)
@@ -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'),