diff --git a/.travis.yml b/.travis.yml index e23794d4f37..8764f28596e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ services: # Package installation install: - python setup.py install - - pip install psycopg2 pyelasticsearch elasticutils==0.8.2 wand + - pip install psycopg2 elasticsearch wand embedly - pip install coveralls # Pre-test configuration before_script: diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6c28594a916..8ebe93959a0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,14 +3,28 @@ Changelog 0.4 (xx.xx.20xx) ~~~~~~~~~~~~~~~~ + * ElasticUtils/pyelasticsearch swapped for elasticsearch-py + * Added notification preferences * Added 'original' as a resizing rule supported by the 'image' tag * Hallo.js updated to version 1.0.4 * Snippets are now ordered alphabetically * Removed the "More" section from the admin menu * Added pagination to page listings in admin + * Support for setting a subpage_types property on page models, to define which page types are allowed as subpages + * Added a new datetime picker widget + * Added styleguide (mainly for wagtail developers) + * Aesthetic improvements to preview experience + * 'image' tag now accepts extra keyword arguments to be output as attributes on the img tag + * Added an 'attrs' property to image rendition objects to output src, width, height and alt attributes all in one go + * Added 'construct_whitelister_element_rules' hook for customising the HTML whitelist used when saving rich text fields + * Added 'in_menu' and 'not_in_menu' methods to PageQuerySet + * Added 'get_next_siblings' and 'get_prev_siblings' to Page + * Added init_new_page signal * Fix: Animated GIFs are now coalesced before resizing * Fix: Wand backend clones images before modifying them * Fix: Admin breadcrumb now positioned correctly on mobile + * Fix: Page chooser breadcrumb now updates the chooser modal instead of linking to Explorer + * Fix: Embeds - Fixed crash when no HTML field is sent back from the embed provider 0.3.1 (03.06.2014) ~~~~~~~~~~~~~~~~~~ diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index d7222307d98..df81741c9b1 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -1,8 +1,8 @@ Original Authors ================ -* Matthew Westcott matthew.westcott@torchbox.com -* David Cranwell david.cranwell@torchbox.com +* Matthew Westcott matthew.westcott@torchbox.com twitter: @gasmanic +* David Cranwell david.cranwell@torchbox.com twitter: @davecranwell * Karl Hobley karl.hobley@torchbox.com * Helen Chapman helen.chapman@torchbox.com @@ -28,6 +28,8 @@ Contributors * Ben Margolis * Tom Talbot * Jeffrey Hearn +* Robert Clark +* Tim Heap Translators =========== diff --git a/README.rst b/README.rst index 62b3c5cd5ac..0e68b8c6b18 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ .. image:: https://travis-ci.org/torchbox/wagtail.png?branch=master :target: https://travis-ci.org/torchbox/wagtail -.. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master&zxcv +.. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master&zxcv1 :target: https://coveralls.io/r/torchbox/wagtail?branch=master .. image:: https://pypip.in/v/wagtail/badge.png?zxcv diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst index cf455d4ca97..c2404036453 100644 --- a/docs/building_your_site/djangodevelopers.rst +++ b/docs/building_your_site/djangodevelopers.rst @@ -1,9 +1,15 @@ For Django developers ===================== +.. contents:: Contents + :local: + .. note:: This documentation is currently being written. +Overview +~~~~~~~~ + Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the construction of Django models using Wagtail's ``Page`` as a base. Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn't prescribe ways to organize and interrelate your content, but here we've sketched out some strategies for organizing your models. @@ -204,7 +210,6 @@ Methods: * get_context * get_template * is_navigable -* get_other_siblings * get_ancestors * get_descendants * get_siblings @@ -269,6 +274,7 @@ not_type(self, model): return self.get_query_set().not_type(model) +.. _wagtail_site_admin: Site ~~~~ @@ -278,3 +284,13 @@ Django's built-in admin interface provides the way to map a "site" (hostname or 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. + + +.. _redirects: + +Redirects +~~~~~~~~~ + +Wagtail provides a simple interface for creating arbitrary redirects to and from any URL. + +.. image:: ../images/screen_wagtail_redirects.png diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index f0054eaab59..35aa1c84509 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -1,7 +1,8 @@ For Front End developers ======================== -.. contents:: +.. contents:: Contents + :local: ======================== Overview @@ -90,6 +91,9 @@ In addition to Django's standard tags and filters, Wagtail provides some of its Images (tag) ~~~~~~~~~~~~ +.. versionchanged:: 0.4 + The 'image_tags' tags library was renamed to 'wagtailimages_tags' + The ``image`` tag inserts an XHTML-compatible ``img`` element into the page, setting its ``src``, ``width``, ``height`` and ``alt``. See also :ref:`image_tag_alt`. The syntax for the tag is thus:: @@ -100,7 +104,7 @@ For example: .. code-block:: django - {% load image %} + {% load wagtailimages_tags %} ... {% image self.photo width-400 %} @@ -186,30 +190,57 @@ The available resizing methods are: More control over the ``img`` tag --------------------------------- -In some cases greater control over the ``img`` tag is required, for example to add a custom ``class``. Rather than generating the ``img`` element for you, Wagtail can assign the relevant data to another object using Django's ``as`` syntax: +Wagtail provides two shorcuts to give greater control over the ``img`` element: + +.. versionadded:: 0.4 +**Adding attributes to the {% image %} tag** + +Extra attributes can be specified with the syntax ``attribute="value"``: .. code-block:: django - - {% load image %} - ... + + {% image self.photo width-400 class="foo" id="bar" %} + +No validation is performed on attributes add in this way by the developer. It's possible to add `src`, `width`, `height` and `alt` of your own that might conflict with those generated by the tag itself. + + +**Generating the image "as"** + +Wagtail can assign the image data to another object using Django's ``as`` syntax: + +.. code-block:: django + {% image self.photo width-400 as tmp_photo %} {{ tmp_photo.alt }} +.. versionadded:: 0.4 +The ``attrs`` shortcut +----------------------- + +You can also use the ``attrs`` property as a shorthand to output the ``src``, ``width``, ``height`` and ``alt`` attributes in one go: + +.. code-block:: django + + + .. _rich-text-filter: Rich text (filter) ~~~~~~~~~~~~~~~~~~ +.. versionchanged:: 0.4 + The 'rich_text' tags library was renamed to 'wagtailcore_tags' + This filter takes a chunk of HTML content and renders it as safe HTML in the page. Importantly it also expands internal shorthand references to embedded images and links made in the Wagtail editor into fully-baked HTML ready for display. Only fields using ``RichTextField`` need this applied in the template. .. code-block:: django - {% load rich_text %} + {% load wagtailcore_tags %} ... {{ self.body|richtext }} @@ -245,6 +276,9 @@ Wagtail embeds and images are included at their full width, which may overflow t Internal links (tag) ~~~~~~~~~~~~~~~~~~~~ +.. versionchanged:: 0.4 + The 'pageurl' tags library was renamed to 'wagtailcore_tags' + pageurl -------- @@ -252,7 +286,7 @@ Takes a Page object and returns a relative URL (``/foo/bar/``) if within the sam .. code-block:: django - {% load pageurl %} + {% load wagtailcore_tags %} ... @@ -263,7 +297,7 @@ Takes any ``slug`` as defined in a page's "Promote" tab and returns the URL for .. code-block:: django - {% load slugurl %} + {% load wagtailcore_tags %} ... diff --git a/docs/contributing.rst b/docs/contributing.rst index feb3b96f874..f5eb168b2c4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -17,6 +17,26 @@ Coding guidelines * PEP8. We ask that all Python contributions adhere to the `PEP8 `_ style guide, apart from the restriction on line length (E501). The `pep8 tool `_ makes it easy to check your code, e.g. ``pep8 --ignore=E501 your_file.py``. * Tests. Wagtail has a suite of tests, which we are committed to improving and expanding. We run continuous integration at `travis-ci.org/torchbox/wagtail `_ to ensure that no commits or pull requests introduce test failures. If your contributions add functionality to Wagtail, please include the additional tests to cover it; if your contributions alter existing functionality, please update the relevant tests accordingly. +Styleguide +~~~~~~~~~~ + +Developers working on the Wagtail UI or creating new UI components may wish to test their work against our Styleguide, which is provided as the contrib module "wagtailstyleguide". + +To install the styleguide module on your site, add it to the list of ``INSTALLED_APPS`` in your settings: + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'wagtail.contrib.wagtailstyleguide', + ... + ) + +At present the styleguide is static: new UI components must be added to it manually, and there are no hooks into it for other modules to use. We hope to support hooks in the future. + +The styleguide doesn't currently provide examples of all the core interface components; notably the Page, Document, Image and Snippet chooser interfaces are not currently represented. + + Translations ~~~~~~~~~~~~ diff --git a/docs/editing_api.rst b/docs/editing_api.rst index c0637002600..609237e76c6 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -1,28 +1,26 @@ -Editing API +Defining models with the 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. + * **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. +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. - Defining Panels ~~~~~~~~~~~~~~~ -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. +A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick the appropriate editing widget for most Django field types; implementors just need to add a panel for each field they want to show in the Wagtail page editor, in the order they want them to appear. -There are three types of panels: +There are four basic 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. @@ -30,10 +28,21 @@ There are three types of panels: ``MultiFieldPanel( children, heading="", classname=None )`` This panel condenses several ``FieldPanel`` s or choosers, from a list or tuple, under a single ``heading`` string. - ``InlinePanel( base_model, relation_name, panels=None, label='', help_text='' )`` + ``InlinePanel( base_model, relation_name, panels=None, classname=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 explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`. -Wagtail provides a tabbed interface to help organize panels. ``content_panels`` is the main tab, used for the meat of your model content. The other, ``promote_panels``, is suggested for organizing metadata about the content, such as SEO information and other machine-readable information. Since you're writing the panel definitions, you can organize them however you want. + ``FieldRowPanel( children, classname=None)`` + This panel is purely aesthetic. It creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below. Use of FieldRowPanel particularly helps reduce the "snow-blindness" effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an "Event" which had a starting date and ending date, it may be intuitive to find the start and end date on the same "row". + + FieldRowPanel should be used in combination with ``col*`` classnames added to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is layed out using a grid system, in which the maximum width of the editor is 12 columns wide. Classes ``col1``-``col12`` can be applied to each child of a FieldRowPanel. The class ``col3`` will ensure that field appears 3 columns wide or a quarter the width. ``col4`` would cause the field to be 4 columns wide, or a third the width. + + **(In addition to these four, there are also Chooser Panels, detailed below.)** + +Wagtail provides a tabbed interface to help organize panels. Three such tabs are provided: + +* ``content_panels`` is the main tab, used for the bulk of your model's fields. +* ``promote_panels`` is suggested for organizing fields regarding the promotion of the page around the site and the Internet. For example, a field to dictate whether the page should show in site-wide menus, descriptive text that should appear in site search results, SEO-friendly titles, OpenGraph meta tag content and other machine-readable information. +* ``settings_panels`` is essentially for non-copy fields. By default it contains the page's scheduled publishing fields. Other suggested fields could include a field to switch between one layout/style and another. Let's look at an example of a panel definition: @@ -55,7 +64,10 @@ Let's look at an example of a panel definition: ExamplePage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('body', classname="full"), - FieldPanel('date'), + FieldRowPanel([ + FieldPanel('start_date', classname="col3"), + FieldPanel('end_date', classname="col3"), + ]), ImageChooserPanel('splash_image'), DocumentChooserPanel('free_download'), PageChooserPanel('related_page'), @@ -119,7 +131,7 @@ One of the features of Wagtail is a unified image library, which you can access on_delete=models.SET_NULL, related_name='+' ) - + BookPage.content_panels = [ ImageChooserPanel('cover'), # ... @@ -225,7 +237,7 @@ Snippets are vanilla Django models you create yourself without a Wagtail-provide on_delete=models.SET_NULL, related_name='+' ) - + BookPage.content_panels = [ SnippetChooserPanel('advert', Advert), # ... @@ -254,6 +266,12 @@ Titles Use ``classname="title"`` to make Page's built-in title field stand out with more vertical padding. +Col* +------ + +Fields within a ``FieldRowPanel`` can have their width dictated in terms of the number of columns it should span. The ``FieldRowPanel`` is always considered to be 12 columns wide regardless of browser size or the nesting of ``FieldRowPanel`` in any other type of panel. Specify a number of columns thus: ``col3``, ``col4``, ``col6`` etc (up to 12). The resulting width with be *relative* to the full width of the ``FieldRowPanel``. + + Required Fields --------------- @@ -346,9 +364,9 @@ The ``RelatedLink`` class is a vanilla Django abstract model. The ``BookPageRela For another example of using model clusters, see :ref:`tagging` -For more on ``django-modelcluster``, visit `the django-modelcluster github project page`_ ). +For more on ``django-modelcluster``, visit `the django-modelcluster github project page`_. -.. _the django-modelcluster github page: https://github.com/torchbox/django-modelcluster +.. _the django-modelcluster github project page: https://github.com/torchbox/django-modelcluster .. _extending_wysiwyg: @@ -366,13 +384,11 @@ hallo.js plugin names are prefixed with the ``"IKS."`` namespace, but the ``name For information on developing custom hallo.js plugins, see the project's page: https://github.com/bergie/hallo - Edit Handler API ~~~~~~~~~~~~~~~~ - -Hooks ------ +Admin Hooks +----------- On loading, Wagtail will search for any app with the file ``wagtail_hooks.py`` and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail's execution, such as when a ``Page`` object is saved or when the main menu is constructed. @@ -484,7 +500,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func .. _construct_main_menu: ``construct_main_menu`` - Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``
  • `` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu. + Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``
  • `` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu. .. code-block:: python @@ -546,6 +562,60 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func + 'demo/css/vendor/font-awesome/css/font-awesome.min.css">') hooks.register('insert_editor_css', editor_css) +.. _construct_whitelister_element_rules: + +``construct_whitelister_element_rules`` + .. versionadded:: 0.4 + Customise the rules that define which HTML elements are allowed in rich text areas. By default only a limited set of HTML elements and attributes are whitelisted - all others are stripped out. The callables passed into this hook must return a dict, which maps element names to handler functions that will perform some kind of manipulation of the element. These handler functions receive the element as a `BeautifulSoup `_ Tag object. + + The ``wagtail.wagtailcore.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute). + + For example, the following hook function will add the ``
    `` element to the whitelist, and allow the ``target`` attribute on ```` elements: + + .. code-block:: python + + from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes + + def whitelister_element_rules(): + return { + 'blockquote': allow_without_attributes, + 'a': attribute_rule({'href': check_url, 'target': True}), + } + hooks.register('construct_whitelister_element_rules', whitelister_element_rules) + + +Image Formats in the Rich Text Editor +------------------------------------- + +On loading, Wagtail will search for any app with the file ``image_formats.py`` and execute the contents. This provides a way to customize the formatting options shown to the editor when inserting images in the ``RichTextField`` editor. + +As an example, add a "thumbnail" format: + +.. code-block:: python + # image_formats.py + from wagtail.wagtailimages.formats import Format, register_image_format + + register_image_format(Format('thumbnail', 'Thumbnail', 'richtext-image thumbnail', 'max-120x120')) + + +To begin, import the the ``Format`` class, ``register_image_format`` function, and optionally ``unregister_image_format`` function. To register a new ``Format``, call the ``register_image_format`` with the ``Format`` object as the argument. The ``Format`` takes the following init arguments: + +``name`` + The unique key used to identify the format. To unregister this format, call ``unregister_image_format`` with this string as the only argument. + +``label`` + The label used in the chooser form when inserting the image into the ``RichTextField``. + +``classnames`` + The string to assign to the ``class`` attribute of the generated ```` tag. + +``filter_spec`` + The string specification to create the image rendition. For more, see the :ref:`image_tag`. + + +To unregister, call ``unregister_image_format`` with the string of the ``name`` of the ``Format`` as the only argument. + Content Index Pages (CRUD) -------------------------- @@ -557,4 +627,3 @@ Custom Choosers Tests ----- - diff --git a/docs/editor_manual/new_pages/inserting_videos.rst b/docs/editor_manual/new_pages/inserting_videos.rst index 038f3e4274b..1b32c0d1c5a 100644 --- a/docs/editor_manual/new_pages/inserting_videos.rst +++ b/docs/editor_manual/new_pages/inserting_videos.rst @@ -1,3 +1,6 @@ + +.. _inserting_videos: + Inserting videos into body content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -9,4 +12,4 @@ As well as inserting videos into a carousel, Wagtail's rich text fields allow yo .. image:: ../../images/screen21_video_in_editor.png -* A placeholder with the name of the video and a screenshot will be inserted into the text area. Clicking the X in the top corner will remove the video. \ No newline at end of file +* A placeholder with the name of the video and a screenshot will be inserted into the text area. Clicking the X in the top corner will remove the video. diff --git a/docs/form_builder.rst b/docs/form_builder.rst index 9aa220e1970..a141cc3768a 100644 --- a/docs/form_builder.rst +++ b/docs/form_builder.rst @@ -1,3 +1,6 @@ + +.. _form_builder: + Form builder ============ diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index 458de387bc1..de635256c95 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -65,7 +65,7 @@ Wagtail instance available as the basis for your new site: - Install `Vagrant `_ 1.1+ - Clone the demonstration site, create the Vagrant box and initialise Wagtail:: - git clone git@github.com:torchbox/wagtaildemo.git + git clone https://github.com/torchbox/wagtaildemo.git cd wagtaildemo vagrant up vagrant ssh diff --git a/docs/images/screen_wagtail_redirects.png b/docs/images/screen_wagtail_redirects.png new file mode 100644 index 00000000000..516bdd04655 Binary files /dev/null and b/docs/images/screen_wagtail_redirects.png differ diff --git a/docs/index.rst b/docs/index.rst index abbb7fdbeaa..5d2344b1802 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support :maxdepth: 3 gettingstarted + settings building_your_site/index editing_api snippets @@ -19,6 +20,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support deploying performance static_site_generation + management_commands contributing support roadmap diff --git a/docs/management_commands.rst b/docs/management_commands.rst new file mode 100644 index 00000000000..3f77fc3eff6 --- /dev/null +++ b/docs/management_commands.rst @@ -0,0 +1,52 @@ +Management commands +=================== + +publish_scheduled_pages +----------------------- + +:code:`./manage.py publish_scheduled_pages` + +This command publishes or unpublishes pages that have had these actions scheduled by an editor. It is recommended to run this command once an hour. + +fixtree +------- + +:code:`./manage.py fixtree` + +This command scans for errors in your database and attempts to fix any issues it finds. + +move_pages +---------- + +:code:`manage.py move_pages from to` + +This command moves a selection of pages from one section of the tree to another. + +Options: + + - **from** + This is the **id** of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children. + + - **to** + This is the **id** of the page to move pages to. + +update_index +------------ + +:code:`./manage.py update_index` + +This command rebuilds the search index from scratch. It is only required when using Elasticsearch. + +It is recommended to run this command once a week and at the following times: + + - whenever any pages have been created through a script (after an import, for example) + - whenever any changes have been made to models or search configuration + +The search may not return any results while this command is running, so avoid running it at peak times. + +search_garbage_collect +---------------------- + +:code:`./manage.py search_garbage_collect` + +Wagtail keeps a log of search queries that are popular on your website. On high traffic websites, this log may get big and you may want to clean out old search queries. This command cleans out all search query logs that are more than one week old. diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 00000000000..018af5d0154 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,603 @@ + +============================== +Configuring Django for Wagtail +============================== + +To install Wagtail completely from scratch, create a new Django project and an app within that project. For instructions on these tasks, see `Writing your first Django app`_. Your project directory will look like the following:: + + myproject/ + myproject/ + __init__.py + settings.py + urls.py + wsgi.py + myapp/ + __init__.py + models.py + tests.py + admin.py + views.py + manage.py + +From your app directory, you can safely remove ``admin.py`` and ``views.py``, since Wagtail will provide this functionality for your models. Configuring Django to load Wagtail involves adding modules and variables to ``settings.py`` and urlconfs to ``urls.py``. For a more complete view of what's defined in these files, see `Django Settings`_ and `Django URL Dispatcher`_. + +.. _Writing your first Django app: https://docs.djangoproject.com/en/dev/intro/tutorial01/ + +.. _Django Settings: https://docs.djangoproject.com/en/dev/topics/settings/ + +.. _Django URL Dispatcher: https://docs.djangoproject.com/en/dev/topics/http/urls/ + +What follows is a settings reference which skips many boilerplate Django settings. If you just want to get your Wagtail install up quickly without fussing with settings at the moment, see :ref:`complete_example_config`. + + +Middleware (settings.py) +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + 'wagtail.wagtailcore.middleware.SiteMiddleware', + + 'wagtail.wagtailredirects.middleware.RedirectMiddleware', + ) + +Wagtail requires several common Django middleware modules to work and cover basic security. Wagtail provides its own middleware to cover these tasks: + +``SiteMiddleware`` + Wagtail routes pre-defined hosts to pages within the Wagtail tree using this middleware. For configuring sites, see :ref:`wagtail_site_admin`. + +``RedirectMiddleware`` + Wagtail provides a simple interface for adding arbitrary redirects to your site and this module makes it happen. + + +Apps (settings.py) +~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'south', + 'compressor', + 'taggit', + 'modelcluster', + 'django.contrib.admin', + + 'wagtail.wagtailcore', + 'wagtail.wagtailadmin', + 'wagtail.wagtaildocs', + 'wagtail.wagtailsnippets', + 'wagtail.wagtailusers', + 'wagtail.wagtailimages', + 'wagtail.wagtailembeds', + 'wagtail.wagtailsearch', + 'wagtail.wagtailredirects', + 'wagtail.wagtailforms', + + 'myapp', # your own app + ) + +Wagtail requires several Django app modules, third-party apps, and defines several apps of its own. Wagtail was built to be modular, so many Wagtail apps can be omitted to suit your needs. Your own app (here ``myapp``) is where you define your models, templates, static assets, template tags, and other custom functionality for your site. + + +Third-Party Apps +---------------- + +``south`` + Used for database migrations. See `South Documentation`_. + +.. _South Documentation: http://south.readthedocs.org/en/latest/ + +``compressor`` + Static asset combiner and minifier for Django. Compressor also enables for the use of preprocessors. See `Compressor Documentation`_. + +.. _Compressor Documentation: http://django-compressor.readthedocs.org/en/latest/ + +``taggit`` + Tagging framework for Django. This is used internally within Wagtail for image and document tagging and is available for your own models as well. See :ref:`tagging` for a Wagtail model recipe or the `Taggit Documentation`_. + +.. _Taggit Documentation: http://django-taggit.readthedocs.org/en/latest/index.html + +``modelcluster`` + Extension of Django ForeignKey relation functionality, which is used in Wagtail pages for on-the-fly related object creation. For more information, see :ref:`inline_panels` or `the django-modelcluster github project page`_. + +.. _the django-modelcluster github project page: https://github.com/torchbox/django-modelcluster + +``django.contrib.admin`` + The Django admin module. While Wagtail will eventually provide a sites-editing interface, the Django admin is included for now to provide that functionality. + + +Wagtail Apps +------------ + +``wagtailcore`` + The core functionality of Wagtail, such as the ``Page`` class, the Wagtail tree, and model fields. + +``wagtailadmin`` + The administration interface for Wagtail, including page edit handlers. + +``wagtaildocs`` + The Wagtail document content type. + +``wagtailsnippets`` + Editing interface for non-Page models and objects. See :ref:`Snippets`. + +``wagtailusers`` + User editing interface. + +``wagtailimages`` + The Wagtail image content type. + +``wagtailembeds`` + Module governing oEmbed and Embedly content in Wagtail rich text fields. See :ref:`inserting_videos`. + +``wagtailsearch`` + Search framework for Page content. See :ref:`search`. + +``wagtailredirects`` + Admin interface for creating arbitrary redirects on your site. See :ref:`redirects`. + +``wagtailforms`` + Models for creating forms on your pages and viewing submissions. See :ref:`form_builder`. + + +Settings Variables (settings.py) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Authentication +-------------- + +.. code-block:: python + + LOGIN_URL = 'wagtailadmin_login' + LOGIN_REDIRECT_URL = 'wagtailadmin_home' + +These settings variables set the Django authentication system to redirect to the Wagtail admin login. If you plan to use the Django authentication module to log in non-privileged users, you should set these variables to your own login views. See `Django User Authentication`_. + +.. _Django User Authentication: https://docs.djangoproject.com/en/dev/topics/auth/ + + +Site Name +--------- + +.. code-block:: python + + WAGTAIL_SITE_NAME = 'Stark Industries Skunkworks' + +This is the human-readable name of your Wagtail install which welcomes users upon login to the Wagtail admin. + + +Search +------ + +.. code-block:: python + + # Override the search results template for wagtailsearch + WAGTAILSEARCH_RESULTS_TEMPLATE = 'myapp/search_results.html' + WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'myapp/includes/search_listing.html' + + # Replace the search backend + WAGTAILSEARCH_BACKENDS = { + 'default': { + 'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch', + 'INDEX': 'myapp' + } + } + +The search settings customize the search results templates as well as choosing a custom backend for search. For a full explanation, see :ref:`search`. + + +Embeds +------ + +Wagtail uses the oEmbed standard with a large but not comprehensive number of "providers" (youtube, vimeo, etc.). You can also use a different embed backend by providing an Embedly key or replacing the embed backend by writing your own embed finder function. + +.. code-block:: python + + WAGTAILEMBEDS_EMBED_FINDER = 'myapp.embeds.my_embed_finder_function' + +Use a custom embed finder function, which takes a URL and returns a dict with metadata and embeddable HTML. Refer to the ``wagtail.wagtailemebds.embeds`` module source for more information and examples. + +.. code-block:: python + + # not a working key, get your own! + EMBEDLY_KEY = '253e433d59dc4d2xa266e9e0de0cb830' + +Providing an API key for the Embedly service will use that as a embed backend, with a more extensive list of providers, as well as analytics and other features. For more information, see `Embedly`_. + +.. _Embedly: http://embed.ly/ + +To use Embedly, you must also install their python module: + +.. code-block:: bash + + $ pip install embedly + + +Images +------ + +.. code-block:: python + + WAGTAILIMAGES_IMAGE_MODEL = 'myapp.MyImage' + +This setting lets you provide your own image model for use in Wagtail, which might extend the built-in ``AbstractImage`` class or replace it entirely. + + +Email Notifications +------------------- + +.. code-block:: python + + WAGTAILADMIN_NOTIFICATION_FROM_EMAIL = 'wagtail@myhost.io' + +Wagtail sends email notifications when content is submitted for moderation, and when the content is accepted or rejected. This setting lets you pick which email address these automatic notifications will come from. If omitted, Django will fall back to using the ``DEFAULT_FROM_EMAIL`` variable if set, and ``webmaster@localhost`` if not. + + +Other Django Settings Used by Wagtail +------------------------------------- + +.. code-block:: python + + ALLOWED_HOSTS + APPEND_SLASH + AUTH_USER_MODEL + BASE_URL + CACHES + DEFAULT_FROM_EMAIL + INSTALLED_APPS + MEDIA_ROOT + SESSION_COOKIE_DOMAIN + SESSION_COOKIE_NAME + SESSION_COOKIE_PATH + STATIC_URL + TEMPLATE_CONTEXT_PROCESSORS + USE_I18N + +For information on what these settings do, see `Django Settings`_. + +.. _Django Settings: https://docs.djangoproject.com/en/dev/ref/settings/ + + +Search Signal Handlers +---------------------- + +.. code-block:: python + + from wagtail.wagtailsearch import register_signal_handlers as wagtailsearch_register_signal_handlers + + wagtailsearch_register_signal_handlers() + +This loads Wagtail's search signal handlers, which need to be loaded very early in the Django life cycle. While not technically a urlconf, this is a convenient place to load them. Calling this function registers signal handlers to watch for when indexed models get saved or deleted. This allows wagtailsearch to update ElasticSearch automatically. + + +URL Patterns +------------ + +.. code-block:: python + + from django.contrib import admin + + from wagtail.wagtailcore import urls as wagtail_urls + from wagtail.wagtailadmin import urls as wagtailadmin_urls + from wagtail.wagtaildocs import urls as wagtaildocs_urls + from wagtail.wagtailsearch.urls import frontend as wagtailsearch_frontend_urls + + admin.autodiscover() + + urlpatterns = patterns('', + url(r'^django-admin/', include(admin.site.urls)), + + url(r'^admin/', include(wagtailadmin_urls)), + url(r'^search/', include(wagtailsearch_frontend_urls)), + url(r'^documents/', include(wagtaildocs_urls)), + + # Optional urlconf for including your own vanilla Django urls/views + url(r'', include('myapp.urls')), + + # For anything not caught by a more specific rule above, hand over to + # Wagtail's serving mechanism + url(r'', include(wagtail_urls)), + ) + +This block of code for your project's ``urls.py`` does a few things: + +* Load the vanilla Django admin interface to ``/django-admin/`` +* Load the Wagtail admin and its various apps +* Dispatch any vanilla Django apps you're using other than Wagtail which require their own urlconfs (this is optional, since Wagtail might be all you need) +* Lets Wagtail handle any further URL dispatching. + +That's not everything you might want to include in your project's urlconf, but it's what's necessary for Wagtail to flourish. + + +.. _complete_example_config: + +Ready to Use Example Config Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These two files should reside in your project directory (``myproject/myproject/``). + + +settings.py +----------- + +.. code-block:: python + + import os + + PROJECT_ROOT = os.path.join(os.path.dirname(__file__), '..', '..') + + DEBUG = True + TEMPLATE_DEBUG = DEBUG + + ADMINS = ( + # ('Your Name', 'your_email@example.com'), + ) + + MANAGERS = ADMINS + + # Default to dummy email backend. Configure dev/production/local backend + # as per https://docs.djangoproject.com/en/dev/topics/email/#email-backends + EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'myprojectdb', + 'USER': 'postgres', + 'PASSWORD': '', + 'HOST': '', # Set to empty string for localhost. + 'PORT': '', # Set to empty string for default. + 'CONN_MAX_AGE': 600, # number of seconds database connections should persist for + } + } + + # Hosts/domain names that are valid for this site; required if DEBUG is False + # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts + ALLOWED_HOSTS = [] + + # Local time zone for this installation. Choices can be found here: + # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name + # although not all choices may be available on all operating systems. + # On Unix systems, a value of None will cause Django to use the same + # timezone as the operating system. + # If running in a Windows environment this must be set to the same as your + # system time zone. + TIME_ZONE = 'Europe/London' + + # Language code for this installation. All choices can be found here: + # http://www.i18nguy.com/unicode/language-identifiers.html + LANGUAGE_CODE = 'en-gb' + + SITE_ID = 1 + + # If you set this to False, Django will make some optimizations so as not + # to load the internationalization machinery. + USE_I18N = True + + # If you set this to False, Django will not format dates, numbers and + # calendars according to the current locale. + # Note that with this set to True, Wagtail will fall back on using numeric dates + # in date fields, as opposed to 'friendly' dates like "24 Sep 2013", because + # Python's strptime doesn't support localised month names: https://code.djangoproject.com/ticket/13339 + USE_L10N = False + + # If you set this to False, Django will not use timezone-aware datetimes. + USE_TZ = True + + # Absolute filesystem path to the directory that will hold user-uploaded files. + # Example: "/home/media/media.lawrence.com/media/" + MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') + + # URL that handles the media served from MEDIA_ROOT. Make sure to use a + # trailing slash. + # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" + MEDIA_URL = '/media/' + + # Absolute path to the directory static files should be collected to. + # Don't put anything in this directory yourself; store your static files + # in apps' "static/" subdirectories and in STATICFILES_DIRS. + # Example: "/home/media/media.lawrence.com/static/" + STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') + + # URL prefix for static files. + # Example: "http://media.lawrence.com/static/" + STATIC_URL = '/static/' + + # List of finder classes that know how to find static files in + # various locations. + STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'compressor.finders.CompressorFinder', + ) + + # Make this unique, and don't share it with anybody. + SECRET_KEY = 'change-me' + + # List of callables that know how to import templates from various sources. + TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ) + + MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + 'wagtail.wagtailcore.middleware.SiteMiddleware', + + 'wagtail.wagtailredirects.middleware.RedirectMiddleware', + ) + + from django.conf import global_settings + TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( + 'django.core.context_processors.request', + ) + + ROOT_URLCONF = 'myproject.urls' + + # Python dotted path to the WSGI application used by Django's runserver. + WSGI_APPLICATION = 'wagtaildemo.wsgi.application' + + INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'south', + 'compressor', + 'taggit', + 'modelcluster', + 'django.contrib.admin', + + 'wagtail.wagtailcore', + 'wagtail.wagtailadmin', + 'wagtail.wagtaildocs', + 'wagtail.wagtailsnippets', + 'wagtail.wagtailusers', + 'wagtail.wagtailimages', + 'wagtail.wagtailembeds', + 'wagtail.wagtailsearch', + 'wagtail.wagtailredirects', + 'wagtail.wagtailforms', + + 'myapp', + ) + + EMAIL_SUBJECT_PREFIX = '[Wagtail] ' + + INTERNAL_IPS = ('127.0.0.1', '10.0.2.2') + + # django-compressor settings + COMPRESS_PRECOMPILERS = ( + ('text/x-scss', 'django_libsass.SassCompiler'), + ) + + # Auth settings + LOGIN_URL = 'wagtailadmin_login' + LOGIN_REDIRECT_URL = 'wagtailadmin_home' + + # A sample logging configuration. The only tangible logging + # performed by this configuration is to send an email to + # the site admins on every HTTP 500 error when DEBUG=False. + # See http://docs.djangoproject.com/en/dev/topics/logging for + # more details on how to customize your logging configuration. + LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } + } + + + # WAGTAIL SETTINGS + + # This is the human-readable name of your Wagtail install + # which welcomes users upon login to the Wagtail admin. + WAGTAIL_SITE_NAME = 'My Project' + + # Override the search results template for wagtailsearch + # WAGTAILSEARCH_RESULTS_TEMPLATE = 'myapp/search_results.html' + # WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'myapp/includes/search_listing.html' + + # Replace the search backend + #WAGTAILSEARCH_BACKENDS = { + # 'default': { + # 'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch', + # 'INDEX': 'myapp' + # } + #} + + # Wagtail email notifications from address + # WAGTAILADMIN_NOTIFICATION_FROM_EMAIL = 'wagtail@myhost.io' + + # If you want to use Embedly for embeds, supply a key + # (this key doesn't work, get your own!) + # EMBEDLY_KEY = '253e433d59dc4d2xa266e9e0de0cb830' + + +urls.py +------- + +.. code-block:: python + + from django.conf.urls import patterns, include, url + from django.conf.urls.static import static + from django.views.generic.base import RedirectView + from django.contrib import admin + from django.conf import settings + import os.path + + from wagtail.wagtailcore import urls as wagtail_urls + from wagtail.wagtailadmin import urls as wagtailadmin_urls + from wagtail.wagtaildocs import urls as wagtaildocs_urls + from wagtail.wagtailsearch.urls import frontend as wagtailsearch_frontend_urls + + admin.autodiscover() + + + # Signal handlers + from wagtail.wagtailsearch import register_signal_handlers as wagtailsearch_register_signal_handlers + wagtailsearch_register_signal_handlers() + + + urlpatterns = patterns('', + url(r'^django-admin/', include(admin.site.urls)), + + url(r'^admin/', include(wagtailadmin_urls)), + url(r'^search/', include(wagtailsearch_frontend_urls)), + url(r'^documents/', include(wagtaildocs_urls)), + + # For anything not caught by a more specific rule above, hand over to + # Wagtail's serving mechanism + url(r'', include(wagtail_urls)), + ) + + + if settings.DEBUG: + from django.contrib.staticfiles.urls import staticfiles_urlpatterns + + urlpatterns += staticfiles_urlpatterns() # tell gunicorn where static files are in dev mode + urlpatterns += static(settings.MEDIA_URL + 'images/', document_root=os.path.join(settings.MEDIA_ROOT, 'images')) + urlpatterns += patterns('', + (r'^favicon\.ico$', RedirectView.as_view(url=settings.STATIC_URL + 'myapp/images/favicon.ico')) + ) + + + diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst index 642a5e74148..6a77b06f3c0 100644 --- a/docs/wagtail_search.rst +++ b/docs/wagtail_search.rst @@ -1,3 +1,6 @@ + +.. _search: + Search ====== @@ -220,14 +223,14 @@ The default DB search backend uses Django's ``__icontains`` filter. Elasticsearch Backend ````````````````````` -Prerequisites are the Elasticsearch service itself and, via pip, the `elasticutils`_ and `pyelasticsearch`_ packages: +Prerequisites are the Elasticsearch service itself and, via pip, the `elasticsearch-py`_ package: .. code-block:: guess - pip install elasticutils pyelasticsearch + pip install elasticsearch .. note:: - The dependency on pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_. + If you are using Elasticsearch < 1.0, install elasticsearch-py version 0.4.5: ```pip install elasticsearch==0.4.5``` The backend is configured in settings: @@ -243,7 +246,7 @@ The backend is configured in settings: } } -Other than ``BACKEND`` the keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticutils. In addition, any other keys are passed directly to the Elasticsearch constructor as case-sensitive keyword arguments (e.g. ``'max_retries': 1``). +Other than ``BACKEND`` the keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticsearch-py. In addition, any other keys are passed directly to the Elasticsearch constructor as case-sensitive keyword arguments (e.g. ``'max_retries': 1``). If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Searchly`_, who offer a free account suitable for testing and development. To use Searchly: @@ -253,8 +256,6 @@ If you prefer not to run an Elasticsearch server in development or production, t - Configure ``URLS`` and ``INDEX`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS`` - Run ``./manage.py update_index`` -.. _elasticutils: http://elasticutils.readthedocs.org -.. _pyelasticsearch: http://pyelasticsearch.readthedocs.org .. _elasticsearch-py: http://elasticsearch-py.readthedocs.org .. _Searchly: http://www.searchly.com/ .. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up diff --git a/runtests.py b/runtests.py index 6fd37ebeb84..efa66910f07 100755 --- a/runtests.py +++ b/runtests.py @@ -13,7 +13,7 @@ if not settings.configured: try: - import elasticutils + import elasticsearch has_elasticsearch = True except ImportError: has_elasticsearch = False @@ -84,6 +84,7 @@ 'wagtail.wagtailsearch', 'wagtail.wagtailredirects', 'wagtail.wagtailforms', + 'wagtail.contrib.wagtailstyleguide', 'wagtail.tests', ], @@ -101,7 +102,9 @@ ), COMPRESS_ENABLED=False, # disable compression so that we can run tests on the content of the compress tag WAGTAILSEARCH_BACKENDS=WAGTAILSEARCH_BACKENDS, - WAGTAIL_SITE_NAME='Test Site' + WAGTAIL_SITE_NAME='Test Site', + LOGIN_REDIRECT_URL='wagtailadmin_home', + LOGIN_URL='wagtailadmin_login', ) diff --git a/wagtail/contrib/wagtailstyleguide/__init__.py b/wagtail/contrib/wagtailstyleguide/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/wagtail/contrib/wagtailstyleguide/models.py b/wagtail/contrib/wagtailstyleguide/models.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/wagtail/contrib/wagtailstyleguide/templates/wagtailstyleguide/base.html b/wagtail/contrib/wagtailstyleguide/templates/wagtailstyleguide/base.html new file mode 100644 index 00000000000..8506e90752d --- /dev/null +++ b/wagtail/contrib/wagtailstyleguide/templates/wagtailstyleguide/base.html @@ -0,0 +1,432 @@ +{% extends "wagtailadmin/base.html" %} +{% load wagtailadmin_tags %} +{% load compress i18n %} +{% load gravatar %} + +{% block extra_css %} + {% compress css %} + + {% endcompress %} +{% endblock %} + +{% block titletag %}{% trans 'Styleguide' %}{% endblock %} +{% block bodyclass %}styleguide{% endblock %} + +{% block content %} + {% trans "Styleguide" as title_trans %} + {% include "wagtailadmin/shared/header.html" with title=title_trans %} + +
    +

    Contents

    +
    + +
    +

    Colour palette

    + +
      +
    • color-teal
    • +
    • color-teal-darker
    • +
    • color-teal-dark
    • +
    +
      +
    • color-salmon
    • +
    • color-salmon-light
    • +
    +
      +
    • color-grey-1
    • +
    • color-grey-1-1
    • +
    • color-grey-2
    • +
    • color-grey-3
    • +
    • color-grey-4
    • +
    • color-grey-5
    • +
    +
      +
    • color-red
    • +
    • color-orange
    • +
    • color-green
    • +
    + +
    + +
    +

    Typography

    +

    This is an h1

    +

    This is an h2

    +

    This is an h3

    +

    This is an h4

    +
    This is an h5
    +

    This is a paragraph

    + +
      +
    • These are
    • +
    • items in an
    • +
    • unordered list
    • +
    + +
      +
    1. These are
    2. +
    3. items in an
    4. +
    5. ordered list
    6. +
    + + This is an example of code + +
    + +
    +

    Listings

    + +

    table listing

    + + + + + + + + + + + + + + + + + + + + +
    Heading 1Heading 2Heading 3
    +

    TD with title class

    +
    Regular listing TDRegular listing TD
    +

    TD with title class

    +
    Regular listing TDRegular listing TD
    + +

    ul listing

    +
      +
    • List item
    • +
    • List item
    • +
    • List item
    • +
    + +

    Listings used for choosing a list item

    + + + + + + + + + + + + + + + + + + + + +
    Heading 1Heading 2Heading 3
    +

    TD with title class

    +
    Regular listing TDRegular listing TD
    +

    TD with title class

    +
    Regular listing TDRegular listing TD
    +
    + +
    +

    Buttons

    + + button + + button-secondary + + yes + + no / serious + + bicolor with icon + + button-small + + bicolo button-small + + mixed 1 + + mixed 2 + +
    button on a div
    + +

    Buttons must have interaction possible (i.e be an input or button element) to get a suitable hover cursor

    + + + + + + + + + + +
    + + + + + +
    +

    Forms

    + +
    +
      + {% for field in example_form %} + {% if field.name == 'file' %} + {% include "wagtailimages/images/_file_field.html" %} + {% elif field.name == 'date' %} + {% include "wagtailadmin/shared/field_as_li.html" with input_classes="iconfield icon-date" %} + {% else %} + {% include "wagtailadmin/shared/field_as_li.html" %} + {% endif %} + {% endfor %} +
    • {% trans "Delete image" %}
    • +
    +
    + +

    TODO: Date picker

    +

    TODO: Time picker

    +

    TODO: Datetime picker

    +

    TODO: Rich text input

    +

    TODO: Page chooser

    +

    TODO: Image chooser

    +

    TODO: Document chooser

    +

    TODO: Snippet chooser

    +
    + +
    +

    Page editor

    +
    + +
    +

    Tabs

    + + + +

    Tabs are currently only used following headers, where they often appear merged with the bottom of the header:

    + + {% include "wagtailadmin/shared/header.html" with title=title_trans merged=1 %} + + +

    Tabs can also indicate errors:

    + + {% include "wagtailadmin/shared/header.html" with title=title_trans merged=1 %} + +
    + + + +
    +

    Misc formatters

    +

    Avatar icons

    + +

    Gravatar set

    +

    Gravatar not set

    + +

    Status tags

    +
    Primary tag
    + +
    Secondary tag
    +
    + +
    +

    Icons

    + +
      +
    • wagtail
    • +
    • wagtail-inverse
    • +
    • cogs
    • +
    • doc-empty-inverse
    • +
    • doc-empty
    • +
    • edit
    • +
    • arrow-up
    • +
    • arrow-down
    • + +
    • cross
    • +
    • folder-open-1
    • +
    • folder-inverse
    • +
    • mail
    • +
    • arrows-up-down
    • +
    • locked
    • +
    • unlocked
    • +
    • arrow-right
    • +
    • doc-full / file-text-alt
    • +
    • image / picture
    • +
    • doc-full-inverse
    • +
    • folder
    • +
    • plus
    • +
    • tag
    • +
    • folder-open-inverse
    • +
    • cog
    • +
    • tick
    • +
    • user
    • +
    • arrow-left
    • +
    • tick-inverse
    • +
    • plus-inverse
    • +
    • snippet
    • +
    • bold
    • +
    • italic
    • +
    • undo
    • +
    • repeat
    • +
    • list-ol
    • +
    • list-ul
    • + +
    • radio-full
    • +
    • radio-empty
    • +
    • arrow-up-big
    • +
    • arrow-down-big
    • +
    • group
    • +
    • media
    • +
    • horizontalrule
    • +
    • password
    • +
    • download
    • +
    • order
    • +
    • grip
    • +
    • home
    • +
    • order-down
    • +
    • order-up
    • +
    • bin
    • +
    • spinner
      spinner
    • +
    • pick
    • +
    • redirect
    • +
    • view
    • +
    • collapse-up
    • +
    • collapse-down
    • +
    • help
    • +
    • warning
    • +
    • success
    • +
    • date
    • +
    • time
    • +
    • form
    • +
    + +
    +
    + +{% endblock %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file diff --git a/wagtail/contrib/wagtailstyleguide/tests.py b/wagtail/contrib/wagtailstyleguide/tests.py new file mode 100644 index 00000000000..6a1c4318360 --- /dev/null +++ b/wagtail/contrib/wagtailstyleguide/tests.py @@ -0,0 +1,15 @@ +from django.test import TestCase +from django.core.urlresolvers import reverse + +from wagtail.tests.utils import WagtailTestUtils + + +class TestStyleGuide(TestCase, WagtailTestUtils): + def setUp(self): + self.login(); + + def test_styleguide(self): + response = self.client.get(reverse('wagtailstyleguide')) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailstyleguide/base.html') diff --git a/wagtail/contrib/wagtailstyleguide/views.py b/wagtail/contrib/wagtailstyleguide/views.py new file mode 100644 index 00000000000..9c830073c20 --- /dev/null +++ b/wagtail/contrib/wagtailstyleguide/views.py @@ -0,0 +1,44 @@ +from django import forms +from django.db import models +from django.shortcuts import render +from django.utils.translation import ugettext as _ +from django.contrib import messages +from django.contrib.auth.decorators import permission_required + +from wagtail.wagtailadmin.edit_handlers import PageChooserPanel +from wagtail.wagtailimages.edit_handlers import ImageChooserPanel +from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel + +from wagtail.wagtailadmin.forms import SearchForm +from wagtail.wagtailcore.fields import RichTextField + + +CHOICES = ( + ('choice1', 'choice 1'), + ('choice2', 'choice 2'), +) + +class ExampleForm(forms.Form): + text = forms.CharField(required=True, help_text="help text") + url = forms.URLField(required=True) + email = forms.EmailField(max_length=254) + date = forms.DateField() + time = forms.TimeField() + select = forms.ChoiceField(choices=CHOICES) + boolean = forms.BooleanField(required=False) + +@permission_required('wagtailadmin.access_admin') +def index(request): + + form = SearchForm(placeholder=_("Search something")) + + example_form = ExampleForm() + + messages.success(request, _("Success message")) + messages.warning(request, _("Warning message")) + messages.error(request, _("Error message")) + + return render(request, 'wagtailstyleguide/base.html', { + 'search_form': form, + 'example_form': example_form, + }) diff --git a/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py new file mode 100644 index 00000000000..ae2989be9ca --- /dev/null +++ b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py @@ -0,0 +1,26 @@ +from django.conf import settings +from django.conf.urls import include, url +from django.core import urlresolvers +from django.utils.html import format_html, format_html_join +from django.utils.translation import ugettext_lazy as _ + +from wagtail.wagtailadmin import hooks +from wagtail.wagtailadmin.menu import MenuItem + +from wagtail.wagtailimages import urls + +from . import views + + +def register_admin_urls(): + return [ + url(r'^styleguide/$', views.index, name='wagtailstyleguide'), + ] +hooks.register('register_admin_urls', register_admin_urls) + + +def construct_main_menu(request, menu_items): + menu_items.append( + MenuItem(_('Styleguide'), urlresolvers.reverse('wagtailstyleguide'), classnames='icon icon-image', order=1000) + ) +hooks.register('construct_main_menu', construct_main_menu) diff --git a/wagtail/tests/fixtures/test.json b/wagtail/tests/fixtures/test.json index 7f9742e8652..57a2591803d 100644 --- a/wagtail/tests/fixtures/test.json +++ b/wagtail/tests/fixtures/test.json @@ -39,7 +39,7 @@ "model": "wagtailcore.page", "fields": { "title": "Events", - "numchild": 3, + "numchild": 4, "show_in_menus": true, "live": true, "depth": 3, @@ -183,6 +183,37 @@ "pk": 8, "model": "tests.formpage", "fields": { + "to_address": "to@email.com", + "from_address": "from@email.com", + "subject": "The subject" + } +}, + +{ + "pk": 9, + "model": "wagtailcore.page", + "fields": { + "title": "Ameristralia Day", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010004", + "url_path": "/home/events/final-event/", + "slug": "final-event", + "owner": 3 + } +}, +{ + "pk": 9, + "model": "tests.eventpage", + "fields": { + "date_from": "2015-04-22", + "audience": "public", + "location": "Ameristralia", + "body": "

    come celebrate the independence of Ameristralia

    ", + "cost": "Free" } }, @@ -216,7 +247,7 @@ }, { - "pk": 9, + "pk": 10, "model": "wagtailcore.page", "fields": { "title": "Old style route method", @@ -231,7 +262,7 @@ } }, { - "pk": 9, + "pk": 10, "model": "tests.pagewitholdstyleroutemethod", "fields": { "content": "

    Test with old style route method

    " @@ -239,7 +270,7 @@ }, { - "pk": 10, + "pk": 11, "model": "wagtailcore.page", "fields": { "title": "Secret plans", @@ -254,7 +285,7 @@ } }, { - "pk": 10, + "pk": 11, "model": "tests.simplepage", "fields": { "content": "

    muahahahaha

    " @@ -262,7 +293,7 @@ }, { - "pk": 11, + "pk": 12, "model": "wagtailcore.page", "fields": { "title": "Steal underpants", @@ -277,7 +308,7 @@ } }, { - "pk": 11, + "pk": 12, "model": "tests.eventpage", "fields": { "date_from": "2015-07-04", @@ -338,6 +369,16 @@ ] } }, +{ + "pk": 6, + "model": "auth.group", + "fields": { + "name": "Admin non-editors", + "permissions": [ + ["access_admin", "wagtailadmin", "admin"] + ] + } +}, { "pk": 1, "model": "wagtailcore.grouppagepermission", @@ -473,6 +514,24 @@ "email": "siteeditor@example.com" } }, +{ + "pk": 6, + "model": "auth.user", + "fields": { + "username": "admin_only_user", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [ + ["Admin non-editors"] + ], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "admin_only_user@example.com" + } +}, { "pk": 1, @@ -497,8 +556,28 @@ "pk": 1, "model": "wagtailcore.pageviewrestriction", "fields": { - "page": 10, + "page": 11, "password": "swordfish" } +}, + +{ + "pk": 1, + "model": "wagtaildocs.Document", + "fields": { + "title": "test document", + "created_at": "2014-01-01T12:00:00.000Z" + } +}, + +{ + "pk": 1, + "model": "wagtailimages.Image", + "fields": { + "title": "test image", + "created_at": "2014-01-01T12:00:00.000Z", + "width": 0, + "height": 0 + } } ] diff --git a/wagtail/tests/models.py b/wagtail/tests/models.py index 350b62809ca..79fad0a39df 100644 --- a/wagtail/tests/models.py +++ b/wagtail/tests/models.py @@ -309,3 +309,19 @@ class ZuluSnippet(models.Model): def __unicode__(self): return self.text + + +class StandardIndex(Page): + pass + +class StandardChild(Page): + pass + +class BusinessIndex(Page): + subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex'] + +class BusinessSubIndex(Page): + subpage_types = ['tests.BusinessChild'] + +class BusinessChild(Page): + subpage_types = [] diff --git a/wagtail/tests/templates/tests/event_page.html b/wagtail/tests/templates/tests/event_page.html index b60bc60588e..795b7bf0a7a 100644 --- a/wagtail/tests/templates/tests/event_page.html +++ b/wagtail/tests/templates/tests/event_page.html @@ -1,4 +1,5 @@ -{% load pageurl %} +{% load wagtailcore_tags %} + diff --git a/wagtail/tests/templates/tests/form_page.html b/wagtail/tests/templates/tests/form_page.html index 5fbd2ae9f24..d908c60a27c 100644 --- a/wagtail/tests/templates/tests/form_page.html +++ b/wagtail/tests/templates/tests/form_page.html @@ -1,4 +1,5 @@ -{% load pageurl %} +{% load wagtailcore_tags %} + diff --git a/wagtail/tests/templates/tests/form_page_landing.html b/wagtail/tests/templates/tests/form_page_landing.html index e29a6a942f2..d9a6369d479 100644 --- a/wagtail/tests/templates/tests/form_page_landing.html +++ b/wagtail/tests/templates/tests/form_page_landing.html @@ -1,4 +1,5 @@ -{% load pageurl %} +{% load wagtailcore_tags %} + diff --git a/wagtail/tests/templates/tests/includes/event_listing.html b/wagtail/tests/templates/tests/includes/event_listing.html index 31c0b1b3bab..02f880ec4ea 100644 --- a/wagtail/tests/templates/tests/includes/event_listing.html +++ b/wagtail/tests/templates/tests/includes/event_listing.html @@ -1,4 +1,4 @@ -{% load pageurl %} +{% load wagtailcore_tags %}