mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-17 03:31:11 +00:00
Merge branch 'master' into feature/crop-closeness
Conflicts: docs/core_components/pages/writing_templates.rst
This commit is contained in:
commit
4d954df20f
135 changed files with 3744 additions and 1117 deletions
|
|
@ -60,3 +60,9 @@ file_filter = wagtail/wagtailforms/locale/<lang>/LC_MESSAGES/django.po
|
|||
source_file = wagtail/wagtailforms/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[wagtail.wagtailsites]
|
||||
file_filter = wagtail/wagtailsites/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_file = wagtail/wagtailsites/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
|
|
|||
|
|
@ -4,6 +4,21 @@ Changelog
|
|||
0.7 (xx.xx.2014)
|
||||
~~~~~~~~~~~~~~~~
|
||||
* Added interface for choosing focal point on images
|
||||
* Redesigned and reorganised navigation menu to include a 'Settings' submenu
|
||||
* Added Groups administration area
|
||||
* Added Sites administration area
|
||||
* Removed 'content_type' template filter from the project template, as the same thing can be accomplished with self.get_verbose_name|slugify
|
||||
* Page copy operations now also copy the page revision history
|
||||
* Page models now support a 'parent_page_types' property in addition to 'subpage types', to restrict the types of page they can be created under
|
||||
* 'register_snippet' can now be invoked as a decorator
|
||||
* Project template updated to Django 1.7
|
||||
* 'boost' applied to the title field on searches reduced from 100 to 2
|
||||
* The 'type' method of PageQuerySet (used to filter the queryset to a specific page type) now includes subclasses of the given page type.
|
||||
* Fix: 'wagtail start' command now works on Windows
|
||||
* Fix: The external image URL generator no longer stores generated images in Django's cache
|
||||
* Fix: Elasticsearch backend can now search querysets that have been filtered with an 'in' clause of a non-list type (such as a ValuesListQuerySet)
|
||||
* Fix: It is now easier to move pages to the beginning and end of their section
|
||||
* Fix: Image rendering no longer creates erroneous duplicate Rendition records when the focal point is blank.
|
||||
|
||||
0.6 (11.09.2014)
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
Original Authors
|
||||
Authors
|
||||
================
|
||||
|
||||
* 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
|
||||
|
||||
Contributors
|
||||
============
|
||||
|
||||
* Helen Chapman helen.chapman@torchbox.com
|
||||
* Balazs Endresz balazs.endresz@torchbox.com
|
||||
* Neal Todd neal.todd@torchbox.com
|
||||
* Paul Hallett (twilio) hello@phalt.co
|
||||
|
|
@ -46,6 +46,7 @@ Translators
|
|||
* Greek: Serafeim Papastefanos
|
||||
* Mongolian: Delgermurun Purevkhuu
|
||||
* Polish: Łukasz Bołdys
|
||||
* Portuguese: Jose Lourenco
|
||||
* Portuguese Brazil: Gilson Filho
|
||||
* Romanian: Dan Braghis
|
||||
* Russian: ice9, HNKNTA
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ Find out more at `wagtail.io <http://wagtail.io/>`_.
|
|||
|
||||
Got a question? Ask it on our `Google Group <https://groups.google.com/forum/#!forum/wagtail>`_.
|
||||
|
||||
Who's using it?
|
||||
~~~~~~~~~~~~~~~
|
||||
We've a list of public Wagtail sites here: https://github.com/torchbox/wagtail/wiki/Public-Wagtail-sites
|
||||
|
||||
Got one of your own? Feel free to add it!
|
||||
|
||||
|
||||
Getting started
|
||||
~~~~~~~~~~~~~~~
|
||||
* To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo <https://github.com/torchbox/wagtaildemo/>`_; see the `README <https://github.com/torchbox/wagtaildemo/blob/master/README.md>`_ for installation instructions.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,188 @@
|
|||
Images
|
||||
======
|
||||
|
||||
|
||||
.. _image_tag:
|
||||
|
||||
Using images in templates
|
||||
=========================
|
||||
|
||||
.. 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::
|
||||
|
||||
{% image [image] [resize-rule] %}
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% load wagtailimages_tags %}
|
||||
...
|
||||
|
||||
{% image self.photo width-400 %}
|
||||
|
||||
<!-- or a square thumbnail: -->
|
||||
{% image self.photo fill-80x80 %}
|
||||
|
||||
In the above syntax example ``[image]`` is the Django object refering to the image. If your page model defined a field called "photo" then ``[image]`` would probably be ``self.photo``. The ``[resize-rule]`` defines how the image is to be resized when inserted into the page; various resizing methods are supported, to cater for different usage cases (e.g. lead images that span the whole width of the page, or thumbnails to be cropped to a fixed size).
|
||||
|
||||
Note that a space separates ``[image]`` and ``[resize-rule]``, but the resize rule must not contain spaces.
|
||||
|
||||
|
||||
The available resizing methods are:
|
||||
|
||||
|
||||
.. glossary::
|
||||
|
||||
``max``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo max-1000x500 %}
|
||||
|
||||
Fit **within** the given dimensions.
|
||||
|
||||
The longest edge will be reduced to the equivalent dimension size defined. e.g A portrait image of width 1000, height 2000, treated with the ``max`` dimensions ``1000x500`` (landscape) would result in the image shrunk so the *height* was 500 pixels and the width 250.
|
||||
|
||||
``min``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo min-500x200 %}
|
||||
|
||||
**Cover** the given dimensions.
|
||||
|
||||
This may result in an image slightly **larger** than the dimensions you specify. e.g A square image of width 2000, height 2000, treated with the ``min`` dimensions ``500x200`` (landscape) would have it's height and width changed to 500, i.e matching the width required, but greater than the height.
|
||||
|
||||
``width``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-640 %}
|
||||
|
||||
Reduces the width of the image to the dimension specified.
|
||||
|
||||
``height``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo height-480 %}
|
||||
|
||||
Resize the height of the image to the dimension specified..
|
||||
|
||||
``fill``
|
||||
(takes two dimensions and an optional ``-c`` parameter)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo fill-200x200 %}
|
||||
|
||||
Resize and **crop** to fill the **exact** dimensions.
|
||||
|
||||
This can be particularly useful for websites requiring square thumbnails of arbitrary images. For example, a landscape image of width 2000, height 1000, treated with ``fill`` dimensions ``200x200`` would have its height reduced to 200, then its width (ordinarily 400) cropped to 200.
|
||||
|
||||
This filter will crop to the images focal point if it has been set. If not, it will crop to the centre of the image.
|
||||
|
||||
**Cropping closer to the focal point**
|
||||
|
||||
By default, Wagtail will only crop to change the aspect ratio of the image.
|
||||
|
||||
In some cases (thumbnails, for example) it may be nice to crop closer to the focal point so the subject of the image is easier to see.
|
||||
|
||||
You can do this by appending ``-c<percentage>`` at the end of the method. For example, if you would like the image to be cropped as closely as possible to its focal point, add ``-c100`` to the end of the method.
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo fill-200x200-c100 %}
|
||||
|
||||
This will crop the image as much as it an but will never crop into the focal point.
|
||||
|
||||
If you find that ``-c100`` is too close, you can try ``-c75`` or ``-c50`` (any whole number from 0 to 100 is accepted).
|
||||
|
||||
``original``
|
||||
(takes no dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo original %}
|
||||
|
||||
Leaves the image at its original size - no resizing is performed.
|
||||
|
||||
|
||||
|
||||
.. Note::
|
||||
Wagtail does not allow deforming or stretching images. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions.
|
||||
|
||||
|
||||
.. _image_tag_alt:
|
||||
|
||||
More control over the ``img`` tag
|
||||
---------------------------------
|
||||
|
||||
Wagtail provides two shortcuts to give greater control over the ``img`` element:
|
||||
|
||||
**1. Adding attributes to the {% image %} tag**
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Extra attributes can be specified with the syntax ``attribute="value"``:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-400 class="foo" id="bar" %}
|
||||
|
||||
No validation is performed on attributes added in this way so it's possible to add `src`, `width`, `height` and `alt` of your own that might conflict with those generated by the tag itself.
|
||||
|
||||
|
||||
**2. Generating the image "as foo" to access individual properties**
|
||||
|
||||
Wagtail can assign the image data to another variable using Django's ``as`` syntax:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-400 as tmp_photo %}
|
||||
|
||||
<img src="{{ tmp_photo.url }}" width="{{ tmp_photo.width }}"
|
||||
height="{{ tmp_photo.height }}" alt="{{ tmp_photo.alt }}" class="my-custom-class" />
|
||||
|
||||
|
||||
This syntax exposes the underlying image "Rendition" (``tmp_photo``) to the developer. A "Rendition" contains just the information specific to the way you've requested to format the image i.e dimensions and source URL.
|
||||
|
||||
If your site defines a custom image model using ``AbstractImage``, then any additional fields you add to an image e.g a copyright holder, are **not** part of the image *rendition*, they're part of the image *model*.
|
||||
|
||||
Therefore in the above example, if you'd added the field ``foo`` to your AbstractImage you'd access it using ``{{ self.photo.foo }}`` not ``{{ tmp_photo.foo }}``.
|
||||
|
||||
(Due to the links in the database between renditions and their parent image, you could also access it as ``{{ tmp_photo.image.foo }}`` but this is clearly confusing.)
|
||||
|
||||
|
||||
.. Note::
|
||||
The image property used for the ``src`` attribute is actually ``image.url``, not ``image.src``.
|
||||
|
||||
|
||||
The ``attrs`` shortcut
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
You can also use the ``attrs`` property as a shorthand to output the attributes ``src``, ``width``, ``height`` and ``alt`` in one go:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<img {{ tmp_photo.attrs }} class="my-custom-class" />
|
||||
|
||||
|
||||
|
||||
Advanced topics
|
||||
===============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ Reference
|
|||
|
||||
.. automethod:: search
|
||||
|
||||
See: :ref:`wagtailsearch_for_python_developers`
|
||||
See: :ref:`wagtailsearch_searching_pages`
|
||||
|
||||
Example:
|
||||
|
||||
|
|
@ -178,3 +178,12 @@ Reference
|
|||
|
||||
# Search future events
|
||||
results = EventPage.objects.live().filter(date__gt=timezone.now()).search("Hello")
|
||||
|
||||
.. automethod:: type
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Find all pages that are of type AbstractEmailForm, or a descendant of it
|
||||
form_pages = Page.objects.type(AbstractEmailForm)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ In addition to the model fields provided, ``Page`` has many properties and metho
|
|||
|
||||
.. attribute:: search_fields
|
||||
|
||||
A list of fields to be indexed by the search engine. See Search docs :ref:`wagtailsearch_for_python_developers`
|
||||
A list of fields to be indexed by the search engine. See Search docs :ref:`wagtailsearch_indexing_fields`
|
||||
|
||||
.. attribute:: subpage_types
|
||||
|
||||
|
|
|
|||
|
|
@ -556,6 +556,13 @@ The available hooks are:
|
|||
def register_frank_menu_item():
|
||||
return MenuItem('Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
|
||||
|
||||
.. _register_settings_menu_item:
|
||||
|
||||
``register_settings_menu_item``
|
||||
.. versionadded:: 0.7
|
||||
|
||||
As ``register_admin_menu_item``, but registers menu items into the 'Settings' sub-menu rather than the top-level menu.
|
||||
|
||||
.. _construct_main_menu:
|
||||
|
||||
``construct_main_menu``
|
||||
|
|
@ -641,6 +648,13 @@ The available hooks are:
|
|||
'a': attribute_rule({'href': check_url, 'target': True}),
|
||||
}
|
||||
|
||||
.. _register_permissions:
|
||||
|
||||
``register_permissions``
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Return a queryset of Permission objects to be shown in the Groups administration area.
|
||||
|
||||
|
||||
Image Formats in the Rich Text Editor
|
||||
-------------------------------------
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ A Parent node could provide its own function returning its descendant objects.
|
|||
|
||||
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()``) and are children of this node (``descendant_of(self)``). By setting a ``subpage_types`` class property in your model, you can specify which models are allowed to be set as children, but Wagtail will allow any ``Page``-derived model by default. Regardless, it's smart for a parent model to provide an index filtered to make sense.
|
||||
This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail's admin interface (``live()``) and are children of this node (``descendant_of(self)``). By setting a ``subpage_types`` class property in your model, you can specify which models are allowed to be set as children, and by setting a ``parent_page_types`` class property, you can specify which models are allowed to be parents of this page model. Wagtail will allow any ``Page``-derived model by default. Regardless, it's smart for a parent model to provide an index filtered to make sense.
|
||||
|
||||
|
||||
Leaves
|
||||
|
|
@ -71,7 +71,7 @@ The model for the leaf could provide a function that traverses the tree in the o
|
|||
# Find closest ancestor which is an event index
|
||||
return self.get_ancestors().type(EventIndexPage).last()
|
||||
|
||||
If defined, ``subpage_types`` will also limit the parent models allowed to contain a leaf. If not, Wagtail will allow any combination of parents and leafs to be associated in the Wagtail tree. Like with index pages, it's a good idea to make sure that the index is actually of the expected model to contain the leaf.
|
||||
If defined, ``subpage_types`` and ``parent_page_types`` will also limit the parent models allowed to contain a leaf. If not, Wagtail will allow any combination of parents and leafs to be associated in the Wagtail tree. Like with index pages, it's a good idea to make sure that the index is actually of the expected model to contain the leaf.
|
||||
|
||||
|
||||
Other Relationships
|
||||
|
|
|
|||
|
|
@ -86,13 +86,9 @@ Template tags & filters
|
|||
In addition to Django's standard tags and filters, Wagtail provides some of its own, which can be ``load``-ed `as you would any other <https://docs.djangoproject.com/en/dev/topics/templates/#custom-tag-and-filter-libraries>`_
|
||||
|
||||
|
||||
.. _image_tag:
|
||||
|
||||
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`.
|
||||
|
||||
|
|
@ -112,156 +108,8 @@ For example:
|
|||
<!-- or a square thumbnail: -->
|
||||
{% image self.photo fill-80x80 %}
|
||||
|
||||
In the above syntax example ``[image]`` is the Django object refering to the image. If your page model defined a field called "photo" then ``[image]`` would probably be ``self.photo``. The ``[resize-rule]`` defines how the image is to be resized when inserted into the page; various resizing methods are supported, to cater for different usage cases (e.g. lead images that span the whole width of the page, or thumbnails to be cropped to a fixed size).
|
||||
|
||||
Note that a space separates ``[image]`` and ``[resize-rule]``, but the resize rule must not contain spaces.
|
||||
|
||||
|
||||
The available resizing methods are:
|
||||
|
||||
|
||||
.. glossary::
|
||||
|
||||
``max``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo max-1000x500 %}
|
||||
|
||||
Fit **within** the given dimensions.
|
||||
|
||||
The longest edge will be reduced to the equivalent dimension size defined. e.g A portrait image of width 1000, height 2000, treated with the ``max`` dimensions ``1000x500`` (landscape) would result in the image shrunk so the *height* was 500 pixels and the width 250.
|
||||
|
||||
``min``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo min-500x200 %}
|
||||
|
||||
**Cover** the given dimensions.
|
||||
|
||||
This may result in an image slightly **larger** than the dimensions you specify. e.g A square image of width 2000, height 2000, treated with the ``min`` dimensions ``500x200`` (landscape) would have it's height and width changed to 500, i.e matching the width required, but greater than the height.
|
||||
|
||||
``width``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-640 %}
|
||||
|
||||
Reduces the width of the image to the dimension specified.
|
||||
|
||||
``height``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo height-480 %}
|
||||
|
||||
Resize the height of the image to the dimension specified..
|
||||
|
||||
``fill``
|
||||
(takes two dimensions and an optional ``-c`` parameter)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo fill-200x200 %}
|
||||
|
||||
Resize and **crop** to fill the **exact** dimensions.
|
||||
|
||||
This can be particularly useful for websites requiring square thumbnails of arbitrary images. For example, a landscape image of width 2000, height 1000, treated with ``fill`` dimensions ``200x200`` would have its height reduced to 200, then its width (ordinarily 400) cropped to 200.
|
||||
|
||||
This filter will crop to the images focal point if it has been set. If not, it will crop to the centre of the image.
|
||||
|
||||
**Cropping closer to the focal point**
|
||||
|
||||
By default, Wagtail will only crop to change the aspect ratio of the image.
|
||||
|
||||
In some cases (thumbnails, for example) it may be nice to crop closer to the focal point so the subject of the image is easier to see.
|
||||
|
||||
You can do this by appending ``-c<percentage>`` at the end of the method. For example, if you would like the image to be cropped as closely as possible to its focal point, add ``-c100`` to the end of the method.
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo fill-200x200-c100 %}
|
||||
|
||||
This will crop the image as much as it an but will never crop into the focal point.
|
||||
|
||||
If you find that ``-c100`` is too close, you can try ``-c75`` or ``-c50`` (any whole number from 0 to 100 is accepted).
|
||||
|
||||
|
||||
``original``
|
||||
(takes no dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo original %}
|
||||
|
||||
Leaves the image at its original size - no resizing is performed.
|
||||
|
||||
|
||||
|
||||
.. Note::
|
||||
Wagtail does not allow deforming or stretching images. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions.
|
||||
|
||||
|
||||
.. _image_tag_alt:
|
||||
|
||||
More control over the ``img`` tag
|
||||
---------------------------------
|
||||
|
||||
Wagtail provides two shorcuts to give greater control over the ``img`` element:
|
||||
|
||||
**1. Adding attributes to the {% image %} tag**
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Extra attributes can be specified with the syntax ``attribute="value"``:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-400 class="foo" id="bar" %}
|
||||
|
||||
No validation is performed on attributes added in this way so it's possible to add `src`, `width`, `height` and `alt` of your own that might conflict with those generated by the tag itself.
|
||||
|
||||
|
||||
**2. Generating the image "as foo" to access individual properties**
|
||||
|
||||
Wagtail can assign the image data to another variable using Django's ``as`` syntax:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-400 as tmp_photo %}
|
||||
|
||||
<img src="{{ tmp_photo.url }}" width="{{ tmp_photo.width }}"
|
||||
height="{{ tmp_photo.height }}" alt="{{ tmp_photo.alt }}" class="my-custom-class" />
|
||||
|
||||
|
||||
This syntax exposes the underlying image "Rendition" (``tmp_photo``) to the developer. A "Rendition" contains just the information specific to the way you've requested to format the image i.e dimensions and source URL.
|
||||
|
||||
If your site defines a custom image model using ``AbstractImage``, then any additional fields you add to an image e.g a copyright holder, are **not** part of the image *rendition*, they're part of the image *model*.
|
||||
|
||||
Therefore in the above example, if you'd added the field ``foo`` to your AbstractImage you'd access it using ``{{ self.photo.foo }}`` not ``{{ tmp_photo.foo }}``.
|
||||
|
||||
(Due to the links in the database between renditions and their parent image, you could also access it as ``{{ tmp_photo.image.foo }}`` but this is clearly confusing.)
|
||||
|
||||
|
||||
.. Note::
|
||||
The image property used for the ``src`` attribute is actually ``image.url``, not ``image.src``.
|
||||
|
||||
|
||||
The ``attrs`` shortcut
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
You can also use the ``attrs`` property as a shorthand to output the attributes ``src``, ``width``, ``height`` and ``alt`` in one go:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<img {{ tmp_photo.attrs }} class="my-custom-class" />
|
||||
See :ref:`image_tag` for full documentation.
|
||||
|
||||
|
||||
.. _rich-text-filter:
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
.. _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 in addition to results from 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 %}
|
||||
<div class="well">
|
||||
<h3>Editors picks</h3>
|
||||
<ul>
|
||||
{% for editors_pick in editors_picks %}
|
||||
<li>
|
||||
<h4>
|
||||
<a href="{% pageurl editors_pick.page %}">
|
||||
{{ editors_pick.page.title }}
|
||||
</a>
|
||||
</h4>
|
||||
<p>{{ editors_pick.description|safe }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
|
@ -1,17 +1,43 @@
|
|||
|
||||
.. _wagtailsearch:
|
||||
|
||||
|
||||
======
|
||||
Search
|
||||
======
|
||||
|
||||
Wagtail provides a comprehensive and extensible search interface. In addition, it provides ways to promote search results through "Editor's Picks." Wagtail also collects simple statistics on queries made through the search interface.
|
||||
|
||||
Wagtail provides a comprehensive and extensible search interface. In addition, it provides ways to promote search results through "Editor's Picks". Wagtail also collects simple statistics on queries made through the search interface.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
for_python_developers
|
||||
frontend_views
|
||||
editors_picks
|
||||
indexing
|
||||
searching
|
||||
backends
|
||||
|
||||
|
||||
Indexing
|
||||
========
|
||||
|
||||
To make objects searchable, they firstly need to be added to the search index. This involves configuring the models/fields that you would like to index (this is done for you for Pages, Images and Documents) and then actually inserting them into the index.
|
||||
|
||||
See :ref:`wagtailsearch_indexing_update` for information on how to keep the objects in your search index in sync with the objects in your database.
|
||||
|
||||
If you have created some extra fields in a subclass of ``Page`` or ``Image``, you may want to add these new fields to the search index too so a users search query will match on their content. See :ref:`wagtailsearch_indexing_fields`.
|
||||
|
||||
If you have a custom model which doesn't derive from ``Page`` or ``Image`` that you would like to make searchable, see :ref:`wagtailsearch_indexing_models`.
|
||||
|
||||
|
||||
Searching
|
||||
=========
|
||||
|
||||
Wagtail provides an API for performing search queries on your models. You can also perform search queries on Django QuerySets.
|
||||
|
||||
See :ref:`wagtailsearch_searching`.
|
||||
|
||||
|
||||
Backends
|
||||
========
|
||||
|
||||
Wagtail provides two backends for storing the search index and performing search queries: Elasticsearch and the database. It's also possible to roll your own search backend.
|
||||
|
||||
See :ref:`wagtailsearch_backends`
|
||||
|
|
|
|||
|
|
@ -1,34 +1,63 @@
|
|||
|
||||
.. _wagtailsearch_for_python_developers:
|
||||
.. _wagtailsearch_indexing:
|
||||
|
||||
|
||||
=====================
|
||||
For Python developers
|
||||
=====================
|
||||
========
|
||||
Indexing
|
||||
========
|
||||
|
||||
To make a model searchable, you'll firstly need to add it into the search index. All pages, images and documents are indexed for you and you can start searching them right away.
|
||||
|
||||
If you have created some extra fields in a subclass of Page or Image, you may want to add these new fields to the search index too so that a user's search query will match on their content. See :ref:`wagtailsearch_indexing_fields` for info on how to do this.
|
||||
|
||||
If you have a custom model that you would like to make searchable, see :ref:`wagtailsearch_indexing_models`.
|
||||
|
||||
|
||||
Basic usage
|
||||
===========
|
||||
|
||||
By default using the :ref:`wagtailsearch_backends_database`, Wagtail's search will only index the ``title`` field of pages.
|
||||
|
||||
All searches are performed on Django QuerySets. Wagtail provides a ``search`` method on the queryset for all page models:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Search future EventPages
|
||||
>>> from wagtail.wagtailcore.models import EventPage
|
||||
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
|
||||
.. _wagtailsearch_indexing_update:
|
||||
|
||||
|
||||
All methods of ``PageQuerySet`` are supported by wagtailsearch:
|
||||
Updating the index
|
||||
==================
|
||||
|
||||
.. code-block:: python
|
||||
If the search index is kept separate from the database (when using Elasticsearch for example), you need to keep them both in sync. There are two ways to do this: using the search signal handlers, or calling the ``update_index`` command periodically. For best speed and reliability, it's best to use both if possible.
|
||||
|
||||
# Search all live EventPages that are under the events index
|
||||
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
|
||||
[<EventPage: Event 1>, <EventPage: Event 2>]
|
||||
|
||||
Signal handlers
|
||||
---------------
|
||||
|
||||
Wagtailsearch provides some signal handlers which bind to the save/delete signals of all indexed models. This would automatically add and delete them from all backends you have registered in ``WAGTAILSEARCH_BACKENDS``.
|
||||
|
||||
To register the signal handlers, add the following code somewhere it would be executed at startup. We reccommend adding this to your projects ``urls.py``:
|
||||
|
||||
.. code-block: python
|
||||
|
||||
# urls.py
|
||||
from wagtail.wagtailsearch.signal_handlers import register_signal_handlers
|
||||
|
||||
register_signal_handlers()
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
If your project was made with the ``wagtail start`` command, this will already be set up for you.
|
||||
|
||||
|
||||
The ``update_index`` command
|
||||
----------------------------
|
||||
|
||||
Wagtail also provides a command for rebuilding the index from scratch.
|
||||
|
||||
:code:`./manage.py update_index`
|
||||
|
||||
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.
|
||||
|
||||
|
||||
.. _wagtailsearch_indexing_fields:
|
||||
|
||||
Indexing extra fields
|
||||
=====================
|
||||
|
|
@ -51,7 +80,7 @@ Fields must be explicitly added to the ``search_fields`` property of your ``Page
|
|||
|
||||
|
||||
Example
|
||||
-------------
|
||||
-------
|
||||
|
||||
This creates an ``EventPage`` model with two fields ``description`` and ``date``. ``description`` is indexed as a ``SearchField`` and ``date`` is indexed as a ``FilterField``
|
||||
|
||||
|
|
@ -75,7 +104,7 @@ This creates an ``EventPage`` model with two fields ``description`` and ``date``
|
|||
|
||||
|
||||
``index.SearchField``
|
||||
-----------------------
|
||||
---------------------
|
||||
|
||||
These are added to the search index and are used for performing full-text searches on your models. These would usually be text fields.
|
||||
|
||||
|
|
@ -84,12 +113,12 @@ Options
|
|||
```````
|
||||
|
||||
- **partial_match** (boolean) - Setting this to true allows results to be matched on parts of words. For example, this is set on the title field by default so a page titled "Hello World!" will be found if the user only types "Hel" into the search box.
|
||||
- **boost** (number) - This allows you to set fields as being more important than others. Setting this to a high number on a field will make pages with matches in that field to be ranked higher. By default, this is set to 100 on the title field and 1 on all other fields.
|
||||
- **boost** (number) - This allows you to set fields as being more important than others. Setting this to a high number on a field will make pages with matches in that field to be ranked higher. By default, this is set to 2 on the Page title field and 1 on all other fields.
|
||||
- **es_extra** (dict) - This field is to allow the developer to set or override any setting on the field in the ElasticSearch mapping. Use this if you want to make use of any ElasticSearch features that are not yet supported in Wagtail.
|
||||
|
||||
|
||||
``index.FilterField``
|
||||
-----------------------
|
||||
---------------------
|
||||
|
||||
These are added to the search index but are not used for full-text searches. Instead, they allow you to run filters on your search results.
|
||||
|
||||
|
|
@ -128,6 +157,8 @@ One use for this is indexing ``get_*_display`` methods Django creates automatica
|
|||
)
|
||||
|
||||
|
||||
.. _wagtailsearch_indexing_models:
|
||||
|
||||
Indexing non-page models
|
||||
========================
|
||||
|
||||
|
|
@ -1,4 +1,67 @@
|
|||
|
||||
.. _wagtailsearch_searching:
|
||||
|
||||
|
||||
=========
|
||||
Searching
|
||||
=========
|
||||
|
||||
|
||||
.. _wagtailsearch_searching_pages:
|
||||
|
||||
Searching Pages
|
||||
===============
|
||||
|
||||
Wagtail provides a ``search`` method on the QuerySet for all page models:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Search future EventPages
|
||||
>>> from wagtail.wagtailcore.models import EventPage
|
||||
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
|
||||
|
||||
|
||||
All methods of ``PageQuerySet`` are supported by wagtailsearch:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Search all live EventPages that are under the events index
|
||||
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
|
||||
[<EventPage: Event 1>, <EventPage: Event 2>]
|
||||
|
||||
|
||||
Searching Images, Documents and custom models
|
||||
=============================================
|
||||
|
||||
You can search these by using the ``search`` method on the search backend:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from wagtail.wagtailimages.models import Image
|
||||
>>> from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
||||
# Search images
|
||||
>>> s = get_search_backend()
|
||||
>>> s.search("Hello", Image)
|
||||
[<Image: Hello>, <Image: Hello world!>]
|
||||
|
||||
|
||||
You can also pass a QuerySet into the ``search`` method which allows you to add filters to your search results:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from wagtail.wagtailimages.models import Image
|
||||
>>> from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
||||
# Search images
|
||||
>>> s = get_search_backend()
|
||||
>>> s.search("Hello", Image.objects.filter(uploaded_by_user=user))
|
||||
[<Image: Hello>]
|
||||
|
||||
|
||||
This should work the same way for Documents and custom models as well.
|
||||
|
||||
|
||||
.. _wagtailsearch_frontend_views:
|
||||
|
||||
|
||||
|
|
@ -166,4 +229,47 @@ In this template, you'll have access to the same context variables provided to t
|
|||
Custom Search Views
|
||||
-------------------
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
|
||||
.. _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 in addition to results from 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 %}
|
||||
<div class="well">
|
||||
<h3>Editors picks</h3>
|
||||
<ul>
|
||||
{% for editors_pick in editors_picks %}
|
||||
<li>
|
||||
<h4>
|
||||
<a href="{% pageurl editors_pick.page %}">
|
||||
{{ editors_pick.page.title }}
|
||||
</a>
|
||||
</h4>
|
||||
<p>{{ editors_pick.description|safe }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
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 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 the ``register_snippet`` class decorator.
|
||||
|
||||
Snippets are not search-able or order-able in the Wagtail admin, so decide carefully if the content type you would want to build into a snippet might be more suited to a page.
|
||||
|
||||
|
|
@ -19,9 +19,10 @@ Here's an example snippet from the Wagtail demo website:
|
|||
|
||||
from wagtail.wagtailadmin.edit_handlers import FieldPanel
|
||||
from wagtail.wagtailsnippets.models import register_snippet
|
||||
|
||||
|
||||
...
|
||||
|
||||
@register_snippet
|
||||
class Advert(models.Model):
|
||||
url = models.URLField(null=True, blank=True)
|
||||
text = models.CharField(max_length=255)
|
||||
|
|
@ -34,11 +35,9 @@ Here's an example snippet from the Wagtail demo website:
|
|||
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.
|
||||
``@register_snippet`` 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
|
||||
-----------------------------------
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _editor_manual:
|
||||
|
||||
Using Wagtail: an Editor's guide
|
||||
================================
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ A basic Wagtail setup can be installed on your machine with only a few prerequis
|
|||
Whether you just want to try out the demo site, or you're ready to dive in and create a Wagtail site with all bells and whistles enabled, we strongly recommend the Vagrant approach. Nevertheless, if you're the sort of person who balks at the idea of downloading a whole operating system just to run a web app, we've got you covered too. Start from `A basic Wagtail installation`_ below.
|
||||
|
||||
|
||||
The no-installation route
|
||||
=========================
|
||||
The demo site (a.k.a. the no-installation route)
|
||||
================================================
|
||||
|
||||
We provide a demo site containing a set of standard templates and page types - if you're new to Wagtail, this is the best way to try it out and familiarise yourself with how Wagtail works from the point of view of an editor.
|
||||
|
||||
If you're happy to use Vagrant, and you just want to set up the Wagtail demo site, or any other pre-existing Wagtail site that ships with Vagrant support, you don't need to install Wagtail at all. Install `Vagrant <http://www.vagrantup.com/>`__ and `VirtualBox <https://www.virtualbox.org/>`__, and run::
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ Then, within the SSH session::
|
|||
./manage.py runserver 0.0.0.0:8000
|
||||
|
||||
|
||||
This will make the demo site available on your host machine at the URL http://localhost:8111/ - you can access the Wagtail admin interface at http://localhost:8111/admin/ .
|
||||
This will make the demo site available on your host machine at the URL http://localhost:8111/ - you can access the Wagtail admin interface at http://localhost:8111/admin/ . Further instructions can be found at :ref:`editor_manual`.
|
||||
|
||||
Once you’ve experimented with the demo site and are ready to build your own site, it's time to install Wagtail on your host machine. Even if you intend to do all further Wagtail work within Vagrant, installing the Wagtail package on your host machine will provide the ``wagtail start`` command that sets up the initial file structure for your project.
|
||||
|
||||
|
|
@ -35,6 +37,8 @@ Once you’ve experimented with the demo site and are ready to build your own si
|
|||
A basic Wagtail installation
|
||||
============================
|
||||
|
||||
This provides everything you need to create a new Wagtail project from scratch, containing no page definitions or templates other than a basic homepage as a starting point for building your site. (For a gentler introduction to Wagtail, you may wish to try out the demo site first!)
|
||||
|
||||
You will need Python's `pip <http://pip.readthedocs.org/en/latest/installing.html>`__ package manager. We also recommend `virtualenvwrapper <http://virtualenvwrapper.readthedocs.org/en/latest/>`_ so that you can manage multiple independent Python environments for different projects - although this is not strictly necessary if you intend to do all your development under Vagrant.
|
||||
|
||||
Wagtail is based on the Django web framework and various other Python libraries. Most of these are pure Python and will install automatically using ``pip``, but there are a few native-code components that require further attention:
|
||||
|
|
@ -59,9 +63,9 @@ You will now be able to run the following command to set up an initial file stru
|
|||
**Without Vagrant:** Run the following steps to complete setup of your project (the ``migrate`` step will prompt you to set up a superuser account)::
|
||||
|
||||
cd myprojectname
|
||||
./manage.py syncdb
|
||||
./manage.py migrate
|
||||
./manage.py runserver
|
||||
pip install -r requirements.txt
|
||||
python manage.py migrate
|
||||
python manage.py runserver
|
||||
|
||||
Your site is now accessible at http://localhost:8000, with the admin backend available at http://localhost:8000/admin/ .
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ The old names will continue to work, but output a ``DeprecationWarning`` - you a
|
|||
New search field configuration format
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``indexed_fields`` is now deprecated and has been replaced by a new search field configuration format called ``search_fields``. See :ref:`wagtailsearch_for_python_developers` for how to define a ``search_fields`` property on your models.
|
||||
``indexed_fields`` is now deprecated and has been replaced by a new search field configuration format called ``search_fields``. See :ref:`wagtailsearch_indexing` for how to define a ``search_fields`` property on your models.
|
||||
|
||||
|
||||
``Page.route`` method should now return a ``RouteResult``
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ All features deprecated in 0.4 have been removed
|
|||
See: :ref:`04_deprecated_features`
|
||||
|
||||
|
||||
Search signal handlers have been moved
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have an import in your ``urls.py`` file like ``from wagtail.wagtailsearch import register_signal_handlers``, this must now be changed to ``from wagtail.wagtailsearch.signal_handlers import register_signal_handlers``
|
||||
|
||||
|
||||
Deprecated features
|
||||
===================
|
||||
|
||||
|
|
|
|||
|
|
@ -10,21 +10,57 @@ Wagtail 0.7 release notes - IN DEVELOPMENT
|
|||
What's new
|
||||
==========
|
||||
|
||||
New interface for choosing images focal point
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
New interface for choosing image focal point
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When editing images, users can now specify a 'focal point' region that cropped versions of the image will be centred on. Previously the focal point could only be set automatically, through image feature detection.
|
||||
|
||||
|
||||
Groups and Sites administration interfaces
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The main navigation menu has been reorganised, placing site configuration options in a 'Settings' submenu. This includes two new items, which were previously only available through the Django admin backend: 'Groups', for setting up user groups with a specific set of permissions, and 'Sites', for managing the list of sites served by this Wagtail instance.
|
||||
|
||||
|
||||
Minor features
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* The ``content_type`` template filter has been removed from the project template, as the same thing can be accomplished with ``self.get_verbose_name|slugify``.
|
||||
* Page copy operations now also copy the page revision history.
|
||||
* Page models now support a ``parent_page_types`` property in addition to ``subpage types``, to restrict the types of page they can be created under.
|
||||
* ``register_snippet`` can now be invoked as a decorator.
|
||||
* The project template (used when running ``wagtail start``) has been updated to Django 1.7.
|
||||
* The 'boost' applied to the title field on searches has been reduced from 100 to 2.
|
||||
* The ``type`` method of ``PageQuerySet`` (used to filter the queryset to a specific page type) now includes subclasses of the given page type.
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
||||
* The 'wagtail start' command now works on Windows and other environments where the ``django-admin.py`` executable is not readily accessible.
|
||||
* The external image URL generator no longer stores generated images in Django's cache; this was an unintentional side-effect of setting cache control headers.
|
||||
* The Elasticsearch backend can now search querysets that have been filtered with an 'in' clause of a non-list type (such as a ``ValuesListQuerySet``).
|
||||
* It is now easier to move pages to the beginning and end of their section
|
||||
* Image rendering no longer creates erroneous duplicate Rendition records when the focal point is blank.
|
||||
|
||||
|
||||
Upgrade considerations
|
||||
======================
|
||||
|
||||
Addition of ``wagtailsites`` app
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Sites administration interface is contained within a new app, ``wagtailsites``. To enable this on an existing Wagtail project, add the line::
|
||||
|
||||
'wagtail.wagtailsites',
|
||||
|
||||
to the ``INSTALLED_APPS`` list in your project's settings file.
|
||||
|
||||
|
||||
Title boost on search reduced to 2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Wagtail's search interface applies a 'boost' value to give extra weighting to matches on the title field. The original boost value of 100 was found to be excessive, and in Wagtail 0.7 this has been reduced to 2. If you have used comparable boost values on other fields, to give them similar weighting to title, you may now wish to reduce these accordingly. See :ref:`wagtailsearch_indexing`.
|
||||
|
||||
|
||||
Deprecated features
|
||||
===================
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Release notes
|
|||
:maxdepth: 1
|
||||
|
||||
roadmap
|
||||
0.7
|
||||
0.6
|
||||
0.5
|
||||
0.4.1
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ coverage==3.7.1
|
|||
flake8==2.2.1
|
||||
mock==1.0.1
|
||||
python-dateutil==2.2
|
||||
pytz==2014.7
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -33,7 +33,7 @@ install_requires = [
|
|||
"django-compressor>=1.4",
|
||||
"django-libsass>=0.2",
|
||||
"django-modelcluster>=0.4",
|
||||
"django-taggit==0.12.1",
|
||||
"django-taggit==0.12.2",
|
||||
"django-treebeard==2.0",
|
||||
"Pillow>=2.3.0",
|
||||
"beautifulsoup4>=4.3.2",
|
||||
|
|
@ -62,7 +62,7 @@ setup(
|
|||
license='BSD',
|
||||
long_description=open('README.rst').read(),
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
|
|
|
|||
1
tox.ini
1
tox.ini
|
|
@ -15,6 +15,7 @@ base =
|
|||
elasticsearch==1.1.0
|
||||
mock==1.0.1
|
||||
python-dateutil==2.2
|
||||
pytz==2014.7
|
||||
Embedly
|
||||
coverage
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import errno
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
from django.core.management import ManagementUtility
|
||||
|
||||
|
||||
def create_project(parser, options, args):
|
||||
|
|
@ -44,15 +44,15 @@ def create_project(parser, options, args):
|
|||
template_path = os.path.join(wagtail_path, 'project_template')
|
||||
|
||||
# Call django-admin startproject
|
||||
result = subprocess.call([
|
||||
utility = ManagementUtility([
|
||||
'django-admin.py', 'startproject',
|
||||
'--template=' + template_path,
|
||||
'--name=Vagrantfile', '--ext=html,rst',
|
||||
project_name
|
||||
])
|
||||
utility.execute()
|
||||
|
||||
if result == 0:
|
||||
print("Success! %(project_name)s is created" % {'project_name': project_name})
|
||||
print("Success! %(project_name)s is created" % {'project_name': project_name})
|
||||
|
||||
|
||||
COMMANDS = {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
<li><a href="#progress">Progress indicators</a></li>
|
||||
<li><a href="#misc">Misc formatters</a></li>
|
||||
<li><a href="#icons">Icons</a></li>
|
||||
<li><a href="#ie9">IE9 debugging</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
|
@ -514,6 +515,15 @@
|
|||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="ie9">
|
||||
<h2>IE9 debugging</h2>
|
||||
|
||||
<p>Internet Explorer 9 has two critical limitations in its CSS support: a maximum of 31 stylesheets per page and a maximum of 4096 selectors per stylesheet. The latter is particularly problematic when CSS is concatenated.</p>
|
||||
|
||||
<div id="ie9-debug"></div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -521,6 +531,30 @@
|
|||
{% block extra_js %}
|
||||
<script>
|
||||
$(function(){
|
||||
// Debugging for stylesheet problems
|
||||
var styleSheets = document.styleSheets, totalStyleSheets = styleSheets.length;
|
||||
for (var j = 0; j < totalStyleSheets; j++) {
|
||||
var styleSheet = styleSheets[j], rules = styleSheet.cssRules, totalSelectorsInStylesheet = 0, style = "";
|
||||
|
||||
var totalRulesInStylesheet = rules ? rules.length : 0;
|
||||
|
||||
for (var i = 0; i < totalRulesInStylesheet; i++) {
|
||||
if (rules[i].selectorText) {
|
||||
try {
|
||||
totalSelectorsInStylesheet += rules[i].selectorText.split(',').length;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(totalSelectorsInStylesheet > 4095){
|
||||
style = 'color:red';
|
||||
}
|
||||
$('#ie9-debug').append("<h3>" + styleSheet.href + "</h3>" + "<p>Total rules: <strong>" + totalRulesInStylesheet + "</strong>. " + "Total selectors: <strong style='" + style + "'>" + totalSelectorsInStylesheet + "</strong></p>");
|
||||
}
|
||||
|
||||
(function runprogress(){
|
||||
var to = setTimeout(function(){
|
||||
runprogress();
|
||||
|
|
|
|||
|
|
@ -1,87 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'HomePage'
|
||||
db.create_table('core_homepage', (
|
||||
('page_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wagtailcore.Page'], unique=True, primary_key=True)),
|
||||
))
|
||||
db.send_create_signal('core', ['HomePage'])
|
||||
dependencies = [
|
||||
('wagtailcore', '0002_initial_data'),
|
||||
]
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'HomePage'
|
||||
db.delete_table('core_homepage')
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.homepage': {
|
||||
'Meta': {'object_name': 'HomePage', '_ormbases': ['wagtailcore.Page']},
|
||||
'page_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wagtailcore.Page']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'wagtailcore.page': {
|
||||
'Meta': {'object_name': 'Page'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': "orm['auth.User']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['core']
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HomePage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,114 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models, connection
|
||||
from django.db.transaction import set_autocommit
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(DataMigration):
|
||||
def create_homepage(apps, schema_editor):
|
||||
# Get models
|
||||
ContentType = apps.get_model('contenttypes.ContentType')
|
||||
Page = apps.get_model('wagtailcore.Page')
|
||||
Site = apps.get_model('wagtailcore.Site')
|
||||
HomePage = apps.get_model('core.HomePage')
|
||||
|
||||
depends_on = (
|
||||
('wagtailcore', '0002_initial_data'),
|
||||
# Delete the default homepage
|
||||
Page.objects.get(id=2).delete()
|
||||
|
||||
# Create content type for homepage model
|
||||
homepage_content_type, created = ContentType.objects.get_or_create(
|
||||
model='homepage', app_label='core', defaults={'name': 'Homepage'})
|
||||
|
||||
# Create a new homepage
|
||||
homepage = HomePage.objects.create(
|
||||
title="Homepage",
|
||||
slug='home',
|
||||
content_type=homepage_content_type,
|
||||
path='00010001',
|
||||
depth=2,
|
||||
numchild=0,
|
||||
url_path='/home/',
|
||||
)
|
||||
|
||||
def forwards(self, orm):
|
||||
if connection.vendor == 'sqlite':
|
||||
set_autocommit(True)
|
||||
# Create a site with the new homepage set as the root
|
||||
Site.objects.create(
|
||||
hostname='localhost', root_page=homepage, is_default_site=True)
|
||||
|
||||
orm['wagtailcore.Page'].objects.get(id=2).delete()
|
||||
|
||||
homepage_content_type, created = orm['contenttypes.contenttype'].objects.get_or_create(
|
||||
model='homepage', app_label='core', defaults={'name': 'Homepage'})
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
homepage = orm['core.HomePage'].objects.create(
|
||||
title="Homepage",
|
||||
slug='home',
|
||||
content_type=homepage_content_type,
|
||||
path='00010001',
|
||||
depth=2,
|
||||
numchild=0,
|
||||
url_path='/home/',
|
||||
)
|
||||
dependencies = [
|
||||
('core', '0001_initial'),
|
||||
]
|
||||
|
||||
orm['wagtailcore.site'].objects.create(
|
||||
hostname='localhost', root_page=homepage, is_default_site=True)
|
||||
|
||||
def backwards(self, orm):
|
||||
pass
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.homepage': {
|
||||
'Meta': {'object_name': 'HomePage', '_ormbases': ['wagtailcore.Page']},
|
||||
'page_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wagtailcore.Page']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'wagtailcore.page': {
|
||||
'Meta': {'object_name': 'Page'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': "orm['auth.User']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
|
||||
},
|
||||
'wagtailcore.site': {
|
||||
'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'},
|
||||
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
|
||||
'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': "orm['wagtailcore.Page']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['core']
|
||||
symmetrical = True
|
||||
operations = [
|
||||
migrations.RunPython(create_homepage),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% templatetag openblock %} load static core_tags {% templatetag closeblock %}
|
||||
|
||||
{% templatetag openblock %} block body_class {% templatetag closeblock %}template-{% templatetag openvariable %} self|content_type|slugify {% templatetag closevariable %}{% templatetag openblock %} endblock {% templatetag closeblock %}
|
||||
{% templatetag openblock %} block body_class {% templatetag closeblock %}template-{% templatetag openvariable %} self.get_verbose_name|slugify {% templatetag closevariable %}{% templatetag openblock %} endblock {% templatetag closeblock %}
|
||||
|
||||
{% templatetag openblock %} block content {% templatetag closeblock %}
|
||||
<h1>Welcome to your new Wagtail site!</h1>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
# Return the model name/"content type" as a string e.g BlogPage, NewsListingPage.
|
||||
# Can be used with "slugify" to create CSS-friendly classnames
|
||||
# Usage: {% verbatim %}{{ self|content_type|slugify }}{% endverbatim %}
|
||||
@register.filter
|
||||
def content_type(model):
|
||||
return model.__class__.__name__
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ INSTALLED_APPS = (
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'south',
|
||||
'compressor',
|
||||
'taggit',
|
||||
'modelcluster',
|
||||
|
|
@ -48,6 +47,7 @@ INSTALLED_APPS = (
|
|||
'wagtail.wagtaildocs',
|
||||
'wagtail.wagtailsnippets',
|
||||
'wagtail.wagtailusers',
|
||||
'wagtail.wagtailsites',
|
||||
'wagtail.wagtailimages',
|
||||
'wagtail.wagtailembeds',
|
||||
'wagtail.wagtailsearch',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
# Minimal requirements
|
||||
Django>=1.6.2,<1.7
|
||||
South==1.0.0
|
||||
Django>=1.7,<1.8
|
||||
wagtail==0.6
|
||||
|
||||
# Recommended components (require additional setup):
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ chmod a+x $PROJECT_DIR/manage.py
|
|||
|
||||
|
||||
# Run syncdb/migrate/update_index
|
||||
su - vagrant -c "$PYTHON $PROJECT_DIR/manage.py syncdb --noinput && \
|
||||
$PYTHON $PROJECT_DIR/manage.py migrate --noinput && \
|
||||
su - vagrant -c "$PYTHON $PROJECT_DIR/manage.py migrate --noinput && \
|
||||
$PYTHON $PROJECT_DIR/manage.py update_index"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -376,7 +376,9 @@ class ZuluSnippet(models.Model):
|
|||
|
||||
|
||||
class StandardIndex(Page):
|
||||
pass
|
||||
""" Index for the site, not allowed to be placed anywhere """
|
||||
parent_page_types = []
|
||||
|
||||
|
||||
StandardIndex.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
|
|
@ -387,14 +389,22 @@ StandardIndex.content_panels = [
|
|||
class StandardChild(Page):
|
||||
pass
|
||||
|
||||
|
||||
class BusinessIndex(Page):
|
||||
""" Can be placed anywhere, can only have Business children """
|
||||
subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex']
|
||||
|
||||
|
||||
class BusinessSubIndex(Page):
|
||||
""" Can be placed under BusinessIndex, and have BusinessChild children """
|
||||
subpage_types = ['tests.BusinessChild']
|
||||
parent_page_types = ['tests.BusinessIndex']
|
||||
|
||||
|
||||
class BusinessChild(Page):
|
||||
""" Can only be placed under Business indexes, no children allowed """
|
||||
subpage_types = []
|
||||
parent_page_types = ['tests.BusinessIndex', BusinessSubIndex]
|
||||
|
||||
|
||||
class SearchTest(models.Model, index.Indexed):
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ INSTALLED_APPS = [
|
|||
'wagtail.wagtaildocs',
|
||||
'wagtail.wagtailsnippets',
|
||||
'wagtail.wagtailusers',
|
||||
'wagtail.wagtailsites',
|
||||
'wagtail.wagtailimages',
|
||||
'wagtail.wagtailembeds',
|
||||
'wagtail.wagtailsearch',
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from django.core.urlresolvers import reverse
|
|||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore
|
||||
from wagtail.wagtailcore.fields import RichTextArea
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore, resolve_model_string
|
||||
|
||||
|
||||
FORM_FIELD_OVERRIDES = {}
|
||||
|
|
@ -483,31 +483,33 @@ class BasePageChooserPanel(BaseChooserPanel):
|
|||
def target_content_type(cls):
|
||||
if cls._target_content_type is None:
|
||||
if cls.page_type:
|
||||
if isinstance(cls.page_type, string_types):
|
||||
# translate the passed model name into an actual model class
|
||||
from django.db.models import get_model
|
||||
try:
|
||||
app_label, model_name = cls.page_type.split('.')
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("The page_type passed to PageChooserPanel must be of the form 'app_label.model_name'")
|
||||
try:
|
||||
model = resolve_model_string(cls.page_type)
|
||||
except LookupError:
|
||||
raise ImproperlyConfigured("{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
|
||||
cls.__name__, cls.page_type))
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("{0}.page_type refers to model {1!r} that has not been installed".format(
|
||||
cls.__name__, cls.page_type))
|
||||
|
||||
try:
|
||||
page_type = get_model(app_label, model_name)
|
||||
except LookupError:
|
||||
page_type = None
|
||||
|
||||
if page_type is None:
|
||||
raise ImproperlyConfigured("PageChooserPanel refers to model '%s' that has not been installed" % cls.page_type)
|
||||
else:
|
||||
page_type = cls.page_type
|
||||
|
||||
cls._target_content_type = ContentType.objects.get_for_model(page_type)
|
||||
cls._target_content_type = ContentType.objects.get_for_model(model)
|
||||
else:
|
||||
# TODO: infer the content type by introspection on the foreign key
|
||||
cls._target_content_type = ContentType.objects.get_by_natural_key('wagtailcore', 'page')
|
||||
|
||||
return cls._target_content_type
|
||||
|
||||
def render_as_field(self, show_help_text=True):
|
||||
instance_obj = self.get_chosen_item()
|
||||
return mark_safe(render_to_string(self.field_template, {
|
||||
'field': self.bound_field,
|
||||
self.object_type_name: instance_obj,
|
||||
'is_chosen': bool(instance_obj),
|
||||
'show_help_text': show_help_text,
|
||||
'choose_another_text_str': ugettext_lazy("Choose another page"),
|
||||
'choose_one_text_str': ugettext_lazy("Choose a page"),
|
||||
}))
|
||||
|
||||
def render_js(self):
|
||||
page = self.get_chosen_item()
|
||||
parent = page.get_parent() if page else None
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -10,7 +10,7 @@ msgstr ""
|
|||
"Project-Id-Version: Wagtail 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-09-11 16:37+0100\n"
|
||||
"PO-Revision-Date: 2014-08-03 01:50+0100\n"
|
||||
"PO-Revision-Date: 2014-09-17 17:21+0100\n"
|
||||
"Last-Translator: Jose Lourenco <jose@lourenco.ws>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pt_PT\n"
|
||||
|
|
@ -293,6 +293,10 @@ msgid ""
|
|||
"Search results will exclude pages of other types.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Neste campo apenas pode escolher páginas do tipo \"%(type)s\". "
|
||||
"Nos resultados da pesquisa serão excluídas as páginas de outros tipos.\n"
|
||||
" "
|
||||
|
||||
#: templates/wagtailadmin/chooser/_search_results.html:16
|
||||
#, python-format
|
||||
|
|
@ -314,9 +318,8 @@ msgstr[1] ""
|
|||
" "
|
||||
|
||||
#: templates/wagtailadmin/chooser/browse.html:3
|
||||
#, fuzzy
|
||||
msgid "Choose"
|
||||
msgstr "Escolher uma página"
|
||||
msgstr "Escolhe"
|
||||
|
||||
#: templates/wagtailadmin/chooser/browse.html:6
|
||||
#: templates/wagtailadmin/chooser/search.html:2
|
||||
|
|
@ -581,11 +584,10 @@ msgid "You can edit the privacy settings on:"
|
|||
msgstr "Você pode editar as configurações de privacidade em:"
|
||||
|
||||
#: templates/wagtailadmin/page_privacy/set_privacy.html:6
|
||||
#, fuzzy
|
||||
msgid "Privacy changes apply to all children of this page too."
|
||||
msgstr ""
|
||||
"<b>Nota:</b> as alterações de privacidade também serão aplicadas a todas as "
|
||||
"páginas filhas desta página."
|
||||
"As alterações de privacidade também serão aplicadas a todas as páginas "
|
||||
"filhas desta página."
|
||||
|
||||
#: templates/wagtailadmin/pages/_moderator_userbar.html:4
|
||||
#, python-format
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ try:
|
|||
except ImportError:
|
||||
from django.forms.util import flatatt
|
||||
|
||||
from django.conf import settings
|
||||
from django.forms import MediaDefiningClass
|
||||
from django.forms import MediaDefiningClass, Media
|
||||
from django.utils.text import slugify
|
||||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
|
||||
|
|
@ -36,21 +36,83 @@ class MenuItem(with_metaclass(MediaDefiningClass)):
|
|||
"""
|
||||
return True
|
||||
|
||||
def render_html(self):
|
||||
def render_html(self, request):
|
||||
return format_html(
|
||||
"""<li class="menu-{0}"><a href="{1}" class="{2}"{3}>{4}</a></li>""",
|
||||
self.name, self.url, self.classnames, self.attr_string, self.label)
|
||||
|
||||
|
||||
_master_menu_item_list = None
|
||||
def get_master_menu_item_list():
|
||||
"""
|
||||
Return the list of menu items registered with the 'register_admin_menu_item' hook.
|
||||
This is the "master list" because the final admin menu may vary per request
|
||||
according to the value of is_shown() and the construct_main_menu hook.
|
||||
"""
|
||||
global _master_menu_item_list
|
||||
if _master_menu_item_list is None:
|
||||
_master_menu_item_list = [fn() for fn in hooks.get_hooks('register_admin_menu_item')]
|
||||
class Menu(object):
|
||||
def __init__(self, register_hook_name, construct_hook_name=None):
|
||||
self.register_hook_name = register_hook_name
|
||||
self.construct_hook_name = construct_hook_name
|
||||
# _registered_menu_items will be populated on first access to the
|
||||
# registered_menu_items property. We can't populate it in __init__ because
|
||||
# we can't rely on all hooks modules to have been imported at the point that
|
||||
# we create the admin_menu and settings_menu instances
|
||||
self._registered_menu_items = None
|
||||
|
||||
return _master_menu_item_list
|
||||
@property
|
||||
def registered_menu_items(self):
|
||||
if self._registered_menu_items is None:
|
||||
self._registered_menu_items = [fn() for fn in hooks.get_hooks(self.register_hook_name)]
|
||||
return self._registered_menu_items
|
||||
|
||||
def menu_items_for_request(self, request):
|
||||
return [item for item in self.registered_menu_items if item.is_shown(request)]
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
media = Media()
|
||||
for item in self.registered_menu_items:
|
||||
media += item.media
|
||||
return media
|
||||
|
||||
def render_html(self, request):
|
||||
menu_items = self.menu_items_for_request(request)
|
||||
|
||||
# provide a hook for modifying the menu, if construct_hook_name has been set
|
||||
if self.construct_hook_name:
|
||||
for fn in hooks.get_hooks(self.construct_hook_name):
|
||||
fn(request, menu_items)
|
||||
|
||||
rendered_menu_items = []
|
||||
for item in sorted(menu_items, key=lambda i: i.order):
|
||||
try:
|
||||
rendered_menu_items.append(item.render_html(request))
|
||||
except TypeError:
|
||||
# fallback for older render_html methods that don't accept a request arg
|
||||
rendered_menu_items.append(item.render_html())
|
||||
|
||||
return mark_safe(''.join(rendered_menu_items))
|
||||
|
||||
|
||||
class SubmenuMenuItem(MenuItem):
|
||||
"""A MenuItem which wraps an inner Menu object"""
|
||||
def __init__(self, label, menu, **kwargs):
|
||||
self.menu = menu
|
||||
super(SubmenuMenuItem, self).__init__(label, '#', **kwargs)
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
return Media(js=['wagtailadmin/js/submenu.js']) + self.menu.media
|
||||
|
||||
def is_shown(self, request):
|
||||
# show the submenu if one or more of its children is shown
|
||||
return bool(self.menu.menu_items_for_request(request))
|
||||
|
||||
def render_html(self, request):
|
||||
return format_html(
|
||||
"""<li class="menu-{0}">
|
||||
<a href="#" class="submenu-trigger {1}"{2}>{3}</a>
|
||||
<div class="nav-submenu">
|
||||
<h2 class="{1}">{3}</h2>
|
||||
<ul>{4}</ul>
|
||||
</div>
|
||||
</li>""",
|
||||
self.name, self.classnames, self.attr_string, self.label, self.menu.render_html(request)
|
||||
)
|
||||
|
||||
|
||||
admin_menu = Menu(register_hook_name='register_admin_menu_item', construct_hook_name='construct_main_menu')
|
||||
settings_menu = Menu(register_hook_name='register_settings_menu_item')
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ function InlinePanel(opts) {
|
|||
var self = {};
|
||||
|
||||
self.setHasContent = function(){
|
||||
if($('li:visible', self.formsUl).length){
|
||||
if($('> li', self.formsUl).not(".deleted").length){
|
||||
self.formsUl.parent().removeClass('empty');
|
||||
}else{
|
||||
self.formsUl.parent().addClass('empty');
|
||||
|
|
@ -139,7 +139,7 @@ function InlinePanel(opts) {
|
|||
$('#' + deleteInputId + '-button').click(function() {
|
||||
/* set 'deleted' form field to true */
|
||||
$('#' + deleteInputId).val('1');
|
||||
$('#' + childId).slideUp(function() {
|
||||
$('#' + childId).addClass('deleted').slideUp(function() {
|
||||
self.updateMoveButtonDisabledStates();
|
||||
self.setHasContent();
|
||||
});
|
||||
|
|
@ -191,8 +191,8 @@ function InlinePanel(opts) {
|
|||
/* Hide container on page load if it is marked as deleted. Remove the error
|
||||
message so that it doesn't count towards the number of errors on the tab at the
|
||||
top of the page. */
|
||||
if ( $('#' + deleteInputId).val() === "1" ) {
|
||||
$('#' + childId).hide(0, function() {
|
||||
if ($('#' + deleteInputId).val() === "1" ) {
|
||||
$('#' + childId).addClass('deleted').hide(0, function() {
|
||||
self.updateMoveButtonDisabledStates();
|
||||
self.setHasContent();
|
||||
});
|
||||
|
|
|
|||
18
wagtail/wagtailadmin/static/wagtailadmin/js/submenu.js
Normal file
18
wagtail/wagtailadmin/static/wagtailadmin/js/submenu.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
$(function(){
|
||||
$('.nav-main .submenu-trigger').on('click', function(){
|
||||
if($(this).closest('li').find('.nav-submenu').length){
|
||||
$(this).closest('li').toggleClass('submenu-active');
|
||||
$('.nav-wrapper').toggleClass('submenu-active')
|
||||
return false
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('keydown click', function(e){
|
||||
if($('.nav-wrapper.submenu-active').length){
|
||||
if(e.keyCode == 27 || !e.keyCode){
|
||||
$('.nav-main .submenu-active').removeClass('submenu-active');
|
||||
$('.nav-wrapper').toggleClass('submenu-active')
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -18,7 +18,7 @@ ul.listing{
|
|||
|
||||
h3{
|
||||
margin:0;
|
||||
font-size:0.95em;
|
||||
font-size:1em;
|
||||
}
|
||||
|
||||
td, th{
|
||||
|
|
@ -156,12 +156,14 @@ ul.listing{
|
|||
}
|
||||
|
||||
.title{
|
||||
color:darken($color-grey-2, 10%);
|
||||
|
||||
h2{
|
||||
text-transform:none;
|
||||
margin:0;
|
||||
font-size:1.15em;
|
||||
font-weight:600;
|
||||
color:darken($color-grey-2, 10%);
|
||||
color:inherit;
|
||||
line-height:1.5em;
|
||||
|
||||
a{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,383 @@
|
|||
$selected-highlight:darken($color-grey-1, 10%);
|
||||
$submenu-color:darken($color-grey-1, 5%);
|
||||
|
||||
.nav-wrapper{
|
||||
position:relative;
|
||||
background: $color-grey-1;
|
||||
margin-left: -$menu-width;
|
||||
width: $menu-width;
|
||||
float: left;
|
||||
height:100%;
|
||||
min-height:800px;
|
||||
}
|
||||
#nav-toggle{
|
||||
left:$mobile-nice-padding;
|
||||
cursor:pointer;
|
||||
position:absolute;
|
||||
|
||||
&:before{
|
||||
font-size:40px;
|
||||
color:white;
|
||||
line-height:40px;
|
||||
content:"\2261";
|
||||
}
|
||||
}
|
||||
|
||||
.nav-main{
|
||||
top: 43px;
|
||||
bottom: 0px;
|
||||
overflow: auto;
|
||||
width:100%;
|
||||
|
||||
ul, li{
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type:none;
|
||||
}
|
||||
|
||||
ul{
|
||||
border-top:1px solid rgba(100,100,100,0.2);
|
||||
}
|
||||
|
||||
li{
|
||||
@include transition(border-color 0.2s ease);
|
||||
border-bottom:1px solid rgba(100,100,100,0.2);
|
||||
position:relative;
|
||||
|
||||
/* TODO: find better way to procedurally detect the appropriate menu to highlight */
|
||||
.menu-snippets &.menu-snippets,
|
||||
.menu-users &.menu-users,
|
||||
.menu-groups &.menu-groups,
|
||||
.menu-sites &.menu-sites,
|
||||
.menu-redirects &.menu-redirects,
|
||||
.menu-editorspicks &.menu-editors-picks,
|
||||
.menu-snippets &.menu-snippets,
|
||||
.menu-documents &.menu-documents,
|
||||
.menu-images &.menu-images,
|
||||
.menu-search &.menu-search,
|
||||
.menu-explorer &.menu-explorer,
|
||||
.menu-forms &.menu-forms{
|
||||
background:$selected-highlight;
|
||||
text-shadow:-1px -1px 0px rgba(0,0,0,0.3);
|
||||
|
||||
a{
|
||||
border-left-color:$color-salmon;
|
||||
color:white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include transition(border-color 0.2s ease);
|
||||
-webkit-font-smoothing: auto;
|
||||
text-decoration:none;
|
||||
display: block;
|
||||
color: #AAA;
|
||||
padding: 0.8em 1.7em;
|
||||
position:relative;
|
||||
font-size:0.95em;
|
||||
font-weight:300;
|
||||
white-space:nowrap;
|
||||
border-left:3px solid transparent;
|
||||
|
||||
&:before{
|
||||
font-size:1rem;
|
||||
vertical-align:-20%;
|
||||
margin-right:0.5em;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background-color:rgba(100,100,100,0.15);
|
||||
color:white;
|
||||
text-shadow:-1px -1px 0px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* only really used for spinners and settings menu */
|
||||
&:after{
|
||||
font-size:1.5em;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
right:0.5em;
|
||||
top:0.5em;
|
||||
margin-top:0.15em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.avatar{
|
||||
display:none;
|
||||
|
||||
a:hover{
|
||||
background-color:transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-submenu{
|
||||
background:$submenu-color;
|
||||
|
||||
h2{
|
||||
display:none;
|
||||
}
|
||||
|
||||
a{
|
||||
white-space:normal;
|
||||
padding: 0.9em 0 0.9em 4.5em;
|
||||
|
||||
&:before{
|
||||
margin-left:-1.5em;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background-color:rgba(100,100,100,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
li{
|
||||
border:0;
|
||||
}
|
||||
}
|
||||
|
||||
.explorer{
|
||||
position:absolute;
|
||||
margin-top:70px;
|
||||
font-size:0.95em;
|
||||
}
|
||||
|
||||
.nav-search{
|
||||
position:relative;
|
||||
padding:0;
|
||||
|
||||
label{
|
||||
@include visuallyhidden();
|
||||
}
|
||||
input, button{
|
||||
font-size:1em;
|
||||
border:0;
|
||||
@include border-radius(0);
|
||||
}
|
||||
input{
|
||||
background-color:darken($color-grey-2, 15%);
|
||||
color:#AAA;
|
||||
padding: 0.9em 2.5em 0.9em 2em;
|
||||
|
||||
&:active, &:focus{
|
||||
background-color:darken($color-grey-2, 5%);
|
||||
color:white;
|
||||
}
|
||||
}
|
||||
button{
|
||||
background-color:transparent;
|
||||
position:absolute;
|
||||
top:0; right:0; bottom:0;
|
||||
margin:auto;
|
||||
padding:0;
|
||||
width:3em; height:100%;
|
||||
overflow:hidden;
|
||||
|
||||
&:before{
|
||||
font-family:wagtail;
|
||||
font-weight:200;
|
||||
text-transform:none;
|
||||
content:"f";
|
||||
display:block;
|
||||
height:100%;
|
||||
line-height:3.3em;
|
||||
padding:0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Navigation open condition */
|
||||
body.nav-open{
|
||||
.wrapper{
|
||||
transform: translate3d($menu-width,0,0);
|
||||
-webkit-transform: translate3d($menu-width,0,0);
|
||||
}
|
||||
.content-wrapper{
|
||||
position:fixed;
|
||||
}
|
||||
footer{
|
||||
bottom:1px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explorer open condition, widens navigation area */
|
||||
body.explorer-open {
|
||||
.wrapper{
|
||||
transform: translate3d($menu-width*2,0,0);
|
||||
-webkit-transform: translate3d($menu-width*2,0,0);
|
||||
}
|
||||
.nav-wrapper{
|
||||
margin-left: -$menu-width*2;
|
||||
width: $menu-width*2;
|
||||
}
|
||||
|
||||
.nav-main{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.explorer{
|
||||
display:block;
|
||||
border-top:1px solid rgba(200,200,200,0.1);
|
||||
|
||||
&:before{
|
||||
position:absolute;
|
||||
top:-3em;
|
||||
content:"Close explorer";
|
||||
padding:0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
.wrapper,
|
||||
body.nav-open .wrapper{
|
||||
-webkit-transform:none;
|
||||
transform:none;
|
||||
padding-left:$menu-width;
|
||||
}
|
||||
|
||||
.nav-wrapper{
|
||||
/* heigt and position necessary to force it to 100% height of screen (with some JS help) */
|
||||
position:absolute;
|
||||
left:0;
|
||||
height:100%;
|
||||
margin-left: 0;
|
||||
|
||||
.inner{
|
||||
height:100%;
|
||||
position:fixed;
|
||||
width:$menu-width;
|
||||
}
|
||||
}
|
||||
|
||||
#nav-toggle{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.nav-main{
|
||||
position:absolute;
|
||||
top: 164px; /* WARNING - magic number - the height of the logo plus search box */
|
||||
margin-bottom: 116px; /* WARNING - magic number - the height of the .footer */
|
||||
|
||||
.footer{
|
||||
padding-top:1em;
|
||||
background-color:$color-grey-1;
|
||||
position:fixed;
|
||||
width:$menu-width - 7;
|
||||
bottom:0;
|
||||
text-align:center;
|
||||
}
|
||||
.avatar{
|
||||
display:block;
|
||||
margin:auto;
|
||||
text-align:center;
|
||||
margin-bottom:1em;
|
||||
|
||||
a{
|
||||
padding:0 0 1em 0;
|
||||
}
|
||||
&:hover{
|
||||
@include box-shadow(0px 0px 6px 0px rgba(0,0,0,1));
|
||||
}
|
||||
}
|
||||
a.submenu-trigger:after{
|
||||
content:"n";
|
||||
}
|
||||
}
|
||||
|
||||
.nav-submenu{
|
||||
position:fixed;
|
||||
height:100%;
|
||||
width:0;
|
||||
padding:0;
|
||||
top:0;
|
||||
left:$menu-width;
|
||||
overflow:auto;
|
||||
max-height:100%;
|
||||
border-right:1px solid rgba(0,0,0,0.1);
|
||||
|
||||
h2,ul{
|
||||
float:right;
|
||||
width:$menu-width;
|
||||
}
|
||||
|
||||
h2{
|
||||
display:block;
|
||||
padding:0.2em 0;
|
||||
font-size:1.2em;
|
||||
font-weight:500;
|
||||
text-transform:none;
|
||||
text-align:center;
|
||||
color: #AAA;
|
||||
|
||||
&:before{
|
||||
font-size:4em;
|
||||
display:block;
|
||||
text-align:center;
|
||||
margin:0 0 0.2em 0;
|
||||
width:100%;
|
||||
opacity:0.15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.submenu-active{
|
||||
background:$submenu-color;
|
||||
|
||||
> a{
|
||||
text-shadow:-1px -1px 0px rgba(0,0,0,0.3);
|
||||
|
||||
&:hover{
|
||||
background-color:transparent;
|
||||
}
|
||||
}
|
||||
.nav-submenu{
|
||||
@include box-shadow(2px 0 2px rgba(0,0,0,0.35));
|
||||
@include transition(width 0.2s ease);
|
||||
width:$menu-width;
|
||||
padding:0 0 1.5em 0;
|
||||
|
||||
a{
|
||||
padding-left:3.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explorer {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:99%;
|
||||
margin-top:164px; /* same as .nav-main minus 1 pixel for border */
|
||||
}
|
||||
|
||||
.dl-menu {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
body.nav-open{
|
||||
.content-wrapper{
|
||||
position:relative;
|
||||
}
|
||||
}
|
||||
|
||||
body.explorer-open {
|
||||
.wrapper{
|
||||
-webkit-transform:none;
|
||||
transform:none;
|
||||
}
|
||||
.nav-wrapper{
|
||||
margin-left: 0;
|
||||
width: $menu-width;
|
||||
}
|
||||
.explorer:before{
|
||||
display:none;
|
||||
}
|
||||
.nav-main{
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ p{
|
|||
}
|
||||
|
||||
a{
|
||||
@include transition(color 0.2s ease, background-color 0.2s ease);
|
||||
outline:none;
|
||||
color:$color-link;
|
||||
text-decoration:none;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
@import "components/header.scss";
|
||||
@import "components/progressbar.scss";
|
||||
@import "components/datetimepicker.scss";
|
||||
@import "components/main-nav.scss";
|
||||
|
||||
@import "fonts.scss";
|
||||
|
||||
|
|
@ -54,219 +55,40 @@ body{
|
|||
}
|
||||
|
||||
.wrapper{
|
||||
@include transition-transform(0.2s ease);
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
.nav-wrapper{
|
||||
@include box-shadow(inset -5px 0px 5px -3px rgba(0, 0, 0, 0.3));
|
||||
position:relative;
|
||||
background: $color-grey-1;
|
||||
margin-left: -100%;
|
||||
width: 180px;
|
||||
float: left;
|
||||
height:100%;
|
||||
min-height:800px;
|
||||
/* See components/main-nav.scss */
|
||||
}
|
||||
.logo{
|
||||
display:block;
|
||||
margin:2em auto;
|
||||
text-align:left;
|
||||
text-decoration:none;
|
||||
color:white;
|
||||
padding: 0.9em 1.2em;
|
||||
margin:0;
|
||||
font-size:0.9em;
|
||||
-webkit-font-smoothing: auto;
|
||||
|
||||
span{
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
img{
|
||||
width:20px;
|
||||
float:left;
|
||||
border:0;
|
||||
margin-right:1em;
|
||||
}
|
||||
}
|
||||
|
||||
#nav-toggle{
|
||||
left:$mobile-nice-padding;
|
||||
cursor:pointer;
|
||||
position:absolute;
|
||||
|
||||
&:before{
|
||||
font-size:40px;
|
||||
color:white;
|
||||
line-height:40px;
|
||||
content:"\2261";
|
||||
}
|
||||
}
|
||||
|
||||
.nav-main{
|
||||
top: 43px;
|
||||
bottom: 0px;
|
||||
overflow: auto;
|
||||
width:100%;
|
||||
|
||||
ul, li{
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type:none;
|
||||
}
|
||||
|
||||
ul{
|
||||
border-top:1px solid $color-grey-1-1;
|
||||
}
|
||||
|
||||
li{
|
||||
border-bottom:1px solid $color-grey-1-1;
|
||||
position:relative;
|
||||
|
||||
&.selected{
|
||||
background-color:$color-grey-1;
|
||||
}
|
||||
|
||||
a:before{
|
||||
font-size:1.2rem;
|
||||
}
|
||||
|
||||
.menu-snippets &.menu-snippets,
|
||||
.menu-users &.menu-users,
|
||||
.menu-snippets &.menu-snippets,
|
||||
.menu-documents &.menu-documents,
|
||||
.menu-images &.menu-images,
|
||||
.menu-search &.menu-search,
|
||||
.menu-explorer &.menu-explorer{
|
||||
background:darken($color-grey-1, 10%);
|
||||
|
||||
a{
|
||||
color:white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
-webkit-font-smoothing: auto;
|
||||
text-decoration:none;
|
||||
text-transform:uppercase;
|
||||
display: block;
|
||||
color: #AAA;
|
||||
padding: 0.9em 1.2em;
|
||||
position:relative;
|
||||
font-size:0.85em;
|
||||
font-weight:400;
|
||||
white-space:nowrap;
|
||||
text-shadow:-1px -1px 0px rgba(0,0,0,0.3);
|
||||
|
||||
&:before{
|
||||
vertical-align:-20%;
|
||||
margin-right:0.5em;
|
||||
}
|
||||
&:hover{
|
||||
color:white;
|
||||
}
|
||||
|
||||
/* only really used for spinners */
|
||||
&:after{
|
||||
font-size:1.5em;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
right:0.5em;
|
||||
top:0.5em;
|
||||
margin-top:0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar{
|
||||
display:none;
|
||||
}
|
||||
|
||||
/* search form */
|
||||
#menu-search{
|
||||
position:relative;
|
||||
|
||||
.fields{
|
||||
@include transition(background-color 0.2s ease);
|
||||
border:0;
|
||||
li{
|
||||
border:0;
|
||||
}
|
||||
}
|
||||
.field{
|
||||
padding:0;
|
||||
color: #AAA;
|
||||
|
||||
&:before{
|
||||
position:absolute;
|
||||
left:0.75em;
|
||||
top:0.45em;
|
||||
}
|
||||
}
|
||||
.submit{
|
||||
@include visuallyhidden();
|
||||
}
|
||||
label{
|
||||
font-weight:normal;
|
||||
-webkit-font-smoothing: auto;
|
||||
line-height:inherit;
|
||||
text-transform:uppercase;
|
||||
padding: 0.9em 1.2em 0.9em 3.7em;
|
||||
color: #AAA;
|
||||
font-size:0.95em;
|
||||
}
|
||||
input{
|
||||
float:left;
|
||||
margin-top:-1000px;
|
||||
text-transform:uppercase;
|
||||
font-size:1em;
|
||||
padding: 0.9em 1.2em 0.9em 3.5em;
|
||||
border:0;
|
||||
border-radius:0;
|
||||
background-color:transparent;
|
||||
line-height:inherit;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
.field, input{
|
||||
color:white;
|
||||
}
|
||||
}
|
||||
|
||||
&.focussed{
|
||||
label{
|
||||
display:none;
|
||||
}
|
||||
.fields{
|
||||
background-color:$color-grey-4;
|
||||
}
|
||||
.field{
|
||||
color: $color-grey-1;
|
||||
}
|
||||
input{
|
||||
margin-top:0px;
|
||||
color:$color-grey-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explorer{
|
||||
position:absolute;
|
||||
margin-top:70px;
|
||||
font-size:0.9em;
|
||||
.logo{
|
||||
display:block;
|
||||
margin:2em auto;
|
||||
text-align:left;
|
||||
text-decoration:none;
|
||||
color: #AAA;
|
||||
padding: 0.9em 1.2em;
|
||||
margin:0;
|
||||
-webkit-font-smoothing: auto;
|
||||
|
||||
img{
|
||||
width:20px;
|
||||
float:left;
|
||||
border:0;
|
||||
margin-right:1em;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper{
|
||||
width:100%;
|
||||
height:100%; /* this has no effect on desktop, but on mobile it helps aesthetics of menu popout action */
|
||||
float: right;
|
||||
float: left;
|
||||
position: relative;
|
||||
background-color:$color-grey-4;
|
||||
border-bottom:1px solid $color-grey-3;
|
||||
|
||||
/* transform: translate3d(0,0,0);
|
||||
-webkit-transform: translate3d(0,0,0);*/
|
||||
}
|
||||
.content{
|
||||
@include row();
|
||||
|
|
@ -276,52 +98,8 @@ body{
|
|||
position:relative; /* yuk. necessary for positions for jquery ui widgets */
|
||||
}
|
||||
|
||||
body.nav-open .nav-wrapper{
|
||||
margin-left:0;
|
||||
}
|
||||
|
||||
body.nav-open .content-wrapper{
|
||||
transform: translate3d(180px,0,0);
|
||||
-webkit-transform: translate3d(180px,0,0);
|
||||
|
||||
position:fixed;
|
||||
}
|
||||
|
||||
body.nav-open footer{
|
||||
bottom:1px;
|
||||
}
|
||||
body.nav-closed footer{
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
body.explorer-open {
|
||||
.nav-wrapper{
|
||||
width:80%;
|
||||
}
|
||||
.nav-main{
|
||||
display:none;
|
||||
}
|
||||
.content-wrapper{
|
||||
transform: translate3d(80%,0,0);
|
||||
-webkit-transform: translate3d(80%,0,0);
|
||||
|
||||
position:fixed;
|
||||
}
|
||||
|
||||
.explorer{
|
||||
display:block;
|
||||
border-top:1px solid rgba(200,200,200,0.1);
|
||||
|
||||
&:before{
|
||||
position:absolute;
|
||||
top:-3em;
|
||||
content:"Close explorer";
|
||||
padding:0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer{
|
||||
@include transition(all 0.2s ease);
|
||||
@include row();
|
||||
@include border-radius(3px 3px 0 0);
|
||||
@include box-shadow(0px 0px 2px rgba(255,255,255,0.5));
|
||||
|
|
@ -420,21 +198,6 @@ footer{
|
|||
}
|
||||
}
|
||||
|
||||
/* &:after, &:before{
|
||||
content: "";
|
||||
border-top: 1.2em solid transparent;
|
||||
border-bottom: 1.2em solid transparent;
|
||||
}
|
||||
&:after {
|
||||
border-left: 1em solid $color-teal-darker;
|
||||
position: absolute; right: -1.2em; top: 0;
|
||||
z-index: 1;
|
||||
border:1em solid red;
|
||||
}
|
||||
&:before {
|
||||
border-left: 1em solid white;
|
||||
position: absolute; left: 0; top: 0;
|
||||
} */
|
||||
&:hover {
|
||||
background: $color-teal-dark;
|
||||
a{
|
||||
|
|
@ -501,6 +264,9 @@ footer{
|
|||
.content-wrapper{
|
||||
z-index:3;
|
||||
}
|
||||
.nav-submenu{
|
||||
z-index:6;
|
||||
}
|
||||
footer, .logo{
|
||||
z-index:100;
|
||||
}
|
||||
|
|
@ -556,135 +322,40 @@ footer, .logo{
|
|||
padding-right:$desktop-nice-padding;
|
||||
}
|
||||
|
||||
.wrapper{
|
||||
margin-left:$menu-width;
|
||||
}
|
||||
|
||||
.browsermessage{
|
||||
margin:0 0 0 -150px;
|
||||
}
|
||||
.content-wrapper{
|
||||
-webkit-transform:none;
|
||||
transform:none;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-wrapper{
|
||||
/* heigt and position necessary to force it to 100% height of screen (with some JS help) */
|
||||
position:absolute;
|
||||
left:0;
|
||||
height:100%;
|
||||
width:$menu-width;
|
||||
margin-left: 0;
|
||||
.logo{
|
||||
margin:1em auto;
|
||||
text-align:center;
|
||||
|
||||
.inner{
|
||||
height:100%;
|
||||
position:fixed;
|
||||
width:$menu-width;
|
||||
}
|
||||
}
|
||||
.logo{
|
||||
margin:1em auto;
|
||||
text-align:center;
|
||||
|
||||
span{
|
||||
display:none;
|
||||
}
|
||||
img{
|
||||
width:60px;
|
||||
float:none;
|
||||
margin:auto;
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
|
||||
.content{
|
||||
border-top:0;
|
||||
background-color:none;
|
||||
padding-top:0;
|
||||
}
|
||||
|
||||
#nav-toggle{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.nav-main{
|
||||
position:absolute;
|
||||
top: 125px;
|
||||
margin-bottom: 116px; /* WARNING: magic number - the height of the .footer */
|
||||
|
||||
.footer{
|
||||
padding-top:1em;
|
||||
background-color:$color-grey-1;
|
||||
position:fixed;
|
||||
width:$menu-width - 7;
|
||||
bottom:0;
|
||||
text-align:center;
|
||||
}
|
||||
.avatar{
|
||||
display:block;
|
||||
margin:auto;
|
||||
text-align:center;
|
||||
margin-bottom:1em;
|
||||
|
||||
a{
|
||||
padding:0 0 1em 0;
|
||||
}
|
||||
&:hover{
|
||||
@include box-shadow(0px 0px 6px 0px rgba(0,0,0,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explorer {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:99%;
|
||||
margin-top:124px; /* same as .nav-main minus 1 pixel for border */
|
||||
}
|
||||
.dl-menu {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* UN-set the transformations used on mobile */
|
||||
body.nav-open{
|
||||
.content-wrapper{
|
||||
position:relative;
|
||||
transform: none;
|
||||
-webkit-transform: none;
|
||||
}
|
||||
.nav-wrapper{
|
||||
margin-left: -$menu-width;
|
||||
}
|
||||
.nav-wrapper,
|
||||
.nav-main{
|
||||
width:$menu-width;
|
||||
}
|
||||
}
|
||||
|
||||
body.explorer-open {
|
||||
.nav-wrapper{
|
||||
width:$menu-width;
|
||||
}
|
||||
.explorer:before{
|
||||
span{
|
||||
display:none;
|
||||
}
|
||||
.nav-main{
|
||||
img{
|
||||
width:60px;
|
||||
float:none;
|
||||
margin:auto;
|
||||
display:block;
|
||||
}
|
||||
.content-wrapper{
|
||||
transform: none;
|
||||
-webkit-transform: none;
|
||||
|
||||
position:relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer{
|
||||
width:80%;
|
||||
margin-left:50px;
|
||||
}
|
||||
|
||||
.content{
|
||||
border-top:0;
|
||||
background-color:none;
|
||||
padding-top:0;
|
||||
}
|
||||
/* END NOT SURE ABOUT THIS */
|
||||
|
||||
|
||||
.breadcrumb{
|
||||
padding-top:0;
|
||||
|
|
@ -699,6 +370,12 @@ footer, .logo{
|
|||
}
|
||||
}
|
||||
|
||||
footer{
|
||||
width:80%;
|
||||
margin-left:50px;
|
||||
}
|
||||
|
||||
|
||||
/* Z-indexes */
|
||||
.nav-main{
|
||||
li{
|
||||
|
|
@ -711,8 +388,14 @@ footer, .logo{
|
|||
.explorer {
|
||||
z-index:$explorer-z-index;
|
||||
}
|
||||
.nav-submenu {
|
||||
z-index:$explorer-z-index;
|
||||
}
|
||||
.nav-wrapper{
|
||||
z-index:auto; /* allows overspill of messages banner onto left menu, but also explorer to spill over main content */
|
||||
z-index:auto; /* allows overspill of messages banner onto left menu, but also explorer to spill over main content */
|
||||
}
|
||||
.nav-wrapper.submenu-active{
|
||||
z-index:5;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -720,9 +403,6 @@ footer, .logo{
|
|||
.wrapper{
|
||||
max-width:$breakpoint-desktop-larger;
|
||||
}
|
||||
.nav-wrapper .inner{
|
||||
background:$color-grey-1;
|
||||
}
|
||||
|
||||
footer{
|
||||
width:90em;
|
||||
|
|
@ -742,4 +422,4 @@ footer,
|
|||
}
|
||||
.nav-main a, a{
|
||||
@include transition(color 0.2s ease, background-color 0.2s ease);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,4 +62,4 @@ $color-text-input: $color-grey-1;
|
|||
|
||||
/* misc sizing */
|
||||
$thumbnail-width: 130px;
|
||||
$menu-width: 150px;
|
||||
$menu-width: 180px;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/*! jQuery UI - v1.10.3 - 2013-09-26
|
||||
/*! jQuery UI - v1.10.4 - 2014-09-12
|
||||
* http://jqueryui.com
|
||||
* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Open Sans%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.4em&cornerRadius=0&bgColorHeader=%23246060&bgTextureHeader=flat&bgImgOpacityHeader=100&borderColorHeader=%23246060&fcHeader=%23ffffff&iconColorHeader=%23ffffff&bgColorContent=%23ffffff&bgTextureContent=flat&bgImgOpacityContent=100&borderColorContent=%23d8d8d8&fcContent=%23222222&iconColorContent=%23222222&bgColorDefault=%23ffffff&bgTextureDefault=flat&bgImgOpacityDefault=100&borderColorDefault=%23d3d3d3&fcDefault=%23555555&iconColorDefault=%23ffffff&bgColorHover=%2349c0c1&bgTextureHover=flat&bgImgOpacityHover=100&borderColorHover=%2349c0c1&fcHover=%23ffffff&iconColorHover=%23ffffff&bgColorActive=%23ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=%23aaaaaa&fcActive=%23212121&iconColorActive=%23454545&bgColorHighlight=%23e8f8f9&bgTextureHighlight=flat&bgImgOpacityHighlight=100&borderColorHighlight=%23e8f8f9&fcHighlight=%23363636&iconColorHighlight=%2349c0c1&bgColorError=%23f7474e&bgTextureError=flat&bgImgOpacityError=100&borderColorError=%23f7474e&fcError=%23ffffff&iconColorError=%23ffffff&bgColorOverlay=%23aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
|
||||
* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Open%20Sans%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.2em&cornerRadius=0&bgColorHeader=%23246060&bgTextureHeader=flat&bgImgOpacityHeader=100&borderColorHeader=%23246060&fcHeader=%23ffffff&iconColorHeader=%23ffffff&bgColorContent=%23ffffff&bgTextureContent=flat&bgImgOpacityContent=100&borderColorContent=%23d8d8d8&fcContent=%23222222&iconColorContent=%23222222&bgColorDefault=%23ffffff&bgTextureDefault=flat&bgImgOpacityDefault=100&borderColorDefault=%23d3d3d3&fcDefault=%23555555&iconColorDefault=%23555555&bgColorHover=%2349c0c1&bgTextureHover=flat&bgImgOpacityHover=100&borderColorHover=%2349c0c1&fcHover=%23ffffff&iconColorHover=%23ffffff&bgColorActive=%2349c0c1&bgTextureActive=flat&bgImgOpacityActive=65&borderColorActive=%23aaaaaa&fcActive=%23ffffff&iconColorActive=%23ffffff&bgColorHighlight=%23e8f8f9&bgTextureHighlight=flat&bgImgOpacityHighlight=100&borderColorHighlight=%23e8f8f9&fcHighlight=%23363636&iconColorHighlight=%2349c0c1&bgColorError=%23f7474e&bgTextureError=flat&bgImgOpacityError=100&borderColorError=%23f7474e&fcError=%23ffffff&iconColorError=%23ffffff&bgColorOverlay=%23aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
|
||||
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
|
|
@ -347,9 +347,6 @@ button.ui-button::-moz-focus-inner {
|
|||
font-size: 1em;
|
||||
margin: 1px 0;
|
||||
}
|
||||
.ui-datepicker select.ui-datepicker-month-year {
|
||||
width: 100%;
|
||||
}
|
||||
.ui-datepicker select.ui-datepicker-month,
|
||||
.ui-datepicker select.ui-datepicker-year {
|
||||
width: 49%;
|
||||
|
|
@ -466,6 +463,7 @@ button.ui-button::-moz-focus-inner {
|
|||
border-left-width: 1px;
|
||||
}
|
||||
.ui-dialog {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
@ -488,7 +486,7 @@ button.ui-button::-moz-focus-inner {
|
|||
position: absolute;
|
||||
right: .3em;
|
||||
top: 50%;
|
||||
width: 21px;
|
||||
width: 20px;
|
||||
margin: -10px 0 0 0;
|
||||
padding: 1px;
|
||||
height: 20px;
|
||||
|
|
@ -704,13 +702,13 @@ button.ui-button::-moz-focus-inner {
|
|||
overflow: hidden;
|
||||
right: 0;
|
||||
}
|
||||
/* more specificity required here to overide default borders */
|
||||
/* more specificity required here to override default borders */
|
||||
.ui-spinner a.ui-spinner-button {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
}
|
||||
/* vertical centre icon */
|
||||
/* vertically center icon */
|
||||
.ui-spinner .ui-icon {
|
||||
position: absolute;
|
||||
margin-top: -8px;
|
||||
|
|
@ -747,7 +745,7 @@ button.ui-button::-moz-focus-inner {
|
|||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li a {
|
||||
.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
|
||||
float: left;
|
||||
padding: .5em 1em;
|
||||
text-decoration: none;
|
||||
|
|
@ -756,13 +754,12 @@ button.ui-button::-moz-focus-inner {
|
|||
margin-bottom: -1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active a,
|
||||
.ui-tabs .ui-tabs-nav li.ui-state-disabled a,
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-loading a {
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
|
||||
cursor: text;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
|
||||
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a {
|
||||
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-tabs .ui-tabs-panel {
|
||||
|
|
@ -801,7 +798,7 @@ body .ui-tooltip {
|
|||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #d8d8d8;
|
||||
background: #ffffff url(images/ui-bg_flat_100_ffffff_40x100.png) 50% 50% repeat-x;
|
||||
background: #ffffff url("images/ui-bg_flat_100_ffffff_40x100.png") 50% 50% repeat-x;
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
|
|
@ -809,7 +806,7 @@ body .ui-tooltip {
|
|||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #246060;
|
||||
background: #246060 url(images/ui-bg_flat_100_246060_40x100.png) 50% 50% repeat-x;
|
||||
background: #246060 url("images/ui-bg_flat_100_246060_40x100.png") 50% 50% repeat-x;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -823,7 +820,7 @@ body .ui-tooltip {
|
|||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default {
|
||||
border: 1px solid #d3d3d3;
|
||||
background: #ffffff url(images/ui-bg_flat_100_ffffff_40x100.png) 50% 50% repeat-x;
|
||||
background: #ffffff url("images/ui-bg_flat_100_ffffff_40x100.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #555555;
|
||||
}
|
||||
|
|
@ -840,14 +837,18 @@ body .ui-tooltip {
|
|||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus {
|
||||
border: 1px solid #49c0c1;
|
||||
background: #49c0c1 url(images/ui-bg_flat_100_49c0c1_40x100.png) 50% 50% repeat-x;
|
||||
background: #49c0c1 url("images/ui-bg_flat_100_49c0c1_40x100.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited {
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -855,14 +856,14 @@ body .ui-tooltip {
|
|||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #49c0c1 url(images/ui-bg_glass_65_49c0c1_1x400.png) 50% 50% repeat-x !important;
|
||||
background: #49c0c1 url("images/ui-bg_flat_65_49c0c1_40x100.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #212121;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
@ -872,7 +873,7 @@ body .ui-tooltip {
|
|||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #e8f8f9;
|
||||
background: #e8f8f9 url(images/ui-bg_flat_100_e8f8f9_40x100.png) 50% 50% repeat-x;
|
||||
background: #e8f8f9 url("images/ui-bg_flat_100_e8f8f9_40x100.png") 50% 50% repeat-x;
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
|
|
@ -884,7 +885,7 @@ body .ui-tooltip {
|
|||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f7474e;
|
||||
background: #f7474e url(images/ui-bg_flat_100_f7474e_40x100.png) 50% 50% repeat-x;
|
||||
background: #f7474e url("images/ui-bg_flat_100_f7474e_40x100.png") 50% 50% repeat-x;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-state-error a,
|
||||
|
|
@ -930,27 +931,27 @@ body .ui-tooltip {
|
|||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url(images/ui-icons_222222_256x240.png);
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url(images/ui-icons_ffffff_256x240.png);
|
||||
background-image: url("images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
.ui-state-default .ui-icon {
|
||||
background-image: url(images/ui-icons_ffffff_256x240.png);
|
||||
background-image: url("images/ui-icons_555555_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon {
|
||||
background-image: url(images/ui-icons_ffffff_256x240.png);
|
||||
background-image: url("images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon {
|
||||
background-image: url(images/ui-icons_454545_256x240.png);
|
||||
background-image: url("images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon {
|
||||
background-image: url(images/ui-icons_49c0c1_256x240.png);
|
||||
background-image: url("images/ui-icons_49c0c1_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url(images/ui-icons_ffffff_256x240.png);
|
||||
background-image: url("images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
|
|
@ -1140,37 +1141,37 @@ body .ui-tooltip {
|
|||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 3px;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;
|
||||
background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30);
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
margin: -8px 0 0 -8px;
|
||||
padding: 8px;
|
||||
background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;
|
||||
background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30);
|
||||
border-radius: 8px;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@
|
|||
<div class="nav-wrapper">
|
||||
<div class="inner">
|
||||
<a href="{% url 'wagtailadmin_home' %}" class="logo" title="Wagtail v.{% wagtail_version %}"><img src="{{ STATIC_URL }}wagtailadmin/images/wagtail-logo.svg" alt="Wagtail" width="80" /><span>{% trans "Dashboard" %}</span></a>
|
||||
|
||||
<form class="nav-search" action="{% url 'wagtailadmin_pages_search' %}" method="get">
|
||||
<div>
|
||||
<label for="menu-search-q">{% trans "Search" %}</label>
|
||||
<input type="text" id="menu-search-q" name="q" placeholder="{% trans 'Search' %}" />
|
||||
<button type="submit">{% trans "Search" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% main_nav %}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load i18n %}
|
||||
{% comment %}
|
||||
TODO: make it possible to specify button labels that are better tuned to the
|
||||
particular use case: e.g. "Choose an author", "Choose a location"
|
||||
{% endcomment %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ page.title }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block choose_another_button_label %}{% trans "Choose another page" %}{% endblock %}
|
||||
{% block choose_button_label %}{% trans "Choose a page" %}{% endblock %}
|
||||
{% block choose_another_button_label %}{{ choose_another_text_str }}{% endblock %}
|
||||
{% block choose_button_label %}{{ choose_one_text_str }}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
{% include "wagtailadmin/shared/breadcrumb.html" with page=parent_page %}
|
||||
</header>
|
||||
|
||||
|
||||
<form id="page-reorder-form">
|
||||
{% csrf_token %}
|
||||
|
||||
{% page_permissions parent_page as parent_page_perms %}
|
||||
{% include "wagtailadmin/pages/list.html" with sortable=1 allow_navigation=1 full_width=1 parent_page=parent_page orderable=parent_page_perms.can_reorder_children %}
|
||||
{% include "wagtailadmin/pages/list.html" with sortable=1 allow_navigation=1 full_width=1 parent_page=parent_page orderable=parent_page_perms.can_reorder_children %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -30,9 +30,10 @@
|
|||
var orderform = $('#page-reorder-form');
|
||||
|
||||
$('.listing tbody').sortable({
|
||||
cursor: "move",
|
||||
containment: "parent",
|
||||
handle: ".handle",
|
||||
cursor: "move",
|
||||
tolerance: "pointer",
|
||||
containment: "parent",
|
||||
handle: ".handle",
|
||||
items: "> tr",
|
||||
axis: "y",
|
||||
placeholder: "dropzone",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<ul class="filter-options">
|
||||
<li><a href="{% url 'wagtailimages_index' %}?q={{ query_string|urlencode }}" class="icon icon-image">{% trans "Images" %}</a></li>
|
||||
<li><a href="{% url 'wagtaildocs_index' %}?q={{ query_string|urlencode }}" class="icon icon-doc-full-inverse">{% trans "Documents" %}</a></li>
|
||||
<li><a href="{% url 'wagtailusers_index' %}?q={{ query_string|urlencode }}" class="icon icon-user">{% trans "Users" %}</a></li>
|
||||
<li><a href="{% url 'wagtailusers_users_index' %}?q={{ query_string|urlencode }}" class="icon icon-user">{% trans "Users" %}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
{% load i18n %}
|
||||
<nav class="nav-main">
|
||||
<ul>
|
||||
{% for menu_item in menu_items %}
|
||||
{{ menu_item.render_html }}
|
||||
{% endfor %}
|
||||
{{ menu_html }}
|
||||
|
||||
<li class="footer">
|
||||
<div class="avatar icon icon-user"><a href="{% url 'wagtailadmin_account' %}" title="{% trans 'Account settings' %}">{% if request.user.email %}<img src="{% gravatar_url request.user.email %}" />{% endif %}</a></div>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.conf import settings
|
||||
from django import template
|
||||
from django.forms import Media
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy, PageViewRestriction
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore
|
||||
from wagtail.wagtailadmin.menu import get_master_menu_item_list
|
||||
from wagtail.wagtailadmin.menu import admin_menu
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
|
@ -30,23 +29,15 @@ def explorer_subnav(nodes):
|
|||
@register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True)
|
||||
def main_nav(context):
|
||||
request = context['request']
|
||||
menu_items = [item for item in get_master_menu_item_list() if item.is_shown(request)]
|
||||
|
||||
for fn in hooks.get_hooks('construct_main_menu'):
|
||||
fn(request, menu_items)
|
||||
|
||||
return {
|
||||
'menu_items': sorted(menu_items, key=lambda i: i.order),
|
||||
'menu_html': admin_menu.render_html(request),
|
||||
'request': request,
|
||||
}
|
||||
|
||||
@register.simple_tag
|
||||
def main_nav_js():
|
||||
media = Media()
|
||||
for item in get_master_menu_item_list():
|
||||
media += item.media
|
||||
|
||||
return media['js']
|
||||
return admin_menu.media['js']
|
||||
|
||||
|
||||
@register.filter("ellipsistrim")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ from django.core import mail
|
|||
from django.core.paginator import Paginator
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail.tests.models import SimplePage, EventPage, EventPageCarouselItem, StandardIndex, BusinessIndex, BusinessChild, BusinessSubIndex, TaggedPage, Advert, AdvertPlacement
|
||||
from wagtail.tests.models import (
|
||||
SimplePage, EventPage, EventPageCarouselItem,
|
||||
StandardIndex, StandardChild,
|
||||
BusinessIndex, BusinessChild, BusinessSubIndex,
|
||||
TaggedPage, Advert, AdvertPlacement)
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailcore.models import Page, PageRevision
|
||||
from wagtail.wagtailcore.signals import page_published, page_unpublished
|
||||
|
|
@ -1483,11 +1487,14 @@ class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, add_subpage_url)
|
||||
|
||||
# add_subpage should give us the full set of page types to choose
|
||||
# add_subpage should give us choices of StandardChild, and BusinessIndex.
|
||||
# BusinessSubIndex and BusinessChild are not allowed
|
||||
response = self.client.get(add_subpage_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Standard Child')
|
||||
self.assertContains(response, 'Business Child')
|
||||
self.assertContains(response, StandardChild.get_verbose_name())
|
||||
self.assertContains(response, BusinessIndex.get_verbose_name())
|
||||
self.assertNotContains(response, BusinessSubIndex.get_verbose_name())
|
||||
self.assertNotContains(response, BusinessChild.get_verbose_name())
|
||||
|
||||
def test_business_subpage(self):
|
||||
add_subpage_url = reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, ))
|
||||
|
|
@ -1500,8 +1507,10 @@ class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
|
|||
# add_subpage should give us a cut-down set of page types to choose
|
||||
response = self.client.get(add_subpage_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'Standard Child')
|
||||
self.assertContains(response, 'Business Child')
|
||||
self.assertNotContains(response, StandardIndex.get_verbose_name())
|
||||
self.assertNotContains(response, StandardChild.get_verbose_name())
|
||||
self.assertContains(response, BusinessSubIndex.get_verbose_name())
|
||||
self.assertContains(response, BusinessChild.get_verbose_name())
|
||||
|
||||
def test_business_child_subpage(self):
|
||||
add_subpage_url = reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, ))
|
||||
|
|
@ -1516,12 +1525,16 @@ class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
|
|||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_cannot_add_invalid_subpage_type(self):
|
||||
# cannot add SimplePage as a child of BusinessIndex, as SimplePage is not present in subpage_types
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.business_index.id)))
|
||||
# cannot add StandardChild as a child of BusinessIndex, as StandardChild is not present in subpage_types
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'standardchild', self.business_index.id)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# likewise for BusinessChild which has an empty subpage_types list
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.business_child.id)))
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'standardchild', self.business_child.id)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# cannot add BusinessChild to StandardIndex, as BusinessChild restricts is parent page types
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'businesschild', self.standard_index.id)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# but we can add a BusinessChild to BusinessIndex
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def add_subpage(request, parent_page_id):
|
|||
if not parent_page.permissions_for_user(request.user).can_add_subpage():
|
||||
raise PermissionDenied
|
||||
|
||||
page_types = sorted(parent_page.clean_subpage_types(), key=lambda pagetype: pagetype.name.lower())
|
||||
page_types = sorted(parent_page.allowed_subpage_types(), key=lambda pagetype: pagetype.name.lower())
|
||||
|
||||
if len(page_types) == 1:
|
||||
# Only one page type is available - redirect straight to the create form rather than
|
||||
|
|
@ -136,7 +136,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
|
|||
raise Http404
|
||||
|
||||
# page must be in the list of allowed subpage types for this parent ID
|
||||
if content_type not in parent_page.clean_subpage_types():
|
||||
if content_type not in parent_page.allowed_subpage_types():
|
||||
raise PermissionDenied
|
||||
|
||||
page = page_class(owner=request.user)
|
||||
|
|
@ -634,8 +634,13 @@ def set_page_position(request, page_to_move_id):
|
|||
# so don't bother to catch InvalidMoveToDescendant
|
||||
|
||||
if position_page:
|
||||
# Move page into this position
|
||||
page_to_move.move(position_page, pos='left')
|
||||
# If the page has been moved to the right, insert it to the
|
||||
# right. If left, then left.
|
||||
old_position = list(parent_page.get_children()).index(page_to_move)
|
||||
if int(position) < old_position:
|
||||
page_to_move.move(position_page, pos='left')
|
||||
elif int(position) > old_position:
|
||||
page_to_move.move(position_page, pos='right')
|
||||
else:
|
||||
# Move page to end
|
||||
page_to_move.move(parent_page, pos='last-child')
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
from django.core import urlresolvers
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
from wagtail.wagtailadmin.menu import MenuItem, SubmenuMenuItem, settings_menu
|
||||
|
||||
|
||||
class ExplorerMenuItem(MenuItem):
|
||||
class Media:
|
||||
js = ['wagtailadmin/js/explorer-menu.js']
|
||||
|
||||
|
||||
@hooks.register('register_admin_menu_item')
|
||||
def register_explorer_menu_item():
|
||||
return ExplorerMenuItem(
|
||||
|
|
@ -17,8 +19,13 @@ def register_explorer_menu_item():
|
|||
attrs={'data-explorer-menu-url': urlresolvers.reverse('wagtailadmin_explorer_nav')},
|
||||
order=100)
|
||||
|
||||
|
||||
@hooks.register('register_admin_menu_item')
|
||||
def register_search_menu_item():
|
||||
return MenuItem(
|
||||
_('Search'), urlresolvers.reverse('wagtailadmin_pages_search'),
|
||||
classnames='icon icon-search', order=200)
|
||||
def register_settings_menu():
|
||||
return SubmenuMenuItem(
|
||||
_('Settings'), settings_menu, classnames='icon icon-cogs', order=10000)
|
||||
|
||||
|
||||
@hooks.register('register_permissions')
|
||||
def register_permissions():
|
||||
return Permission.objects.filter(content_type__app_label='wagtailadmin', codename='access_admin')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0002_initial_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='grouppagepermission',
|
||||
name='permission_type',
|
||||
field=models.CharField(max_length=20, choices=[(b'add', b'Add/edit pages you own'), (b'edit', b'Add/edit any page'), (b'publish', b'Publish any page')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='grouppagepermission',
|
||||
unique_together=set([('group', 'page', 'permission_type')]),
|
||||
),
|
||||
]
|
||||
|
|
@ -8,7 +8,7 @@ from six.moves.urllib.parse import urlparse
|
|||
from modelcluster.models import ClusterableModel, get_all_child_relations
|
||||
|
||||
from django.db import models, connection, transaction
|
||||
from django.db.models import get_model, Q
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.core.cache import cache
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
|
|
@ -20,13 +20,13 @@ from django.conf import settings
|
|||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore, resolve_model_string
|
||||
from wagtail.wagtailcore.query import PageQuerySet
|
||||
from wagtail.wagtailcore.url_routing import RouteResult
|
||||
|
||||
|
|
@ -58,25 +58,25 @@ class Site(models.Model):
|
|||
@staticmethod
|
||||
def find_for_request(request):
|
||||
"""
|
||||
Find the site object responsible for responding to this HTTP
|
||||
request object. Try:
|
||||
- unique hostname first
|
||||
- then hostname and port
|
||||
- if there is no matching hostname at all, or no matching
|
||||
hostname:port combination, fall back to the unique default site,
|
||||
or raise an exception
|
||||
NB this means that high-numbered ports on an extant hostname may
|
||||
still be routed to a different hostname which is set as the default
|
||||
Find the site object responsible for responding to this HTTP
|
||||
request object. Try:
|
||||
- unique hostname first
|
||||
- then hostname and port
|
||||
- if there is no matching hostname at all, or no matching
|
||||
hostname:port combination, fall back to the unique default site,
|
||||
or raise an exception
|
||||
NB this means that high-numbered ports on an extant hostname may
|
||||
still be routed to a different hostname which is set as the default
|
||||
"""
|
||||
try:
|
||||
hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause
|
||||
hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause
|
||||
try:
|
||||
# find a Site matching this specific hostname
|
||||
return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause
|
||||
return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause
|
||||
except Site.MultipleObjectsReturned:
|
||||
# as there were more than one, try matching by port too
|
||||
port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause
|
||||
return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause
|
||||
port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause
|
||||
return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause
|
||||
except (Site.DoesNotExist, KeyError):
|
||||
# If no matching site exists, or request does not specify an HTTP_HOST (which
|
||||
# will often be the case for the Django test client), look for a catch-all Site.
|
||||
|
|
@ -106,9 +106,9 @@ class Site(models.Model):
|
|||
raise ValidationError(
|
||||
{'is_default_site': [
|
||||
_("%(hostname)s is already configured as the default site. You must unset that before you can save this site as default.")
|
||||
% { 'hostname': default.hostname }
|
||||
]}
|
||||
)
|
||||
% {'hostname': default.hostname}
|
||||
]}
|
||||
)
|
||||
|
||||
# clear the wagtail_site_root_paths cache whenever Site records are updated
|
||||
def save(self, *args, **kwargs):
|
||||
|
|
@ -236,6 +236,7 @@ class PageBase(models.base.ModelBase):
|
|||
cls.ajax_template = None
|
||||
|
||||
cls._clean_subpage_types = None # to be filled in on first call to cls.clean_subpage_types
|
||||
cls._clean_parent_page_types = None # to be filled in on first call to cls.clean_parent_page_types
|
||||
|
||||
if not dct.get('is_abstract'):
|
||||
# subclasses are only abstract if the subclass itself defines itself so
|
||||
|
|
@ -267,7 +268,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
expired = models.BooleanField(default=False, editable=False)
|
||||
|
||||
search_fields = (
|
||||
index.SearchField('title', partial_match=True, boost=100),
|
||||
index.SearchField('title', partial_match=True, boost=2),
|
||||
index.FilterField('id'),
|
||||
index.FilterField('live'),
|
||||
index.FilterField('owner'),
|
||||
|
|
@ -354,8 +355,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
SET url_path = %s || substring(url_path from %s)
|
||||
WHERE path LIKE %s AND id <> %s
|
||||
"""
|
||||
cursor.execute(update_statement,
|
||||
[new_url_path, len(old_url_path) + 1, self.path + '%', self.id])
|
||||
cursor.execute(update_statement, [new_url_path, len(old_url_path) + 1, self.path + '%', self.id])
|
||||
|
||||
#: Return this page in its most specific subclassed form.
|
||||
@cached_property
|
||||
|
|
@ -504,8 +504,8 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
@classmethod
|
||||
def clean_subpage_types(cls):
|
||||
"""
|
||||
Returns the list of subpage types, with strings converted to class objects
|
||||
where required
|
||||
Returns the list of subpage types, with strings converted to class objects
|
||||
where required
|
||||
"""
|
||||
if cls._clean_subpage_types is None:
|
||||
subpage_types = getattr(cls, 'subpage_types', None)
|
||||
|
|
@ -513,43 +513,64 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
# if subpage_types is not specified on the Page class, allow all page types as subpages
|
||||
res = get_page_types()
|
||||
else:
|
||||
res = []
|
||||
for page_type in cls.subpage_types:
|
||||
if isinstance(page_type, string_types):
|
||||
try:
|
||||
app_label, model_name = page_type.split(".")
|
||||
except ValueError:
|
||||
# If we can't split, assume a model in current app
|
||||
app_label = cls._meta.app_label
|
||||
model_name = page_type
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if model:
|
||||
res.append(ContentType.objects.get_for_model(model))
|
||||
else:
|
||||
raise NameError(_("name '{0}' (used in subpage_types list) is not defined.").format(page_type))
|
||||
|
||||
else:
|
||||
# assume it's already a model class
|
||||
res.append(ContentType.objects.get_for_model(page_type))
|
||||
try:
|
||||
models = [resolve_model_string(model_string, cls._meta.app_label)
|
||||
for model_string in subpage_types]
|
||||
except LookupError as err:
|
||||
raise ImproperlyConfigured("{0}.subpage_types must be a list of 'app_label.model_name' strings, given {1!r}".format(
|
||||
cls.__name__, err.args[1]))
|
||||
res = list(map(ContentType.objects.get_for_model, models))
|
||||
|
||||
cls._clean_subpage_types = res
|
||||
|
||||
return cls._clean_subpage_types
|
||||
|
||||
@classmethod
|
||||
def allowed_parent_page_types(cls):
|
||||
def clean_parent_page_types(cls):
|
||||
"""
|
||||
Returns the list of page types that this page type can be a subpage of
|
||||
Returns the list of parent page types, with strings converted to class
|
||||
objects where required
|
||||
"""
|
||||
return [ct for ct in get_page_types() if cls in ct.model_class().clean_subpage_types()]
|
||||
if cls._clean_parent_page_types is None:
|
||||
parent_page_types = getattr(cls, 'parent_page_types', None)
|
||||
if parent_page_types is None:
|
||||
# if parent_page_types is not specified on the Page class, allow all page types as subpages
|
||||
res = get_page_types()
|
||||
else:
|
||||
try:
|
||||
models = [resolve_model_string(model_string, cls._meta.app_label)
|
||||
for model_string in parent_page_types]
|
||||
except LookupError as err:
|
||||
raise ImproperlyConfigured("{0}.parent_page_types must be a list of 'app_label.model_name' strings, given {1!r}".format(
|
||||
cls.__name__, err.args[1]))
|
||||
res = list(map(ContentType.objects.get_for_model, models))
|
||||
|
||||
cls._clean_parent_page_types = res
|
||||
|
||||
return cls._clean_parent_page_types
|
||||
|
||||
@classmethod
|
||||
def allowed_parent_pages(cls):
|
||||
def allowed_parent_page_types(cls):
|
||||
"""
|
||||
Returns the list of pages that this page type can be a subpage of
|
||||
Returns the list of page types that this page type can be a subpage of
|
||||
"""
|
||||
return Page.objects.filter(content_type__in=cls.allowed_parent_page_types())
|
||||
cls_ct = ContentType.objects.get_for_model(cls)
|
||||
return [ct for ct in cls.clean_parent_page_types()
|
||||
if cls_ct in ct.model_class().clean_subpage_types()]
|
||||
|
||||
@classmethod
|
||||
def allowed_subpage_types(cls):
|
||||
"""
|
||||
Returns the list of page types that this page type can be a subpage of
|
||||
"""
|
||||
# Special case the 'Page' class, such as the Root page or Home page -
|
||||
# otherwise you can not add initial pages when setting up a site
|
||||
if cls == Page:
|
||||
return get_page_types()
|
||||
|
||||
cls_ct = ContentType.objects.get_for_model(cls)
|
||||
return [ct for ct in cls.clean_subpage_types()
|
||||
if cls_ct in ct.model_class().clean_parent_page_types()]
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
|
|
@ -601,7 +622,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
new_self.save()
|
||||
new_self._update_descendant_url_paths(old_url_path, new_url_path)
|
||||
|
||||
def copy(self, recursive=False, to=None, update_attrs=None):
|
||||
def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True):
|
||||
# Make a copy
|
||||
page_copy = Page.objects.get(id=self.id).specific
|
||||
page_copy.pk = None
|
||||
|
|
@ -631,6 +652,15 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
setattr(child_object, parental_key_name, page_copy.id)
|
||||
child_object.save()
|
||||
|
||||
# Copy revisions
|
||||
if copy_revisions:
|
||||
for revision in self.revisions.all():
|
||||
revision.pk = None
|
||||
revision.submitted_for_moderation = False
|
||||
revision.approved_go_live_at = None
|
||||
revision.page = page_copy
|
||||
revision.save()
|
||||
|
||||
# Copy child pages
|
||||
if recursive:
|
||||
for child_page in self.get_children():
|
||||
|
|
@ -795,7 +825,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
|||
def get_navigation_menu_items():
|
||||
# Get all pages that appear in the navigation menu: ones which have children,
|
||||
# or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu)
|
||||
pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
|
||||
pages = Page.objects.filter(Q(depth=2) | Q(numchild__gt=0)).order_by('path')
|
||||
|
||||
# Turn this into a tree structure:
|
||||
# tree_node = (page, children)
|
||||
|
|
@ -910,9 +940,9 @@ class PageRevision(models.Model):
|
|||
|
||||
|
||||
PAGE_PERMISSION_TYPE_CHOICES = [
|
||||
('add', 'Add'),
|
||||
('edit', 'Edit'),
|
||||
('publish', 'Publish'),
|
||||
('add', 'Add/edit pages you own'),
|
||||
('edit', 'Add/edit any page'),
|
||||
('publish', 'Publish any page'),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -921,6 +951,9 @@ class GroupPagePermission(models.Model):
|
|||
page = models.ForeignKey('Page', related_name='group_permissions')
|
||||
permission_type = models.CharField(max_length=20, choices=PAGE_PERMISSION_TYPE_CHOICES)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('group', 'page', 'permission_type')
|
||||
|
||||
|
||||
class UserPagePermissionsProxy(object):
|
||||
"""Helper object that encapsulates all the page permission rules that this user has
|
||||
|
|
@ -1012,7 +1045,7 @@ class PagePermissionTester(object):
|
|||
self.user = user_perms.user
|
||||
self.user_perms = user_perms
|
||||
self.page = page
|
||||
self.page_is_root = page.depth == 1 # Equivalent to page.is_root()
|
||||
self.page_is_root = page.depth == 1 # Equivalent to page.is_root()
|
||||
|
||||
if self.user.is_active and not self.user.is_superuser:
|
||||
self.permissions = set(
|
||||
|
|
@ -1023,7 +1056,7 @@ class PagePermissionTester(object):
|
|||
def can_add_subpage(self):
|
||||
if not self.user.is_active:
|
||||
return False
|
||||
if not self.page.specific_class.clean_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
if not self.page.specific_class.allowed_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
return False
|
||||
return self.user.is_superuser or ('add' in self.permissions)
|
||||
|
||||
|
|
@ -1087,7 +1120,7 @@ class PagePermissionTester(object):
|
|||
"""
|
||||
if not self.user.is_active:
|
||||
return False
|
||||
if not self.page.specific_class.clean_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
if not self.page.specific_class.allowed_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
return False
|
||||
|
||||
return self.user.is_superuser or ('publish' in self.permissions)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.db.models import Q
|
||||
from django.db.models import Q, get_models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from treebeard.mp_tree import MP_NodeQuerySet
|
||||
|
||||
|
|
@ -152,13 +152,17 @@ class PageQuerySet(MP_NodeQuerySet):
|
|||
"""
|
||||
return self.exclude(self.sibling_of_q(other, inclusive))
|
||||
|
||||
def type_q(self, model):
|
||||
content_type = ContentType.objects.get_for_model(model)
|
||||
return Q(content_type=content_type)
|
||||
def type_q(self, klass):
|
||||
content_types = ContentType.objects.get_for_models(*[
|
||||
model for model in get_models()
|
||||
if issubclass(model, klass)
|
||||
]).values()
|
||||
|
||||
return Q(content_type__in=content_types)
|
||||
|
||||
def type(self, model):
|
||||
"""
|
||||
This filters the queryset to only contain pages that are an instance of the specified model.
|
||||
This filters the queryset to only contain pages that are an instance of the specified model (including subclasses).
|
||||
"""
|
||||
return self.filter(self.type_q(model))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type']
|
||||
db.create_unique('wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type']
|
||||
db.delete_unique('wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type'])
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'wagtailcore.grouppagepermission': {
|
||||
'Meta': {'unique_together': "(('group', 'page', 'permission_type'),)", 'object_name': 'GroupPagePermission'},
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': "orm['auth.Group']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': "orm['wagtailcore.Page']"}),
|
||||
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'})
|
||||
},
|
||||
'wagtailcore.page': {
|
||||
'Meta': {'object_name': 'Page'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': "orm['auth.User']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
|
||||
},
|
||||
'wagtailcore.pagerevision': {
|
||||
'Meta': {'object_name': 'PageRevision'},
|
||||
'approved_go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_json': ('django.db.models.fields.TextField', [], {}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['wagtailcore.Page']"}),
|
||||
'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'wagtailcore.pageviewrestriction': {
|
||||
'Meta': {'object_name': 'PageViewRestriction'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'view_restrictions'", 'to': "orm['wagtailcore.Page']"}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'wagtailcore.site': {
|
||||
'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'},
|
||||
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
|
||||
'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': "orm['wagtailcore.Page']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['wagtailcore']
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
import warnings
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.test.utils import override_settings
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage, PageWithOldStyleRouteMethod
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage, PageWithOldStyleRouteMethod, BusinessIndex, BusinessSubIndex, BusinessChild, StandardIndex
|
||||
|
||||
|
||||
class TestSiteRouting(TestCase):
|
||||
|
|
@ -413,6 +417,45 @@ class TestCopyPage(TestCase):
|
|||
self.assertEqual(new_christmas_event.advert_placements.count(), 1, "Child objects defined on the superclass weren't copied")
|
||||
self.assertEqual(christmas_event.advert_placements.count(), 1, "Child objects defined on the superclass were removed from the original page")
|
||||
|
||||
def test_copy_page_copies_revisions(self):
|
||||
christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
christmas_event.save_revision()
|
||||
|
||||
# Copy it
|
||||
new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'})
|
||||
|
||||
# Check that the revisions were copied
|
||||
self.assertEqual(new_christmas_event.revisions.count(), 1, "Revisions weren't copied")
|
||||
|
||||
# Check that the revisions weren't removed from old page
|
||||
self.assertEqual(christmas_event.revisions.count(), 1, "Revisions were removed from the original page")
|
||||
|
||||
def test_copy_page_copies_revisions_and_doesnt_submit_for_moderation(self):
|
||||
christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
christmas_event.save_revision(submitted_for_moderation=True)
|
||||
|
||||
# Copy it
|
||||
new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'})
|
||||
|
||||
# Check that the old revision is still submitted for moderation
|
||||
self.assertTrue(christmas_event.revisions.first().submitted_for_moderation)
|
||||
|
||||
# Check that the new revision is not submitted for moderation
|
||||
self.assertFalse(new_christmas_event.revisions.first().submitted_for_moderation)
|
||||
|
||||
def test_copy_page_copies_revisions_and_doesnt_schedule(self):
|
||||
christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
christmas_event.save_revision(approved_go_live_at=datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc))
|
||||
|
||||
# Copy it
|
||||
new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'})
|
||||
|
||||
# Check that the old revision is still scheduled
|
||||
self.assertEqual(christmas_event.revisions.first().approved_go_live_at, datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc))
|
||||
|
||||
# Check that the new revision is not scheduled
|
||||
self.assertEqual(new_christmas_event.revisions.first().approved_go_live_at, None)
|
||||
|
||||
def test_copy_page_copies_child_objects_with_nonspecific_class(self):
|
||||
# Get chrismas page as Page instead of EventPage
|
||||
christmas_event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
|
|
@ -458,3 +501,55 @@ class TestCopyPage(TestCase):
|
|||
|
||||
# Check that the speakers weren't removed from old page
|
||||
self.assertEqual(old_christmas_event.specific.speakers.count(), 1, "Child objects were removed from the original page")
|
||||
|
||||
def test_copy_page_copies_recursively_with_revisions(self):
|
||||
events_index = EventIndex.objects.get(url_path='/home/events/')
|
||||
old_christmas_event = events_index.get_children().filter(slug='christmas').first()
|
||||
old_christmas_event.save_revision()
|
||||
|
||||
# Copy it
|
||||
new_events_index = events_index.copy(recursive=True, update_attrs={'title': "New events index", 'slug': 'new-events-index'})
|
||||
|
||||
# Get christmas event
|
||||
new_christmas_event = new_events_index.get_children().filter(slug='christmas').first()
|
||||
|
||||
# Check that the revisions were copied
|
||||
self.assertEqual(new_christmas_event.specific.revisions.count(), 1, "Revisions weren't copied")
|
||||
|
||||
# Check that the revisions weren't removed from old page
|
||||
self.assertEqual(old_christmas_event.specific.revisions.count(), 1, "Revisions were removed from the original page")
|
||||
|
||||
|
||||
class TestSubpageTypeBusinessRules(TestCase):
|
||||
def test_allowed_subpage_types(self):
|
||||
# SimplePage does not define any restrictions on subpage types
|
||||
# SimplePage is a valid subpage of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(SimplePage), SimplePage.allowed_subpage_types())
|
||||
# BusinessIndex is a valid subpage of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessIndex), SimplePage.allowed_subpage_types())
|
||||
# BusinessSubIndex is not valid, because it explicitly omits SimplePage from parent_page_types
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessSubIndex), SimplePage.allowed_subpage_types())
|
||||
|
||||
# BusinessChild has an empty subpage_types list, so does not allow anything
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessIndex), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessSubIndex), BusinessChild.allowed_subpage_types())
|
||||
|
||||
# BusinessSubIndex only allows BusinessChild as subpage type
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessSubIndex.allowed_subpage_types())
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessChild), BusinessSubIndex.allowed_subpage_types())
|
||||
|
||||
def test_allowed_parent_page_types(self):
|
||||
# SimplePage does not define any restrictions on parent page types
|
||||
# SimplePage is a valid parent page of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(SimplePage), SimplePage.allowed_parent_page_types())
|
||||
# BusinessChild cannot be a parent of anything
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessChild), SimplePage.allowed_parent_page_types())
|
||||
|
||||
# StandardIndex does not allow anything as a parent
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), StandardIndex.allowed_parent_page_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(StandardIndex), StandardIndex.allowed_parent_page_types())
|
||||
|
||||
# BusinessSubIndex only allows BusinessIndex as a parent
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessSubIndex.allowed_parent_page_types())
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessIndex), BusinessSubIndex.allowed_parent_page_types())
|
||||
|
|
|
|||
|
|
@ -260,6 +260,19 @@ class TestPageQuerySet(TestCase):
|
|||
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_type_includes_subclasses(self):
|
||||
from wagtail.wagtailforms.models import AbstractEmailForm
|
||||
pages = Page.objects.type(AbstractEmailForm)
|
||||
|
||||
# Check that all objects are instances of AbstractEmailForm
|
||||
for page in pages:
|
||||
self.assertIsInstance(page.specific, AbstractEmailForm)
|
||||
|
||||
# Check that the contact form page is in the results
|
||||
contact_us = Page.objects.get(url_path='/home/contact-us/')
|
||||
self.assertTrue(pages.filter(id=contact_us.id).exists())
|
||||
|
||||
|
||||
def test_not_type(self):
|
||||
pages = Page.objects.not_type(EventPage)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,38 @@
|
|||
import re
|
||||
from django.db.models import Model, get_model
|
||||
from six import string_types
|
||||
|
||||
|
||||
def camelcase_to_underscore(str):
|
||||
# http://djangosnippets.org/snippets/585/
|
||||
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str).lower().strip('_')
|
||||
|
||||
|
||||
def resolve_model_string(model_string, default_app=None):
|
||||
"""
|
||||
Resolve an 'app_label.model_name' string into an actual model class.
|
||||
If a model class is passed in, just return that.
|
||||
"""
|
||||
if isinstance(model_string, string_types):
|
||||
try:
|
||||
app_label, model_name = model_string.split(".")
|
||||
except ValueError:
|
||||
if default_app is not None:
|
||||
# If we can't split, assume a model in current app
|
||||
app_label = default_app
|
||||
model_name = model_string
|
||||
else:
|
||||
raise ValueError("Can not resolve {0!r} into a model. Model names "
|
||||
"should be in the form app_label.model_name".format(
|
||||
model_string), model_string)
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if not model:
|
||||
raise LookupError("Can not resolve {0!r} into a model".format(model_string), model_string)
|
||||
return model
|
||||
|
||||
elif isinstance(model_string, type) and issubclass(model_string, Model):
|
||||
return model_string
|
||||
|
||||
else:
|
||||
raise LookupError("Can not resolve {0!r} into a model".format(model_string), model_string)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from django.core.urlresolvers import reverse
|
|||
from django.core.files.base import ContentFile
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
from wagtail.tests.models import EventPage, EventPageRelatedLink
|
||||
|
|
@ -432,7 +432,7 @@ class TestIssue613(TestCase, WagtailTestUtils):
|
|||
return get_search_backend(backend_name)
|
||||
else:
|
||||
# no conf entry found - skip tests for this backend
|
||||
raise unittest.SkipTest("No WAGTAILSEARCH_BACKENDS entry for the backend %s" % self.backend_path)
|
||||
raise unittest.SkipTest("No WAGTAILSEARCH_BACKENDS entry for the backend %s" % backend_path)
|
||||
|
||||
def setUp(self):
|
||||
self.search_backend = self.get_elasticsearch_backend()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ 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 django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
|
@ -44,3 +46,10 @@ def editor_js():
|
|||
""",
|
||||
urlresolvers.reverse('wagtaildocs_chooser')
|
||||
)
|
||||
|
||||
|
||||
@hooks.register('register_permissions')
|
||||
def register_permissions():
|
||||
document_content_type = ContentType.objects.get(app_label='wagtaildocs', model='document')
|
||||
document_permissions = Permission.objects.filter(content_type = document_content_type)
|
||||
return document_permissions
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
from .models import Embed
|
||||
from .embeds import get_embed
|
||||
|
||||
default_app_config = 'wagtail.wagtailembeds.apps.WagtailEmbedsAppConfig'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import division # Use true division
|
|||
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from wagtail.wagtailembeds import get_embed
|
||||
from wagtail.wagtailembeds.embeds import get_embed
|
||||
|
||||
|
||||
def embed_to_frontend_html(url):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import warnings
|
|||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from wagtail.wagtailembeds import get_embed
|
||||
from wagtail.wagtailembeds.embeds import get_embed
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@ from django.test import TestCase
|
|||
|
||||
from wagtail.tests.utils import WagtailTestUtils, unittest
|
||||
|
||||
from wagtail.wagtailembeds import get_embed
|
||||
from wagtail.wagtailembeds.embeds import (
|
||||
EmbedNotFoundException,
|
||||
EmbedlyException,
|
||||
AccessDeniedEmbedlyException,
|
||||
get_embed,
|
||||
embedly as wagtail_embedly,
|
||||
oembed as wagtail_oembed,
|
||||
)
|
||||
from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly, oembed as wagtail_oembed
|
||||
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import embed as embed_filter
|
||||
|
||||
|
||||
|
|
|
|||
BIN
wagtail/wagtailforms/locale/pt_PT/LC_MESSAGES/django.mo
Normal file
BIN
wagtail/wagtailforms/locale/pt_PT/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
136
wagtail/wagtailforms/locale/pt_PT/LC_MESSAGES/django.po
Normal file
136
wagtail/wagtailforms/locale/pt_PT/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Wagtail\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-09-11 16:38+0100\n"
|
||||
"PO-Revision-Date: 2014-09-17 17:32+0100\n"
|
||||
"Last-Translator: Jose Lourenco <jose@lourenco.ws>\n"
|
||||
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/"
|
||||
"wagtail/language/pt_PT/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pt_PT\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
|
||||
#: models.py:22
|
||||
msgid "Single line text"
|
||||
msgstr "Linha de texto"
|
||||
|
||||
#: models.py:23
|
||||
msgid "Multi-line text"
|
||||
msgstr "Caixa de linhas de texto"
|
||||
|
||||
#: models.py:24
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: models.py:25
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#: models.py:26
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: models.py:27
|
||||
msgid "Checkbox"
|
||||
msgstr "Caixa de seleção"
|
||||
|
||||
#: models.py:28
|
||||
msgid "Checkboxes"
|
||||
msgstr "Caixas de seleção"
|
||||
|
||||
#: models.py:29
|
||||
msgid "Drop down"
|
||||
msgstr "Lista pendente"
|
||||
|
||||
#: models.py:30
|
||||
msgid "Radio buttons"
|
||||
msgstr "Botões de seleção"
|
||||
|
||||
#: models.py:31
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
#: models.py:32
|
||||
msgid "Date/time"
|
||||
msgstr "Data/hora"
|
||||
|
||||
#: models.py:60
|
||||
msgid "The label of the form field"
|
||||
msgstr "O nome do campo do formulário"
|
||||
|
||||
#: models.py:67
|
||||
msgid ""
|
||||
"Comma seperated list of choices. Only applicable in checkboxes, radio and "
|
||||
"dropdown."
|
||||
msgstr ""
|
||||
"Lista de opções separadas por vírgula. Só utilizável nas caixas de seleção, "
|
||||
"botões e listas pendentes."
|
||||
|
||||
#: models.py:72
|
||||
msgid "Default value. Comma seperated values supported for checkboxes."
|
||||
msgstr ""
|
||||
"Valor pré-definido. Valores separados por vírgula admitidos nas caixas de "
|
||||
"seleção."
|
||||
|
||||
#: models.py:191
|
||||
msgid "Optional - form submissions will be emailed to this address"
|
||||
msgstr ""
|
||||
"Opcional - as submissões de formulários serão enviadas por email para este "
|
||||
"endereço"
|
||||
|
||||
#: wagtail_hooks.py:25 templates/wagtailforms/index.html:3
|
||||
#: templates/wagtailforms/index.html:6
|
||||
msgid "Forms"
|
||||
msgstr "Formulários"
|
||||
|
||||
#: templates/wagtailforms/index.html:7
|
||||
msgid "Pages"
|
||||
msgstr "Páginas"
|
||||
|
||||
#: templates/wagtailforms/index_submissions.html:3
|
||||
#, python-format
|
||||
msgid "Submissions of %(form_title)s"
|
||||
msgstr "Submissões de %(form_title)s"
|
||||
|
||||
#: templates/wagtailforms/index_submissions.html:36
|
||||
#, python-format
|
||||
msgid "Form data <span>%(form_title)s</span>"
|
||||
msgstr "Dados do formulário <span>%(form_title)s</span>"
|
||||
|
||||
#: templates/wagtailforms/index_submissions.html:45
|
||||
msgid "Filter"
|
||||
msgstr "Filtrar"
|
||||
|
||||
#: templates/wagtailforms/index_submissions.html:51
|
||||
msgid "Download CSV"
|
||||
msgstr "Descarregar CSV"
|
||||
|
||||
#: templates/wagtailforms/index_submissions.html:63
|
||||
#, python-format
|
||||
msgid "There have been no submissions of the '%(title)s' form."
|
||||
msgstr "Não houve submissões do formulário '%(title)s'."
|
||||
|
||||
#: templates/wagtailforms/list_forms.html:7
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: templates/wagtailforms/list_forms.html:8
|
||||
msgid "Origin"
|
||||
msgstr "Origem"
|
||||
|
||||
#: templates/wagtailforms/list_submissions.html:9
|
||||
msgid "Submission Date"
|
||||
msgstr "Data de submissão"
|
||||
|
||||
#: templates/wagtailforms/results_forms.html:7
|
||||
msgid "No form pages have been created."
|
||||
msgstr "Não foram criadas páginas de formulários."
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% blocktrans with form_title=form_page.title|capfirst %}Submissions of {{ form_title }}{% endblocktrans %}{% endblock %}
|
||||
{% block bodyclass %}menu-snippets{% endblock %}
|
||||
{% block bodyclass %}menu-forms{% endblock %}
|
||||
{% block extra_js %}
|
||||
{% include "wagtailadmin/shared/datetimepicker_translations.html" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""
|
||||
When the initial migration was created, the focal point fields on image
|
||||
did not have blank=True set.
|
||||
|
||||
This migration fixes this.
|
||||
"""
|
||||
|
||||
dependencies = [
|
||||
('wagtailimages', '0002_initial_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='focal_point_height',
|
||||
field=models.PositiveIntegerField(null=True, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='focal_point_width',
|
||||
field=models.PositiveIntegerField(null=True, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='focal_point_x',
|
||||
field=models.PositiveIntegerField(null=True, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='focal_point_y',
|
||||
field=models.PositiveIntegerField(null=True, blank=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailimages', '0003_fix_focal_point_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rendition',
|
||||
name='focal_point_key',
|
||||
field=models.CharField(blank=True, default='', max_length=255, editable=False),
|
||||
),
|
||||
]
|
||||
|
|
@ -143,7 +143,7 @@ class AbstractImage(models.Model, TagSearchable):
|
|||
else:
|
||||
rendition = self.renditions.get(
|
||||
filter=filter,
|
||||
focal_point_key=None,
|
||||
focal_point_key='',
|
||||
)
|
||||
except ObjectDoesNotExist:
|
||||
file_field = self.file
|
||||
|
|
@ -175,7 +175,6 @@ class AbstractImage(models.Model, TagSearchable):
|
|||
else:
|
||||
rendition, created = self.renditions.get_or_create(
|
||||
filter=filter,
|
||||
focal_point_key=None,
|
||||
defaults={'file': generated_image_file}
|
||||
)
|
||||
|
||||
|
|
@ -353,7 +352,7 @@ class AbstractRendition(models.Model):
|
|||
file = models.ImageField(upload_to='images', width_field='width', height_field='height')
|
||||
width = models.IntegerField(editable=False)
|
||||
height = models.IntegerField(editable=False)
|
||||
focal_point_key = models.CharField(max_length=255, null=True, editable=False)
|
||||
focal_point_key = models.CharField(max_length=255, blank=True, default='', editable=False)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Changing field 'Rendition.focal_point_key'
|
||||
db.alter_column('wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, default=''))
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Changing field 'Rendition.focal_point_key'
|
||||
db.alter_column('wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'wagtailimages.filter': {
|
||||
'Meta': {'object_name': 'Filter'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'spec': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
|
||||
},
|
||||
'wagtailimages.image': {
|
||||
'Meta': {'object_name': 'Image'},
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
|
||||
'focal_point_height': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'focal_point_width': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'focal_point_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'focal_point_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'height': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'uploaded_by_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'width': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
'wagtailimages.rendition': {
|
||||
'Meta': {'unique_together': "(('image', 'filter', 'focal_point_key'),)", 'object_name': 'Rendition'},
|
||||
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
|
||||
'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['wagtailimages.Filter']"}),
|
||||
'focal_point_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'height': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'renditions'", 'to': "orm['wagtailimages.Image']"}),
|
||||
'width': ('django.db.models.fields.IntegerField', [], {})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['wagtailimages']
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@import "../../wagtailadmin/static/wagtailadmin/scss/variables.scss";
|
||||
@import "../../wagtailadmin/static/wagtailadmin/scss/mixins.scss";
|
||||
@import "../../wagtailadmin/static/wagtailadmin/scss/grid.scss";
|
||||
@import "wagtailadmin/scss/variables.scss";
|
||||
@import "wagtailadmin/scss/mixins.scss";
|
||||
@import "wagtailadmin/scss/grid.scss";
|
||||
|
||||
.replace-file-input{
|
||||
display:inline-block;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import "../../wagtailadmin/static/wagtailadmin/scss/variables.scss";
|
||||
@import "../../wagtailadmin/static/wagtailadmin/scss/mixins.scss";
|
||||
@import "wagtailadmin/scss/variables.scss";
|
||||
@import "wagtailadmin/scss/mixins.scss";
|
||||
|
||||
.focal-point-chooser {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@
|
|||
{% csrf_token %}
|
||||
<ul class="fields">
|
||||
{% for field in uploadform %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
|
||||
{% if field.is_hidden %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<li><input type="submit" value="{% trans 'Upload' %}" /></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ from django.contrib.auth import get_user_model
|
|||
from django.contrib.auth.models import Group, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
from wagtail.wagtailimages.models import get_image_model, Rendition
|
||||
from wagtail.wagtailimages.formats import (
|
||||
Format,
|
||||
get_image_format,
|
||||
|
|
@ -781,9 +782,6 @@ class TestFrontendServeView(TestCase):
|
|||
self.assertEqual(response['Cache-Control'].split('=')[0], 'max-age')
|
||||
self.assertTrue(int(response['Cache-Control'].split('=')[1]) > datetime.timedelta(days=30).seconds)
|
||||
|
||||
self.assertIn('Expires', response)
|
||||
self.assertTrue(dateutil.parser.parse(response['Expires']) > timezone.now() + datetime.timedelta(days=30))
|
||||
|
||||
def test_get_invalid_signature(self):
|
||||
"""
|
||||
Test that an invalid signature returns a 403 response
|
||||
|
|
@ -985,7 +983,7 @@ class TestIssue613(TestCase, WagtailTestUtils):
|
|||
return get_search_backend(backend_name)
|
||||
else:
|
||||
# no conf entry found - skip tests for this backend
|
||||
raise unittest.SkipTest("No WAGTAILSEARCH_BACKENDS entry for the backend %s" % self.backend_path)
|
||||
raise unittest.SkipTest("No WAGTAILSEARCH_BACKENDS entry for the backend %s" % backend_path)
|
||||
|
||||
def setUp(self):
|
||||
self.search_backend = self.get_elasticsearch_backend()
|
||||
|
|
@ -1069,3 +1067,28 @@ class TestIssue613(TestCase, WagtailTestUtils):
|
|||
# Check
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].id, image.id)
|
||||
|
||||
|
||||
class TestIssue312(TestCase):
|
||||
def test_duplicate_renditions(self):
|
||||
# Create an image
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
# Get two renditions and check that they're the same
|
||||
rend1 = image.get_rendition('fill-100x100')
|
||||
rend2 = image.get_rendition('fill-100x100')
|
||||
self.assertEqual(rend1, rend2)
|
||||
|
||||
# Now manually duplicate the renditon and check that the database blocks it
|
||||
self.assertRaises(
|
||||
IntegrityError,
|
||||
Rendition.objects.create,
|
||||
image=rend1.image,
|
||||
filter=rend1.filter,
|
||||
width=rend1.width,
|
||||
height=rend1.height,
|
||||
focal_point_key=rend1.focal_point_key,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.cache import cache_control
|
||||
|
||||
from wagtail.wagtailimages.models import get_image_model, Filter
|
||||
from wagtail.wagtailimages.utils.crypto import verify_signature
|
||||
|
||||
|
||||
@cache_page(60 * 60 * 24 * 60) # Cache for 60 days
|
||||
@cache_control(max_age=60*60*24*60) # Cache for 60 days
|
||||
def serve(request, signature, image_id, filter_spec):
|
||||
image = get_object_or_404(get_image_model(), id=image_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ from django.core import urlresolvers
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
|
@ -87,3 +89,10 @@ def editor_js():
|
|||
""",
|
||||
urlresolvers.reverse('wagtailimages_chooser')
|
||||
)
|
||||
|
||||
|
||||
@hooks.register('register_permissions')
|
||||
def register_permissions():
|
||||
image_content_type = ContentType.objects.get(app_label='wagtailimages', model='image')
|
||||
image_permissions = Permission.objects.filter(content_type = image_content_type)
|
||||
return image_permissions
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ class RedirectsMenuItem(MenuItem):
|
|||
# TEMPORARY: Only show if the user is a superuser
|
||||
return request.user.is_superuser
|
||||
|
||||
@hooks.register('register_admin_menu_item')
|
||||
@hooks.register('register_settings_menu_item')
|
||||
def register_redirects_menu_item():
|
||||
return RedirectsMenuItem(_('Redirects'), urlresolvers.reverse('wagtailredirects_index'), classnames='icon icon-redirect', order=800)
|
||||
|
|
|
|||
|
|
@ -1,5 +1 @@
|
|||
from wagtail.wagtailsearch.index import Indexed
|
||||
from wagtail.wagtailsearch.signal_handlers import register_signal_handlers
|
||||
from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
||||
default_app_config = 'wagtail.wagtailsearch.apps.WagtailSearchAppConfig'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import absolute_import
|
|||
import json
|
||||
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from six import text_type
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.sql.where import SubqueryConstraint, WhereNode
|
||||
|
|
@ -140,7 +141,7 @@ class ElasticSearchQuery(object):
|
|||
|
||||
# Give error if the field doesn't exist
|
||||
if field is None:
|
||||
raise FieldError('Cannot filter ElasticSearch results with field "' + field_name + '". Please add FilterField(\'' + field_name + '\') to ' + self.queryset.model.__name__ + '.search_fields.')
|
||||
raise FieldError('Cannot filter ElasticSearch results with field "' + field_attname + '". Please add FilterField(\'' + field_attname + '\') to ' + self.queryset.model.__name__ + '.search_fields.')
|
||||
|
||||
# Get the name of the field in the index
|
||||
field_index_name = field.get_index_name(self.queryset.model)
|
||||
|
|
@ -206,11 +207,11 @@ class ElasticSearchQuery(object):
|
|||
if lookup == 'in':
|
||||
return {
|
||||
'terms': {
|
||||
field_index_name: value,
|
||||
field_index_name: list(value),
|
||||
}
|
||||
}
|
||||
|
||||
raise FilterError('Could not apply filter on ElasticSearch results: "' + field_name + '__' + lookup + ' = ' + unicode(value) + '". Lookup "' + lookup + '"" not recognosed.')
|
||||
raise FilterError('Could not apply filter on ElasticSearch results: "' + field_attname + '__' + lookup + ' = ' + text_type(value) + '". Lookup "' + lookup + '"" not recognised.')
|
||||
|
||||
def _get_filters_from_where(self, where_node):
|
||||
# Check if this is a leaf node
|
||||
|
|
@ -510,12 +511,12 @@ class ElasticSearch(BaseSearch):
|
|||
'ngram_analyzer': {
|
||||
'type': 'custom',
|
||||
'tokenizer': 'lowercase',
|
||||
'filter': ['ngram']
|
||||
'filter': ['asciifolding', 'ngram']
|
||||
},
|
||||
'edgengram_analyzer': {
|
||||
'type': 'custom',
|
||||
'tokenizer': 'lowercase',
|
||||
'filter': ['edgengram']
|
||||
'filter': ['asciifolding', 'edgengram']
|
||||
}
|
||||
},
|
||||
'tokenizer': {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.db import models
|
||||
|
||||
from wagtail.wagtailsearch import Indexed, get_search_backend
|
||||
from wagtail.wagtailsearch.indexed import Indexed
|
||||
from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
<legend>{% trans "Editors pick" %}</legend>
|
||||
<ul class="fields">
|
||||
<li class="model_choice_field">
|
||||
{% trans "Choose another page" as choose_another_text_str %}
|
||||
{% trans "Choose a page" as choose_one_text_str %}
|
||||
{% if form.instance.page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True only %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False only %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="char_field">
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ class BackendTests(WagtailTestUtils):
|
|||
# Should return two results
|
||||
self.assertEqual(len(results), 2)
|
||||
|
||||
def test_filters_with_in_lookup(self):
|
||||
live_page_titles = models.SearchTest.objects.filter(live=True).values_list('title', flat=True)
|
||||
results = self.backend.search("Hello", models.SearchTest, filters=dict(title__in=live_page_titles))
|
||||
|
||||
# Should return two results
|
||||
self.assertEqual(len(results), 2)
|
||||
|
||||
def test_single_result(self):
|
||||
# Get a single result
|
||||
result = self.backend.search("Hello", models.SearchTest)[0]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from wagtail.tests.utils import unittest
|
||||
import datetime
|
||||
import json
|
||||
|
|
@ -21,6 +24,19 @@ class TestElasticSearchBackend(BackendTests, TestCase):
|
|||
|
||||
# Didn't crash, yay!
|
||||
|
||||
def test_filter_on_non_filterindex_field(self):
|
||||
# id is not listed in the search_fields for SearchTest; this should raise a FieldError
|
||||
from wagtail.wagtailsearch.backends.elasticsearch import FieldError
|
||||
|
||||
with self.assertRaises(FieldError):
|
||||
results = list(self.backend.search("Hello", models.SearchTest, filters=dict(id=42)))
|
||||
|
||||
def test_filter_with_unsupported_lookup_type(self):
|
||||
from wagtail.wagtailsearch.backends.elasticsearch import FilterError
|
||||
|
||||
with self.assertRaises(FilterError):
|
||||
results = list(self.backend.search("Hello", models.SearchTest, filters=dict(title__iregex='h(ea)llo')))
|
||||
|
||||
def test_partial_search(self):
|
||||
# Reset the index
|
||||
self.backend.reset_index()
|
||||
|
|
@ -65,6 +81,28 @@ class TestElasticSearchBackend(BackendTests, TestCase):
|
|||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].id, obj.id)
|
||||
|
||||
def test_ascii_folding(self):
|
||||
# Reset the index
|
||||
self.backend.reset_index()
|
||||
self.backend.add_type(models.SearchTest)
|
||||
self.backend.add_type(models.SearchTestChild)
|
||||
|
||||
# Add some test data
|
||||
obj = models.SearchTest()
|
||||
obj.title = "Ĥéỻø"
|
||||
obj.live = True
|
||||
obj.save()
|
||||
self.backend.add(obj)
|
||||
|
||||
# Refresh the index
|
||||
self.backend.refresh_index()
|
||||
|
||||
# Search and check
|
||||
results = self.backend.search("Hello", models.SearchTest.objects.all())
|
||||
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].id, obj.id)
|
||||
|
||||
|
||||
class TestElasticSearchQuery(TestCase):
|
||||
def assertDictEqual(self, a, b):
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ class EditorsPicksMenuItem(MenuItem):
|
|||
# TEMPORARY: Only show if the user is a superuser
|
||||
return request.user.is_superuser
|
||||
|
||||
@hooks.register('register_admin_menu_item')
|
||||
@hooks.register('register_settings_menu_item')
|
||||
def register_editors_picks_menu_item():
|
||||
return EditorsPicksMenuItem(_('Editors picks'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900)
|
||||
return EditorsPicksMenuItem(_('Promoted search results'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900)
|
||||
|
|
|
|||
0
wagtail/wagtailsites/__init__.py
Normal file
0
wagtail/wagtailsites/__init__.py
Normal file
14
wagtail/wagtailsites/forms.py
Normal file
14
wagtail/wagtailsites/forms.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from django import forms
|
||||
|
||||
from wagtail.wagtailcore.models import Site
|
||||
|
||||
|
||||
class SiteForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SiteForm, self).__init__(*args, **kwargs)
|
||||
self.fields['root_page'].widget = forms.HiddenInput()
|
||||
|
||||
required_css_class = "required"
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
109
wagtail/wagtailsites/locale/en/LC_MESSAGES/django.po
Normal file
109
wagtail/wagtailsites/locale/en/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-10-03 16:23+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: views.py:31
|
||||
msgid "Site '{0}' created."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:34
|
||||
msgid "The site could not be created due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:51
|
||||
msgid "Site '{0}' updated."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:54
|
||||
msgid "The site could not be saved due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:70
|
||||
msgid "Site '{0}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:24 templates/wagtailsites/index.html:3
|
||||
#: templates/wagtailsites/index.html:5
|
||||
msgid "Sites"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/confirm_delete.html:3
|
||||
#: templates/wagtailsites/confirm_delete.html:7
|
||||
#: templates/wagtailsites/edit.html:36
|
||||
msgid "Delete site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/confirm_delete.html:11
|
||||
msgid "Are you sure you want to delete this site?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/confirm_delete.html:14
|
||||
msgid "Yes, delete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/create.html:4 templates/wagtailsites/create.html:9
|
||||
msgid "Add site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/create.html:20
|
||||
msgid "Choose a different root page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/create.html:21
|
||||
msgid "Choose a root page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/create.html:31 templates/wagtailsites/edit.html:34
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/edit.html:4 templates/wagtailsites/edit.html.py:9
|
||||
msgid "Editing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/edit.html:21
|
||||
msgid "Change page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/edit.html:22
|
||||
msgid "Choose page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:7
|
||||
msgid "Add a site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:19
|
||||
msgid "Site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:26
|
||||
msgid "Port"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:27
|
||||
msgid "Root page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:28
|
||||
msgid "Default?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailsites/index.html:41
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Delete site" %}{% endblock %}
|
||||
{% block bodyclass %}menu-site{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% trans "Delete site" as del_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=del_str subtitle=site.hostname icon="site" %}
|
||||
|
||||
<div class="row row-flush nice-padding">
|
||||
<p>{% trans "Are you sure you want to delete this site?" %}</p>
|
||||
<form action="{% url 'wagtailsites_delete' site.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="{% trans 'Yes, delete' %}" class="serious" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
wagtail/wagtailsites/templates/wagtailsites/create.html
Normal file
42
wagtail/wagtailsites/templates/wagtailsites/create.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block titletag %}{% trans "Add site" %} {{ site.hostname }}{% endblock %}
|
||||
{% block bodyclass %}menu-sites{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% trans "Add site" as add_site_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=add_site_str icon="site" %}
|
||||
|
||||
<form action="{% url 'wagtailsites_create' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="nice-padding">
|
||||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Choose a different root page" as choose_another_text_str %}
|
||||
{% trans "Choose a root page" as choose_one_text_str %}
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li><input type="submit" value="{% trans 'Save' %}" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block extra_js %}
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% include "wagtailsites/includes/site_form.js" %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
50
wagtail/wagtailsites/templates/wagtailsites/edit.html
Normal file
50
wagtail/wagtailsites/templates/wagtailsites/edit.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block titletag %}{% trans "Editing" %} {{ site.hostname }}{% endblock %}
|
||||
{% block bodyclass %}menu-sites{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% trans "Editing" as editing_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=site.hostname icon="site" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<form action="{% url 'wagtailsites_edit' site.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Change page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li>
|
||||
<input type="submit" value="{% trans 'Save' %}" />
|
||||
{% if perms.site.delete_site %}
|
||||
<a href="{% url 'wagtailsites_delete' site.id %}" class="button button-secondary no">{% trans "Delete site" %}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script>
|
||||
{% include "wagtailsites/includes/site_form.js" %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
(function() {
|
||||
function fixPrefix(str) { return str; }
|
||||
createPageChooser(fixPrefix('id_root_page'));
|
||||
|
||||
})();
|
||||
49
wagtail/wagtailsites/templates/wagtailsites/index.html
Normal file
49
wagtail/wagtailsites/templates/wagtailsites/index.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Sites" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Sites" as sites_str %}
|
||||
{% if perms.site.add_site %}
|
||||
{% trans "Add a site" as add_a_site_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=sites_str add_link="wagtailsites_create" add_text=add_a_site_str icon="site" %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=sites_str icon="site" %}
|
||||
{% endif %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<div id="sites-list">
|
||||
<table class="listing">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hostname">
|
||||
{% trans "Site" %}
|
||||
{% if ordering == "site" %}
|
||||
<a href="{% url 'wagtailsites_index' %}" class="icon icon-arrow-down-after teal"></a>
|
||||
{% else %}
|
||||
<a href="{% url 'wagtailsites_index' %}?ordering=name" class="icon icon-arrow-down-after"></a>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th class="port">{% trans "Port" %}</th>
|
||||
<th class="root-page">{% trans "Root page" %}</th>
|
||||
<th class="is-default-site">{% trans "Default?" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for site in sites %}
|
||||
<tr>
|
||||
<td class="hostname title">
|
||||
<h2>
|
||||
<a href="{% url 'wagtailsites_edit' site.id %}">{{ site.hostname }}</a>
|
||||
</h2>
|
||||
</td>
|
||||
<td class="port">{{ site.port }}</td>
|
||||
<td class="root-page">{{ site.root_page }}</td>
|
||||
<td class="is-default-site"><div class="status-tag {% if site.is_default_site %}primary{% endif %}">{% if site.is_default_site %}{% trans "Default" %}{% endif %}</div></td>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue