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!

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.

Wagtail Version 1.0 Launched including Streamfield Feature!

Read more about it here : https://wagtail.io/blog/wagtail-10/ Exciting times in the CMS world. This version also contains an API built in. Hope to make some more blog posts very soon specifically on Streamfield, I have loads of ideas for using it in our current work, to make layout and integration of external information easier for our authors. For a good explanation of what the Streamfield is for check out https://torchbox.com/blog/streamfield-solves-content-management-conundrum/ which includes a video demo too!

A list of Wagtail’s Streamfield Icons

So in this example:

class TwitterBlock(blocks.StructBlock):
    twitter_box_username = blocks.CharBlock(required=True)
    twitter_box_widget_id = blocks.CharBlock(required=True)
    twitter_box_tweet_limit = blocks.CharBlock(required=True,max_length=2)
   

    class Meta:
        template = 'appname/blocks/twitter.html'
        icon = 'cogs'
        label = 'Twitter Widget'

Replace the ‘icon’ value with one from below.

wagtail
wagtail-inverse
cogs
doc-empty-inverse
doc-empty
edit
arrow-up
arrow-down
search
cross
folder-open-1
folder-inverse
mail
arrows-up-down
locked
unlocked
arrow-right
doc-full / file-text-alt
image / picture
doc-full-inverse
folder
plus
tag
folder-open-inverse
cog
tick
user
arrow-left
tick-inverse
plus-inverse
snippet
bold
italic
undo
repeat
list-ol
list-ul
link
radio-full
radio-empty
arrow-up-big
arrow-down-big
group
media
horizontalrule
password
download
order
grip
home
order-down
order-up
bin
spinner
pick
redirect
view
no-view
collapse-up
collapse-down
help
warning
success
date
time
form
site
placeholder
pilcrow
title
code
openquote