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

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!

Adding a Twitter Widget for Wagtail’s new StreamField

Wagtail‘s latest feature is called the ‘StreamField’, currently only available in the 1.0 Beta 1 version, but clearly a bit of a Django CMS game changer.

It allows the editor to assemble a flow of page content of varying types (‘Blocks’ defined by the developer) in any order / combination they wish, kind of a Sir Trevor / Tumblr style affair.  It comes with a good selection of default block types, but it’s easy to repurpose these to create things like a Twitter Widget block.

In this guide I’m going to add a StreamField to an existing page class, which allows the user to add headers, paragraphs, images and a Twitter Widget to the page in any iteration they choose. The Twitter Widget I’m talking about is the one you can generate from your twitter settings by going to https://twitter.com/settings/widgets when logged in.

The user will be able to specify their twitter username, widget ID and a limit to how many latest tweets to display. The same principle could be applied for also adding Soundcloud, Instagram or whatever widgets, especially useful if you want a social media rich page, which has your content mixed in with feed content from the social media sites you use.

ss1

I like things laid out in easy clear steps, I’m a bit slow I guess 😦 so here goes… (Bare in mind the code may change, but this works for the current incarnation)

First off, add this class to your models.py

At the top I had to add (modify the edit_handlers Panels as you need, but you will obviously need the StreamFieldPanel for this)

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

then

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 = 'yourapp/blocks/twitter.html'
        icon = 'cogs'
        label = 'Twitter Widget'

We’re just making our own class based on the StructBlock (one of the built in types) and setting up the fields we need for our widget. The other thing I like is you can specify a template for your widget at this point, and choose an Icon, I’ve gone with a Cog, but there are plenty of others. The Label is useful to tell the users what it is once it’s part of the interface.

Next go to your page class where you want the StreamField to appear, and add the code below with other existing fields.

class ArticlePage(Page):

....
content = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('twitter', TwitterBlock()),
    ],null=True,blank=True)

....

This sets up our content field which can have a Header, Paragraph,Image or twitter widget.

 Then, so it appears in the admin, add it to the correct panel


....
ArticlePage.content_panels = [
    FieldPanel('title', classname="full title"),
    StreamFieldPanel('content'),

.....

At this point you’ll need to create and apply migrations for the new field. (If you’re replacing a body field with a Streamfield, you might want to think about adding a new field as we’ve done here rather than changing the existing body field. This will allow your editors to migrate the main page content from the old rich text field into the new StreamField one)


./manage.py makemigrations
python manage.py migrate

The final step is to obviously take care of the templating part!

You’ll need to add a new folder called ‘blocks’ in the template folder of your app, in the location we referenced earlier when defining the block, then create twitter.html as below.


&lt;div class="twitter-widget"&gt;

{% if self.twitter_box_username %}

    &lt;a class="twitter-timeline" href="https://twitter.com/{{ self.twitter_box_username }}" data-widget-id="{{ self.twitter_box_widget_id }}" data-tweet-limit="{{ self.twitter_box_tweet_limit }}"&gt;Tweets by {{ self.twitter_box_username}}&lt;/a&gt;
    &lt;script&gt;!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");&lt;/script&gt;

{% endif %}

&lt;/div&gt;

Then of course the main template for your page that has the new StreamField content will need to be changed. Add this code below whereever you want the content StreamField to appear. There a few ways of outputting the blocks, I’ve chosen one from the Wagtail examples where I get a bit more control, for more examples go to http://docs.wagtail.io/en/v1.0b1/pages/streamfield.html#template-rendering


 &lt;article&gt;
 {% for block in self.content %}
     {% if block.block_type == 'heading' %}
         &lt;h1&gt;{{ block.value }}&lt;/h1&gt;
     {% elif block.block_type == 'image' %}
         {% image block.value width-400 %}
     {% else %}
        &lt;section class="block-{{ block.block_type }}"&gt;
            {{ block }}
        &lt;/section&gt;
     {% endif %}
 {% endfor %}
 &lt;/article&gt;

Hopefully this might help someone, let me know of any mistakes.

Outputing JSON for a model with properties and db fields in Wagtail/Django

I thought I’d share this example because it took me ages to figure out how to output JSON for a model which included a model property as well as the actual fields of the model. For our footer links we had a property called ‘link’ in the snippet which would return either a full URL or a relative link to a Wagtail CMS page. If we just tried to return link_page directly without the link property we’d just get the ID of the page, this wouldn’t be any use to us in the JSON.

This example uses a comprehension list to get the model property and model fields together.
In the models.py for the footer snippet:


class MainFooterLink(models.Model):

 link_title = models.CharField(max_length=255)
 link_url = models.URLField("Link URL", blank=True)
 link_page = models.ForeignKey(
 'wagtailcore.Page',
 null=True,
 blank=True,
 related_name='+'
 )
 order_rank = models.IntegerField(default=1)

 panels = [
 FieldPanel('link_title'),
 FieldPanel('link_url'),
 PageChooserPanel('link_page'),
 FieldPanel('order_rank'),
 ]

 def __unicode__(self):
 return self.link_title

 @property
 def link(self):
 if self.link_page:
 return "<your base URL here>" + self.link_page.url
 else:
 return self.link_url

register_snippet(MainFooterLink)

Define your view function (make sure to import json and HttpResponse):

def footer_json(request):

footerlinks = MainFooterLink.objects.all().order_by('order_rank')

the_links = []
the_links = [{'title':s.link_title, 'link':s.link , 'order':s.order_rank} for s in footerlinks]

return HttpResponse(json.dumps(the_links, sort_keys=True, indent=4, separators=(',', ': ')), mimetype='application/json')

Bi-lingual website using Wagtail CMS.

We’re now using Wagtail for our main website where I work and by law we need to have a Welsh version of the site. So English and Welsh are the two languages we needed. In this example of our approach I’ll outline how to get a simple, easy bi-lingual website going with wagtail. Some of this code and approach is based on Karl from Torchbox’s tutorial, but we didn’t need multiple languages only two, and there is no automatic selection of language based on your locale.

For this to work your second language site must live within the main site, as a parent page with all of the second language pages living from that page. In our example within the top level of the main site, is a page called ‘Hafan’ (Home in Engish) with a slug of ‘cymraeg’. So basically everything that lives of that slug is considered to be a Welsh Page.

For the content editor the process goes something like this:

  • Add an English page where ever in the main site.
  • Add a Welsh page somewhere within the /cymraeg part of the site as a child or grandchild page (Wagtail’s content is in a tree)
  • When editing the Welsh page (using Wagtails useful page browsing interface panel) link to the equivalent English page. (If the pages are the same template type, then you only need to link in one direction, but if you want to form a relationship betweent two different page types, that’s fine but you need to link in both directions, i.e from the English page also choose the Welsh version of the page)
  • In the front end on any page that has a Welsh/English version of itself, a link will appear saying ‘Read this page in English, or the same in Welsh)
  • When a Welsh page is viewed the template elements know to change to show Welsh items, e.g the Navigation, breadcrumbs footer

How to code this approach (the Basics)

Firstly in your models.py add this mix-in class :

class TranslatablePageMixin(models.Model):

 translated_link = models.ForeignKey(Page, null=True, on_delete=models.SET_NULL, blank=True, related_name='+')

 class Meta:
 abstract = True

Now in any pages types that need a Welsh version add the mix-in

class ArticlePage(Page, TranslatablePageMixin):
body = RichTextField(blank=True)
.....

in the content_panels for that page type remember to add a Page Chooser for the translated version of the page (other wise you won’t get the nice Wagtail page chooser UI appearing in the edit view of the page) :

PageChooserPanel('translated_link'),

That’s all you need to do in the models.py bit, obviously you need to run your migrations stuff to make the new database changes happen.

Next you’re going to need a template tag which will produce the ‘View this page in….’ link to the correct translated page.

So in your site’s yourapp_tags.py define an inclusion tag :


@register.inclusion_tag('yourapp/tags/translated_link.html', takes_context=True)
def view_this_page_in(context, calling_page=None):

 # First see if calling page has the link set
 try:
 if calling_page.translated_link:
 translated_version = calling_page.translated_link
 else:
 # next see if a page of same type has relationship to calling page
 if type(calling_page).objects.filter(translated_link=calling_page).count()>0:
 translated_version = type(calling_page).objects.filter(translated_link=calling_page).first().specific
 else:
 # else return nothing, dont bother looking through other models.
 translated_version = []
 if welsh_site(context):
 link_text = "View this page in English"
 else:
 link_text = "Darllenwch y dudalen hon yn y Gymraeg"
 return {
 'translated_version': translated_version,
 'link_text': link_text,
 # required by the pageurl tag that we want to use within this template
 'request': context['request'],

 }
 except Exception as e:
 return {
 'translated_version': [],
 'link_text': "",
 # required by the pageurl tag that we want to use within this template
 'request': context['request'],
 }

And the HTML bit  (translated_link.html) :


{% load yourapp_tags wagtailcore_tags %}

{% if translated_version %}

 <div class="container-translate">
 <div class="container">
 <div class="row">
 <div class="col-md-12">
 <a href="{% pageurl translated_version %}">{{ link_text }}</a>
 </div>
 </div>
 </div>
 </div>

{% endif %}

Basically this will look to see if the calling page already has a link to the translated version, if so just use that, but if not check if a page in the tree has a link pointing back to our calling page, if so use that. It could look through all the types of pages, but there might be too many database calls depending on how many page types you’ve setup. So if you’re linking two different page types just link both directions.

This Inclusion tag also needs a function called welsh_site which returns true or false.  It uses this to know if you’re in the Welsh site or not, to then pass back the link title in the correct language.  It’s also used from the template too but more on that in a bit. Here’s the code :

@register.assignment_tag(takes_context=True)
def welsh_site(context):
 request = context['request']
 if "/cymraeg" in request.get_full_path():
 return True
 else:
 return False

Place this in your base template where you’d like the ‘View this page in’ link to appear (remember to load your yourapp_tags.py) :


{% view_this_page_in calling_page=self %}

The last thing you need to do is to make any elements in the templates/includes change based on if they’re being used in the Welsh site or not, so for example in my footer include, I use the assignment tag again to change the footer statement.


 {% welsh_site as welsh %}
 {% if welsh %}
 <p class="reg">&copy; Prifysgol De Cymru. Mae Prifysgol De Cymru yn elusen gofrestredig. Rhif yr elusen 1140312.</p>
 {% else %}
 <p class="reg">&copy; University of South Wales. The University of South Wales is a registered charity. Registration No.1140312</p>
 {% endif %}

If your site generates it’s top navigation from the pages in the tree, like the Demo wagtail site, you’ll need to modify the inclusion tag responsible for the top nav items. You’ll only want the main Welsh pages as your navigation if you’re in the Welsh site. So basically if you’re in the welsh site, and a welsh homepage exists, then create an instance of the Welsh homepage, then get it’s children. If you’re not in the Welsh site then use the original wagtaildemo bit.


@register.inclusion_tag('yourapp/tags/top_menu.html', takes_context=True)
def top_menu(context, parent, calling_page=None):

 if welsh_site(context) and has_welsh_homepage():
 welsh_home = welsh_homepage()
 menuitems = welsh_home.get_children().filter(live=True,show_in_menus=True)
 else:
 menuitems = parent.get_children().filter(live=True,show_in_menus=True)

 for menuitem in menuitems:
 menuitem.show_dropdown = has_menu_children(menuitem)
 return {
 'calling_page': calling_page,
 'menuitems': menuitems,
 # required by the pageurl tag that we want to use within this template
 'request': context['request'],
 }

This inclusion tag needs two other functions:


def has_welsh_homepage():
 if Page.objects.filter(slug='cymraeg').count() > 0:
 return True
 else:
 return False

def welsh_homepage():
 try:
 return Page.objects.filter(slug='cymraeg').first().specific
 except:
 return []

These functions require your second language home page (in our case Welsh) to have a set slug, ours is ‘cymraeg’.

We also made our footer use different types of snippets if the Welsh site’s being served by the template, and also our breadcrumbs checks if it’s being used in the Welsh site, so it can have a different first base crumb.

But basically it’s one set of templates and base that delivers both English and Welsh pages, through one site. It’s far from a perfect approach, but it’s flexible and was quick and easy to get going under a tight deadline.

Please let me know if you spot any problems with the code, or have any suggestions or comments.