From a3b7259f98caca8e02b8e95f973207b4f66cfe76 Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Thu, 20 Mar 2014 15:58:02 +0200 Subject: [PATCH 01/84] Add a basic skeleton for Wagtail form builder --- wagtail/wagtailforms/__init__.py | 0 wagtail/wagtailforms/models.py | 72 ++++++++++++++++++++++++++++++++ wagtail/wagtailforms/tests.py | 19 +++++++++ 3 files changed, 91 insertions(+) create mode 100644 wagtail/wagtailforms/__init__.py create mode 100644 wagtail/wagtailforms/models.py create mode 100644 wagtail/wagtailforms/tests.py diff --git a/wagtail/wagtailforms/__init__.py b/wagtail/wagtailforms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py new file mode 100644 index 000000000..e4be5a379 --- /dev/null +++ b/wagtail/wagtailforms/models.py @@ -0,0 +1,72 @@ +from django.db import models +from django.shortcuts import render +from django.utils.translation import ugettext_lazy as _ + +from wagtail.wagtailcore.models import Page, Orderable +from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel + +from modelcluster.fields import ParentalKey + +FORM_FIELD_CHOICES = ( + ('SINGLELINE', _('Single line text')), + ('MULTILINE', _('Multi-line text')), + ('EMAIL', _('Email')), + ('NUMBER', _('Number')), + ('URL', _('URL')), + ('CHECKBOX', _('Checkbox')), + ('CHECKBOXES', _('Checkboxes')), + ('DROPDOWN', _('Drop down')), + ('RADIO', _('Radio buttons')), + ('DATE', _('Date')), + ('DATETIME', _('Date/time')), +) + +class AbstractFormFields(models.Model): + #page = ParentalKey('wagtailforms.AbstractForm', related_name='form_fields') + label = models.CharField(max_length=255) + field_type = models.CharField(max_length=16, choices = FORM_FIELD_CHOICES) + required = models.BooleanField( default=True) + choices = models.CharField(max_length=512, blank=True, help_text='Comma seperated list of choices') + default_value = models.CharField(max_length=255, blank=True) + help_text = models.CharField(max_length=255, blank=True) + + panels = [ + FieldPanel('label'), + FieldPanel('field_type'), + FieldPanel('required'), + FieldPanel('choices'), + FieldPanel('default_value'), + FieldPanel('help_text'), + ] + + class Meta: + abstract = True + + + +class AbstractForm(Page): + is_abstract = True #Don't display me in "Add" + + class Meta: + abstract = True + + def serve(self, request): + # Get fields + form_fields = self.form_fields + + return render(request, self.template, { + 'self': self, + }) + + +class ConcreteFormFields(Orderable, AbstractFormFields): + page = ParentalKey('wagtailforms.ConcreteForm', related_name='form_fields') + + +class ConcreteForm(AbstractForm): + pass + +ConcreteForm.content_panels = [ + FieldPanel('title', classname="full title"), + InlinePanel(ConcreteForm, 'form_fields', label="Form Fields"), +] diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py new file mode 100644 index 000000000..95cbacbf0 --- /dev/null +++ b/wagtail/wagtailforms/tests.py @@ -0,0 +1,19 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +import unittest + +from django.test import TestCase + + +@unittest.skip("Need real tests") +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) From 92563ff53532eb9480139fdc95186085246e22ba Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Thu, 20 Mar 2014 23:04:44 +0200 Subject: [PATCH 02/84] Add form builder and the basic form workflow also add the implementation of all form field types. The form will save the submitted data to the database (using the FormSubmission) model. --- wagtail/wagtailforms/forms.py | 67 ++++++++++++++++++++++ wagtail/wagtailforms/models.py | 102 +++++++++++++++++++++++++-------- 2 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 wagtail/wagtailforms/forms.py diff --git a/wagtail/wagtailforms/forms.py b/wagtail/wagtailforms/forms.py new file mode 100644 index 000000000..b444895c5 --- /dev/null +++ b/wagtail/wagtailforms/forms.py @@ -0,0 +1,67 @@ +import django.forms +from django.utils.datastructures import SortedDict +from django.utils.text import slugify +from unidecode import unidecode + +class FormBuilder(): + formfields = SortedDict() + def __init__(self, fields): + for field in fields: + options = self.get_options(field) + f = getattr(self, "create_"+field.field_type+"_field" )(field, options) + # unidecode will return an ascii string while slugify wants a unicode string + # on the other hand, slugify returns a safe-string which will be converted + # to a normal str + field_name = str(slugify(unicode(unidecode(field.label)))) + self.formfields[field_name] = f + + def get_options(self, field): + options = {} + options['label'] = field.label + options['help_text'] = field.help_text + options['required'] = field.required + options['initial'] = field.default_value + return options + + def create_singleline_field(self, field, options): + # TODO: This is a default value - it may need to be changed + options['max_length'] = 255 + return django.forms.CharField(**options) + + def create_multiline_field(self, field, options): + return django.forms.CharField(widget=django.forms.Textarea, **options) + + def create_date_field(self, field, options): + return django.forms.DateField(**options) + + def create_datetime_field(self, field, options): + return django.forms.DateTimeField(**options) + + def create_email_field(self, field, options): + return django.forms.EmailField(**options) + + def create_url_field(self, field, options): + return django.forms.URLField(**options) + + def create_number_field(self, field, options): + return django.forms.DecimalField(**options) + + def create_dropdown_field(self, field, options): + options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + return django.forms.ChoiceField(**options) + + def create_radio_field(self, field, options): + options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + return django.forms.ChoiceField(widget=django.forms.RadioSelect, **options) + + def create_checkboxes_field(self, field, options): + options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + options['initial'] = field.default_value.split(',') + return django.forms.MultipleChoiceField(widget=django.forms.CheckboxSelectMultiple, **options) + + def create_checkbox_field(self, field, options): + return django.forms.BooleanField(**options) + + def get_form_class(self): + return type('WagtailForm', (django.forms.Form,), self.formfields ) + diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index e4be5a379..ff758497c 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,32 +1,53 @@ +from django.conf import settings from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ +import json +import re + from wagtail.wagtailcore.models import Page, Orderable from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel from modelcluster.fields import ParentalKey +from .forms import FormBuilder + FORM_FIELD_CHOICES = ( - ('SINGLELINE', _('Single line text')), - ('MULTILINE', _('Multi-line text')), - ('EMAIL', _('Email')), - ('NUMBER', _('Number')), - ('URL', _('URL')), - ('CHECKBOX', _('Checkbox')), - ('CHECKBOXES', _('Checkboxes')), - ('DROPDOWN', _('Drop down')), - ('RADIO', _('Radio buttons')), - ('DATE', _('Date')), - ('DATETIME', _('Date/time')), + ('singleline', _('Single line text')), + ('multiline', _('Multi-line text')), + ('email', _('Email')), + ('number', _('Number')), + ('url', _('URL')), + ('checkbox', _('Checkbox')), + ('checkboxes', _('Checkboxes')), + ('dropdown', _('Drop down')), + ('radio', _('Radio buttons')), + ('date', _('Date')), + ('datetime', _('Date/time')), ) + +HTML_EXTENSION_RE = re.compile(r"(.*)\.html") + + +class FormSubmission(models.Model): + """Data for a Form submission.""" + form_data = models.TextField() + form_page = models.ForeignKey('wagtailcore.Page',related_name='+') + submit_time = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True) + + def __unicode__(self): + return self.form_data + class AbstractFormFields(models.Model): - #page = ParentalKey('wagtailforms.AbstractForm', related_name='form_fields') - label = models.CharField(max_length=255) + """Database Fields required for building a Django Form field.""" + + label = models.CharField(max_length=255, help_text=_('The label of the form field') ) field_type = models.CharField(max_length=16, choices = FORM_FIELD_CHOICES) - required = models.BooleanField( default=True) - choices = models.CharField(max_length=512, blank=True, help_text='Comma seperated list of choices') + required = models.BooleanField(default=True) + choices = models.CharField(max_length=512, blank=True, help_text=_('Comma seperated list of choices')) default_value = models.CharField(max_length=255, blank=True) help_text = models.CharField(max_length=255, blank=True) @@ -42,31 +63,64 @@ class AbstractFormFields(models.Model): class Meta: abstract = True - - + class AbstractForm(Page): - is_abstract = True #Don't display me in "Add" + """A Form Page. Pages with form should inhert from it""" + form_builder = FormBuilder + is_abstract = True # Don't display me in "Add" + + def __init__(self, *args, **kwargs): + super(Page, self).__init__(*args, **kwargs) + if not hasattr(self, 'landing_page_template'): + template_wo_ext = re.match(HTML_EXTENSION_RE, self.template).group(1) + self.landing_page_template = template_wo_ext + '_landing.html' class Meta: abstract = True - + def serve(self, request): - # Get fields - form_fields = self.form_fields + fb = self.form_builder(self.form_fields.all() ) + form_class = fb.get_form_class() + + if request.method == 'POST': + self.form = form_class(request.POST) + + if self.form.is_valid(): + # remove csrf_token from form.data + form_data = dict( + i for i in self.form.data.items() + if i[0] != 'csrfmiddlewaretoken' + ) + FormSubmission.objects.create( + form_data = json.dumps(form_data), + form_page = self.page_ptr, + user = request.user, + ) + # TODO: Do other things like sending email + # render the landing_page + # TODO: It is much better to redirect to it + return render(request, self.landing_page_template, { + 'self': self, + }) + else: + self.form = form_class() return render(request, self.template, { 'self': self, + 'form': self.form, }) - + + +######## TEST class ConcreteFormFields(Orderable, AbstractFormFields): page = ParentalKey('wagtailforms.ConcreteForm', related_name='form_fields') - class ConcreteForm(AbstractForm): - pass + thank_you = models.CharField(max_length=255) ConcreteForm.content_panels = [ FieldPanel('title', classname="full title"), + FieldPanel('thank_you', classname="full"), InlinePanel(ConcreteForm, 'form_fields', label="Form Fields"), ] From b6aca526dac3b2e55c99cea0b1491e6b387314f2 Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Fri, 21 Mar 2014 23:44:44 +0200 Subject: [PATCH 03/84] Add generic send_email task in wagtailadmin.tasks This will be used from the send form data to email. It has been added to the wagtailadmin.tasks module to use celery if it has been defined and configured or just send the email if not (just like the send_notification task). --- wagtail/wagtailadmin/tasks.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wagtail/wagtailadmin/tasks.py b/wagtail/wagtailadmin/tasks.py index 3d6f3e8f1..d63b17f79 100644 --- a/wagtail/wagtailadmin/tasks.py +++ b/wagtail/wagtailadmin/tasks.py @@ -85,3 +85,16 @@ def send_notification(page_revision_id, notification, excluded_user_id): # Send email send_mail(email_subject, email_content, from_email, email_addresses) + + +@task +def send_email_task(email_subject, email_content, from_email, email_addresses): + if not from_email: + if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'): + from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL + elif hasattr(settings, 'DEFAULT_FROM_EMAIL'): + from_email = settings.DEFAULT_FROM_EMAIL + else: + from_email = 'webmaster@localhost' + + send_mail(email_subject, email_content, from_email, email_addresses) From 9557bc66050ac70a884584596ca0bcb231180ead Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Fri, 21 Mar 2014 23:47:48 +0200 Subject: [PATCH 04/84] Add first version of send form data to email This has been added in a generic way to allow defining form processing backends. Sending the form data to an email is one of these backends - others may include for instance call a web service with the form data (for instance in a customer complains form we'd need to start a customer complains workflow). In any case, if the AbstractForm has a 'form_processing_backend' attribute which should be a class, a new object of that class will be generated and its process method will be called. The process method needs two argumetns: The Page to pass any needed paramaters and the form to actually pass the form data. The form processing backends should inherit from the BaseFormProcessor class (however probably this will be refactored to just use duck-typing since I don't think that a base class offers anything here) and implement the process method. Also, another useful method would be the validate_usage to be called from the Form that uses the backend and actually check that the form defines the correct fields - an example is that for the email processor we need to define an email_to field in the form. The validate_usage would need to raise an ImproperlyConfigured exception if it has not been configured yet, however it has not been yet implemented. --- wagtail/wagtailforms/backends/__init__.py | 0 wagtail/wagtailforms/backends/base.py | 11 ++++++ wagtail/wagtailforms/backends/email.py | 17 +++++++++ wagtail/wagtailforms/forms.py | 4 +-- wagtail/wagtailforms/models.py | 44 +++++++++++++++++++---- 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 wagtail/wagtailforms/backends/__init__.py create mode 100644 wagtail/wagtailforms/backends/base.py create mode 100644 wagtail/wagtailforms/backends/email.py diff --git a/wagtail/wagtailforms/backends/__init__.py b/wagtail/wagtailforms/backends/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/wagtailforms/backends/base.py b/wagtail/wagtailforms/backends/base.py new file mode 100644 index 000000000..45d0343ae --- /dev/null +++ b/wagtail/wagtailforms/backends/base.py @@ -0,0 +1,11 @@ +from django.core.exceptions import ImproperlyConfigured + +class BaseFormProcessor(object): + def __init__(self): + pass + + def validate_usage(page): + return True + + def process(self, page, form): + return NotImplemented \ No newline at end of file diff --git a/wagtail/wagtailforms/backends/email.py b/wagtail/wagtailforms/backends/email.py new file mode 100644 index 000000000..ec1677318 --- /dev/null +++ b/wagtail/wagtailforms/backends/email.py @@ -0,0 +1,17 @@ +import datetime + +from django.core.exceptions import ImproperlyConfigured +from .base import BaseFormProcessor +from wagtail.wagtailadmin import tasks + + +class EmailFormProcessor(BaseFormProcessor): + def __init__(self): + pass + + def validate_usage(page): + return True + + def process(self, page, form): + content = ', '.join([ x[1].label +': '+ form.data.get(x[0]) for x in form.fields.items() ]) + tasks.send_email_task.delay("New " + page.title+" form submission at " + str(datetime.datetime.now()) , content, page.email_from, [page.email_to] ) diff --git a/wagtail/wagtailforms/forms.py b/wagtail/wagtailforms/forms.py index b444895c5..6e938822d 100644 --- a/wagtail/wagtailforms/forms.py +++ b/wagtail/wagtailforms/forms.py @@ -55,8 +55,8 @@ class FormBuilder(): return django.forms.ChoiceField(widget=django.forms.RadioSelect, **options) def create_checkboxes_field(self, field, options): - options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) - options['initial'] = field.default_value.split(',') + options['choices'] = [ (x.strip(), x.strip()) for x in field.choices.split(',')] + options['initial'] = [ x.strip() for x in field.default_value.split(',') ] return django.forms.MultipleChoiceField(widget=django.forms.CheckboxSelectMultiple, **options) def create_checkbox_field(self, field, options): diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index ff758497c..9eed4f61b 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -9,6 +9,8 @@ import re from wagtail.wagtailcore.models import Page, Orderable from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel +from wagtail.wagtailforms.backends.email import EmailFormProcessor + from modelcluster.fields import ParentalKey from .forms import FormBuilder @@ -47,8 +49,8 @@ class AbstractFormFields(models.Model): label = models.CharField(max_length=255, help_text=_('The label of the form field') ) field_type = models.CharField(max_length=16, choices = FORM_FIELD_CHOICES) required = models.BooleanField(default=True) - choices = models.CharField(max_length=512, blank=True, help_text=_('Comma seperated list of choices')) - default_value = models.CharField(max_length=255, blank=True) + choices = models.CharField(max_length=512, blank=True, help_text=_('Comma seperated list of choices. Only applicable in checkboxes, radio and dropdown.')) + default_value = models.CharField(max_length=255, blank=True, help_text=_('Default value. Comma seperated values supported for checkboxes.')) help_text = models.CharField(max_length=255, blank=True) panels = [ @@ -65,12 +67,12 @@ class AbstractFormFields(models.Model): class AbstractForm(Page): - """A Form Page. Pages with form should inhert from it""" + """A Form Page. Pages implementing a form should inhert from it""" form_builder = FormBuilder is_abstract = True # Don't display me in "Add" def __init__(self, *args, **kwargs): - super(Page, self).__init__(*args, **kwargs) + super(AbstractForm, self).__init__(*args, **kwargs) if not hasattr(self, 'landing_page_template'): template_wo_ext = re.match(HTML_EXTENSION_RE, self.template).group(1) self.landing_page_template = template_wo_ext + '_landing.html' @@ -93,10 +95,14 @@ class AbstractForm(Page): ) FormSubmission.objects.create( form_data = json.dumps(form_data), - form_page = self.page_ptr, + form_page = self, user = request.user, ) - # TODO: Do other things like sending email + # If we have a form_processing_backend call its process method + if hasattr(self, 'form_processing_backend'): + form_processor = self.form_processing_backend() + form_processor.process(self, self.form) + # render the landing_page # TODO: It is much better to redirect to it return render(request, self.landing_page_template, { @@ -111,6 +117,17 @@ class AbstractForm(Page): }) +class AbstractEmailForm(AbstractForm): + """A Form Page that sends email. Pages implementing a form that should be send to an email should inhert from it""" + is_abstract = True # Don't display me in "Add" + form_processing_backend = EmailFormProcessor + + email_to = models.CharField(max_length=255, ) + email_from = models.CharField(max_length=255, ) + + class Meta: + abstract = True + ######## TEST class ConcreteFormFields(Orderable, AbstractFormFields): @@ -124,3 +141,18 @@ ConcreteForm.content_panels = [ FieldPanel('thank_you', classname="full"), InlinePanel(ConcreteForm, 'form_fields', label="Form Fields"), ] + +######## +class ConcreteEmailFormFields(Orderable, AbstractFormFields): + page = ParentalKey('wagtailforms.ConcreteEmailForm', related_name='form_fields') + +class ConcreteEmailForm(AbstractEmailForm): + thank_you = models.CharField(max_length=255) + +ConcreteEmailForm.content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('thank_you', classname="full"), + FieldPanel('email_from', classname="full"), + FieldPanel('email_to', classname="full"), + InlinePanel(ConcreteEmailForm, 'form_fields', label="Form Fields"), +] From 680119dfef425a6cbc8e44f58c471fcb791f1a18 Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Sat, 22 Mar 2014 08:18:53 +0200 Subject: [PATCH 05/84] Make from_address optional in send_email_task --- wagtail/wagtailadmin/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/tasks.py b/wagtail/wagtailadmin/tasks.py index d63b17f79..59779d36b 100644 --- a/wagtail/wagtailadmin/tasks.py +++ b/wagtail/wagtailadmin/tasks.py @@ -88,7 +88,7 @@ def send_notification(page_revision_id, notification, excluded_user_id): @task -def send_email_task(email_subject, email_content, from_email, email_addresses): +def send_email_task(email_subject, email_content, email_addresses, from_email=None): if not from_email: if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'): from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL From 3855da21bf386c821a879339cf55bb2a0094e392 Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Sat, 22 Mar 2014 09:40:40 +0200 Subject: [PATCH 06/84] Add metaclass for AbstractForm and validate ... FormEmailProcessor. The metaclass is used to add each non-abstract form in a registry in a similar way as for Pages. It also checks if an form_processing_backend is defined and if it is it will call its validate_usage method. This way, the validate_usage method can throw immediately and ImproperlyConfigured method so the developer would know if he's done something wrong. The validate_usage method of the FormEmailProcessor has been implemented this way. --- wagtail/wagtailforms/backends/email.py | 10 ++++-- wagtail/wagtailforms/models.py | 49 +++++++++++++++++++++----- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/wagtail/wagtailforms/backends/email.py b/wagtail/wagtailforms/backends/email.py index ec1677318..063a2ce65 100644 --- a/wagtail/wagtailforms/backends/email.py +++ b/wagtail/wagtailforms/backends/email.py @@ -9,9 +9,15 @@ class EmailFormProcessor(BaseFormProcessor): def __init__(self): pass + @staticmethod def validate_usage(page): - return True + try: + page._meta.get_field('subject') + page._meta.get_field('to_address') + page._meta.get_field('from_address') + except: + raise ImproperlyConfigured("To use the EmailFormProcessor your Page must define the fields: subject, to_address and from_address.") def process(self, page, form): content = ', '.join([ x[1].label +': '+ form.data.get(x[0]) for x in form.fields.items() ]) - tasks.send_email_task.delay("New " + page.title+" form submission at " + str(datetime.datetime.now()) , content, page.email_from, [page.email_to] ) + tasks.send_email_task.delay(page.subject, content, [page.to_address], page.from_address, ) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index 9eed4f61b..f64f56df7 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,4 +1,6 @@ + from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ @@ -6,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ import json import re -from wagtail.wagtailcore.models import Page, Orderable +from wagtail.wagtailcore.models import PageBase, Page, Orderable from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel from wagtail.wagtailforms.backends.email import EmailFormProcessor @@ -66,11 +68,39 @@ class AbstractFormFields(models.Model): abstract = True +FORM_MODEL_CLASSES = [] +_FORM_CONTENT_TYPES = [] + +def get_form_types(): + global _FORM_CONTENT_TYPES + if len(_FORM_CONTENT_TYPES) != len(FORM_MODEL_CLASSES): + _FORM_CONTENT_TYPES = [ + ContentType.objects.get_for_model(cls) for cls in FORM_MODEL_CLASSES + ] + return _FORM_CONTENT_TYPES + + +class FormBase(PageBase): + """Metaclass for Forms""" + def __init__(cls, name, bases, dct): + super(FormBase, cls).__init__(name, bases, dct) + + if not cls.is_abstract: + # register this type in the list of page content types + FORM_MODEL_CLASSES.append(cls) + # Check if form_processing_backend is ok + if hasattr(cls, 'form_processing_backend'): + cls.form_processing_backend.validate_usage(cls) + + class AbstractForm(Page): """A Form Page. Pages implementing a form should inhert from it""" + + __metaclass__ = FormBase + form_builder = FormBuilder is_abstract = True # Don't display me in "Add" - + def __init__(self, *args, **kwargs): super(AbstractForm, self).__init__(*args, **kwargs) if not hasattr(self, 'landing_page_template'): @@ -118,17 +148,18 @@ class AbstractForm(Page): class AbstractEmailForm(AbstractForm): - """A Form Page that sends email. Pages implementing a form that should be send to an email should inhert from it""" + """A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it""" is_abstract = True # Don't display me in "Add" form_processing_backend = EmailFormProcessor - - email_to = models.CharField(max_length=255, ) - email_from = models.CharField(max_length=255, ) - + + to_address = models.CharField(max_length=255, ) + from_address = models.CharField(max_length=255, blank=True) + subject = models.CharField(max_length=255, ) + class Meta: abstract = True - - + + ######## TEST class ConcreteFormFields(Orderable, AbstractFormFields): page = ParentalKey('wagtailforms.ConcreteForm', related_name='form_fields') From 49a1ef30791fd9915fbbbea647bdcee2833d029d Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Sat, 22 Mar 2014 09:45:49 +0200 Subject: [PATCH 07/84] Add a basic views skeleton for wagtailforms --- .../templates/wagtailforms/index.html | 23 ++++ wagtail/wagtailforms/urls.py | 14 ++ wagtail/wagtailforms/views.py | 125 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 wagtail/wagtailforms/templates/wagtailforms/index.html create mode 100644 wagtail/wagtailforms/urls.py create mode 100644 wagtail/wagtailforms/views.py diff --git a/wagtail/wagtailforms/templates/wagtailforms/index.html b/wagtail/wagtailforms/templates/wagtailforms/index.html new file mode 100644 index 000000000..b4928e8e3 --- /dev/null +++ b/wagtail/wagtailforms/templates/wagtailforms/index.html @@ -0,0 +1,23 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n %} +{% block titletag %}{% trans "Forms" %}{% endblock %} +{% block bodyclass %}menu-forms{% endblock %} +{% block content %} + + {% include "wagtailadmin/shared/header.html" with title="Forms" %} + +
+
    + {% for name, description, content_type in form_types %} +
  • +
    + + {{ name|capfirst }} + + {{ description }} +
    +
  • + {% endfor %} +
+
+{% endblock %} diff --git a/wagtail/wagtailforms/urls.py b/wagtail/wagtailforms/urls.py new file mode 100644 index 000000000..7de8b003b --- /dev/null +++ b/wagtail/wagtailforms/urls.py @@ -0,0 +1,14 @@ +from django.conf.urls import patterns, url + + +urlpatterns = patterns( + 'wagtail.wagtailforms.views', + url(r'^$', 'index', name='wagtailforms_index'), + + #url(r'^choose/$', 'chooser.choose', name='wagtailsnippets_choose_generic'), + #url(r'^choose/(\w+)/(\w+)/$', 'chooser.choose', name='wagtailsnippets_choose'), + + #url(r'^(\w+)/(\w+)/$', 'snippets.list', name='wagtailsnippets_list'), + #url(r'^(\w+)/(\w+)/new/$', 'snippets.create', name='wagtailsnippets_create'), + #url(r'^(\w+)/(\w+)/(\d+)/$', 'snippets.edit', name='wagtailsnippets_edit'), +) diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py new file mode 100644 index 000000000..1814cd814 --- /dev/null +++ b/wagtail/wagtailforms/views.py @@ -0,0 +1,125 @@ +from django.http import Http404 +from django.shortcuts import get_object_or_404, render, redirect +from django.utils.encoding import force_text +from django.utils.text import capfirst +from django.contrib.contenttypes.models import ContentType +from django.contrib import messages +from django.contrib.auth.decorators import permission_required +from django.core.exceptions import PermissionDenied +from django.utils.translation import ugettext as _ + +from wagtail.wagtailadmin.edit_handlers import ObjectList, extract_panel_definitions_from_model_class + + +@permission_required('wagtailadmin.access_admin') +def index(request): + + + return render(request, 'wagtailforms/index.html', { + #'snippet_types': snippet_types, + }) + +""" +@permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view +def list(request, content_type_app_name, content_type_model_name): + content_type = get_content_type_from_url_params(content_type_app_name, content_type_model_name) + if not user_can_edit_snippet_type(request.user, content_type): + raise PermissionDenied + + model = content_type.model_class() + snippet_type_name, snippet_type_name_plural = get_snippet_type_name(content_type) + + items = model.objects.all() + + return render(request, 'wagtailsnippets/snippets/type_index.html', { + 'content_type': content_type, + 'snippet_type_name': snippet_type_name, + 'snippet_type_name_plural': snippet_type_name_plural, + 'items': items, + }) + + +@permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view +def create(request, content_type_app_name, content_type_model_name): + content_type = get_content_type_from_url_params(content_type_app_name, content_type_model_name) + if not user_can_edit_snippet_type(request.user, content_type): + raise PermissionDenied + + model = content_type.model_class() + snippet_type_name = get_snippet_type_name(content_type)[0] + + instance = model() + edit_handler_class = get_snippet_edit_handler(model) + form_class = edit_handler_class.get_form_class(model) + + if request.POST: + form = form_class(request.POST, request.FILES, instance=instance) + + if form.is_valid(): + form.save() + + messages.success( + request, + _("{snippet_type} '{instance}' created.").format( + snippet_type=capfirst(get_snippet_type_name(content_type)[0]), + instance=instance + ) + ) + return redirect('wagtailsnippets_list', content_type.app_label, content_type.model) + else: + messages.error(request, _("The snippet could not be created due to errors.")) + edit_handler = edit_handler_class(instance=instance, form=form) + else: + form = form_class(instance=instance) + edit_handler = edit_handler_class(instance=instance, form=form) + + return render(request, 'wagtailsnippets/snippets/create.html', { + 'content_type': content_type, + 'snippet_type_name': snippet_type_name, + 'edit_handler': edit_handler, + }) + + +@permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view +def edit(request, content_type_app_name, content_type_model_name, id): + content_type = get_content_type_from_url_params(content_type_app_name, content_type_model_name) + if not user_can_edit_snippet_type(request.user, content_type): + raise PermissionDenied + + model = content_type.model_class() + snippet_type_name = get_snippet_type_name(content_type)[0] + + instance = get_object_or_404(model, id=id) + edit_handler_class = get_snippet_edit_handler(model) + form_class = edit_handler_class.get_form_class(model) + + if request.POST: + form = form_class(request.POST, request.FILES, instance=instance) + + if form.is_valid(): + form.save() + + messages.success( + request, + _("{snippet_type} '{instance}' updated.").format( + snippet_type=capfirst(snippet_type_name), + instance=instance + ) + ) + return redirect('wagtailsnippets_list', content_type.app_label, content_type.model) + else: + messages.error(request, _("The snippet could not be saved due to errors.")) + edit_handler = edit_handler_class(instance=instance, form=form) + else: + form = form_class(instance=instance) + edit_handler = edit_handler_class(instance=instance, form=form) + + return render(request, 'wagtailsnippets/snippets/edit.html', { + 'content_type': content_type, + 'snippet_type_name': snippet_type_name, + 'instance': instance, + 'edit_handler': edit_handler, + }) + + +""" \ No newline at end of file From e3cb37ca72e43c2b544a9f1bef10942f47ce154a Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Sat, 22 Mar 2014 13:38:37 +0200 Subject: [PATCH 08/84] Add basic form submission views Add two urls: index which lists all different forms that have been defined and submissions which for a specific form will list all its submissions. The index lists each Page seperately (so if we have two different instances of a form they will be listed seperately here), so the submissions view will need the app_label, model_name and id to find out all the submissions of a specific page. --- .../templates/wagtailforms/form_index.html | 25 +++++++++ .../templates/wagtailforms/index.html | 14 ++--- .../templates/wagtailforms/list.html | 28 ++++++++++ wagtail/wagtailforms/urls.py | 7 +-- wagtail/wagtailforms/views.py | 53 ++++++++++++------- 5 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 wagtail/wagtailforms/templates/wagtailforms/form_index.html create mode 100644 wagtail/wagtailforms/templates/wagtailforms/list.html diff --git a/wagtail/wagtailforms/templates/wagtailforms/form_index.html b/wagtail/wagtailforms/templates/wagtailforms/form_index.html new file mode 100644 index 000000000..143542367 --- /dev/null +++ b/wagtail/wagtailforms/templates/wagtailforms/form_index.html @@ -0,0 +1,25 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n %} +{% block titletag %}{% blocktrans with form_title=form_page.title|capfirst %}Submissions of {{ form_title }}{% endblocktrans %}{% endblock %} +{% block bodyclass %}menu-snippets{% endblock %} +{% block content %} + +
+
+
+

+ {% blocktrans with form_title=form_page.title|capfirst %}Submissions of {{ form_title }}{% endblocktrans %} +

+
+ +
+
+
+
+ {% if submissions %} + {% include "wagtailforms/list.html" %} + {% else %} +

{% blocktrans with title=form_page.title %}No submissions of the '{{ title }}' form.{% endblocktrans %}

+ {% endif %} +
+{% endblock %} diff --git a/wagtail/wagtailforms/templates/wagtailforms/index.html b/wagtail/wagtailforms/templates/wagtailforms/index.html index b4928e8e3..d99fc8d98 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/index.html +++ b/wagtail/wagtailforms/templates/wagtailforms/index.html @@ -3,18 +3,20 @@ {% block titletag %}{% trans "Forms" %}{% endblock %} {% block bodyclass %}menu-forms{% endblock %} {% block content %} - - {% include "wagtailadmin/shared/header.html" with title="Forms" %} + {% trans "Forms" as forms_str %} + {% trans "Please select a form to view its submissions" as select_form_str %} + {% include "wagtailadmin/shared/header.html" with title=forms_str subtitle=select_form_str %}
    - {% for name, description, content_type in form_types %} + {% for fp in form_pages %}
  • - - {{ name|capfirst }} + + {{ fp|capfirst }} - {{ description }} + + {{ fp.content_type.name |capfirst }} ({{ fp.content_type.app_label }}.{{ fp.content_type.model }})
  • {% endfor %} diff --git a/wagtail/wagtailforms/templates/wagtailforms/list.html b/wagtail/wagtailforms/templates/wagtailforms/list.html new file mode 100644 index 000000000..243846615 --- /dev/null +++ b/wagtail/wagtailforms/templates/wagtailforms/list.html @@ -0,0 +1,28 @@ +{% load i18n %} + + + + + + + + + + + + + {% for submission in submissions %} + + + + + + {% endfor %} + +
    {% trans "Submission Date" %}{% trans "User" %}{% trans "Data" %}
    + {{ submission.submit_time }} + + {{ submission.user }} + + {{ submission.form_data }} +
    \ No newline at end of file diff --git a/wagtail/wagtailforms/urls.py b/wagtail/wagtailforms/urls.py index 7de8b003b..afcaaa5a6 100644 --- a/wagtail/wagtailforms/urls.py +++ b/wagtail/wagtailforms/urls.py @@ -4,11 +4,6 @@ from django.conf.urls import patterns, url urlpatterns = patterns( 'wagtail.wagtailforms.views', url(r'^$', 'index', name='wagtailforms_index'), + url(r'^submissions/(\w+)/(\w+)/(\d+)/$', 'list_submissions', name='wagtailforms_list_submissions'), - #url(r'^choose/$', 'chooser.choose', name='wagtailsnippets_choose_generic'), - #url(r'^choose/(\w+)/(\w+)/$', 'chooser.choose', name='wagtailsnippets_choose'), - - #url(r'^(\w+)/(\w+)/$', 'snippets.list', name='wagtailsnippets_list'), - #url(r'^(\w+)/(\w+)/new/$', 'snippets.create', name='wagtailsnippets_create'), - #url(r'^(\w+)/(\w+)/(\d+)/$', 'snippets.edit', name='wagtailsnippets_edit'), ) diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py index 1814cd814..a263acaf0 100644 --- a/wagtail/wagtailforms/views.py +++ b/wagtail/wagtailforms/views.py @@ -9,35 +9,48 @@ from django.core.exceptions import PermissionDenied from django.utils.translation import ugettext as _ from wagtail.wagtailadmin.edit_handlers import ObjectList, extract_panel_definitions_from_model_class +from wagtail.wagtailcore.models import Page +from wagtail.wagtailforms.models import FormSubmission, get_form_types + + +def get_form_type_from_url_params(app_name, model_name): + """ + Retrieve a form type from an app_name / model_name combo. + Throw Http404 if not a valid form type + """ + try: + content_type = ContentType.objects.get_by_natural_key(app_name, model_name) + except ContentType.DoesNotExist: + raise Http404 + if content_type not in get_form_types(): + raise Http404 + + return content_type @permission_required('wagtailadmin.access_admin') def index(request): - + form_types = get_form_types() + form_pages = Page.objects.filter(content_type__in=form_types) return render(request, 'wagtailforms/index.html', { - #'snippet_types': snippet_types, + 'form_pages': form_pages, + }) + +@permission_required('wagtailadmin.access_admin') +def list_submissions(request, app_label, model, id): + + model = get_form_type_from_url_params(app_label, model).model_class() + form_page = get_object_or_404(model, id=id) + + submissions = FormSubmission.objects.filter(form_page=form_page) + + return render(request, 'wagtailforms/form_index.html', { + 'form_page': form_page, + 'submissions': submissions, }) """ -@permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view -def list(request, content_type_app_name, content_type_model_name): - content_type = get_content_type_from_url_params(content_type_app_name, content_type_model_name) - if not user_can_edit_snippet_type(request.user, content_type): - raise PermissionDenied - - model = content_type.model_class() - snippet_type_name, snippet_type_name_plural = get_snippet_type_name(content_type) - - items = model.objects.all() - - return render(request, 'wagtailsnippets/snippets/type_index.html', { - 'content_type': content_type, - 'snippet_type_name': snippet_type_name, - 'snippet_type_name_plural': snippet_type_name_plural, - 'items': items, - }) - @permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view def create(request, content_type_app_name, content_type_model_name): From 2a4c371f51b5a53d9fdbab0faa2c07c80fb3921e Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Sat, 22 Mar 2014 18:23:16 +0200 Subject: [PATCH 09/84] Change pagination_nav.html to work without linkurl pagination_nav.html received a linkurl parameter which has to be a url name that get resolved to generate the link for next/previous. The thing is that if the url to be resolved had parameters, these wouldn't be passed and an exception would be thrown when trying to resolve it. A quick and dirty fix is to just use {% url linkurl as link_to_use %} so link_to_use will be empty and the currrent page url will be used. --- .../templates/wagtailadmin/shared/pagination_nav.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/pagination_nav.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/pagination_nav.html index 39eb88a15..847b971e0 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/pagination_nav.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/pagination_nav.html @@ -1,4 +1,5 @@ {% load i18n %} +{% url linkurl as url_to_use %}
    {% if submissions %} - {% include "wagtailforms/list.html" %} - {% include "wagtailadmin/shared/pagination_nav.html" with items=submissions is_searching=False linkurl='-' %}{# Here we pass an invalid non-empty URL name as linkurl to generate pagination links of the form "?p=123" with the URL path omitted #} + {% include "wagtailforms/list_submissions.html" %} + + {% include "wagtailadmin/shared/pagination_nav.html" with items=submissions is_searching=False linkurl='-' %} + {# Here we pass an invalid non-empty URL name as linkurl to generate pagination links with the URL path omitted #} {% else %}

    {% blocktrans with title=form_page.title %}No submissions of the '{{ title }}' form.{% endblocktrans %}

    {% endif %} diff --git a/wagtail/wagtailforms/templates/wagtailforms/list_forms.html b/wagtail/wagtailforms/templates/wagtailforms/list_forms.html new file mode 100644 index 000000000..b1958f5b2 --- /dev/null +++ b/wagtail/wagtailforms/templates/wagtailforms/list_forms.html @@ -0,0 +1,23 @@ +{% load i18n %} + + + + + + + + + + + {% for fp in form_pages %} + + + + + {% endfor %} + +
    {% trans "Title" %}{% trans "Origin" %}
    +

    {{ fp|capfirst }}

    +
    + {{ fp.content_type.name |capfirst }} ({{ fp.content_type.app_label }}.{{ fp.content_type.model }}) +
    \ No newline at end of file diff --git a/wagtail/wagtailforms/templates/wagtailforms/list.html b/wagtail/wagtailforms/templates/wagtailforms/list_submissions.html similarity index 90% rename from wagtail/wagtailforms/templates/wagtailforms/list.html rename to wagtail/wagtailforms/templates/wagtailforms/list_submissions.html index 4dea4a61d..0ead37bde 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/list.html +++ b/wagtail/wagtailforms/templates/wagtailforms/list_submissions.html @@ -2,9 +2,9 @@ - + - + {% for heading in data_headings %} diff --git a/wagtail/wagtailforms/templates/wagtailforms/results_forms.html b/wagtail/wagtailforms/templates/wagtailforms/results_forms.html new file mode 100644 index 000000000..6cf198553 --- /dev/null +++ b/wagtail/wagtailforms/templates/wagtailforms/results_forms.html @@ -0,0 +1,8 @@ +{% load i18n %} +{% if form_pages %} + {% include "wagtailforms/list_forms.html" %} + + {% include "wagtailadmin/shared/pagination_nav.html" with items=form_pages linkurl="wagtailforms_index" %} +{% else %} +

    {% trans "No form pages have been created." %}

    +{% endif %} diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py index f5ce0b07a..aa3d525df 100644 --- a/wagtail/wagtailforms/views.py +++ b/wagtail/wagtailforms/views.py @@ -14,8 +14,19 @@ from wagtail.wagtailforms.forms import SelectDateForm @permission_required('wagtailadmin.access_admin') def index(request): + p = request.GET.get("p", 1) + form_pages = get_forms_for_user(request.user) + paginator = Paginator(form_pages, 20) + + try: + form_pages = paginator.page(p) + except PageNotAnInteger: + form_pages = paginator.page(1) + except EmptyPage: + form_pages = paginator.page(paginator.num_pages) + return render(request, 'wagtailforms/index.html', { 'form_pages': form_pages, }) @@ -68,7 +79,7 @@ def list_submissions(request, page_id): return response p = request.GET.get('p', 1) - paginator = Paginator(submissions, 20) + paginator = Paginator(submissions, 1) try: submissions = paginator.page(p) @@ -84,7 +95,7 @@ def list_submissions(request, page_id): data_row = [s.submit_time] + [form_data.get(name) for name, label in data_fields] data_rows.append(data_row) - return render(request, 'wagtailforms/form_index.html', { + return render(request, 'wagtailforms/index_submissions.html', { 'form_page': form_page, 'select_date_form': select_date_form, 'submissions': submissions, From aa41a5e8a9a11e1c7babbcf547b0ea590e414e87 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 13 May 2014 12:45:49 +0100 Subject: [PATCH 40/84] updating header form style --- .../templates/wagtailforms/index.html | 2 +- .../wagtailforms/index_submissions.html | 47 ++++++++++--------- wagtail/wagtailforms/views.py | 2 +- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/wagtail/wagtailforms/templates/wagtailforms/index.html b/wagtail/wagtailforms/templates/wagtailforms/index.html index e542e596d..c3a831501 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/index.html +++ b/wagtail/wagtailforms/templates/wagtailforms/index.html @@ -4,7 +4,7 @@ {% block bodyclass %}menu-forms{% endblock %} {% block content %} {% trans "Forms" as forms_str %} - {% trans "Data" as select_form_str %} + {% trans "Pages" as select_form_str %} {% include "wagtailadmin/shared/header.html" with title=forms_str subtitle=select_form_str %}
    diff --git a/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html b/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html index 26ed4a7bd..7bba352fb 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html +++ b/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html @@ -30,27 +30,32 @@ {% endblock %} {% block content %}
    -
    -
    -

    - {% blocktrans with form_title=form_page.title|capfirst %}Submissions of {{ form_title }}{% endblocktrans %} -

    -
    -
    -
      - {% for field in select_date_form %} -
      - {% include "wagtailadmin/shared/field_as_li.html" with field=field %} -
      - {% endfor %} -
      -
    • -
    • + +
      +
      +
      +

      + {% blocktrans with form_title=form_page.title|capfirst %}Form data {{ form_title }}{% endblocktrans %} +

      - -
    - -
    + +
    +
    + +
    + +
    {% if submissions %} @@ -59,7 +64,7 @@ {% include "wagtailadmin/shared/pagination_nav.html" with items=submissions is_searching=False linkurl='-' %} {# Here we pass an invalid non-empty URL name as linkurl to generate pagination links with the URL path omitted #} {% else %} -

    {% blocktrans with title=form_page.title %}No submissions of the '{{ title }}' form.{% endblocktrans %}

    +

    {% blocktrans with title=form_page.title %}There have been no submissions of the '{{ title }}' form.{% endblocktrans %}

    {% endif %}
    {% endblock %} diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py index aa3d525df..2174642c6 100644 --- a/wagtail/wagtailforms/views.py +++ b/wagtail/wagtailforms/views.py @@ -79,7 +79,7 @@ def list_submissions(request, page_id): return response p = request.GET.get('p', 1) - paginator = Paginator(submissions, 1) + paginator = Paginator(submissions, 20) try: submissions = paginator.page(p) From 6f5203c6fdd052ca8b274246c261c0e5b6437593 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 13 May 2014 15:51:28 +0100 Subject: [PATCH 41/84] Medium-scale changes to fields to allow 'iconfields' and header refactored into own css file --- .../static/wagtailadmin/js/core.js | 2 +- .../wagtailadmin/scss/components/forms.scss | 63 +++++++- .../wagtailadmin/scss/components/header.scss | 153 ++++++++++++++++++ .../wagtailadmin/scss/components/icons.scss | 10 +- .../static/wagtailadmin/scss/core.scss | 129 +-------------- .../wagtailadmin/scss/fonts/wagtail.eot | Bin 12996 -> 13308 bytes .../wagtailadmin/scss/fonts/wagtail.svg | 4 +- .../wagtailadmin/scss/fonts/wagtail.ttf | Bin 12832 -> 13144 bytes .../wagtailadmin/scss/fonts/wagtail.woff | Bin 9164 -> 9356 bytes .../wagtailadmin/shared/field_as_li.html | 10 +- .../templates/wagtailadmin/shared/header.html | 6 +- .../templates/wagtailforms/index.html | 2 +- .../wagtailforms/index_submissions.html | 12 +- wagtail/wagtailforms/wagtail_hooks.py | 2 +- 14 files changed, 237 insertions(+), 156 deletions(-) create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js index e5895e34f..380c3d179 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js @@ -128,7 +128,7 @@ $(function(){ $(window.headerSearch.termInput).trigger('focus'); function search () { - var workingClasses = "working icon icon-spinner"; + var workingClasses = "icon-spinner"; $(window.headerSearch.termInput).parent().addClass(workingClasses); search_next_index++; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index 2425458ee..fa35e6226 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -356,12 +356,6 @@ button.icon{ color:$color-grey-2; } -/* permanently show checkbox/radio help as they have no focus state */ -.boolean_field .help, .radio .help{ - opacity:1; -} - - fieldset:hover > .help, .field.focused + .help, .field:focus + .help, @@ -391,7 +385,58 @@ li.focused > .help{ background-color:$color-input-error-bg; } + +/* Layouts for particular kinds of of fields */ + +/* permanently show checkbox/radio help as they have no focus state */ +.boolean_field .help, .radio .help{ + opacity:1; +} +.iconfield { + position:relative; + + input:not([type=radio]), input:not([type=checkbox]), input:not([type=submit]), input:not([type=button]){ + padding-left:2.5em; + } + + &:before, &:after{ + font-family:wagtail; + position:absolute; + top:0.4em; + font-size:1.4em; + color:$color-grey-3; + } + &:before{ + left:0.5em; + } + &:after{ + right:0.5em; + } + + /* special case for search spinners */ + &.icon-spinner:after{ + color:$color-teal; + opacity:0.8; + font-size:20px; + width:20px; + height:20px; + line-height:23px; + text-align:center; + top:0.3em; + + } +} + + /* field sizing */ + +.field-small{ + input, textarea, select, .richtext, .tagit{ + @include border-radius(3px); + padding:0.4em 1em; + } +} + .field{ &.col1, &.col2, @@ -568,7 +613,8 @@ ul.tagit li.tagit-choice-editable{ } -/* search bars (search integrated into header area) */ +/* +// search bars (search integrated into header area) .search-bar{ margin-top:-2em; padding-top:1em; @@ -666,12 +712,13 @@ ul.tagit li.tagit-choice-editable{ } } -/* mozilla specific hack */ +// mozilla specific hack @-moz-document url-prefix() { .search-bar .fields .field:after{ line-height:20px; } } +*/ /* Transitions */ fieldset, input, textarea, select{ diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss new file mode 100644 index 000000000..1c7f7435a --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss @@ -0,0 +1,153 @@ +header{ + padding-top:1em; + padding-bottom:1em; + background-color: $color-header-bg; + margin-bottom:2em; + color:white; + + h1, h2{ + margin:0; + color:white; + } + + h1{ + padding:0.2em 0; + + &.icon:before{ + width:1em; + display:none; + margin-right:0.4em; + font-size:1.5em; + } + } + + .col{ + float:left; + margin-right:2em; + } + .left{ + float:left; + + .hasform &:first-child{ + padding-bottom:0.5em; + float:none; + } + } + .right{ + text-align:right; + float:right; + } + + /* For case where content below header should merge with it */ + &.merged{ + margin-bottom:0; + } + &.tab-merged, &.no-border{ + border:0; + } + &.merged.no-border{ + padding-bottom:0; + } + &.no-v-padding{ + padding-top:0; + padding-bottom:0; + } + /* + &.hasform h1{ + margin-top:0.2em; + } + */ + .button{ + background-color:$color-teal-darker; + &:hover{ + background-color:$color-teal-dark; + } + } + + /* necessary on mobile only to make way for hamburger menu */ + &.nice-padding{ + padding-left:4em; + } + + label{ + @include visuallyhidden(); + } + + input[type=text], select{ + border-width:0; + + &:focus{ + background-color:white; + } + } + + .fields{ + margin-top:-0.5em; + + .field{ + padding:0; + } + } +} + +/* mozilla specific hack */ +@-moz-document url-prefix() { + .search-form .fields .field:after{ + line-height:20px; + } +} + +.page-explorer header{ + margin-bottom:0; + padding-bottom:0em; +} + + +@media screen and (min-width: $breakpoint-mobile){ + header{ + padding-top:1.5em; + padding-bottom:1.5em; + + &.nice-padding{ + @include nice-padding(); + } + + .left{ + float:left; + margin-right:0; + + &:first-child{ + padding-bottom:0; + float:left; + } + } + .second{ + clear:none; + + .right, .left{ + float:right; + } + } + + h1.icon:before{ + display:inline-block; + } + + .col3{ + @include column(3); + } + .col3.addbutton{ + width:auto; + } + .col6{ + @include column(6); + } + .col9{ + @include column(9); + } + .breadcrumb{ + margin-left:-($desktop-nice-padding); + margin-right:-($desktop-nice-padding); + } + } +} \ No newline at end of file diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/icons.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/icons.scss index 9a1351d9a..4de3aa131 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/icons.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/icons.scss @@ -231,14 +231,20 @@ .icon-collapse-up:before{ content:"6"; } +.icon-date:before{ + content:"7"; +} +.icon-success:before{ + content:"9"; +} .icon-help:before{ content:"?"; } .icon-warning:before{ content:"!"; } -.icon-success:before{ - content:"9"; +.icon-form:before{ + content:"$"; } .icon.text-replace{ diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index 8ffaa450a..d2e9e4759 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -11,6 +11,7 @@ @import "components/listing.scss"; @import "components/messages.scss"; @import "components/formatters.scss"; +@import "components/header.scss"; @import "fonts.scss"; @@ -385,88 +386,6 @@ body.explorer-open { } } -header{ - padding-top:1em; - padding-bottom:1em; - background-color: $color-header-bg; - margin-bottom:2em; - color:white; - - h1, h2{ - margin:0; - color:white; - } - - h1{ - padding:0.2em 0; - - &.icon:before{ - width:1em; - display:none; - margin-right:0.4em; - font-size:1.5em; - } - } - - .col{ - float:left; - margin-right:2em; - } - .left{ - float:left; - - .hasform &:first-child{ - padding-bottom:0.5em; - float:none; - } - } - .search-bar input{ - @include border-radius(3px); - width:auto; - border-width:0; - } - .right{ - text-align:right; - float:right; - } - - /* For case where content below header should merge with it */ - &.merged{ - margin-bottom:0; - } - &.tab-merged, &.no-border{ - border:0; - } - &.merged.no-border{ - padding-bottom:0; - } - &.no-v-padding{ - padding-top:0; - padding-bottom:0; - } - /* - &.hasform h1{ - margin-top:0.2em; - } - */ - .button{ - background-color:$color-teal-darker; - &:hover{ - background-color:$color-teal-dark; - } - } - /* necessary on mobile only to make way for hamburger menu */ - &.nice-padding{ - padding-left:4em; - } -} - -.page-explorer header{ - margin-bottom:0; - padding-bottom:0em; -} - - footer{ @include row(); @include border-radius(3px 3px 0 0); @@ -832,52 +751,6 @@ footer, .logo{ } } - header{ - padding-top:1.5em; - padding-bottom:1.5em; - - &.nice-padding{ - @include nice-padding(); - } - - .left{ - float:left; - margin-right:0; - - &:first-child{ - padding-bottom:0; - float:left; - } - } - .second{ - clear:none; - - .right, .left{ - float:right; - } - } - - h1.icon:before{ - display:inline-block; - } - - .col3{ - @include column(3); - } - .col3.addbutton{ - width:auto; - } - .col6{ - @include column(6); - } - .col9{ - @include column(9); - } - .breadcrumb{ - margin-left:-($desktop-nice-padding); - margin-right:-($desktop-nice-padding); - } - } footer{ width:80%; margin-left:50px; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.eot b/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.eot index f55be6906a2bfcc05ed4e43273c1ebe331ee1914..6ac63514abbec26479e6a6f827ba9212dd270687 100644 GIT binary patch delta 890 zcmZ8fOH30{6g{t12dgYDL)-R&_rV-G10J48m+J>(gaL^P3s0- zp*RZ@e_^9>;X)~i3nd{@;)bXz;=%#)Okhx0$4PF{TND&r3(dEDaKQ- zb0{@=(epBK5a3<_2g1Y2#08vqc3ZDtLdGrFb5!(%5@%AQ=M%%P$KsS2CI8w;Vj_*} ztd8P@A$Tbt zUD&q3#GRz{9BZ=mupnIeXSSIsL7$Om63uVy0KsevtdnOPAb`U=(ms?CVssJ_LJL}P z2m_d+%?&az$;7&lFfjqugxd<+X58px&<;}uaFler2P)#!N~6Lybm;@uz3h;lwWj!P zGwqjXSK}>OYg^Y%u9Bhxx1hh_rkwn`f7<^&z~_$Jk`4N&=JDLqrYOs}j2d&{D%4{? znxW7E?KEpIjsXpNuczn8aLMK2#v#7Io7SNwG}uo%ftMshHoD{* zqYWD@z~l3I2DLdYSmd=bNh%|z2l%~|6;71G3+!g1ml-4O5>>H^k+2|tZ&+Ad%uln) z{8ctdK3zn7~WTqHB6c+d!*S4@OW5 z^&o^fd9R>1B|Z2D)Pf)s6uqb@>jNJbFTIE*{${$MEWgKm-?zV+Z)g8h*Ijd605Q=t zvwYg~qRryf{#$P!0D=RsPL)qqFb5Mzer~=PNWVWF-UkFG0QRlwEY#a=1c`e`+Ky`N z`jwkMzyAREL*RJVjO$!RX>wH#Fssq|!Td!pJtpDZ+WaNwZ0_7)5>nvKtg}?d3Ohu+ zP3zd4GwYTMFFygJYlPjd&o3;7L?}WVDSj1gzD@%EIktNXr+c*;MkzrLyyt`gUBL}0;X_-JntY0jg;VM z#3ImWZ);A?0J?PUMTP1(GO&@O33-gkncxvtl=p(QUM=y{c*f2Z@?((Qd_U1cd`*7j zOT6z)-*W4s;T~({N7GC`8(EYq$)oXuxaOalBZNnOZy+@3XFN>4 zx^ds{4eY@azc&egEe{Bn*>YJ7h3( - + + + diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.ttf b/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.ttf index a401ae2ddd45ba74cf37715cee04a925d9c13915..2006feefbf5c293024c0dcc039aa196fcb4aad67 100644 GIT binary patch delta 967 zcmZ8fOH31C5dQw{_CYI^)@^;T$QG&;G`*5}1j;)`(v!pPcj{??e+`@rrIP9pF1>n0xpmT5DwWKuyv%V_tQQ1R znTecBv@H@|AUvBM8&p#t#^cnWj_}=#n#~;g#vH|m%-BMkBbzZ#{1Y>_!jC00wh_OETTn5A z39>gqd#Z(%B5hEsnk1unw(H$=hQ%h$FCT>q;4G*M2LduDu0b G?)eMGS+v&x delta 698 zcmYLGO=uHA6#jO1vN1`m9$GCLA=N6V&?bv*k~We?V(LMuP*=pC%F?EZlB9tKjUG%v zDcFOcg9mROD(FpVkD?bTcq#~jpdv&IS}$ID5hQ-IS#*|p^Zm@enfKGyq#M z4+E!jxihCK^BBh{hpFcJL@f0}RQOrBj$kN|?*XqG9 z<}dQ+TEKr`f!4d0T1|As)rCc0XvhS3PJy(2n{tS_Gy%rfoKI2q;Gt;m)v|189<|R)rz3 z%5^(GwFl_M5RypaJZ`f1wi0!f3>;SpQ}ng|xSq-Zbhxn#Ij$oZL;`VUNMcyN(f4bk zazihvhWr~NgNb-LIShH#*iC!ESdl4F7u_ej7nWa&{K4gi{YNy}+h3FG(F2XWxaQqj zz>jAh_929~9yY13tBOQ`N+-{0W1YCi!v?a5dRS0@hEzK4hpC-UO@?v zi!obE$Y$&iW2ZGK%T`3o$a_{}Y$toghhkTB_a^G(9=A%x4U94h^7b>iy;_@G{jGNV If3u+d7u{ieXaE2J diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.woff b/wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail.woff index fbefc159a6096afaf3cae10fbf91a86aba458210..072c20a1661ce2b507bdde0c196bb483c2f20f94 100644 GIT binary patch delta 9324 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 RIa`UsMInPfnvMa${s&%0s&N1S delta 9109 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 MuJv=#F(9=60A-IkQUCw| diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html index 131449915..e40e6dbda 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html @@ -1,10 +1,12 @@ {% load wagtailadmin_tags %} -
  • +
  • {{ field.label_tag }} - {% block form_field %} - {{ field }} - {% endblock %} +
    + {% block form_field %} + {{ field }} + {% endblock %} +
    {% if field.errors %} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/header.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/header.html index 3ea5d4d10..59340d2d8 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/header.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/header.html @@ -5,12 +5,12 @@

    {{ title }} {{ subtitle }}

    {% if search_url %} - +
      {% for field in search_form %} - {% include "wagtailadmin/shared/field_as_li.html" with field=field %} + {% include "wagtailadmin/shared/field_as_li.html" with field=field input_classes="field-small iconfield icon-search" %} {% endfor %} - +
    {% endif %} diff --git a/wagtail/wagtailforms/templates/wagtailforms/index.html b/wagtail/wagtailforms/templates/wagtailforms/index.html index c3a831501..c5ccc1aa4 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/index.html +++ b/wagtail/wagtailforms/templates/wagtailforms/index.html @@ -5,7 +5,7 @@ {% block content %} {% trans "Forms" as forms_str %} {% trans "Pages" as select_form_str %} - {% include "wagtailadmin/shared/header.html" with title=forms_str subtitle=select_form_str %} + {% include "wagtailadmin/shared/header.html" with title=forms_str subtitle=select_form_str icon="form" %}
    diff --git a/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html b/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html index 7bba352fb..fa4bfdb13 100644 --- a/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html +++ b/wagtail/wagtailforms/templates/wagtailforms/index_submissions.html @@ -38,15 +38,13 @@ {% blocktrans with form_title=form_page.title|capfirst %}Form data {{ form_title }}{% endblocktrans %}
    -
  • {% trans "Submission Date" %}{{ heading }}
    @@ -23,3 +24,4 @@ {% endfor %}
    +
    From 4111baf9331bd5aea92da6274a4d5fbae19f1502 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 20 May 2014 10:25:14 +0100 Subject: [PATCH 52/84] font size fix for login form icons --- wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss index a8164272d..4abf3ae20 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss @@ -179,6 +179,7 @@ form{ left: $desktop-nice-padding; margin-top: -1em; top: 50%; + font-size:1.5em; } .full{ From 7d88d285cd7891ffb43fa9a74b55755eaf7aa70b Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 20 May 2014 10:41:08 +0100 Subject: [PATCH 53/84] updated login styles to match new 'iconfield' implementation --- .../wagtailadmin/scss/layouts/login.scss | 38 ++++++++++--------- .../templates/wagtailadmin/login.html | 12 ++++-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss index 4abf3ae20..3e158e51f 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss @@ -82,7 +82,7 @@ form{ .field{ padding:0; } - .field.icon:before{ + .iconfield:before{ display:none; } @@ -168,25 +168,29 @@ form{ font-size:4em; } - .field.icon:before{ - display:inline-block; - position: absolute; - color:$color-grey-4; - border: 2px solid $color-grey-4; - border-radius: 100%; - width: 1em; - padding: 0.3em; - left: $desktop-nice-padding; - margin-top: -1em; - top: 50%; - font-size:1.5em; - } - .full{ margin:0px (-$desktop-nice-padding); - input{ - padding-left:($desktop-nice-padding + 50px); + .iconfield{ + &:before{ + display:inline-block; + position: absolute; + color:$color-grey-4; + border: 2px solid $color-grey-4; + border-radius: 100%; + width: 1em; + padding: 0.3em; + left: $desktop-nice-padding; + margin-top: -1.1rem; + top: 50%; + font-size:1.3rem; + } + + input{ + padding-left:($desktop-nice-padding + 50px); + } } + + } } \ No newline at end of file diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/login.html b/wagtail/wagtailadmin/templates/wagtailadmin/login.html index 9cb28ed86..012dd32f8 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/login.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/login.html @@ -28,15 +28,19 @@
    • -
      +
      {{ form.username.label_tag }} - {{ form.username }} +
      + {{ form.username }} +
    • -
      +
      {{ form.password.label_tag }} - {{ form.password }} +
      + {{ form.password }} +
      {% if show_password_reset %}

      {% trans "Forgotten it?" %}

      From 26183e608bb1c4c8df8838879a74c1b43d91a4da Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 20 May 2014 11:05:19 +0100 Subject: [PATCH 54/84] mention some more optional/required dependencies in 'getting started' docs --- docs/gettingstarted.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index e4d2d6484..626aa42c0 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -102,12 +102,17 @@ Required dependencies ===================== - `pip `_ +- `libjpeg `_ +- `libxml2 `_ +- `libxslt `_ +- `zlib `_ Optional dependencies ===================== - `PostgreSQL`_ - `Elasticsearch`_ +- `Redis`_ Installation ============ @@ -137,6 +142,7 @@ with a regular Django project. .. _the Wagtail codebase: https://github.com/torchbox/wagtail .. _PostgreSQL: http://www.postgresql.org .. _Elasticsearch: http://www.elasticsearch.org +.. _Redis: http://redis.io/ _`Remove the demo app` ~~~~~~~~~~~~~~~~~~~~~~ From 9564cbf39f1366dbb0f99f904982c0e50356fc31 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 20 May 2014 11:17:49 +0100 Subject: [PATCH 55/84] Added document describing how to generate a static website --- docs/static_site_generation.rst | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/static_site_generation.rst diff --git a/docs/static_site_generation.rst b/docs/static_site_generation.rst new file mode 100644 index 000000000..dbdf40eb2 --- /dev/null +++ b/docs/static_site_generation.rst @@ -0,0 +1,83 @@ +Generating a static site +======================== + +This document describes how to render your Wagtail site into static HTML files using `django medusa`_ and the 'wagtail.contrib.wagtailmedusa' module. + + +Installing django-medusa +~~~~~~~~~~~~~~~~~~~~~~~~ + +Firstly, install django medusa from pip: + +.. code:: + + pip install django-medusa + + +Then add 'django_medusa' and 'wagtail.contrib.wagtailmedusa' to INSTALLED_APPS: + +.. code:: python + + INSTALLED_APPS = [ + ... + 'django_medusa', + 'wagtail.contrib.wagtailmedusa', + ] + + +Replacing GET parameters with custom routing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pages which require GET parameters (eg, pagination) don't generate suitable filenames for generated HTML files so they need to be changed to use custom routing instead. + +For example, lets say we have a Blog Index which uses pagination. We can override the 'route' method to make it respond on urls like '/page/1' and pass the page number through to the serve method: + +.. code:: python + + class BlogIndex(Page): + ... + + def serve(self, request, page=1): + ... + + def route(self, request, path_components): + if self.live and len(path_components) == 2 and path_components[0] == 'page': + try: + return self.serve(request, page=int(path_components[1])) + except (TypeError, ValueError): + pass + + return super(BlogIndex, self).route(request, path_components) + + +Rendering pages which use custom routing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For page types that override the route method, we need to let django medusa know which URLs it responds on. This is done by overriding the 'get_staticsite_paths' method to make it yield one string per URL path. + +For example, the BlogIndex above would need to yield one URL for each page of results: + +.. code:: python + + def get_staticsite_paths(self): + # Get page count + page_count = ... + + # Yield a path for each page + for page in range(page_count): + yield '/%d/' % (page + 1) + + # Yield from superclass + for path in super(BlogIndex, self).get_staticsite_paths(): + yield path + + +Rendering +~~~~~~~~~ + +To render a site, just run ``./manage.py staticsitegen``. This will render the entire website and place the HTML in a folder called 'medusa_output'. The static and media folders need to be copied into this folder manually after the rendering is complete. + +To test, open the 'medusa_output' folder in a terminal and run ``python -m SimpleHTTPServer``. + + +.. _django medusa: https://github.com/mtigas/django-medusa From c194a55304a5e9b45cf53f133502cfdcb820ec42 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 20 May 2014 12:39:23 +0100 Subject: [PATCH 56/84] Renamed get_staticsite_paths to get_static_site_paths --- docs/static_site_generation.rst | 6 +++--- wagtail/contrib/wagtailmedusa/renderers.py | 2 +- wagtail/wagtailcore/models.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/static_site_generation.rst b/docs/static_site_generation.rst index dbdf40eb2..66bd23ff5 100644 --- a/docs/static_site_generation.rst +++ b/docs/static_site_generation.rst @@ -53,13 +53,13 @@ For example, lets say we have a Blog Index which uses pagination. We can overrid Rendering pages which use custom routing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For page types that override the route method, we need to let django medusa know which URLs it responds on. This is done by overriding the 'get_staticsite_paths' method to make it yield one string per URL path. +For page types that override the route method, we need to let django medusa know which URLs it responds on. This is done by overriding the 'get_static_site_paths' method to make it yield one string per URL path. For example, the BlogIndex above would need to yield one URL for each page of results: .. code:: python - def get_staticsite_paths(self): + def get_static_site_paths(self): # Get page count page_count = ... @@ -68,7 +68,7 @@ For example, the BlogIndex above would need to yield one URL for each page of re yield '/%d/' % (page + 1) # Yield from superclass - for path in super(BlogIndex, self).get_staticsite_paths(): + for path in super(BlogIndex, self).get_static_site_paths(): yield path diff --git a/wagtail/contrib/wagtailmedusa/renderers.py b/wagtail/contrib/wagtailmedusa/renderers.py index ee0ccf4f8..950e13a14 100644 --- a/wagtail/contrib/wagtailmedusa/renderers.py +++ b/wagtail/contrib/wagtailmedusa/renderers.py @@ -12,7 +12,7 @@ class PageRenderer(StaticSiteRenderer): return [] # Return list of paths - return site.root_page.get_staticsite_paths() + return site.root_page.get_static_site_paths() class DocumentRenderer(StaticSiteRenderer): diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index d453684bd..17b88198e 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -622,7 +622,7 @@ class Page(MP_Node, ClusterableModel, Indexed): """ return self.serve(self.dummy_request()) - def get_staticsite_paths(self): + def get_static_site_paths(self): """ This is a generator of URL paths to feed into a static site generator Override this if you would like to create static versions of subpages @@ -632,7 +632,7 @@ class Page(MP_Node, ClusterableModel, Indexed): # Yield paths for child pages for child in self.get_children().live(): - for path in child.specific.get_staticsite_paths(): + for path in child.specific.get_static_site_paths(): yield '/' + child.slug + path def get_ancestors(self, inclusive=False): From 61779be5c96f62c0c2e99c99e91b8a6cf9caf4c1 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 20 May 2014 13:04:47 +0100 Subject: [PATCH 57/84] Added unit tests for get_static_site_paths --- wagtail/tests/models.py | 42 ++++++++++++++++++++++++++++++++++-- wagtail/wagtailcore/tests.py | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/wagtail/tests/models.py b/wagtail/tests/models.py index 9124cb253..97858df4d 100644 --- a/wagtail/tests/models.py +++ b/wagtail/tests/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from modelcluster.fields import ParentalKey from wagtail.wagtailcore.models import Page, Orderable from wagtail.wagtailcore.fields import RichTextField @@ -187,11 +188,48 @@ class EventIndex(Page): intro = RichTextField(blank=True) ajax_template = 'tests/includes/event_listing.html' - def get_context(self, request): + def get_events(self): + return self.get_children().live().type(EventPage) + + def get_paginator(self): + return Paginator(self.get_events(), 4) + + def get_context(self, request, page=1): + # Pagination + paginator = self.get_paginator() + try: + events = paginator.page(page) + except PageNotAnInteger: + events = paginator.page(1) + except EmptyPage: + events = paginator.page(paginator.num_pages) + + # Update context context = super(EventIndex, self).get_context(request) - context['events'] = EventPage.objects.filter(live=True) + context['events'] = events return context + def route(self, request, path_components): + if self.live and len(path_components) == 1: + try: + return self.serve(request, page=int(path_components[0])) + except (TypeError, ValueError): + pass + + return super(EventIndex, self).route(request, path_components) + + def get_static_site_paths(self): + # Get page count + page_count = self.get_paginator().num_pages + + # Yield a path for each page + for page in range(page_count): + yield '/%d/' % (page + 1) + + # Yield from superclass + for path in super(EventIndex, self).get_static_site_paths(): + yield path + EventIndex.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('intro', classname="full"), diff --git a/wagtail/wagtailcore/tests.py b/wagtail/wagtailcore/tests.py index 757221a29..db498650a 100644 --- a/wagtail/wagtailcore/tests.py +++ b/wagtail/wagtailcore/tests.py @@ -162,6 +162,47 @@ class TestServeView(TestCase): self.assertContains(response, 'Christmas') +class TestStaticSitePaths(TestCase): + def setUp(self): + self.root_page = Page.objects.get(id=1) + + # For simple tests + self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home")) + self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about")) + self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact")) + + # For custom tests + self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events")) + for i in range(20): + self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i))) + + def test_local_static_site_paths(self): + paths = list(self.about_page.get_static_site_paths()) + + self.assertEqual(paths, ['/']) + + def test_child_static_site_paths(self): + paths = list(self.home_page.get_static_site_paths()) + + self.assertEqual(paths, ['/', '/about/', '/contact/']) + + def test_custom_static_site_paths(self): + paths = list(self.event_index.get_static_site_paths()) + + # Event index path + expected_paths = ['/'] + + # One path for each page of results + expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)]) + + # One path for each event page + expected_paths.extend(['/event' + str(i) + '/' for i in range(20)]) + + paths.sort() + expected_paths.sort() + self.assertEqual(paths, expected_paths) + + class TestPageUrlTags(TestCase): fixtures = ['test.json'] From 27333b1ea1da28ca52c3a82b0999190e4fcb303c Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 20 May 2014 13:22:19 +0100 Subject: [PATCH 58/84] updated formbulider fields with identifyable class names, although not output yet --- wagtail/wagtailforms/models.py | 6 +++--- wagtail/wagtailforms/static/wagtailforms/js/page-editor.js | 3 +++ wagtail/wagtailforms/wagtail_hooks.py | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 wagtail/wagtailforms/static/wagtailforms/js/page-editor.js diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index e51eba225..cf794c86a 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -79,10 +79,10 @@ class AbstractFormField(Orderable): panels = [ FieldPanel('label'), - FieldPanel('field_type'), + FieldPanel('field_type', classname="formbuilder-type"), FieldPanel('required'), - FieldPanel('choices'), - FieldPanel('default_value'), + FieldPanel('choices', classname="formbuilder-choices"), + FieldPanel('default_value', classname="formbuilder-default"), FieldPanel('help_text'), ] diff --git a/wagtail/wagtailforms/static/wagtailforms/js/page-editor.js b/wagtail/wagtailforms/static/wagtailforms/js/page-editor.js new file mode 100644 index 000000000..8e7398133 --- /dev/null +++ b/wagtail/wagtailforms/static/wagtailforms/js/page-editor.js @@ -0,0 +1,3 @@ +$(function(){ + +}); \ No newline at end of file diff --git a/wagtail/wagtailforms/wagtail_hooks.py b/wagtail/wagtailforms/wagtail_hooks.py index b56f633dc..277fc6e73 100644 --- a/wagtail/wagtailforms/wagtail_hooks.py +++ b/wagtail/wagtailforms/wagtail_hooks.py @@ -1,4 +1,5 @@ from django.core import urlresolvers +from django.conf import settings from django.conf.urls import include, url from django.utils.translation import ugettext_lazy as _ @@ -8,7 +9,6 @@ from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailforms import urls from wagtail.wagtailforms.models import get_forms_for_user - def register_admin_urls(): return [ url(r'^forms/', include(urls)), @@ -22,3 +22,7 @@ def construct_main_menu(request, menu_items): MenuItem(_('Forms'), urlresolvers.reverse('wagtailforms_index'), classnames='icon icon-form', order=700) ) hooks.register('construct_main_menu', construct_main_menu) + +def editor_js(): + return """""" % settings.STATIC_URL +hooks.register('insert_editor_js', editor_js) From 7b98664cd0a62d247c95faa91666d4a9d1558337 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 20 May 2014 13:22:32 +0100 Subject: [PATCH 59/84] unrelated fix for IE --- .../static/wagtailadmin/scss/components/header.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss index 39840abe4..849420ae0 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/header.scss @@ -115,10 +115,6 @@ header{ padding-top:1.5em; padding-bottom:1.5em; - &.nice-padding{ - @include nice-padding(); - } - .left{ float:left; margin-right:0; From 9d65cf8f12c6374d417cadbb8a3c0fef82340ef4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 20 May 2014 13:48:02 +0100 Subject: [PATCH 60/84] Added changelog entry --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a0024b6a0..7e5cb1be1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,6 +5,7 @@ Changelog ~~~~~~~~~~~~~~~~ * Added toolbar to allow logged-in users to add and edit pages from the site front-end * Support for alternative image processing backends such as Wand, via the WAGTAILIMAGES_BACKENDS setting + * Added support for generating static sites using django-medusa * Added custom Query set for Pages with some handy methods for querying pages * Editor's guide documentation * Editor interface now outputs form media CSS / JS, to support custom widgets with assets From 2aaec7c332c6b52b95389c786bade412101c52dd Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Tue, 20 May 2014 14:38:22 +0100 Subject: [PATCH 61/84] reordered fields --- wagtail/wagtailforms/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index cf794c86a..d04279c94 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -79,11 +79,11 @@ class AbstractFormField(Orderable): panels = [ FieldPanel('label'), - FieldPanel('field_type', classname="formbuilder-type"), + FieldPanel('help_text'), FieldPanel('required'), + FieldPanel('field_type', classname="formbuilder-type"), FieldPanel('choices', classname="formbuilder-choices"), FieldPanel('default_value', classname="formbuilder-default"), - FieldPanel('help_text'), ] class Meta: From 3e152e0386e6329eef01014fe48857395aa7de89 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 20 May 2014 14:53:02 +0100 Subject: [PATCH 62/84] fix unit tests to handle form_pages being a paginator.Page instance --- wagtail/wagtailforms/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py index a26e9d4de..1fe1c082a 100644 --- a/wagtail/wagtailforms/tests.py +++ b/wagtail/wagtailforms/tests.py @@ -38,14 +38,14 @@ class TestFormsBackend(TestCase): self.client.login(username='eventeditor', password='password') response = self.client.get('/admin/forms/') - self.assertFalse(response.context['form_pages'].filter(id=form_page.id).exists()) + self.assertFalse(form_page in response.context['form_pages']) def test_can_see_forms_with_permission(self): form_page = Page.objects.get(url_path='/home/contact-us/') self.client.login(username='siteeditor', password='password') response = self.client.get('/admin/forms/') - self.assertTrue(response.context['form_pages'].filter(id=form_page.id).exists()) + self.assertTrue(form_page in response.context['form_pages']) def test_can_get_submissions(self): form_page = Page.objects.get(url_path='/home/contact-us/') From d096b14132fb16d83b4477e127263ec861f6c90f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 20 May 2014 16:16:26 +0100 Subject: [PATCH 63/84] Remove label_suffix from form builder forms by default, but make that preference overrideable in the FormPage class --- wagtail/wagtailforms/forms.py | 8 +++++++- wagtail/wagtailforms/models.py | 8 ++++++-- wagtail/wagtailforms/tests.py | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailforms/forms.py b/wagtail/wagtailforms/forms.py index da46f7486..f17023a77 100644 --- a/wagtail/wagtailforms/forms.py +++ b/wagtail/wagtailforms/forms.py @@ -2,6 +2,12 @@ import django.forms from django.utils.datastructures import SortedDict +class BaseForm(django.forms.Form): + def __init__(self, *args, **kwargs): + kwargs.setdefault('label_suffix', '') + return super(BaseForm, self).__init__(*args, **kwargs) + + class FormBuilder(): formfields = SortedDict() @@ -67,7 +73,7 @@ class FormBuilder(): return django.forms.BooleanField(**options) def get_form_class(self): - return type('WagtailForm', (django.forms.Form,), self.formfields) + return type('WagtailForm', (BaseForm,), self.formfields) class SelectDateForm(django.forms.Form): diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index cf794c86a..bb275c2d1 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -140,12 +140,16 @@ class AbstractForm(Page): class Meta: abstract = True + def get_form_parameters(self): + return {} + def serve(self, request): fb = self.form_builder(self.form_fields.all()) form_class = fb.get_form_class() + form_params = self.get_form_parameters() if request.method == 'POST': - self.form = form_class(request.POST) + self.form = form_class(request.POST, **form_params) if self.form.is_valid(): # remove csrf_token from form.data @@ -170,7 +174,7 @@ class AbstractForm(Page): 'self': self, }) else: - self.form = form_class() + self.form = form_class(**form_params) return render(request, self.template, { 'self': self, diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py index 1fe1c082a..8ec2ee5c3 100644 --- a/wagtail/wagtailforms/tests.py +++ b/wagtail/wagtailforms/tests.py @@ -8,7 +8,7 @@ class TestFormSubmission(TestCase): def test_get_form(self): response = self.client.get('/contact-us/') - self.assertContains(response, "Your email") + self.assertContains(response, """""") self.assertNotContains(response, "Thank you for your feedback") def test_post_invalid_form(self): From c349f06d6adcd363d254e9def094ce8043aad18d Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Tue, 20 May 2014 17:22:19 +0100 Subject: [PATCH 64/84] Missing underline for new static generation docs --- docs/static_site_generation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static_site_generation.rst b/docs/static_site_generation.rst index 66bd23ff5..fe4b504ea 100644 --- a/docs/static_site_generation.rst +++ b/docs/static_site_generation.rst @@ -26,7 +26,7 @@ Then add 'django_medusa' and 'wagtail.contrib.wagtailmedusa' to INSTALLED_APPS: Replacing GET parameters with custom routing -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pages which require GET parameters (eg, pagination) don't generate suitable filenames for generated HTML files so they need to be changed to use custom routing instead. From 431931b6a9551d02b642b5abb54f04fd315b6e0e Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Tue, 20 May 2014 17:24:16 +0100 Subject: [PATCH 65/84] Include static site generation docs in doc index --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index dbfe91f33..5b2b62bc4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,6 +13,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support wagtail_search deploying performance + static_site_generation contributing support roadmap From e0438a81170dc79f55c3f2c431bdd51f31a0fdd4 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 21 May 2014 09:22:06 +0100 Subject: [PATCH 66/84] updates to allow better formatting of fields --- .../wagtailadmin/scss/components/forms.scss | 41 ++++++++++++++++++- .../edit_handlers/field_panel_field.html | 1 + .../wagtailadmin/shared/field_as_li.html | 1 + .../templates/wagtailadmin/skeleton.html | 9 ++-- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index 7eeb828f6..29c44b4f3 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -67,6 +67,41 @@ input, textarea, select, .richtext, .tagit{ } } +/* select boxes */ +.typed_choice_field .input{ + position:relative; + + select{ + outline:none; + } + + &:after{ + @include border-radius(0 6px 6px 0); + z-index:0; + position:absolute; + right:1px; + top:1px; + height:95%; + width:1.5em; + font-family:wagtail; + content:"q"; + border:1px solid $color-input-border; + border-width:0 0 0 1px; + text-align:center; + line-height:1.4em; + font-size:3em; + pointer-events:none; + color:$color-grey-3; + background-color:$color-fieldset-hover; + margin:0px 1px 0 0; + } + + .ie &:after{ + display:none; + } +} + +/* radio and check boxes */ input[type=radio], input[type=checkbox]{ @include border-radius(0); cursor:pointer; @@ -415,7 +450,7 @@ li.focused > .help{ .fields li{ padding-top:0.5em; - padding-bottom:1em; + padding-bottom:1.2em; } .field-content .input li{ @@ -637,6 +672,10 @@ input[type=submit], input[type=reset], input[type=button], .button, button{ .file_field &{ padding-top:0; } + + .boolean_field &{ + padding-bottom:0; + } } input[type=submit], input[type=reset], input[type=button], .button, button{ diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/field_panel_field.html b/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/field_panel_field.html index 49ef50e9a..472cae0b2 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/field_panel_field.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/field_panel_field.html @@ -5,6 +5,7 @@ {% block form_field %} {{ field }} {% endblock %} +
      {% if field.help_text %}

      {{ field.help_text }}

      diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html index 89f2d6fa7..678dd67fd 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/field_as_li.html @@ -7,6 +7,7 @@ {% block form_field %} {{ field }} {% endblock %} +
{% if field.help_text %}

{{ field.help_text }}

diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html b/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html index bc9475695..ac5038056 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html @@ -1,9 +1,10 @@ {% load compress %} - - - - + + + + + From d6910e3db841bc3565d82765760811a1187090bf Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 21 May 2014 13:35:07 +0100 Subject: [PATCH 67/84] Rewrite get_form_types() to not require fiddling with metaclasses to populate FORM_MODEL_CLASSES --- wagtail/wagtailforms/models.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index bb275c2d1..d70187dfc 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,5 +1,3 @@ -from django.conf import settings -from django.contrib.contenttypes.models import ContentType from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ @@ -9,7 +7,7 @@ from unidecode import unidecode import json import re -from wagtail.wagtailcore.models import PageBase, Page, Orderable, UserPagePermissionsProxy +from wagtail.wagtailcore.models import PageBase, Page, Orderable, UserPagePermissionsProxy, get_page_types from wagtail.wagtailadmin.edit_handlers import FieldPanel from wagtail.wagtailforms.backends.email import EmailFormProcessor @@ -91,15 +89,14 @@ class AbstractFormField(Orderable): ordering = ['sort_order'] -FORM_MODEL_CLASSES = [] -_FORM_CONTENT_TYPES = [] - +_FORM_CONTENT_TYPES = None def get_form_types(): global _FORM_CONTENT_TYPES - if len(_FORM_CONTENT_TYPES) != len(FORM_MODEL_CLASSES): + if _FORM_CONTENT_TYPES is None: _FORM_CONTENT_TYPES = [ - ContentType.objects.get_for_model(cls) for cls in FORM_MODEL_CLASSES + ct for ct in get_page_types() + if issubclass(ct.model_class(), AbstractForm) ] return _FORM_CONTENT_TYPES @@ -116,8 +113,6 @@ class FormBase(PageBase): super(FormBase, cls).__init__(name, bases, dct) if not cls.is_abstract: - # register this type in the list of page content types - FORM_MODEL_CLASSES.append(cls) # Check if form_processing_backend is ok if hasattr(cls, 'form_processing_backend'): cls.form_processing_backend.validate_usage(cls) From 1f8e8a062bb1ea86c4ec73f0ac6e931cd4ab23ef Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 21 May 2014 13:44:21 +0100 Subject: [PATCH 68/84] Removed stray print statement --- wagtail/wagtailadmin/views/pages.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index a827b6cab..308f6fdd4 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -82,8 +82,6 @@ def content_type_use(request, content_type_app_name, content_type_model_name): except EmptyPage: pages = paginator.page(paginator.num_pages) - print page_class - return render(request, 'wagtailadmin/pages/content_type_use.html', { 'pages': pages, 'app_name': content_type_app_name, From ddcd750b7a1f3a806d126516cab71ebd0c6f3a2c Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 21 May 2014 13:51:27 +0100 Subject: [PATCH 69/84] Remove validate_usage from the wagtailforms.backends API. Since non-expert users are likely to be inheriting from AbstractEmailForm rather than hooking up backends to their own custom form models, it is of marginal usefulness and not worth the added complexity of the metaclass setup --- wagtail/wagtailforms/backends/base.py | 5 ----- wagtail/wagtailforms/backends/email.py | 10 ---------- wagtail/wagtailforms/models.py | 15 +-------------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/wagtail/wagtailforms/backends/base.py b/wagtail/wagtailforms/backends/base.py index 45d0343ae..e3c735ec9 100644 --- a/wagtail/wagtailforms/backends/base.py +++ b/wagtail/wagtailforms/backends/base.py @@ -1,11 +1,6 @@ -from django.core.exceptions import ImproperlyConfigured - class BaseFormProcessor(object): def __init__(self): pass - def validate_usage(page): - return True - def process(self, page, form): return NotImplemented \ No newline at end of file diff --git a/wagtail/wagtailforms/backends/email.py b/wagtail/wagtailforms/backends/email.py index 52b296768..f18da1a66 100644 --- a/wagtail/wagtailforms/backends/email.py +++ b/wagtail/wagtailforms/backends/email.py @@ -1,4 +1,3 @@ -from django.core.exceptions import ImproperlyConfigured from .base import BaseFormProcessor from wagtail.wagtailadmin import tasks @@ -7,15 +6,6 @@ class EmailFormProcessor(BaseFormProcessor): def __init__(self): pass - @staticmethod - def validate_usage(page): - try: - page._meta.get_field('subject') - page._meta.get_field('to_address') - page._meta.get_field('from_address') - except: - raise ImproperlyConfigured("To use the EmailFormProcessor your Page must define the fields: subject, to_address and from_address.") - def process(self, page, form): if page.to_address: content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()]) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index d70187dfc..a963db4c6 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -7,7 +7,7 @@ from unidecode import unidecode import json import re -from wagtail.wagtailcore.models import PageBase, Page, Orderable, UserPagePermissionsProxy, get_page_types +from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types from wagtail.wagtailadmin.edit_handlers import FieldPanel from wagtail.wagtailforms.backends.email import EmailFormProcessor @@ -107,22 +107,9 @@ def get_forms_for_user(user): return editable_pages.filter(content_type__in=get_form_types()) -class FormBase(PageBase): - """Metaclass for Forms""" - def __init__(cls, name, bases, dct): - super(FormBase, cls).__init__(name, bases, dct) - - if not cls.is_abstract: - # Check if form_processing_backend is ok - if hasattr(cls, 'form_processing_backend'): - cls.form_processing_backend.validate_usage(cls) - - class AbstractForm(Page): """A Form Page. Pages implementing a form should inhert from it""" - __metaclass__ = FormBase - form_builder = FormBuilder is_abstract = True # Don't display me in "Add" From 7445335f040d7845c68dc950f43de966eba6921d Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 21 May 2014 13:58:08 +0100 Subject: [PATCH 70/84] no point in making form a property of the form page, since its scope is entirely within the 'serve' method --- wagtail/wagtailforms/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index a963db4c6..c1db1e4bd 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -131,12 +131,12 @@ class AbstractForm(Page): form_params = self.get_form_parameters() if request.method == 'POST': - self.form = form_class(request.POST, **form_params) + form = form_class(request.POST, **form_params) - if self.form.is_valid(): + if form.is_valid(): # remove csrf_token from form.data form_data = dict( - i for i in self.form.data.items() + i for i in form.data.items() if i[0] != 'csrfmiddlewaretoken' ) @@ -148,7 +148,7 @@ class AbstractForm(Page): # If we have a form_processing_backend call its process method if hasattr(self, 'form_processing_backend'): form_processor = self.form_processing_backend() - form_processor.process(self, self.form) + form_processor.process(self, form) # render the landing_page # TODO: It is much better to redirect to it @@ -156,11 +156,11 @@ class AbstractForm(Page): 'self': self, }) else: - self.form = form_class(**form_params) + form = form_class(**form_params) return render(request, self.template, { 'self': self, - 'form': self.form, + 'form': form, }) def get_page_modes(self): From 07fdcc021669951259b2e187736506bc9cf77dfa Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 21 May 2014 14:17:47 +0100 Subject: [PATCH 71/84] Replace wagtailforms.backends model with a simple inheritance model --- wagtail/wagtailforms/backends/__init__.py | 0 wagtail/wagtailforms/backends/base.py | 6 ---- wagtail/wagtailforms/backends/email.py | 12 -------- wagtail/wagtailforms/models.py | 35 ++++++++++++++--------- 4 files changed, 22 insertions(+), 31 deletions(-) delete mode 100644 wagtail/wagtailforms/backends/__init__.py delete mode 100644 wagtail/wagtailforms/backends/base.py delete mode 100644 wagtail/wagtailforms/backends/email.py diff --git a/wagtail/wagtailforms/backends/__init__.py b/wagtail/wagtailforms/backends/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/wagtail/wagtailforms/backends/base.py b/wagtail/wagtailforms/backends/base.py deleted file mode 100644 index e3c735ec9..000000000 --- a/wagtail/wagtailforms/backends/base.py +++ /dev/null @@ -1,6 +0,0 @@ -class BaseFormProcessor(object): - def __init__(self): - pass - - def process(self, page, form): - return NotImplemented \ No newline at end of file diff --git a/wagtail/wagtailforms/backends/email.py b/wagtail/wagtailforms/backends/email.py deleted file mode 100644 index f18da1a66..000000000 --- a/wagtail/wagtailforms/backends/email.py +++ /dev/null @@ -1,12 +0,0 @@ -from .base import BaseFormProcessor -from wagtail.wagtailadmin import tasks - - -class EmailFormProcessor(BaseFormProcessor): - def __init__(self): - pass - - def process(self, page, form): - if page.to_address: - content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()]) - tasks.send_email_task.delay(page.subject, content, [page.to_address], page.from_address,) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index c1db1e4bd..31c86c22c 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -9,7 +9,7 @@ import re from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types from wagtail.wagtailadmin.edit_handlers import FieldPanel -from wagtail.wagtailforms.backends.email import EmailFormProcessor +from wagtail.wagtailadmin import tasks from .forms import FormBuilder @@ -125,6 +125,18 @@ class AbstractForm(Page): def get_form_parameters(self): return {} + def process_form_submission(self, form): + # remove csrf_token from form.data + form_data = dict( + i for i in form.data.items() + if i[0] != 'csrfmiddlewaretoken' + ) + + FormSubmission.objects.create( + form_data=json.dumps(form_data), + page=self, + ) + def serve(self, request): fb = self.form_builder(self.form_fields.all()) form_class = fb.get_form_class() @@ -134,17 +146,7 @@ class AbstractForm(Page): form = form_class(request.POST, **form_params) if form.is_valid(): - # remove csrf_token from form.data - form_data = dict( - i for i in form.data.items() - if i[0] != 'csrfmiddlewaretoken' - ) - - FormSubmission.objects.create( - form_data=json.dumps(form_data), - page=self, - ) - + self.process_form_submission(form) # If we have a form_processing_backend call its process method if hasattr(self, 'form_processing_backend'): form_processor = self.form_processing_backend() @@ -181,11 +183,18 @@ class AbstractForm(Page): class AbstractEmailForm(AbstractForm): """A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it""" is_abstract = True # Don't display me in "Add" - form_processing_backend = EmailFormProcessor to_address = models.CharField(max_length=255, blank=True, help_text=_("Optional - form submissions will be emailed to this address")) from_address = models.CharField(max_length=255, blank=True) subject = models.CharField(max_length=255, blank=True) + def process_form_submission(self, form): + super(AbstractEmailForm, self).process_form_submission(form) + + if self.to_address: + content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()]) + tasks.send_email_task.delay(self.subject, content, [self.to_address], self.from_address,) + + class Meta: abstract = True From 0ad412c343be6fba5c81e7f014bc4355afed5aa6 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 21 May 2014 17:49:04 +0100 Subject: [PATCH 72/84] Although the ubuntu/debian install scripts are not meant to produce a generic, secure production environment, nevertheless make Elasticsearch to only listen on localhost rather than the default 0.0.0.0 address. --- scripts/install/debian.sh | 5 +++-- scripts/install/ubuntu.sh | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/install/debian.sh b/scripts/install/debian.sh index 93f513d16..d29d5acdc 100644 --- a/scripts/install/debian.sh +++ b/scripts/install/debian.sh @@ -1,5 +1,5 @@ -# Production-configured Wagtail installation -# (secure services/account for full production use). +# Production-configured Wagtail installation. +# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE! # Tested on Debian 7.0. # Tom Dyson and Neal Todd @@ -42,6 +42,7 @@ aptitude -y install openjdk-7-jre-headless curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb dpkg -i elasticsearch-1.0.0.deb rm elasticsearch-1.0.0.deb +perl -pi -e"s/# network.host: 192.168.0.1/network.host: 127.0.0.1/" /etc/elasticsearch/elasticsearch.yml update-rc.d elasticsearch defaults 95 10 service elasticsearch start diff --git a/scripts/install/ubuntu.sh b/scripts/install/ubuntu.sh index 9f60e2e8e..c713cefa9 100644 --- a/scripts/install/ubuntu.sh +++ b/scripts/install/ubuntu.sh @@ -1,5 +1,5 @@ -# Production-configured Wagtail installation -# (secure services/account for full production use). +# Production-configured Wagtail installation. +# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE! # Tested on Ubuntu 13.04 and 13.10. # Tom Dyson and Neal Todd @@ -40,6 +40,7 @@ aptitude -y install openjdk-7-jre-headless curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb dpkg -i elasticsearch-1.0.0.deb rm elasticsearch-1.0.0.deb +perl -pi -e"s/# network.host: 192.168.0.1/network.host: 127.0.0.1/" /etc/elasticsearch/elasticsearch.yml update-rc.d elasticsearch defaults 95 10 service elasticsearch start From 18df9e094cc37c5b274cda91df9117300c83a028 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 14:26:09 +0100 Subject: [PATCH 73/84] tweaked font size of userbar icons, it got reduced accidentally during previous changes accidentally --- wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss index 84b99b537..af9e20195 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss @@ -87,11 +87,13 @@ li, .home{ .action{ @include transition(background-color 0.2s ease, color 0.2s ease); background-color:$color-teal; - color:white; + color:$color-teal; &:before{ margin-right:0.4em; vertical-align:middle; + font-size:1.7em; + color:white; } &:hover{ From b3c34d96cb44a2082c04bc6022540c3676bb0472 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 22 May 2014 15:11:30 +0100 Subject: [PATCH 74/84] Add form builder documentation --- docs/form_builder.rst | 69 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + docs/roadmap.rst | 4 +-- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 docs/form_builder.rst diff --git a/docs/form_builder.rst b/docs/form_builder.rst new file mode 100644 index 000000000..9aa220e19 --- /dev/null +++ b/docs/form_builder.rst @@ -0,0 +1,69 @@ +Form builder +============ + +The `wagtailforms` module allows you to set up single-page forms, such as a 'Contact us' form, as pages of a Wagtail site. It provides a set of base models that site implementors can extend to create their own 'Form' page type with their own site-specific templates. Once a page type has been set up in this way, editors can build forms within the usual page editor, consisting of any number of fields. Form submissions are stored for later retrieval through a new 'Forms' section within the Wagtail admin interface; in addition, they can be optionally e-mailed to an address specified by the editor. + + +Usage +~~~~~ + +Add 'wagtail.wagtailforms' to your INSTALLED_APPS: + +.. code:: python + + INSTALLED_APPS = [ + ... + 'wagtail.wagtailforms', + ] + +Within the models.py of one of your apps, create a model that extends wagtailforms.models.AbstractEmailForm: + + +.. code:: python + + from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField + + class FormField(AbstractFormField): + page = ParentalKey('FormPage', related_name='form_fields') + + class FormPage(AbstractEmailForm): + intro = RichTextField(blank=True) + thank_you_text = RichTextField(blank=True) + + FormPage.content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('intro', classname="full"), + InlinePanel(FormPage, 'form_fields', label="Form fields"), + FieldPanel('thank_you_text', classname="full"), + MultiFieldPanel([ + FieldPanel('to_address', classname="full"), + FieldPanel('from_address', classname="full"), + FieldPanel('subject', classname="full"), + ], "Email") + ] + +AbstractEmailForm defines the fields 'to_address', 'from_address' and 'subject', and expects form_fields to be defined. Any additional fields are treated as ordinary page content - note that FormPage is responsible for serving both the form page itself and the landing page after submission, so the model definition should include all necessary content fields for both of those views. + +If you do not want your form page type to offer form-to-email functionality, you can inherit from AbstractForm instead of AbstractEmailForm, and omit the 'to_address', 'from_address' and 'subject' fields from the content_panels definition. + +You now need to create two templates named form_page.html and form_page_landing.html (where 'form_page' is the underscore-formatted version of the class name). form_page.html differs from a standard Wagtail template in that it is passed a variable 'form', containing a Django form object, in addition to the usual 'self' variable. A very basic template for the form would thus be: + +.. code:: html + + {% load pageurl rich_text %} + + + {{ self.title }} + + +

{{ self.title }}

+ {{ self.intro|richtext }} +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + + +form_page_landing.html is a regular Wagtail template, displayed after the user makes a successful form submission. diff --git a/docs/index.rst b/docs/index.rst index 5b2b62bc4..bdcd6a517 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,6 +13,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support wagtail_search deploying performance + form_builder static_site_generation contributing support diff --git a/docs/roadmap.rst b/docs/roadmap.rst index c94692fac..bdc711470 100644 --- a/docs/roadmap.rst +++ b/docs/roadmap.rst @@ -10,7 +10,7 @@ https://raw.github.com/torchbox/wagtail/master/CHANGELOG.txt In summary: - * February 2013: Reduced dependencies, basic documentation, translations, tests + * February 2014: Reduced dependencies, basic documentation, translations, tests What's next ~~~~~~~~~~~ @@ -19,12 +19,10 @@ The `issue list `_ gives a detailed * More and better tests (>80% `coverage `_) * Better documentation: simple setup guides for all levels of user, a manual for editors and administrators, in-depth intstructions for Django developers. - * A form builder * Move site section permissions out of Django admin * Improved image handling: intelligent cropping, animated gif support * Block-level editing UI (see `Sir Trevor `_) * Site settings management - * Edit bird for logged-in visitors * Support for an HTML content type * Simple inline stats From c5f8ddfa7f35adc939050675d895b56162973a3c Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 22 May 2014 15:14:47 +0100 Subject: [PATCH 75/84] Add changelog entry for wagtailforms --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index bcb58c15b..d8417db22 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -7,6 +7,7 @@ Changelog * Support for alternative image processing backends such as Wand, via the WAGTAILIMAGES_BACKENDS setting * Added support for generating static sites using django-medusa * Added custom Query set for Pages with some handy methods for querying pages + * Added 'wagtailforms' module for creating form pages on a site, and handling form submissions * Editor's guide documentation * Editor interface now outputs form media CSS / JS, to support custom widgets with assets * Migrations and user management now correctly handle custom AUTH_USER_MODEL settings From 3d23bc2b255b6b070da81d1133f61403a635a198 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 22 May 2014 16:33:15 +0100 Subject: [PATCH 76/84] user_can_edit_snippets should return False for administrators if no snippet models exist, and thus hide the Snippets menu option - fixes #204 --- CHANGELOG.txt | 1 + wagtail/wagtailsnippets/permissions.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d8417db22..8b69d346a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -27,6 +27,7 @@ Changelog * Fix: Filter objects are cached to avoid a database hit every time an {% image %} tag is compiled * Fix: Moving or changing a site root page no longer causes URLs for subpages to change to 'None' * Fix: Eliminated raw SQL queries from wagtailcore / wagtailadmin, to ensure cross-database compatibility + * Fix: Snippets menu item is hidden for administrators if no snippet types are defined 0.2 (11.03.2014) ~~~~~~~~~~~~~~~~ diff --git a/wagtail/wagtailsnippets/permissions.py b/wagtail/wagtailsnippets/permissions.py index 57441f356..5a730909b 100644 --- a/wagtail/wagtailsnippets/permissions.py +++ b/wagtail/wagtailsnippets/permissions.py @@ -19,10 +19,12 @@ def user_can_edit_snippet_type(user, content_type): def user_can_edit_snippets(user): """ true if user has any permission related to any content type registered as a snippet type """ + snippet_content_types = get_snippet_content_types() if user.is_active and user.is_superuser: - return True + # admin can edit snippets iff any snippet types exist + return bool(snippet_content_types) - permissions = Permission.objects.filter(content_type__in=get_snippet_content_types()).select_related('content_type') + permissions = Permission.objects.filter(content_type__in=snippet_content_types).select_related('content_type') for perm in permissions: permission_name = "%s.%s" % (perm.content_type.app_label, perm.codename) if user.has_perm(permission_name): From b9c7c871dd8d55593456d99494cc286d322e68dd Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:19:40 +0100 Subject: [PATCH 77/84] adding incomplete front end developer documentation --- docs/building_your_site/djangodevelopers.rst | 3 + .../building_your_site/frontenddevelopers.rst | 117 ++++++++++++++++++ .../index.rst} | 11 +- docs/editor_manual/index.rst | 5 +- docs/index.rst | 2 +- 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 docs/building_your_site/djangodevelopers.rst create mode 100644 docs/building_your_site/frontenddevelopers.rst rename docs/{building_your_site.rst => building_your_site/index.rst} (59%) diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst new file mode 100644 index 000000000..62f37c12b --- /dev/null +++ b/docs/building_your_site/djangodevelopers.rst @@ -0,0 +1,3 @@ +For Django developers +================== + diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst new file mode 100644 index 000000000..f6b676d1f --- /dev/null +++ b/docs/building_your_site/frontenddevelopers.rst @@ -0,0 +1,117 @@ +For Front End developers +======================== + +.. note:: + This documentation is currently being written. + +======================== +Overview +======================== + +Wagtail uses Django's templating language. For developers new to Django, start with Django's own template documentation: +https://docs.djangoproject.com/en/dev/topics/templates/ + +Python programmers new to Django/Wagtail may prefer more technical documentation: +https://docs.djangoproject.com/en/dev/ref/templates/api/ + +======================== +Page content and variables +======================== + + +======================== +Static files (css, js, images) +======================== + + + +Images +~~~~~~~~~~ + +Images uploaded to Wagtail go into the image library and from there are added to pages via the :doc:`page editor interface `. + +Unlike other CMS, adding images to a page does not involve choosing a "version" of the image to use. Wagtail has no predefined image "formats" or "sizes". Instead the template developer defines image manipulation to occur *on the fly* when the image is requested, via a special syntax within the template. + +Images from the library **must** be requested using this syntax, but images in your codebase can be added via conventional means e.g ``img`` tags. Only images from the library can be manipulated on the fly. + +Read more about the image manipulation syntax here :ref:`Images tag `. + + +======================== +Template tags & filters +======================== + +In addition to Django's standard tags and filters, Wagtail provides some of it's own, which can be ``load``-ed `as you would any other `_ + +.. _image-tag: +Images (tag) +~~~~~~~~~~~~ + +The syntax for displaying/manipulating an image is thus:: + + {% image [image] [method]-[dimension(s)] %} + +The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[height]`` or ``[width]x[height]``) to refine that algorithm. + +Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``. A hyphen between ``width`` and ``dimensions`` is mandatory. + +The available ``method`` s are: + +.. glossary:: + ``max`` + (takes two dimensions) + + Fit **within** the given dimensions. + + The longest edge will be reduced to the equivalent dimension size defined. e.g A portrait image of width 1000, height 2000, treated with the ``max`` dimensions ``1000x500`` (landscape) would result in the image shrunk so the *height* was 500 pixels and the width 250. + + ``min`` + (takes two dimensions) + + **Cover** the given dimensions. + + This may result in an image slightly **larger** than the dimensions you specify. e.g A square image of width 2000, height 2000, treated with the ``min`` dimensions ``500x200`` (landscape) would have it's height and width changed to 500, i.e matching the width required, but greater than the height. + + ``width`` + (takes one dimension) + + Reduces the width of the image to the dimension specified. + + ``height`` + (takes one dimension) + + Resize the height of the image to the dimension specified.. + + ``fill`` + (takes two dimensions) + + Resize and **crop** to fill the **exact** dimensions. + + This can be particularly useful for websites requiring square thumbnails of arbitrary images. e.g A landscape image of width 2000, height 1000, treated with ``fill`` dimensions ``200x200`` would have it's height reduced to 200, then it's width (ordinarily 400) cropped to 200. + + **The crop always aligns on the centre of the image.** + +.. Note:: + Wagtail *does not allow deforming or stretching images*. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions. + + +Rich text (filter) +~~~~~~~~~~~~~~~~~~ + + +Internal links (tag) +~~~~~~~~~~~~~~~~~~~~ + + +Static files (tag) +~~~~~~~~~~~~~~ + + +Misc +~~~~~~~~~~ + + +======================== +Wagtail User Bar +======================== + diff --git a/docs/building_your_site.rst b/docs/building_your_site/index.rst similarity index 59% rename from docs/building_your_site.rst rename to docs/building_your_site/index.rst index b506f67f2..fb336e18b 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site/index.rst @@ -1,6 +1,15 @@ Building your site ================== +.. note:: + Documentation currently incomplete and in draft status + Serafeim Papastefanos has written a comprehensive tutorial on creating a site from scratch in Wagtail; for the time being, this is our recommended resource: -`spapas.github.io/2014/02/13/wagtail-tutorial/ `_ \ No newline at end of file +`spapas.github.io/2014/02/13/wagtail-tutorial/ `_ + +.. toctree:: + :maxdepth: 3 + + djangodevelopers + frontenddevelopers diff --git a/docs/editor_manual/index.rst b/docs/editor_manual/index.rst index 4562844e7..b7b5c84f7 100644 --- a/docs/editor_manual/index.rst +++ b/docs/editor_manual/index.rst @@ -1,9 +1,10 @@ Using Wagtail: an Editor's guide ================================ -This section of the documentation is written for the users of a Wagtail-powered site. That is, the content editors, moderators and administrators who will be running things on a day-to-day basis. +.. note:: + Documentation currently incomplete and in draft status -**NOTE:** This section of the documentation is currently in draft status. +This section of the documentation is written for the users of a Wagtail-powered site. That is, the content editors, moderators and administrators who will be running things on a day-to-day basis. .. toctree:: :maxdepth: 3 diff --git a/docs/index.rst b/docs/index.rst index dbfe91f33..40510c7da 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support :maxdepth: 3 gettingstarted - building_your_site + building_your_site/index wagtail_search deploying performance From 3a42a0eb465a66f0f0125fc920fd4ee7f5c59fa3 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:27:01 +0100 Subject: [PATCH 78/84] adding incomplete front end developer documentation --- docs/building_your_site/frontenddevelopers.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index f6b676d1f..a39e6cd0c 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -95,6 +95,9 @@ The available ``method`` s are: Wagtail *does not allow deforming or stretching images*. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions. +To request the "original" version of an image, it is suggested you use the lack of upscalling support by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown, at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. + + Rich text (filter) ~~~~~~~~~~~~~~~~~~ From 0c92a0009f3014e15e3488f735f0744d60d474ca Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:32:26 +0100 Subject: [PATCH 79/84] updated note about origianl version of image --- docs/building_your_site/frontenddevelopers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index a39e6cd0c..276c525ae 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -95,7 +95,7 @@ The available ``method`` s are: Wagtail *does not allow deforming or stretching images*. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions. -To request the "original" version of an image, it is suggested you use the lack of upscalling support by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown, at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. +To request the "original" version of an image, it is suggested you rely on the lack of upscalling support by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown, at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. Rich text (filter) From 8bacb807ccd75e89a11cfc4e955b8cc2ea61d75f Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:38:43 +0100 Subject: [PATCH 80/84] added better examples --- docs/building_your_site/frontenddevelopers.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 276c525ae..f86f09459 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -51,6 +51,13 @@ The syntax for displaying/manipulating an image is thus:: {% image [image] [method]-[dimension(s)] %} +For example:: + + {% image self.photo width-400 %} + + + {% image self.photo fill-80x80 %} + The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[height]`` or ``[width]x[height]``) to refine that algorithm. Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``. A hyphen between ``width`` and ``dimensions`` is mandatory. From 4b07378b89aad825f944035631e90fdc5d4ffa79 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:40:32 +0100 Subject: [PATCH 81/84] corrected syntax --- docs/building_your_site/frontenddevelopers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index f86f09459..7f7a438c1 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -58,7 +58,7 @@ For example:: {% image self.photo fill-80x80 %} -The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[height]`` or ``[width]x[height]``) to refine that algorithm. +The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm. Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``. A hyphen between ``width`` and ``dimensions`` is mandatory. From 5f4cc1fa914590204b4fcfe8af27997e79c516e6 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 22 May 2014 17:47:32 +0100 Subject: [PATCH 82/84] corrected mistake --- docs/building_your_site/frontenddevelopers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 7f7a438c1..1c2268506 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -60,7 +60,7 @@ For example:: The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm. -Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``. A hyphen between ``width`` and ``dimensions`` is mandatory. +Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``: a hyphen between ``method`` and ``dimensions`` is mandatory. Multiple dimensions must be separated by an ``x``. The available ``method`` s are: From 99c9d875befca5d6c76a6388a2b4a4bec1fb96c4 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Thu, 22 May 2014 17:51:50 +0100 Subject: [PATCH 83/84] Static site generation docs update Formatting tweaks and S3 / Google App Engine options. --- docs/static_site_generation.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/static_site_generation.rst b/docs/static_site_generation.rst index fe4b504ea..a9379cd67 100644 --- a/docs/static_site_generation.rst +++ b/docs/static_site_generation.rst @@ -1,20 +1,20 @@ Generating a static site ======================== -This document describes how to render your Wagtail site into static HTML files using `django medusa`_ and the 'wagtail.contrib.wagtailmedusa' module. +This document describes how to render your Wagtail site into static HTML files on your local filesystem, Amazon S3 or Google App Engine, using `django medusa`_ and the ``wagtail.contrib.wagtailmedusa`` module. Installing django-medusa ~~~~~~~~~~~~~~~~~~~~~~~~ -Firstly, install django medusa from pip: +First, install django medusa from pip: .. code:: pip install django-medusa -Then add 'django_medusa' and 'wagtail.contrib.wagtailmedusa' to INSTALLED_APPS: +Then add ``django_medusa`` and ``wagtail.contrib.wagtailmedusa`` to ``INSTALLED_APPS``: .. code:: python @@ -28,9 +28,9 @@ Then add 'django_medusa' and 'wagtail.contrib.wagtailmedusa' to INSTALLED_APPS: Replacing GET parameters with custom routing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pages which require GET parameters (eg, pagination) don't generate suitable filenames for generated HTML files so they need to be changed to use custom routing instead. +Pages which require GET parameters (e.g. for pagination) don't generate suitable filenames for generated HTML files so they need to be changed to use custom routing instead. -For example, lets say we have a Blog Index which uses pagination. We can override the 'route' method to make it respond on urls like '/page/1' and pass the page number through to the serve method: +For example, let's say we have a Blog Index which uses pagination. We can override the ``route`` method to make it respond on urls like '/page/1', and pass the page number through to the ``serve`` method: .. code:: python @@ -53,7 +53,7 @@ For example, lets say we have a Blog Index which uses pagination. We can overrid Rendering pages which use custom routing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For page types that override the route method, we need to let django medusa know which URLs it responds on. This is done by overriding the 'get_static_site_paths' method to make it yield one string per URL path. +For page types that override the ``route`` method, we need to let django medusa know which URLs it responds on. This is done by overriding the ``get_static_site_paths`` method to make it yield one string per URL path. For example, the BlogIndex above would need to yield one URL for each page of results: @@ -75,7 +75,7 @@ For example, the BlogIndex above would need to yield one URL for each page of re Rendering ~~~~~~~~~ -To render a site, just run ``./manage.py staticsitegen``. This will render the entire website and place the HTML in a folder called 'medusa_output'. The static and media folders need to be copied into this folder manually after the rendering is complete. +To render a site, run ``./manage.py staticsitegen``. This will render the entire website and place the HTML in a folder called 'medusa_output'. The static and media folders need to be copied into this folder manually after the rendering is complete. This feature inherits django-medusa's ability to render your static site to Amazon S3 or Google App Engine; see the `medusa docs `_ for configuration details. To test, open the 'medusa_output' folder in a terminal and run ``python -m SimpleHTTPServer``. From cde047f4850770ae0ffd2fcbf0580336bb6cf8ac Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Thu, 22 May 2014 18:48:14 +0100 Subject: [PATCH 84/84] Getting started docs Less version precision for Ubuntu installs. --- docs/gettingstarted.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index 626aa42c0..a46ea9664 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -4,7 +4,7 @@ Getting Started On Ubuntu ~~~~~~~~~ -If you have a fresh instance of Ubuntu 13.04 or 13.10, you can install Wagtail, +If you have a fresh instance of Ubuntu 13.04 or later, you can install Wagtail, along with a demonstration site containing a set of standard templates and page types, in one step. As the root user::