mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-26 23:54:00 +00:00
Merge branch 'master' into varnish-cache-invalidation
Conflicts: wagtail/wagtailadmin/tests/test_pages_views.py
This commit is contained in:
commit
d9b22292f2
44 changed files with 1427 additions and 401 deletions
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
Editing API
|
||||
Defining models with the Editing API
|
||||
===========
|
||||
|
||||
.. note::
|
||||
|
|
@ -22,18 +22,30 @@ Defining Panels
|
|||
|
||||
A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick the appropriate editing widget for most Django field types, you just need to add a panel for each field you want to show in the Wagtail page editor, in the order you want them to appear.
|
||||
|
||||
There are three types of panels:
|
||||
|
||||
There are four basic types of panels:
|
||||
|
||||
``FieldPanel( field_name, classname=None )``
|
||||
This is the panel used for basic Django field types. ``field_name`` is the name of the class property used in your model definition. ``classname`` is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields. The CSS class ``full`` can be used to format the panel so it covers the full width of the Wagtail page editor. The CSS class ``title`` can be used to mark a field as the source for auto-generated slug strings.
|
||||
|
||||
``MultiFieldPanel( children, heading="", classname=None )``
|
||||
This panel condenses several ``FieldPanel`` s or choosers, from a list or tuple, under a single ``heading`` string.
|
||||
|
||||
``InlinePanel( base_model, relation_name, panels=None, label='', help_text='' )``
|
||||
``InlinePanel( base_model, relation_name, panels=None, classname=None, label='', help_text='' )``
|
||||
This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. This is a very powerful, but tricky feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`.
|
||||
|
||||
Wagtail provides a tabbed interface to help organize panels. ``content_panels`` is the main tab, used for the meat of your model content. The other, ``promote_panels``, is suggested for organizing metadata about the content, such as SEO information and other machine-readable information. Since you're writing the panel definitions, you can organize them however you want.
|
||||
``FieldRowPanel( children, classname=None)``
|
||||
This panel is purely aesthetic. It creates a columnar layout in the editing interface, where each of the child Panels appears alongside each others rather than below. Use of FieldRowPanel particularly helps reduce the "snow-blindness" effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an "Event" which had a starting date and ending date, it would be intuitive to find the start and end date on the same "row".
|
||||
|
||||
FieldRowPanel should be used in combination with ``col*`` classnames added to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is layed out using a grid system, in which the maximum width of the editor is 12 columns wide. Classes ``col1``-``col12`` can be applied to each child of a FieldRowPanel. The class ``col3`` will ensure that field appears 3 columns wide or a quarter the width. ``col4`` would cause the field to be 4 columns wide, or a third the width.
|
||||
|
||||
**(In addition to these four, there are also Chooser Panels, detailed below.)**
|
||||
|
||||
|
||||
Wagtail provides a tabbed interface to help organize panels. Three such tabs are provided:
|
||||
|
||||
* ``content_panels`` is the main tab, used for the bulk of your model's fields.
|
||||
* ``promote_panels`` is suggested for organizing fields regarding the promotion of the page around the site and the internet e.g A field to dictate whether the page should show in site-wide menus, descriptive text that should appear in site search results, SEO friendly titles, OpenGraph meta tag content and other machine-readable information.
|
||||
* ``settings_panels`` is essentially for non-copy fields. By default it contains the page's scheduled publishing fields. Other suggested fields e.g: a field to switch between one layout/style and another.
|
||||
|
||||
Let's look at an example of a panel definition:
|
||||
|
||||
|
|
@ -55,7 +67,10 @@ Let's look at an example of a panel definition:
|
|||
ExamplePage.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('body', classname="full"),
|
||||
FieldPanel('date'),
|
||||
FieldRowPanel([
|
||||
FieldPanel('start_date', classname="col3"),
|
||||
FieldPanel('end_date', classname="col3"),
|
||||
]),
|
||||
ImageChooserPanel('splash_image'),
|
||||
DocumentChooserPanel('free_download'),
|
||||
PageChooserPanel('related_page'),
|
||||
|
|
@ -254,6 +269,12 @@ Titles
|
|||
Use ``classname="title"`` to make Page's built-in title field stand out with more vertical padding.
|
||||
|
||||
|
||||
Col*
|
||||
------
|
||||
|
||||
Fields within a ``FieldRowPanel`` can have their width dictated in terms of the number of columns it should span. The ``FieldRowPanel`` is always considered to be 12 columns wide regardless of browser size or the nesting of ``FieldRowPanel`` in any other type of panel. Specify a number of columns thus: ``col3``, ``col4``, ``col6`` etc (up to 12). The resulting width with be *relative* to the full width of the ``FieldRowPanel``.
|
||||
|
||||
|
||||
Required Fields
|
||||
---------------
|
||||
|
||||
|
|
|
|||
59
docs/management_commands.rst
Normal file
59
docs/management_commands.rst
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
Management commands
|
||||
===================
|
||||
|
||||
publish_scheduled_pages
|
||||
-----------------------
|
||||
|
||||
**./manage.py publish_scheduled_pages**
|
||||
|
||||
This command publishes/unpublishes pages that have had these actions scheduled by an editor.
|
||||
|
||||
It is recommended to run this command once an hour.
|
||||
|
||||
|
||||
fixtree
|
||||
-------
|
||||
|
||||
**./manage.py fixtree**
|
||||
|
||||
This command scans for errors in your database and attempts to fix any issues it finds.
|
||||
|
||||
|
||||
move_pages
|
||||
----------
|
||||
|
||||
**./manage.py move_pages from to**
|
||||
|
||||
This mass moves a bunch of pages from one section of the tree to another.
|
||||
|
||||
Options:
|
||||
|
||||
- **from**
|
||||
This is id of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children.
|
||||
|
||||
- **to**
|
||||
This is the id of the page to move pages to.
|
||||
|
||||
|
||||
update_index
|
||||
------------
|
||||
|
||||
**./manage.py update_index**
|
||||
|
||||
This command rebuilds the search index from scratch. It is only required when using ElasticSearch.
|
||||
|
||||
It is recommended to run this command once a week and at the following times:
|
||||
- Whenever any pages have been created through a script (eg, import)
|
||||
- Whenever any changes have been made to models or search configuration
|
||||
|
||||
While this command is running, the search may not return any results so avoid running this command at peak times.
|
||||
|
||||
|
||||
search_garbage_collect
|
||||
----------------------
|
||||
|
||||
**./manage.py search_garbage_collect**
|
||||
|
||||
Wagtail keeps a log of search queries that are popular on your website. On high traffic websites, this log may get big and sometimes you may want to clean out old search queries.
|
||||
|
||||
This command cleans out all search query logs that are more than one week old.
|
||||
|
|
@ -42,9 +42,10 @@
|
|||
<li class="color-teal">color-teal</li>
|
||||
<li class="color-teal-darker">color-teal-darker</li>
|
||||
<li class="color-teal-dark">color-teal-dark</li>
|
||||
<li class="color-red">color-red</li>
|
||||
<li class="color-orange">color-orange</li>
|
||||
<li class="color-green">color-green</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="color-salmon">color-salmon</li>
|
||||
<li class="color-salmon-light">color-salmon-light</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="color-grey-1">color-grey-1</li>
|
||||
|
|
@ -54,6 +55,12 @@
|
|||
<li class="color-grey-4">color-grey-4</li>
|
||||
<li class="color-grey-5">color-grey-5</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="color-red">color-red</li>
|
||||
<li class="color-orange">color-orange</li>
|
||||
<li class="color-green">color-green</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="typography">
|
||||
|
|
@ -407,6 +414,7 @@
|
|||
<li class="icon icon-warning">warning</li>
|
||||
<li class="icon icon-success">success</li>
|
||||
<li class="icon icon-date">date</li>
|
||||
<li class="icon icon-time">time</li>
|
||||
<li class="icon icon-form">form</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class ExampleForm(forms.Form):
|
|||
url = forms.URLField(required=True)
|
||||
email = forms.EmailField(max_length=254)
|
||||
date = forms.DateField()
|
||||
time = forms.TimeField()
|
||||
select = forms.ChoiceField(choices=CHOICES)
|
||||
boolean = forms.BooleanField(required=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ def extract_panel_definitions_from_model_class(model, exclude=None):
|
|||
return panels
|
||||
|
||||
|
||||
def set_page_edit_handler(page_class, handlers):
|
||||
page_class.handlers = handlers
|
||||
|
||||
|
||||
class EditHandler(object):
|
||||
"""
|
||||
Abstract class providing sensible default behaviours for objects implementing
|
||||
|
|
@ -183,19 +187,21 @@ class EditHandler(object):
|
|||
heading = ""
|
||||
help_text = ""
|
||||
|
||||
def object_classnames(self):
|
||||
def classes(self):
|
||||
"""
|
||||
Additional classnames to add to the <li class="object"> when rendering this
|
||||
within an ObjectList
|
||||
Additional CSS classnames to add to whatever kind of object this is at output.
|
||||
Subclasses of EditHandler should override this, invoking super(B, self).classes() to
|
||||
append more classes specific to the situation.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def field_classnames(self):
|
||||
"""
|
||||
Additional classnames to add to the <li> when rendering this within a
|
||||
<ul class="fields">
|
||||
"""
|
||||
return ""
|
||||
classes = []
|
||||
|
||||
try:
|
||||
classes.append(self.classname)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return classes
|
||||
|
||||
def field_type(self):
|
||||
"""
|
||||
|
|
@ -261,12 +267,6 @@ class BaseCompositeEditHandler(EditHandler):
|
|||
"""
|
||||
_widget_overrides = None
|
||||
|
||||
def object_classnames(self):
|
||||
try:
|
||||
return "multi-field " + self.classname
|
||||
except (AttributeError, TypeError):
|
||||
return "multi-field"
|
||||
|
||||
@classmethod
|
||||
def widget_overrides(cls):
|
||||
if cls._widget_overrides is None:
|
||||
|
|
@ -326,18 +326,33 @@ class BaseObjectList(BaseCompositeEditHandler):
|
|||
template = "wagtailadmin/edit_handlers/object_list.html"
|
||||
|
||||
|
||||
def ObjectList(children, heading=""):
|
||||
def ObjectList(children, heading="", classname=""):
|
||||
return type('_ObjectList', (BaseObjectList,), {
|
||||
'children': children,
|
||||
'heading': heading,
|
||||
'classname': classname
|
||||
})
|
||||
|
||||
|
||||
class BaseFieldRowPanel(BaseCompositeEditHandler):
|
||||
template = "wagtailadmin/edit_handlers/field_row_panel.html"
|
||||
|
||||
def FieldRowPanel(children, classname=""):
|
||||
return type('_FieldRowPanel', (BaseFieldRowPanel,), {
|
||||
'children': children,
|
||||
'classname': classname,
|
||||
})
|
||||
|
||||
class BaseMultiFieldPanel(BaseCompositeEditHandler):
|
||||
template = "wagtailadmin/edit_handlers/multi_field_panel.html"
|
||||
|
||||
def classes(self):
|
||||
classes = super(BaseMultiFieldPanel, self).classes()
|
||||
classes.append("multi-field")
|
||||
|
||||
return classes
|
||||
|
||||
def MultiFieldPanel(children, heading="", classname=None):
|
||||
def MultiFieldPanel(children, heading="", classname=""):
|
||||
return type('_MultiFieldPanel', (BaseMultiFieldPanel,), {
|
||||
'children': children,
|
||||
'heading': heading,
|
||||
|
|
@ -353,25 +368,23 @@ class BaseFieldPanel(EditHandler):
|
|||
self.heading = self.bound_field.label
|
||||
self.help_text = self.bound_field.help_text
|
||||
|
||||
def object_classnames(self):
|
||||
try:
|
||||
return "single-field " + self.classname
|
||||
except (AttributeError, TypeError):
|
||||
return "single-field"
|
||||
def classes(self):
|
||||
classes = super(BaseFieldPanel, self).classes();
|
||||
|
||||
if self.bound_field.field.required:
|
||||
classes.append("required")
|
||||
if self.bound_field.errors:
|
||||
classes.append("error")
|
||||
|
||||
classes.append(self.field_type())
|
||||
classes.append("single-field")
|
||||
|
||||
return classes
|
||||
|
||||
def field_type(self):
|
||||
return camelcase_to_underscore(self.bound_field.field.__class__.__name__)
|
||||
|
||||
def field_classnames(self):
|
||||
classname = self.field_type()
|
||||
if self.bound_field.field.required:
|
||||
classname += " required"
|
||||
if self.bound_field.errors:
|
||||
classname += " error"
|
||||
|
||||
return classname
|
||||
|
||||
object_template = "wagtailadmin/edit_handlers/field_panel_object.html"
|
||||
object_template = "wagtailadmin/edit_handlers/single_field_panel.html"
|
||||
|
||||
def render_as_object(self):
|
||||
return mark_safe(render_to_string(self.object_template, {
|
||||
|
|
@ -401,7 +414,7 @@ class BaseFieldPanel(EditHandler):
|
|||
return [self.field_name]
|
||||
|
||||
|
||||
def FieldPanel(field_name, classname=None):
|
||||
def FieldPanel(field_name, classname=""):
|
||||
return type('_FieldPanel', (BaseFieldPanel,), {
|
||||
'field_name': field_name,
|
||||
'classname': classname,
|
||||
|
|
@ -597,10 +610,23 @@ def InlinePanel(base_model, relation_name, panels=None, label='', help_text=''):
|
|||
})
|
||||
|
||||
|
||||
# This allows users to include the publishing panel in their own per-model override
|
||||
# without having to write these fields out by hand, potentially losing 'classname'
|
||||
# and therefore the associated styling of the publishing panel
|
||||
def PublishingPanel():
|
||||
return MultiFieldPanel([
|
||||
FieldRowPanel([
|
||||
FieldPanel('go_live_at'),
|
||||
FieldPanel('expire_at'),
|
||||
], classname="label-above"),
|
||||
], ugettext_lazy('Scheduled publishing'), classname="publishing")
|
||||
|
||||
|
||||
# Now that we've defined EditHandlers, we can set up wagtailcore.Page to have some.
|
||||
Page.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
]
|
||||
|
||||
Page.promote_panels = [
|
||||
MultiFieldPanel([
|
||||
FieldPanel('slug'),
|
||||
|
|
@ -609,3 +635,7 @@ Page.promote_panels = [
|
|||
FieldPanel('search_description'),
|
||||
], ugettext_lazy('Common page configuration')),
|
||||
]
|
||||
|
||||
Page.settings_panels = [
|
||||
PublishingPanel()
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
(function(jQuery) {
|
||||
return jQuery.widget('IKS.halloToolbarFixed', {
|
||||
toolbar: null,
|
||||
options: {
|
||||
parentElement: 'body',
|
||||
editable: null,
|
||||
toolbar: null,
|
||||
affix: true,
|
||||
affixTopOffset: 2
|
||||
},
|
||||
_create: function() {
|
||||
var el, widthToAdd,
|
||||
_this = this;
|
||||
this.toolbar = this.options.toolbar;
|
||||
this.toolbar.show();
|
||||
jQuery(this.options.parentElement).append(this.toolbar);
|
||||
this._bindEvents();
|
||||
jQuery(window).resize(function(event) {
|
||||
return _this.setPosition();
|
||||
});
|
||||
jQuery(window).scroll(function(event) {
|
||||
return _this.setPosition();
|
||||
});
|
||||
if (this.options.parentElement === 'body') {
|
||||
el = jQuery(this.element);
|
||||
widthToAdd = parseFloat(el.css('padding-left'));
|
||||
widthToAdd += parseFloat(el.css('padding-right'));
|
||||
widthToAdd += parseFloat(el.css('border-left-width'));
|
||||
widthToAdd += parseFloat(el.css('border-right-width'));
|
||||
widthToAdd += (parseFloat(el.css('outline-width'))) * 2;
|
||||
widthToAdd += (parseFloat(el.css('outline-offset'))) * 2;
|
||||
return jQuery(this.toolbar).css("width", el.width() + widthToAdd);
|
||||
}
|
||||
},
|
||||
_getPosition: function(event, selection) {
|
||||
var offset, position, width;
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
width = parseFloat(this.element.css('outline-width'));
|
||||
offset = width + parseFloat(this.element.css('outline-offset'));
|
||||
return position = {
|
||||
top: this.element.offset().top - this.toolbar.outerHeight() - offset,
|
||||
left: this.element.offset().left - offset
|
||||
};
|
||||
},
|
||||
_getCaretPosition: function(range) {
|
||||
var newRange, position, tmpSpan;
|
||||
tmpSpan = jQuery("<span/>");
|
||||
newRange = rangy.createRange();
|
||||
newRange.setStart(range.endContainer, range.endOffset);
|
||||
newRange.insertNode(tmpSpan.get(0));
|
||||
position = {
|
||||
top: tmpSpan.offset().top,
|
||||
left: tmpSpan.offset().left
|
||||
};
|
||||
tmpSpan.remove();
|
||||
return position;
|
||||
},
|
||||
setPosition: function() {
|
||||
var elementBottom, elementTop, height, offset, scrollTop, topOffset;
|
||||
if (this.options.parentElement !== 'body') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toolbar.css('top', this.element.offset().top - this.toolbar.outerHeight());
|
||||
|
||||
if (this.options.affix) {
|
||||
this.toolbar.removeClass('affixed');
|
||||
scrollTop = jQuery(window).scrollTop();
|
||||
offset = this.element.offset();
|
||||
height = this.element.height();
|
||||
topOffset = this.options.affixTopOffset;
|
||||
elementTop = offset.top - (this.toolbar.height() + this.options.affixTopOffset);
|
||||
elementBottom = (height - topOffset) + (offset.top - this.toolbar.height());
|
||||
if (scrollTop > elementTop && scrollTop < elementBottom) {
|
||||
this.toolbar.addClass('affixed');
|
||||
this.toolbar.css('top', this.options.affixTopOffset);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
return this.toolbar;
|
||||
},
|
||||
_updatePosition: function(position) {},
|
||||
_bindEvents: function() {
|
||||
var _this = this;
|
||||
this.element.on('halloactivated', function(event, data) {
|
||||
_this.setPosition();
|
||||
return _this.toolbar.show();
|
||||
});
|
||||
return this.element.on('hallodeactivated', function(event, data) {
|
||||
return _this.toolbar.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
"use strict";
|
||||
|
||||
var halloPlugins = {
|
||||
'halloformat': {},
|
||||
'halloheadings': {formatBlocks: ["p", "h2", "h3", "h4", "h5"]},
|
||||
'hallolists': {},
|
||||
'hallohr': {},
|
||||
'halloreundo': {},
|
||||
'hallowagtaillink': {},
|
||||
'hallowagtaillink': {}
|
||||
};
|
||||
|
||||
function registerHalloPlugin(name, opts) {
|
||||
halloPlugins[name] = (opts || {});
|
||||
}
|
||||
|
|
@ -30,7 +33,7 @@ function makeRichTextEditable(id) {
|
|||
|
||||
richText.hallo({
|
||||
toolbar: 'halloToolbarFixed',
|
||||
toolbarcssClass: 'testy',
|
||||
toolbarCssClass: (input.closest('.object').hasClass('full')) ? 'full' : '',
|
||||
plugins: halloPlugins
|
||||
}).bind('hallomodified', function(event, data) {
|
||||
input.val(data.content);
|
||||
|
|
@ -57,6 +60,7 @@ function initDateChooser(id) {
|
|||
if (window.dateTimePickerTranslations) {
|
||||
$('#' + id).datetimepicker({
|
||||
timepicker: false,
|
||||
scrollInput:false,
|
||||
format: 'Y-m-d',
|
||||
i18n: {
|
||||
lang: window.dateTimePickerTranslations
|
||||
|
|
@ -66,6 +70,7 @@ function initDateChooser(id) {
|
|||
} else {
|
||||
$('#' + id).datetimepicker({
|
||||
timepicker: false,
|
||||
scrollInput:false,
|
||||
format: 'Y-m-d',
|
||||
});
|
||||
}
|
||||
|
|
@ -75,6 +80,7 @@ function initTimeChooser(id) {
|
|||
if (window.dateTimePickerTranslations) {
|
||||
$('#' + id).datetimepicker({
|
||||
datepicker: false,
|
||||
scrollInput:false,
|
||||
format: 'H:i',
|
||||
i18n: {
|
||||
lang: window.dateTimePickerTranslations
|
||||
|
|
@ -93,6 +99,7 @@ function initDateTimeChooser(id) {
|
|||
if (window.dateTimePickerTranslations) {
|
||||
$('#' + id).datetimepicker({
|
||||
format: 'Y-m-d H:i',
|
||||
scrollInput:false,
|
||||
i18n: {
|
||||
lang: window.dateTimePickerTranslations
|
||||
},
|
||||
|
|
@ -197,7 +204,7 @@ function InlinePanel(opts) {
|
|||
|
||||
self.updateMoveButtonDisabledStates = function() {
|
||||
if (opts.canOrder) {
|
||||
forms = self.formsUl.children('li:visible');
|
||||
var forms = self.formsUl.children('li:visible');
|
||||
forms.each(function(i) {
|
||||
$('ul.controls .inline-child-move-up', this).toggleClass('disabled', i === 0).toggleClass('enabled', i !== 0);
|
||||
$('ul.controls .inline-child-move-down', this).toggleClass('disabled', i === forms.length - 1).toggleClass('enabled', i != forms.length - 1);
|
||||
|
|
|
|||
|
|
@ -2829,6 +2829,7 @@
|
|||
} else {
|
||||
|
||||
}
|
||||
|
||||
return this.toolbar.css('left', this.element.offset().left - 2);
|
||||
},
|
||||
_updatePosition: function(position) {},
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@
|
|||
.xdsoft_calendar td.xdsoft_default,
|
||||
.xdsoft_calendar td.xdsoft_current,
|
||||
.xdsoft_timepicker .xdsoft_time_box > div > div.xdsoft_current{
|
||||
background: $color-orange;
|
||||
background: $color-salmon;
|
||||
color:#fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
These are the generic stylings for forms of any type.
|
||||
If you're styling something specific to the page editing interface,
|
||||
it probably ought to go in layouts/page-editor.scss
|
||||
*/
|
||||
|
||||
form {
|
||||
ul, li{
|
||||
list-style-type:none;
|
||||
|
|
@ -6,9 +12,6 @@ form {
|
|||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
li{
|
||||
@include row();
|
||||
}
|
||||
}
|
||||
|
||||
fieldset{
|
||||
|
|
@ -315,6 +318,7 @@ button.icon{
|
|||
overflow:hidden;
|
||||
|
||||
> li{
|
||||
@include row();
|
||||
position:relative;
|
||||
background-color:white;
|
||||
padding:1em 10em 1em 1.5em; /* 10em padding leaves room for controls */
|
||||
|
|
@ -435,58 +439,112 @@ li.focused > .help{
|
|||
.boolean_field .help, .radio .help{
|
||||
opacity:1;
|
||||
}
|
||||
.iconfield {
|
||||
position:relative;
|
||||
|
||||
/*
|
||||
This is expected to go on the parent of the input/select/textarea
|
||||
so in most cases .input
|
||||
*/
|
||||
.iconfield, /* generic */
|
||||
.date_field,
|
||||
.time_field,
|
||||
.date_time_field,
|
||||
.url_field{
|
||||
.input{
|
||||
position:relative;
|
||||
|
||||
&:before, &:after{
|
||||
font-family:wagtail;
|
||||
position:absolute;
|
||||
top:0.5em;
|
||||
line-height:100%;
|
||||
font-size:2em;
|
||||
color:$color-grey-3;
|
||||
}
|
||||
&:before{
|
||||
left:0.3em;
|
||||
}
|
||||
&:after{
|
||||
right:0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
/* smaller fields required slight repositioning of icons */
|
||||
&.field-small{
|
||||
.input{
|
||||
&:before, &:after{
|
||||
font-size:1.5em;
|
||||
top:0.3em;
|
||||
}
|
||||
&: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{
|
||||
.date_field,
|
||||
.date_time_field{
|
||||
.input:before{
|
||||
@extend .icon-date:before;
|
||||
}
|
||||
}
|
||||
|
||||
.time_field{
|
||||
.input:before{
|
||||
@extend .icon-time:before;
|
||||
}
|
||||
}
|
||||
|
||||
.url_field{
|
||||
.input:before{
|
||||
@extend .icon-link:before;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This is specifically for model that are a generated set of checkboxes/radios */
|
||||
.model_multiple_choice_field .input li,
|
||||
.choice_field .input li{
|
||||
label{
|
||||
display:block;
|
||||
width:auto;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
|
||||
.fields > li,
|
||||
.field-col{
|
||||
@include clearfix();
|
||||
padding-top:0.5em;
|
||||
padding-bottom:1.2em;
|
||||
}
|
||||
|
||||
.field-content .input li{
|
||||
label{
|
||||
width:auto;
|
||||
float:none;
|
||||
}
|
||||
.field-row{
|
||||
@include clearfix();
|
||||
/* negative margin the bottom so it doesn't add too much space */
|
||||
margin-bottom:-1.2em;
|
||||
}
|
||||
|
||||
.input{
|
||||
clear:both;
|
||||
}
|
||||
|
||||
/* field sizing */
|
||||
/* field sizing and alignment */
|
||||
|
||||
.field-small{
|
||||
input, textarea, select, .richtext, .tagit{
|
||||
|
|
@ -697,9 +755,14 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
|
|||
.file_field &{
|
||||
padding-top:0;
|
||||
}
|
||||
}
|
||||
|
||||
.boolean_field &{
|
||||
padding-bottom:0;
|
||||
.label-above{
|
||||
.field > label{
|
||||
display:block;
|
||||
padding:0 0 0.8em 0;
|
||||
float:none;
|
||||
width:auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -758,8 +821,14 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
|
|||
@include row();
|
||||
}
|
||||
|
||||
.field-col{
|
||||
float:left;
|
||||
padding-left:0 !important;
|
||||
}
|
||||
|
||||
.field-content{
|
||||
@include column(10);
|
||||
padding-right:0;
|
||||
}
|
||||
padding-left:0;
|
||||
}
|
||||
}
|
||||
|
|
@ -97,13 +97,6 @@ header{
|
|||
}
|
||||
}
|
||||
|
||||
/* mozilla specific hack */
|
||||
@-moz-document url-prefix() {
|
||||
.iconfield.icon-spinner:after{
|
||||
line-height:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-explorer header{
|
||||
margin-bottom:0;
|
||||
padding-bottom:0em;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@
|
|||
.icon-unlocked:before {
|
||||
content: "p";
|
||||
}
|
||||
|
||||
.icon-doc-full-inverse:before {
|
||||
content: "r";
|
||||
}
|
||||
|
|
@ -210,9 +209,9 @@
|
|||
}
|
||||
.icon-spinner:after{
|
||||
width:1em;
|
||||
animation: spin 1s infinite;
|
||||
-webkit-animation: spin 1s infinite;
|
||||
-moz-animation: spin 1s infinite;
|
||||
animation: spin 0.5s infinite linear;
|
||||
-webkit-animation: spin 0.5s infinite linear;
|
||||
-moz-animation: spin 0.5s infinite linear;
|
||||
content:"1";
|
||||
}
|
||||
.icon-pick:before{
|
||||
|
|
@ -236,6 +235,9 @@
|
|||
.icon-date:before{
|
||||
content:"7";
|
||||
}
|
||||
.icon-time:before{
|
||||
content:"8";
|
||||
}
|
||||
.icon-success:before{
|
||||
content:"9";
|
||||
}
|
||||
|
|
@ -248,6 +250,7 @@
|
|||
.icon-form:before{
|
||||
content:"$";
|
||||
}
|
||||
|
||||
.icon.text-replace{
|
||||
font-size:0em;
|
||||
line-height:0;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
.tab-nav{
|
||||
@include clearfix();
|
||||
@include row();
|
||||
padding:0;
|
||||
background:$color-grey-4;
|
||||
|
||||
li{
|
||||
list-style-type:none;
|
||||
width:48%;
|
||||
width:33%;
|
||||
float:left;
|
||||
padding:0;
|
||||
position:relative;
|
||||
|
||||
&:before,&:after{
|
||||
display:none;
|
||||
}
|
||||
margin-right:1px;
|
||||
}
|
||||
a{
|
||||
@include transition(border-color 0.2s ease);
|
||||
background-color:lighten($color-teal-darker, 3%);
|
||||
outline:none;
|
||||
line-height:3em;
|
||||
text-transform:uppercase;
|
||||
font-weight:700;
|
||||
font-size:1.2em;
|
||||
text-decoration:none;
|
||||
display:block;
|
||||
padding:0 20px;
|
||||
padding:0.7em;
|
||||
color:white;
|
||||
border-top:0.3em solid lighten($color-teal-darker, 3%);
|
||||
border-bottom:1px solid transparent;
|
||||
max-height:1.2em;
|
||||
overflow:hidden;
|
||||
|
||||
&:hover{
|
||||
color:white;
|
||||
|
|
@ -45,7 +43,6 @@
|
|||
min-width:0.9em;
|
||||
color:white;
|
||||
background:$color-red;
|
||||
|
||||
content:attr(data-count);
|
||||
padding:0 0.3em;
|
||||
line-height:1.4em;
|
||||
|
|
@ -61,10 +58,21 @@
|
|||
border-top:0.3em solid $color-grey-1;
|
||||
}
|
||||
|
||||
li.settings a{
|
||||
&:before{
|
||||
font-family:wagtail;
|
||||
vertical-align:middle;
|
||||
text-transform:none;
|
||||
content:"w";
|
||||
margin-right:0.5em;
|
||||
font-size:1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/* For cases where tab-nav should merge with header */
|
||||
&.merged{
|
||||
background-color:$color-header-bg;
|
||||
margin-top:0;
|
||||
background-color:$color-header-bg;
|
||||
}
|
||||
}
|
||||
.tab-content{
|
||||
|
|
@ -79,14 +87,27 @@
|
|||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
.tab-nav li{
|
||||
width:auto;
|
||||
padding:0;
|
||||
margin-left:0.7em;
|
||||
}
|
||||
.tab-nav{
|
||||
/* For cases where tab-nav should merge with header */
|
||||
&.merged{
|
||||
background-color:$color-header-bg;
|
||||
}
|
||||
|
||||
.tab-nav a{
|
||||
padding:0 50px;
|
||||
li{
|
||||
width:auto;
|
||||
padding:0;
|
||||
margin-left:0.7em;
|
||||
}
|
||||
|
||||
a{
|
||||
padding-left:$desktop-nice-padding - 10;
|
||||
padding-right:$desktop-nice-padding - 10;
|
||||
}
|
||||
|
||||
li.settings a{
|
||||
padding-left:2em;
|
||||
padding-right:2em;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content .tab-nav li{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,29 @@
|
|||
{
|
||||
"IcoMoonType": "selection",
|
||||
"icons": [
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M632.913 707.493l-173.647-173.649v-232.782h105.469v189.094l142.759 142.757zM512 90.125c-232.995 0-421.875 188.88-421.875 421.875s188.88 421.875 421.875 421.875 421.875-188.88 421.875-421.875-188.88-421.875-421.875-421.875zM512 828.406c-174.747 0-316.406-141.659-316.406-316.406s141.659-316.406 316.406-316.406c174.747 0 316.406 141.659 316.406 316.406s-141.659 316.406-316.406 316.406z"
|
||||
],
|
||||
"tags": [
|
||||
"clock",
|
||||
"time",
|
||||
"schedule"
|
||||
],
|
||||
"grid": 16
|
||||
},
|
||||
"properties": {
|
||||
"id": 72,
|
||||
"order": 9,
|
||||
"prevSize": 32,
|
||||
"code": 56,
|
||||
"name": "clock",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 72
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
|
|
@ -20,7 +43,7 @@
|
|||
"name": "lock39copy",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
|
|
@ -42,7 +65,7 @@
|
|||
"name": "lock39-open",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -63,7 +86,7 @@
|
|||
"name": "form",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 2
|
||||
},
|
||||
{
|
||||
|
|
@ -82,7 +105,7 @@
|
|||
"name": "uni61",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 3
|
||||
},
|
||||
{
|
||||
|
|
@ -101,7 +124,7 @@
|
|||
"name": "uni62",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 4
|
||||
},
|
||||
{
|
||||
|
|
@ -120,7 +143,7 @@
|
|||
"name": "uni63",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 5
|
||||
},
|
||||
{
|
||||
|
|
@ -139,7 +162,7 @@
|
|||
"name": "uni64",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 6
|
||||
},
|
||||
{
|
||||
|
|
@ -158,7 +181,7 @@
|
|||
"name": "uni65",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 7
|
||||
},
|
||||
{
|
||||
|
|
@ -177,7 +200,7 @@
|
|||
"name": "uni66",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 8
|
||||
},
|
||||
{
|
||||
|
|
@ -196,7 +219,7 @@
|
|||
"name": "uni67",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 9
|
||||
},
|
||||
{
|
||||
|
|
@ -215,7 +238,7 @@
|
|||
"name": "uni69",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 10
|
||||
},
|
||||
{
|
||||
|
|
@ -234,7 +257,7 @@
|
|||
"name": "uni6A",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 11
|
||||
},
|
||||
{
|
||||
|
|
@ -253,7 +276,7 @@
|
|||
"name": "uni6B",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 12
|
||||
},
|
||||
{
|
||||
|
|
@ -272,7 +295,7 @@
|
|||
"name": "uni6C",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 13
|
||||
},
|
||||
{
|
||||
|
|
@ -291,7 +314,7 @@
|
|||
"name": "uni6E",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 14
|
||||
},
|
||||
{
|
||||
|
|
@ -310,7 +333,7 @@
|
|||
"name": "uni68",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 15
|
||||
},
|
||||
{
|
||||
|
|
@ -329,7 +352,7 @@
|
|||
"name": "uni6F",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 16
|
||||
},
|
||||
{
|
||||
|
|
@ -348,7 +371,7 @@
|
|||
"name": "uni71",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 17
|
||||
},
|
||||
{
|
||||
|
|
@ -367,7 +390,7 @@
|
|||
"name": "uni72",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 18
|
||||
},
|
||||
{
|
||||
|
|
@ -386,7 +409,7 @@
|
|||
"name": "uni73",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 19
|
||||
},
|
||||
{
|
||||
|
|
@ -405,7 +428,7 @@
|
|||
"name": "uni74",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 20
|
||||
},
|
||||
{
|
||||
|
|
@ -424,7 +447,7 @@
|
|||
"name": "uni75",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 21
|
||||
},
|
||||
{
|
||||
|
|
@ -443,7 +466,7 @@
|
|||
"name": "uni76",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 22
|
||||
},
|
||||
{
|
||||
|
|
@ -462,7 +485,7 @@
|
|||
"name": "uni77",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 23
|
||||
},
|
||||
{
|
||||
|
|
@ -481,7 +504,7 @@
|
|||
"name": "uni78",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 24
|
||||
},
|
||||
{
|
||||
|
|
@ -500,7 +523,7 @@
|
|||
"name": "uni7A",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 25
|
||||
},
|
||||
{
|
||||
|
|
@ -519,7 +542,7 @@
|
|||
"name": "uni41",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 26
|
||||
},
|
||||
{
|
||||
|
|
@ -538,7 +561,7 @@
|
|||
"name": "uni42",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 27
|
||||
},
|
||||
{
|
||||
|
|
@ -557,7 +580,7 @@
|
|||
"name": "uni44",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 28
|
||||
},
|
||||
{
|
||||
|
|
@ -576,7 +599,7 @@
|
|||
"name": "uni43",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 29
|
||||
},
|
||||
{
|
||||
|
|
@ -595,7 +618,7 @@
|
|||
"name": "uni45",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 30
|
||||
},
|
||||
{
|
||||
|
|
@ -614,7 +637,7 @@
|
|||
"name": "uni46",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 31
|
||||
},
|
||||
{
|
||||
|
|
@ -633,7 +656,7 @@
|
|||
"name": "uni47",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 32
|
||||
},
|
||||
{
|
||||
|
|
@ -652,7 +675,7 @@
|
|||
"name": "uni48",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 33
|
||||
},
|
||||
{
|
||||
|
|
@ -671,7 +694,7 @@
|
|||
"name": "uni49",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 34
|
||||
},
|
||||
{
|
||||
|
|
@ -690,7 +713,7 @@
|
|||
"name": "uni4A",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 35
|
||||
},
|
||||
{
|
||||
|
|
@ -709,7 +732,7 @@
|
|||
"name": "uni4B",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 36
|
||||
},
|
||||
{
|
||||
|
|
@ -728,7 +751,7 @@
|
|||
"name": "uni4C",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 37
|
||||
},
|
||||
{
|
||||
|
|
@ -747,7 +770,7 @@
|
|||
"name": "uni4D",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 38
|
||||
},
|
||||
{
|
||||
|
|
@ -766,7 +789,7 @@
|
|||
"name": "uni4E",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 39
|
||||
},
|
||||
{
|
||||
|
|
@ -785,7 +808,7 @@
|
|||
"name": "uni4F",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 40
|
||||
},
|
||||
{
|
||||
|
|
@ -804,7 +827,7 @@
|
|||
"name": "uni50",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 41
|
||||
},
|
||||
{
|
||||
|
|
@ -823,7 +846,7 @@
|
|||
"name": "uni51",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 42
|
||||
},
|
||||
{
|
||||
|
|
@ -842,7 +865,7 @@
|
|||
"name": "uni79",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 43
|
||||
},
|
||||
{
|
||||
|
|
@ -861,7 +884,7 @@
|
|||
"name": "uni52",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 44
|
||||
},
|
||||
{
|
||||
|
|
@ -880,7 +903,7 @@
|
|||
"name": "uni54",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 45
|
||||
},
|
||||
{
|
||||
|
|
@ -899,7 +922,7 @@
|
|||
"name": "uni57",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 46
|
||||
},
|
||||
{
|
||||
|
|
@ -918,7 +941,7 @@
|
|||
"name": "uni58",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 47
|
||||
},
|
||||
{
|
||||
|
|
@ -937,7 +960,7 @@
|
|||
"name": "uni59",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 48
|
||||
},
|
||||
{
|
||||
|
|
@ -956,7 +979,7 @@
|
|||
"name": "uni5A",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 49
|
||||
},
|
||||
{
|
||||
|
|
@ -975,7 +998,7 @@
|
|||
"name": "uni56",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 50
|
||||
},
|
||||
{
|
||||
|
|
@ -994,7 +1017,7 @@
|
|||
"name": "uni31",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 51
|
||||
},
|
||||
{
|
||||
|
|
@ -1013,7 +1036,7 @@
|
|||
"name": "uni55",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 52
|
||||
},
|
||||
{
|
||||
|
|
@ -1032,7 +1055,7 @@
|
|||
"name": "uni33",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 53
|
||||
},
|
||||
{
|
||||
|
|
@ -1051,7 +1074,7 @@
|
|||
"name": "uni32",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 54
|
||||
},
|
||||
{
|
||||
|
|
@ -1070,7 +1093,7 @@
|
|||
"name": "uni35",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 55
|
||||
},
|
||||
{
|
||||
|
|
@ -1089,7 +1112,7 @@
|
|||
"name": "uni36",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 56
|
||||
},
|
||||
{
|
||||
|
|
@ -1108,7 +1131,7 @@
|
|||
"name": "uni30",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 57
|
||||
},
|
||||
{
|
||||
|
|
@ -1127,7 +1150,7 @@
|
|||
"name": "uni3F",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 58
|
||||
},
|
||||
{
|
||||
|
|
@ -1146,7 +1169,7 @@
|
|||
"name": "uni21",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 59
|
||||
},
|
||||
{
|
||||
|
|
@ -1165,7 +1188,7 @@
|
|||
"name": "uni39",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 60
|
||||
},
|
||||
{
|
||||
|
|
@ -1184,7 +1207,7 @@
|
|||
"name": "uni53",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 61
|
||||
},
|
||||
{
|
||||
|
|
@ -1203,7 +1226,7 @@
|
|||
"name": "uni34",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 62
|
||||
},
|
||||
{
|
||||
|
|
@ -1222,7 +1245,7 @@
|
|||
"name": "uni37",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"iconIdx": 63
|
||||
}
|
||||
],
|
||||
|
|
@ -1245,13 +1268,14 @@
|
|||
"baseline": 6.25,
|
||||
"whitespace": 50
|
||||
},
|
||||
"showMetadata": false,
|
||||
"showMetrics": true,
|
||||
"useClassSelector": false,
|
||||
"classSelector": ".icon",
|
||||
"embed": false
|
||||
"resetPoint": 58880
|
||||
},
|
||||
"imagePref": {
|
||||
"color": 0,
|
||||
"height": 32,
|
||||
"columns": 16,
|
||||
"margin": 16
|
||||
},
|
||||
"imagePref": {},
|
||||
"historySize": 100,
|
||||
"showCodes": true,
|
||||
"search": "",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -17,6 +17,7 @@
|
|||
<glyph unicode="5" d="M135 424h241v-23h-241zM405 247l-127-124v222h-45v-220l-125 122-33-32 181-181 181 181z" />
|
||||
<glyph unicode="6" d="M136 424h241v-23h-241zM108 122l126 124v-222h45v220l126-122 32 32-181 181-181-181z" />
|
||||
<glyph unicode="7" d="M387.836 13.063h-263.672c-43.671 0-79.102 35.431-79.102 79.101v263.672c0 34.607 22.248 63.446 52.734 74.158v-34.607c0-22.248 18.127-39.551 39.551-39.551s39.551 17.303 39.551 39.551v39.551h158.203v-39.551c0-22.248 18.127-39.551 39.551-39.551s39.551 17.303 39.551 39.551v34.607c30.487-10.712 52.735-39.551 52.735-74.158v-263.672c0-43.671-35.431-79.101-79.101-79.101zM414.203 303.101h-316.406v-210.938c0-14.832 11.535-26.367 26.367-26.367h263.672c14.832 0 26.367 11.536 26.367 26.367zM308.735 171.265h52.735v-52.735h-52.735zM308.735 250.367h52.735v-52.734h-52.735zM229.633 171.265h52.734v-52.735h-52.734zM229.633 250.367h52.734v-52.734h-52.734zM150.531 171.265h52.734v-52.735h-52.734zM150.531 250.367h52.734v-52.734h-52.734zM374.652 382.203c-7.416 0-13.183 5.768-13.183 13.184v39.551h26.367v-39.551c0-7.416-5.768-13.184-13.183-13.184zM137.347 382.203c-7.416 0-13.183 5.768-13.183 13.184v39.551h26.367v-39.551c0-7.416-5.768-13.184-13.184-13.184z" />
|
||||
<glyph unicode="8" d="M316.457 126.253l-86.823 86.825v116.391h52.734v-94.547l71.38-71.379zM256 434.938c-116.498 0-210.938-94.44-210.938-210.938s94.44-210.938 210.938-210.938 210.938 94.44 210.938 210.938-94.44 210.938-210.938 210.938zM256 65.797c-87.374 0-158.203 70.829-158.203 158.203s70.829 158.203 158.203 158.203c87.374 0 158.203-70.829 158.203-158.203s-70.829-158.203-158.203-158.203z" />
|
||||
<glyph unicode="9" d="M256 449c-123.926 0-225-101.074-225-225s101.074-225 225-225c123.926 0 225 101.074 225 225s-101.074 225-225 225zM220.844 120.289l-102.832 103.711 39.551 39.551 63.281-64.16 135.351 135.351 39.551-39.551z" />
|
||||
<glyph unicode="?" d="M253.188 445.25c60.938 0 112.5-20.625 156.563-62.813 43.125-42.188 65.625-93.75 67.5-154.688 0-60.938-20.625-113.438-63.75-156.563-42.188-44.063-93.75-66.563-154.688-68.438-60.938 0-113.438 20.625-156.563 63.75-44.063 42.188-66.563 93.75-67.5 154.688s19.688 113.438 62.813 156.563c43.125 44.063 94.688 66.563 155.625 67.5zM252.25 89.938c9.375 0 17.813 2.813 23.438 8.438 5.625 6.563 9.375 14.063 9.375 22.5 0 10.313-1.875 17.813-8.438 24.375-5.625 5.625-14.063 8.438-23.438 8.438 0 0-0.938 0-0.938 0-9.375 0-16.875-2.813-22.5-8.438-6.563-5.625-9.375-13.125-10.313-22.5 0-9.375 2.813-16.875 9.375-23.438 5.625-5.625 13.125-9.375 22.5-9.375 0 0 0.938 0 0.938 0zM331.938 247.438c8.438 10.313 12.188 22.5 12.188 37.5 0 24.375-8.438 43.125-25.313 55.313s-38.438 17.813-64.688 17.813c-20.625 0-37.5-3.75-49.688-12.188-22.5-13.125-33.75-36.563-34.688-70.313 0 0 0-1.875 0-1.875s52.5 0 52.5 0c0 0 0 1.875 0 1.875 0 8.438 2.813 16.875 7.5 26.25 5.625 7.5 14.063 11.25 26.25 11.25 13.125 0 21.563-2.813 25.313-9.375 4.688-6.563 7.5-13.125 7.5-21.563 0-5.625-2.813-12.188-7.5-18.75-2.813-3.75-6.563-7.5-10.313-9.375 0 0-2.813-1.875-2.813-1.875-1.875-1.875-3.75-3.75-7.5-5.625-2.813-1.875-6.563-4.688-9.375-7.5-3.75-1.875-6.563-4.688-10.313-7.5s-6.563-5.625-8.438-8.438c-3.75-6.563-6.563-18.75-8.438-37.5 0 0 0-3.75 0-3.75s52.5 0 52.5 0c0 0 0 1.875 0 1.875 0 3.75 0 8.438 1.875 13.125 1.875 6.563 5.625 12.188 13.125 17.813 0 0 13.125 8.438 13.125 8.438 15 11.25 23.438 18.75 27.188 24.375z" />
|
||||
<glyph unicode="A" d="M232 109l176 175c3 4 5 8 5 13s-2 9-5 13l-29 29c-4 4-8 6-13 6s-10-2-13-6l-134-133-60 60c-3 4-8 5-13 5s-9-1-13-5l-29-29c-3-4-5-8-5-13s2-9 5-13l103-102c3-4 7-6 12-6s10 2 13 6zM475 361v-274c0-23-8-42-24-58s-35-24-58-24h-274c-23 0-42 8-58 24s-24 35-24 58v274c0 23 8 42 24 58s35 24 58 24h274c23 0 42-8 58-24s24-35 24-58z" />
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -44,6 +44,7 @@ form{
|
|||
}
|
||||
}
|
||||
label{
|
||||
width:auto;
|
||||
color:white;
|
||||
}
|
||||
input[type=submit]{
|
||||
|
|
@ -83,24 +84,9 @@ form{
|
|||
.field{
|
||||
padding:0;
|
||||
}
|
||||
.iconfield:before{
|
||||
.iconfield .input:before{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.full label{
|
||||
@include border-radius(2px);
|
||||
text-transform:uppercase;
|
||||
padding:2px 5px;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
margin-top:-1px;
|
||||
font-size:0.7em;
|
||||
z-index:1;
|
||||
margin:0.2em 0;
|
||||
line-height:1.5em;
|
||||
font-weight:normal;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special full-width, one-off fields i.e a single text or textarea input */
|
||||
.full input{
|
||||
|
|
@ -173,7 +159,7 @@ form{
|
|||
margin:0px (-$desktop-nice-padding);
|
||||
|
||||
.iconfield{
|
||||
&:before{
|
||||
.input:before{
|
||||
display:inline-block;
|
||||
position: absolute;
|
||||
color:$color-grey-4;
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.objects{
|
||||
background:url("#{$static-root}bg-dark-diag.svg");
|
||||
}
|
||||
.object{
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
|
|
@ -80,9 +77,9 @@
|
|||
|
||||
> h2, &.single-field label{
|
||||
-webkit-font-smoothing: auto;
|
||||
background:$color-grey-3;
|
||||
background:$color-salmon-light;
|
||||
text-transform:uppercase;
|
||||
padding:0.9em 0 0.9em 4em;
|
||||
padding:0.9em 0 0.9em 4.1em;
|
||||
font-size:0.95em;
|
||||
margin:0 0 0.2em 0;
|
||||
line-height:1.5em;
|
||||
|
|
@ -92,10 +89,10 @@
|
|||
left:0;
|
||||
right:0;
|
||||
z-index:1;
|
||||
text-shadow:1px 1px 1px rgba(255,255,255,0.5);
|
||||
@include box-shadow(0 0 7px 0 rgba(0,0,0,0.4));
|
||||
overflow:hidden;
|
||||
|
||||
&:before{
|
||||
text-shadow:none;
|
||||
font-family:wagtail;
|
||||
text-transform:none;
|
||||
content:"q";
|
||||
|
|
@ -108,10 +105,11 @@
|
|||
line-height:1.8em;
|
||||
left:0px;
|
||||
width:1.7em;
|
||||
opacity:0.15;
|
||||
color:white;
|
||||
padding:0;
|
||||
margin:0;
|
||||
cursor:pointer;
|
||||
background-color:$color-salmon;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +184,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* special panel for the publishing fields, requires a bit more pizzazz */
|
||||
&.publishing{
|
||||
h2:before{
|
||||
content:"7";
|
||||
font-size:2.4em;
|
||||
line-height:1.4em;
|
||||
width:1.4em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.title input,
|
||||
&.title textarea{
|
||||
font-size:2em;
|
||||
|
|
@ -235,7 +244,6 @@
|
|||
top:0px;
|
||||
left:0px;
|
||||
width:3.3em;
|
||||
background-color:$color-teal;
|
||||
padding:0;
|
||||
margin:0 0 0 -20px;
|
||||
cursor:pointer;
|
||||
|
|
@ -246,6 +254,7 @@
|
|||
display:inline-block;
|
||||
padding:0;
|
||||
width:3.45em;
|
||||
background-color:$color-salmon;
|
||||
|
||||
&:before{
|
||||
position:relative;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ section{
|
|||
.color-grey-5{
|
||||
background-color:$color-grey-5;
|
||||
}
|
||||
.color-salmon{
|
||||
background-color:$color-salmon;
|
||||
}
|
||||
.color-salmon-light{
|
||||
background-color:$color-salmon-light;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
.hallotoolbar{
|
||||
position:absolute;
|
||||
left:$mobile-nice-padding;
|
||||
z-index:5;
|
||||
margin-top:4em;
|
||||
margin-left:0em;
|
||||
margin-left:1.2em;
|
||||
}
|
||||
/* full is added to hallotoolbar when invoked on a field set to the full layout style */
|
||||
.hallotoolbar.full{
|
||||
margin-left:$mobile-nice-padding;
|
||||
}
|
||||
.hallotoolbar.affixed{
|
||||
position:fixed;
|
||||
|
|
@ -150,7 +153,7 @@
|
|||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
.hallotoolbar{
|
||||
left:$menu-width + $desktop-nice-padding;
|
||||
.hallotoolbar.full{
|
||||
margin-left:$desktop-nice-padding;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,11 @@ $breakpoint-desktop-larger: 100em; /* 1600px */
|
|||
$color-teal: #43b1b0;
|
||||
$color-teal-darker: darken($color-teal, 10%);
|
||||
$color-teal-dark: #246060;
|
||||
$color-red: #f7474e;
|
||||
$color-red: #cd3238;
|
||||
$color-orange:#e9b04d;
|
||||
$color-green: #189370;
|
||||
$color-salmon: #f37e77;
|
||||
$color-salmon-light: #fcf2f2;
|
||||
|
||||
/* darker to lighter */
|
||||
$color-grey-1: #333333;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "wagtailadmin/edit_handlers/field_panel_field.html" %}
|
||||
{% extends "wagtailadmin/shared/field.html" %}
|
||||
{% load i18n %}
|
||||
{% comment %}
|
||||
Either the chosen or unchosen div will be shown, depending on the presence
|
||||
|
|
|
|||
|
|
@ -1,23 +1 @@
|
|||
<div class="field">
|
||||
{{ 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>
|
||||
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<ul class="field-row {{ self.classes|join:" " }}">
|
||||
{% for child in self.children %}
|
||||
<li class="field-col {{ child.classes|join:" " }}">
|
||||
{{ child.render_as_field }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -2,9 +2,7 @@
|
|||
<legend>{{ self.heading }}</legend>
|
||||
<ul class="fields">
|
||||
{% for child in self.children %}
|
||||
<li {% if child.field_classnames %}class="{{ child.field_classnames }}"{% endif %}>
|
||||
{{ child.render_as_field }}
|
||||
</li>
|
||||
<li class="{{ child.classes|join:" " }}">{{ child.render_as_field }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<ul class="objects">
|
||||
{% for child in self.children %}
|
||||
<li class="object {{ child.object_classnames }}">
|
||||
<li class="object {{ child.classes|join:" " }}">
|
||||
{% if child.heading %}
|
||||
<h2>{{ child.heading }}</h2>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<fieldset>
|
||||
<legend>{{ self.heading }}</legend>
|
||||
<ul class="fields">
|
||||
<li class="{{ self.field_classnames }}">{{ field_content }}</li>
|
||||
<li>{{ field_content }}</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
<ul class="tab-nav merged">
|
||||
{% for child in self.children %}
|
||||
<li class="{% if forloop.first %}active{% endif %}"><a href="#{{ child.heading|slugify }}" class="{% if forloop.first %}active{% endif %}">{{ child.heading }}</a></li>
|
||||
<li class="{{ child.classes|join:" " }} {% if forloop.first %}active{% endif %}"><a href="#{{ child.heading|slugify }}" class="{% if forloop.first %}active{% endif %}">{{ child.heading }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{% for child in self.children %}
|
||||
<section id="{{ child.heading|slugify }}" class="{% if forloop.first %}active{% endif %}">
|
||||
<section id="{{ child.heading|slugify }}" class="{{ child.classes|join:" " }} {% if forloop.first %}active{% endif %}">
|
||||
{{ child.render_as_object }}
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -28,17 +28,17 @@
|
|||
|
||||
<ul class="fields">
|
||||
<li class="full">
|
||||
<div class="field">
|
||||
<div class="field iconfield">
|
||||
{{ form.username.label_tag }}
|
||||
<div class="input iconfield icon-user">
|
||||
<div class="input icon-user">
|
||||
{{ form.username }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="full">
|
||||
<div class="field">
|
||||
<div class="field iconfield">
|
||||
{{ form.password.label_tag }}
|
||||
<div class="input iconfield icon-password">
|
||||
<div class="input icon-password">
|
||||
{{ form.password }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,9 @@
|
|||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/tag-it.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/expanding_formset.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/modal-workflow.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtail-toolbar.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-hr.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/page-editor.js"></script>
|
||||
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/page-chooser.js"></script>
|
||||
<script src="{{ STATIC_URL }}admin/js/urlify.js"></script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
<div class="field {{ field.field_classnames }} {{ field|fieldtype }} {{ field_classes }}">
|
||||
{{ field.label_tag }}
|
||||
<div class="field-content">
|
||||
<div class="input {{ field.input_classnames }} {{ input_classes }} ">
|
||||
{% block form_field %}
|
||||
{{ field }}
|
||||
{% endblock %}
|
||||
|
||||
{# This span only used on rare occasions by certain types of input #}
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,25 +1,4 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
<li class="{% if field.field.required %}required{% endif %} {{ field.css_classes }} {{ field|fieldtype }} {{ li_classes }} {% if field.errors %}error{% endif %}">
|
||||
<div class="field">
|
||||
{{ 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 %}
|
||||
</div>
|
||||
</div>
|
||||
<li class="{% if field.field.required %}required{% endif %} {{ wrapper_classes }} {{ li_classes }} {% if field.errors %}error{% endif %}">
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
</li>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<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 input_classes="field-small iconfield icon-search" %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field field_classes="field-small iconfield" input_classes="icon-search" %}
|
||||
{% endfor %}
|
||||
<li class="submit visuallyhidden"><input type="submit" value="Search" class="button" /></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.core import mail
|
||||
from django.core.paginator import Paginator
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail.tests.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild, BusinessSubIndex
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
|
|
@ -170,6 +173,61 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
self.assertIsInstance(page, SimplePage)
|
||||
self.assertFalse(page.live)
|
||||
|
||||
def test_create_simplepage_scheduled(self):
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
post_data = {
|
||||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# Find the page and check the scheduled times
|
||||
page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific
|
||||
self.assertEquals(page.go_live_at.date(), go_live_at.date())
|
||||
self.assertEquals(page.expire_at.date(), expire_at.date())
|
||||
self.assertEquals(page.expired, False)
|
||||
self.assertTrue(page.status_string, "draft")
|
||||
|
||||
# No revisions with approved_go_live_at
|
||||
self.assertFalse(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
def test_create_simplepage_scheduled_go_live_before_expiry(self):
|
||||
post_data = {
|
||||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(timezone.now() + timedelta(days=2)).split('.')[0],
|
||||
'expire_at': str(timezone.now() + timedelta(days=1)).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that a form error was raised
|
||||
self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time")
|
||||
self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time")
|
||||
|
||||
def test_create_simplepage_scheduled_expire_in_the_past(self):
|
||||
post_data = {
|
||||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'expire_at': str(timezone.now() + timedelta(days=-1)).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that a form error was raised
|
||||
self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future")
|
||||
|
||||
def test_create_simplepage_post_publish(self):
|
||||
# Connect a mock signal handler to page_published signal
|
||||
signal_fired = [False]
|
||||
|
|
@ -197,10 +255,40 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
self.assertIsInstance(page, SimplePage)
|
||||
self.assertTrue(page.live)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Check that the page_published signal was fired
|
||||
self.assertTrue(signal_fired[0])
|
||||
self.assertEqual(signal_page[0], page)
|
||||
self.assertEqual(signal_page[0], signal_page[0].specific)
|
||||
=======
|
||||
def test_create_simplepage_post_publish_scheduled(self):
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
post_data = {
|
||||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# Find the page and check it
|
||||
page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific
|
||||
self.assertEquals(page.go_live_at.date(), go_live_at.date())
|
||||
self.assertEquals(page.expire_at.date(), expire_at.date())
|
||||
self.assertEquals(page.expired, False)
|
||||
|
||||
# A revision with approved_go_live_at should exist now
|
||||
self.assertTrue(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists())
|
||||
# But Page won't be live
|
||||
self.assertFalse(page.live)
|
||||
self.assertTrue(page.status_string, "scheduled")
|
||||
>>>>>>> master
|
||||
|
||||
def test_create_simplepage_post_submit(self):
|
||||
# Create a moderator user for testing email
|
||||
|
|
@ -341,6 +429,63 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
child_page_new = SimplePage.objects.get(id=self.child_page.id)
|
||||
self.assertTrue(child_page_new.has_unpublished_changes)
|
||||
|
||||
def test_edit_post_scheduled(self):
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
child_page_new = SimplePage.objects.get(id=self.child_page.id)
|
||||
|
||||
# The page will still be live
|
||||
self.assertTrue(child_page_new.live)
|
||||
|
||||
# A revision with approved_go_live_at should not exist
|
||||
self.assertFalse(PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
# But a revision with go_live_at and expire_at in their content json *should* exist
|
||||
self.assertTrue(PageRevision.objects.filter(page=child_page_new, content_json__contains=str(go_live_at.date())).exists())
|
||||
self.assertTrue(PageRevision.objects.filter(page=child_page_new, content_json__contains=str(expire_at.date())).exists())
|
||||
|
||||
def test_edit_scheduled_go_live_before_expiry(self):
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(timezone.now() + timedelta(days=2)).split('.')[0],
|
||||
'expire_at': str(timezone.now() + timedelta(days=1)).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that a form error was raised
|
||||
self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time")
|
||||
self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time")
|
||||
|
||||
def test_edit_scheduled_expire_in_the_past(self):
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'expire_at': str(timezone.now() + timedelta(days=-1)).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that a form error was raised
|
||||
self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future")
|
||||
|
||||
def test_page_edit_post_publish(self):
|
||||
# Connect a mock signal handler to page_published signal
|
||||
signal_fired = [False]
|
||||
|
|
@ -374,6 +519,77 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
# The page shouldn't have "has_unpublished_changes" flag set
|
||||
self.assertFalse(child_page_new.has_unpublished_changes)
|
||||
|
||||
def test_edit_post_publish_scheduled(self):
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
child_page_new = SimplePage.objects.get(id=self.child_page.id)
|
||||
|
||||
# The page should not be live anymore
|
||||
self.assertFalse(child_page_new.live)
|
||||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
def test_edit_post_publish_now_an_already_scheduled(self):
|
||||
# First let's publish a page with a go_live_at in the future
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
child_page_new = SimplePage.objects.get(id=self.child_page.id)
|
||||
|
||||
# The page should not be live anymore
|
||||
self.assertFalse(child_page_new.live)
|
||||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
# Now, let's edit it and publish it right now
|
||||
go_live_at = timezone.now()
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': "",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
child_page_new = SimplePage.objects.get(id=self.child_page.id)
|
||||
|
||||
# The page should be live now
|
||||
self.assertTrue(child_page_new.live)
|
||||
|
||||
# And a revision with approved_go_live_at should not exist
|
||||
self.assertFalse(PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
def test_page_edit_post_submit(self):
|
||||
# Create a moderator user for testing email
|
||||
moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password')
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from django.contrib import messages
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
|
|
@ -142,21 +143,63 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
|
|||
return slug
|
||||
form.fields['slug'].clean = clean_slug
|
||||
|
||||
# Stick another validator into the form to check that the scheduled publishing settings are set correctly
|
||||
def clean():
|
||||
cleaned_data = form_class.clean(form)
|
||||
|
||||
# Go live must be before expire
|
||||
go_live_at = cleaned_data.get('go_live_at')
|
||||
expire_at = cleaned_data.get('expire_at')
|
||||
|
||||
if go_live_at and expire_at:
|
||||
if go_live_at > expire_at:
|
||||
msg = _('Go live date/time must be before expiry date/time')
|
||||
form._errors['go_live_at'] = form.error_class([msg])
|
||||
form._errors['expire_at'] = form.error_class([msg])
|
||||
del cleaned_data['go_live_at']
|
||||
del cleaned_data['expire_at']
|
||||
|
||||
# Expire must be in the future
|
||||
expire_at = cleaned_data.get('expire_at')
|
||||
|
||||
if expire_at and expire_at < timezone.now():
|
||||
form._errors['expire_at'] = form.error_class([_('Expiry date/time must be in the future')])
|
||||
del cleaned_data['expire_at']
|
||||
|
||||
return cleaned_data
|
||||
form.clean = clean
|
||||
|
||||
if form.is_valid():
|
||||
page = form.save(commit=False) # don't save yet, as we need treebeard to assign tree params
|
||||
|
||||
is_publishing = bool(request.POST.get('action-publish')) and parent_page_perms.can_publish_subpage()
|
||||
is_submitting = bool(request.POST.get('action-submit'))
|
||||
go_live_at = form.cleaned_data.get('go_live_at')
|
||||
future_go_live = go_live_at and go_live_at > timezone.now()
|
||||
approved_go_live_at = None
|
||||
|
||||
if is_publishing:
|
||||
page.live = True
|
||||
page.has_unpublished_changes = False
|
||||
page.expired = False
|
||||
if future_go_live:
|
||||
page.live = False
|
||||
# Set approved_go_live_at only if is publishing
|
||||
# and the future_go_live is actually in future
|
||||
approved_go_live_at = go_live_at
|
||||
else:
|
||||
page.live = True
|
||||
else:
|
||||
page.live = False
|
||||
page.has_unpublished_changes = True
|
||||
|
||||
parent_page.add_child(instance=page) # assign tree parameters - will cause page to be saved
|
||||
page.save_revision(user=request.user, submitted_for_moderation=is_submitting)
|
||||
|
||||
# Pass approved_go_live_at to save_revision
|
||||
page.save_revision(
|
||||
user=request.user,
|
||||
submitted_for_moderation=is_submitting,
|
||||
approved_go_live_at=approved_go_live_at
|
||||
)
|
||||
|
||||
if is_publishing:
|
||||
page_published.send(sender=page_class, instance=page)
|
||||
|
|
@ -174,7 +217,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
|
|||
|
||||
return redirect('wagtailadmin_explore', page.get_parent().id)
|
||||
else:
|
||||
messages.error(request, _("The page could not be created due to errors."))
|
||||
messages.error(request, _("The page could not be created due to validation errors"))
|
||||
edit_handler = edit_handler_class(instance=page, form=form)
|
||||
else:
|
||||
signals.init_new_page.send(sender=create, page=page, parent=parent_page)
|
||||
|
|
@ -217,15 +260,54 @@ def edit(request, page_id):
|
|||
return slug
|
||||
form.fields['slug'].clean = clean_slug
|
||||
|
||||
# Stick another validator into the form to check that the scheduled publishing settings are set correctly
|
||||
def clean():
|
||||
cleaned_data = form_class.clean(form)
|
||||
|
||||
# Go live must be before expire
|
||||
go_live_at = cleaned_data.get('go_live_at')
|
||||
expire_at = cleaned_data.get('expire_at')
|
||||
|
||||
if go_live_at and expire_at:
|
||||
if go_live_at > expire_at:
|
||||
msg = _('Go live date/time must be before expiry date/time')
|
||||
form._errors['go_live_at'] = form.error_class([msg])
|
||||
form._errors['expire_at'] = form.error_class([msg])
|
||||
del cleaned_data['go_live_at']
|
||||
del cleaned_data['expire_at']
|
||||
|
||||
# Expire must be in the future
|
||||
expire_at = cleaned_data.get('expire_at')
|
||||
|
||||
if expire_at and expire_at < timezone.now():
|
||||
form._errors['expire_at'] = form.error_class([_('Expiry date/time must be in the future')])
|
||||
del cleaned_data['expire_at']
|
||||
|
||||
return cleaned_data
|
||||
form.clean = clean
|
||||
|
||||
if form.is_valid():
|
||||
is_publishing = bool(request.POST.get('action-publish')) and page_perms.can_publish()
|
||||
is_submitting = bool(request.POST.get('action-submit'))
|
||||
go_live_at = form.cleaned_data.get('go_live_at')
|
||||
future_go_live = go_live_at and go_live_at > timezone.now()
|
||||
approved_go_live_at = None
|
||||
|
||||
if is_publishing:
|
||||
page.live = True
|
||||
page.has_unpublished_changes = False
|
||||
page.expired = False
|
||||
if future_go_live:
|
||||
page.live = False
|
||||
# Set approved_go_live_at only if publishing
|
||||
approved_go_live_at = go_live_at
|
||||
else:
|
||||
page.live = True
|
||||
form.save()
|
||||
page.revisions.update(submitted_for_moderation=False)
|
||||
# Clear approved_go_live_at for older revisions
|
||||
page.revisions.update(
|
||||
submitted_for_moderation=False,
|
||||
approved_go_live_at=None,
|
||||
)
|
||||
else:
|
||||
# not publishing the page
|
||||
if page.live:
|
||||
|
|
@ -237,7 +319,11 @@ def edit(request, page_id):
|
|||
page.has_unpublished_changes = True
|
||||
form.save()
|
||||
|
||||
page.save_revision(user=request.user, submitted_for_moderation=is_submitting)
|
||||
page.save_revision(
|
||||
user=request.user,
|
||||
submitted_for_moderation=is_submitting,
|
||||
approved_go_live_at=approved_go_live_at
|
||||
)
|
||||
|
||||
if is_publishing:
|
||||
page_published.send(sender=page.__class__, instance=page)
|
||||
|
|
@ -256,6 +342,7 @@ def edit(request, page_id):
|
|||
return redirect('wagtailadmin_explore', page.get_parent().id)
|
||||
else:
|
||||
messages.error(request, _("The page could not be saved due to validation errors"))
|
||||
|
||||
edit_handler = edit_handler_class(instance=page, form=form)
|
||||
errors_debug = (
|
||||
repr(edit_handler.form.errors)
|
||||
|
|
@ -447,6 +534,8 @@ def unpublish(request, page_id):
|
|||
parent_id = page.get_parent().id
|
||||
page.live = False
|
||||
page.save()
|
||||
# Since page is unpublished clear the approved_go_live_at of all revisions
|
||||
page.revisions.update(approved_go_live_at=None)
|
||||
messages.success(request, _("Page '{0}' unpublished.").format(page.title))
|
||||
return redirect('wagtailadmin_explore', parent_id)
|
||||
|
||||
|
|
@ -549,7 +638,8 @@ def get_page_edit_handler(page_class):
|
|||
if page_class not in PAGE_EDIT_HANDLERS:
|
||||
PAGE_EDIT_HANDLERS[page_class] = TabbedInterface([
|
||||
ObjectList(page_class.content_panels, heading='Content'),
|
||||
ObjectList(page_class.promote_panels, heading='Promote')
|
||||
ObjectList(page_class.promote_panels, heading='Promote'),
|
||||
ObjectList(page_class.settings_panels, heading='Settings', classname="settings")
|
||||
])
|
||||
|
||||
return PAGE_EDIT_HANDLERS[page_class]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import datetime
|
||||
import json
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import dateparse, timezone
|
||||
from wagtail.wagtailcore.models import Page, PageRevision
|
||||
|
||||
|
||||
def revision_date_expired(r):
|
||||
expiry_str = json.loads(r.content_json).get('expire_at')
|
||||
if not expiry_str:
|
||||
return False
|
||||
expire_at = dateparse.parse_datetime(expiry_str)
|
||||
if expire_at < timezone.now():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option(
|
||||
'--dryrun',
|
||||
action='store_true',
|
||||
dest='dryrun',
|
||||
default=False,
|
||||
help='Dry run -- don\'t change anything.'),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dryrun = False
|
||||
if options['dryrun']:
|
||||
print "Will do a dry run."
|
||||
dryrun = True
|
||||
|
||||
# 1. get all expired pages with live = True
|
||||
expired_pages = Page.objects.filter(
|
||||
live=True,
|
||||
expire_at__lt=timezone.now()
|
||||
)
|
||||
if dryrun:
|
||||
if expired_pages:
|
||||
print "Expired pages to be deactivated:"
|
||||
print "Expiry datetime\t\tSlug\t\tName"
|
||||
print "---------------\t\t----\t\t----"
|
||||
for ep in expired_pages:
|
||||
print "{0}\t{1}\t{2}".format(
|
||||
ep.expire_at.strftime("%Y-%m-%d %H:%M"),
|
||||
ep.slug,
|
||||
ep.title
|
||||
)
|
||||
else:
|
||||
print "No expired pages to be deactivated found."
|
||||
else:
|
||||
expired_pages.update(expired=True, live=False)
|
||||
|
||||
# 2. get all page revisions for moderation that have been expired
|
||||
expired_revs = [
|
||||
r for r in PageRevision.objects.filter(
|
||||
submitted_for_moderation=True
|
||||
) if revision_date_expired(r)
|
||||
]
|
||||
if dryrun:
|
||||
print "---------------------------------"
|
||||
if expired_revs:
|
||||
print "Expired revisions to be dropped from moderation queue:"
|
||||
print "Expiry datetime\t\tSlug\t\tName"
|
||||
print "---------------\t\t----\t\t----"
|
||||
for er in expired_revs:
|
||||
rev_data = json.loads(er.content_json)
|
||||
print "{0}\t{1}\t{2}".format(
|
||||
dateparse.parse_datetime(
|
||||
rev_data.get('expire_at')
|
||||
).strftime("%Y-%m-%d %H:%M"),
|
||||
rev_data.get('slug'),
|
||||
rev_data.get('title')
|
||||
)
|
||||
else:
|
||||
print "No expired revision to be dropped from moderation."
|
||||
else:
|
||||
for er in expired_revs:
|
||||
er.submitted_for_moderation = False
|
||||
er.save()
|
||||
|
||||
# 3. get all revisions that need to be published
|
||||
revs_for_publishing = PageRevision.objects.filter(
|
||||
approved_go_live_at__lt=timezone.now()
|
||||
)
|
||||
if dryrun:
|
||||
print "---------------------------------"
|
||||
if revs_for_publishing:
|
||||
print "Revisions to be published:"
|
||||
print "Go live datetime\t\tSlug\t\tName"
|
||||
print "---------------\t\t\t----\t\t----"
|
||||
for rp in revs_for_publishing:
|
||||
rev_data = json.loads(rp.content_json)
|
||||
print "{0}\t\t{1}\t{2}".format(
|
||||
rp.approved_go_live_at.strftime("%Y-%m-%d %H:%M"),
|
||||
rev_data.get('slug'),
|
||||
rev_data.get('title')
|
||||
)
|
||||
else:
|
||||
print "No pages to go live."
|
||||
else:
|
||||
for rp in revs_for_publishing:
|
||||
# just run publish for the revision -- since the approved go
|
||||
# live datetime is before now it will make the page live
|
||||
rp.publish()
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# -*- 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
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Removing unique constraint on 'Site', fields ['hostname']
|
||||
db.delete_unique(u'wagtailcore_site', ['hostname'])
|
||||
|
||||
# Adding unique constraint on 'Site', fields ['hostname', 'port']
|
||||
db.create_unique(u'wagtailcore_site', ['hostname', 'port'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Site', fields ['hostname', 'port']
|
||||
db.delete_unique(u'wagtailcore_site', ['hostname', 'port'])
|
||||
|
||||
# Adding unique constraint on 'Site', fields ['hostname']
|
||||
db.create_unique(u'wagtailcore_site', ['hostname'])
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "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.grouppagepermission': {
|
||||
'Meta': {'object_name': 'GroupPagePermission'},
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': u"orm['auth.Group']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': u"orm['wagtailcore.Page']"}),
|
||||
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'})
|
||||
},
|
||||
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['auth.User']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
|
||||
},
|
||||
u'wagtailcore.pagerevision': {
|
||||
'Meta': {'object_name': 'PageRevision'},
|
||||
'content_json': ('django.db.models.fields.TextField', [], {}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': u"orm['wagtailcore.Page']"}),
|
||||
'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'wagtailcore.site': {
|
||||
'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'},
|
||||
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
|
||||
'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': u"orm['wagtailcore.Page']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['wagtailcore']
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
# -*- 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
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'PageRevision.approved_go_live_at'
|
||||
db.add_column(u'wagtailcore_pagerevision', 'approved_go_live_at',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Page.go_live_at'
|
||||
db.add_column(u'wagtailcore_page', 'go_live_at',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Page.expire_at'
|
||||
db.add_column(u'wagtailcore_page', 'expire_at',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Page.expired'
|
||||
db.add_column(u'wagtailcore_page', 'expired',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'PageRevision.approved_go_live_at'
|
||||
db.delete_column(u'wagtailcore_pagerevision', 'approved_go_live_at')
|
||||
|
||||
# Deleting field 'Page.go_live_at'
|
||||
db.delete_column(u'wagtailcore_page', 'go_live_at')
|
||||
|
||||
# Deleting field 'Page.expire_at'
|
||||
db.delete_column(u'wagtailcore_page', 'expire_at')
|
||||
|
||||
# Deleting field 'Page.expired'
|
||||
db.delete_column(u'wagtailcore_page', 'expired')
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "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.grouppagepermission': {
|
||||
'Meta': {'object_name': 'GroupPagePermission'},
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': u"orm['auth.Group']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': u"orm['wagtailcore.Page']"}),
|
||||
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'})
|
||||
},
|
||||
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', [], {}),
|
||||
'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
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['auth.User']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
|
||||
},
|
||||
u'wagtailcore.pagerevision': {
|
||||
'Meta': {'object_name': 'PageRevision'},
|
||||
'approved_go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_json': ('django.db.models.fields.TextField', [], {}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': u"orm['wagtailcore.Page']"}),
|
||||
'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'wagtailcore.site': {
|
||||
'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'},
|
||||
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
|
||||
'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': u"orm['wagtailcore.Page']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['wagtailcore']
|
||||
|
|
@ -14,7 +14,10 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from django.contrib.auth.models import Group
|
||||
from django.conf import settings
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
|
|
@ -26,29 +29,47 @@ from wagtail.wagtailsearch import Indexed, get_search_backend
|
|||
|
||||
|
||||
class SiteManager(models.Manager):
|
||||
def get_by_natural_key(self, hostname):
|
||||
return self.get(hostname=hostname)
|
||||
def get_by_natural_key(self, hostname, port):
|
||||
return self.get(hostname=hostname, port=port)
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
hostname = models.CharField(max_length=255, unique=True, db_index=True)
|
||||
hostname = models.CharField(max_length=255, db_index=True)
|
||||
port = models.IntegerField(default=80, help_text=_("Set this to something other than 80 if you need a specific port number to appear in URLs (e.g. development on port 8000). Does not affect request handling (so port forwarding still works)."))
|
||||
root_page = models.ForeignKey('Page', related_name='sites_rooted_here')
|
||||
is_default_site = models.BooleanField(default=False, help_text=_("If true, this site will handle requests for all other hostnames that do not have a site entry of their own"))
|
||||
|
||||
class Meta:
|
||||
unique_together = ('hostname', 'port')
|
||||
|
||||
def natural_key(self):
|
||||
return (self.hostname,)
|
||||
return (self.hostname, self.port)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.hostname + ("" if self.port == 80 else (":%d" % self.port)) + (" [default]" if self.is_default_site else "")
|
||||
|
||||
@staticmethod
|
||||
def find_for_request(request):
|
||||
"""Find the site object responsible for responding to this HTTP request object"""
|
||||
"""
|
||||
Find the site object responsible for responding to this HTTP
|
||||
request object. Try:
|
||||
- unique hostname first
|
||||
- then hostname and port
|
||||
- if there is no matching hostname at all, or no matching
|
||||
hostname:port combination, fall back to the unique default site,
|
||||
or raise an exception
|
||||
NB this means that high-numbered ports on an extant hostname may
|
||||
still be routed to a different hostname which is set as the default
|
||||
"""
|
||||
try:
|
||||
hostname = request.META['HTTP_HOST'].split(':')[0]
|
||||
# find a Site matching this specific hostname
|
||||
return Site.objects.get(hostname=hostname)
|
||||
hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause
|
||||
try:
|
||||
# find a Site matching this specific hostname
|
||||
return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause
|
||||
except Site.MultipleObjectsReturned:
|
||||
# as there were more than one, try matching by port too
|
||||
port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause
|
||||
return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause
|
||||
except (Site.DoesNotExist, KeyError):
|
||||
# If no matching site exists, or request does not specify an HTTP_HOST (which
|
||||
# will often be the case for the Django test client), look for a catch-all Site.
|
||||
|
|
@ -64,6 +85,24 @@ class Site(models.Model):
|
|||
else:
|
||||
return 'http://%s:%d' % (self.hostname, self.port)
|
||||
|
||||
def clean_fields(self, exclude=None):
|
||||
super(Site, self).clean_fields(exclude)
|
||||
# Only one site can have the is_default_site flag set
|
||||
try:
|
||||
default = Site.objects.get(is_default_site=True)
|
||||
except Site.DoesNotExist:
|
||||
pass
|
||||
except Site.MultipleObjectsReturned:
|
||||
raise
|
||||
else:
|
||||
if self.is_default_site and self.pk != default.pk:
|
||||
raise ValidationError(
|
||||
{'is_default_site': [
|
||||
_("%(hostname)s is already configured as the default site. You must unset that before you can save this site as default.")
|
||||
% { 'hostname': default.hostname }
|
||||
]}
|
||||
)
|
||||
|
||||
# clear the wagtail_site_root_paths cache whenever Site records are updated
|
||||
def save(self, *args, **kwargs):
|
||||
result = super(Site, self).save(*args, **kwargs)
|
||||
|
|
@ -233,6 +272,10 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
show_in_menus = models.BooleanField(default=False, help_text=_("Whether a link to this page will appear in automatically generated menus"))
|
||||
search_description = models.TextField(blank=True)
|
||||
|
||||
go_live_at = models.DateTimeField(verbose_name=_("Go live date/time"), help_text=_("Please add a date-time in the form YYYY-MM-DD hh:mm."), blank=True, null=True)
|
||||
expire_at = models.DateTimeField(verbose_name=_("Expiry date/time"), help_text=_("Please add a date-time in the form YYYY-MM-DD hh:mm."), blank=True, null=True)
|
||||
expired = models.BooleanField(default=False, editable=False)
|
||||
|
||||
indexed_fields = {
|
||||
'title': {
|
||||
'type': 'string',
|
||||
|
|
@ -327,7 +370,7 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
SET url_path = %s || substring(url_path from %s)
|
||||
WHERE path LIKE %s AND id <> %s
|
||||
"""
|
||||
cursor.execute(update_statement,
|
||||
cursor.execute(update_statement,
|
||||
[new_url_path, len(old_url_path) + 1, self.path + '%', self.id])
|
||||
|
||||
@cached_property
|
||||
|
|
@ -373,8 +416,13 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
else:
|
||||
raise Http404
|
||||
|
||||
def save_revision(self, user=None, submitted_for_moderation=False):
|
||||
return self.revisions.create(content_json=self.to_json(), user=user, submitted_for_moderation=submitted_for_moderation)
|
||||
def save_revision(self, user=None, submitted_for_moderation=False, approved_go_live_at=None):
|
||||
return self.revisions.create(
|
||||
content_json=self.to_json(),
|
||||
user=user,
|
||||
submitted_for_moderation=submitted_for_moderation,
|
||||
approved_go_live_at=approved_go_live_at,
|
||||
)
|
||||
|
||||
def get_latest_revision(self):
|
||||
return self.revisions.order_by('-created_at').first()
|
||||
|
|
@ -401,8 +449,8 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
|
||||
def serve(self, request, *args, **kwargs):
|
||||
return TemplateResponse(
|
||||
request,
|
||||
self.get_template(request, *args, **kwargs),
|
||||
request,
|
||||
self.get_template(request, *args, **kwargs),
|
||||
self.get_context(request, *args, **kwargs)
|
||||
)
|
||||
|
||||
|
|
@ -533,13 +581,22 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
@property
|
||||
def status_string(self):
|
||||
if not self.live:
|
||||
return "draft"
|
||||
if self.expired:
|
||||
return "expired"
|
||||
elif self.approved_schedule:
|
||||
return "scheduled"
|
||||
else:
|
||||
return "draft"
|
||||
else:
|
||||
if self.has_unpublished_changes:
|
||||
return "live + draft"
|
||||
else:
|
||||
return "live"
|
||||
|
||||
@property
|
||||
def approved_schedule(self):
|
||||
return self.revisions.exclude(approved_go_live_at__isnull=True).exists()
|
||||
|
||||
def has_unpublished_subtree(self):
|
||||
"""
|
||||
An awkwardly-defined flag used in determining whether unprivileged editors have
|
||||
|
|
@ -733,6 +790,7 @@ class PageRevision(models.Model):
|
|||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
|
||||
content_json = models.TextField()
|
||||
approved_go_live_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
objects = models.Manager()
|
||||
submitted_revisions = SubmittedRevisionsManager()
|
||||
|
|
@ -766,7 +824,19 @@ class PageRevision(models.Model):
|
|||
|
||||
def publish(self):
|
||||
page = self.as_page_object()
|
||||
page.live = True
|
||||
if page.go_live_at and page.go_live_at > timezone.now():
|
||||
# if we have a go_live in the future don't make the page live
|
||||
page.live = False
|
||||
# Instead set the approved_go_live_at of this revision
|
||||
self.approved_go_live_at = page.go_live_at
|
||||
self.save()
|
||||
# And clear the the approved_go_live_at of any other revisions
|
||||
page.revisions.exclude(id=self.id).update(approved_go_live_at=None)
|
||||
else:
|
||||
page.live = True
|
||||
# If page goes live clear the approved_go_live_at of all revisions
|
||||
page.revisions.update(approved_go_live_at=None)
|
||||
page.expired = False # When a page is published it can't be expired
|
||||
page.save()
|
||||
self.submitted_for_moderation = False
|
||||
page.revisions.update(submitted_for_moderation=False)
|
||||
|
|
@ -774,6 +844,7 @@ class PageRevision(models.Model):
|
|||
def __unicode__(self):
|
||||
return '"' + unicode(self.page) + '" at ' + unicode(self.created_at)
|
||||
|
||||
|
||||
PAGE_PERMISSION_TYPE_CHOICES = [
|
||||
('add', 'Add'),
|
||||
('edit', 'Edit'),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from StringIO import StringIO
|
||||
from datetime import timedelta
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.wagtailcore.models import Page, PageRevision, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
|
|
@ -87,3 +89,107 @@ class TestReplaceTextCommand(TestCase):
|
|||
|
||||
# Check that its now about easter
|
||||
self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter")
|
||||
|
||||
|
||||
class TestPublishScheduledPagesCommand(TestCase):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
||||
def test_go_live_page_will_be_published(self):
|
||||
page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
live=False,
|
||||
go_live_at=timezone.now() - timedelta(days=1),
|
||||
)
|
||||
self.root_page.add_child(instance=page)
|
||||
|
||||
page.save_revision(approved_go_live_at=timezone.now() - timedelta(days=1))
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(PageRevision.objects.filter(page=p).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
management.call_command('publish_scheduled_pages')
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertTrue(p.live)
|
||||
self.assertFalse(PageRevision.objects.filter(page=p).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
def test_future_go_live_page_will_not_be_published(self):
|
||||
page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
live=False,
|
||||
go_live_at=timezone.now() + timedelta(days=1),
|
||||
)
|
||||
self.root_page.add_child(instance=page)
|
||||
|
||||
page.save_revision(approved_go_live_at=timezone.now() - timedelta(days=1))
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(PageRevision.objects.filter(page=p).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
management.call_command('publish_scheduled_pages')
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(PageRevision.objects.filter(page=p).exclude(approved_go_live_at__isnull=True).exists())
|
||||
|
||||
def test_expired_page_will_be_unpublished(self):
|
||||
page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
live=True,
|
||||
expire_at=timezone.now() - timedelta(days=1),
|
||||
)
|
||||
self.root_page.add_child(instance=page)
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertTrue(p.live)
|
||||
|
||||
management.call_command('publish_scheduled_pages')
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(p.expired)
|
||||
|
||||
def test_future_expired_page_will_not_be_unpublished(self):
|
||||
page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
live=True,
|
||||
expire_at=timezone.now() + timedelta(days=1),
|
||||
)
|
||||
self.root_page.add_child(instance=page)
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertTrue(p.live)
|
||||
|
||||
management.call_command('publish_scheduled_pages')
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertTrue(p.live)
|
||||
self.assertFalse(p.expired)
|
||||
|
||||
def test_expired_pages_are_dropped_from_mod_queue(self):
|
||||
page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
live=False,
|
||||
expire_at=timezone.now() - timedelta(days=1),
|
||||
)
|
||||
self.root_page.add_child(instance=page)
|
||||
|
||||
page.save_revision(submitted_for_moderation=True)
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(PageRevision.objects.filter(page=p, submitted_for_moderation=True).exists())
|
||||
|
||||
management.call_command('publish_scheduled_pages')
|
||||
|
||||
p = Page.objects.get(slug='hello-world')
|
||||
self.assertFalse(PageRevision.objects.filter(page=p, submitted_for_moderation=True).exists())
|
||||
|
|
|
|||
|
|
@ -9,30 +9,96 @@ from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
|||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestRouting(TestCase):
|
||||
class TestSiteRouting(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_find_site_for_request(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
def setUp(self):
|
||||
self.default_site = Site.objects.get(is_default_site=True)
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
about_page = Page.objects.get(url_path='/home/about-us/')
|
||||
self.events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
self.alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765')
|
||||
self.about_site = Site.objects.create(hostname='about.example.com', root_page=about_page)
|
||||
self.unrecognised_port = '8000'
|
||||
self.unrecognised_hostname = 'unknown.site.com'
|
||||
|
||||
def test_no_host_header_routes_to_default_site(self):
|
||||
# requests without a Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
self.assertEqual(Site.find_for_request(request), self.default_site)
|
||||
|
||||
def test_valid_headers_route_to_specific_site(self):
|
||||
# requests with a known Host: header should be directed to the specific site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'events.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), events_site)
|
||||
request.META['HTTP_HOST'] = self.events_site.hostname
|
||||
request.META['SERVER_PORT'] = self.events_site.port
|
||||
self.assertEqual(Site.find_for_request(request), self.events_site)
|
||||
|
||||
def test_ports_in_request_headers_are_respected(self):
|
||||
# ports in the Host: header should be respected
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = self.alternate_port_events_site.hostname
|
||||
request.META['SERVER_PORT'] = self.alternate_port_events_site.port
|
||||
self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site)
|
||||
|
||||
def test_unrecognised_host_header_routes_to_default_site(self):
|
||||
# requests with an unrecognised Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'unknown.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
request.META['HTTP_HOST'] = self.unrecognised_hostname
|
||||
request.META['SERVER_PORT'] = '80'
|
||||
self.assertEqual(Site.find_for_request(request), self.default_site)
|
||||
|
||||
def test_unrecognised_port_and_default_host_routes_to_default_site(self):
|
||||
# requests to the default host on an unrecognised port should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = self.default_site.hostname
|
||||
request.META['SERVER_PORT'] = self.unrecognised_port
|
||||
self.assertEqual(Site.find_for_request(request), self.default_site)
|
||||
|
||||
def test_unrecognised_port_and_unrecognised_host_routes_to_default_site(self):
|
||||
# requests with an unrecognised Host: header _and_ an unrecognised port
|
||||
# hould be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = self.unrecognised_hostname
|
||||
request.META['SERVER_PORT'] = self.unrecognised_port
|
||||
self.assertEqual(Site.find_for_request(request), self.default_site)
|
||||
|
||||
def test_unrecognised_port_on_known_hostname_routes_there_if_no_ambiguity(self):
|
||||
# requests on an unrecognised port should be directed to the site with
|
||||
# matching hostname if there is no ambiguity
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = self.about_site.hostname
|
||||
request.META['SERVER_PORT'] = self.unrecognised_port
|
||||
self.assertEqual(Site.find_for_request(request), self.about_site)
|
||||
|
||||
def test_unrecognised_port_on_known_hostname_routes_to_default_site_if_ambiguity(self):
|
||||
# requests on an unrecognised port should be directed to the default
|
||||
# site, even if their hostname (but not port) matches more than one
|
||||
# other entry
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = self.events_site.hostname
|
||||
request.META['SERVER_PORT'] = self.unrecognised_port
|
||||
self.assertEqual(Site.find_for_request(request), self.default_site)
|
||||
|
||||
def test_port_in_http_host_header_is_ignored(self):
|
||||
# port in the HTTP_HOST header is ignored
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = "%s:%s" % (self.events_site.hostname, self.events_site.port)
|
||||
request.META['SERVER_PORT'] = self.alternate_port_events_site.port
|
||||
self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site)
|
||||
|
||||
|
||||
class TestRouting(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_urls(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<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" %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=field field_classes="field-small" li_classes="col4" %}
|
||||
{% endfor %}
|
||||
<li class="submit col2">
|
||||
<button name="action" value="filter" class="button">{% trans 'Filter' %}</button>
|
||||
|
|
|
|||
Loading…
Reference in a new issue