From 6cc5df1faff7c600d0cf4b4174621eca99e86354 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 20 Dec 2011 16:43:13 +0100 Subject: [PATCH] standarize ajaxable dialogs --- apps/ajaxable/__init__.py | 4 + apps/ajaxable/models.py | 0 apps/ajaxable/templates/ajaxable/form.html | 9 + .../templates/ajaxable/form_on_page.html | 14 + apps/ajaxable/utils.py | 82 ++++++ apps/catalogue/urls.py | 3 - apps/catalogue/views.py | 68 +---- apps/pdcounter/urls.py | 36 --- apps/pdcounter/views.py | 7 +- apps/suggest/forms.py | 39 ++- .../suggest/templates/publishing_suggest.html | 10 +- .../templates/publishing_suggest_full.html | 24 -- apps/suggest/templates/suggest.html | 9 - apps/suggest/urls.py | 17 +- apps/suggest/views.py | 107 +------- wolnelektury/settings.py | 29 ++- wolnelektury/static/css/dialogs.css | 87 +++++++ wolnelektury/static/css/main_page.css | 2 +- wolnelektury/static/js/dialogs.js | 56 ++++ wolnelektury/static/js/jquery.jqmodal.js | 239 ++++-------------- wolnelektury/static/js/locale.js | 29 +++ wolnelektury/static/js/pdcounter.js | 36 +++ wolnelektury/templates/auth/login.html | 25 -- wolnelektury/templates/base.html | 37 ++- wolnelektury/templates/main_page.html | 34 +-- .../templates/pdcounter/author_detail.html | 7 +- .../templates/pdcounter/book_stub_detail.html | 7 +- .../templates/pdcounter/pd_counter.html | 12 - wolnelektury/urls.py | 14 +- wolnelektury/views.py | 62 ++++- 30 files changed, 534 insertions(+), 571 deletions(-) create mode 100644 apps/ajaxable/__init__.py create mode 100644 apps/ajaxable/models.py create mode 100755 apps/ajaxable/templates/ajaxable/form.html create mode 100755 apps/ajaxable/templates/ajaxable/form_on_page.html create mode 100755 apps/ajaxable/utils.py delete mode 100644 apps/pdcounter/urls.py delete mode 100755 apps/suggest/templates/publishing_suggest_full.html delete mode 100644 apps/suggest/templates/suggest.html create mode 100755 wolnelektury/static/css/dialogs.css create mode 100755 wolnelektury/static/js/dialogs.js create mode 100755 wolnelektury/static/js/locale.js create mode 100755 wolnelektury/static/js/pdcounter.js delete mode 100644 wolnelektury/templates/auth/login.html delete mode 100644 wolnelektury/templates/pdcounter/pd_counter.html diff --git a/apps/ajaxable/__init__.py b/apps/ajaxable/__init__.py new file mode 100644 index 000000000..a0105436b --- /dev/null +++ b/apps/ajaxable/__init__.py @@ -0,0 +1,4 @@ +""" +Provides a way to create forms behaving correctly as AJAX forms +as well as standalone forms without any Javascript. +""" diff --git a/apps/ajaxable/models.py b/apps/ajaxable/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ajaxable/templates/ajaxable/form.html b/apps/ajaxable/templates/ajaxable/form.html new file mode 100755 index 000000000..d8f00361f --- /dev/null +++ b/apps/ajaxable/templates/ajaxable/form.html @@ -0,0 +1,9 @@ +{% load i18n %} +

{{ title }}

+ +
+
    + {{ form.as_ul }} +
  1. +
+
\ No newline at end of file diff --git a/apps/ajaxable/templates/ajaxable/form_on_page.html b/apps/ajaxable/templates/ajaxable/form_on_page.html new file mode 100755 index 000000000..e54231dc0 --- /dev/null +++ b/apps/ajaxable/templates/ajaxable/form_on_page.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block titleextra %}:: {{ title }}{% endblock %} + +{% block body %} + + {% include ajax_template %} + + {% if response_data.message %} +

{{ response_data.message }}

+ {% endif %} + +{% endblock %} diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py new file mode 100755 index 000000000..14b5dfc7a --- /dev/null +++ b/apps/ajaxable/utils.py @@ -0,0 +1,82 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.encoding import force_unicode +from django.utils.functional import Promise +from django.utils.http import urlquote_plus +from django.utils import simplejson +from django.utils.translation import ugettext_lazy as _ + + +class LazyEncoder(simplejson.JSONEncoder): + def default(self, obj): + if isinstance(obj, Promise): + return force_unicode(obj) + return obj + +# shortcut for JSON reponses +class JSONResponse(HttpResponse): + def __init__(self, data={}, callback=None, **kwargs): + # get rid of mimetype + kwargs.pop('mimetype', None) + data = simplejson.dumps(data) + if callback: + data = callback + "(" + data + ");" + super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs) + + + +class AjaxableFormView(object): + """Subclass this to create an ajaxable view for any form. + + In the subclass, provide at least form_class. + + """ + form_class = None + # override to customize form look + template = "ajaxable/form.html" + # set to redirect after succesful ajax-less post + submit = _('Send') + redirect = None + title = '' + success_message = '' + formname = "form" + full_template = "ajaxable/form_on_page.html" + + def __call__(self, request): + """A view displaying a form, or JSON if `ajax' GET param is set.""" + ajax = request.GET.get('ajax', False) + if request.method == "POST": + form = self.form_class(data=request.POST) + if form.is_valid(): + self.success(form, request) + redirect = request.GET.get('next') + if not ajax and redirect is not None: + return HttpResponseRedirect(urlquote_plus( + redirect, safe='/?=')) + response_data = {'success': True, 'message': self.success_message} + else: + response_data = {'success': False, 'errors': form.errors} + if ajax: + return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) + else: + form = self.form_class() + response_data = None + + template = self.template if ajax else self.full_template + return render_to_response(template, { + self.formname: form, + "title": self.title, + "submit": self.submit, + "response_data": response_data, + "ajax_template": self.template, + }, + context_instance=RequestContext(request)) + + def success(self, form, request): + """What to do when the form is valid. + + By default, just save the form. + + """ + return form.save(request) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index bb1b96033..0e62c300e 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -29,9 +29,6 @@ urlpatterns = patterns('catalogue.views', #url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'), #url(r'^zip/audiobook/(?P%s)\.zip' % Book.FILEID_RE, 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'), - # tools - url(r'^zegar/$', 'clock', name='clock'), - # Public interface. Do not change this URLs. url(r'^lektura/(?P%s)\.html$' % Book.FILEID_RE, 'book_text', name='book_text'), url(r'^lektura/(?P%s)/$' % Book.URLID_RE, 'book_detail', name='book_detail'), diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 13bb5ac18..8bf5681b7 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -17,15 +17,13 @@ from django.utils.datastructures import SortedDict from django.views.decorators.http import require_POST from django.contrib import auth from django.contrib.auth.forms import UserCreationForm, AuthenticationForm -from django.utils import simplejson -from django.utils.functional import Promise -from django.utils.encoding import force_unicode from django.utils.http import urlquote_plus from django.views.decorators import cache from django.utils import translation from django.utils.translation import ugettext as _ from django.views.generic.list_detail import object_list +from ajaxable.utils import LazyEncoder, JSONResponse from catalogue import models from catalogue import forms from catalogue.utils import split_tags, AttachmentHttpResponse, async_build_pdf @@ -39,23 +37,6 @@ from os import path staff_required = user_passes_test(lambda user: user.is_staff) -class LazyEncoder(simplejson.JSONEncoder): - def default(self, obj): - if isinstance(obj, Promise): - return force_unicode(obj) - return obj - -# shortcut for JSON reponses -class JSONResponse(HttpResponse): - def __init__(self, data={}, callback=None, **kwargs): - # get rid of mimetype - kwargs.pop('mimetype', None) - data = simplejson.dumps(data) - if callback: - data = callback + "(" + data + ");" - super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs) - - def catalogue(request): tags = models.Tag.objects.exclude( category__in=('set', 'book')).exclude(book_count=0) @@ -663,45 +644,6 @@ def delete_shelf(request, slug): return HttpResponseRedirect('/') -# ================== -# = Authentication = -# ================== -@require_POST -@cache.never_cache -def login(request): - form = AuthenticationForm(data=request.POST, prefix='login') - if form.is_valid(): - auth.login(request, form.get_user()) - response_data = {'success': True, 'errors': {}} - else: - response_data = {'success': False, 'errors': form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - - -@require_POST -@cache.never_cache -def register(request): - registration_form = UserCreationForm(request.POST, prefix='registration') - if registration_form.is_valid(): - user = registration_form.save() - user = auth.authenticate( - username=registration_form.cleaned_data['username'], - password=registration_form.cleaned_data['password1'] - ) - auth.login(request, user) - response_data = {'success': True, 'errors': {}} - else: - response_data = {'success': False, 'errors': registration_form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - - -@cache.never_cache -def logout_then_redirect(request): - auth.logout(request) - return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?=')) - - - # ========= # = Admin = # ========= @@ -726,14 +668,6 @@ def import_book(request): return HttpResponse(_("Error importing file: %r") % book_import_form.errors) - -def clock(request): - """ Provides server time for jquery.countdown, - in a format suitable for Date.parse() - """ - return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S')) - - # info views for API def book_info(request, id, lang='pl'): diff --git a/apps/pdcounter/urls.py b/apps/pdcounter/urls.py deleted file mode 100644 index 232513754..000000000 --- a/apps/pdcounter/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.conf.urls.defaults import * - - -urlpatterns = patterns('catalogue.views', - url(r'^$', 'main_page', name='main_page'), - url(r'^polki/(?P[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'), - url(r'^polki/(?P[a-zA-Z0-9-]+)/(?P[a-zA-Z0-9-0-]+)/usun$', 'remove_from_shelf', name='remove_from_shelf'), - url(r'^polki/$', 'user_shelves', name='user_shelves'), - url(r'^polki/(?P[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'), - url(r'^polki/(?P[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'), - url(r'^lektury/', 'book_list', name='book_list'), - url(r'^audiobooki/', 'audiobook_list', name='audiobook_list'), - url(r'^daisy/', 'daisy_list', name='daisy_list'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'), - url(r'^polki/nowa/$', 'new_set', name='new_set'), - url(r'^tags/$', 'tags_starting_with', name='hint'), - url(r'^jtags/$', 'json_tags_starting_with', name='jhint'), - url(r'^szukaj/$', 'search', name='search'), - - # tools - url(r'^zegar/$', 'clock', name='clock'), - url(r'^xmls.zip$', 'xmls', name='xmls'), - url(r'^epubs.tar$', 'epubs', name='epubs'), - - # Public interface. Do not change this URLs. - url(r'^lektura/(?P[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/motyw/(?P[a-zA-Z0-9-]+)/$', - 'book_fragments', name='book_fragments'), - url(r'^(?P[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'), -) - diff --git a/apps/pdcounter/views.py b/apps/pdcounter/views.py index efcfe95ee..b07ee11e9 100644 --- a/apps/pdcounter/views.py +++ b/apps/pdcounter/views.py @@ -7,16 +7,14 @@ from django.template import RequestContext from django.shortcuts import render_to_response, get_object_or_404 from pdcounter import models -from catalogue import forms from suggest.forms import PublishingSuggestForm def book_stub_detail(request, slug): book = get_object_or_404(models.BookStub, slug=slug) pd_counter = book.pd - form = forms.SearchForm() - pubsuggest_form = PublishingSuggestForm( + form = PublishingSuggestForm( initial={"books": u"%s — %s, \n" % (book.author, book.title)}) return render_to_response('pdcounter/book_stub_detail.html', locals(), @@ -26,9 +24,8 @@ def book_stub_detail(request, slug): def author_detail(request, slug): author = get_object_or_404(models.Author, slug=slug) pd_counter = author.goes_to_pd() - form = forms.SearchForm() - pubsuggest_form = PublishingSuggestForm(initial={"books": author.name + ", \n"}) + form = PublishingSuggestForm(initial={"books": author.name + ", \n"}) return render_to_response('pdcounter/author_detail.html', locals(), context_instance=RequestContext(request)) diff --git a/apps/suggest/forms.py b/apps/suggest/forms.py index 14cec03f9..b7c614f31 100644 --- a/apps/suggest/forms.py +++ b/apps/suggest/forms.py @@ -9,14 +9,49 @@ from django.core.validators import email_re from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext - -from suggest.models import PublishingSuggestion +from suggest.models import PublishingSuggestion, Suggestion class SuggestForm(forms.Form): contact = forms.CharField(label=_('Contact'), max_length=120, required=False) description = forms.CharField(label=_('Description'), widget=forms.Textarea, required=True) + def save(self, request): + contact = self.cleaned_data['contact'] + description = self.cleaned_data['description'] + + suggestion = Suggestion(contact=contact, + description=description, ip=request.META['REMOTE_ADDR']) + if request.user.is_authenticated(): + suggestion.user = request.user + suggestion.save() + + mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\ +Zgłoszono nową sugestię w serwisie WolneLektury.pl. +http://%(site)s%(url)s + +Użytkownik: %(user)s +Kontakt: %(contact)s + +%(description)s''' % { + 'site': Site.objects.get_current().domain, + 'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]), + 'user': str(request.user) if request.user.is_authenticated() else '', + 'contact': contact, + 'description': description, + }, fail_silently=True) + + if email_re.match(contact): + send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'), + _(u"""\ +Thank you for your comment on WolneLektury.pl. +The suggestion has been referred to the project coordinator.""") + +u""" + +-- +""" + _(u'''Message sent automatically. Please do not reply.'''), + 'no-reply@wolnelektury.pl', [contact], fail_silently=True) + class PublishingSuggestForm(forms.Form): contact = forms.CharField(label=_('Contact'), max_length=120, required=False) diff --git a/apps/suggest/templates/publishing_suggest.html b/apps/suggest/templates/publishing_suggest.html index a6ed283d5..3e710008c 100755 --- a/apps/suggest/templates/publishing_suggest.html +++ b/apps/suggest/templates/publishing_suggest.html @@ -1,14 +1,16 @@ {% load i18n %} -

{% trans "Didn't find a book? Make a suggestion." %}

+

{% trans "Didn't find a book? Make a suggestion." %}

+
+{% csrf_token %}
    -
  1. {{ pubsuggest_form.contact.errors }} {{ pubsuggest_form.contact }}
  2. +
  3. {{ form.contact.errors }} {{ form.contact }}
  4. {% trans "I'd like to find in WolneLektury.pl these…" %}
  5. -
  6. {{ pubsuggest_form.books.errors }} {{ pubsuggest_form.books }}
  7. +
  8. {{ form.books.errors }} {{ form.books }}
  9. -
  10. {{ pubsuggest_form.audiobooks.errors }} {{ pubsuggest_form.audiobooks }}
  11. +
  12. {{ form.audiobooks.errors }} {{ form.audiobooks }}
diff --git a/apps/suggest/templates/publishing_suggest_full.html b/apps/suggest/templates/publishing_suggest_full.html deleted file mode 100755 index c5d8c2833..000000000 --- a/apps/suggest/templates/publishing_suggest_full.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block title %}Sugestia do planu wydawniczego w WolneLektury.pl{% endblock %} - -{% block metadescription %}Sugestia do planu wydawniczego.{% endblock %} - -{% block bodyid %}suggest-publishing{% endblock %} - -{% block body %} - -

{{ form.q }} {% trans "or" %} {% trans "return to main page" %}

-
- -
-
- -
- {% include "publishing_suggest.html" %} - {% if response_data.message %} -

{{ response_data.message }}

- {% endif %} -
-{% endblock %} diff --git a/apps/suggest/templates/suggest.html b/apps/suggest/templates/suggest.html deleted file mode 100644 index c7fdd8128..000000000 --- a/apps/suggest/templates/suggest.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load i18n %} -

{% trans "Report a bug or suggestion" %}

-
-
    -
  1. {{ form.contact }}
  2. -
  3. {{ form.description }}
  4. -
  5. -
-
\ No newline at end of file diff --git a/apps/suggest/urls.py b/apps/suggest/urls.py index b769e491b..ae4ac1573 100644 --- a/apps/suggest/urls.py +++ b/apps/suggest/urls.py @@ -3,21 +3,10 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.conf.urls.defaults import * -from django.views.generic.simple import direct_to_template -from suggest.forms import SuggestForm, PublishingSuggestForm -from suggest.views import PublishingSuggestionFormView +from suggest import views urlpatterns = patterns('', - url(r'^$', 'django.views.generic.simple.direct_to_template', - {'template': 'suggest.html', 'extra_context': {'form': SuggestForm }}, name='suggest'), - url(r'^wyslij/$', 'suggest.views.report', name='report'), - - #url(r'^plan/$', 'suggest.views.publishing', name='suggest_publishing'), - url(r'^plan/$', PublishingSuggestionFormView(), name='suggest_publishing'), - #url(r'^plan_block/$', 'django.views.generic.simple.direct_to_template', - # {'template': 'publishing_suggest.html', - # 'extra_context': {'pubsuggest_form': PublishingSuggestForm }}, - # name='suggest_publishing'), - #url(r'^plan/wyslij/$', 'suggest.views.publishing_commit', name='suggest_publishing_commit'), + url(r'^$', views.SuggestionFormView(), name='suggest'), + url(r'^plan/$', views.PublishingSuggestionFormView(), name='suggest_publishing'), ) diff --git a/apps/suggest/views.py b/apps/suggest/views.py index 24ee12ce4..15b65f24d 100644 --- a/apps/suggest/views.py +++ b/apps/suggest/views.py @@ -2,107 +2,22 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.core.mail import send_mail, mail_managers -from django.core.urlresolvers import reverse -from django.core.validators import email_re -from django.http import HttpResponse, HttpResponseRedirect -from django.utils.translation import ugettext as _ -from django.views.decorators import cache -from django.views.decorators.http import require_POST -from django.contrib.sites.models import Site -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.utils.translation import ugettext_lazy as _ -from catalogue.forms import SearchForm +from ajaxable.utils import AjaxableFormView from suggest import forms from suggest.models import Suggestion, PublishingSuggestion -# FIXME - shouldn't be in catalogue -from catalogue.views import LazyEncoder - - -class AjaxableFormView(object): - formClass = None - template = None - ajax_template = None - formname = None - - def __call__(self, request): - """ - A view displaying a form, or JSON if `ajax' GET param is set. - """ - ajax = request.GET.get('ajax', False) - if request.method == "POST": - form = self.formClass(request.POST) - if form.is_valid(): - form.save(request) - response_data = {'success': True, 'message': _('Report was sent successfully.')} - else: - response_data = {'success': False, 'errors': form.errors} - if ajax: - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - else: - form = self.formClass() - response_data = None - - template = self.ajax_template if ajax else self.template - return render_to_response(template, { - self.formname: form, - "form": SearchForm(), - "response_data": response_data, - }, - context_instance=RequestContext(request)) - - class PublishingSuggestionFormView(AjaxableFormView): - formClass = forms.PublishingSuggestForm - ajax_template = "publishing_suggest.html" - template = "publishing_suggest_full.html" - formname = "pubsuggest_form" - - -@require_POST -@cache.never_cache -def report(request): - suggest_form = forms.SuggestForm(request.POST) - if suggest_form.is_valid(): - contact = suggest_form.cleaned_data['contact'] - description = suggest_form.cleaned_data['description'] - - suggestion = Suggestion(contact=contact, - description=description, ip=request.META['REMOTE_ADDR']) - if request.user.is_authenticated(): - suggestion.user = request.user - suggestion.save() - - mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\ -Zgłoszono nową sugestię w serwisie WolneLektury.pl. -http://%(site)s%(url)s - -Użytkownik: %(user)s -Kontakt: %(contact)s - -%(description)s''' % { - 'site': Site.objects.get_current().domain, - 'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]), - 'user': str(request.user) if request.user.is_authenticated() else '', - 'contact': contact, - 'description': description, - }, fail_silently=True) - - if email_re.match(contact): - send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'), - _(u"""\ -Thank you for your comment on WolneLektury.pl. -The suggestion has been referred to the project coordinator.""") + -u""" + form_class = forms.PublishingSuggestForm + title = _('Report a bug or suggestion') + template = "publishing_suggest.html" + success_message = _('Report was sent successfully.') --- -""" + _(u'''Message sent automatically. Please do not reply.'''), - 'no-reply@wolnelektury.pl', [contact], fail_silently=True) - response_data = {'success': True, 'message': _('Report was sent successfully.')} - else: - response_data = {'success': False, 'errors': suggest_form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) +class SuggestionFormView(AjaxableFormView): + form_class = forms.SuggestForm + title = _('Report a bug or suggestion') + submit = _('Send report') + success_message = _('Report was sent successfully.') diff --git a/wolnelektury/settings.py b/wolnelektury/settings.py index 91254ab74..40b45be04 100644 --- a/wolnelektury/settings.py +++ b/wolnelektury/settings.py @@ -110,7 +110,7 @@ TEMPLATE_DIRS = [ path.join(PROJECT_DIR, 'templates'), ] -LOGIN_URL = '/uzytkownicy/login/' +LOGIN_URL = '/uzytkownicy/zaloguj/' LOGIN_REDIRECT_URL = '/' @@ -137,6 +137,7 @@ INSTALLED_APPS = [ 'modeltranslation', # our + 'ajaxable', 'api', 'catalogue', 'chunks', @@ -161,11 +162,14 @@ CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True # CSS and JavaScript file groups COMPRESS_CSS = { 'all': { - #'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/jquery.countdown.css', 'css/master.plain.css', 'css/sponsors.css', 'css/facelist_2-0.css',), + #'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/master.plain.css', 'css/facelist_2-0.css',), 'source_filenames': [ + 'css/jquery.countdown.css', + 'css/base.css', 'css/header.css', 'css/main_page.css', + 'css/dialogs.css', 'css/book_box.css', 'css/sponsors.css', ], @@ -185,15 +189,20 @@ COMPRESS_JS = { 'base': { 'source_filenames': ( 'js/jquery.cycle.min.js', - + 'js/jquery.jqmodal.js', + 'js/jquery.form.js', + 'js/jquery.countdown.js', 'js/jquery.countdown-pl.js', + 'js/jquery.countdown-de.js', 'js/jquery.countdown-uk.js', + 'js/jquery.countdown-es.js', 'js/jquery.countdown-lt.js', + 'js/jquery.countdown-ru.js', 'js/jquery.countdown-fr.js', + + 'js/locale.js', + 'js/dialogs.js', 'js/sponsors.js', - - #~ 'js/jquery.autocomplete.js', 'js/jquery.form.js', - #~ 'js/jquery.countdown.js', 'js/jquery.countdown-pl.js', - #~ 'js/jquery.countdown-de.js', 'js/jquery.countdown-uk.js', - #~ 'js/jquery.countdown-es.js', 'js/jquery.countdown-lt.js', - #~ 'js/jquery.countdown-ru.js', 'js/jquery.countdown-fr.js', - #~ 'js/jquery.jqmodal.js', 'js/jquery.labelify.js', 'js/catalogue.js', + 'js/pdcounter.js', + + #~ 'js/jquery.autocomplete.js', + #~ 'js/jquery.labelify.js', 'js/catalogue.js', ), 'output_filename': 'js/base?.min.js', }, diff --git a/wolnelektury/static/css/dialogs.css b/wolnelektury/static/css/dialogs.css new file mode 100755 index 000000000..dc76e6cd8 --- /dev/null +++ b/wolnelektury/static/css/dialogs.css @@ -0,0 +1,87 @@ +.cuteform ol, .cuteform ul { + padding: 0; + margin: 0; + list-style: none; +} + +.cuteform ol li, .cuteform ul li { + margin-top: 0.7em; +} + +.cuteform label { + display: block; +} + +.cuteform span.help-text { + display: block; + font-size: 0.8em; + color: #999; +} + +.cuteform .error { + color: #BF3024; + display: block; +} + + +.jqmOverlay { background-color: #000; } + + +.dialog-window { + position: absolute; + display: none; + background-color: transparent; + margin-top: -0.5em; + margin-left: 1em; +} + +.dialog-window div.header { + width: 4em; + background-color: #FFF; + border-right: 0.3em solid #DDD; + padding: 0.5em 1em 0.5em 1em; + right: 0; + left: auto; + float: right; + text-align: center; +} + + +.dialog-window div.target { + background-color: #FFF; + color: black; + border-right: 0.3em solid #DDD; + border-bottom: 0.3em solid #DDD; + padding: 1em; + clear: both; +} + +.dialog-window h1 { + font-size: 1.2em; +} + + +#login-window { + width: 24em; +} +#register-window { + width: 24em; +} + +#suggest-window { + width: 24em; +} + +#suggest-window textarea { + width: 19em; + height: 6em; +} + +#suggest-publishing-window { + width: 26em; +} + +#suggest-publishing-window textarea { + width: 21em; + height: 3em; +} diff --git a/wolnelektury/static/css/main_page.css b/wolnelektury/static/css/main_page.css index 28a11480b..94401cb51 100755 --- a/wolnelektury/static/css/main_page.css +++ b/wolnelektury/static/css/main_page.css @@ -1,6 +1,6 @@ #big-cite { background-color: white; - padding: 8em; + padding: 4em 12em; margin: 0; } diff --git a/wolnelektury/static/js/dialogs.js b/wolnelektury/static/js/dialogs.js new file mode 100755 index 000000000..0793a7ffd --- /dev/null +++ b/wolnelektury/static/js/dialogs.js @@ -0,0 +1,56 @@ +(function($) { + $(function() { + + // create containers for all ajaxable form links + $('.ajaxable').each(function() { + var $window = $("#ajaxable-window").clone(); + $window.attr("id", this.id + "-window"); + $('body').append($window); + + var trigger = '#' + this.id; + + var href = $(this).attr('href'); + if (href.search('\\?') != -1) + href += '&ajax=1'; + else href += '?ajax=1'; + + $window.jqm({ + ajax: href, + ajaxText: '

* ' + gettext("Loading") + '

', + target: $('.target', $window)[0], + overlay: 60, + trigger: trigger, + onShow: function(hash) { + var offset = $(hash.t).offset(); + hash.w.css({position: 'absolute', left: offset.left - hash.w.width() + $(hash.t).width(), top: offset.top}); + $('.header', hash.w).css({width: $(hash.t).width()}); + hash.w.show(); + }, + onLoad: function(hash) { + $('form', hash.w).each(function() {this.action += '?ajax=1';}); + $('form', hash.w).ajaxForm({ + dataType: 'json', + target: $('.target', $window), + success: function(response) { + if (response.success) { + $('.target', $window).text(response.message); + setTimeout(function() { $window.jqmHide() }, 1000) + } + else { + $('.error', $window).remove(); + $.each(response.errors, function(id, errors) { + $('#id_' + id, $window).before('' + errors[0] + ''); + }); + $('input[type=submit]', $window).removeAttr('disabled'); + return false; + } + } + }); + } + }); + }); + + + }); +})(jQuery) + diff --git a/wolnelektury/static/js/jquery.jqmodal.js b/wolnelektury/static/js/jquery.jqmodal.js index 248bb1931..3aac816e7 100644 --- a/wolnelektury/static/js/jquery.jqmodal.js +++ b/wolnelektury/static/js/jquery.jqmodal.js @@ -1,214 +1,69 @@ /* * jqModal - Minimalist Modaling with jQuery + * (http://dev.iceburg.net/jquery/jqModal/) * - * Copyright (c) 2007 Brice Burgess , http://www.iceburg.net - * Licensed under the MIT License: - * http://www.opensource.org/licenses/mit-license.php - * - * $Version: 2007.??.?? +r12 beta - * Requires: jQuery 1.1.3+ + * Copyright (c) 2007,2008 Brice Burgess + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * $Version: 03/01/2009 +r14 */ (function($) { -/** - * Initialize a set of elements as "modals". Modals typically are popup dialogs, - * notices, modal windows, and image containers. An expando ("_jqm") containing - * the UUID or "serial" of the modal is added to each element. This expando helps - * reference the modal's settings in the jqModal Hash Object (jQuery.jqm.hash) - * - * Accepts a parameter object with the following modal settings; - * - * (Integer) zIndex - Desired z-Index of the modal. This setting does not override (has no effect on) preexisting z-Index styling (set via CSS or inline style). - * (Integer) overlay - [0-100] Translucency percentage (opacity) of the body covering overlay. Set to 0 for NO overlay, and up to 100 for a 100% opaque overlay. - * (String) overlayClass - This class is applied to the body covering overlay. Allows CSS control of the overlay look (tint, background image, etc.). - * (String) closeClass - A close trigger is added to all elements matching this class within the modal. - * (Mixed) trigger - An open trigger is added to all matching elements within the DOM. Trigger can be a selector String, a jQuery collection of elements, a DOM element, or a False boolean. - * (Mixed) ajax - If not false; The URL (string) to load content from via an AJAX request. - * If ajax begins with a "@", the URL is extracted from the requested attribute of the triggering element. - * (Mixed) target - If not false; The element within the modal to load the ajax response (content) into. Allows retention of modal design (e.g. framing and close elements are not overwritten by the AJAX response). - * Target may be a selector string, jQuery collection of elements, or a DOM element -- but MUST exist within (as a child of) the modal. - * (Boolean) modal - If true, user interactivity will be locked to the modal window until closed. - * (Boolean) toTop - If true, modal will be posistioned as a first child of the BODY element when opened, and its DOM posistion restored when closed. This aids in overcoming z-Index stacking order/containment issues where overlay covers whole page *including* modal. - * (Mixed) onShow - User defined callback function fired when modal opened. - * (Mixed) onHide - User defined callback function fired when modal closed. - * (Mixed) onLoad - User defined callback function fired when ajax content loads. - * - * @name jqm - * @param Map options User defined settings for the modal(s). - * @type jQuery - * @cat Plugins/jqModal - */ -$.fn.jqm=function(p){ -var o = { -zIndex: 3000, +$.fn.jqm=function(o){ +var p={ overlay: 50, overlayClass: 'jqmOverlay', closeClass: 'jqmClose', trigger: '.jqModal', -ajax: false, -target: false, -modal: false, -toTop: false, -onShow: false, -onHide: false, -onLoad: false +ajax: F, +ajaxText: '', +target: F, +modal: F, +toTop: F, +onShow: F, +onHide: F, +onLoad: F }; - -// For each element (aka "modal") $.jqm() has been called on; -// IF the _jqm expando exists, return (do nothing) -// ELSE increment serials and add _jqm expando to element ("serialization") -// *AND*... -return this.each(function(){if(this._jqm)return;s++;this._jqm=s; - -// ... Add this element's serial to the jqModal Hash Object -// Hash is globally accessible via jQuery.jqm.hash. It consists of; -// c: {obj} config/options -// a: {bool} active state (true: active/visible, false: inactive/hidden) -// w: {JQ DOM Element} The modal element (window/dialog/notice/etc. container) -// s: {int} The serial number of this modal (same as "H[s].w[0]._jqm") -// t: {DOM Element} The triggering element -// *AND* ... -H[s]={c:$.extend(o,p),a:false,w:$(this).addClass('jqmID'+s),s:s}; - -// ... Attach events to trigger showing of this modal -o.trigger&&$(this).jqmAddTrigger(o.trigger); +return this.each(function(){if(this._jqm)return H[this._jqm].c=$.extend({},H[this._jqm].c,o);s++;this._jqm=s; +H[s]={c:$.extend(p,$.jqm.params,o),a:F,w:$(this).addClass('jqmID'+s),s:s}; +if(p.trigger)$(this).jqmAddTrigger(p.trigger); });}; -// Adds behavior to triggering elements via the hide-show (HS) function. -// -$.fn.jqmAddClose=function(e){return HS(this,e,'jqmHide');}; -$.fn.jqmAddTrigger=function(e){return HS(this,e,'jqmShow');}; - -// Hide/Show a modal -- first check if it is already shown or hidden via the toggle state (H[{modal serial}].a) -$.fn.jqmShow=function(t){return this.each(function(){!H[this._jqm].a&&$.jqm.open(this._jqm,t)});}; -$.fn.jqmHide=function(t){return this.each(function(){H[this._jqm].a&&$.jqm.close(this._jqm,t)});}; +$.fn.jqmAddClose=function(e){return hs(this,e,'jqmHide');}; +$.fn.jqmAddTrigger=function(e){return hs(this,e,'jqmShow');}; +$.fn.jqmShow=function(t){return this.each(function(){t=t||window.event;$.jqm.open(this._jqm,t);});}; +$.fn.jqmHide=function(t){return this.each(function(){t=t||window.event;$.jqm.close(this._jqm,t)});}; $.jqm = { hash:{}, - -// Function is executed by $.jqmShow to show a modal -// s: {INT} serial of modal -// t: {DOM Element} the triggering element - -// set local shortcuts -// h: {obj} this Modal's "hash" -// c: {obj} (h.c) config/options -// cc: {STR} closing class ('.'+h.c.closeClass) -// z: {INT} z-Index of Modal. If the Modal (h.w) has the z-index style set it will use this value before defaulting to the one passed in the config (h.c.zIndex) -// o: The overlay object -// mark this modal as active (h.a === true) -// set the triggering object (h.t) and the modal's z-Index. -open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=/^\d+$/.test(h.w.css('z-index'))&&h.w.css('z-index')||c.zIndex,o=$('
').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});h.t=t;h.a=true;h.w.css('z-index',z); - - // IF the modal argument was passed as true; - // Bind the Keep Focus Function if no other Modals are open (!A[0]), - // Add this modal to the opened modals stack (A) for nested modal support, - // and Mark overlay to show wait cursor when mouse hovers over it. - if(c.modal) {!A[0]&&F('bind');A.push(s);o.css('cursor','wait');} - - // ELSE IF an overlay was requested (translucency set greater than 0); - // Attach a Close event to overlay to hide modal when overlay is clicked. +open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=(parseInt(h.w.css('z-index'))),z=(z>0)?z:3000,o=$('
').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});if(h.a)return F;h.t=t;h.a=true;h.w.css('z-index',z); + if(c.modal) {if(!A[0])L('bind');A.push(s);} else if(c.overlay > 0)h.w.jqmAddClose(o); + else o=F; - // ELSE disable the overlay - else o=false; + h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):F; + if(ie6){$('html,body').css({height:'100%',width:'100%'});if(o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");}} - // Add the Overlay to BODY if not disabled. - h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):false; - - // IF IE6; - // Set the Overlay to 100% height/width, and fix-position it via JS workaround - if(ie6&&$('html,body').css({height:'100%',width:'100%'})&&o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");} - - // IF the modal's content is to be loaded via ajax; - // determine the target element {JQ} to recieve content (r), - // determine the URL {STR} to load content from (u) if(c.ajax) {var r=c.target||h.w,u=c.ajax,r=(typeof r == 'string')?$(r,h.w):$(r),u=(u.substr(0,1) == '@')?$(t).attr(u.substring(1)):u; + r.html(c.ajaxText).load(u,function(){if(c.onLoad)c.onLoad.call(this,h);if(cc)h.w.jqmAddClose($(cc,h.w));e(h);});} + else if(cc)h.w.jqmAddClose($(cc,h.w)); - // Load the Content (and once loaded); - // Fire the onLoad callback (if exists), - // Attach closing events to elements inside the modal that match the closingClass, - // and Execute the jqModal default Open Callback - r.load(u,function(){c.onLoad&&c.onLoad.call(this,h);cc&&h.w.jqmAddClose($(cc,h.w));O(h);});} - - // ELSE the modal content is NOT to be loaded via ajax; - // Attach closing events to elements inside the modal that match the closingClass - else cc&&h.w.jqmAddClose($(cc,h.w)); - - // IF toTop was passed and an overlay exists; - // Remember the DOM posistion of the modal by inserting a tagged (matching serial) before the modal - // Move the Modal from its current position to a first child of the body tag (after the overlay) - c.toTop&&h.o&&h.w.before('').insertAfter(h.o); - - // Execute user defined onShow callback, or else show (make visible) the modal. - // Execute the jqModal default Open Callback. - // Return false to prevent trigger click from being followed. - (c.onShow)?c.onShow(h):h.w.show();O(h);return false; - + if(c.toTop&&h.o)h.w.before('').insertAfter(h.o); + (c.onShow)?c.onShow(h):h.w.show();e(h);return F; }, - -// Function is executed by $.jqmHide to hide a modal - // mark this modal as inactive (h.a === false) -close:function(s){var h=H[s];h.a=false; - // If modal, remove from modal stack. - // If no modals in modal stack, unbind the Keep Focus Function - if(h.c.modal){A.pop();!A[0]&&F('unbind');} - - // IF toTop was passed and an overlay exists; - // Move modal back to its previous ("remembered") position. - h.c.toTop&&h.o&&$('#jqmP'+h.w[0]._jqm).after(h.w).remove(); - - // Execute user defined onHide callback, or else hide (make invisible) the modal and remove the overlay. - if(h.c.onHide)h.c.onHide(h);else{h.w.hide()&&h.o&&h.o.remove()}return false; -}}; - -// set jqModal scope shortcuts; -// s: {INT} serials placeholder -// H: {HASH} shortcut to jqModal Hash Object -// A: {ARRAY} Array of active/visible modals -// ie6: {bool} True if client browser is Internet Explorer 6 -// i: {JQ, DOM Element} iframe placeholder used to prevent active-x bleedthrough in IE6 -// NOTE: It is important to include the iframe styling (iframe.jqm) in your CSS! -// *AND* ... -var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),i=$('').css({opacity:0}), - -// O: The jqModal default Open Callback; -// IF ie6; Add the iframe to the overlay (if overlay exists) OR to the modal (if an iframe doesn't already exist from a previous opening) -// Execute the Modal Focus Function -O=function(h){if(ie6)h.o&&h.o.html('

').prepend(i)||(!$('iframe.jqm',h.w)[0]&&h.w.prepend(i)); f(h);}, - -// f: The Modal Focus Function; -// Attempt to focus the first visible input within the modal -f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(e){}}, - -// F: The Keep Focus Function; -// Binds or Unbinds (t) the Focus Examination Function to keypresses and clicks -F=function(t){$()[t]("keypress",x)[t]("keydown",x)[t]("mousedown",x);}, - -// x: The Focus Examination Function; -// Fetch the current modal's Hash as h (supports nested modals) -// Determine if the click/press falls within the modal. If not (r===true); -// call the Modal Focus Function and prevent click/press follow-through (return false [!true]) -// ELSE if so (r===false); follow event (return true [!false]) -x=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);r&&f(h);return !r;}, - -// hide-show function; assigns click events to trigger elements that -// hide, show, or hide AND show modals. - -// Expandos (jqmShow and/or jqmHide) are added to all trigger elements. -// These Expandos hold an array of modal serials {INT} to show or hide. - -// w: {DOM Element} The modal element (window/dialog/notice/etc. container) -// e: {DOM Elemet||jQ Selector String} The triggering element -// y: {String} Type (jqmHide||jqmShow) - -// s: {array} the serial number of passed modals, calculated below; -HS=function(w,e,y){var s=[];w.each(function(){s.push(this._jqm)}); - -// for each triggering element attach the jqmHide or jqmShow expando (y) -// or else expand the expando with the current serial array - $(e).each(function(){if(this[y])$.extend(this[y],s); - - // Assign a click event on the trigger element which examines the element's - // jqmHide/Show expandos and attempts to execute $.jqmHide/Show on matching modals - else{this[y]=s;$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return false;});}});return w;}; +close:function(s){var h=H[s];if(!h.a)return F;h.a=F; + if(A[0]){A.pop();if(!A[0])L('unbind');} + if(h.c.toTop&&h.o)$('#jqmP'+h.w[0]._jqm).after(h.w).remove(); + if(h.c.onHide)h.c.onHide(h);else{h.w.hide();if(h.o)h.o.remove();} return F; +}, +params:{}}; +var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),F=false, +i=$('').css({opacity:0}), +e=function(h){if(ie6)if(h.o)h.o.html('

').prepend(i);else if(!$('iframe.jqm',h.w)[0])h.w.prepend(i); f(h);}, +f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(_){}}, +L=function(t){$()[t]("keypress",m)[t]("keydown",m)[t]("mousedown",m);}, +m=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);if(r)f(h);return !r;}, +hs=function(w,t,c){return w.each(function(){var s=this._jqm;$(t).each(function() { + if(!this[c]){this[c]=[];$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return F;});}this[c].push(s);});});}; })(jQuery); \ No newline at end of file diff --git a/wolnelektury/static/js/locale.js b/wolnelektury/static/js/locale.js new file mode 100755 index 000000000..e765548ad --- /dev/null +++ b/wolnelektury/static/js/locale.js @@ -0,0 +1,29 @@ +var LOCALE_TEXTS = { + "pl": { + "Loading": "Ładowanie" + }, + "de": { + "Loading": "Herunterladen" + }, + "fr": { + "Loading": "Chargement" + }, + "en": { + "Loading": "Loading" + }, + "ru": { + "Loading": "Загрузка" + }, + "es": { + "Loading": "Cargando" + }, + "lt":{ + "Loading": "Krovimas" + }, + "uk":{ + "Loading": "Завантажується" + } +} +function gettext(text) { + return LOCALE_TEXTS[LANGUAGE_CODE][text]; +} \ No newline at end of file diff --git a/wolnelektury/static/js/pdcounter.js b/wolnelektury/static/js/pdcounter.js new file mode 100755 index 000000000..00c074280 --- /dev/null +++ b/wolnelektury/static/js/pdcounter.js @@ -0,0 +1,36 @@ +(function($) { + $(function() { + + + $('#countdown').each(function() { + var $this = $(this); + + var serverTime = function() { + var time = null; + $.ajax({url: '/zegar/', + async: false, dataType: 'text', + success: function(text) { + time = new Date(text); + }, error: function(http, message, exc) { + time = new Date(); + }}); + return time; + } + + if (LANGUAGE_CODE != 'en') { + $.countdown.setDefaults($.countdown.regional[LANGUAGE_CODE]); + } + else { + $.countdown.setDefaults($.countdown.regional['']); + } + + var d = new Date($this.attr('data-year'), 0, 1); + function re() {location.reload()}; + $this.countdown({until: d, format: 'ydHMS', serverSync: serverTime, + onExpiry: re, alwaysExpire: true}); + + }); + + + }); +})(jQuery) \ No newline at end of file diff --git a/wolnelektury/templates/auth/login.html b/wolnelektury/templates/auth/login.html deleted file mode 100644 index a168ff7f8..000000000 --- a/wolnelektury/templates/auth/login.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block title %}{% trans "Sign in" %} / {% trans "Register on"%} WolneLektury.pl{% endblock %} - -{% block body %} -

{% trans "Sign in" %} / {% trans "Register"%}

-
-

  • {{ search_form.q }}
  • {% trans "or" %} {% trans "return to main page" %}

    -
    -
    -

    {% trans "Sign in" %}

    -
      - {{ form.as_ul }} -
    1. -
    -

    -
    - -
    -

    {% trans "Register" %}

    - -

    -
    -{% endblock %} diff --git a/wolnelektury/templates/base.html b/wolnelektury/templates/base.html index b7f699316..b53bab7b4 100644 --- a/wolnelektury/templates/base.html +++ b/wolnelektury/templates/base.html @@ -41,7 +41,13 @@ {% endif %} | {% trans "Logout" %} {% else %} - + + {% trans "Sign in" %} + / + + {% trans "Register" %} {% endif %}

    @@ -140,34 +146,23 @@ {# end main-content #} - + {% endblock bodycontent %} - + {% compressed_js "base" %}