From 21dfbbfee9d4b89f40546859bd38408d77669139 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:26:04 -0400 Subject: [PATCH 01/26] moar docs --- docs/building_your_site.rst | 247 +++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index b506f67f26..0d1eed85c6 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -1,6 +1,251 @@ Building your site ================== +Site +~~~~ + +Django's built-in admin interface provides the way to map a "site" (hostname or domain) to the root of a wagtail tree. 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.. + +Model Design +~~~~~~~~~~~~ + +Wagtail manages content internally as a tree of pages. Each node in the tree is an instance of a Django model which subclasses the Wagtail ``Page`` class. You define the structure and interrelationships of your Wagtail site by coding these models and then publishing pages which use the models through the Wagtail admin interface. + +``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 Suitable for Inclusion in Templates +````````````````````````````````````````````````````` +``title`` (required) + Human-readable title for the content + +``slug`` (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`` + Alternate SEO-crafted title which overrides the normal title for use in the of a page + +``search_description`` + A SEO-crafted description of the content, used in both internal search indexing and for the meta description read by search engines + +Private Properties Meant for Use in the Wagtail Admin +````````````````````````````````````````````````````` +``show_in_menus`` + Boolean (checkbox) - Whether a link to this page will appear in automatically generated menus")) + +``live`` + Boolean (content status selectors) - whether the page is in a published, public-visible state + +``has_unpublished_changes`` + Boolean (content status selectors) - whether the page is in a draft state + +``owner`` + User who owns the page + +Internal Properties Which Describe The Model Instance +````````````````````````````````````````````````````` +``content_type`` + ??? Used to keep track of class names + +``url_path`` + The full URL path, including the slugs of all parents going back to the site root. Whenever a slug is changed in the tree, all of the node's descendants are updated with the new path. + +Introduction to Trees +--------------------- + +If you're unfamiliar with trees as an abstract data type, you might want to review the concepts involved: +http://en.wikipedia.org/wiki/Tree_(data_structure) + +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. + +Parents +``````` +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. + + 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. + + 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. + +Advanced +-------- + + custom serve methods + iCal + JSON + + custom route methods + + ParentalKey for storing groups of stuff to a Page-thing + + ClusterTaggableManager for tagging + + Orderable + Provides an abstract group of properties for ordering a collection of stuff + + Using or subclassing the site model? + + +Wagtail Admin +~~~~~~~~~~~~~ + +Fields & Edit Handlers +---------------------- + + RichTextField + + Image + + FieldPanel + + MultiFieldPanel + + InlinePanel + + PageChooserPanel + + ImageChooserPanel + + DocumentChooserPanel + +Snippets +-------- + +Registering and using template tags? + + + +Templates +~~~~~~~~~ + +Location +-------- + Wagtail looks for templates matching your models in... + +Self +---- + Without a custom rendering function, a + +Tags Provided by Wagtail +------------------------ + pageurl + Loaded into a template with + {% load pageurl %} + Used like + + Given a Page-derived class, outputs a page's 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. + slugurl + Loaded into a template with + {% load slugurl %} + Used like + + Returns the URL for the page that has the given slug. Like pageurl, will try to provide a relative link if possible, but will default to an absolute link if on a different site. + wagtailuserbar + Loaded into a template with + {% load wagtailuserbar %} + Used like + {% 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. + image + Loaded with + {% load image_tags %} + Used with + {% image self.photo max-320x200 %} + {% image self.photo max-320x200 as img %} + This template tag provides a way to process an image with a method and dimensions + + 'max': 'resize_to_max', + 'min': 'resize_to_min', + 'width': 'resize_to_width', + 'height': 'resize_to_height', + 'fill': 'resize_to_fill', + + +Filters Provided by Wagtail +--------------------------- + rich_text + Loaded into template with + {% load rich_text %} + Used with + {{ body|richtext }} + This filter is required for use with any RichTextField, because it will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. + + +Advanced Wagtail +~~~~~~~~~~~~~~~~ + + replacing image processing backend + + custom image processing methods? + + wagtail user bar custom CSS option? + + + + + + + + + + +Example Site +~~~~~~~~~~~~ + 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/ `_ From 037a77da8adf26e549111dcac21cec20ec0d32f3 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:28:20 -0400 Subject: [PATCH 02/26] moar docs --- docs/building_your_site.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 0d1eed85c6..c3a01d2707 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -24,7 +24,7 @@ Public Properties Suitable for Inclusion in Templates Human-readable title for the content ``slug`` (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]/ + 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`` Alternate SEO-crafted title which overrides the normal title for use in the of a page From 1bd49f85e1c75d431c42bb9efea538ef428e3db2 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:31:48 -0400 Subject: [PATCH 03/26] moar docs --- docs/building_your_site.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index c3a01d2707..36d2640486 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -27,7 +27,7 @@ Public Properties Suitable for Inclusion in Templates 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`` - Alternate SEO-crafted title which overrides the normal title for use in the of a page + Alternate SEO-crafted title which overrides the normal title for use in the ```` of a page ``search_description`` A SEO-crafted description of the content, used in both internal search indexing and for the meta description read by search engines @@ -60,7 +60,7 @@ Introduction to Trees If you're unfamiliar with trees as an abstract data type, you might want to review the concepts involved: http://en.wikipedia.org/wiki/Tree_(data_structure) -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: +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/ @@ -82,7 +82,7 @@ Parents ``````` 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. +A Parent node could provide its own function returning its descendant objects. :: class EventPageIndex(Page): ... @@ -102,7 +102,7 @@ Leaves are the pieces of content itself, a page which is consumable, and might j 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. +The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor.:: class BlogPage(Page): ... From b614781e9cd3198429696f170fd8ba9867ede2fc Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:35:38 -0400 Subject: [PATCH 04/26] moar docs --- docs/building_your_site.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 36d2640486..c3289958bb 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -82,7 +82,7 @@ Parents ``````` 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. :: +A Parent node could provide its own function returning its descendant objects. :: python class EventPageIndex(Page): ... @@ -102,7 +102,7 @@ Leaves are the pieces of content itself, a page which is consumable, and might j 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.:: +The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor. :: python class BlogPage(Page): ... From af881e07890f0481882693d45635c607d5ebf322 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:36:56 -0400 Subject: [PATCH 05/26] moar docs --- docs/building_your_site.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index c3289958bb..0f1ed7e19d 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -82,8 +82,9 @@ Parents ``````` 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. :: python +A Parent node could provide its own function returning its descendant objects. +.. code-block:: python class EventPageIndex(Page): ... def events(self): From 89d774da76b6f3ae3c64e616e7ecc12fb6c4b4d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Wed, 7 May 2014 14:39:07 -0400 Subject: [PATCH 06/26] moar docs --- docs/building_your_site.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 0f1ed7e19d..36d2640486 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -82,9 +82,8 @@ Parents ``````` 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. +A Parent node could provide its own function returning its descendant objects. :: -.. code-block:: python class EventPageIndex(Page): ... def events(self): @@ -103,7 +102,7 @@ Leaves are the pieces of content itself, a page which is consumable, and might j 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. :: python +The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor.:: class BlogPage(Page): ... From 1aa04c277d44dd8ee1c03d6a829a11588db955e3 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 8 May 2014 00:11:47 -0400 Subject: [PATCH 07/26] moar docs --- docs/building_your_site.rst | 348 +++++++++++++++++++++++------------- 1 file changed, 224 insertions(+), 124 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 36d2640486..9dd98ecfcf 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -1,13 +1,10 @@ Building your site ================== -Site -~~~~ -Django's built-in admin interface provides the way to map a "site" (hostname or domain) to the root of a wagtail tree. 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.. -Model Design -~~~~~~~~~~~~ +Model Design with Wagtail +~~~~~~~~~~~~~~~~~~~~~~~~~ Wagtail manages content internally as a tree of pages. Each node in the tree is an instance of a Django model which subclasses the Wagtail ``Page`` class. You define the structure and interrelationships of your Wagtail site by coding these models and then publishing pages which use the models through the Wagtail admin interface. @@ -20,55 +17,60 @@ Wagtail provides some properties in the Page class which are common to most webp Public Properties Suitable for Inclusion in Templates ````````````````````````````````````````````````````` -``title`` (required) - Human-readable title for the content -``slug`` (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]/`` + ``title`` (string, required) + Human-readable title for the content -``seo_title`` - Alternate SEO-crafted title which overrides the normal title for use in the ```` of a page + ``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]/`` -``search_description`` - A SEO-crafted description of the content, used in both internal search indexing and for the meta description read by search engines + ``seo_title`` (string) + Alternate SEO-crafted title which overrides the normal title for use in the ```` of a page -Private Properties Meant for Use in the Wagtail Admin -````````````````````````````````````````````````````` -``show_in_menus`` - Boolean (checkbox) - Whether a link to this page will appear in automatically generated menus")) + ``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 -``live`` - Boolean (content status selectors) - whether the page is in a published, public-visible state +Semi-Private Properties Intended for Use in the Wagtail Admin +````````````````````````````````````````````````````````````` -``has_unpublished_changes`` - Boolean (content status selectors) - whether the page is in a draft state + ``show_in_menus`` (boolean) + Whether a link to this page will appear in automatically generated menus")) -``owner`` - User who owns the page + ``live`` (boolean) + Whether the page is in a published, public-visible state + + ``has_unpublished_changes`` (boolean) + Whether the page is in a draft state + + ``owner`` (User) + Model relation pointing to the user who owns the page. Uses the user model set in ``settings.AUTH_USER_MODEL``. + + ``indexed_fields`` (dict) + Describes the properties which should be indexed by search and how they should be weighted for maximum accuracy. See the Search section for usage. Internal Properties Which Describe The Model Instance ````````````````````````````````````````````````````` -``content_type`` - ??? Used to keep track of class names -``url_path`` - The full URL path, including the slugs of all parents going back to the site root. Whenever a slug is changed in the tree, all of the node's descendants are updated with the new path. + ``content_type`` (ContentType) + A relation to an internal content type model. + + ``url_path`` (string) + The full URL path, including the slugs of all parents going back to the site root. Whenever a slug is changed in the tree, all of the node's descendants are updated with the new path. Use the ``pageurl`` template tag for outputting the URL of a page-subclassed object. Introduction to Trees --------------------- -If you're unfamiliar with trees as an abstract data type, you might want to review the concepts involved: -http://en.wikipedia.org/wiki/Tree_(data_structure) +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/ + / + 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. @@ -76,7 +78,7 @@ The Wagtail admin interface uses the tree to organize content for editing, letti 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. +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. Parents ``````` @@ -84,17 +86,17 @@ Parent nodes on the Wagtail tree probably want to organize and display a browsab A Parent node could provide its own function returning its descendant objects. :: - 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 + 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. +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 `````` @@ -102,65 +104,147 @@ Leaves are the pieces of content itself, a page which is consumable, and might j 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.:: +The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor:: - 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 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() + # 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. +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. -Advanced --------- +Model Recipes +------------- - custom serve methods - iCal - JSON +Overriding the Serve() Method +````````````````````````````` - custom route methods +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. - ParentalKey for storing groups of stuff to a Page-thing +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:: - ClusterTaggableManager for tagging + 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) - Orderable - Provides an abstract group of properties for ordering a collection of stuff +``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. - Using or subclassing the site model? +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. + +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``:: + + 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:: + + 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:: + + {% 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. -Wagtail Admin -~~~~~~~~~~~~~ + + custom route methods + + ParentalKey for storing groups of stuff to a Page-thing + + + Orderable + Provides an abstract group of properties for ordering a collection of stuff + + Using or subclassing the site model? + + Extending indexed_fields and making models search-friendly + + +Wagtail Admin API +~~~~~~~~~~~~~~~~~ Fields & Edit Handlers ---------------------- - RichTextField + RichTextField - Image + Image - FieldPanel + FieldPanel - MultiFieldPanel + MultiFieldPanel - InlinePanel + InlinePanel - PageChooserPanel + PageChooserPanel - ImageChooserPanel + ImageChooserPanel - DocumentChooserPanel + DocumentChooserPanel Snippets -------- @@ -174,65 +258,81 @@ Templates Location -------- - Wagtail looks for templates matching your models in... + Wagtail looks for templates matching your models in... Self ---- - Without a custom rendering function, a + Without a custom rendering function, a ... -Tags Provided by Wagtail ------------------------- - pageurl - Loaded into a template with - {% load pageurl %} - Used like - - Given a Page-derived class, outputs a page's 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. - slugurl - Loaded into a template with - {% load slugurl %} - Used like - - Returns the URL for the page that has the given slug. Like pageurl, will try to provide a relative link if possible, but will default to an absolute link if on a different site. - wagtailuserbar - Loaded into a template with - {% load wagtailuserbar %} - Used like - {% 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. - image - Loaded with - {% load image_tags %} - Used with - {% image self.photo max-320x200 %} - {% image self.photo max-320x200 as img %} - This template tag provides a way to process an image with a method and dimensions +Template Tags +------------- - 'max': 'resize_to_max', - 'min': 'resize_to_min', - 'width': 'resize_to_width', - 'height': 'resize_to_height', - 'fill': 'resize_to_fill', + **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. :: + + {% 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. :: + + {% 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. :: + + {% load wagtailuserbar %} + ... + {% wagtailuserbar %} + + **image** + + This template tag provides a way to process an image with a method and dimensions. :: + + {% 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', -Filters Provided by Wagtail ---------------------------- - rich_text - Loaded into template with - {% load rich_text %} - Used with - {{ body|richtext }} - This filter is required for use with any RichTextField, because it will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. +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.** :: + + {% load rich_text %} + ... + {{ body|richtext }} + +Site +~~~~ + +Django's built-in admin interface provides the way to map a "site" (hostname or domain) to the root of a wagtail tree. 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. Advanced Wagtail ~~~~~~~~~~~~~~~~ - replacing image processing backend + replacing image processing backend - custom image processing methods? + custom image processing methods? - wagtail user bar custom CSS option? + wagtail user bar custom CSS option? From e6df0ef01fbabeba5eac74f6e5d22396e1f475b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 8 May 2014 21:01:47 -0400 Subject: [PATCH 08/26] stuff --- docs/building_your_site.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 9dd98ecfcf..95215cbdfd 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -221,7 +221,6 @@ This is just one possible way of creating a taxonomy for Wagtail objects. With a Using or subclassing the site model? - Extending indexed_fields and making models search-friendly Wagtail Admin API @@ -319,6 +318,19 @@ Template Filters ... {{ body|richtext }} +Search +~~~~~~ + + + Extending indexed_fields and making models search-friendly + + + + + + + + Site ~~~~ From cb3c41222c3119e38aafeb4fac18865a3ad1e3fe Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sat, 10 May 2014 01:23:58 -0400 Subject: [PATCH 09/26] omg search --- docs/building_your_site.rst | 6 -- docs/wagtail_search.rst | 116 +++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 95215cbdfd..b52d797a13 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -318,12 +318,6 @@ Template Filters ... {{ body|richtext }} -Search -~~~~~~ - - - Extending indexed_fields and making models search-friendly - diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 1bef60724e..4c6ce62f28 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -1,7 +1,113 @@ Search ====== -Wagtail can degrade to a database-backed text search, but we strongly recommend `Elasticsearch`_. 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: +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. + +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. + +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 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. + +Lets use our own template for the results, though. First, in your project's ``settings.py``, define a path to your template:: + + WAGTAILSEARCH_RESULTS_TEMPLATE = 'mysite/search_results.html' + +Next, lets look at the template itself:: + + {% extends "mysite/base.html" %} + {% load pageurl %} + + {% block title %}Search{% if search_results %} Results{% endif %}{% endblock %} + + {% block search_box %} + {% include "mysite/includes/search_box.html" with query_string=query_string only %} + {% endblock %} + + {% block content %} +

Search Results{% if request.GET.q %} for {{ request.GET.q }}{% endif %}

+
+ {% endblock %} + +The search view returns a context with a few useful variables. + + ``query_string`` + The terms (string) used to make the search. + + ``search_results`` + A collection of Page objects matching the query. + + ``is_ajax`` + Boolean. This returns Django's ``request.is_ajax()``. + + ``query`` + The Query object matching the terms. The query model provides several class methods for viewing the statistics of all queries, but exposes only one property for single queries, ``query.hits``, which tracks the number of time the search string has been used. + + + + +Default Page Search with AJAX +----------------------------- + + + +Editor's Picks +-------------- + + + + +Indexing Custom Fields & Custom Search Views +-------------------------------------------- + + + + + + + +Strategies for Total Search Coverage +------------------------------------ + +Want every field searchable? Every custom model and each of their custom fields? Here's how... + + + +Search Backends +--------------- + +Wagtail can degrade to a database-backed text search, but we strongly recommend `Elasticsearch`_. + +.. _Elasticsearch: http://www.elasticsearch.org/ + + +Default DB Backend +`````````````````` +The default DB search backend effectively acts as a ``__icontains`` filter on the ``indexed_fields`` of your models. + + +Elasticsearch Backend +````````````````````` +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' @@ -10,6 +116,10 @@ Wagtail can degrade to a database-backed text search, but we strongly recommend your local settings - Run ``./manage.py update_index`` -.. _Elasticsearch: http://www.elasticsearch.org/ .. _Searchly: http://www.searchly.com/ -.. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up \ No newline at end of file +.. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up + + +Rolling Your Own +```````````````` + From 876283debfbc4c9f5e029b157e6c344ce0e1e076 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sat, 10 May 2014 01:35:52 -0400 Subject: [PATCH 10/26] omg search --- 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 4c6ce62f28..c43a537375 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -54,13 +54,13 @@ The search view returns a context with a few useful variables. The terms (string) used to make the search. ``search_results`` - A collection of Page objects matching the query. + A collection of Page objects matching the query. The ``specific`` property of ``Page`` will give the most-specific subclassed model object for the Wagtail page. For instance, if an ``Event`` model derived from the basic Wagtail ``Page`` were included in the search results, you could use ``specific`` to access the custom properties of the ``Event`` model (``result.specific.date_of_event``). ``is_ajax`` Boolean. This returns Django's ``request.is_ajax()``. ``query`` - The Query object matching the terms. The query model provides several class methods for viewing the statistics of all queries, but exposes only one property for single queries, ``query.hits``, which tracks the number of time the search string has been used. + A Wagtail Query object matching the terms. The query model provides several class methods for viewing the statistics of all queries, but exposes only one property for single objects, ``query.hits``, which tracks the number of time the search string has been used over the lifetime of the site. From 206a3e3732bed4e11f53b4abac66f55c6c135c03 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sat, 10 May 2014 16:49:05 -0400 Subject: [PATCH 11/26] Async search docs done --- docs/building_your_site.rst | 44 ++++++++++++---- docs/conf.py | 2 +- docs/wagtail_search.rst | 100 +++++++++++++++++++++++++++++++++--- 3 files changed, 128 insertions(+), 18 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index b52d797a13..4c62d8cab0 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -84,7 +84,9 @@ Parents ``````` 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. :: +A Parent node could provide its own function returning its descendant objects. + +.. code-block:: python class EventPageIndex(Page): ... @@ -104,7 +106,9 @@ Leaves are the pieces of content itself, a page which is consumable, and might j 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:: +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): ... @@ -131,7 +135,9 @@ 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:: +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): ... @@ -161,7 +167,9 @@ 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``:: +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 @@ -181,7 +189,9 @@ Using an example from the Wagtail demo site, here's what the tag model and the r 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:: +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): ... @@ -199,7 +209,9 @@ Now that we have the many-to-many tag relationship in place, we can fit in a way '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:: +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 }} @@ -268,7 +280,9 @@ 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. :: + 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 %} ... @@ -276,7 +290,9 @@ Template Tags **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. :: + 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 %} ... @@ -284,7 +300,9 @@ Template Tags **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. :: + 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 %} ... @@ -292,7 +310,9 @@ Template Tags **image** - This template tag provides a way to process an image with a method and dimensions. :: + This template tag provides a way to process an image with a method and dimensions. + + .. code-block:: django {% load image_tags %} ... @@ -312,7 +332,9 @@ 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.** :: + 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 %} ... diff --git a/docs/conf.py b/docs/conf.py index 11517ada25..4112e3753f 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 = [] diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index c43a537375..c3ebd236cd 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -8,7 +8,9 @@ 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. -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, lets put it in ``mysite/includes/search_box.html`` and then use ``{% include ... %}`` to weave it into templates: + +.. code-block:: django
@@ -17,11 +19,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. -Lets use our own template for the results, though. First, in your project's ``settings.py``, define a path to your template:: +Lets 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, lets look at the template itself: + +.. code-block:: django {% extends "mysite/base.html" %} {% load pageurl %} @@ -48,7 +54,7 @@ Next, lets look at the template itself:: {% endblock %} -The search view returns a context with a few useful variables. +The search view provides a context with a few useful variables. ``query_string`` The terms (string) used to make the search. @@ -62,13 +68,95 @@ The search view returns a context with a few useful variables. ``query`` A Wagtail Query object matching the terms. The query model provides several class methods for viewing the statistics of all queries, but exposes only one property for single objects, ``query.hits``, which tracks the number of time the search string has been used over the lifetime of the site. +Asyncronous 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``: +.. code-block:: django -Default Page Search with AJAX ------------------------------ + +Lets also add a simple interface for the search with an ```` element and ``
`` for the results: +.. code-block:: html + +
+

Search

+ +
+
+ +Finally, we'll use JQuery to make the aynchronous requests and handle the interactivity: + +.. code-block:: guess + + $(function() { + + // cache the elements + var searchBox = $('#json-search'), + resultsBox = $('#json-results'); + // when there's something in the input box, make the query + searchBox.on('input', function() { + if( searchBox.val() == ''){ + resultsBox.html(''); + return; + } + // make the request to the Wagtail JSON search view + $.ajax({ + url: wagtailJSONSearchURL + "?q=" + searchBox.val(), + dataType: "json" + }) + .done(function(data) { + console.log(data); + if( data == undefined ){ + resultsBox.html(''); + return; + } + // we're in business! let's format the results + var htmlOutput = ''; + data.forEach(function(element, index, array){ + htmlOutput += '

' + element.title + '

'; + }); + // and display them + resultsBox.html(htmlOutput); + }) + .error(function(data){ + console.log(data); + }); + }); + + }); + +Results are returned as a JSON object with this structure: + +.. code-block:: guess + + { + [ + { + title: "Lumpy Space Princess", + url: "/oh-my-glob/" + }, + { + title: "Lumpy Space", + url: "/no-smooth-posers/" + }, + ... + ] + } + +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. + +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: + +.. code-block:: python + + WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'mirrorstage/includes/search_listing.html' + +You could provide a template in JSON format with extra properties, such as ``query.hits``, 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. Editor's Picks -------------- From c4b5f09eace7d56b25404a13c3d87eb148c0a709 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sat, 10 May 2014 16:52:19 -0400 Subject: [PATCH 12/26] Async search docs done --- 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 c3ebd236cd..7af946ec2f 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -154,9 +154,9 @@ The AJAX interface uses the same view as the normal HTML search, ``wagtailsearch .. code-block:: python - WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'mirrorstage/includes/search_listing.html' + WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'myapp/includes/search_listing.html' -You could provide a template in JSON format with extra properties, such as ``query.hits``, 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 variablies provided to the HTML template. You could provide a template in JSON format with extra properties, such as ``query.hits``, 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. Editor's Picks -------------- From 66d29b37df908864375fc2eba6611eee4afbce7e Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sun, 11 May 2014 20:07:40 -0400 Subject: [PATCH 13/26] done with search docs --- docs/wagtail_search.rst | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 7af946ec2f..5176172725 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -66,7 +66,9 @@ The search view provides a context with a few useful variables. Boolean. This returns Django's ``request.is_ajax()``. ``query`` - A Wagtail Query object matching the terms. The query model provides several class methods for viewing the statistics of all queries, but exposes only one property for single objects, ``query.hits``, which tracks the number of time the search string has been used over the lifetime of the site. + A Wagtail ``Query`` object matching the terms. The ``Query`` model provides several class methods for viewing the statistics of all queries, but exposes only one property for single objects, ``query.hits``, which tracks the number of time the search string has been used over the lifetime of the site. ``Query`` also joins to the Editor's Picks functionality though ``query.editors_picks``. See :ref:`editors-picks`. + + Asyncronous Search with JSON and AJAX ------------------------------------- @@ -156,28 +158,52 @@ 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``, 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 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. + +.. _editors-picks: Editor's Picks -------------- +Editor's Picks are a way of explicitly linking relevant content to search terms, so results pages can contain curated content instead of being at the mercy of the search algorithm. In a template using the search results view, editor's picks can be accessed through the variable ``query.editors_picks``. To include editor's picks in your search results template, use the following properties. +``query.editors_picks.all`` + This gathers all of the editor's picks objects relating to the current query, in order according to their sort order in the Wagtail admin. You can then iterate through them using a ``{% for ... %}`` loop. Each editor's pick object provides these properties: + ``editors_pick.page`` + The page object associated with the pick. Use ``{% pageurl editors_pick.page %}`` to generate a URL or provide other properties of the page object. + + ``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: + +.. code-block:: django + + {% with query.editors_picks.all as editors_picks %} + {% if editors_picks %} +
+

Editors picks

+ +
+ {% endif %} + {% endwith %} Indexing Custom Fields & Custom Search Views -------------------------------------------- - - - - - - -Strategies for Total Search Coverage ------------------------------------- - -Want every field searchable? Every custom model and each of their custom fields? Here's how... - +This functionality is still under active development to provide a streamlined interface, but take a look at ``wagtail/wagtail/wagtailsearch/views/frontend.py`` if you are interested in coding custom search views. Search Backends @@ -190,7 +216,7 @@ Wagtail can degrade to a database-backed text search, but we strongly recommend Default DB Backend `````````````````` -The default DB search backend effectively acts as a ``__icontains`` filter on the ``indexed_fields`` of your models. +The default DB search backend uses Django's ``__icontains`` filter. Elasticsearch Backend @@ -207,7 +233,6 @@ If you prefer not to run an Elasticsearch server in development or production, t .. _Searchly: http://www.searchly.com/ .. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up - Rolling Your Own ```````````````` - +Wagtail search backends implement the interface defined in ``wagtail/wagtail/wagtailsearch/backends/base.py``. At a minimum, the backend's ``search()`` method must return a collection of objects or ``model.objects.none()``. For a fully-featured search backend, examine the Elasticsearch backend code in ``elasticsearch.py``. From e37b6f6a5fa97cf5c24d16ecdc8d036b5cbd0726 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Mon, 12 May 2014 21:25:26 -0400 Subject: [PATCH 14/26] ugh neck hurt --- docs/building_your_site.rst | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 4c62d8cab0..8912a8158b 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -227,10 +227,6 @@ This is just one possible way of creating a taxonomy for Wagtail objects. With a ParentalKey for storing groups of stuff to a Page-thing - - Orderable - Provides an abstract group of properties for ordering a collection of stuff - Using or subclassing the site model? @@ -260,7 +256,41 @@ Fields & Edit Handlers Snippets -------- -Registering and using template tags? +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()``. + +Here's an example snippet from the Wagtail demo website: + +.. code-block:: python + + class Advert(models.Model): + page = models.ForeignKey( + 'wagtailcore.Page', + related_name='adverts', + null=True, + blank=True + ) + url = models.URLField(null=True, blank=True) + text = models.CharField(max_length=255) + + panels = [ + PageChooserPanel('page'), + FieldPanel('url'), + FieldPanel('text'), + ] + + def __unicode__(self): + return self.text + + register_snippet(Advert) + +The ``Advert`` model uses the basic Django model class and defines three properties: text, url, and page. The editing interface is very close to that provided for ``Page``-derived models, with fields assigned in the panels property. + + + + + + + From a2ed70bd8d862137a955e69a4e8132091c5ba477 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Tue, 13 May 2014 22:38:44 -0400 Subject: [PATCH 15/26] Snippets and some reorg --- docs/advanced_topics.rst | 9 +++ docs/building_your_site.rst | 144 +++++++----------------------------- docs/index.rst | 3 + docs/panels.rst | 33 +++++++++ docs/snippets.rst | 140 +++++++++++++++++++++++++++++++++++ docs/wagtail_search.rst | 75 +++++++++---------- 6 files changed, 250 insertions(+), 154 deletions(-) create mode 100644 docs/advanced_topics.rst create mode 100644 docs/panels.rst create mode 100644 docs/snippets.rst diff --git a/docs/advanced_topics.rst b/docs/advanced_topics.rst new file mode 100644 index 0000000000..ae0e24015e --- /dev/null +++ b/docs/advanced_topics.rst @@ -0,0 +1,9 @@ +Advanced Topics +~~~~~~~~~~~~~~~~ + +replacing image processing backend + +custom image processing tags? + +wagtail user bar custom CSS option? + diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 8912a8158b..1b4c9dd098 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -1,22 +1,22 @@ Building your site ================== - - -Model Design with Wagtail -~~~~~~~~~~~~~~~~~~~~~~~~~ - Wagtail manages content internally as a tree of pages. Each node in the tree is an instance of a Django model which subclasses the Wagtail ``Page`` class. You define the structure and interrelationships of your Wagtail site by coding these models and then publishing pages which use the models through the Wagtail admin interface. + +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. +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 Suitable for Inclusion in Templates -````````````````````````````````````````````````````` +Public Properties +````````````````` ``title`` (string, required) Human-readable title for the content @@ -30,32 +30,23 @@ Public Properties Suitable for Inclusion in Templates ``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 -Semi-Private Properties Intended for Use in the Wagtail Admin -````````````````````````````````````````````````````````````` +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? + + - ``show_in_menus`` (boolean) - Whether a link to this page will appear in automatically generated menus")) - ``live`` (boolean) - Whether the page is in a published, public-visible state - ``has_unpublished_changes`` (boolean) - Whether the page is in a draft state - ``owner`` (User) - Model relation pointing to the user who owns the page. Uses the user model set in ``settings.AUTH_USER_MODEL``. - ``indexed_fields`` (dict) - Describes the properties which should be indexed by search and how they should be weighted for maximum accuracy. See the Search section for usage. -Internal Properties Which Describe The Model Instance -````````````````````````````````````````````````````` - ``content_type`` (ContentType) - A relation to an internal content type model. - ``url_path`` (string) - The full URL path, including the slugs of all parents going back to the site root. Whenever a slug is changed in the tree, all of the node's descendants are updated with the new path. Use the ``pageurl`` template tag for outputting the URL of a page-subclassed object. Introduction to Trees --------------------- @@ -78,10 +69,10 @@ The Wagtail admin interface uses the tree to organize content for editing, letti 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. +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. -Parents -``````` +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. @@ -127,11 +118,12 @@ 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. + 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. @@ -164,7 +156,8 @@ Consider this example from the Wagtail demo site's ``models.py``, which serves a 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. 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``: @@ -227,69 +220,6 @@ This is just one possible way of creating a taxonomy for Wagtail objects. With a ParentalKey for storing groups of stuff to a Page-thing - Using or subclassing the site model? - - - -Wagtail Admin API -~~~~~~~~~~~~~~~~~ - -Fields & Edit Handlers ----------------------- - - RichTextField - - Image - - FieldPanel - - MultiFieldPanel - - InlinePanel - - PageChooserPanel - - ImageChooserPanel - - DocumentChooserPanel - -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()``. - -Here's an example snippet from the Wagtail demo website: - -.. code-block:: python - - class Advert(models.Model): - page = models.ForeignKey( - 'wagtailcore.Page', - related_name='adverts', - null=True, - blank=True - ) - url = models.URLField(null=True, blank=True) - text = models.CharField(max_length=255) - - panels = [ - PageChooserPanel('page'), - FieldPanel('url'), - FieldPanel('text'), - ] - - def __unicode__(self): - return self.text - - register_snippet(Advert) - -The ``Advert`` model uses the basic Django model class and defines three properties: text, url, and page. The editing interface is very close to that provided for ``Page``-derived models, with fields assigned in the panels property. - - - - - - @@ -372,32 +302,14 @@ Template Filters - - - - - Site ~~~~ -Django's built-in admin interface provides the way to map a "site" (hostname or domain) to the root of a wagtail tree. 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. - - -Advanced Wagtail -~~~~~~~~~~~~~~~~ - - replacing image processing backend - - custom image processing methods? - - wagtail user bar custom CSS option? - - - - - +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/index.rst b/docs/index.rst index dbfe91f33d..defbe99cf5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,10 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support gettingstarted building_your_site + panels + snippets wagtail_search + advanced_topics deploying performance contributing diff --git a/docs/panels.rst b/docs/panels.rst new file mode 100644 index 0000000000..7cf6c10564 --- /dev/null +++ b/docs/panels.rst @@ -0,0 +1,33 @@ + +Panels & Edit Handlers +====================== + +RichTextField +~~~~~~~~~~~~~ + +Image +~~~~~ + +FieldPanel +~~~~~~~~~~ + +MultiFieldPanel +~~~~~~~~~~~~~~~ + +InlinePanel +~~~~~~~~~~~ + +PageChooserPanel +~~~~~~~~~~~~~~~~ + +ImageChooserPanel +~~~~~~~~~~~~~~~~~ + +DocumentChooserPanel +~~~~~~~~~~~~~~~~~~~~ + +SnippetChooserPanel +~~~~~~~~~~~~~~~~~~~ + + + diff --git a/docs/snippets.rst b/docs/snippets.rst new file mode 100644 index 0000000000..14d62b7934 --- /dev/null +++ b/docs/snippets.rst @@ -0,0 +1,140 @@ +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. + +Snippet Models +-------------- + +Here's an example snippet from the Wagtail demo website: + +.. code-block:: python + + from django.db import models + + from wagtail.wagtailadmin.edit_handlers import FieldPanel + from wagtail.wagtailsnippets.models import register_snippet + + ... + + class Advert(models.Model): + url = models.URLField(null=True, blank=True) + text = models.CharField(max_length=255) + + panels = [ + FieldPanel('url'), + FieldPanel('text'), + ] + + def __unicode__(self): + return self.text + + register_snippet(Advert) + +The ``Advert`` model uses the basic Django model class and defines two properties: text and url. The editing interface is very close to that provided for ``Page``-derived models, with fields assigned in the panels property. Snippets do not use multiple tabs of fields, nor do they provide the "save as draft" or "submit for moderation" features. + +``register_snippet(Advert)`` tells Wagtail to treat the model as a snippet. The ``panels`` list defines the fields to show on the snippet editing page. It's also important to provide a string representation of the class through ``def __unicode__(self):`` so that the snippet objects make sense when listed in the Wagtail admin. + +Including Snippets in Template Tags +----------------------------------- + +The simplest way to make your snippets available to templates is with a template tag. This is mostly done with vanilla Django, so perhaps reviewing Django's documentation for `django custom template tags`_ will be more helpful. We'll go over the basics, though, and make note of any considerations to make for Wagtail. + +First, add a new python file to a ``templatetags`` folder within your app. The demo website, for instance uses the path ``wagtaildemo/demo/templatetags/demo_tags.py``. We'll need to load some Django modules and our app's models and ready the ``register`` decorator: + +.. _django custom template tags: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/ + +.. code-block:: python + + from django import template + from demo.models import * + + register = template.Library() + + ... + + # Advert snippets + @register.inclusion_tag('demo/tags/adverts.html', takes_context=True) + def adverts(context): + return { + 'adverts': Advert.objects.all(), + 'request': context['request'], + } + +``@register.inclusion_tag()`` takes two variables: a template and a boolean on whether that template should be passed a request context. It's a good idea to include request contexts in your custom template tags, since some Wagtail-specific template tags like ``pageurl`` need the context to work properly. The template tag function could take arguments and filter the adverts to return a specific model, but for brevity we'll just use ``Advert.objects.all()``. + +Here's what's in the template used by the template tag: + +.. code-block:: django + + {% for advert in adverts %} +

+ + {{ advert.text }} + +

+ {% endfor %} + +Then in your own page templates, you can include your snippet template tag with: + +.. code-block:: django + + {% block content %} + + ... + + {% adverts %} + + {% endblock %} + +Binding Pages to Snippets +------------------------- + +An alternate strategy for including snippets might involve explicitly binding a specific page object to a specific snippet object. Lets add another snippet class to see how that might work: + +.. code-block:: python + + from django.db import models + + from wagtail.wagtailcore.models import Page + from wagtail.wagtailadmin.edit_handlers import PageChooserPanel + from wagtail.wagtailsnippets.models import register_snippet + from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel + + from modelcluster.fields import ParentalKey + + ... + + class AdvertPlacement(models.Model): + page = ParentalKey('wagtailcore.Page', related_name='advert_placements') + advert = models.ForeignKey('demo.Advert', related_name='+') + + class Meta: + verbose_name = "Advert Placement" + verbose_name_plural = "Advert Placements" + + panels = [ + PageChooserPanel('page'), + SnippetChooserPanel('advert', Advert), + ] + + def __unicode__(self): + return self.page.title + " -> " + self.advert.text + + register_snippet(AdvertPlacement) + +The class ``AdvertPlacement`` has two properties, ``page`` and ``advert``, which point to other models. Wagtail provides a ``PageChooserPanel`` and ``SnippetChooserPanel`` to let us make painless selection of those properties in the Wagtail admin. Note also the ``Meta`` class, which you can stock with the ``verbose_name`` and ``verbose_name_plural`` properties to override the snippet labels in the Wagtail admin. The text representation of the class has also gotten fancy, using both properties to construct a compound label showing the relationship it forms between a page and an Advert. + +With this snippet in place, we can use the reverse ``related_name`` lookup label ``advert_placements`` to iterate over any placements within our template files. In the template for a ``Page``-derived model, we could include the following: + +.. code-block:: django + + {% if self.advert_placements %} + {% for advert_placement in self.advert_placements.all %} +

{{ advert_placement.advert.text }}

+ {% endfor %} + {% endif %} + + diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 5176172725..fff462349c 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -68,7 +68,43 @@ The search view provides a context with a few useful variables. ``query`` A Wagtail ``Query`` object matching the terms. The ``Query`` model provides several class methods for viewing the statistics of all queries, but exposes only one property for single objects, ``query.hits``, which tracks the number of time the search string has been used over the lifetime of the site. ``Query`` also joins to the Editor's Picks functionality though ``query.editors_picks``. See :ref:`editors-picks`. +Editor's Picks +-------------- +Editor's Picks are a way of explicitly linking relevant content to search terms, so results pages can contain curated content instead of being at the mercy of the search algorithm. In a template using the search results view, editor's picks can be accessed through the variable ``query.editors_picks``. To include editor's picks in your search results template, use the following properties. + +``query.editors_picks.all`` + This gathers all of the editor's picks objects relating to the current query, in order according to their sort order in the Wagtail admin. You can then iterate through them using a ``{% for ... %}`` loop. Each editor's pick object provides these properties: + + ``editors_pick.page`` + The page object associated with the pick. Use ``{% pageurl editors_pick.page %}`` to generate a URL or provide other properties of the page object. + + ``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: + +.. code-block:: django + + {% with query.editors_picks.all as editors_picks %} + {% if editors_picks %} +
+

Editors picks

+ +
+ {% endif %} + {% endwith %} Asyncronous Search with JSON and AJAX ------------------------------------- @@ -81,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 an ```` element and ``
`` for the results: +Lets also add a simple interface for the search with a ```` element to gather search terms and a ``
`` to display the results: .. code-block:: html @@ -162,43 +198,6 @@ In this template, you'll have access to the same context variablies provided to .. _editors-picks: -Editor's Picks --------------- - -Editor's Picks are a way of explicitly linking relevant content to search terms, so results pages can contain curated content instead of being at the mercy of the search algorithm. In a template using the search results view, editor's picks can be accessed through the variable ``query.editors_picks``. To include editor's picks in your search results template, use the following properties. - -``query.editors_picks.all`` - This gathers all of the editor's picks objects relating to the current query, in order according to their sort order in the Wagtail admin. You can then iterate through them using a ``{% for ... %}`` loop. Each editor's pick object provides these properties: - - ``editors_pick.page`` - The page object associated with the pick. Use ``{% pageurl editors_pick.page %}`` to generate a URL or provide other properties of the page object. - - ``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: - -.. code-block:: django - - {% with query.editors_picks.all as editors_picks %} - {% if editors_picks %} -
-

Editors picks

- -
- {% endif %} - {% endwith %} Indexing Custom Fields & Custom Search Views -------------------------------------------- From 65b98992699a80390999c11f117068a97222bf2a Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 15 May 2014 22:41:13 -0400 Subject: [PATCH 16/26] Routes --- docs/building_your_site.rst | 106 ++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 1b4c9dd098..04c2e2ae5e 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -122,7 +122,7 @@ Your ``Page``-derived models might have other interrelationships which extend th Model Recipes ~~~~~~~~~~~~~ -Overriding the Serve() Method +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. @@ -155,6 +155,90 @@ Consider this example from the Wagtail demo site's ``models.py``, which serves a 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 ------- @@ -216,10 +300,10 @@ This is just one possible way of creating a taxonomy for Wagtail objects. With a - custom route methods - ParentalKey for storing groups of stuff to a Page-thing +Extended Page Data with ParentalKey +----------------------------------- @@ -229,11 +313,23 @@ Templates Location -------- - Wagtail looks for templates matching your models in... + +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``. Self ---- - Without a custom rendering function, a ... + +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 ------------- From da2c69a5ee3820f5763ae8b2a1f898e4e2190ab0 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 15 May 2014 22:50:26 -0400 Subject: [PATCH 17/26] Routes --- docs/building_your_site.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 04c2e2ae5e..7cb2f04a5d 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -302,9 +302,15 @@ This is just one possible way of creating a taxonomy for Wagtail objects. With a -Extended Page Data with ParentalKey +Page Data Clusters with ParentalKey ----------------------------------- +The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page. + + + + + From 04e56198ffc186352b0693f6b8180e7bb37fcd5c Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Fri, 16 May 2014 12:02:32 -0400 Subject: [PATCH 18/26] placeholders added --- docs/building_your_site.rst | 75 +++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 36d2640486..28fbd7010d 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -178,50 +178,51 @@ Location Self ---- - Without a custom rendering function, a + +Without a custom rendering function, the ``Page`` class will provide a ``self`` variable which a template can use to display Tags Provided by Wagtail ------------------------ - pageurl - Loaded into a template with - {% load pageurl %} - Used like - - Given a Page-derived class, outputs a page's 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. - slugurl - Loaded into a template with - {% load slugurl %} - Used like - - Returns the URL for the page that has the given slug. Like pageurl, will try to provide a relative link if possible, but will default to an absolute link if on a different site. - wagtailuserbar - Loaded into a template with - {% load wagtailuserbar %} - Used like - {% 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. - image - Loaded with - {% load image_tags %} - Used with - {% image self.photo max-320x200 %} - {% image self.photo max-320x200 as img %} - This template tag provides a way to process an image with a method and dimensions +``pageurl`` + Loaded into a template with + ``{% load pageurl %}`` + Used like + ```` + Given a Page-derived class, outputs a page's 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. +``slugurl`` + Loaded into a template with + ``{% load slugurl %}`` + Used like + ```` + Returns the URL for the page that has the given slug. Like pageurl, will try to provide a relative link if possible, but will default to an absolute link if on a different site. +``wagtailuserbar`` + Loaded into a template with + ``{% load wagtailuserbar %}`` + Used like + ``{% 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. +``image`` + Loaded with + ``{% load image_tags %}`` + Used with + ``{% image self.photo max-320x200 %}`` + ``{% image self.photo max-320x200 as img %}`` + This template tag provides a way to process an image with a method and dimensions - 'max': 'resize_to_max', - 'min': 'resize_to_min', - 'width': 'resize_to_width', - 'height': 'resize_to_height', - 'fill': 'resize_to_fill', + 'max': 'resize_to_max', + 'min': 'resize_to_min', + 'width': 'resize_to_width', + 'height': 'resize_to_height', + 'fill': 'resize_to_fill', Filters Provided by Wagtail --------------------------- - rich_text + ``rich_text`` Loaded into template with - {% load rich_text %} + ``{% load rich_text %}`` Used with - {{ body|richtext }} + ``{{ body|richtext }}`` This filter is required for use with any RichTextField, because it will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. @@ -234,11 +235,11 @@ Advanced Wagtail wagtail user bar custom CSS option? +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) From 62f3ecaa9ba27393c4173f13aa07e24eb83f3370 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Fri, 16 May 2014 12:10:46 -0400 Subject: [PATCH 19/26] another placeholder --- docs/panels.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/panels.rst b/docs/panels.rst index 7cf6c10564..47cd3e1a73 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -29,5 +29,8 @@ DocumentChooserPanel SnippetChooserPanel ~~~~~~~~~~~~~~~~~~~ +Custom Panels +~~~~~~~~~~~~~ + From 446c053a170bf5099a64e2e7438033c7d305a7be Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sat, 17 May 2014 20:54:36 -0400 Subject: [PATCH 20/26] more model basics --- docs/building_your_site.rst | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index e49c863603..a6677a88a0 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -36,10 +36,44 @@ The ``Page`` class actually has alot more to it, but these are probably the only Anatomy of a Wagtail Model ~~~~~~~~~~~~~~~~~~~~~~~~~~ -So what does a Wagtail model definition look like? +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 (``date``) and Wagtail models (``feed_image`` and ``body``). From bfc53dd6ce9c1fc82fa6383b04d42fc3c1d3a54a Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Sun, 18 May 2014 01:21:17 -0400 Subject: [PATCH 21/26] added request lifestyle introduction --- docs/building_your_site.rst | 38 ++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index a6677a88a0..2768cb9cf8 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -1,7 +1,13 @@ Building your site ================== -Wagtail manages content internally as a tree of pages. Each node in the tree is an instance of a Django model which subclasses the Wagtail ``Page`` class. You define the structure and interrelationships of your Wagtail site by coding these models and then publishing pages which use the models through the Wagtail admin interface. +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 @@ -73,17 +79,17 @@ 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 (``date``) and Wagtail models (``feed_image`` and ``body``). - - - +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. `_ @@ -105,6 +111,7 @@ 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. @@ -125,6 +132,7 @@ A Parent node could provide its own function returning its descendant objects. 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. @@ -148,6 +156,7 @@ The model for the leaf could provide a function that traverses the tree in the o 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. @@ -155,8 +164,16 @@ Your ``Page``-derived models might have other interrelationships which extend th Anatomy of a Wagtail Request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -route() -> serve() -> render() -> template +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 @@ -339,9 +356,6 @@ Iterating through ``self.tags.all`` will display each tag associated with ``self 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. - - - Page Data Clusters with ParentalKey ----------------------------------- @@ -369,7 +383,9 @@ For each of your ``Page``-derived models, Wagtail will look for a template in th 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``. +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 From 73fa1000b138cfc9b0c8ca4527c392fa685e4f3f Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Mon, 19 May 2014 21:14:45 -0400 Subject: [PATCH 22/26] editing api started --- docs/editing_api.rst | 119 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 2 +- docs/panels.rst | 36 ------------- 3 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 docs/editing_api.rst delete mode 100644 docs/panels.rst diff --git a/docs/editing_api.rst b/docs/editing_api.rst new file mode 100644 index 0000000000..99791c333e --- /dev/null +++ b/docs/editing_api.rst @@ -0,0 +1,119 @@ + +Wagtail Editing API +=================== + +Wagtail provides a highly-customizable editing interface consisting of several components: + + * **Fields** — built-in content types to augment the basic types provided by Django. + * **Field Widgets** — editing widgets which streamline data input + * **Panels** — containers which hold related field widgets + * **Choosers** — interfaces for finding related objects in a ForeignKey relationship + +Configuring your models to use these components will shape the Wagtail editor to your needs. Wagtail also provides an API for injecting custom CSS and Javascript for further customization, including extending the hallo.js rich text editor. + +There is also an Edit Handler API for creating your own Wagtail editor components. + + +Fields +~~~~~~ + +Django's field types are automatically recognized and provided with an appropriate widget for input. + + +``RichTextField`` + + body = RichTextField() + +``Image`` + + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + +``Document`` + + link_document = models.ForeignKey( + 'wagtaildocs.Document', + null=True, + blank=True, + related_name='+' + ) + +``Page`` + + page = models.ForeignKey( + 'wagtailcore.Page', + related_name='adverts', + null=True, + blank=True + ) + +Can also use more specific models. + + +Snippets + +Snippets are not not subclasses, so you must include the model class directly. A chooser is provided which takes the snippet class. + + advert = models.ForeignKey( + 'demo.Advert', + related_name='+' + ) + + +Panels +~~~~~~ + + + + + + + + + + + +FieldPanel +~~~~~~~~~~ +Takes field_name, classname=None + + +MultiFieldPanel +~~~~~~~~~~~~~~~ +Condenses several ``FieldPanel``s under a single heading and classname. + + +InlinePanel +~~~~~~~~~~~ +Allows for creating/editing a modelcluster of related objects (carousel example). + + + +RichTextFieldPanel +~~~~~~~~~~~~~~~~~~ + + + + +PageChooserPanel +~~~~~~~~~~~~~~~~ + +ImageChooserPanel +~~~~~~~~~~~~~~~~~ + +DocumentChooserPanel +~~~~~~~~~~~~~~~~~~~~ + +SnippetChooserPanel +~~~~~~~~~~~~~~~~~~~ + +Edit Handler API +~~~~~~~~~~~~~~~~ + + + diff --git a/docs/index.rst b/docs/index.rst index defbe99cf5..9717326d4b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support gettingstarted building_your_site - panels + editing_api snippets wagtail_search advanced_topics diff --git a/docs/panels.rst b/docs/panels.rst deleted file mode 100644 index 47cd3e1a73..0000000000 --- a/docs/panels.rst +++ /dev/null @@ -1,36 +0,0 @@ - -Panels & Edit Handlers -====================== - -RichTextField -~~~~~~~~~~~~~ - -Image -~~~~~ - -FieldPanel -~~~~~~~~~~ - -MultiFieldPanel -~~~~~~~~~~~~~~~ - -InlinePanel -~~~~~~~~~~~ - -PageChooserPanel -~~~~~~~~~~~~~~~~ - -ImageChooserPanel -~~~~~~~~~~~~~~~~~ - -DocumentChooserPanel -~~~~~~~~~~~~~~~~~~~~ - -SnippetChooserPanel -~~~~~~~~~~~~~~~~~~~ - -Custom Panels -~~~~~~~~~~~~~ - - - From b6788f7c66bf13c7b418d6fcecf40c2e9afda6d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Tue, 20 May 2014 23:34:47 -0400 Subject: [PATCH 23/26] Editing API docs looking better at least --- docs/building_your_site.rst | 3 - docs/editing_api.rst | 193 ++++++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 64 deletions(-) diff --git a/docs/building_your_site.rst b/docs/building_your_site.rst index 2768cb9cf8..2f010ba8c0 100644 --- a/docs/building_your_site.rst +++ b/docs/building_your_site.rst @@ -356,10 +356,7 @@ Iterating through ``self.tags.all`` will display each tag associated with ``self 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. -Page Data Clusters with ParentalKey ------------------------------------ -The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page. diff --git a/docs/editing_api.rst b/docs/editing_api.rst index 99791c333e..8fcb0b7159 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -1,72 +1,146 @@ -Wagtail Editing API -=================== +Editing API +=========== Wagtail provides a highly-customizable editing interface consisting of several components: - * **Fields** — built-in content types to augment the basic types provided by Django. - * **Field Widgets** — editing widgets which streamline data input - * **Panels** — containers which hold related field widgets - * **Choosers** — interfaces for finding related objects in a ForeignKey relationship + * **Fields** — built-in content types to augment the basic types provided by Django. + * **Panels** — the basic editing blocks for fields, groups of fields, and related object clusters + * **Choosers** — interfaces for finding related objects in a ForeignKey relationship Configuring your models to use these components will shape the Wagtail editor to your needs. Wagtail also provides an API for injecting custom CSS and Javascript for further customization, including extending the hallo.js rich text editor. There is also an Edit Handler API for creating your own Wagtail editor components. -Fields -~~~~~~ +Defining Panels +~~~~~~~~~~~~~~~ -Django's field types are automatically recognized and provided with an appropriate widget for input. +A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick the appropriate editing widget for most Django field types, you just need to add a panel for each field you want to show in the Wagtail page editor, in the order you want them to appear. + +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 )`` + 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`. + +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. + +Let's look at an example of a panel definition: + +.. code-block:: python + + COMMON_PANELS = ( + FieldPanel('slug'), + FieldPanel('seo_title'), + FieldPanel('show_in_menus'), + FieldPanel('search_description'), + ) + + ... + + class ExamplePage( Page ): + # field definitions omitted + ... + + ExamplePage.content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('body', classname="full"), + FieldPanel('date'), + ImageChooserPanel('splash_image'), + DocumentChooserPanel('free_download'), + PageChooserPanel('related_page'), + ] + + ExamplePage.promote_panels = [ + MultiFieldPanel(COMMON_PANELS, "Common page configuration"), + ] -``RichTextField`` - body = RichTextField() -``Image`` - feed_image = models.ForeignKey( - 'wagtailimages.Image', - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) +Built-in Fields and Choosers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``Document`` +Django's field types are automatically recognized and provided with an appropriate widget for input. Just define that field the normal Django way and pass the field name into ``FieldPanel()`` when defining your panels. Wagtail will take care of the rest. - link_document = models.ForeignKey( - 'wagtaildocs.Document', - null=True, - blank=True, - related_name='+' - ) +Here are some Wagtail-specific types that you might include as fields in your models. -``Page`` - page = models.ForeignKey( - 'wagtailcore.Page', - related_name='adverts', - null=True, - blank=True - ) +Rich Text (HTML) +---------------- + +Wagtail provides a general-purpose WYSIWYG editor for creating rich text content (HTML) and embedding media such as images, video, and documents. To include this in your models, use the ``RichTextField()`` function when defining a model field: + +.. code-block:: python + + from wagtail.wagtailcore.fields import RichTextField + ... + class BookPage(Page): + book_text = RichTextField() + + + +If you're interested in extending the capabilities of the Wagtail editor, See :ref:`extending_wysiwyg`. + + +Images +------ + + from wagtail.wagtailimages.models import Image + + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + +Documents +--------- + + from wagtail.wagtaildocs.models import Document + + link_document = models.ForeignKey( + 'wagtaildocs.Document', + null=True, + blank=True, + related_name='+' + ) + + +Pages and Page-derived Models +----------------------------- + + from wagtail.wagtailcore.models import Page + + page = models.ForeignKey( + 'wagtailcore.Page', + related_name='+', + null=True, + blank=True + ) Can also use more specific models. -Snippets +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. - advert = models.ForeignKey( - 'demo.Advert', - related_name='+' - ) - - -Panels -~~~~~~ + advert = models.ForeignKey( + 'demo.Advert', + related_name='+' + ) @@ -78,25 +152,6 @@ Panels -FieldPanel -~~~~~~~~~~ -Takes field_name, classname=None - - -MultiFieldPanel -~~~~~~~~~~~~~~~ -Condenses several ``FieldPanel``s under a single heading and classname. - - -InlinePanel -~~~~~~~~~~~ -Allows for creating/editing a modelcluster of related objects (carousel example). - - - -RichTextFieldPanel -~~~~~~~~~~~~~~~~~~ - @@ -112,6 +167,22 @@ DocumentChooserPanel SnippetChooserPanel ~~~~~~~~~~~~~~~~~~~ + +.. _inline_panels: + +Inline Panels and Model Clusters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page. + + +.. _extending_wysiwyg: + +Extending the WYSIWYG Editor (hallo.js) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + Edit Handler API ~~~~~~~~~~~~~~~~ From 779ac271424254e765ea0e0ecf20ba050c451ffa Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Thu, 22 May 2014 19:25:30 -0400 Subject: [PATCH 24/26] 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 1f5cec1bf1..7e23d8442d 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 62f37c12b5..49f80c3718 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 1c22685063..033c64e9ec 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 0f122d8a6c..a42c400998 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 8fcb0b7159..2985a12298 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 f64eec0b9e..abbb7fdbea 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 0000000000..db88daf7c2 --- /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 25/26] 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 4112e3753f..11517ada25 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 26/26] 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 a42c400998..fb336e18bc 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