working waiter
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 28 Mar 2012 14:30:47 +0000 (16:30 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 28 Mar 2012 14:30:47 +0000 (16:30 +0200)
apps/ajaxable/utils.py
apps/catalogue/forms.py
apps/catalogue/templates/catalogue/book_wide.html
apps/catalogue/templatetags/catalogue_tags.py
apps/catalogue/urls.py
apps/catalogue/views.py
apps/waiter/migrations/0001_initial.py
apps/waiter/models.py
apps/waiter/settings.py
apps/waiter/tasks.py [new file with mode: 0644]

index 52cf638..4ae6e86 100755 (executable)
@@ -101,14 +101,16 @@ class AjaxableFormView(object):
             form = self.form_class(*form_args, **form_kwargs)
             if form.is_valid():
                 add_args = self.success(form, request)
-                redirect = request.GET.get('next')
-                if not request.is_ajax() and redirect:
-                    return HttpResponseRedirect(urlquote_plus(
-                            redirect, safe='/?=&'))
-                response_data = {'success': True, 
-                    'message': self.success_message, 'redirect': redirect}
+                response_data = {
+                    'success': True, 
+                    'message': self.success_message,
+                    'redirect': request.GET.get('next')
+                    }
                 if add_args:
                     response_data.update(add_args)
+                if not request.is_ajax() and response_data['redirect']:
+                    return HttpResponseRedirect(urlquote_plus(
+                            response_data['redirect'], safe='/?=&'))
             elif request.is_ajax():
                 # Form was sent with errors. Send them back.
                 if self.form_prefix:
index d191d46..c4ddbcb 100644 (file)
@@ -6,6 +6,10 @@ from django import forms
 from django.utils.translation import ugettext_lazy as _
 
 from catalogue.models import Book
+from waiter.models import WaitedFile
+from django.core.exceptions import ValidationError
+from catalogue.utils import get_customized_pdf_path
+from catalogue.tasks import build_custom_pdf
 
 
 class BookImportForm(forms.Form):
@@ -61,13 +65,22 @@ CUSTOMIZATION_OPTIONS = (
 
 
 class CustomPDFForm(forms.Form):
-    def __init__(self, *args, **kwargs):
+    def __init__(self, book, *args, **kwargs):
         super(CustomPDFForm, self).__init__(*args, **kwargs)
+        self.book = book
         for name, label in CUSTOMIZATION_FLAGS:
             self.fields[name] = forms.BooleanField(required=False, label=label)
         for name, label, choices in CUSTOMIZATION_OPTIONS:
             self.fields[name] = forms.ChoiceField(choices, label=label)
 
+    def clean(self):
+        self.cleaned_data['cust'] = self.customizations
+        self.cleaned_data['path'] = get_customized_pdf_path(self.book,
+            self.cleaned_data['cust'])
+        if not WaitedFile.can_order(self.cleaned_data['path']):
+            raise ValidationError(_('Queue is full. Please try again later.'))
+        return self.cleaned_data
+
     @property
     def customizations(self):
         c = []
@@ -78,3 +91,12 @@ class CustomPDFForm(forms.Form):
             c.append(self.cleaned_data[name])
         c.sort()
         return c
+
+    def save(self, *args, **kwargs):
+        url = WaitedFile.order(self.cleaned_data['path'],
+            lambda p: build_custom_pdf.delay(self.book.id,
+                self.cleaned_data['cust'], p),
+            self.book.pretty_title()
+            )
+        #return redirect(url)
+        return {"redirect": url}
index 9c5c856..ad588bd 100644 (file)
@@ -62,7 +62,7 @@
        {% endif %}
       </li>
       <li>
-       <a href="{% url custom_pdf_form %}?slug={{book.slug}}" id="custom-pdf" class="ajaxable">{% trans "Download a custom PDF" %}</a>
+       <a href="{% url custom_pdf_form book.slug %}" id="custom-pdf" class="ajaxable">{% trans "Download a custom PDF" %}</a>
       </li>
     </ul>
   </div>
index 7d40128..e4ed6e8 100644 (file)
@@ -309,7 +309,6 @@ def book_wide(context, book):
         'extra_info': book.get_extra_info_value(),
         'hide_about': hide_about,
         'themes': book_themes,
-        'custom_pdf_form': forms.CustomPDFForm(),
         'request': context.get('request'),
     }
 
index da4a3ae..647bc9f 100644 (file)
@@ -37,6 +37,11 @@ urlpatterns += patterns('catalogue.views',
     url(r'^jtags/$', 'json_tags_starting_with', name='jhint'),
     #url(r'^szukaj/$', 'search', name='old_search'),
 
+    url(r'^custompdf/(?P<slug>%s)/$' % SLUG, CustomPDFFormView(), name='custom_pdf_form'),
+
+    url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
+
+
     # zip
     url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
     url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'),
@@ -51,11 +56,6 @@ urlpatterns += patterns('catalogue.views',
     url(r'^lektura/(?P<slug>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % SLUG,
         'book_fragments', name='book_fragments'),
 
+    # This should be the last pattern.
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
-
-    url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
-
-    url(r'^custompdf$', CustomPDFFormView(), name='custom_pdf_form'),
-    url(r'^custompdf/(?P<slug>%s).pdf' % SLUG, 'download_custom_pdf'),
-
 )
index eadaeca..d2176bf 100644 (file)
@@ -22,15 +22,12 @@ from ajaxable.utils import JSONResponse, AjaxableFormView
 
 from catalogue import models
 from catalogue import forms
-from catalogue.utils import split_tags, MultiQuerySet, get_customized_pdf_path
-from catalogue.tasks import build_custom_pdf
+from catalogue.utils import split_tags, MultiQuerySet
 from pdcounter import models as pdcounter_models
 from pdcounter import views as pdcounter_views
 from suggest.forms import PublishingSuggestForm
 from picture.models import Picture
 
-from waiter.models import WaitedFile
-
 staff_required = user_passes_test(lambda user: user.is_staff)
 
 
@@ -531,45 +528,18 @@ def download_zip(request, format, slug=None):
     return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?='))
 
 
-def download_custom_pdf(request, slug, method='GET'):
-    book = get_object_or_404(models.Book, slug=slug)
-
-    if request.method == method:
-        form = forms.CustomPDFForm(method == 'GET' and request.GET or request.POST)
-        if form.is_valid():
-            cust = form.customizations
-            pdf_file = get_customized_pdf_path(book, cust)
-
-            url = WaitedFile.order(pdf_file,
-                    lambda p: build_custom_pdf.delay(book.id, cust, p),
-                    book.pretty_title()
-                )
-            return redirect(url)
-        else:
-            raise Http404(_('Incorrect customization options for PDF'))
-    else:
-        raise Http404(_('Bad method'))
-
-
 class CustomPDFFormView(AjaxableFormView):
     form_class = forms.CustomPDFForm
     title = ugettext_lazy('Download custom PDF')
     submit = ugettext_lazy('Download')
     honeypot = True
 
-    def __call__(self, request):
-        from copy import copy
-        if request.method == 'POST':
-            request.GET = copy(request.GET)
-            request.GET['next'] = "%s?%s" % (reverse('catalogue.views.download_custom_pdf', args=[request.GET.get('slug')]),
-                                             request.POST.urlencode())
-        return super(CustomPDFFormView, self).__call__(request)
+    def form_args(self, request, obj):
+        """Override to parse view args and give additional args to the form."""
+        return (obj,), {}
 
-    def get_object(self, request):
-        return get_object_or_404(models.Book, slug=request.GET.get('slug'))
+    def get_object(self, request, slug, *args, **kwargs):
+        return get_object_or_404(models.Book, slug=slug)
 
     def context_description(self, request, obj):
         return obj.pretty_title()
-
-    def success(self, *args):
-        pass
index 1c27085..a75884b 100644 (file)
@@ -12,6 +12,7 @@ class Migration(SchemaMigration):
         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_id', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=128, null=True, blank=True)),
             ('task', self.gf('picklefield.fields.PickledObjectField')(null=True)),
             ('description', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
         ))
@@ -30,7 +31,8 @@ class Migration(SchemaMigration):
             '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': ('picklefield.fields.PickledObjectField', [], {'null': 'True'})
+            'task': ('picklefield.fields.PickledObjectField', [], {'null': 'True'}),
+            'task_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'})
         }
     }
 
index 59eeea6..10f9289 100644 (file)
@@ -1,14 +1,14 @@
 from os.path import join, isfile
 from django.core.urlresolvers import reverse
 from django.db import models
-from djcelery.models import TaskMeta
-from waiter.settings import WAITER_URL
+from waiter.settings import WAITER_URL, WAITER_MAX_QUEUE
 from waiter.utils import check_abspath
 from picklefield import PickledObjectField
 
 
 class WaitedFile(models.Model):
     path = models.CharField(max_length=255, unique=True, db_index=True)
+    task_id = models.CharField(max_length=128, db_index=True, null=True, blank=True)
     task = PickledObjectField(null=True, editable=False)
     description = models.CharField(max_length=255, null=True, blank=True)
 
@@ -28,6 +28,13 @@ class WaitedFile(models.Model):
         else:
             return False
 
+    @classmethod
+    def can_order(cls, path):
+        return (cls.objects.filter(path=path).exists() or
+                cls.exists(path) or
+                cls.objects.count() < WAITER_MAX_QUEUE
+                )
+
     def is_stale(self):
         if self.task is None:
             # Race; just let the other task roll. 
@@ -51,6 +58,7 @@ class WaitedFile(models.Model):
             waited, created = cls.objects.get_or_create(path=path)
             if created or waited.is_stale():
                 waited.task = task_creator(check_abspath(path))
+                waited.task_id = waited.task.task_id
                 waited.description = description
                 waited.save()
             return reverse("waiter", args=[path])
index f75edfe..fef4a7e 100644 (file)
@@ -10,3 +10,9 @@ try:
     WAITER_URL = settings.WAITER_URL
 except AttributeError:
     WAITER_URL = join(settings.MEDIA_URL, 'waiter')
+
+try:
+    WAITER_MAX_QUEUE = settings.WAITER_MAX_QUEUE
+except AttributeError:
+    WAITER_MAX_QUEUE = 20
+
diff --git a/apps/waiter/tasks.py b/apps/waiter/tasks.py
new file mode 100644 (file)
index 0000000..4c3933e
--- /dev/null
@@ -0,0 +1,7 @@
+from celery.signals import task_postrun
+from waiter.models import WaitedFile
+
+
+def task_delete_after(task_id=None, **kwargs):
+    WaitedFile.objects.filter(task_id=task_id).delete()
+task_postrun.connect(task_delete_after)