Heist nomination for a recent project!

I’m pleased to say a recent massive project I worked on to launch a new Courses website for the University has been nominated for a Heist Award.

The courses website is built within a Wagtail site, using some Django Views and some Wagtail features, and talks to a GoLang based API.

I was only one of many in the project team which consisted of IT and Marketing people working together. It was a tough project at times, doing things we’d never done before, but at the same time a lot of fun, and a bonus to get even just a nomination!

Blog Update

I haven’t posted much on Wagtail or anything really this year, though we’re still using Wagtail, I’ve been working on a large project for most of the year which involves Rails, GoLang as well as ‘some’ Django/Wagtail for the front end. But I’m planning in the new year to do some Wagtail based blog posts and hopefully more blog posts generally 🙂

Merry Xmas

Joss

 

Using CAS authentication with Wagtail

Hi, this is quick setup guide for using django-cas-client (1.2.0) with Wagtail, gave me a few headaches so maybe this can help someone else.

I’m assuming you have a CAS authentication server setup already, this guide doesn’t cover that.

First off you’ll need to install the django-cas-client, I did this by adding a line in my requirements.txt of :

django-cas-client==1.2.0

Then in your settings file, make sure you add a backend like :


AUTHENTICATION_BACKENDS = (
	'django.contrib.auth.backends.ModelBackend',
	'cas.backends.CASBackend',
)

then :

CAS_SERVER_URL = 'https://login.yourserveraddresstocas.com/cas/'

CAS_REDIRECT_URL = '/admin/' 

CAS_AUTO_CREATE_USER = False

(The auto create setting is really important otherwise the CAS client will attempt to make a user in Wagtail with no details, this will cause a redirect loop on login for authenticated users)

next :

Add

'cas.middleware.CASMiddleware', 

to the MIDDLEWARE_CLASSES section

then :

Add

'cas',

to the list of installed apps.

That’s all for settings, next open up your urls.py and add these lines :


url(r'^admin/login/$', 'cas.views.login', name='login'),
url(r'^admin/logout/$', 'cas.views.logout', name='logout'),

That should be it, any problems comment below!

How to stop users accidentally leaving a Wagtail edit page with JQuery

We had a request recently to implement a feature to stop users accidentally losing work while editing a page then forgetting to save. Especially now in Wagtail with features like StreamField, they can get pretty engrossed in their page layout design then forget to save, hit the ‘Back’ button my mistake etc.

Although I believe this functionality will be introduced into Wagtail itself soon, here is how to do it in a ‘quick and easy’ way before that time.

First off create a Javascript file in your app where ever you keep those, let’s call it leave_page.js (You can think of a better title name I’m sure)

Then add this code:


(function() {
(function($) {

window.onload = function() {

var submitted = false;

$(":submit").click(function(event){
submitted = true;
});

var del_button = $( "a:contains('Delete')" );
del_button.click(function(event){
submitted = true;
});

var unpublish_button = $( "a:contains('Unpublish')" );
unpublish_button.click(function(event){
submitted = true;
});

window.addEventListener("beforeunload", function (e) {

if (submitted == false){
var confirmationMessage = 'It looks like you have been editing something. ';
confirmationMessage += 'If you leave before saving, your changes will be lost.';

(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
}
else {
return undefined;
}

});
};

})(jQuery);

}).call(this);


The first part of the code sets a flag so we can tell if any of the Wagtail main actions have been pressed like Save,Publish, Delete etc, we don’t want to bother the user if the page is being unloaded by those actions. If someone clicks Publish then we assume that’s what they want.

The code finds the Wagtail specific buttons and gives them a click handler which sets the flag to true.

The last part adds an event listener for the page exiting ‘beforeunload’, so just before we can remind them with an alert that they might lose their work, as long as submitted isn’t true.

You’ll need to get this JS picked up by Wagtail so use the Wagtail Hook ‘insert_editor_js’, create a file called ‘wagtail_hooks.py’ if you don’t already have one, then register the new JS Code from that. For more info on how to do that visit http://docs.wagtail.io/en/latest/reference/hooks.html

Hope this helps, let me know if there are any mistakes or if there is a cleaner, nicer way to do it.

More StreamField Examples – Top news stories block by tag

We thought it would be useful to be able to display say the top 5 news stories tagged with a certain word anywhere within a page using StreamField, so for example the latest sport related news stories could be added into a column next to something else on a page in Wagtail. You could adapt this example to use any page type in your Models.py, doesn’t have to be news. Also I really like the way you can set a default template for a latest news block, but then override that and set another one if you need it rendered within a thinner column. Anyway here’s what I did :

Firstly make a new block class bases on StructBlock,

The filter below on the query for news assumes your news model is hooked up using taggit so each news story can be tagged.



class NewsStoriesBlock(blocks.StructBlock):
    tagged_by_keyword = blocks.CharBlock(required=True)
    stories_limit = blocks.CharBlock(required=True,max_length=2)
    
    def render(self, value): 
        news = NewsPage.objects.filter(live=True).filter(tags__name=value['tagged_by_keyword']).order_by('-date')
        news = news[:value['stories_limit']]
        
        return render_to_string(self.meta.template, { 
            'self': value, 
            'news_stories': news, 
            
        })
        
    class Meta:
        template = 'yourapp/blocks/news_stories.html'
        icon = 'cogs'
        label = 'News Stories Widget'

tagged_by_keyword is the field used to hold the tag name ‘sport’ etc

for stories_limit I’ve set max_length to 2, meaning 99 is the most a user could request, but you could more practically change this to a choiceBlock of restricted values, or even add some code into the render method, so it checks the value is no more than say 30, and if it is just sets it 30 as a cap.

Then add the new block to your StreamField in your page model, like in the example one below. For how to do a two column block see ‘Some Wagtail v1 StreamField Examples‘ post


class ArticlePage(Page):
    ...
    page_content = StreamField([
            ('heading', blocks.CharBlock(classname="full title",icon="title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock(icon="image")),
            ('two_columns', TwoColumnBlock()),
            ('news_stories', NewsStoriesBlock()),
        ],null=True,blank=True)
     ...

When you add the news block to a two column StreamField, set another block template if you need it rendered in a different format better suited to a thinner half column, like


...
left_column = blocks.StreamBlock([
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
            ('news_stories', NewsStoriesBlock(icon="cogs",template='yourapp/blocks/news_stories_cols.html')),
        ], icon='arrow-left', label='Left column content')            
...

And a block template for rendering news into three bootstrap columns (note you need to use a filter from a template tag to make news page URL workable, code below. You can’t use {% pageurl news %} there is no request object at this level, if someone knows how to do that, or if I’m missing something let me know)

Also this assumes your news model has a title, date and news_image field, change to reflect your page setup.


{% load wagtailimages_tags static yourapp_tags %}

{% if news_stories %}
		
<div class="container-newsbytag">

  <div class="row">
		
              <div class="col-md-12">
                 <h2>News tagged with {{ self.tagged_by_keyword }}</h2>
              </div>
			
	      {% for news in news_stories %}
	      
              {% if forloop.first %}<div class='row'>{% endif %}	

              <div class="col-md-4">

                   <a href="{{ news.url_path|clean_root_url }}" class="news_itembytag">
      
                      {% image news.news_image fill-300x200 class="img-responsive" %}
		
                      <h4>{{ news.title }}</h4>
                      <p class="date">{{ news.date|date:"j F Y" }}</p>

                   </a>


              </div>
	      
              {% if forloop.counter|divisibleby:3 %}</div><div class='row'>{% endif %}
			
              {% if forloop.last %}</div>{% endif %}	
		  	
	      {% empty %} No News Stories found
							
              {% endfor %}

 </div>

</div>
	

{% endif %}


in your yourapp_tags.py (what ever ‘templatetags’ code you have setup. This is used to remove the root /home/ part from the url_path of the news page.


@register.filter
def clean_root_url(url):
    url = url.split('/')
    new_url = ""
    for item in url[2:]:
        new_url = new_url + "/" + item
          
    return new_url

Parallax Background Image Block for Wagtail’s StreamField

Another custom block idea for Streamfield, to go with the ones described in a previous post

I will also refer to some code in the previous post too, to save repeating code here.

This block definition and block template allow you to add a column with a background image in the parallax style and allows you to set some adjustments like text align, image alignment etc.

Firstly here is the block class, based on a StructBlock. Basically some choice block fields and a StreamBlock so you can nest headers and text within it.


ALIGN_CHOICES = (
    ('left', "Left"),
    ('right', "Right"),
    ('center', "Centre"),
)

SIZE_CHOICES = (
    ('auto', "Auto"),
    ('cover', "Cover"),
    ('50%', "Small"),
    ('200%', "Large"),
)


PERCENT_CHOICES = (
    ('10%', "10%"),
    ('20%', "20%"),
    ('30%', "30%"),
    ('40%', "40%"),
    ('50%', "50%"),
    ('60%', "60%"),
    ('70%', "70%"),
    ('80%', "80%"),
    ('90%', "90%"),
    ('100%', "100%"),
)

class OneColumnBlock(blocks.StructBlock):

    back_image = ImageChooserBlock()
    background_size = blocks.ChoiceBlock(choices=SIZE_CHOICES,default="auto")
    background_x_position = blocks.ChoiceBlock(choices=PERCENT_CHOICES,default="50%")
    background_y_position = blocks.ChoiceBlock(choices=PERCENT_CHOICES,default="50%")
    text_align = blocks.ChoiceBlock(choices=ALIGN_CHOICES,default="center")
    one_column = blocks.StreamBlock([
           ('heading', blocks.CharBlock(classname="full title")),
           ('paragraph', blocks.RichTextBlock()),
        ], icon='arrow-left', label='Parallax content')

    class Meta:
        template = 'yourapp/blocks/one_column_block.html'
        icon = 'placeholder'
        label = 'Parallax Column'

Obviously you’ll need to add this as an allowed block to your StreamField in your model, see the previous post (link above)

Then the block template code to render it:


{% load wagtailimages_tags block_tags %}

{% image self.back_image width-2000 as back_photo %}

{% timestamp as id_prefix %}

<style>

.parallax{{ id_prefix }} {
 height: 50vh;

 background-attachment: fixed;
 background-size:{{ self.background_size }};
 background-repeat:no-repeat;
 background-position: {{ self.background_x_position }} {{ self.background_y_position }};
}

</style>
<div class="parallax{{ id_prefix }}" style="background-image: url({{ back_photo.url }});">

 <div style="text-align:{{ self.text_align }};padding:15px;">

 {% include "yourapp/includes/sf_blocks.html" with blocks=self.one_column only %}

 </div>

</div>

The block_tags and include for sf_blocks (above) are explained here in ‘Some Wagtail V1 StreamField Examples

The CSS could be applied via an ID rather than class which would make sense, but I’ve done it as a class with an added millisecond timestamp, to allow more than one of these block per page.

Some Wagtail V1 Streamfield examples

Ok, firstly just to say StreamField is amazing, it adds so much flexibility for adding content onto a page, the way its been developed makes it so versatile.

I’ve started upgrading one of our existing article pages within our Wagtail CMS to use StreamField. Streamfields can get long so I’ve put it into its own custom tab (another V1 feature).

I’m going to show some example of blocks and how I’ve used them below because at the moment there aren’t too many examples out there. Once people realise how good this CMS and its StreamField feature is, there will be loads more trust me.

First of all I added a SF to my page model ‘Article’ but also don’t forget to add (below) at the top of your models.py :


from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore import blocks
from wagtail.wagtailimages.blocks import ImageChooserBlock
from wagtail.wagtailembeds.blocks import EmbedBlock
from wagtail.wagtailadmin.edit_handlers import FieldPanel, FieldRowPanel,MultiFieldPanel, \
    InlinePanel, PageChooserPanel, StreamFieldPanel

then


class ArticlePage(Page):
...
    page_content = StreamField([
        ('heading', blocks.CharBlock(classname="full title",icon="title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock(icon="image")),
        ('two_columns', TwoColumnBlock()),
        ('three_columns', ThreeColumnBlock()),
        ('embedded_video', EmbedBlock(icon="media")),
        ('google_map', GoogleMapBlock()),
        ('image_carousel', blocks.ListBlock(ImageCarouselBlock(),template='yourapp/blocks/carousel.html',icon="image")),
    ],null=True,blank=True)

...

You can see there are some of the standard built in block types there, like heading,paragraph and image, but also some custom ones. I’ll go through some of these next.

Google Map Block

We often need to place a map or two on a page, up until now that was in fixed position within a template, but now they can be added in any order and also in columns (see next bit). So the code to define the block referred to above is :


class GoogleMapBlock(blocks.StructBlock):
    map_long = blocks.CharBlock(required=True,max_length=255)
    map_lat = blocks.CharBlock(required=True,max_length=255)
    map_zoom_level = blocks.CharBlock(default=14,required=True,max_length=3)

    class Meta:
        template = 'yourapp/blocks/google_map.html'
        icon = 'cogs'
        label = 'Google Map'

This is based on a StructBlock and just has three fields for the google map template. It’s very simple like the Twitter one from a previous post.

The template is something like this: (google_map.html)

<script src="http://maps.googleapis.com/maps/api/js"></script>
<script>
function initialize() {
  var mapProp = {
    center:new google.maps.LatLng({{ self.map_lat }},{{ self.map_long }}),
    zoom:{{ self.map_zoom_level }},
    mapTypeId:google.maps.MapTypeId.ROADMAP
  };
  var map=new google.maps.Map(document.getElementById("googleMap-{{ self.map_lat }}{{ self.map_long }}"),mapProp);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>

<div id="googleMap-{{ self.map_lat }}{{ self.map_long }}" style="width:100%;height:380px;"></div>

Because there maybe any number of Maps added through streamfield, you need to make sure the mapID is unique-ish, so I’ve used the long/lat values to form the ID, assuming the same map isn’t added twice, a timestamp would be another way to prefix the ID (as I’ve done in the image gallery later on)

Also this loads the Google Map API JS each time a map is used, so best to add that just once somewhere else, ideally conditionally, I’ve put it within the block template just for example.

Two Column Block


class TwoColumnBlock(blocks.StructBlock):

    background = blocks.ChoiceBlock(choices=COLOUR_CHOICES,default="white")
    left_column = blocks.StreamBlock([
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
            ('embedded_video', EmbedBlock()),
            ('google_map', GoogleMapBlock()),
        ], icon='arrow-left', label='Left column content')

    right_column = blocks.StreamBlock([
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
            ('embedded_video', EmbedBlock()),
            ('google_map', GoogleMapBlock()),
        ], icon='arrow-right', label='Right column content')

    class Meta:
        template = 'yourapp/blocks/two_column_block.html'
        icon = 'placeholder'
        label = 'Two Columns'

Again this is based on a StructBlock, but now we have two fields that use StreamBlock type, which allows nesting more block types within. You can define which blocks are then allowed in each column. This is really useful as in some implementations you wouldn’t probably want users to add say a wide image gallery into a narrow column. This block also has a background ChoiceBlock field to define a background colour class which gets applied to the entire row DIV that holds the two columns of StreamBlocks.

Here is the template:


<div class="row {{ self.background }}">

      <div class="col-md-6">
           {% include "yourapp/includes/sf_blocks.html" with blocks=self.left_column only %}
      </div>
      <div class="col-md-6">
           {% include "yourapp/includes/sf_blocks.html" with blocks=self.right_column only %}
     </div>

</div>

and the include that renders the blocks:


{% load wagtailcore_tags wagtailimages_tags %}

{% if blocks %}

						{% for block in blocks %}
						    {% if block.block_type == 'heading' %}
						        <h1>{{ block.value }}</h1>
						    {% elif block.block_type == 'image' %}
						        {% image block.value width-900 class="img-responsive" %}
						    {% else %}
						       <section class="block-{{ block.block_type }}">
						           {{ block }}
						       </section>
						    {% endif %}
						{% endfor %}

{% endif %}

This template code gets used in a few places so I made it as an include.

Image Gallery/Carousel

This is based on a StructBlock but gets used as part of a list block (remembering back to our SF in the model)

...
        ('image_carousel', blocks.ListBlock(ImageCarouselBlock(),template='yourapp/blocks/carousel.html',icon="image")),
...

It’s a simple image carousel with just an image and a caption.


class ImageCarouselBlock(blocks.StructBlock):
    image = ImageChooserBlock()
    caption = blocks.TextBlock(required=False)

    class Meta:
        icon = 'image'

The template for this uses Royal Slider to render the images in a nice interface. It refers to specific Royal Slider CSS and JS, you’ll need to replace this with your specific slider code etc


{% load wagtailimages_tags static block_tags %}

	{% timestamp as id_prefix %}

	  <link rel="stylesheet" href="{% static "yourapp/css/sw_carousel.min.css" %}" />

	  <div class="container-carousel">

	      <div id="{{ id_prefix }}-gallery" class="royalSlider rsDefault visibleNearby">

		 {% for item in self %}

	            {% image item.image height-400 as carouselimagedata %}
	            <a class="rsImg" data-rsw="{{ carouselimagedata.width }}" data-rsh="{{ carouselimagedata.height }}"  href="{{ carouselimagedata.url }}">
	  				    {{ item.caption }}
	            </a>

		 {% endfor %}

	      </div>

	  </div>

	<script>
	  // Important note! If you're adding CSS3 transition to slides, fadeInLoadedSlide should be disabled to avoid fade-conflicts.
	  jQuery(document).ready(function($) {
	    var si = $('#{{ id_prefix }}-gallery').royalSlider({
	      addActiveClass: true,
	      arrowsNav: true,
	      controlNavigation: 'none',
	      autoScaleSlider: true,
	      autoScaleSliderWidth: 900,
	      autoScaleSliderHeight: 250,
	      loop: true,
	      fadeinLoadedSlide: false,
	      globalCaption: true,
	      keyboardNavEnabled: true,
	      globalCaptionInside: false,
	      visibleNearby: {
	        enabled: true,
	        centerArea: 0.4,
	        center: true,
	        breakpoint: 650,
	        breakpointCenterArea: 0.64,
	        navigateByCenterClick: true
	      }
	    }).data('royalSlider');

	  });
	</script>

This template uses an assignment tag from block_tags.py which creates a timestamp number which is used to prefix the gallery ID. This is in case there is more than one gallery on a page. The ID is used in the HTML and then the JS for the slider. The assignment tag looks like this:


@register.assignment_tag(takes_context=False)
    def timestamp():
        dt = datetime.now()
        ts = dt.microsecond
        return str(ts)

Please let me know by commenting if I’ve done anything crazy or there is a better way, hopefully these example may assist someone else as a starting point to using StreamField in Wagtail.

Official StreamField docs : http://docs.wagtail.io/en/v1.0b1/pages/streamfield.html

Hopefully I will add another post soon on using StreamField for a Parallax image block, Instagram Feed and a pull out quote maybe.