From a3b7259f98caca8e02b8e95f973207b4f66cfe76 Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Thu, 20 Mar 2014 15:58:02 +0200 Subject: [PATCH 001/220] 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 002/220] 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 003/220] 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 004/220] 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 005/220] 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 006/220] 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 007/220] 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 008/220] 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 %}
{% 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 178/220] 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 179/220] 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 180/220] 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 181/220] 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 182/220] 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 183/220] 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 184/220] 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 185/220] 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 186/220] 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 187/220] 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 188/220] 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 189/220] 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 190/220] 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 191/220] 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 192/220] 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 193/220] 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 194/220] 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 195/220] 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:: From 779ac271424254e765ea0e0ecf20ba050c451ffa Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 22 May 2014 19:25:30 -0400 Subject: [PATCH 196/220] Merged with docs coming from torchbox --- docs/advanced_topics.rst | 5 +- docs/building_your_site/djangodevelopers.rst | 188 ++++++- .../building_your_site/frontenddevelopers.rst | 65 ++- docs/building_your_site/index.rst | 485 +----------------- docs/editing_api.rst | 12 + docs/index.rst | 5 +- docs/model_recipes.rst | 176 +++++++ 7 files changed, 447 insertions(+), 489 deletions(-) create mode 100644 docs/model_recipes.rst diff --git a/docs/advanced_topics.rst b/docs/advanced_topics.rst index 1f5cec1bf..7e23d8442 100644 --- a/docs/advanced_topics.rst +++ b/docs/advanced_topics.rst @@ -1,6 +1,9 @@ Advanced Topics ~~~~~~~~~~~~~~~~ +.. note:: + This documentation is currently being written. + replacing image processing backend custom image processing tags? @@ -11,4 +14,4 @@ extending hallo editor plugins with editor_js() injecting any JS into page edit with editor_js() -Custom content module (same level as docs or images) \ No newline at end of file +Custom content module (same level as docs or images) diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst index 62f37c12b..49f80c371 100644 --- a/docs/building_your_site/djangodevelopers.rst +++ b/docs/building_your_site/djangodevelopers.rst @@ -1,3 +1,189 @@ For Django developers -================== +===================== +.. note:: + This documentation is currently being written. + +Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the contruction of Django models using Wagtail's ``Page`` as a base. + +Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn't prescribe ways to organize and interrelate your content, but here we've sketched out some strategies for organizing your models. + +The presentation of your content, the actual webpages, includes the normal use of the Django template system. We'll cover additional functionality that Wagtail provides at the template level later on. + +But first, we'll take a look at the ``Page`` class and model definitions. + + +The Page Class +~~~~~~~~~~~~~~ + +``Page`` uses Django's model interface, so you can include any field type and field options that Django allows. Wagtail provides some fields and editing handlers that simplify data entry in the Wagtail admin interface, so you may want to keep those in mind when deciding what properties to add to your models in addition to those already provided by ``Page``. + + +Built-in Properties of the Page Class +------------------------------------- + +Wagtail provides some properties in the ``Page`` class which are common to most webpages. Since you'll be subclassing ``Page``, you don't have to worry about implementing them. + +Public Properties +````````````````` + + ``title`` (string, required) + Human-readable title for the content + + ``slug`` (string, required) + Machine-readable URL component for this piece of content. The name of the page as it will appear in URLs e.g ``http://domain.com/blog/[my-slug]/`` + + ``seo_title`` (string) + Alternate SEO-crafted title which overrides the normal title for use in the ```` of a page + + ``search_description`` (string) + A SEO-crafted description of the content, used in both internal search indexing and for the meta description read by search engines + +The ``Page`` class actually has alot more to it, but these are probably the only built-in properties you'll need to worry about when creating templates for your models. + + +Anatomy of a Wagtail Model +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So what does a Wagtail model definition look like? Here's a model representing a typical blog post: + +.. code-block:: python + + from django.db import models + + from wagtail.wagtailcore.models import Page + from wagtail.wagtailcore.fields import RichTextField + from wagtail.wagtailadmin.edit_handlers import FieldPanel + from wagtail.wagtailimages.edit_handlers import ImageChooserPanel + from wagtail.wagtailimages.models import Image + + class BlogPage(Page): + body = RichTextField() + date = models.DateField("Post date") + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + BlogPage.content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('date'), + FieldPanel('body', classname="full"), + ] + + BlogPage.promote_panels = [ + FieldPanel('slug'), + FieldPanel('seo_title'), + FieldPanel('show_in_menus'), + FieldPanel('search_description'), + ImageChooserPanel('feed_image'), + ] + +To keep track of your ``Page``-derived models, it might be helpful to include "Page" as the last part of your classname. ``BlogPage`` defines three properties: ``body``, ``date``, and ``feed_image``. These are a mix of basic Django models (``DateField``), Wagtail fields (``RichTextField``), and a pointer to a Wagtail model (``Image``). + +Next, the ``content_panels`` and ``promote_panels`` lists define the capabilities and layout of the Wagtail admin page edit interface. The lists are filled with "panels" and "choosers", which will provide a fine-grain interface for inputting the model's content. The ``ImageChooserPanel``, for instance, lets one browse the image library, upload new images, and input image metadata. The ``RichTextField`` is the basic field for creating web-ready website rich text, including text formatting and embedded media like images and video. The Wagtail admin offers other choices for fields, Panels, and Choosers, with the option of creating your own to precisely fit your content without workarounds or other compromises. + +Your models may be even more complex, with methods overriding the built-in functionality of the ``Page`` to achieve webdev magic. Or, you can keep your models simple and let Wagtail's built-in functionality do the work. + +Now that we have a basic idea of how our content is defined, lets look at relationships between pieces of content. + + +Introduction to Trees +~~~~~~~~~~~~~~~~~~~~~ + +If you're unfamiliar with trees as an abstract data type, you might want to `review the concepts involved. `_ + +As a web developer, though, you probably already have a good understanding of trees as filesystem directories or paths. Wagtail pages can create the same structure, as each page in the tree has its own URL path, like so:: + + / + people/ + nien-nunb/ + laura-roslin/ + events/ + captain-picard-day/ + winter-wrap-up/ + +The Wagtail admin interface uses the tree to organize content for editing, letting you navigate up and down levels in the tree through its Explorer menu. This method of organization is a good place to start in thinking about your own Wagtail models. + + +Nodes and Leaves +---------------- + +It might be handy to think of the ``Page``-derived models you want to create as being one of two node types: parents and leaves. Wagtail isn't prescriptive in this approach, but it's a good place to start if you're not experienced in structuring your own content types. + + +Nodes +````` +Parent nodes on the Wagtail tree probably want to organize and display a browsable index of their descendents. A blog, for instance, needs a way to show a list of individual posts. + +A Parent node could provide its own function returning its descendant objects. + +.. code-block:: python + + class EventPageIndex(Page): + ... + def events(self): + # Get list of event pages that are descendants of this page + events = EventPage.objects.filter( + live=True, + path__startswith=self.path + ) + return events + +This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail's admin interface (``live=True``) and are descendants of this node. Wagtail will allow the "illogical" placement of child nodes under a parent, so it's necessary for a parent model to index only those children which make sense. + + +Leaves +`````` +Leaves are the pieces of content itself, a page which is consumable, and might just consist of a bunch of properties. A blog page leaf might have some body text and an image. A person page leaf might have a photo, a name, and an address. + +It might be helpful for a leaf to provide a way to back up along the tree to a parent, such as in the case of breadcrumbs navigation. The tree might also be deep enough that a leaf's parent won't be included in general site navigation. + +The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor: + +.. code-block:: python + + class BlogPage(Page): + ... + def blog_index(self): + # Find blog index in ancestors + for ancestor in reversed(self.get_ancestors()): + if isinstance(ancestor.specific, BlogIndexPage): + return ancestor + + # No ancestors are blog indexes, just return first blog index in database + return BlogIndexPage.objects.first() + +Since Wagtail doesn't limit what Page-derived classes can be assigned as parents and children, the reverse tree traversal needs to accommodate cases which might not be expected, such as the lack of a "logical" parent to a leaf. + + +Other Relationships +``````````````````` +Your ``Page``-derived models might have other interrelationships which extend the basic Wagtail tree or depart from it entirely. You could provide functions to navigate between siblings, such as a "Next Post" link on a blog page (``post->post->post``). It might make sense for subtrees to interrelate, such as in a discussion forum (``forum->post->replies``) Skipping across the hierarchy might make sense, too, as all objects of a certain model class might interrelate regardless of their ancestors (``events = EventPage.objects.all``). Since there's no restriction on the combination of model classes that can be used at any point in the tree, and it's largely up to the models to define their interrelations, the possibilities are really endless. + + +Anatomy of a Wagtail Request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For going beyond the basics of model definition and interrelation, it might help to know how Wagtail handles requests and constructs responses. In short, it goes something like: + + #. Django gets a request and routes through Wagtail's URL dispatcher definitions + #. Starting from the root content piece, Wagtail traverses the page tree, letting the model for each piece of content along the path decide how to ``route()`` the next step in the path. + #. A model class decides that routing is done and it's now time to ``serve()`` content. + #. The model constructs a context, finds a template to pass it to, and renders the content. + #. The templates are rendered and the response object is sent back to the requester. + +You can apply custom behavior to this process by overriding the ``route()`` and ``serve()`` methods of the ``Page`` class in your own models. + + +Site +~~~~ + +Django's built-in admin interface provides the way to map a "site" (hostname or domain) to any node in the wagtail tree, using that node as the site's root. + +Access this by going to ``/django-admin/`` and then "Home › Wagtailcore › Sites." To try out a development site, add a single site with the hostname ``localhost`` at port ``8000`` and map it to one of the pieces of content you have created. + +Wagtail's developers plan to move the site settings into the Wagtail admin interface. diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 1c2268506..033c64e9e 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -14,10 +14,31 @@ 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 -======================== +========================== +Displaying Pages +========================== +Template Location +----------------- + +For each of your ``Page``-derived models, Wagtail will look for a template in the following location, relative to your project root:: + + project/ + app/ + templates/ + app/ + blog_index_page.html + models.py + +Class names are converted from camel case to underscores. For example, the template for model class ``BlogIndexPage`` would be assumed to be ``blog_index_page.html``. For more information, see the Django documentation for the `application directories template loader`_. + +.. _application directories template loader: https://docs.djangoproject.com/en/dev/ref/templates/api/ + + +Self +---- + +By default, the context passed to a model's template consists of two properties: ``self`` and ``request``. ``self`` is the model object being displayed. ``request`` is the normal Django request object. So, to include the title of a ``Page``, use ``{{ self.title }}``. ======================== Static files (css, js, images) @@ -104,14 +125,42 @@ The available ``method`` s are: 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) ~~~~~~~~~~~~~~~~~~ +This filter is required for use with any ``RichTextField``. It will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. **Note that the template tag loaded differs from the name of the filter.** + +.. code-block:: django + + {% load rich_text %} + ... + {{ body|richtext }} Internal links (tag) ~~~~~~~~~~~~~~~~~~~~ +**pageurl** + +Takes a ``Page``-derived object and returns its URL as relative (``/foo/bar/``) if it's within the same site as the current page, or absolute (``http://example.com/foo/bar/``) if not. + +.. code-block:: django + + {% load pageurl %} + ... + + +**slugurl** + +Takes a ``slug`` string and returns the URL for the ``Page``-derived object with that slug. Like ``pageurl``, will try to provide a relative link if possible, but will default to an absolute link if on a different site. + +.. code-block:: django + + {% load slugurl %} + ... + + + + Static files (tag) ~~~~~~~~~~~~~~ @@ -121,7 +170,15 @@ Misc ~~~~~~~~~~ + ======================== Wagtail User Bar ======================== +This tag provides a Wagtail icon and flyout menu on the top-right of a page for a logged-in user with editing capabilities, with the option of editing the current Page-derived object or adding a new sibling object. + +.. code-block:: django + + {% load wagtailuserbar %} + ... + {% wagtailuserbar %} diff --git a/docs/building_your_site/index.rst b/docs/building_your_site/index.rst index 0f122d8a6..a42c40099 100644 --- a/docs/building_your_site/index.rst +++ b/docs/building_your_site/index.rst @@ -1,492 +1,15 @@ Building your site ================== -<<<<<<< HEAD:docs/building_your_site.rst -Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the contruction of Django models using Wagtail's ``Page`` as a base. - -Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn't prescribe ways to organize and interrelate your content, but here we've sketched out some strategies for organizing your models. - -The presentation of your content, the actual webpages, includes the normal use of the Django template system. We'll cover additional functionality that Wagtail provides at the template level later on. - -But first, we'll take a look at the ``Page`` class and model definitions. - - -The Page Class -~~~~~~~~~~~~~~ - -``Page`` uses Django's model interface, so you can include any field type and field options that Django allows. Wagtail provides some fields and editing handlers that simplify data entry in the Wagtail admin interface, so you may want to keep those in mind when deciding what properties to add to your models in addition to those already provided by ``Page``. - - -Built-in Properties of the Page Class -------------------------------------- - -Wagtail provides some properties in the ``Page`` class which are common to most webpages. Since you'll be subclassing ``Page``, you don't have to worry about implementing them. - -Public Properties -````````````````` - - ``title`` (string, required) - Human-readable title for the content - - ``slug`` (string, required) - Machine-readable URL component for this piece of content. The name of the page as it will appear in URLs e.g ``http://domain.com/blog/[my-slug]/`` - - ``seo_title`` (string) - Alternate SEO-crafted title which overrides the normal title for use in the ```` of a page - - ``search_description`` (string) - A SEO-crafted description of the content, used in both internal search indexing and for the meta description read by search engines - -The ``Page`` class actually has alot more to it, but these are probably the only built-in properties you'll need to worry about when creating templates for your models. - - -Anatomy of a Wagtail Model -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -So what does a Wagtail model definition look like? Here's a model representing a typical blog post: - -.. code-block:: python - - from django.db import models - - from wagtail.wagtailcore.models import Page - from wagtail.wagtailcore.fields import RichTextField - from wagtail.wagtailadmin.edit_handlers import FieldPanel - from wagtail.wagtailimages.edit_handlers import ImageChooserPanel - from wagtail.wagtailimages.models import Image - - class BlogPage(Page): - body = RichTextField() - date = models.DateField("Post date") - feed_image = models.ForeignKey( - 'wagtailimages.Image', - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) - - BlogPage.content_panels = [ - FieldPanel('title', classname="full title"), - FieldPanel('date'), - FieldPanel('body', classname="full"), - ] - - BlogPage.promote_panels = [ - FieldPanel('slug'), - FieldPanel('seo_title'), - FieldPanel('show_in_menus'), - FieldPanel('search_description'), - ImageChooserPanel('feed_image'), - ] - -To keep track of your ``Page``-derived models, it might be helpful to include "Page" as the last part of your classname. ``BlogPage`` defines three properties: ``body``, ``date``, and ``feed_image``. These are a mix of basic Django models (``DateField``), Wagtail fields (``RichTextField``), and a pointer to a Wagtail model (``Image``). - -Next, the ``content_panels`` and ``promote_panels`` lists define the capabilities and layout of the Wagtail admin page edit interface. The lists are filled with "panels" and "choosers", which will provide a fine-grain interface for inputting the model's content. The ``ImageChooserPanel``, for instance, lets one browse the image library, upload new images, and input image metadata. The ``RichTextField`` is the basic field for creating web-ready website rich text, including text formatting and embedded media like images and video. The Wagtail admin offers other choices for fields, Panels, and Choosers, with the option of creating your own to precisely fit your content without workarounds or other compromises. - -Your models may be even more complex, with methods overriding the built-in functionality of the ``Page`` to achieve webdev magic. Or, you can keep your models simple and let Wagtail's built-in functionality do the work. - -Now that we have a basic idea of how our content is defined, lets look at relationships between pieces of content. - - -Introduction to Trees -~~~~~~~~~~~~~~~~~~~~~ - -If you're unfamiliar with trees as an abstract data type, you might want to `review the concepts involved. `_ - -As a web developer, though, you probably already have a good understanding of trees as filesystem directories or paths. Wagtail pages can create the same structure, as each page in the tree has its own URL path, like so:: - - / - people/ - nien-nunb/ - laura-roslin/ - events/ - captain-picard-day/ - winter-wrap-up/ - -The Wagtail admin interface uses the tree to organize content for editing, letting you navigate up and down levels in the tree through its Explorer menu. This method of organization is a good place to start in thinking about your own Wagtail models. - - -Nodes and Leaves ----------------- - -It might be handy to think of the ``Page``-derived models you want to create as being one of two node types: parents and leaves. Wagtail isn't prescriptive in this approach, but it's a good place to start if you're not experienced in structuring your own content types. - - -Nodes -````` -Parent nodes on the Wagtail tree probably want to organize and display a browsable index of their descendents. A blog, for instance, needs a way to show a list of individual posts. - -A Parent node could provide its own function returning its descendant objects. - -.. code-block:: python - - class EventPageIndex(Page): - ... - def events(self): - # Get list of event pages that are descendants of this page - events = EventPage.objects.filter( - live=True, - path__startswith=self.path - ) - return events - -This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail's admin interface (``live=True``) and are descendants of this node. Wagtail will allow the "illogical" placement of child nodes under a parent, so it's necessary for a parent model to index only those children which make sense. - - -Leaves -`````` -Leaves are the pieces of content itself, a page which is consumable, and might just consist of a bunch of properties. A blog page leaf might have some body text and an image. A person page leaf might have a photo, a name, and an address. - -It might be helpful for a leaf to provide a way to back up along the tree to a parent, such as in the case of breadcrumbs navigation. The tree might also be deep enough that a leaf's parent won't be included in general site navigation. - -The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor: - -.. code-block:: python - - class BlogPage(Page): - ... - def blog_index(self): - # Find blog index in ancestors - for ancestor in reversed(self.get_ancestors()): - if isinstance(ancestor.specific, BlogIndexPage): - return ancestor - - # No ancestors are blog indexes, just return first blog index in database - return BlogIndexPage.objects.first() - -Since Wagtail doesn't limit what Page-derived classes can be assigned as parents and children, the reverse tree traversal needs to accommodate cases which might not be expected, such as the lack of a "logical" parent to a leaf. - - -Other Relationships -``````````````````` -Your ``Page``-derived models might have other interrelationships which extend the basic Wagtail tree or depart from it entirely. You could provide functions to navigate between siblings, such as a "Next Post" link on a blog page (``post->post->post``). It might make sense for subtrees to interrelate, such as in a discussion forum (``forum->post->replies``) Skipping across the hierarchy might make sense, too, as all objects of a certain model class might interrelate regardless of their ancestors (``events = EventPage.objects.all``). Since there's no restriction on the combination of model classes that can be used at any point in the tree, and it's largely up to the models to define their interrelations, the possibilities are really endless. - - -Anatomy of a Wagtail Request -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For going beyond the basics of model definition and interrelation, it might help to know how Wagtail handles requests and constructs responses. In short, it goes something like: - - #. Django gets a request and routes through Wagtail's URL dispatcher definitions - #. Starting from the root content piece, Wagtail traverses the page tree, letting the model for each piece of content along the path decide how to ``route()`` the next step in the path. - #. A model class decides that routing is done and it's now time to ``serve()`` content. - #. The model constructs a context, finds a template to pass it to, and renders the content. - #. The templates are rendered and the response object is sent back to the requester. - -You can apply custom behavior to this process by overriding the ``route()`` and ``serve()`` methods of the ``Page`` class in your own models. - - -Model Recipes -~~~~~~~~~~~~~ - -Overriding the serve() Method ------------------------------ - -Wagtail defaults to serving ``Page``-derived models by passing ``self`` to a Django HTML template matching the model's name, but suppose you wanted to serve something other than HTML? You can override the ``serve()`` method provided by the ``Page`` class and handle the Django request and response more directly. - -Consider this example from the Wagtail demo site's ``models.py``, which serves an ``EventPage`` object as an iCal file if the ``format`` variable is set in the request: - -.. code-block:: python - - class EventPage(Page): - ... - def serve(self, request): - if "format" in request.GET: - if request.GET['format'] == 'ical': - # Export to ical format - response = HttpResponse( - export_event(self, 'ical'), - content_type='text/calendar', - ) - response['Content-Disposition'] = 'attachment; filename=' + self.slug + '.ics' - return response - else: - # Unrecognised format error - message = 'Could not export event\n\nUnrecognised format: ' + request.GET['format'] - return HttpResponse(message, content_type='text/plain') - else: - # Display event page as usual - return super(EventPage, self).serve(request) - -``serve()`` takes a Django request object and returns a Django response object. Wagtail returns a ``TemplateResponse`` object with the template and context which it generates, which allows middleware to function as intended, so keep in mind that a simpler response object like a ``HttpResponse`` will not receive these benefits. - -With this strategy, you could use Django or Python utilities to render your model in JSON or XML or any other format you'd like. - - -Adding Endpoints with Custom route() Methods --------------------------------------------- - -Wagtail routes requests by iterating over the path components (separated with a forward slash ``/``), finding matching objects based on their slug, and delegating further routing to that object's model class. The Wagtail source is very instructive in figuring out what's happening. This is the default ``route()`` method of the ``Page`` class: - -.. code-block:: python - - class Page(...): - - ... - - def route(self, request, path_components): - if path_components: - # request is for a child of this page - child_slug = path_components[0] - remaining_components = path_components[1:] - - # find a matching child or 404 - try: - subpage = self.get_children().get(slug=child_slug) - except Page.DoesNotExist: - raise Http404 - - # delegate further routing - return subpage.specific.route(request, remaining_components) - - else: - # request is for this very page - if self.live: - # use the serve() method to render the request if the page is published - return self.serve(request) - else: - # the page matches the request, but isn't published, so 404 - raise Http404 - -The contract is pretty simple. ``route()`` takes the current object (``self``), the ``request`` object, and a list of the remaining ``path_components`` from the request URL. It either continues delegating routing by calling ``route()`` again on one of its children in the Wagtail tree, or ends the routing process by serving something -- either normally through the ``self.serve()`` method or by raising a 404 error. - -By overriding the ``route()`` method, we could create custom endpoints for each object in the Wagtail tree. One use case might be using an alternate template when encountering the ``print/`` endpoint in the path. Another might be a REST API which interacts with the current object. Just to see what's involved, lets make a simple model which prints out all of its child path components. - -First, ``models.py``: - -.. code-block:: python - - from django.shortcuts import render - - ... - - class Echoer(Page): - - def route(self, request, path_components): - if path_components: - return render(request, self.template, { - 'self': self, - 'echo': ' '.join(path_components), - }) - else: - if self.live: - return self.serve(request) - else: - raise Http404 - - Echoer.content_panels = [ - FieldPanel('title', classname="full title"), - ] - - Echoer.promote_panels = [ - MultiFieldPanel(COMMON_PANELS, "Common page configuration"), - ] - -This model, ``Echoer``, doesn't define any properties, but does subclass ``Page`` so objects will be able to have a custom title and slug. The template just has to display our ``{{ echo }}`` property. We're skipping the ``serve()`` method entirely, but you could include your render code there to stay consistent with Wagtail's conventions. - -Now, once creating a new ``Echoer`` page in the Wagtail admin titled "Echo Base," requests such as:: - - http://127.0.0.1:8000/echo-base/tauntaun/kennel/bed/and/breakfast/ - -Will return:: - - tauntaun kennel bed and breakfast - -Lovely, huh? (We know.) - - - -Tagging -------- - -Wagtail provides tagging capability through the combination of two django modules, ``taggit`` and ``modelcluster``. ``taggit`` provides a model for tags which is extended by ``modelcluster``, which in turn provides some magical database abstraction which makes drafts and revisions possible in Wagtail. It's a tricky recipe, but the net effect is a many-to-many relationship between your model and a tag class reserved for your model. - -Using an example from the Wagtail demo site, here's what the tag model and the relationship field looks like in ``models.py``: - -.. code-block:: python - - from modelcluster.fields import ParentalKey - from modelcluster.tags import ClusterTaggableManager - from taggit.models import Tag, TaggedItemBase - ... - class BlogPageTag(TaggedItemBase): - content_object = ParentalKey('demo.BlogPage', related_name='tagged_items') - ... - class BlogPage(Page): - ... - tags = ClusterTaggableManager(through=BlogPageTag, blank=True) - - BlogPage.promote_panels = [ - ... - FieldPanel('tags'), - ] - -Wagtail's admin provides a nice interface for inputting tags into your content, with typeahead tag completion and friendly tag icons. - -Now that we have the many-to-many tag relationship in place, we can fit in a way to render both sides of the relation. Here's more of the Wagtail demo site ``models.py``, where the index model for ``BlogPage`` is extended with logic for filtering the index by tag: - -.. code-block:: python - - class BlogIndexPage(Page): - ... - def serve(self, request): - # Get blogs - blogs = self.blogs - - # Filter by tag - tag = request.GET.get('tag') - if tag: - blogs = blogs.filter(tags__name=tag) - - return render(request, self.template, { - 'self': self, - 'blogs': blogs, - }) - -Here, ``blogs.filter(tags__name=tag)`` invokes a reverse Django queryset filter on the ``BlogPageTag`` model to optionally limit the ``BlogPage`` objects sent to the template for rendering. Now, lets render both sides of the relation by showing the tags associated with an object and a way of showing all of the objects associated with each tag. This could be added to the ``blog_page.html`` template: - -.. code-block:: django - - {% for tag in self.tags.all %} - {{ tag }} - {% endfor %} - -Iterating through ``self.tags.all`` will display each tag associated with ``self``, while the link(s) back to the index make use of the filter option added to the ``BlogIndexPage`` model. A Django query could also use the ``tagged_items`` related name field to get ``BlogPage`` objects associated with a tag. - -This is just one possible way of creating a taxonomy for Wagtail objects. With all of the components for a taxonomy available through Wagtail, you should be able to fulfill even the most exotic taxonomic schemes. - - - - - - - - - - - -Templates -~~~~~~~~~ - -Location --------- - -For each of your ``Page``-derived models, Wagtail will look for a template in the following location, relative to your project root:: - - project/ - app/ - templates/ - app/ - blog_index_page.html - models.py - -Class names are converted from camel case to underscores. For example, the template for model class ``BlogIndexPage`` would be assumed to be ``blog_index_page.html``. For more information, see the Django documentation for the `application directories template loader`_. - -.. _application directories template loader: https://docs.djangoproject.com/en/dev/ref/templates/api/ - - -Self ----- - -By default, the context passed to a model's template consists of two properties: ``self`` and ``request``. ``self`` is the model object being displayed. ``request`` is the normal Django request object. - - -Template Tags -------------- - - **pageurl** - - Takes a ``Page``-derived object and returns its URL as relative (``/foo/bar/``) if it's within the same site as the current page, or absolute (``http://example.com/foo/bar/``) if not. - - .. code-block:: django - - {% load pageurl %} - ... - - - **slugurl** - - Takes a ``slug`` string and returns the URL for the ``Page``-derived object with that slug. Like ``pageurl``, will try to provide a relative link if possible, but will default to an absolute link if on a different site. - - - .. code-block:: django - - {% load slugurl %} - ... - - - **wagtailuserbar** - - This tag provides a Wagtail icon and flyout menu on the top-right of a page for a logged-in user with editing capabilities, with the option of editing the current Page-derived object or adding a new sibling object. - - .. code-block:: django - - {% load wagtailuserbar %} - ... - {% wagtailuserbar %} - - **image** - - This template tag provides a way to process an image with a method and dimensions. - - .. code-block:: django - - {% load image_tags %} - ... - {% image self.photo max-320x200 %} - or - {% image self.photo max-320x200 as img %} - - 'max': 'resize_to_max', - 'min': 'resize_to_min', - 'width': 'resize_to_width', - 'height': 'resize_to_height', - 'fill': 'resize_to_fill', - - -Template Filters ----------------- - - **rich_text** - - This filter is required for use with any ``RichTextField``. It will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. **Note that the template tag loaded differs from the name of the filter.** - - .. code-block:: django - - {% load rich_text %} - ... - {{ body|richtext }} - - - -Site -~~~~ - -Django's built-in admin interface provides the way to map a "site" (hostname or domain) to any node in the wagtail tree, using that node as the site's root. - -Access this by going to ``/django-admin/`` and then "Home › Wagtailcore › Sites." To try out a development site, add a single site with the hostname ``localhost`` at port ``8000`` and map it to one of the pieces of content you have created. - -Wagtail's developers plan to move the site settings into the Wagtail admin interface. - - - -Example Site -~~~~~~~~~~~~ -======= .. note:: - Documentation currently incomplete and in draft status ->>>>>>> cde047f4850770ae0ffd2fcbf0580336bb6cf8ac:docs/building_your_site/index.rst + 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/ `_ -<<<<<<< HEAD:docs/building_your_site.rst -======= .. toctree:: - :maxdepth: 3 + :maxdepth: 3 - djangodevelopers - frontenddevelopers ->>>>>>> cde047f4850770ae0ffd2fcbf0580336bb6cf8ac:docs/building_your_site/index.rst + djangodevelopers + frontenddevelopers diff --git a/docs/editing_api.rst b/docs/editing_api.rst index 8fcb0b715..2985a1229 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -2,6 +2,10 @@ Editing API =========== +.. note:: + This documentation is currently being written. + + Wagtail provides a highly-customizable editing interface consisting of several components: * **Fields** — built-in content types to augment the basic types provided by Django. @@ -93,6 +97,8 @@ If you're interested in extending the capabilities of the Wagtail editor, See :r Images ------ +.. code-block:: python + from wagtail.wagtailimages.models import Image feed_image = models.ForeignKey( @@ -107,6 +113,8 @@ Images Documents --------- +.. code-block:: python + from wagtail.wagtaildocs.models import Document link_document = models.ForeignKey( @@ -120,6 +128,8 @@ Documents Pages and Page-derived Models ----------------------------- +.. code-block:: python + from wagtail.wagtailcore.models import Page page = models.ForeignKey( @@ -137,6 +147,8 @@ Snippets (and Basic Django Models?) Snippets are not not subclasses, so you must include the model class directly. A chooser is provided which takes the snippet class. +.. code-block:: python + advert = models.ForeignKey( 'demo.Advert', related_name='+' diff --git a/docs/index.rst b/docs/index.rst index f64eec0b9..abbb7fdbe 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,14 +9,15 @@ 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/index editing_api snippets - building_your_site/index wagtail_search + form_builder + model_recipes advanced_topics deploying performance - form_builder static_site_generation contributing support diff --git a/docs/model_recipes.rst b/docs/model_recipes.rst new file mode 100644 index 000000000..db88daf7c --- /dev/null +++ b/docs/model_recipes.rst @@ -0,0 +1,176 @@ + +Model Recipes +============= + +Overriding the serve() Method +----------------------------- + +Wagtail defaults to serving ``Page``-derived models by passing ``self`` to a Django HTML template matching the model's name, but suppose you wanted to serve something other than HTML? You can override the ``serve()`` method provided by the ``Page`` class and handle the Django request and response more directly. + +Consider this example from the Wagtail demo site's ``models.py``, which serves an ``EventPage`` object as an iCal file if the ``format`` variable is set in the request: + +.. code-block:: python + + class EventPage(Page): + ... + def serve(self, request): + if "format" in request.GET: + if request.GET['format'] == 'ical': + # Export to ical format + response = HttpResponse( + export_event(self, 'ical'), + content_type='text/calendar', + ) + response['Content-Disposition'] = 'attachment; filename=' + self.slug + '.ics' + return response + else: + # Unrecognised format error + message = 'Could not export event\n\nUnrecognised format: ' + request.GET['format'] + return HttpResponse(message, content_type='text/plain') + else: + # Display event page as usual + return super(EventPage, self).serve(request) + +``serve()`` takes a Django request object and returns a Django response object. Wagtail returns a ``TemplateResponse`` object with the template and context which it generates, which allows middleware to function as intended, so keep in mind that a simpler response object like a ``HttpResponse`` will not receive these benefits. + +With this strategy, you could use Django or Python utilities to render your model in JSON or XML or any other format you'd like. + + +Adding Endpoints with Custom route() Methods +-------------------------------------------- + +Wagtail routes requests by iterating over the path components (separated with a forward slash ``/``), finding matching objects based on their slug, and delegating further routing to that object's model class. The Wagtail source is very instructive in figuring out what's happening. This is the default ``route()`` method of the ``Page`` class: + +.. code-block:: python + + class Page(...): + + ... + + def route(self, request, path_components): + if path_components: + # request is for a child of this page + child_slug = path_components[0] + remaining_components = path_components[1:] + + # find a matching child or 404 + try: + subpage = self.get_children().get(slug=child_slug) + except Page.DoesNotExist: + raise Http404 + + # delegate further routing + return subpage.specific.route(request, remaining_components) + + else: + # request is for this very page + if self.live: + # use the serve() method to render the request if the page is published + return self.serve(request) + else: + # the page matches the request, but isn't published, so 404 + raise Http404 + +The contract is pretty simple. ``route()`` takes the current object (``self``), the ``request`` object, and a list of the remaining ``path_components`` from the request URL. It either continues delegating routing by calling ``route()`` again on one of its children in the Wagtail tree, or ends the routing process by serving something -- either normally through the ``self.serve()`` method or by raising a 404 error. + +By overriding the ``route()`` method, we could create custom endpoints for each object in the Wagtail tree. One use case might be using an alternate template when encountering the ``print/`` endpoint in the path. Another might be a REST API which interacts with the current object. Just to see what's involved, lets make a simple model which prints out all of its child path components. + +First, ``models.py``: + +.. code-block:: python + + from django.shortcuts import render + + ... + + class Echoer(Page): + + def route(self, request, path_components): + if path_components: + return render(request, self.template, { + 'self': self, + 'echo': ' '.join(path_components), + }) + else: + if self.live: + return self.serve(request) + else: + raise Http404 + + Echoer.content_panels = [ + FieldPanel('title', classname="full title"), + ] + + Echoer.promote_panels = [ + MultiFieldPanel(COMMON_PANELS, "Common page configuration"), + ] + +This model, ``Echoer``, doesn't define any properties, but does subclass ``Page`` so objects will be able to have a custom title and slug. The template just has to display our ``{{ echo }}`` property. We're skipping the ``serve()`` method entirely, but you could include your render code there to stay consistent with Wagtail's conventions. + +Now, once creating a new ``Echoer`` page in the Wagtail admin titled "Echo Base," requests such as:: + + http://127.0.0.1:8000/echo-base/tauntaun/kennel/bed/and/breakfast/ + +Will return:: + + tauntaun kennel bed and breakfast + + +Tagging +------- + +Wagtail provides tagging capability through the combination of two django modules, ``taggit`` and ``modelcluster``. ``taggit`` provides a model for tags which is extended by ``modelcluster``, which in turn provides some magical database abstraction which makes drafts and revisions possible in Wagtail. It's a tricky recipe, but the net effect is a many-to-many relationship between your model and a tag class reserved for your model. + +Using an example from the Wagtail demo site, here's what the tag model and the relationship field looks like in ``models.py``: + +.. code-block:: python + + from modelcluster.fields import ParentalKey + from modelcluster.tags import ClusterTaggableManager + from taggit.models import Tag, TaggedItemBase + ... + class BlogPageTag(TaggedItemBase): + content_object = ParentalKey('demo.BlogPage', related_name='tagged_items') + ... + class BlogPage(Page): + ... + tags = ClusterTaggableManager(through=BlogPageTag, blank=True) + + BlogPage.promote_panels = [ + ... + FieldPanel('tags'), + ] + +Wagtail's admin provides a nice interface for inputting tags into your content, with typeahead tag completion and friendly tag icons. + +Now that we have the many-to-many tag relationship in place, we can fit in a way to render both sides of the relation. Here's more of the Wagtail demo site ``models.py``, where the index model for ``BlogPage`` is extended with logic for filtering the index by tag: + +.. code-block:: python + + class BlogIndexPage(Page): + ... + def serve(self, request): + # Get blogs + blogs = self.blogs + + # Filter by tag + tag = request.GET.get('tag') + if tag: + blogs = blogs.filter(tags__name=tag) + + return render(request, self.template, { + 'self': self, + 'blogs': blogs, + }) + +Here, ``blogs.filter(tags__name=tag)`` invokes a reverse Django queryset filter on the ``BlogPageTag`` model to optionally limit the ``BlogPage`` objects sent to the template for rendering. Now, lets render both sides of the relation by showing the tags associated with an object and a way of showing all of the objects associated with each tag. This could be added to the ``blog_page.html`` template: + +.. code-block:: django + + {% for tag in self.tags.all %} + {{ tag }} + {% endfor %} + +Iterating through ``self.tags.all`` will display each tag associated with ``self``, while the link(s) back to the index make use of the filter option added to the ``BlogIndexPage`` model. A Django query could also use the ``tagged_items`` related name field to get ``BlogPage`` objects associated with a tag. + +This is just one possible way of creating a taxonomy for Wagtail objects. With all of the components for a taxonomy available through Wagtail, you should be able to fulfill even the most exotic taxonomic schemes. From 1d6eee6b36cdaca80d628bf502f616ea8de202f4 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 22 May 2014 19:50:44 -0400 Subject: [PATCH 197/220] fixed pygments thing --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4112e3753..11517ada2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ exclude_patterns = ['_build'] #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -#pygments_style = 'sphinx' +pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] From 40d9aff789a5741406b60c934d8b59ba5ead172a Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 22 May 2014 19:53:49 -0400 Subject: [PATCH 198/220] didnt intend to edit that file --- docs/building_your_site/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/building_your_site/index.rst b/docs/building_your_site/index.rst index a42c40099..fb336e18b 100644 --- a/docs/building_your_site/index.rst +++ b/docs/building_your_site/index.rst @@ -2,14 +2,14 @@ Building your site ================== .. note:: - Documentation currently incomplete and in draft status + 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/ `_ .. toctree:: - :maxdepth: 3 + :maxdepth: 3 - djangodevelopers - frontenddevelopers + djangodevelopers + frontenddevelopers From 9d45d3a4a29c0ee7a94456de96439a6f7ce9f0f6 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 23 May 2014 10:22:32 +0100 Subject: [PATCH 199/220] Update README.rst --- README.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 9a1961b32..a102c0f51 100644 --- a/README.rst +++ b/README.rst @@ -26,9 +26,7 @@ Wagtail is a Django content management system built originally for the `Royal Co * Fast out of the box. `Varnish `_-friendly if you need it * Tests! But not enough; we're working hard to improve this -It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. - -Find out more at `wagtail.io `_. Documentation is at `wagtail.readthedocs.org `_. +Find out more at `wagtail.io `_. Got a question? Ask it on our `Google Group `_. @@ -38,6 +36,15 @@ Getting started * See the `Getting Started `_ docs for installation (with the demo app) on a fresh Debian/Ubuntu box with production-ready dependencies, on OS X and on a Vagrant box. * `Serafeim Papastefanos `_ has written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. +Documentation +~~~~~~~~~~~~~ +`wagtail.readthedocs.org `_. + +Compatibility +~~~~~~~~~~~~~ +Wagtao; supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. + Contributing ~~~~~~~~~~~~ If you're a Python or Django developer, fork the repo and get stuck in! Send us a useful pull request and we'll post you a `t-shirt `_. Our immediate priorities are better docs, more tests, internationalisation and localisation. + From 2edb0695031ffae79705834b23933f6533505aad Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 23 May 2014 10:22:49 +0100 Subject: [PATCH 200/220] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a102c0f51..8e7b174b7 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ Documentation Compatibility ~~~~~~~~~~~~~ -Wagtao; supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. +Wagtail supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. Contributing ~~~~~~~~~~~~ From b5a0e6731b58a6e2be0c26a79c73a1df5580b97c Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 23 May 2014 11:49:39 +0100 Subject: [PATCH 201/220] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8e7b174b7..eddbb85fc 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ Getting started Documentation ~~~~~~~~~~~~~ -`wagtail.readthedocs.org `_. +Available at `wagtail.readthedocs.org `_. and always being updated. Compatibility ~~~~~~~~~~~~~ From 992ef2eec7d4e39e1a1a04e2aa262a0fa1bad712 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Fri, 23 May 2014 15:33:21 +0100 Subject: [PATCH 202/220] Typo fix --- docs/gettingstarted.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index a46ea9664..458de387b 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -166,4 +166,4 @@ Once you've experimented with the demo app and are ready to build your pages via COMMIT; EOF rm -r demo media/images/* media/original_images/* - perl -pi -e"s/('demo',|WAGTAILSEARCH_RESULTS_TEMPLATE)/#\1/" $PROJECT/settingsbase.py + perl -pi -e"s/('demo',|WAGTAILSEARCH_RESULTS_TEMPLATE)/#\1/" $PROJECT/settings/base.py From 94af650c8380688e48af99f3467fb1dac7ae0dea Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Fri, 23 May 2014 23:03:07 -0400 Subject: [PATCH 203/220] Spellchecked, cleaned, and Edit API section expanded Added to edit api, fixed queryset example code, stubbed out Page ref Some cleanup of the edit api doc Some cleanup of the frontend doc Added to edit api docs Added to edit api docs --- docs/building_your_site/djangodevelopers.rst | 145 +++++++++-- .../building_your_site/frontenddevelopers.rst | 16 +- docs/editing_api.rst | 228 +++++++++++++----- docs/model_recipes.rst | 23 ++ docs/snippets.rst | 5 +- docs/wagtail_search.rst | 6 +- 6 files changed, 333 insertions(+), 90 deletions(-) diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst index 49f80c371..5632a419b 100644 --- a/docs/building_your_site/djangodevelopers.rst +++ b/docs/building_your_site/djangodevelopers.rst @@ -4,7 +4,7 @@ For Django developers .. note:: This documentation is currently being written. -Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the contruction of Django models using Wagtail's ``Page`` as a base. +Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the construction of Django models using Wagtail's ``Page`` as a base. Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn't prescribe ways to organize and interrelate your content, but here we've sketched out some strategies for organizing your models. @@ -82,7 +82,7 @@ So what does a Wagtail model definition look like? Here's a model representing a ImageChooserPanel('feed_image'), ] -To keep track of your ``Page``-derived models, it might be helpful to include "Page" as the last part of your classname. ``BlogPage`` defines three properties: ``body``, ``date``, and ``feed_image``. These are a mix of basic Django models (``DateField``), Wagtail fields (``RichTextField``), and a pointer to a Wagtail model (``Image``). +To keep track of your ``Page``-derived models, it might be helpful to include "Page" as the last part of your class name. ``BlogPage`` defines three properties: ``body``, ``date``, and ``feed_image``. These are a mix of basic Django models (``DateField``), Wagtail fields (``RichTextField``), and a pointer to a Wagtail model (``Image``). Next, the ``content_panels`` and ``promote_panels`` lists define the capabilities and layout of the Wagtail admin page edit interface. The lists are filled with "panels" and "choosers", which will provide a fine-grain interface for inputting the model's content. The ``ImageChooserPanel``, for instance, lets one browse the image library, upload new images, and input image metadata. The ``RichTextField`` is the basic field for creating web-ready website rich text, including text formatting and embedded media like images and video. The Wagtail admin offers other choices for fields, Panels, and Choosers, with the option of creating your own to precisely fit your content without workarounds or other compromises. @@ -117,23 +117,28 @@ It might be handy to think of the ``Page``-derived models you want to create as Nodes ````` -Parent nodes on the Wagtail tree probably want to organize and display a browsable index of their descendents. A blog, for instance, needs a way to show a list of individual posts. +Parent nodes on the Wagtail tree probably want to organize and display a browse-able index of their descendants. A blog, for instance, needs a way to show a list of individual posts. A Parent node could provide its own function returning its descendant objects. .. code-block:: python class EventPageIndex(Page): - ... + # ... def events(self): - # Get list of event pages that are descendants of this page - events = EventPage.objects.filter( - live=True, - path__startswith=self.path - ) + # Get list of live event pages that are descendants of this page + events = EventPage.objects.live().descendant_of(self) + + # Filter events list to get ones that are either + # running now or start in the future + events = events.filter(date_from__gte=date.today()) + + # Order by date + events = events.order_by('date_from') + return events -This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail's admin interface (``live=True``) and are descendants of this node. Wagtail will allow the "illogical" placement of child nodes under a parent, so it's necessary for a parent model to index only those children which make sense. +This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail's admin interface (``live()``) and are children of this node (``descendant_of(self)``). By setting a ``subpage_types`` class property in your model, you can specify which models are allowed to be set as children, but Wagtail will allow any ``Page``-derived model by default. Regardless, it's smart for a parent model to provide an index filtered to make sense. Leaves @@ -146,23 +151,18 @@ The model for the leaf could provide a function that traverses the tree in the o .. code-block:: python - class BlogPage(Page): - ... - def blog_index(self): - # Find blog index in ancestors - for ancestor in reversed(self.get_ancestors()): - if isinstance(ancestor.specific, BlogIndexPage): - return ancestor + class EventPage(Page): + # ... + def event_index(self): + # Find closest ancestor which is an event index + return self.get_ancestors().type(EventIndexPage).last() - # No ancestors are blog indexes, just return first blog index in database - return BlogIndexPage.objects.first() - -Since Wagtail doesn't limit what Page-derived classes can be assigned as parents and children, the reverse tree traversal needs to accommodate cases which might not be expected, such as the lack of a "logical" parent to a leaf. +If defined, ``subpage_types`` will also limit the parent models allowed to contain a leaf. If not, Wagtail will allow any combination of parents and leafs to be associated in the Wagtail tree. Like with index pages, it's a good idea to make sure that the index is actually of the expected model to contain the leaf. Other Relationships ``````````````````` -Your ``Page``-derived models might have other interrelationships which extend the basic Wagtail tree or depart from it entirely. You could provide functions to navigate between siblings, such as a "Next Post" link on a blog page (``post->post->post``). It might make sense for subtrees to interrelate, such as in a discussion forum (``forum->post->replies``) Skipping across the hierarchy might make sense, too, as all objects of a certain model class might interrelate regardless of their ancestors (``events = EventPage.objects.all``). Since there's no restriction on the combination of model classes that can be used at any point in the tree, and it's largely up to the models to define their interrelations, the possibilities are really endless. +Your ``Page``-derived models might have other interrelationships which extend the basic Wagtail tree or depart from it entirely. You could provide functions to navigate between siblings, such as a "Next Post" link on a blog page (``post->post->post``). It might make sense for subtrees to interrelate, such as in a discussion forum (``forum->post->replies``) Skipping across the hierarchy might make sense, too, as all objects of a certain model class might interrelate regardless of their ancestors (``events = EventPage.objects.all``). It's largely up to the models to define their interrelations, the possibilities are really endless. Anatomy of a Wagtail Request @@ -173,10 +173,105 @@ For going beyond the basics of model definition and interrelation, it might help #. Django gets a request and routes through Wagtail's URL dispatcher definitions #. Starting from the root content piece, Wagtail traverses the page tree, letting the model for each piece of content along the path decide how to ``route()`` the next step in the path. #. A model class decides that routing is done and it's now time to ``serve()`` content. - #. The model constructs a context, finds a template to pass it to, and renders the content. - #. The templates are rendered and the response object is sent back to the requester. + #. ``serve()`` constructs a context using ``get_context()`` + #. ``serve()`` finds a template to pass it to using ``get_template()`` + #. A response object is returned by ``serve()`` and Django responds to the requester. + +You can apply custom behavior to this process by overriding ``Page`` class methods such as ``route()`` and ``serve()`` in your own models. For examples, see :ref:`model_recipes`. + + +Page Properties and Methods Reference +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to the model fields provided, ``Page`` has many properties and methods that you may wish to reference, use, or override in creating your own models. Those listed here are relatively straightforward to use, but consult the Wagtail source code for a full view of what's possible. + +Properties: + +specific +url +full_url +relative_url +has_unpublished_changes +status_string +subpage_types +indexed_fields + + +Methods: + +route +serve +get_context +get_template +is_navigable +get_other_siblings +get_ancestors +get_descendants +get_siblings +search +get_page_modes +show_as_mode + + + + + +Page Queryset Methods +~~~~~~~~~~~~~~~~~~~~~ + +The ``Page`` class uses a custom Django model manager which provides these methods for structuring queries on ``Page`` objects. + +get_query_set() + return PageQuerySet(self.model).order_by('path') + +live(self): + return self.get_query_set().live() + +not_live(self): + return self.get_query_set().not_live() + +page(self, other): + return self.get_query_set().page(other) + +not_page(self, other): + return self.get_query_set().not_page(other) + +descendant_of(self, other, inclusive=False): + return self.get_query_set().descendant_of(other, inclusive) + +not_descendant_of(self, other, inclusive=False): + return self.get_query_set().not_descendant_of(other, inclusive) + +child_of(self, other): + return self.get_query_set().child_of(other) + +not_child_of(self, other): + return self.get_query_set().not_child_of(other) + +ancestor_of(self, other, inclusive=False): + return self.get_query_set().ancestor_of(other, inclusive) + +not_ancestor_of(self, other, inclusive=False): + return self.get_query_set().not_ancestor_of(other, inclusive) + +parent_of(self, other): + return self.get_query_set().parent_of(other) + +not_parent_of(self, other): + return self.get_query_set().not_parent_of(other) + +sibling_of(self, other, inclusive=False): + return self.get_query_set().sibling_of(other, inclusive) + +not_sibling_of(self, other, inclusive=False): + return self.get_query_set().not_sibling_of(other, inclusive) + +type(self, model): + return self.get_query_set().type(model) + +not_type(self, model): + return self.get_query_set().not_type(model) -You can apply custom behavior to this process by overriding the ``route()`` and ``serve()`` methods of the ``Page`` class in your own models. Site diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 033c64e9e..d39bd8c33 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -19,7 +19,7 @@ Displaying Pages ========================== Template Location ------------------ +~~~~~~~~~~~~~~~~~ For each of your ``Page``-derived models, Wagtail will look for a template in the following location, relative to your project root:: @@ -36,7 +36,7 @@ Class names are converted from camel case to underscores. For example, the templ Self ----- +~~~~ By default, the context passed to a model's template consists of two properties: ``self`` and ``request``. ``self`` is the model object being displayed. ``request`` is the normal Django request object. So, to include the title of a ``Page``, use ``{{ self.title }}``. @@ -45,9 +45,8 @@ 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 `. @@ -55,7 +54,7 @@ Unlike other CMS, adding images to a page does not involve choosing a "version" 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 `. +Read more about the image manipulation syntax here :ref:`image_tag`. ======================== @@ -64,7 +63,9 @@ 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: + +.. _image_tag: + Images (tag) ~~~~~~~~~~~~ @@ -123,8 +124,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 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. +To request the "original" version of an image, it is suggested you rely on the lack of upscaling 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: Rich text (filter) ~~~~~~~~~~~~~~~~~~ diff --git a/docs/editing_api.rst b/docs/editing_api.rst index 2985a1229..99e859437 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -27,11 +27,11 @@ There are three types of panels: ``FieldPanel( field_name, classname=None )`` This is the panel used for basic Django field types. ``field_name`` is the name of the class property used in your model definition. ``classname`` is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields. The CSS class ``full`` can be used to format the panel so it covers the full width of the Wagtail page editor. The CSS class ``title`` can be used to mark a field as the source for auto-generated slug strings. - ``MultiFieldPanel( panel_list, heading )`` + ``MultiFieldPanel( children, heading="", classname=None )`` This panel condenses several ``FieldPanel`` s or choosers, from a list or tuple, under a single ``heading`` string. ``InlinePanel( base_model, relation_name, panels=None, label='', help_text='' )`` - This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. This is a very powerful, but tricky feature which will take some space to cover, so we'll skip over it for now. For a full explaination on the usage of ``InlinePanel``, see :ref:`inline_panels`. + This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. This is a very powerful, but tricky feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`. Wagtail provides a tabbed interface to help organize panels. ``content_panels`` is the main tab, used for the meat of your model content. The other, ``promote_panels``, is suggested for organizing metadata about the content, such as SEO information and other machine-readable information. Since you're writing the panel definitions, you can organize them however you want. @@ -65,8 +65,7 @@ Let's look at an example of a panel definition: MultiFieldPanel(COMMON_PANELS, "Common page configuration"), ] - - +After the ``Page``-derived class definition, just add lists of panel definitions to order and organize the Wagtail page editing interface for your model. Built-in Fields and Choosers @@ -85,100 +84,219 @@ Wagtail provides a general-purpose WYSIWYG editor for creating rich text content .. code-block:: python from wagtail.wagtailcore.fields import RichTextField - ... + from wagtail.wagtailadmin.edit_handlers import FieldPanel + # ... class BookPage(Page): book_text = RichTextField() + BookPage.content_panels = [ + FieldPanel('body', classname="full"), + # ... + ] +``RichTextField`` inherits from Django's basic ``TextField`` field, so you can pass any field parameters into ``RichTextField`` as if using a normal Django field. This field does not need a special panel and can be defined with ``FieldPanel``. -If you're interested in extending the capabilities of the Wagtail editor, See :ref:`extending_wysiwyg`. +However, template output from ``RichTextField`` is special and need to be filtered to preserve embedded content. See :ref:`rich-text-filter`. + +If you're interested in extending the capabilities of the Wagtail WYSIWYG editor (hallo.js), See :ref:`extending_wysiwyg`. Images ------ +One of the features of Wagtail is a unified image library, which you can access in your models through the ``Image`` model and the ``ImageChooserPanel`` chooser. Here's how: + .. code-block:: python from wagtail.wagtailimages.models import Image + from wagtail.wagtailimages.edit_handlers import ImageChooserPanel + # ... + class BookPage(Page): + cover = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + BookPage.content_panels = [ + ImageChooserPanel('cover'), + # ... + ] - feed_image = models.ForeignKey( - 'wagtailimages.Image', - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) +Django's default behavior is to "cascade" deletions through a ForeignKey relationship, which is probably not what you want happening. This is why the ``null``, ``blank``, and ``on_delete`` parameters should be set to allow for an empty field. (See `Django model field reference (on_delete)`_ ). ``ImageChooserPanel`` takes only one argument: the name of the field. + +.. _Django model field reference (on_delete): https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete + +Displaying ``Image`` objects in a template requires the use of a template tag. See :ref:`image_tag`. Documents --------- +For files in other formats, Wagtail provides a generic file store through the ``Document`` model: + .. code-block:: python from wagtail.wagtaildocs.models import Document + from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel + # ... + class BookPage(Page): + book_file = models.ForeignKey( + 'wagtaildocs.Document', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) - link_document = models.ForeignKey( - 'wagtaildocs.Document', - null=True, - blank=True, - related_name='+' - ) + BookPage.content_panels = [ + DocumentChooserPanel('book_file'), + # ... + ] + +As with images, Wagtail documents should also have the appropriate extra parameters to prevent cascade deletions across a ForeignKey relationship. ``DocumentChooserPanel`` takes only one argument: the name of the field. + +Documents can be used directly in templates without tags or filters. Its properties are: + +.. glossary:: + + ``title`` + The title of the document. + + ``url`` + URL to the file. + + ``created_at`` + The date and time the document was created (DateTime). + + ``filename`` + The filename of the file. + + ``file_extension`` + The extension of the file. + + ``tags`` + A ``TaggableManager`` which keeps track of tags associated with the document (uses the ``django-taggit`` module). Pages and Page-derived Models ----------------------------- +You can explicitly link ``Page``-derived models together using the ``Page`` model and ``PageChooserPanel``. + .. code-block:: python from wagtail.wagtailcore.models import Page + from wagtail.wagtailadmin.edit_handlers import PageChooserPanel + # ... + class BookPage(Page): + publisher = models.ForeignKey( + 'wagtailcore.Page', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+', + ) - page = models.ForeignKey( - 'wagtailcore.Page', - related_name='+', - null=True, - blank=True - ) + BookPage.content_panels = [ + PageChooserPanel('related_page', 'demo.PublisherPage'), + # ... + ] -Can also use more specific models. +``PageChooserPanel`` takes two arguments: a field name and an optional page type. Specifying a page type (in the form of an ``"appname.modelname"`` string) will filter the chooser to display only pages of that type. -Snippets (and Basic Django Models?) +Snippets -------- -Snippets are not not subclasses, so you must include the model class directly. A chooser is provided which takes the snippet class. +Snippets are not subclasses, so you must include the model class directly. A chooser is provided which takes the field name snippet class. .. code-block:: python - advert = models.ForeignKey( - 'demo.Advert', - related_name='+' - ) + from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel + # ... + class BookPage(Page): + advert = models.ForeignKey( + 'demo.Advert', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + BookPage.content_panels = [ + SnippetChooserPanel('advert', Advert), + # ... + ] + +See :ref:`snippets` for more information. - - - - - - - - - - - - -PageChooserPanel -~~~~~~~~~~~~~~~~ - -ImageChooserPanel -~~~~~~~~~~~~~~~~~ - -DocumentChooserPanel -~~~~~~~~~~~~~~~~~~~~ - -SnippetChooserPanel +Field Customization ~~~~~~~~~~~~~~~~~~~ +By adding CSS classnames to your panel definitions or adding extra parameters to your field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail's page editing interface takes much of its behavior from Django's admin, so you may find many options for customization covered there. (See `Django model field reference`_ ). + +.. _Django model field reference:https://docs.djangoproject.com/en/dev/ref/models/fields/ + + +Full-Width Input +---------------- + +Use ``classname="full"`` to make a field (input element) stretch the full width of the Wagtail page editor. This will not work if the field is encapsulated in a ``MultiFieldPanel``, which places its child fields into a formset. + + +Required Fields +--------------- + +To make input or chooser selection manditory for a field, add ``blank=False`` to its model definition. (See `Django model field reference (blank)`_ ). + +.. _Django model field reference (blank): https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.Field.blank + + +Hiding Fields +------------- + +Without a panel definition, a default form field (without label) will be used to represent your fields. If you intend to hide a field on the Wagtail page editor, define the field with ``editable=False`` (See `Django model field reference (editable)`_ ). + +.. _Django model field reference (editable): https://docs.djangoproject.com/en/dev/ref/models/fields/#editable + + + + + + + + + + + + +MultiFieldPanel +~~~~~~~~~~~~~~~ + +.. code-block:: python + + BOOK_FIELD_COLLECTION = [ + ImageChooserPanel('cover'), + DocumentChooserPanel('book_file'), + PageChooserPanel('publisher'), + ] + + BookPage.content_panels = [ + MultiFieldPanel( + BOOK_FIELD_COLLECTION, + heading="Collection of Book Fields", + classname="collapsible collapsed" + ), + # ... + ] + + + + .. _inline_panels: @@ -193,6 +311,8 @@ The ``django-modelcluster`` module allows for streamlined relation of extra mode Extending the WYSIWYG Editor (hallo.js) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding hallo.js plugins: +https://github.com/torchbox/wagtail/commit/1ecc215759142e6cafdacb185bbfd3f8e9cd3185 Edit Handler API diff --git a/docs/model_recipes.rst b/docs/model_recipes.rst index db88daf7c..fe8a89e32 100644 --- a/docs/model_recipes.rst +++ b/docs/model_recipes.rst @@ -1,4 +1,6 @@ +.. _model_recipes: + Model Recipes ============= @@ -174,3 +176,24 @@ Here, ``blogs.filter(tags__name=tag)`` invokes a reverse Django queryset filter Iterating through ``self.tags.all`` will display each tag associated with ``self``, while the link(s) back to the index make use of the filter option added to the ``BlogIndexPage`` model. A Django query could also use the ``tagged_items`` related name field to get ``BlogPage`` objects associated with a tag. This is just one possible way of creating a taxonomy for Wagtail objects. With all of the components for a taxonomy available through Wagtail, you should be able to fulfill even the most exotic taxonomic schemes. + + +Custom Page Contexts by Overriding get_context() +------------------------------------------------ + + + +Load Alternate Templates by Overriding get_template() +----------------------------------------------------- + + + +Page Modes +---------- + +get_page_modes +show_as_mode + + + + diff --git a/docs/snippets.rst b/docs/snippets.rst index 14d62b793..a40b57d94 100644 --- a/docs/snippets.rst +++ b/docs/snippets.rst @@ -1,9 +1,12 @@ + +.. _snippets: + Snippets ======== Snippets are pieces of content which do not necessitate a full webpage to render. They could be used for making secondary content, such as headers, footers, and sidebars, editable in the Wagtail admin. Snippets are models which do not inherit the ``Page`` class and are thus not organized into the Wagtail tree, but can still be made editable by assigning panels and identifying the model as a snippet with ``register_snippet()``. -Snippets are not searchable or orderable in the Wagtail admin, so decide carefully if the content type you would want to build into a snippet might be more suited to a page. +Snippets are not search-able or order-able in the Wagtail admin, so decide carefully if the content type you would want to build into a snippet might be more suited to a page. Snippet Models -------------- diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index fff462349..8fa263801 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -127,7 +127,7 @@ Lets also add a simple interface for the search with a ```` element to ga
-Finally, we'll use JQuery to make the aynchronous requests and handle the interactivity: +Finally, we'll use JQuery to make the asynchronous requests and handle the interactivity: .. code-block:: guess @@ -186,7 +186,7 @@ Results are returned as a JSON object with this structure: ] } -What if you wanted access to the rest of the results context or didn't feel like using JSON? Wagtail also provides a generalized AJAX interface where you can use your own template to serve results asyncronously. +What if you wanted access to the rest of the results context or didn't feel like using JSON? Wagtail also provides a generalized AJAX interface where you can use your own template to serve results asynchronously. The AJAX interface uses the same view as the normal HTML search, ``wagtailsearch_search``, but will serve different results if Django classifies the request as AJAX (``request.is_ajax()``). Another entry in your project settings will let you override the template used to serve this response: @@ -194,7 +194,7 @@ The AJAX interface uses the same view as the normal HTML search, ``wagtailsearch WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'myapp/includes/search_listing.html' -In this template, you'll have access to the same context variablies provided to the HTML template. You could provide a template in JSON format with extra properties, such as ``query.hits`` and editor's picks, or render an HTML snippet that can go directly into your results ``
``. If you need more flexibility, such as multiple formats/templates based on differing requests, you can set up a custom search view. +In this template, you'll have access to the same context variables provided to the HTML template. You could provide a template in JSON format with extra properties, such as ``query.hits`` and editor's picks, or render an HTML snippet that can go directly into your results ``
``. If you need more flexibility, such as multiple formats/templates based on differing requests, you can set up a custom search view. .. _editors-picks: From fd5f07eb8ab61d718307aed01ba0d096002b13ff Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 27 May 2014 13:29:49 +0100 Subject: [PATCH 204/220] Fixed CSS selector to search form on page chooser fixes #263 --- .../templates/wagtailadmin/chooser/_search_behaviour.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js b/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js index 9c3f77154..a3fee7722 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js +++ b/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js @@ -1,6 +1,6 @@ -modal.ajaxifyForm($('form.search-bar', modal.body)); +modal.ajaxifyForm($('form.search-form', modal.body)); -var searchUrl = $('form.search-bar', modal.body).attr('action'); +var searchUrl = $('form.search-form', modal.body).attr('action'); function search() { $.ajax({ From 9bbe3f1c191e712e81b53c019cdb2535ee249f82 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Tue, 27 May 2014 16:54:06 +0100 Subject: [PATCH 205/220] Installation comment to address wagtail issue 220. --- scripts/install/debian.sh | 2 ++ scripts/install/ubuntu.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/install/debian.sh b/scripts/install/debian.sh index d29d5acdc..ee08191ac 100644 --- a/scripts/install/debian.sh +++ b/scripts/install/debian.sh @@ -1,5 +1,7 @@ # Production-configured Wagtail installation. # BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE! +# For a non-dummy email backend configure Django's EMAIL_BACKEND +# in settings/production.py post-installation. # Tested on Debian 7.0. # Tom Dyson and Neal Todd diff --git a/scripts/install/ubuntu.sh b/scripts/install/ubuntu.sh index c713cefa9..299e67a8d 100644 --- a/scripts/install/ubuntu.sh +++ b/scripts/install/ubuntu.sh @@ -1,5 +1,7 @@ # Production-configured Wagtail installation. # BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE! +# For a non-dummy email backend configure Django's EMAIL_BACKEND +# in settings/production.py post-installation. # Tested on Ubuntu 13.04 and 13.10. # Tom Dyson and Neal Todd From 9ec2c902b5f277f2701f837e27ece23df854bc65 Mon Sep 17 00:00:00 2001 From: Tom Talbot Date: Tue, 27 May 2014 17:06:37 +0100 Subject: [PATCH 206/220] Fix #175. Change to image chooser: 'Upload' tab now retains focus if submit action returns a form error. --- .../templates/wagtailimages/chooser/chooser.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.html b/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.html index 03b4554c8..1fcadb3f9 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.html +++ b/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.html @@ -5,13 +5,13 @@ {% if uploadform %} {% endif %}
-
{% if uploadform %} -
+
{% csrf_token %}
    From 6314c75ca6013eca502eca02fb9d0cadb66ba1b0 Mon Sep 17 00:00:00 2001 From: Tom Talbot Date: Wed, 28 May 2014 11:08:13 +0100 Subject: [PATCH 207/220] Fix #268: search input now appears on image chooser after form validation error --- wagtail/wagtailimages/views/chooser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailimages/views/chooser.py b/wagtail/wagtailimages/views/chooser.py index 510174c4c..221a75581 100644 --- a/wagtail/wagtailimages/views/chooser.py +++ b/wagtail/wagtailimages/views/chooser.py @@ -113,6 +113,8 @@ def chooser_upload(request): Image = get_image_model() ImageForm = get_image_form() + searchform = SearchForm() + if request.POST: image = Image(uploaded_by_user=request.user) form = ImageForm(request.POST, request.FILES, instance=image) @@ -138,7 +140,7 @@ def chooser_upload(request): return render_modal_workflow( request, 'wagtailimages/chooser/chooser.html', 'wagtailimages/chooser/chooser.js', - {'images': images, 'uploadform': form} + {'images': images, 'uploadform': form, 'searchform': searchform} ) From 580e9130ad22ac01515902f06eb7efe5600c29a3 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 28 May 2014 12:05:35 +0100 Subject: [PATCH 208/220] Ref issue 177, Elasticsearch timeout on Travis: Support passing configuration arguments to Elasticsearch. Also documenting Elasticsearch setup in Wagtail. --- docs/wagtail_search.rst | 27 +++++++++++++++++++ runtests.py | 2 ++ .../wagtailsearch/backends/elasticsearch.py | 20 ++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 8fa263801..c03f74fcf 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -220,6 +220,30 @@ The default DB search backend uses Django's ``__icontains`` filter. Elasticsearch Backend ````````````````````` +Prerequisites are the Elasticsearch service itself and, via pip, the `elasticutils`_ and `pyelasticsearch`_ packages: + +.. code-block:: guess + + pip install elasticutils pyelasticsearch + +NB: The dependency on pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_. + +The backend is configured in settings: + +.. code-block:: python + + WAGTAILSEARCH_BACKENDS = { + 'default': { + 'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch', + 'URL': ['http://localhost:9200'], + 'INDEX': 'wagtail', + 'TIMEOUT': 5, + 'FORCE_NEW': False, + } + } + +Other than `BACKEND` the other keys are optional and default to the values shown. In addition any other keys are passed directly to the Elasticsearch constructor as keyword arguments (e.g. `'max_retries': 1`). + If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Searchly`_, who offer a free account suitable for testing and development. To use Searchly: - Sign up for an account at `dashboard.searchly.com/users/sign\_up`_ @@ -229,6 +253,9 @@ If you prefer not to run an Elasticsearch server in development or production, t your local settings - Run ``./manage.py update_index`` +.. _elasticutuils: http://elasticutils.readthedocs.org +.. _pyelasticsearch: http://pyelasticsearch.readthedocs.org +.. _elasticsearch-py: http://elasticsearch-py.readthedocs.org .. _Searchly: http://www.searchly.com/ .. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up diff --git a/runtests.py b/runtests.py index 1577d73b5..e3942646a 100755 --- a/runtests.py +++ b/runtests.py @@ -26,6 +26,8 @@ if not settings.configured: if has_elasticsearch: WAGTAILSEARCH_BACKENDS['elasticsearch'] = { 'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch', + 'TIMEOUT': 10, + 'max_retries': 1, } settings.configure( diff --git a/wagtail/wagtailsearch/backends/elasticsearch.py b/wagtail/wagtailsearch/backends/elasticsearch.py index b308add0b..d2b58d564 100644 --- a/wagtail/wagtailsearch/backends/elasticsearch.py +++ b/wagtail/wagtailsearch/backends/elasticsearch.py @@ -1,5 +1,4 @@ from django.db import models -from django.conf import settings from elasticutils import get_es, S @@ -58,12 +57,23 @@ class ElasticSearch(BaseSearch): super(ElasticSearch, self).__init__(params) # Get settings - self.es_urls = params.get('URLS', ['http://localhost:9200']) - self.es_index = params.get('INDEX', 'wagtail') + self.es_urls = params.pop('URLS', ['http://localhost:9200']) + self.es_index = params.pop('INDEX', 'wagtail') + self.es_timeout = params.pop('TIMEOUT', 5) + self.es_force_new = params.pop('FORCE_NEW', False) # Get ElasticSearch interface - self.es = get_es(urls=self.es_urls) - self.s = S().es(urls=self.es_urls).indexes(self.es_index) + # Any remaining params are passed into the ElasticSearch constructor + self.es = get_es( + urls=self.es_urls, + timeout=self.es_timeout, + force_new=self.es_force_new, + **params) + self.s = S().es( + urls=self.es_urls, + timeout=self.es_timeout, + force_new=self.es_force_new, + **params).indexes(self.es_index) def reset_index(self): # Delete old index From 0c2163734cbcdf94d67aab5efe88d0881ea4b6a4 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 28 May 2014 12:11:57 +0100 Subject: [PATCH 209/220] Elasticsearch documentation tweak --- docs/wagtail_search.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index c03f74fcf..250a3bcb3 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -242,7 +242,7 @@ The backend is configured in settings: } } -Other than `BACKEND` the other keys are optional and default to the values shown. In addition any other keys are passed directly to the Elasticsearch constructor as keyword arguments (e.g. `'max_retries': 1`). +Other than `BACKEND` the other keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticutils. In addition, any other keys are passed directly to the Elasticsearch constructor as keyword arguments (e.g. ``'max_retries': 1``). If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Searchly`_, who offer a free account suitable for testing and development. To use Searchly: @@ -253,7 +253,7 @@ If you prefer not to run an Elasticsearch server in development or production, t your local settings - Run ``./manage.py update_index`` -.. _elasticutuils: http://elasticutils.readthedocs.org +.. _elasticututils: http://elasticutils.readthedocs.org .. _pyelasticsearch: http://pyelasticsearch.readthedocs.org .. _elasticsearch-py: http://elasticsearch-py.readthedocs.org .. _Searchly: http://www.searchly.com/ From 700fcac8133bc1e8ee28305e917942e2f9318a8d Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 28 May 2014 12:14:30 +0100 Subject: [PATCH 210/220] Elasticsearch documentation typo fix --- docs/wagtail_search.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 250a3bcb3..3c432f5d6 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -253,7 +253,7 @@ If you prefer not to run an Elasticsearch server in development or production, t your local settings - Run ``./manage.py update_index`` -.. _elasticututils: http://elasticutils.readthedocs.org +.. _elasticutils: http://elasticutils.readthedocs.org .. _pyelasticsearch: http://pyelasticsearch.readthedocs.org .. _elasticsearch-py: http://elasticsearch-py.readthedocs.org .. _Searchly: http://www.searchly.com/ From c5effdc641f69f40fb8f986133399860565539e0 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 28 May 2014 12:51:19 +0100 Subject: [PATCH 211/220] Elasticsearch documentation update --- docs/wagtail_search.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 3c432f5d6..ae9c73dca 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -242,15 +242,14 @@ The backend is configured in settings: } } -Other than `BACKEND` the other keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticutils. In addition, any other keys are passed directly to the Elasticsearch constructor as keyword arguments (e.g. ``'max_retries': 1``). +Other than ``BACKEND`` the keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticutils. In addition, any other keys are passed directly to the Elasticsearch constructor as case-sensitive keyword arguments (e.g. ``'max_retries': 1``). If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Searchly`_, who offer a free account suitable for testing and development. To use Searchly: - Sign up for an account at `dashboard.searchly.com/users/sign\_up`_ - Use your Searchly dashboard to create a new index, e.g. 'wagtaildemo' - Note the connection URL from your Searchly dashboard -- Update ``WAGTAILSEARCH_ES_URLS`` and ``WAGTAILSEARCH_ES_INDEX`` in - your local settings +- Configure ``URL`` and ``INDEX`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS`` - Run ``./manage.py update_index`` .. _elasticutils: http://elasticutils.readthedocs.org From 07733c83ec9079926f3c81f255765b150c42b1fc Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 28 May 2014 12:58:10 +0100 Subject: [PATCH 212/220] Elasticsearch documentation typo fix --- docs/wagtail_search.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index ae9c73dca..0e260c062 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -235,7 +235,7 @@ The backend is configured in settings: WAGTAILSEARCH_BACKENDS = { 'default': { 'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch', - 'URL': ['http://localhost:9200'], + 'URLS': ['http://localhost:9200'], 'INDEX': 'wagtail', 'TIMEOUT': 5, 'FORCE_NEW': False, @@ -249,7 +249,7 @@ If you prefer not to run an Elasticsearch server in development or production, t - Sign up for an account at `dashboard.searchly.com/users/sign\_up`_ - Use your Searchly dashboard to create a new index, e.g. 'wagtaildemo' - Note the connection URL from your Searchly dashboard -- Configure ``URL`` and ``INDEX`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS`` +- Configure ``URLS`` and ``INDEX`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS`` - Run ``./manage.py update_index`` .. _elasticutils: http://elasticutils.readthedocs.org From 9e294cb030194ce7f595da20eb1f97c6e89fc6a2 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Wed, 28 May 2014 13:22:32 +0100 Subject: [PATCH 213/220] Documentation tweaks --- docs/building_your_site/djangodevelopers.rst | 44 +++++++++----------- docs/building_your_site/index.rst | 6 +-- docs/wagtail_search.rst | 27 ++++++------ 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst index 5632a419b..b4899d70f 100644 --- a/docs/building_your_site/djangodevelopers.rst +++ b/docs/building_your_site/djangodevelopers.rst @@ -187,33 +187,29 @@ In addition to the model fields provided, ``Page`` has many properties and metho Properties: -specific -url -full_url -relative_url -has_unpublished_changes -status_string -subpage_types -indexed_fields - +* specific +* url +* full_url +* relative_url +* has_unpublished_changes +* status_string +* subpage_types +* indexed_fields Methods: -route -serve -get_context -get_template -is_navigable -get_other_siblings -get_ancestors -get_descendants -get_siblings -search -get_page_modes -show_as_mode - - - +* route +* serve +* get_context +* get_template +* is_navigable +* get_other_siblings +* get_ancestors +* get_descendants +* get_siblings +* search +* get_page_modes +* show_as_mode Page Queryset Methods diff --git a/docs/building_your_site/index.rst b/docs/building_your_site/index.rst index fb336e18b..da5cf4f2d 100644 --- a/docs/building_your_site/index.rst +++ b/docs/building_your_site/index.rst @@ -2,11 +2,7 @@ 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/ `_ + This documentation is currently incomplete. .. toctree:: :maxdepth: 3 diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 0e260c062..642a5e741 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -1,14 +1,14 @@ Search ====== -Wagtail provides a very comprehensive, extensible, and flexible search interface. In addition, it provides ways to promote search results through "Editor's Picks." Wagtail also collects simple statistics on queries made through the search interface. +Wagtail provides a comprehensive and extensible search interface. In addition, it provides ways to promote search results through "Editor's Picks." Wagtail also collects simple statistics on queries made through the search interface. Default Page Search ------------------- -Wagtail provides a default frontend search interface which indexes the ``title`` field common to all ``Page``-derived models. Lets take a look at all the components of the search interface. +Wagtail provides a default frontend search interface which indexes the ``title`` field common to all ``Page``-derived models. Let's take a look at all the components of the search interface. -The most basic search functionality just needs a search box which submits a request. Since this will be reused throughout the site, lets put it in ``mysite/includes/search_box.html`` and then use ``{% include ... %}`` to weave it into templates: +The most basic search functionality just needs a search box which submits a request. Since this will be reused throughout the site, let's put it in ``mysite/includes/search_box.html`` and then use ``{% include ... %}`` to weave it into templates: .. code-block:: django @@ -17,15 +17,15 @@ The most basic search functionality just needs a search box which submits a requ -The form is submitted to the url of the ``wagtailsearch_search`` view, with the search terms variable ``q``. The view will use its own (very) basic search results template. +The form is submitted to the url of the ``wagtailsearch_search`` view, with the search terms variable ``q``. The view will use its own basic search results template. -Lets use our own template for the results, though. First, in your project's ``settings.py``, define a path to your template: +Let's use our own template for the results, though. First, in your project's ``settings.py``, define a path to your template: .. code-block:: python WAGTAILSEARCH_RESULTS_TEMPLATE = 'mysite/search_results.html' -Next, lets look at the template itself: +Next, let's look at the template itself: .. code-block:: django @@ -82,7 +82,7 @@ Editor's Picks are a way of explicitly linking relevant content to search terms, ``editors_pick.description`` The description entered when choosing the pick, perhaps explaining why the page is relevant to the search terms. -Putting this all together, a block of your search results template displaying Editor's Picks might look like this: +Putting this all together, a block of your search results template displaying editor's Picks might look like this: .. code-block:: django @@ -106,10 +106,10 @@ Putting this all together, a block of your search results template displaying Ed {% endif %} {% endwith %} -Asyncronous Search with JSON and AJAX -------------------------------------- +Asynchronous Search with JSON and AJAX +-------------------------------------- -Wagtail's provides JSON search results when queries are made to the ``wagtailsearch_suggest`` view. To take advantage of it, we need a way to make that URL available to a static script. Instead of hard-coding it, lets set a global variable in our ``base.html``: +Wagtail provides JSON search results when queries are made to the ``wagtailsearch_suggest`` view. To take advantage of it, we need a way to make that URL available to a static script. Instead of hard-coding it, let's set a global variable in our ``base.html``: .. code-block:: django @@ -117,7 +117,7 @@ Wagtail's provides JSON search results when queries are made to the ``wagtailsea var wagtailJSONSearchURL = "{% url 'wagtailsearch_suggest' %}"; -Lets also add a simple interface for the search with a ```` element to gather search terms and a ``
    `` to display the results: +Now add a simple interface for the search with a ```` element to gather search terms and a ``
    `` to display the results: .. code-block:: html @@ -130,7 +130,7 @@ Lets also add a simple interface for the search with a ```` element to ga Finally, we'll use JQuery to make the asynchronous requests and handle the interactivity: .. code-block:: guess - + $(function() { // cache the elements @@ -226,7 +226,8 @@ Prerequisites are the Elasticsearch service itself and, via pip, the `elasticuti pip install elasticutils pyelasticsearch -NB: The dependency on pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_. +.. note:: + The dependency on pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_. The backend is configured in settings: From 0393128509558d1dff525acbc63db7b5b86e3d8f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 15:41:40 +0100 Subject: [PATCH 214/220] update changelog / contributors for #267 --- CHANGELOG.txt | 1 + CONTRIBUTORS.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8b69d346a..27db208fb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -28,6 +28,7 @@ Changelog * 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 + * Fix: 'Upload' tab in image chooser now retains focus if submit action returns a form error. 0.2 (11.03.2014) ~~~~~~~~~~~~~~~~ diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 3549e8407..2edc1c079 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -26,6 +26,7 @@ Contributors * Ben Emery * David Smith * Ben Margolis +* Tom Talbot Translators =========== From 545723cd8b69b18043a8637681fe30f97c8bbb09 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 15:44:50 +0100 Subject: [PATCH 215/220] changelog entry for #269 --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 27db208fb..2d6a9ca53 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -29,6 +29,7 @@ Changelog * 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 * Fix: 'Upload' tab in image chooser now retains focus if submit action returns a form error. + * Fix: Search input now appears on image chooser after form validation error. 0.2 (11.03.2014) ~~~~~~~~~~~~~~~~ From c7731ad02047b43a7267b01abac9081de1be5d28 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 15:56:01 +0100 Subject: [PATCH 216/220] Additional contributors / translators --- CHANGELOG.txt | 3 ++- CONTRIBUTORS.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2d6a9ca53..9016e93b3 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -9,6 +9,7 @@ Changelog * 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 + * Expanded developer 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 * Added 'slugurl' template tag to output the URL of a page with a given slug @@ -16,7 +17,7 @@ Changelog * Added 'insert_editor_css' and 'insert_editor_js' hooks for passing in custom CSS / JS to the editor interface * Made JPEG compression level configurable through the IMAGE_COMPRESSION_QUALITY setting, and increased default to 85 * Added document_served signal which gets fired when a document is downloaded - * Added translation for Portuguese Brazil + * Added translations for Portuguese Brazil and Traditional Chinese (Taiwan). * Made compatible with Python 2.6 * 'richtext' template filter now wraps output in
    , to assist in styling * Embeds now save author_name and provider_name if set by oEmbed provider diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 2edc1c079..d7222307d 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -27,6 +27,7 @@ Contributors * David Smith * Ben Margolis * Tom Talbot +* Jeffrey Hearn Translators =========== @@ -34,7 +35,7 @@ Translators * Basque: Unai Zalakain * Bulgarian: Lyuboslav Petrov * Catalan: David Llop -* Chinese: Lihan Li +* Chinese: Lihan Li, tulpar008, wwj718 * French: Sylvain Fankhauser * Galician: fooflare * German: Karl Sander, Johannes Spielmann @@ -44,3 +45,4 @@ Translators * Portuguese Brazil: Gilson Filho * Romanian: Dan Braghis * Spanish: Unai Zalakain, fooflare +* Traditional Chinese (Taiwan): wdv4758h From 7bd8c160227d61295f0a22b0f399ebdcbfc2a36e Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 15:59:58 +0100 Subject: [PATCH 217/220] change deprecated mimetype args to HttpResponse to content_type --- wagtail/wagtailadmin/modal_workflow.py | 2 +- wagtail/wagtailadmin/views/tags.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/modal_workflow.py b/wagtail/wagtailadmin/modal_workflow.py index a4d86f501..998dfdec3 100644 --- a/wagtail/wagtailadmin/modal_workflow.py +++ b/wagtail/wagtailadmin/modal_workflow.py @@ -23,4 +23,4 @@ def render_modal_workflow(request, html_template, js_template, template_vars={}) response_text = "{%s}" % ','.join(response_keyvars) - return HttpResponse(response_text, mimetype="text/javascript") + return HttpResponse(response_text, content_type="text/javascript") diff --git a/wagtail/wagtailadmin/views/tags.py b/wagtail/wagtailadmin/views/tags.py index 31281d958..bef3043ae 100644 --- a/wagtail/wagtailadmin/views/tags.py +++ b/wagtail/wagtailadmin/views/tags.py @@ -16,4 +16,4 @@ def autocomplete(request): response = json.dumps([tag.name for tag in tags]) - return HttpResponse(response, mimetype='text/javascript') + return HttpResponse(response, content_type='text/javascript') From 3042d6ef6a2938f190c13870232199df95d4b516 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 16:27:53 +0100 Subject: [PATCH 218/220] set USE_TZ=True in runtests to handle fixtures correctly on sqlite --- runtests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/runtests.py b/runtests.py index e3942646a..6fd37ebeb 100755 --- a/runtests.py +++ b/runtests.py @@ -42,6 +42,7 @@ if not settings.configured: STATIC_URL='/static/', STATIC_ROOT=STATIC_ROOT, MEDIA_ROOT=MEDIA_ROOT, + USE_TZ=True, STATICFILES_FINDERS=( 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'compressor.finders.CompressorFinder', From 2cbf128eb5066743930e40b450d30a2901f759c0 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 28 May 2014 16:28:21 +0100 Subject: [PATCH 219/220] prepare for 0.3 release --- CHANGELOG.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9016e93b3..0a6024bd1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,7 @@ Changelog ========= -0.3 (xx.xx.20xx) +0.3 (28.05.2014) ~~~~~~~~~~~~~~~~ * 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 diff --git a/setup.py b/setup.py index 8a85f618c..4f5b87c12 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ except ImportError: setup( name='wagtail', - version='0.2', + version='0.3', description='A Django content management system focused on flexibility and user experience', author='Matthew Westcott', author_email='matthew.westcott@torchbox.com', From f25c44851e510efe055b2ec5f56755419e328621 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Thu, 29 May 2014 09:47:21 +0100 Subject: [PATCH 220/220] Recache version badge --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index eddbb85fc..d967dcb1f 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ .. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master :target: https://coveralls.io/r/torchbox/wagtail?branch=master -.. image:: https://pypip.in/v/wagtail/badge.png?asdf +.. image:: https://pypip.in/v/wagtail/badge.png?zxcv :target: https://crate.io/packages/wagtail/ Wagtail CMS