mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-18 04:01:11 +00:00
egads integrating with new docs
This commit is contained in:
commit
4527a58568
63 changed files with 1864 additions and 368 deletions
|
|
@ -5,7 +5,9 @@ Changelog
|
|||
~~~~~~~~~~~~~~~~
|
||||
* Added toolbar to allow logged-in users to add and edit pages from the site front-end
|
||||
* Support for alternative image processing backends such as Wand, via the WAGTAILIMAGES_BACKENDS setting
|
||||
* Added support for generating static sites using django-medusa
|
||||
* Added custom Query set for Pages with some handy methods for querying pages
|
||||
* Added 'wagtailforms' module for creating form pages on a site, and handling form submissions
|
||||
* Editor's guide documentation
|
||||
* Editor interface now outputs form media CSS / JS, to support custom widgets with assets
|
||||
* Migrations and user management now correctly handle custom AUTH_USER_MODEL settings
|
||||
|
|
@ -25,6 +27,7 @@ Changelog
|
|||
* Fix: Filter objects are cached to avoid a database hit every time an {% image %} tag is compiled
|
||||
* Fix: Moving or changing a site root page no longer causes URLs for subpages to change to 'None'
|
||||
* Fix: Eliminated raw SQL queries from wagtailcore / wagtailadmin, to ensure cross-database compatibility
|
||||
* Fix: Snippets menu item is hidden for administrators if no snippet types are defined
|
||||
|
||||
0.2 (11.03.2014)
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
3
docs/building_your_site/djangodevelopers.rst
Normal file
3
docs/building_your_site/djangodevelopers.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
For Django developers
|
||||
==================
|
||||
|
||||
127
docs/building_your_site/frontenddevelopers.rst
Normal file
127
docs/building_your_site/frontenddevelopers.rst
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
For Front End developers
|
||||
========================
|
||||
|
||||
.. note::
|
||||
This documentation is currently being written.
|
||||
|
||||
========================
|
||||
Overview
|
||||
========================
|
||||
|
||||
Wagtail uses Django's templating language. For developers new to Django, start with Django's own template documentation:
|
||||
https://docs.djangoproject.com/en/dev/topics/templates/
|
||||
|
||||
Python programmers new to Django/Wagtail may prefer more technical documentation:
|
||||
https://docs.djangoproject.com/en/dev/ref/templates/api/
|
||||
|
||||
========================
|
||||
Page content and variables
|
||||
========================
|
||||
|
||||
|
||||
========================
|
||||
Static files (css, js, images)
|
||||
========================
|
||||
|
||||
|
||||
|
||||
Images
|
||||
~~~~~~~~~~
|
||||
|
||||
Images uploaded to Wagtail go into the image library and from there are added to pages via the :doc:`page editor interface </editor_manual/new_pages/inserting_images>`.
|
||||
|
||||
Unlike other CMS, adding images to a page does not involve choosing a "version" of the image to use. Wagtail has no predefined image "formats" or "sizes". Instead the template developer defines image manipulation to occur *on the fly* when the image is requested, via a special syntax within the template.
|
||||
|
||||
Images from the library **must** be requested using this syntax, but images in your codebase can be added via conventional means e.g ``img`` tags. Only images from the library can be manipulated on the fly.
|
||||
|
||||
Read more about the image manipulation syntax here :ref:`Images tag <image-tag>`.
|
||||
|
||||
|
||||
========================
|
||||
Template tags & filters
|
||||
========================
|
||||
|
||||
In addition to Django's standard tags and filters, Wagtail provides some of it's own, which can be ``load``-ed `as you would any other <https://docs.djangoproject.com/en/dev/topics/templates/#custom-tag-and-filter-libraries>`_
|
||||
|
||||
.. _image-tag:
|
||||
Images (tag)
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The syntax for displaying/manipulating an image is thus::
|
||||
|
||||
{% image [image] [method]-[dimension(s)] %}
|
||||
|
||||
For example::
|
||||
|
||||
{% image self.photo width-400 %}
|
||||
|
||||
<!-- or a square thumbnail: -->
|
||||
{% image self.photo fill-80x80 %}
|
||||
|
||||
The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm.
|
||||
|
||||
Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``: a hyphen between ``method`` and ``dimensions`` is mandatory. Multiple dimensions must be separated by an ``x``.
|
||||
|
||||
The available ``method`` s are:
|
||||
|
||||
.. glossary::
|
||||
``max``
|
||||
(takes two dimensions)
|
||||
|
||||
Fit **within** the given dimensions.
|
||||
|
||||
The longest edge will be reduced to the equivalent dimension size defined. e.g A portrait image of width 1000, height 2000, treated with the ``max`` dimensions ``1000x500`` (landscape) would result in the image shrunk so the *height* was 500 pixels and the width 250.
|
||||
|
||||
``min``
|
||||
(takes two dimensions)
|
||||
|
||||
**Cover** the given dimensions.
|
||||
|
||||
This may result in an image slightly **larger** than the dimensions you specify. e.g A square image of width 2000, height 2000, treated with the ``min`` dimensions ``500x200`` (landscape) would have it's height and width changed to 500, i.e matching the width required, but greater than the height.
|
||||
|
||||
``width``
|
||||
(takes one dimension)
|
||||
|
||||
Reduces the width of the image to the dimension specified.
|
||||
|
||||
``height``
|
||||
(takes one dimension)
|
||||
|
||||
Resize the height of the image to the dimension specified..
|
||||
|
||||
``fill``
|
||||
(takes two dimensions)
|
||||
|
||||
Resize and **crop** to fill the **exact** dimensions.
|
||||
|
||||
This can be particularly useful for websites requiring square thumbnails of arbitrary images. e.g A landscape image of width 2000, height 1000, treated with ``fill`` dimensions ``200x200`` would have it's height reduced to 200, then it's width (ordinarily 400) cropped to 200.
|
||||
|
||||
**The crop always aligns on the centre of the image.**
|
||||
|
||||
.. Note::
|
||||
Wagtail *does not allow deforming or stretching images*. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions.
|
||||
|
||||
|
||||
To request the "original" version of an image, it is suggested you rely on the lack of upscalling support by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown, at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide.
|
||||
|
||||
|
||||
Rich text (filter)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Internal links (tag)
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Static files (tag)
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Misc
|
||||
~~~~~~~~~~
|
||||
|
||||
|
||||
========================
|
||||
Wagtail User Bar
|
||||
========================
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
Building your site
|
||||
==================
|
||||
|
||||
<<<<<<< HEAD:docs/building_your_site.rst
|
||||
Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the ``Page``, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the contruction of Django models using Wagtail's ``Page`` as a base.
|
||||
|
||||
Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn't prescribe ways to organize and interrelate your content, but here we've sketched out some strategies for organizing your models.
|
||||
|
|
@ -472,7 +473,20 @@ Wagtail's developers plan to move the site settings into the Wagtail admin inter
|
|||
|
||||
Example Site
|
||||
~~~~~~~~~~~~
|
||||
=======
|
||||
.. note::
|
||||
Documentation currently incomplete and in draft status
|
||||
>>>>>>> cde047f4850770ae0ffd2fcbf0580336bb6cf8ac:docs/building_your_site/index.rst
|
||||
|
||||
Serafeim Papastefanos has written a comprehensive tutorial on creating a site from scratch in Wagtail; for the time being, this is our recommended resource:
|
||||
|
||||
`spapas.github.io/2014/02/13/wagtail-tutorial/ <http://spapas.github.io/2014/02/13/wagtail-tutorial/>`_
|
||||
<<<<<<< HEAD:docs/building_your_site.rst
|
||||
=======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
djangodevelopers
|
||||
frontenddevelopers
|
||||
>>>>>>> cde047f4850770ae0ffd2fcbf0580336bb6cf8ac:docs/building_your_site/index.rst
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
Using Wagtail: an Editor's guide
|
||||
================================
|
||||
|
||||
This section of the documentation is written for the users of a Wagtail-powered site. That is, the content editors, moderators and administrators who will be running things on a day-to-day basis.
|
||||
.. note::
|
||||
Documentation currently incomplete and in draft status
|
||||
|
||||
**NOTE:** This section of the documentation is currently in draft status.
|
||||
This section of the documentation is written for the users of a Wagtail-powered site. That is, the content editors, moderators and administrators who will be running things on a day-to-day basis.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
|
|
|||
69
docs/form_builder.rst
Normal file
69
docs/form_builder.rst
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
Form builder
|
||||
============
|
||||
|
||||
The `wagtailforms` module allows you to set up single-page forms, such as a 'Contact us' form, as pages of a Wagtail site. It provides a set of base models that site implementors can extend to create their own 'Form' page type with their own site-specific templates. Once a page type has been set up in this way, editors can build forms within the usual page editor, consisting of any number of fields. Form submissions are stored for later retrieval through a new 'Forms' section within the Wagtail admin interface; in addition, they can be optionally e-mailed to an address specified by the editor.
|
||||
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
|
||||
Add 'wagtail.wagtailforms' to your INSTALLED_APPS:
|
||||
|
||||
.. code:: python
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
'wagtail.wagtailforms',
|
||||
]
|
||||
|
||||
Within the models.py of one of your apps, create a model that extends wagtailforms.models.AbstractEmailForm:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
|
||||
|
||||
class FormField(AbstractFormField):
|
||||
page = ParentalKey('FormPage', related_name='form_fields')
|
||||
|
||||
class FormPage(AbstractEmailForm):
|
||||
intro = RichTextField(blank=True)
|
||||
thank_you_text = RichTextField(blank=True)
|
||||
|
||||
FormPage.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('intro', classname="full"),
|
||||
InlinePanel(FormPage, 'form_fields', label="Form fields"),
|
||||
FieldPanel('thank_you_text', classname="full"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('to_address', classname="full"),
|
||||
FieldPanel('from_address', classname="full"),
|
||||
FieldPanel('subject', classname="full"),
|
||||
], "Email")
|
||||
]
|
||||
|
||||
AbstractEmailForm defines the fields 'to_address', 'from_address' and 'subject', and expects form_fields to be defined. Any additional fields are treated as ordinary page content - note that FormPage is responsible for serving both the form page itself and the landing page after submission, so the model definition should include all necessary content fields for both of those views.
|
||||
|
||||
If you do not want your form page type to offer form-to-email functionality, you can inherit from AbstractForm instead of AbstractEmailForm, and omit the 'to_address', 'from_address' and 'subject' fields from the content_panels definition.
|
||||
|
||||
You now need to create two templates named form_page.html and form_page_landing.html (where 'form_page' is the underscore-formatted version of the class name). form_page.html differs from a standard Wagtail template in that it is passed a variable 'form', containing a Django form object, in addition to the usual 'self' variable. A very basic template for the form would thus be:
|
||||
|
||||
.. code:: html
|
||||
|
||||
{% load pageurl rich_text %}
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ self.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ self.title }}</h1>
|
||||
{{ self.intro|richtext }}
|
||||
<form action="{% pageurl self %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
form_page_landing.html is a regular Wagtail template, displayed after the user makes a successful form submission.
|
||||
|
|
@ -4,7 +4,7 @@ Getting Started
|
|||
On Ubuntu
|
||||
~~~~~~~~~
|
||||
|
||||
If you have a fresh instance of Ubuntu 13.04 or 13.10, you can install Wagtail,
|
||||
If you have a fresh instance of Ubuntu 13.04 or later, you can install Wagtail,
|
||||
along with a demonstration site containing a set of standard templates and page
|
||||
types, in one step. As the root user::
|
||||
|
||||
|
|
@ -102,12 +102,17 @@ Required dependencies
|
|||
=====================
|
||||
|
||||
- `pip <https://github.com/pypa/pip>`_
|
||||
- `libjpeg <http://ijg.org/>`_
|
||||
- `libxml2 <http://xmlsoft.org/>`_
|
||||
- `libxslt <http://xmlsoft.org/XSLT/>`_
|
||||
- `zlib <http://www.zlib.net/>`_
|
||||
|
||||
Optional dependencies
|
||||
=====================
|
||||
|
||||
- `PostgreSQL`_
|
||||
- `Elasticsearch`_
|
||||
- `Redis`_
|
||||
|
||||
Installation
|
||||
============
|
||||
|
|
@ -137,6 +142,7 @@ with a regular Django project.
|
|||
.. _the Wagtail codebase: https://github.com/torchbox/wagtail
|
||||
.. _PostgreSQL: http://www.postgresql.org
|
||||
.. _Elasticsearch: http://www.elasticsearch.org
|
||||
.. _Redis: http://redis.io/
|
||||
|
||||
_`Remove the demo app`
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support
|
|||
:maxdepth: 3
|
||||
|
||||
gettingstarted
|
||||
building_your_site
|
||||
editing_api
|
||||
snippets
|
||||
building_your_site/index
|
||||
wagtail_search
|
||||
advanced_topics
|
||||
deploying
|
||||
performance
|
||||
form_builder
|
||||
static_site_generation
|
||||
contributing
|
||||
support
|
||||
roadmap
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ https://raw.github.com/torchbox/wagtail/master/CHANGELOG.txt
|
|||
|
||||
In summary:
|
||||
|
||||
* February 2013: Reduced dependencies, basic documentation, translations, tests
|
||||
* February 2014: Reduced dependencies, basic documentation, translations, tests
|
||||
|
||||
What's next
|
||||
~~~~~~~~~~~
|
||||
|
|
@ -19,12 +19,10 @@ The `issue list <https://github.com/torchbox/wagtail/issues>`_ gives a detailed
|
|||
|
||||
* More and better tests (>80% `coverage <https://coveralls.io/r/torchbox/wagtail>`_)
|
||||
* Better documentation: simple setup guides for all levels of user, a manual for editors and administrators, in-depth intstructions for Django developers.
|
||||
* A form builder
|
||||
* Move site section permissions out of Django admin
|
||||
* Improved image handling: intelligent cropping, animated gif support
|
||||
* Block-level editing UI (see `Sir Trevor <http://madebymany.github.io/sir-trevor-js/>`_)
|
||||
* Site settings management
|
||||
* Edit bird for logged-in visitors
|
||||
* Support for an HTML content type
|
||||
* Simple inline stats
|
||||
|
||||
|
|
|
|||
83
docs/static_site_generation.rst
Normal file
83
docs/static_site_generation.rst
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
Generating a static site
|
||||
========================
|
||||
|
||||
This document describes how to render your Wagtail site into static HTML files on your local filesystem, Amazon S3 or Google App Engine, using `django medusa`_ and the ``wagtail.contrib.wagtailmedusa`` module.
|
||||
|
||||
|
||||
Installing django-medusa
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First, install django medusa from pip:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install django-medusa
|
||||
|
||||
|
||||
Then add ``django_medusa`` and ``wagtail.contrib.wagtailmedusa`` to ``INSTALLED_APPS``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
'django_medusa',
|
||||
'wagtail.contrib.wagtailmedusa',
|
||||
]
|
||||
|
||||
|
||||
Replacing GET parameters with custom routing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pages which require GET parameters (e.g. for pagination) don't generate suitable filenames for generated HTML files so they need to be changed to use custom routing instead.
|
||||
|
||||
For example, let's say we have a Blog Index which uses pagination. We can override the ``route`` method to make it respond on urls like '/page/1', and pass the page number through to the ``serve`` method:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class BlogIndex(Page):
|
||||
...
|
||||
|
||||
def serve(self, request, page=1):
|
||||
...
|
||||
|
||||
def route(self, request, path_components):
|
||||
if self.live and len(path_components) == 2 and path_components[0] == 'page':
|
||||
try:
|
||||
return self.serve(request, page=int(path_components[1]))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return super(BlogIndex, self).route(request, path_components)
|
||||
|
||||
|
||||
Rendering pages which use custom routing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For page types that override the ``route`` method, we need to let django medusa know which URLs it responds on. This is done by overriding the ``get_static_site_paths`` method to make it yield one string per URL path.
|
||||
|
||||
For example, the BlogIndex above would need to yield one URL for each page of results:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def get_static_site_paths(self):
|
||||
# Get page count
|
||||
page_count = ...
|
||||
|
||||
# Yield a path for each page
|
||||
for page in range(page_count):
|
||||
yield '/%d/' % (page + 1)
|
||||
|
||||
# Yield from superclass
|
||||
for path in super(BlogIndex, self).get_static_site_paths():
|
||||
yield path
|
||||
|
||||
|
||||
Rendering
|
||||
~~~~~~~~~
|
||||
|
||||
To render a site, run ``./manage.py staticsitegen``. This will render the entire website and place the HTML in a folder called 'medusa_output'. The static and media folders need to be copied into this folder manually after the rendering is complete. This feature inherits django-medusa's ability to render your static site to Amazon S3 or Google App Engine; see the `medusa docs <https://github.com/mtigas/django-medusa/blob/master/README.markdown>`_ for configuration details.
|
||||
|
||||
To test, open the 'medusa_output' folder in a terminal and run ``python -m SimpleHTTPServer``.
|
||||
|
||||
|
||||
.. _django medusa: https://github.com/mtigas/django-medusa
|
||||
|
|
@ -80,6 +80,7 @@ if not settings.configured:
|
|||
'wagtail.wagtailembeds',
|
||||
'wagtail.wagtailsearch',
|
||||
'wagtail.wagtailredirects',
|
||||
'wagtail.wagtailforms',
|
||||
'wagtail.tests',
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Production-configured Wagtail installation
|
||||
# (secure services/account for full production use).
|
||||
# Production-configured Wagtail installation.
|
||||
# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE!
|
||||
# Tested on Debian 7.0.
|
||||
# Tom Dyson and Neal Todd
|
||||
|
||||
|
|
@ -42,6 +42,7 @@ aptitude -y install openjdk-7-jre-headless
|
|||
curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb
|
||||
dpkg -i elasticsearch-1.0.0.deb
|
||||
rm elasticsearch-1.0.0.deb
|
||||
perl -pi -e"s/# network.host: 192.168.0.1/network.host: 127.0.0.1/" /etc/elasticsearch/elasticsearch.yml
|
||||
update-rc.d elasticsearch defaults 95 10
|
||||
service elasticsearch start
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Production-configured Wagtail installation
|
||||
# (secure services/account for full production use).
|
||||
# Production-configured Wagtail installation.
|
||||
# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE!
|
||||
# Tested on Ubuntu 13.04 and 13.10.
|
||||
# Tom Dyson and Neal Todd
|
||||
|
||||
|
|
@ -40,6 +40,7 @@ aptitude -y install openjdk-7-jre-headless
|
|||
curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb
|
||||
dpkg -i elasticsearch-1.0.0.deb
|
||||
rm elasticsearch-1.0.0.deb
|
||||
perl -pi -e"s/# network.host: 192.168.0.1/network.host: 127.0.0.1/" /etc/elasticsearch/elasticsearch.yml
|
||||
update-rc.d elasticsearch defaults 95 10
|
||||
service elasticsearch start
|
||||
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -51,6 +51,7 @@ setup(
|
|||
"Pillow>=2.3.0",
|
||||
"beautifulsoup4>=4.3.2",
|
||||
"lxml>=3.3.0",
|
||||
'unicodecsv>=0.9.4',
|
||||
'Unidecode>=0.04.14',
|
||||
"BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed
|
||||
],
|
||||
|
|
|
|||
0
wagtail/contrib/__init__.py
Normal file
0
wagtail/contrib/__init__.py
Normal file
0
wagtail/contrib/wagtailmedusa/__init__.py
Normal file
0
wagtail/contrib/wagtailmedusa/__init__.py
Normal file
0
wagtail/contrib/wagtailmedusa/models.py
Normal file
0
wagtail/contrib/wagtailmedusa/models.py
Normal file
24
wagtail/contrib/wagtailmedusa/renderers.py
Normal file
24
wagtail/contrib/wagtailmedusa/renderers.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from django_medusa.renderers import StaticSiteRenderer
|
||||
from wagtail.wagtailcore.models import Site
|
||||
from wagtail.wagtaildocs.models import Document
|
||||
|
||||
|
||||
class PageRenderer(StaticSiteRenderer):
|
||||
def get_paths(self):
|
||||
# Get site
|
||||
# TODO: Find way to get this to work with other sites
|
||||
site = Site.objects.filter(is_default_site=True).first()
|
||||
if site is None:
|
||||
return []
|
||||
|
||||
# Return list of paths
|
||||
return site.root_page.get_static_site_paths()
|
||||
|
||||
|
||||
class DocumentRenderer(StaticSiteRenderer):
|
||||
def get_paths(self):
|
||||
# Return list of paths to documents
|
||||
return (doc.url for doc in Document.objects.all())
|
||||
|
||||
|
||||
renderers = [PageRenderer, DocumentRenderer]
|
||||
112
wagtail/tests/fixtures/test.json
vendored
112
wagtail/tests/fixtures/test.json
vendored
|
|
@ -23,7 +23,7 @@
|
|||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Welcome to the Wagtail test site!",
|
||||
"numchild": 1,
|
||||
"numchild": 3,
|
||||
"show_in_menus": false,
|
||||
"live": true,
|
||||
"depth": 2,
|
||||
|
|
@ -164,6 +164,57 @@
|
|||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Contact us",
|
||||
"numchild": 0,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 3,
|
||||
"content_type": ["tests", "formpage"],
|
||||
"path": "000100010003",
|
||||
"url_path": "/home/contact-us/",
|
||||
"slug": "contact-us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "tests.formpage",
|
||||
"fields": {
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "tests.formfield",
|
||||
"fields": {
|
||||
"sort_order": 1,
|
||||
"label": "Your email",
|
||||
"field_type": "email",
|
||||
"required": true,
|
||||
"choices": "",
|
||||
"default_value": "",
|
||||
"help_text": "",
|
||||
"page": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "tests.formfield",
|
||||
"fields": {
|
||||
"sort_order": 2,
|
||||
"label": "Your message",
|
||||
"field_type": "multiline",
|
||||
"required": true,
|
||||
"choices": "",
|
||||
"default_value": "",
|
||||
"help_text": "",
|
||||
"page": 8
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailcore.site",
|
||||
|
|
@ -201,6 +252,19 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Site-wide editors",
|
||||
"permissions": [
|
||||
["access_admin", "wagtailadmin", "admin"],
|
||||
["add_image", "wagtailimages", "image"],
|
||||
["change_image", "wagtailimages", "image"],
|
||||
["delete_image", "wagtailimages", "image"]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
|
|
@ -237,6 +301,15 @@
|
|||
"permission_type": "publish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Site-wide editors"],
|
||||
"page": 2,
|
||||
"permission_type": "edit"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
|
|
@ -308,5 +381,42 @@
|
|||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "inactiveuser@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "auth.user",
|
||||
"fields": {
|
||||
"username": "siteeditor",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": false,
|
||||
"groups": [
|
||||
["Site-wide editors"]
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "siteeditor@example.com"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailforms.formsubmission",
|
||||
"fields": {
|
||||
"form_data": "{\"your-email\": \"old@example.com\", \"your-message\": \"this is a really old message\"}",
|
||||
"page": 8,
|
||||
"submit_time": "2013-01-01T12:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "wagtailforms.formsubmission",
|
||||
"fields": {
|
||||
"form_data": "{\"your-email\": \"new@example.com\", \"your-message\": \"this is a fairly new message\"}",
|
||||
"page": 8,
|
||||
"submit_time": "2014-01-01T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from django.db import models
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from modelcluster.fields import ParentalKey
|
||||
from wagtail.wagtailcore.models import Page, Orderable
|
||||
from wagtail.wagtailcore.fields import RichTextField
|
||||
from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel, PageChooserPanel
|
||||
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
||||
from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel
|
||||
from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
|
||||
|
||||
|
||||
EVENT_AUDIENCE_CHOICES = (
|
||||
|
|
@ -187,12 +189,66 @@ class EventIndex(Page):
|
|||
intro = RichTextField(blank=True)
|
||||
ajax_template = 'tests/includes/event_listing.html'
|
||||
|
||||
def get_context(self, request):
|
||||
def get_events(self):
|
||||
return self.get_children().live().type(EventPage)
|
||||
|
||||
def get_paginator(self):
|
||||
return Paginator(self.get_events(), 4)
|
||||
|
||||
def get_context(self, request, page=1):
|
||||
# Pagination
|
||||
paginator = self.get_paginator()
|
||||
try:
|
||||
events = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
events = paginator.page(1)
|
||||
except EmptyPage:
|
||||
events = paginator.page(paginator.num_pages)
|
||||
|
||||
# Update context
|
||||
context = super(EventIndex, self).get_context(request)
|
||||
context['events'] = EventPage.objects.filter(live=True)
|
||||
context['events'] = events
|
||||
return context
|
||||
|
||||
def route(self, request, path_components):
|
||||
if self.live and len(path_components) == 1:
|
||||
try:
|
||||
return self.serve(request, page=int(path_components[0]))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return super(EventIndex, self).route(request, path_components)
|
||||
|
||||
def get_static_site_paths(self):
|
||||
# Get page count
|
||||
page_count = self.get_paginator().num_pages
|
||||
|
||||
# Yield a path for each page
|
||||
for page in range(page_count):
|
||||
yield '/%d/' % (page + 1)
|
||||
|
||||
# Yield from superclass
|
||||
for path in super(EventIndex, self).get_static_site_paths():
|
||||
yield path
|
||||
|
||||
EventIndex.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('intro', classname="full"),
|
||||
]
|
||||
|
||||
|
||||
class FormField(AbstractFormField):
|
||||
page = ParentalKey('FormPage', related_name='form_fields')
|
||||
|
||||
class FormPage(AbstractEmailForm):
|
||||
pass
|
||||
|
||||
FormPage.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
InlinePanel(FormPage, 'form_fields', label="Form fields"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('to_address', classname="full"),
|
||||
FieldPanel('from_address', classname="full"),
|
||||
FieldPanel('subject', classname="full"),
|
||||
], "Email")
|
||||
]
|
||||
|
|
|
|||
15
wagtail/tests/templates/tests/form_page.html
Normal file
15
wagtail/tests/templates/tests/form_page.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{% load pageurl %}
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ self.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ self.title }}</h1>
|
||||
<form action="{% pageurl self %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
11
wagtail/tests/templates/tests/form_page_landing.html
Normal file
11
wagtail/tests/templates/tests/form_page_landing.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{% load pageurl %}
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ self.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ self.title }}</h1>
|
||||
<p>Thank you for your feedback.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -128,7 +128,7 @@ $(function(){
|
|||
$(window.headerSearch.termInput).trigger('focus');
|
||||
|
||||
function search () {
|
||||
var workingClasses = "working icon icon-spinner";
|
||||
var workingClasses = "icon-spinner";
|
||||
|
||||
$(window.headerSearch.termInput).parent().addClass(workingClasses);
|
||||
search_next_index++;
|
||||
|
|
|
|||
|
|
@ -177,4 +177,9 @@ a.tag:hover{
|
|||
/* make a block-level element inline */
|
||||
.inline{
|
||||
display:inline;
|
||||
}
|
||||
|
||||
/* utility class to allow things to be scrollable if their contents can't wrap more nicely */
|
||||
.overflow{
|
||||
overflow:auto;
|
||||
}
|
||||
|
|
@ -21,22 +21,13 @@ legend{
|
|||
@include visuallyhidden();
|
||||
}
|
||||
|
||||
.fields li{
|
||||
padding-top:0.5em;
|
||||
padding-bottom:0.5em;
|
||||
}
|
||||
|
||||
.field{
|
||||
padding:0 0 0.6em 0;
|
||||
}
|
||||
|
||||
label{
|
||||
font-weight:bold;
|
||||
color:$color-grey-1;
|
||||
font-size:1.1em;
|
||||
display:block;
|
||||
padding:0 0 0.8em 0;
|
||||
line-height:1em;
|
||||
line-height:1.3em;
|
||||
|
||||
.checkbox &,
|
||||
.radio &{
|
||||
|
|
@ -47,10 +38,10 @@ label{
|
|||
input, textarea, select, .richtext, .tagit{
|
||||
@include border-radius(6px);
|
||||
@include border-box();
|
||||
font-family:Open Sans,Arial,sans-serif;
|
||||
width:100%;
|
||||
border:1px dashed $color-input-border;
|
||||
padding:1.2em;
|
||||
font-family:Open Sans,Arial,sans-serif;
|
||||
border:1px solid $color-input-border;
|
||||
padding:0.9em 1.2em;
|
||||
background-color:$color-fieldset-hover;
|
||||
-webkit-appearance: none;
|
||||
color:$color-text-input;
|
||||
|
|
@ -76,10 +67,44 @@ input, textarea, select, .richtext, .tagit{
|
|||
}
|
||||
}
|
||||
|
||||
input[type=radio],input[type=checkbox]{
|
||||
/* select boxes */
|
||||
.typed_choice_field .input{
|
||||
position:relative;
|
||||
|
||||
select{
|
||||
outline:none;
|
||||
}
|
||||
|
||||
&:after{
|
||||
@include border-radius(0 6px 6px 0);
|
||||
z-index:0;
|
||||
position:absolute;
|
||||
right:1px;
|
||||
top:1px;
|
||||
height:95%;
|
||||
width:1.5em;
|
||||
font-family:wagtail;
|
||||
content:"q";
|
||||
border:1px solid $color-input-border;
|
||||
border-width:0 0 0 1px;
|
||||
text-align:center;
|
||||
line-height:1.4em;
|
||||
font-size:3em;
|
||||
pointer-events:none;
|
||||
color:$color-grey-3;
|
||||
background-color:$color-fieldset-hover;
|
||||
margin:0px 1px 0 0;
|
||||
}
|
||||
|
||||
.ie &:after{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
||||
/* radio and check boxes */
|
||||
input[type=radio], input[type=checkbox]{
|
||||
@include border-radius(0);
|
||||
cursor:pointer;
|
||||
float:left;
|
||||
border:0;
|
||||
}
|
||||
|
||||
|
|
@ -350,18 +375,15 @@ button.icon{
|
|||
.help, .error-message{
|
||||
font-size:0.85em;
|
||||
font-weight:normal;
|
||||
margin:0 0 0.5em 0;
|
||||
margin:0.5em 0 0 0;
|
||||
}
|
||||
.error-message{
|
||||
color:$color-red;
|
||||
}
|
||||
.help{
|
||||
color:$color-grey-2;
|
||||
}
|
||||
|
||||
/* permanently show checkbox/radio help as they have no focus state */
|
||||
.boolean_field .help, .radio .help{
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
|
||||
fieldset:hover > .help,
|
||||
.field.focused + .help,
|
||||
.field:focus + .help,
|
||||
|
|
@ -380,18 +402,77 @@ li.focused > .help{
|
|||
font-size:13px;
|
||||
}
|
||||
|
||||
.error-message{
|
||||
margin:0;
|
||||
color:$color-red;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
.error input, .error textarea, .error select, .error .tagit{
|
||||
border-color:$color-red;
|
||||
background-color:$color-input-error-bg;
|
||||
}
|
||||
|
||||
/* Layouts for particular kinds of of fields */
|
||||
|
||||
/* permanently show checkbox/radio help as they have no focus state */
|
||||
.boolean_field .help, .radio .help{
|
||||
opacity:1;
|
||||
}
|
||||
.iconfield {
|
||||
position:relative;
|
||||
|
||||
input:not([type=radio]), input:not([type=checkbox]), input:not([type=submit]), input:not([type=button]){
|
||||
padding-left:2.5em;
|
||||
}
|
||||
|
||||
&:before, &:after{
|
||||
font-family:wagtail;
|
||||
position:absolute;
|
||||
top:0.4em;
|
||||
font-size:1.4em;
|
||||
color:$color-grey-3;
|
||||
}
|
||||
&:before{
|
||||
left:0.5em;
|
||||
}
|
||||
&:after{
|
||||
right:0.5em;
|
||||
}
|
||||
|
||||
/* special case for search spinners */
|
||||
&.icon-spinner:after{
|
||||
color:$color-teal;
|
||||
opacity:0.8;
|
||||
font-size:20px;
|
||||
width:20px;
|
||||
height:20px;
|
||||
line-height:23px;
|
||||
text-align:center;
|
||||
top:0.3em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.fields li{
|
||||
padding-top:0.5em;
|
||||
padding-bottom:1.2em;
|
||||
}
|
||||
|
||||
.field-content .input li{
|
||||
label{
|
||||
width:auto;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
|
||||
.input{
|
||||
clear:both;
|
||||
}
|
||||
|
||||
/* field sizing */
|
||||
|
||||
.field-small{
|
||||
input, textarea, select, .richtext, .tagit{
|
||||
@include border-radius(3px);
|
||||
padding:0.4em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.field{
|
||||
&.col1,
|
||||
&.col2,
|
||||
|
|
@ -453,7 +534,7 @@ ul.inline li:first-child, li.inline:first-child{
|
|||
display:block;
|
||||
float:left;
|
||||
color:$color-grey-3;
|
||||
line-height:0.85em;
|
||||
line-height:1em;
|
||||
font-size:2.5em;
|
||||
margin-right:0.3em;
|
||||
}
|
||||
|
|
@ -495,7 +576,6 @@ ul.inline li:first-child, li.inline:first-child{
|
|||
.unchosen, .chosen{
|
||||
&:before{
|
||||
content:"b";
|
||||
margin-left:-0.1em; /* this glyphs appear to have left padding, counteracted here */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -568,112 +648,6 @@ ul.tagit li.tagit-choice-editable{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* search bars (search integrated into header area) */
|
||||
.search-bar{
|
||||
margin-top:-2em;
|
||||
padding-top:1em;
|
||||
padding-bottom:1em;
|
||||
margin-bottom:2em;
|
||||
|
||||
&.full-width{
|
||||
@include nice-padding();
|
||||
background-color:$color-header-bg;
|
||||
border-bottom:1px solid $color-grey-4;
|
||||
}
|
||||
|
||||
label{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.fields{
|
||||
position:relative;
|
||||
clear:both;
|
||||
|
||||
.field input{
|
||||
padding-left:3em;
|
||||
|
||||
&:focus{
|
||||
background-color:white;
|
||||
}
|
||||
}
|
||||
.field:before, .field:after{
|
||||
font-family:wagtail;
|
||||
position:absolute;
|
||||
top:1em;
|
||||
font-size:25px;
|
||||
|
||||
}
|
||||
.field:before{
|
||||
left:0.5em;
|
||||
content:"f";
|
||||
color:$color-grey-3;
|
||||
}
|
||||
.field:after{
|
||||
color:$color-teal;
|
||||
opacity:0.8;
|
||||
font-size:20px;
|
||||
width:20px;
|
||||
height:20px;
|
||||
line-height:23px;
|
||||
text-align:center;
|
||||
top:0.3em;
|
||||
right:0.5em;
|
||||
}
|
||||
}
|
||||
.submit{
|
||||
display:none;
|
||||
position:absolute;
|
||||
right:0;
|
||||
top:0;
|
||||
input{
|
||||
padding:1.55em 2em;
|
||||
}
|
||||
}
|
||||
.taglist{
|
||||
font-size:0.9em;
|
||||
line-height:2.4em;
|
||||
h3{
|
||||
display:inline;
|
||||
}
|
||||
a{
|
||||
white-space: nowrap
|
||||
}
|
||||
}
|
||||
|
||||
&.small{
|
||||
margin:0;
|
||||
padding:0;
|
||||
.fields{
|
||||
li{
|
||||
padding:0;
|
||||
}
|
||||
.field{
|
||||
padding:0;
|
||||
}
|
||||
.field input{
|
||||
padding:0.4em 1.4em 0.4em 2em;
|
||||
|
||||
&:focus{
|
||||
background-color:white;
|
||||
}
|
||||
}
|
||||
.field:before{
|
||||
font-size:1.1rem;
|
||||
top:0.45em;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* mozilla specific hack */
|
||||
@-moz-document url-prefix() {
|
||||
.search-bar .fields .field:after{
|
||||
line-height:20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
fieldset, input, textarea, select{
|
||||
@include transition(background-color 0.2s ease);
|
||||
|
|
@ -686,11 +660,22 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
|
|||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
.help{
|
||||
opacity:1;
|
||||
}
|
||||
.fields{
|
||||
max-width:800px;
|
||||
label{
|
||||
@include column(2);
|
||||
padding-top:1.2em;
|
||||
padding-left:0;
|
||||
|
||||
.model_multiple_choice_field &,
|
||||
.boolean_field &,
|
||||
.model_choice_field &,
|
||||
.image_field &,
|
||||
.file_field &{
|
||||
padding-top:0;
|
||||
}
|
||||
|
||||
.boolean_field &{
|
||||
padding-bottom:0;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=submit], input[type=reset], input[type=button], .button, button{
|
||||
|
|
@ -706,4 +691,20 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help{
|
||||
opacity:1;
|
||||
}
|
||||
.fields{
|
||||
max-width:800px;
|
||||
}
|
||||
|
||||
.field{
|
||||
@include row();
|
||||
}
|
||||
|
||||
.field-content{
|
||||
@include column(10);
|
||||
padding-right:0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
header{
|
||||
padding-top:1em;
|
||||
padding-bottom:1em;
|
||||
background-color: $color-header-bg;
|
||||
margin-bottom:2em;
|
||||
color:white;
|
||||
|
||||
h1, h2{
|
||||
margin:0;
|
||||
color:white;
|
||||
}
|
||||
|
||||
h1{
|
||||
padding:0.2em 0;
|
||||
|
||||
&.icon:before{
|
||||
width:1em;
|
||||
display:none;
|
||||
margin-right:0.4em;
|
||||
font-size:1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.col{
|
||||
float:left;
|
||||
margin-right:2em;
|
||||
}
|
||||
.left{
|
||||
float:left;
|
||||
|
||||
.hasform &:first-child{
|
||||
padding-bottom:0.5em;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
.right{
|
||||
text-align:right;
|
||||
float:right;
|
||||
}
|
||||
|
||||
/* For case where content below header should merge with it */
|
||||
&.merged{
|
||||
margin-bottom:0;
|
||||
}
|
||||
&.tab-merged, &.no-border{
|
||||
border:0;
|
||||
}
|
||||
&.merged.no-border{
|
||||
padding-bottom:0;
|
||||
}
|
||||
&.no-v-padding{
|
||||
padding-top:0;
|
||||
padding-bottom:0;
|
||||
}
|
||||
/*
|
||||
&.hasform h1{
|
||||
margin-top:0.2em;
|
||||
}
|
||||
*/
|
||||
.button{
|
||||
background-color:$color-teal-darker;
|
||||
&:hover{
|
||||
background-color:$color-teal-dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* necessary on mobile only to make way for hamburger menu */
|
||||
&.nice-padding{
|
||||
padding-left:4em;
|
||||
}
|
||||
|
||||
label{
|
||||
@include visuallyhidden();
|
||||
}
|
||||
|
||||
input[type=text], select{
|
||||
border-width:0;
|
||||
|
||||
&:focus{
|
||||
background-color:white;
|
||||
}
|
||||
}
|
||||
|
||||
.fields{
|
||||
margin-top:-0.5em;
|
||||
li{
|
||||
padding-bottom:0;
|
||||
}
|
||||
.field{
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
.field-content{
|
||||
width:auto;
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
/* mozilla specific hack */
|
||||
@-moz-document url-prefix() {
|
||||
.iconfield.icon-spinner:after{
|
||||
line-height:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-explorer header{
|
||||
margin-bottom:0;
|
||||
padding-bottom:0em;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
header{
|
||||
padding-top:1.5em;
|
||||
padding-bottom:1.5em;
|
||||
|
||||
.left{
|
||||
float:left;
|
||||
margin-right:0;
|
||||
|
||||
&:first-child{
|
||||
padding-bottom:0;
|
||||
float:left;
|
||||
}
|
||||
}
|
||||
.second{
|
||||
clear:none;
|
||||
|
||||
.right, .left{
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
||||
h1.icon:before{
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.col3{
|
||||
@include column(3);
|
||||
}
|
||||
.col3.addbutton{
|
||||
width:auto;
|
||||
}
|
||||
.col6{
|
||||
@include column(6);
|
||||
}
|
||||
.col9{
|
||||
@include column(9);
|
||||
}
|
||||
.breadcrumb{
|
||||
margin-left:-($desktop-nice-padding);
|
||||
margin-right:-($desktop-nice-padding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,14 +231,20 @@
|
|||
.icon-collapse-up:before{
|
||||
content:"6";
|
||||
}
|
||||
.icon-date:before{
|
||||
content:"7";
|
||||
}
|
||||
.icon-success:before{
|
||||
content:"9";
|
||||
}
|
||||
.icon-help:before{
|
||||
content:"?";
|
||||
}
|
||||
.icon-warning:before{
|
||||
content:"!";
|
||||
}
|
||||
.icon-success:before{
|
||||
content:"9";
|
||||
.icon-form:before{
|
||||
content:"$";
|
||||
}
|
||||
|
||||
.icon.text-replace{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
@import "components/listing.scss";
|
||||
@import "components/messages.scss";
|
||||
@import "components/formatters.scss";
|
||||
@import "components/header.scss";
|
||||
|
||||
@import "fonts.scss";
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ img{
|
|||
}
|
||||
|
||||
.nav-wrapper{
|
||||
@include box-shadow(inset -2px 0px 10px 0px rgba(0, 0, 0, 0.5));
|
||||
@include box-shadow(inset -5px 0px 5px -3px rgba(0, 0, 0, 0.3));
|
||||
position:relative;
|
||||
background: $color-grey-1;
|
||||
margin-left: -100%;
|
||||
|
|
@ -385,88 +386,6 @@ body.explorer-open {
|
|||
}
|
||||
}
|
||||
|
||||
header{
|
||||
padding-top:1em;
|
||||
padding-bottom:1em;
|
||||
background-color: $color-header-bg;
|
||||
margin-bottom:2em;
|
||||
color:white;
|
||||
|
||||
h1, h2{
|
||||
margin:0;
|
||||
color:white;
|
||||
}
|
||||
|
||||
h1{
|
||||
padding:0.2em 0;
|
||||
|
||||
&.icon:before{
|
||||
width:1em;
|
||||
display:none;
|
||||
margin-right:0.4em;
|
||||
font-size:1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.col{
|
||||
float:left;
|
||||
margin-right:2em;
|
||||
}
|
||||
.left{
|
||||
float:left;
|
||||
|
||||
.hasform &:first-child{
|
||||
padding-bottom:0.5em;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
.search-bar input{
|
||||
@include border-radius(3px);
|
||||
width:auto;
|
||||
border-width:0;
|
||||
}
|
||||
.right{
|
||||
text-align:right;
|
||||
float:right;
|
||||
}
|
||||
|
||||
/* For case where content below header should merge with it */
|
||||
&.merged{
|
||||
margin-bottom:0;
|
||||
}
|
||||
&.tab-merged, &.no-border{
|
||||
border:0;
|
||||
}
|
||||
&.merged.no-border{
|
||||
padding-bottom:0;
|
||||
}
|
||||
&.no-v-padding{
|
||||
padding-top:0;
|
||||
padding-bottom:0;
|
||||
}
|
||||
/*
|
||||
&.hasform h1{
|
||||
margin-top:0.2em;
|
||||
}
|
||||
*/
|
||||
.button{
|
||||
background-color:$color-teal-darker;
|
||||
&:hover{
|
||||
background-color:$color-teal-dark;
|
||||
}
|
||||
}
|
||||
/* necessary on mobile only to make way for hamburger menu */
|
||||
&.nice-padding{
|
||||
padding-left:4em;
|
||||
}
|
||||
}
|
||||
|
||||
.page-explorer header{
|
||||
margin-bottom:0;
|
||||
padding-bottom:0em;
|
||||
}
|
||||
|
||||
|
||||
footer{
|
||||
@include row();
|
||||
@include border-radius(3px 3px 0 0);
|
||||
|
|
@ -832,52 +751,6 @@ footer, .logo{
|
|||
}
|
||||
}
|
||||
|
||||
header{
|
||||
padding-top:1.5em;
|
||||
padding-bottom:1.5em;
|
||||
|
||||
&.nice-padding{
|
||||
@include nice-padding();
|
||||
}
|
||||
|
||||
.left{
|
||||
float:left;
|
||||
margin-right:0;
|
||||
|
||||
&:first-child{
|
||||
padding-bottom:0;
|
||||
float:left;
|
||||
}
|
||||
}
|
||||
.second{
|
||||
clear:none;
|
||||
|
||||
.right, .left{
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
||||
h1.icon:before{
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.col3{
|
||||
@include column(3);
|
||||
}
|
||||
.col3.addbutton{
|
||||
width:auto;
|
||||
}
|
||||
.col6{
|
||||
@include column(6);
|
||||
}
|
||||
.col9{
|
||||
@include column(9);
|
||||
}
|
||||
.breadcrumb{
|
||||
margin-left:-($desktop-nice-padding);
|
||||
margin-right:-($desktop-nice-padding);
|
||||
}
|
||||
}
|
||||
footer{
|
||||
width:80%;
|
||||
margin-left:50px;
|
||||
|
|
@ -904,13 +777,8 @@ footer, .logo{
|
|||
.wrapper{
|
||||
max-width:$breakpoint-desktop-larger;
|
||||
}
|
||||
.nav-wrapper{
|
||||
@include box-shadow(inset -6px 0px 4px 0px rgba(0, 0, 0, 0.2));
|
||||
|
||||
.inner{
|
||||
background:$color-grey-1;
|
||||
@include box-shadow(inset -6px 0px 4px 0px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
.nav-wrapper .inner{
|
||||
background:$color-grey-1;
|
||||
}
|
||||
|
||||
footer{
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -67,6 +67,8 @@
|
|||
<glyph unicode="?" d="M253 492c65 0 120-22 167-67 46-45 70-100 72-165 0-65-22-121-68-167-45-47-100-71-165-73-65 0-121 22-167 68-47 45-71 100-72 165-1 65 21 121 67 167 46 47 101 71 166 72m-1-379c10 0 19 3 25 9 6 7 10 15 10 24 0 11-2 19-9 26-6 6-15 9-25 9 0 0-1 0-1 0-10 0-18-3-24-9-7-6-10-14-11-24 0-10 3-18 10-25 6-6 14-10 24-10 0 0 1 0 1 0m85 168c9 11 13 24 13 40 0 26-9 46-27 59-18 13-41 19-69 19-22 0-40-4-53-13-24-14-36-39-37-75 0 0 0-2 0-2 0 0 56 0 56 0 0 0 0 2 0 2 0 9 3 18 8 28 6 8 15 12 28 12 14 0 23-3 27-10 5-7 8-14 8-23 0-6-3-13-8-20-3-4-7-8-11-10 0 0-3-2-3-2-2-2-4-4-8-6-3-2-7-5-10-8-4-2-7-5-11-8-4-3-7-6-9-9-4-7-7-20-9-40 0 0 0-4 0-4 0 0 56 0 56 0 0 0 0 2 0 2 0 4 0 9 2 14 2 7 6 13 14 19 0 0 14 9 14 9 16 12 25 20 29 26"/>
|
||||
<glyph unicode="!" d="M499 65c4-6 4-12 0-18-3-5-8-8-15-8 0 0-457 0-457 0-6 0-11 3-14 8-4 6-4 12-1 18 0 0 228 400 228 400 3 6 8 9 16 9 7 0 12-3 15-9 0 0 228-400 228-400m-215 25c0 0 0 51 0 51 0 0-56 0-56 0 0 0 0-51 0-51 0 0 56 0 56 0m0 89c0 0 0 154 0 154 0 0-56 0-56 0 0 0 0-154 0-154 0 0 56 0 56 0"/>
|
||||
<glyph unicode="9" d="M256 512c-141 0-256-115-256-256 0-141 115-256 256-256 141 0 256 115 256 256 0 141-115 256-256 256z m-40-374l-117 118 45 45 72-73 154 154 45-45z"/>
|
||||
<glyph unicode="4" d="M256 309c8 0 15-1 22-4 7-3 13-6 18-11 5-5 9-11 11-17 3-7 5-14 5-21 0-7-2-14-5-21-2-6-6-12-11-17-5-5-11-8-18-11-7-3-14-4-22-4-8 0-15 1-22 4-7 3-13 6-18 11-5 5-9 11-11 17-3 7-5 14-5 21 0 7 2 14 5 21 2 6 6 12 11 17 5 5 11 8 18 11 7 3 14 4 22 4z m0-136c12 0 23 2 34 6 11 4 20 10 28 18 8 7 14 16 19 26 4 11 6 21 6 33 0 12-2 22-6 33-5 10-11 19-19 26-8 8-17 14-28 18-11 4-22 6-34 6-12 0-23-2-34-6-11-4-20-10-28-18-8-7-14-16-19-26-4-11-6-21-6-33 0-12 2-22 6-33 5-10 11-19 19-26 8-8 17-14 28-18 11-4 22-6 34-6z m0 194c18 0 35-2 51-6 16-4 31-10 45-17 14-6 26-14 37-22 11-9 21-17 29-25 7-8 13-16 17-23 5-8 7-14 7-18 0-4-2-10-7-18-4-7-10-15-17-23-8-8-18-16-29-25-11-8-23-16-37-22-14-7-29-13-45-17-16-4-33-6-51-6-18 0-35 2-51 6-17 4-31 10-45 17-14 6-26 14-37 22-11 9-21 17-29 25-7 8-13 16-17 23-5 8-7 14-7 18 0 4 2 10 7 18 4 7 10 15 17 23 8 8 18 16 29 25 11 8 23 16 37 22 14 7 28 13 45 17 16 4 33 6 51 6z"/>
|
||||
<glyph unicode="S" d="M435 186l0-117-358 0 0 117-68 0 0-151c0-19 15-35 34-35l426 0c19 0 34 16 34 35l0 151z m-185 12l-99 118c0 0-15 15 2 15 16 0 55 0 55 0 0 0 0 9 0 24 0 41 0 118 0 149 0 0-2 8 11 8 12 0 68 0 78 0 9 0 8-7 8-7 0-30 0-109 0-149 0-13 0-22 0-22 0 0 32 0 52 0 19 0 5-15 5-15 0 0-84-111-96-122-8-9-16 1-16 1z"/>
|
||||
<glyph unicode="4" d="M256 309c8 0 15-1 22-4 7-3 13-6 18-11 5-5 9-11 11-17 3-7 5-14 5-21 0-7-2-14-5-21-2-6-6-12-11-17-5-5-11-8-18-11-7-3-14-4-22-4-8 0-15 1-22 4-7 3-13 6-18 11-5 5-9 11-11 17-3 7-5 14-5 21 0 7 2 14 5 21 2 6 6 12 11 17 5 5 11 8 18 11 7 3 14 4 22 4z m0-136c12 0 23 2 34 6 11 4 20 10 28 18 8 7 14 16 19 26 4 11 6 21 6 33 0 12-2 22-6 33-5 10-11 19-19 26-8 8-17 14-28 18-11 4-22 6-34 6-12 0-23-2-34-6-11-4-20-10-28-18-8-7-14-16-19-26-4-11-6-21-6-33 0-12 2-22 6-33 5-10 11-19 19-26 8-8 17-14 28-18 11-4 22-6 34-6z m0 194c18 0 35-2 51-6 16-4 31-10 45-17 14-6 26-14 37-22 11-9 21-17 29-25 7-8 13-16 17-23 5-8 7-14 7-18 0-4-2-10-7-18-4-7-10-15-17-23-8-8-18-16-29-25-11-8-23-16-37-22-14-7-29-13-45-17-16-4-33-6-51-6-18 0-35 2-51 6-17 4-31 10-45 17-14 6-26 14-37 22-11 9-21 17-29 25-7 8-13 16-17 23-5 8-7 14-7 18 0 4 2 10 7 18 4 7 10 15 17 23 8 8 18 16 29 25 11 8 23 16 37 22 14 7 28 13 45 17 16 4 33 6 51 6z"/>
|
||||
<glyph unicode="7" d="M416 0l-320 0c-53 0-96 43-96 96l0 320c0 42 27 77 64 90l0-42c0-27 22-48 48-48 26 0 48 21 48 48l0 48 192 0 0-48c0-27 22-48 48-48 26 0 48 21 48 48l0 42c37-13 64-48 64-90l0-320c0-53-43-96-96-96z m32 352l-384 0 0-256c0-18 14-32 32-32l320 0c18 0 32 14 32 32z m-128-160l64 0 0-64-64 0z m0 96l64 0 0-64-64 0z m-96-96l64 0 0-64-64 0z m0 96l64 0 0-64-64 0z m-96-96l64 0 0-64-64 0z m0 96l64 0 0-64-64 0z m272 160c-9 0-16 7-16 16l0 48 32 0 0-48c0-9-7-16-16-16z m-288 0c-9 0-16 7-16 16l0 48 32 0 0-48c0-9-7-16-16-16z"/>
|
||||
<glyph unicode="$" d="M0 512l0-512 512 0 0 512z m157-431l-104 0 0 53 189 0 0-53z m274 269l-378 0 0 81 377 0 0-81z m-1-144l-377 0 0 80 377 0z"/>
|
||||
</font></defs></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -82,7 +82,7 @@ form{
|
|||
.field{
|
||||
padding:0;
|
||||
}
|
||||
.field.icon:before{
|
||||
.iconfield:before{
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
|
@ -168,24 +168,29 @@ form{
|
|||
font-size:4em;
|
||||
}
|
||||
|
||||
.field.icon:before{
|
||||
display:inline-block;
|
||||
position: absolute;
|
||||
color:$color-grey-4;
|
||||
border: 2px solid $color-grey-4;
|
||||
border-radius: 100%;
|
||||
width: 1em;
|
||||
padding: 0.3em;
|
||||
left: $desktop-nice-padding;
|
||||
margin-top: -1em;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.full{
|
||||
margin:0px (-$desktop-nice-padding);
|
||||
|
||||
input{
|
||||
padding-left:($desktop-nice-padding + 50px);
|
||||
.iconfield{
|
||||
&:before{
|
||||
display:inline-block;
|
||||
position: absolute;
|
||||
color:$color-grey-4;
|
||||
border: 2px solid $color-grey-4;
|
||||
border-radius: 100%;
|
||||
width: 1em;
|
||||
padding: 0.3em;
|
||||
left: $desktop-nice-padding;
|
||||
margin-top: -1.1rem;
|
||||
top: 50%;
|
||||
font-size:1.3rem;
|
||||
}
|
||||
|
||||
input{
|
||||
padding-left:($desktop-nice-padding + 50px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,6 @@
|
|||
display:block;
|
||||
float:none;
|
||||
|
||||
|
||||
.help{
|
||||
display:none;
|
||||
}
|
||||
|
|
@ -343,6 +342,10 @@ footer .preview{
|
|||
@include column(10);
|
||||
padding-left:0;
|
||||
padding-right:0;
|
||||
|
||||
fieldset{
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
|
||||
.object-help{
|
||||
|
|
@ -371,6 +374,12 @@ footer .preview{
|
|||
.field{
|
||||
padding:0;
|
||||
}
|
||||
.field-content{
|
||||
display: block;
|
||||
float: none;
|
||||
width: auto;
|
||||
padding: inherit;
|
||||
}
|
||||
}
|
||||
.multiple{
|
||||
@include column(10);
|
||||
|
|
@ -381,5 +390,12 @@ footer .preview{
|
|||
&.empty .add{
|
||||
margin:0 0 0 -50px;
|
||||
}
|
||||
|
||||
&.single-field label{
|
||||
display: block;
|
||||
float: none;
|
||||
width: auto;
|
||||
padding:auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,11 +87,13 @@ li, .home{
|
|||
.action{
|
||||
@include transition(background-color 0.2s ease, color 0.2s ease);
|
||||
background-color:$color-teal;
|
||||
color:white;
|
||||
color:$color-teal;
|
||||
|
||||
&:before{
|
||||
margin-right:0.4em;
|
||||
vertical-align:middle;
|
||||
font-size:1.7em;
|
||||
color:white;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ $color-grey-5: #fafafa;
|
|||
$color-thead-bg: $color-grey-5;
|
||||
$color-header-bg: $color-teal; // #ff6a58;
|
||||
$color-fieldset-hover: $color-grey-5;
|
||||
$color-input-border: $color-grey-3;
|
||||
$color-input-border: $color-grey-4;
|
||||
$color-input-focus: #f4fcfc;
|
||||
$color-input-error-bg: #feedee;
|
||||
$color-button: $color-teal;
|
||||
|
|
|
|||
|
|
@ -85,3 +85,16 @@ def send_notification(page_revision_id, notification, excluded_user_id):
|
|||
|
||||
# Send email
|
||||
send_mail(email_subject, email_content, from_email, email_addresses)
|
||||
|
||||
|
||||
@task
|
||||
def send_email_task(email_subject, email_content, email_addresses, from_email=None):
|
||||
if not from_email:
|
||||
if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'):
|
||||
from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL
|
||||
elif hasattr(settings, 'DEFAULT_FROM_EMAIL'):
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
else:
|
||||
from_email = 'webmaster@localhost'
|
||||
|
||||
send_mail(email_subject, email_content, from_email, email_addresses)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
<div class="field">
|
||||
{% if field_type != "boolean_field" %}{{ field.label_tag }}{% endif %}
|
||||
{% block form_field %}
|
||||
{{ field }}
|
||||
{% endblock %}
|
||||
{% if field_type = "boolean_field" %}{{ field.label_tag }}{% endif %}
|
||||
{{ field.label_tag }}
|
||||
<div class="field-content">
|
||||
<div class="input {{ input_classes }} ">
|
||||
{% block form_field %}
|
||||
{{ field }}
|
||||
{% endblock %}
|
||||
<span></span>
|
||||
</div>
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if field.errors %}
|
||||
<p class="error-message">
|
||||
{% for error in field.errors %}
|
||||
<span>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if field.errors %}
|
||||
<p class="error-message">
|
||||
{% for error in field.errors %}
|
||||
<span>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text }}</p>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -28,15 +28,19 @@
|
|||
|
||||
<ul class="fields">
|
||||
<li class="full">
|
||||
<div class="field icon icon-user">
|
||||
<div class="field">
|
||||
{{ form.username.label_tag }}
|
||||
{{ form.username }}
|
||||
<div class="input iconfield icon-user">
|
||||
{{ form.username }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="full">
|
||||
<div class="field icon icon-password">
|
||||
<div class="field">
|
||||
{{ form.password.label_tag }}
|
||||
{{ form.password }}
|
||||
<div class="input iconfield icon-password">
|
||||
{{ form.password }}
|
||||
</div>
|
||||
</div>
|
||||
{% if show_password_reset %}
|
||||
<p class="help"><a href="{% url 'django.contrib.auth.views.password_reset' %}">{% trans "Forgotten it?" %}</a></p>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
<li class="{{ field.css_classes }} {{ field|fieldtype }} {% if field.errors %}error{% endif %}">
|
||||
<li class="{{ field.css_classes }} {{ field|fieldtype }} {{ li_classes }} {% if field.errors %}error{% endif %}">
|
||||
<div class="field">
|
||||
{% if field|fieldtype != "boolean_field" %}{{ field.label_tag }}{% endif %}
|
||||
{% block form_field %}
|
||||
{{ field }}
|
||||
{% endblock %}
|
||||
{% if field|fieldtype = "boolean_field" %}{{ field.label_tag }}{% endif %}
|
||||
</div>
|
||||
{{ field.label_tag }}
|
||||
<div class="field-content">
|
||||
<div class="input {{ input_classes }} ">
|
||||
{% block form_field %}
|
||||
{{ field }}
|
||||
{% endblock %}
|
||||
<span></span>
|
||||
</div>
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if field.errors %}
|
||||
<p class="error-message">
|
||||
{% for error in field.errors %}
|
||||
<span>{{ error|escape }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text }}</p>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<p class="error-message">
|
||||
{% for error in field.errors %}
|
||||
<span>{{ error|escape }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
<h1 class="icon icon-{{ icon }}">{{ title }} <span>{{ subtitle }}</span></h1>
|
||||
</div>
|
||||
{% if search_url %}
|
||||
<form class="col search-bar small" action="{% url search_url %}" method="get">
|
||||
<form class="col search-form" action="{% url search_url %}" method="get">
|
||||
<ul class="fields">
|
||||
{% for field in search_form %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field input_classes="field-small iconfield icon-search" %}
|
||||
{% endfor %}
|
||||
<li class="submit icon icon-search"><input type="submit" value="Search" /></li>
|
||||
<li class="submit visuallyhidden"><input type="submit" value="Search" class="button" /></li>
|
||||
</ul>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
{% load i18n %}
|
||||
{% if not is_ajax %}
|
||||
{% comment %}
|
||||
HACK: This template expects to be passed a 'linkurl' parameter, containing a URL name
|
||||
that can be reverse-resolved by the {% url %} tag with no further parameters.
|
||||
Views that have parameters in their URL can work around this by passing a bogus
|
||||
(but non-blank) URL name, which will return an empty string and produce a final URL
|
||||
of the form "?q=123", implicitly preserving the current URL path.
|
||||
Using the {% url ... as ... %} form of the tag ensures that this fails silently,
|
||||
rather than throwing a NoReverseMatch exception.
|
||||
{% endcomment %}
|
||||
{% url linkurl as url_to_use %}
|
||||
{% endif %}
|
||||
<div class="pagination">
|
||||
<p>{% blocktrans with page_num=items.number total_pages=items.paginator.num_pages %}Page {{ page_num }} of {{ total_pages }}.{% endblocktrans %}</p>
|
||||
<ul>
|
||||
|
|
@ -7,11 +19,9 @@
|
|||
{% if is_ajax %}
|
||||
<a href="#" data-page="{{ items.previous_page_number }}" class="icon icon-arrow-left">{% trans 'Previous' %}</a>
|
||||
{% elif is_searching %}
|
||||
<a href="{% url linkurl %}?q={{ query_string|urlencode }}&p={{ items.previous_page_number }}" class="icon icon-arrow-left">{% trans 'Previous' %}</a>
|
||||
{% elif is_ajax %}
|
||||
|
||||
<a href="{{ url_to_use }}?q={{ query_string|urlencode }}&p={{ items.previous_page_number }}" class="icon icon-arrow-left">{% trans 'Previous' %}</a>
|
||||
{% else %}
|
||||
<a href="{% url linkurl %}?p={{ items.previous_page_number }}&ordering={{ ordering }}" class="icon icon-arrow-left">{% trans 'Previous' %}</a>
|
||||
<a href="{{ url_to_use }}?p={{ items.previous_page_number }}&ordering={{ ordering }}" class="icon icon-arrow-left">{% trans 'Previous' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
@ -20,9 +30,9 @@
|
|||
{% if is_ajax %}
|
||||
<a href="#" data-page="{{ items.next_page_number }}" class="icon icon-arrow-right-after">{% trans 'Next' %}</a>
|
||||
{% elif is_searching %}
|
||||
<a href="{% url linkurl %}?q={{ query_string|urlencode }}&p={{ items.next_page_number }}" class="icon icon-arrow-right-after">{% trans 'Next' %}</a>
|
||||
<a href="{{ url_to_use }}?q={{ query_string|urlencode }}&p={{ items.next_page_number }}" class="icon icon-arrow-right-after">{% trans 'Next' %}</a>
|
||||
{% else %}
|
||||
<a href="{% url linkurl %}?p={{ items.next_page_number }}&ordering={{ ordering }}" class="icon icon-arrow-right-after">{% trans 'Next' %}</a>
|
||||
<a href="{{ url_to_use }}?p={{ items.next_page_number }}&ordering={{ ordering }}" class="icon icon-arrow-right-after">{% trans 'Next' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<!doctype html>
|
||||
{% load compress %}
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <!--<![endif]-->
|
||||
<!--[if lt IE 7]> <html class="no-js ie lt-ie9 lt-ie8 lt-ie7" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js ie lt-ie9 lt-ie8" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js ie lt-ie9" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if IE 9]> <html class="no-js ie lt-ie10" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <![endif]-->
|
||||
<!--[if gt IE 9]><!--> <html class="no-js" lang="{{ LANGUAGE_CODE|default:"en-gb" }}"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from wagtail.wagtailadmin.menu import MenuItem
|
|||
from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy
|
||||
from wagtail.wagtailcore.util import camelcase_to_underscore
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,6 @@ def content_type_use(request, content_type_app_name, content_type_model_name):
|
|||
except EmptyPage:
|
||||
pages = paginator.page(paginator.num_pages)
|
||||
|
||||
print page_class
|
||||
|
||||
return render(request, 'wagtailadmin/pages/content_type_use.html', {
|
||||
'pages': pages,
|
||||
'app_name': content_type_app_name,
|
||||
|
|
|
|||
|
|
@ -622,6 +622,19 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
"""
|
||||
return self.serve(self.dummy_request())
|
||||
|
||||
def get_static_site_paths(self):
|
||||
"""
|
||||
This is a generator of URL paths to feed into a static site generator
|
||||
Override this if you would like to create static versions of subpages
|
||||
"""
|
||||
# Yield paths for this page
|
||||
yield '/'
|
||||
|
||||
# Yield paths for child pages
|
||||
for child in self.get_children().live():
|
||||
for path in child.specific.get_static_site_paths():
|
||||
yield '/' + child.slug + path
|
||||
|
||||
def get_ancestors(self, inclusive=False):
|
||||
return Page.objects.ancestor_of(self, inclusive)
|
||||
|
||||
|
|
@ -786,6 +799,37 @@ class UserPagePermissionsProxy(object):
|
|||
permission to perform specific tasks on the given page"""
|
||||
return PagePermissionTester(self, page)
|
||||
|
||||
def editable_pages(self):
|
||||
"""Return a queryset of the pages that this user has permission to edit"""
|
||||
# Deal with the trivial cases first...
|
||||
if not self.user.is_active:
|
||||
return Page.objects.none()
|
||||
if self.user.is_superuser:
|
||||
return Page.objects.all()
|
||||
|
||||
# Translate each of the user's permission rules into a Q-expression
|
||||
q_expressions = []
|
||||
for perm in self.permissions:
|
||||
if perm.permission_type == 'add':
|
||||
# user has edit permission on any subpage of perm.page
|
||||
# (including perm.page itself) that is owned by them
|
||||
q_expressions.append(
|
||||
Q(path__startswith=perm.page.path, owner=self.user)
|
||||
)
|
||||
elif perm.permission_type == 'edit':
|
||||
# user has edit permission on any subpage of perm.page
|
||||
# (including perm.page itself) regardless of owner
|
||||
q_expressions.append(
|
||||
Q(path__startswith=perm.page.path)
|
||||
)
|
||||
|
||||
if q_expressions:
|
||||
all_rules = q_expressions[0]
|
||||
for expr in q_expressions[1:]:
|
||||
all_rules = all_rules | expr
|
||||
return Page.objects.filter(all_rules)
|
||||
else:
|
||||
return Page.objects.none()
|
||||
|
||||
class PagePermissionTester(object):
|
||||
def __init__(self, user_perms, page):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from django.http import HttpRequest, Http404
|
|||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
|
|
@ -162,6 +162,47 @@ class TestServeView(TestCase):
|
|||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
|
||||
class TestStaticSitePaths(TestCase):
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(id=1)
|
||||
|
||||
# For simple tests
|
||||
self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home"))
|
||||
self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about"))
|
||||
self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact"))
|
||||
|
||||
# For custom tests
|
||||
self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
|
||||
for i in range(20):
|
||||
self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i)))
|
||||
|
||||
def test_local_static_site_paths(self):
|
||||
paths = list(self.about_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/'])
|
||||
|
||||
def test_child_static_site_paths(self):
|
||||
paths = list(self.home_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/', '/about/', '/contact/'])
|
||||
|
||||
def test_custom_static_site_paths(self):
|
||||
paths = list(self.event_index.get_static_site_paths())
|
||||
|
||||
# Event index path
|
||||
expected_paths = ['/']
|
||||
|
||||
# One path for each page of results
|
||||
expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])
|
||||
|
||||
# One path for each event page
|
||||
expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])
|
||||
|
||||
paths.sort()
|
||||
expected_paths.sort()
|
||||
self.assertEqual(paths, expected_paths)
|
||||
|
||||
|
||||
class TestPageUrlTags(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
|
|
@ -336,6 +377,62 @@ class TestPagePermission(TestCase):
|
|||
self.assertTrue(homepage_perms.can_move_to(root))
|
||||
self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
|
||||
|
||||
def test_editable_pages_for_user_with_add_permission(self):
|
||||
event_editor = User.objects.get(username='eventeditor')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_editor).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_user_with_edit_permission(self):
|
||||
event_moderator = User.objects.get(username='eventmoderator')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_moderator).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_inactive_user(self):
|
||||
user = User.objects.get(username='inactiveuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_superuser(self):
|
||||
user = User.objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertTrue(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
|
||||
class TestPageQuerySet(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
|
|
|||
0
wagtail/wagtailforms/__init__.py
Normal file
0
wagtail/wagtailforms/__init__.py
Normal file
87
wagtail/wagtailforms/forms.py
Normal file
87
wagtail/wagtailforms/forms.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import django.forms
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
class BaseForm(django.forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('label_suffix', '')
|
||||
return super(BaseForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class FormBuilder():
|
||||
formfields = SortedDict()
|
||||
|
||||
def __init__(self, fields):
|
||||
for field in fields:
|
||||
options = self.get_options(field)
|
||||
f = getattr(self, "create_"+field.field_type+"_field")(field, options)
|
||||
self.formfields[field.clean_name] = f
|
||||
|
||||
def get_options(self, field):
|
||||
options = {}
|
||||
options['label'] = field.label
|
||||
options['help_text'] = field.help_text
|
||||
options['required'] = field.required
|
||||
options['initial'] = field.default_value
|
||||
return options
|
||||
|
||||
def create_singleline_field(self, field, options):
|
||||
# TODO: This is a default value - it may need to be changed
|
||||
options['max_length'] = 255
|
||||
return django.forms.CharField(**options)
|
||||
|
||||
def create_multiline_field(self, field, options):
|
||||
return django.forms.CharField(widget=django.forms.Textarea, **options)
|
||||
|
||||
def create_date_field(self, field, options):
|
||||
return django.forms.DateField(**options)
|
||||
|
||||
def create_datetime_field(self, field, options):
|
||||
return django.forms.DateTimeField(**options)
|
||||
|
||||
def create_email_field(self, field, options):
|
||||
return django.forms.EmailField(**options)
|
||||
|
||||
def create_url_field(self, field, options):
|
||||
return django.forms.URLField(**options)
|
||||
|
||||
def create_number_field(self, field, options):
|
||||
return django.forms.DecimalField(**options)
|
||||
|
||||
def create_dropdown_field(self, field, options):
|
||||
options['choices'] = map(
|
||||
lambda x: (x.strip(), x.strip()),
|
||||
field.choices.split(',')
|
||||
)
|
||||
return django.forms.ChoiceField(**options)
|
||||
|
||||
def create_radio_field(self, field, options):
|
||||
options['choices'] = map(
|
||||
lambda x: (x.strip(), x.strip()),
|
||||
field.choices.split(',')
|
||||
)
|
||||
return django.forms.ChoiceField(widget=django.forms.RadioSelect, **options)
|
||||
|
||||
def create_checkboxes_field(self, field, options):
|
||||
options['choices'] = [(x.strip(), x.strip()) for x in field.choices.split(',')]
|
||||
options['initial'] = [x.strip() for x in field.default_value.split(',')]
|
||||
return django.forms.MultipleChoiceField(
|
||||
widget=django.forms.CheckboxSelectMultiple, **options
|
||||
)
|
||||
|
||||
def create_checkbox_field(self, field, options):
|
||||
return django.forms.BooleanField(**options)
|
||||
|
||||
def get_form_class(self):
|
||||
return type('WagtailForm', (BaseForm,), self.formfields)
|
||||
|
||||
|
||||
class SelectDateForm(django.forms.Form):
|
||||
date_from = django.forms.DateField(
|
||||
required=False,
|
||||
widget=django.forms.DateInput(attrs={'placeholder': 'Date from'})
|
||||
)
|
||||
date_to = django.forms.DateField(
|
||||
required=False,
|
||||
widget=django.forms.DateInput(attrs={'placeholder': 'Date to'})
|
||||
)
|
||||
95
wagtail/wagtailforms/migrations/0001_initial.py
Normal file
95
wagtail/wagtailforms/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
from wagtail.wagtailcore.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
depends_on = (
|
||||
("wagtailcore", "0002_initial_data"),
|
||||
)
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'FormSubmission'
|
||||
db.create_table(u'wagtailforms_formsubmission', (
|
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('form_data', self.gf('django.db.models.fields.TextField')()),
|
||||
('page', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wagtailcore.Page'])),
|
||||
('submit_time', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal(u'wagtailforms', ['FormSubmission'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'FormSubmission'
|
||||
db.delete_table(u'wagtailforms_formsubmission')
|
||||
|
||||
|
||||
models = {
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
AUTH_USER_MODEL: {
|
||||
'Meta': {'object_name': AUTH_USER_MODEL_NAME},
|
||||
'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': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
|
||||
u'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': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
u'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'}),
|
||||
u'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'})
|
||||
},
|
||||
u'wagtailcore.page': {
|
||||
'Meta': {'object_name': 'Page'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': u"orm['contenttypes.ContentType']"}),
|
||||
'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'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': u"orm['%s']" % AUTH_USER_MODEL}),
|
||||
'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'})
|
||||
},
|
||||
u'wagtailforms.formsubmission': {
|
||||
'Meta': {'object_name': 'FormSubmission'},
|
||||
'form_data': ('django.db.models.fields.TextField', [], {}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wagtailcore.Page']"}),
|
||||
'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['wagtailforms']
|
||||
0
wagtail/wagtailforms/migrations/__init__.py
Normal file
0
wagtail/wagtailforms/migrations/__init__.py
Normal file
200
wagtail/wagtailforms/models.py
Normal file
200
wagtail/wagtailforms/models.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
from django.db import models
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.text import slugify
|
||||
|
||||
from unidecode import unidecode
|
||||
import json
|
||||
import re
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types
|
||||
from wagtail.wagtailadmin.edit_handlers import FieldPanel
|
||||
from wagtail.wagtailadmin import tasks
|
||||
|
||||
from .forms import FormBuilder
|
||||
|
||||
|
||||
FORM_FIELD_CHOICES = (
|
||||
('singleline', _('Single line text')),
|
||||
('multiline', _('Multi-line text')),
|
||||
('email', _('Email')),
|
||||
('number', _('Number')),
|
||||
('url', _('URL')),
|
||||
('checkbox', _('Checkbox')),
|
||||
('checkboxes', _('Checkboxes')),
|
||||
('dropdown', _('Drop down')),
|
||||
('radio', _('Radio buttons')),
|
||||
('date', _('Date')),
|
||||
('datetime', _('Date/time')),
|
||||
)
|
||||
|
||||
|
||||
HTML_EXTENSION_RE = re.compile(r"(.*)\.html")
|
||||
|
||||
|
||||
class FormSubmission(models.Model):
|
||||
"""Data for a Form submission."""
|
||||
|
||||
form_data = models.TextField()
|
||||
page = models.ForeignKey(Page)
|
||||
|
||||
submit_time = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def get_data(self):
|
||||
return json.loads(self.form_data)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.form_data
|
||||
|
||||
|
||||
class AbstractFormField(Orderable):
|
||||
"""Database Fields required for building a Django Form field."""
|
||||
|
||||
label = models.CharField(
|
||||
max_length=255,
|
||||
help_text=_('The label of the form field')
|
||||
)
|
||||
field_type = models.CharField(max_length=16, choices=FORM_FIELD_CHOICES)
|
||||
required = models.BooleanField(default=True)
|
||||
choices = models.CharField(
|
||||
max_length=512,
|
||||
blank=True,
|
||||
help_text=_('Comma seperated list of choices. Only applicable in checkboxes, radio and dropdown.')
|
||||
)
|
||||
default_value = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text=_('Default value. Comma seperated values supported for checkboxes.')
|
||||
)
|
||||
help_text = models.CharField(max_length=255, blank=True)
|
||||
|
||||
@property
|
||||
def clean_name(self):
|
||||
# unidecode will return an ascii string while slugify wants a
|
||||
# unicode string on the other hand, slugify returns a safe-string
|
||||
# which will be converted to a normal str
|
||||
return str(slugify(unicode(unidecode(self.label))))
|
||||
|
||||
panels = [
|
||||
FieldPanel('label'),
|
||||
FieldPanel('help_text'),
|
||||
FieldPanel('required'),
|
||||
FieldPanel('field_type', classname="formbuilder-type"),
|
||||
FieldPanel('choices', classname="formbuilder-choices"),
|
||||
FieldPanel('default_value', classname="formbuilder-default"),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ['sort_order']
|
||||
|
||||
|
||||
_FORM_CONTENT_TYPES = None
|
||||
|
||||
def get_form_types():
|
||||
global _FORM_CONTENT_TYPES
|
||||
if _FORM_CONTENT_TYPES is None:
|
||||
_FORM_CONTENT_TYPES = [
|
||||
ct for ct in get_page_types()
|
||||
if issubclass(ct.model_class(), AbstractForm)
|
||||
]
|
||||
return _FORM_CONTENT_TYPES
|
||||
|
||||
|
||||
def get_forms_for_user(user):
|
||||
"""Return a queryset of form pages that this user is allowed to access the submissions for"""
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
return editable_pages.filter(content_type__in=get_form_types())
|
||||
|
||||
|
||||
class AbstractForm(Page):
|
||||
"""A Form Page. Pages implementing a form should inhert from it"""
|
||||
|
||||
form_builder = FormBuilder
|
||||
is_abstract = True # Don't display me in "Add"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AbstractForm, self).__init__(*args, **kwargs)
|
||||
if not hasattr(self, 'landing_page_template'):
|
||||
template_wo_ext = re.match(HTML_EXTENSION_RE, self.template).group(1)
|
||||
self.landing_page_template = template_wo_ext + '_landing.html'
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_form_parameters(self):
|
||||
return {}
|
||||
|
||||
def process_form_submission(self, form):
|
||||
# remove csrf_token from form.data
|
||||
form_data = dict(
|
||||
i for i in form.data.items()
|
||||
if i[0] != 'csrfmiddlewaretoken'
|
||||
)
|
||||
|
||||
FormSubmission.objects.create(
|
||||
form_data=json.dumps(form_data),
|
||||
page=self,
|
||||
)
|
||||
|
||||
def serve(self, request):
|
||||
fb = self.form_builder(self.form_fields.all())
|
||||
form_class = fb.get_form_class()
|
||||
form_params = self.get_form_parameters()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = form_class(request.POST, **form_params)
|
||||
|
||||
if form.is_valid():
|
||||
self.process_form_submission(form)
|
||||
# If we have a form_processing_backend call its process method
|
||||
if hasattr(self, 'form_processing_backend'):
|
||||
form_processor = self.form_processing_backend()
|
||||
form_processor.process(self, form)
|
||||
|
||||
# render the landing_page
|
||||
# TODO: It is much better to redirect to it
|
||||
return render(request, self.landing_page_template, {
|
||||
'self': self,
|
||||
})
|
||||
else:
|
||||
form = form_class(**form_params)
|
||||
|
||||
return render(request, self.template, {
|
||||
'self': self,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
def get_page_modes(self):
|
||||
return [
|
||||
('form', 'Form'),
|
||||
('landing', 'Landing page'),
|
||||
]
|
||||
|
||||
def show_as_mode(self, mode):
|
||||
if mode == 'landing':
|
||||
return render(self.dummy_request(), self.landing_page_template, {
|
||||
'self': self,
|
||||
})
|
||||
else:
|
||||
return super(AbstractForm, self).show_as_mode(mode)
|
||||
|
||||
|
||||
class AbstractEmailForm(AbstractForm):
|
||||
"""A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it"""
|
||||
is_abstract = True # Don't display me in "Add"
|
||||
|
||||
to_address = models.CharField(max_length=255, blank=True, help_text=_("Optional - form submissions will be emailed to this address"))
|
||||
from_address = models.CharField(max_length=255, blank=True)
|
||||
subject = models.CharField(max_length=255, blank=True)
|
||||
|
||||
def process_form_submission(self, form):
|
||||
super(AbstractEmailForm, self).process_form_submission(form)
|
||||
|
||||
if self.to_address:
|
||||
content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()])
|
||||
tasks.send_email_task.delay(self.subject, content, [self.to_address], self.from_address,)
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
$(function(){
|
||||
|
||||
});
|
||||
15
wagtail/wagtailforms/templates/wagtailforms/index.html
Normal file
15
wagtail/wagtailforms/templates/wagtailforms/index.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Forms" %}{% endblock %}
|
||||
{% block bodyclass %}menu-forms{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Forms" as forms_str %}
|
||||
{% trans "Pages" as select_form_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=forms_str subtitle=select_form_str icon="form" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<div id="form-results" class="forms">
|
||||
{% include "wagtailforms/results_forms.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load localize %}
|
||||
{% block titletag %}{% blocktrans with form_title=form_page.title|capfirst %}Submissions of {{ form_title }}{% endblocktrans %}{% endblock %}
|
||||
{% block bodyclass %}menu-snippets{% endblock %}
|
||||
{% block extra_js %}
|
||||
{% get_localized_datepicker_js %}
|
||||
{% get_date_format_override as format_override %}
|
||||
|
||||
<script>
|
||||
window.overrideDateInputFormat ='{{ format_override }}';
|
||||
$(function() {
|
||||
if(window.overrideDateInputFormat && window.overrideDateInputFormat !='') {
|
||||
$('#id_date_from').datepicker({
|
||||
dateFormat: window.overrideDateInputFormat, constrainInput: false, /* showOn: 'button', */ firstDay: 1
|
||||
});
|
||||
$('#id_date_to').datepicker({
|
||||
dateFormat: window.overrideDateInputFormat, constrainInput: false, /* showOn: 'button', */ firstDay: 1
|
||||
});
|
||||
} else {
|
||||
$('#id_date_from').datepicker({
|
||||
constrainInput: false, /* showOn: 'button', */ firstDay: 1
|
||||
});
|
||||
$('#id_date_to').datepicker({
|
||||
constrainInput: false, /* showOn: 'button', */ firstDay: 1
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<header class="nice-padding">
|
||||
<form action="" method="get">
|
||||
<div class="row">
|
||||
<div class="left">
|
||||
<div class="col">
|
||||
<h1 class="icon icon-form">
|
||||
{% blocktrans with form_title=form_page.title|capfirst %}Form data <span>{{ form_title }}</span>{% endblocktrans %}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col search-bar">
|
||||
<ul class="fields row rowflush">
|
||||
{% for field in select_date_form %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field input_classes="field-small iconfield icon-date" li_classes="col4" %}
|
||||
{% endfor %}
|
||||
<li class="submit col2">
|
||||
<button name="action" value="filter" class="button">{% trans 'Filter' %}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button name="action" value="CSV" class="button bicolor icon icon-download">{% trans 'Download CSV' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</header>
|
||||
<div class="nice-padding">
|
||||
{% if submissions %}
|
||||
{% include "wagtailforms/list_submissions.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=submissions is_searching=False linkurl='-' %}
|
||||
{# Here we pass an invalid non-empty URL name as linkurl to generate pagination links with the URL path omitted #}
|
||||
{% else %}
|
||||
<p class="no-results-message">{% blocktrans with title=form_page.title %}There have been no submissions of the '{{ title }}' form.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
23
wagtail/wagtailforms/templates/wagtailforms/list_forms.html
Normal file
23
wagtail/wagtailforms/templates/wagtailforms/list_forms.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{% load i18n %}
|
||||
<table class="listing">
|
||||
<col width="50%"/>
|
||||
<col width="50%"/>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title">{% trans "Title" %}</th>
|
||||
<th class="type">{% trans "Origin" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fp in form_pages %}
|
||||
<tr>
|
||||
<td class="title">
|
||||
<h2><a href="{% url 'wagtailforms_list_submissions' fp.id %}">{{ fp|capfirst }}</a></h2>
|
||||
</td>
|
||||
<td class="type">
|
||||
<small><a href="{% url 'wagtailadmin_pages_edit' fp.id %}" class="nolink">{{ fp.content_type.name |capfirst }} ({{ fp.content_type.app_label }}.{{ fp.content_type.model }})</a></small>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{% load i18n %}
|
||||
<div class="overflow">
|
||||
<table class="listing">
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Submission Date" %}</th>
|
||||
{% for heading in data_headings %}
|
||||
<th>{{ heading }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data_rows %}
|
||||
<tr>
|
||||
{% for cell in row %}
|
||||
<td>
|
||||
{{ cell }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{% load i18n %}
|
||||
{% if form_pages %}
|
||||
{% include "wagtailforms/list_forms.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=form_pages linkurl="wagtailforms_index" %}
|
||||
{% else %}
|
||||
<p>{% trans "No form pages have been created." %}</p>
|
||||
{% endif %}
|
||||
63
wagtail/wagtailforms/tests.py
Normal file
63
wagtail/wagtailforms/tests.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailforms.models import FormSubmission
|
||||
|
||||
class TestFormSubmission(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_get_form(self):
|
||||
response = self.client.get('/contact-us/')
|
||||
self.assertContains(response, """<label for="id_your-email">Your email</label>""")
|
||||
self.assertNotContains(response, "Thank you for your feedback")
|
||||
|
||||
def test_post_invalid_form(self):
|
||||
response = self.client.post('/contact-us/', {
|
||||
'your-email': 'bob', 'your-message': 'hello world'
|
||||
})
|
||||
self.assertNotContains(response, "Thank you for your feedback")
|
||||
self.assertContains(response, "Enter a valid email address.")
|
||||
|
||||
def test_post_valid_form(self):
|
||||
response = self.client.post('/contact-us/', {
|
||||
'your-email': 'bob@example.com', 'your-message': 'hello world'
|
||||
})
|
||||
self.assertNotContains(response, "Your email")
|
||||
self.assertContains(response, "Thank you for your feedback")
|
||||
|
||||
form_page = Page.objects.get(url_path='/home/contact-us/')
|
||||
|
||||
self.assertTrue(FormSubmission.objects.filter(page=form_page, form_data__contains='hello world').exists())
|
||||
|
||||
|
||||
class TestFormsBackend(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_cannot_see_forms_without_permission(self):
|
||||
form_page = Page.objects.get(url_path='/home/contact-us/')
|
||||
|
||||
self.client.login(username='eventeditor', password='password')
|
||||
response = self.client.get('/admin/forms/')
|
||||
self.assertFalse(form_page in response.context['form_pages'])
|
||||
|
||||
def test_can_see_forms_with_permission(self):
|
||||
form_page = Page.objects.get(url_path='/home/contact-us/')
|
||||
|
||||
self.client.login(username='siteeditor', password='password')
|
||||
response = self.client.get('/admin/forms/')
|
||||
self.assertTrue(form_page in response.context['form_pages'])
|
||||
|
||||
def test_can_get_submissions(self):
|
||||
form_page = Page.objects.get(url_path='/home/contact-us/')
|
||||
|
||||
self.client.login(username='siteeditor', password='password')
|
||||
|
||||
response = self.client.get('/admin/forms/submissions/%d/' % form_page.id)
|
||||
self.assertEqual(len(response.context['data_rows']), 2)
|
||||
|
||||
response = self.client.get('/admin/forms/submissions/%d/?date_from=01%%2F01%%2F2014' % form_page.id)
|
||||
self.assertEqual(len(response.context['data_rows']), 1)
|
||||
|
||||
response = self.client.get('/admin/forms/submissions/%d/?date_from=01%%2F01%%2F2014&action=CSV' % form_page.id)
|
||||
data_line = response.content.split("\n")[1]
|
||||
self.assertTrue('new@example.com' in data_line)
|
||||
9
wagtail/wagtailforms/urls.py
Normal file
9
wagtail/wagtailforms/urls.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'wagtail.wagtailforms.views',
|
||||
url(r'^$', 'index', name='wagtailforms_index'),
|
||||
url(r'^submissions/(\d+)/$', 'list_submissions', name='wagtailforms_list_submissions'),
|
||||
|
||||
)
|
||||
104
wagtail/wagtailforms/views.py
Normal file
104
wagtail/wagtailforms/views.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import datetime
|
||||
import unicodecsv
|
||||
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailforms.models import FormSubmission, get_forms_for_user
|
||||
from wagtail.wagtailforms.forms import SelectDateForm
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def index(request):
|
||||
p = request.GET.get("p", 1)
|
||||
|
||||
form_pages = get_forms_for_user(request.user)
|
||||
|
||||
paginator = Paginator(form_pages, 20)
|
||||
|
||||
try:
|
||||
form_pages = paginator.page(p)
|
||||
except PageNotAnInteger:
|
||||
form_pages = paginator.page(1)
|
||||
except EmptyPage:
|
||||
form_pages = paginator.page(paginator.num_pages)
|
||||
|
||||
return render(request, 'wagtailforms/index.html', {
|
||||
'form_pages': form_pages,
|
||||
})
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def list_submissions(request, page_id):
|
||||
form_page = get_object_or_404(Page, id=page_id).specific
|
||||
|
||||
if not get_forms_for_user(request.user).filter(id=page_id).exists():
|
||||
raise PermissionDenied
|
||||
|
||||
data_fields = [
|
||||
(field.clean_name, field.label)
|
||||
for field in form_page.form_fields.all()
|
||||
]
|
||||
|
||||
submissions = FormSubmission.objects.filter(page=form_page)
|
||||
|
||||
select_date_form = SelectDateForm(request.GET)
|
||||
if select_date_form.is_valid():
|
||||
date_from = select_date_form.cleaned_data.get('date_from')
|
||||
date_to = select_date_form.cleaned_data.get('date_to')
|
||||
# careful: date_to should be increased by 1 day since the submit_time
|
||||
# is a time so it will always be greater
|
||||
if date_to:
|
||||
date_to += datetime.timedelta(days=1)
|
||||
if date_from and date_to:
|
||||
submissions = submissions.filter(submit_time__range=[date_from, date_to])
|
||||
elif date_from and not date_to:
|
||||
submissions = submissions.filter(submit_time__gte=date_from)
|
||||
elif not date_from and date_to:
|
||||
submissions = submissions.filter(submit_time__lte=date_to)
|
||||
|
||||
if request.GET.get('action') == 'CSV':
|
||||
# return a CSV instead
|
||||
response = HttpResponse(content_type='text/csv; charset=utf-8')
|
||||
response['Content-Disposition'] = 'attachment;filename=export.csv'
|
||||
writer = unicodecsv.writer(response, encoding='utf-8')
|
||||
|
||||
header_row = ['Submission date'] + [label for name, label in data_fields]
|
||||
|
||||
writer.writerow(header_row)
|
||||
for s in submissions:
|
||||
data_row = [s.submit_time]
|
||||
form_data = s.get_data()
|
||||
for name, label in data_fields:
|
||||
data_row.append(form_data.get(name))
|
||||
writer.writerow(data_row)
|
||||
return response
|
||||
|
||||
p = request.GET.get('p', 1)
|
||||
paginator = Paginator(submissions, 20)
|
||||
|
||||
try:
|
||||
submissions = paginator.page(p)
|
||||
except PageNotAnInteger:
|
||||
submissions = paginator.page(1)
|
||||
except EmptyPage:
|
||||
submissions = paginator.page(paginator.num_pages)
|
||||
|
||||
data_headings = [label for name, label in data_fields]
|
||||
data_rows = []
|
||||
for s in submissions:
|
||||
form_data = s.get_data()
|
||||
data_row = [s.submit_time] + [form_data.get(name) for name, label in data_fields]
|
||||
data_rows.append(data_row)
|
||||
|
||||
return render(request, 'wagtailforms/index_submissions.html', {
|
||||
'form_page': form_page,
|
||||
'select_date_form': select_date_form,
|
||||
'submissions': submissions,
|
||||
'data_headings': data_headings,
|
||||
'data_rows': data_rows
|
||||
})
|
||||
28
wagtail/wagtailforms/wagtail_hooks.py
Normal file
28
wagtail/wagtailforms/wagtail_hooks.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from django.core import urlresolvers
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
||||
from wagtail.wagtailforms import urls
|
||||
from wagtail.wagtailforms.models import get_forms_for_user
|
||||
|
||||
def register_admin_urls():
|
||||
return [
|
||||
url(r'^forms/', include(urls)),
|
||||
]
|
||||
hooks.register('register_admin_urls', register_admin_urls)
|
||||
|
||||
def construct_main_menu(request, menu_items):
|
||||
# show this only if the user has permission to retrieve submissions for at least one form
|
||||
if get_forms_for_user(request.user).exists():
|
||||
menu_items.append(
|
||||
MenuItem(_('Forms'), urlresolvers.reverse('wagtailforms_index'), classnames='icon icon-form', order=700)
|
||||
)
|
||||
hooks.register('construct_main_menu', construct_main_menu)
|
||||
|
||||
def editor_js():
|
||||
return """<script src="%swagtailforms/js/page-editor.js"></script>""" % settings.STATIC_URL
|
||||
hooks.register('insert_editor_js', editor_js)
|
||||
|
|
@ -19,10 +19,12 @@ def user_can_edit_snippet_type(user, content_type):
|
|||
|
||||
def user_can_edit_snippets(user):
|
||||
""" true if user has any permission related to any content type registered as a snippet type """
|
||||
snippet_content_types = get_snippet_content_types()
|
||||
if user.is_active and user.is_superuser:
|
||||
return True
|
||||
# admin can edit snippets iff any snippet types exist
|
||||
return bool(snippet_content_types)
|
||||
|
||||
permissions = Permission.objects.filter(content_type__in=get_snippet_content_types()).select_related('content_type')
|
||||
permissions = Permission.objects.filter(content_type__in=snippet_content_types).select_related('content_type')
|
||||
for perm in permissions:
|
||||
permission_name = "%s.%s" % (perm.content_type.app_label, perm.codename)
|
||||
if user.has_perm(permission_name):
|
||||
|
|
|
|||
Loading…
Reference in a new issue