From 56511fcfdd20abcaf54827a90125c527d154125b Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 28 Mar 2012 16:30:47 +0200 Subject: [PATCH] working waiter --- apps/ajaxable/utils.py | 14 ++++--- apps/catalogue/forms.py | 24 ++++++++++- .../templates/catalogue/book_wide.html | 2 +- apps/catalogue/templatetags/catalogue_tags.py | 1 - apps/catalogue/urls.py | 12 +++--- apps/catalogue/views.py | 42 +++---------------- apps/waiter/migrations/0001_initial.py | 4 +- apps/waiter/models.py | 12 +++++- apps/waiter/settings.py | 6 +++ apps/waiter/tasks.py | 7 ++++ 10 files changed, 70 insertions(+), 54 deletions(-) create mode 100644 apps/waiter/tasks.py diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py index 52cf63833..4ae6e869e 100755 --- a/apps/ajaxable/utils.py +++ b/apps/ajaxable/utils.py @@ -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: diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index d191d46db..c4ddbcbf5 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -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} diff --git a/apps/catalogue/templates/catalogue/book_wide.html b/apps/catalogue/templates/catalogue/book_wide.html index 9c5c85697..ad588bd7e 100644 --- a/apps/catalogue/templates/catalogue/book_wide.html +++ b/apps/catalogue/templates/catalogue/book_wide.html @@ -62,7 +62,7 @@ {% endif %}
  • - {% trans "Download a custom PDF" %} + {% trans "Download a custom PDF" %}
  • diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py index 7d401289b..e4ed6e82a 100644 --- a/apps/catalogue/templatetags/catalogue_tags.py +++ b/apps/catalogue/templatetags/catalogue_tags.py @@ -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'), } diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index da4a3aec3..647bc9f37 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -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%s)/$' % SLUG, CustomPDFFormView(), name='custom_pdf_form'), + + url(r'^audiobooki/(?Pmp3|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%s)/motyw/(?P[a-zA-Z0-9-]+)/$' % SLUG, 'book_fragments', name='book_fragments'), + # This should be the last pattern. url(r'^(?P[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'), - - url(r'^audiobooki/(?Pmp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'), - - url(r'^custompdf$', CustomPDFFormView(), name='custom_pdf_form'), - url(r'^custompdf/(?P%s).pdf' % SLUG, 'download_custom_pdf'), - ) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index eadaeca94..d2176bf6b 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -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 diff --git a/apps/waiter/migrations/0001_initial.py b/apps/waiter/migrations/0001_initial.py index 1c27085e8..a75884bac 100644 --- a/apps/waiter/migrations/0001_initial.py +++ b/apps/waiter/migrations/0001_initial.py @@ -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'}) } } diff --git a/apps/waiter/models.py b/apps/waiter/models.py index 59eeea682..10f92897e 100644 --- a/apps/waiter/models.py +++ b/apps/waiter/models.py @@ -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]) diff --git a/apps/waiter/settings.py b/apps/waiter/settings.py index f75edfe50..fef4a7e49 100644 --- a/apps/waiter/settings.py +++ b/apps/waiter/settings.py @@ -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 index 000000000..4c3933ef1 --- /dev/null +++ b/apps/waiter/tasks.py @@ -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) -- 2.20.1