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
Advertisements

My first DjangoCon

Screen Shot 2015-06-03 at 20.36.58DjangoCon Europe 2015 was my first DjangoCon, held in Cardiff City Hall and Cardiff University, luckily Cardiff is where I live so the traveling was easy!

There were some great talks and I learnt a lot, it’s given me a whole array of things I need to look into now.

Highlights for me were Baptiste Mispelon’s adventures in Djangoland, the tales of someone living a much bolder and exciting life than me ūüė¶ Also Peter Finch the poet gave a very entertaining talk on Cardiff, which was a nice way to end the opening day’s morning talks.

After lunch we got to experience the 24-hour free #django emergency hotline, with the legendary doismellburning, apollo13 and MarkusH, who enacted it live in the room, I haven’t seen anything like this before and was more interesting than just a normal talk, plus it made me realise there is a lot more Django support and help out there that I haven’t known about.

Erik Romijn’s talk on Django security was very good, and I need to try his Online Django Security Checker on a few websites, it’s at https://www.ponycheckup.com/ check it out!

Ola Sendecka, the key note speaker on Tuesday gave a well presented talk on¬†the dangers that rabbit holes present to the programmer,¬†Stefan Foulis’ talk on Docker was interesting for someone who has so far only used Vagrant, Stefan talked about the advantages of Docker, and gave a thorough talk with many examples.

In the afternoon on Tuesday¬†Kat Stevens presented her talk ¬†‘The Full Stack Octopus’, all about being the only developer in a company, this really made me appreciate getting to work in a team, I really admire Kat she has to cover a lot of areas in her job.

Loek Van Gent gave one of my favourite talks entitled ¬†‘True beauty is on the inside, but users are shallow’, all about front end development. There was a lot of talk at the conference generally about separating out the front end of app or sites, using things like Ember, the slides of his talk can be found here¬†http://www.slideshare.net/LoekvanGent/shallow

Wednesday’s highlights for me were¬†Ludvig Wadenstein’s talk ‘Better web applications through user testing’, this talk made me realise how we could be doing more regular user testing and the benefits, also he outlined a relatively straight forward way to do this on a monthly basis. Although the ‘Testing Chamber’ sounds a little frightening, check out his presentation slides here¬†http://ludw.se/~ludw/user_testing.pdf

The after lunch CMS panel talked about the state of the CMS in Django, with Iacopo Spalletti and Tom Dyson from the django CMS and Wagtail teams, this was too short for my liking I would have liked more CMS talk, but that’s just because CMSes are my thing, and obviously I’m already a big Wagtail fan!

Ana Balica’s ‘Demystifying mixins with Django’ was really good, even during the talk It made me think of something I’d recently worked on that could benefit from mixins, and though I have used them, maybe I could use them a bit more actually.

DjangoCon 2015 was a great event and these are just some of my personal highlights!

 

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.

 

Wagtail CMS – Lesser known features

Wagtail CMS is an easy to use, sleek, django based CMS with an admin interface that’s had a lot of time and care put into making it a really pleasurable and easy UI experience. It’s being developed at quite a fast pace with new releases coming fairly frequently, currently its on version 0.6.

Here’s a breakdown of a few of its features some of which aren’t really emphasised enough and I only found out about by chance in the wagtail docs site.

Smart Image cropping

When you upload an image wagtail will use Thumbor style smart cropping based on openCV to find a focal point, and when that image is called from a template it will crop to that focal point. Its face recognition is pretty good, but on more abstract images you can also in version 0.7 (coming soon) define the focal point yourself in the wagtail image admin interface.
http://docs.wagtail.io/en/latest/core_components/images/feature_detection.html

Site Map Generation

i was asked by our marketing people if the site could generate a site map XML file, I was pleasantly surprised that Wagtail can do this for you. Strangely it’s not out of the box, but can be easily turned on my editing a few files.
http://docs.wagtail.io/en/latest/contrib_components/sitemap_generation.html?highlight=sitemap

Image Usage Stats

Again another minor but nice feature, that I almost missed and isn’t turned on by default. When you view an image in the admin you can have wagtail tell you how many times it’s been used in the content. Useful if you wan’t to house clean your image library on the site but are scared to delete any images if they’re needed. Just set the¬†WAGTAIL_USAGE_COUNT_ENABLED to True in your settings file.
http://docs.wagtail.io/en/latest/releases/0.5.html?highlight=image%20usage#usage-stats-for-images-documents-and-snippets

Redirects

In wagtail you can allow your content editors to setup redirects, they can specify the slug, the redirect URL and whether it’s to be a permanent redirect. This saves us a lot of time, now our marketing web users can manage redirects quickly to keep up with campaigns that come and go. You can set which users have access to redirects by using django-admin that sits alongside the wagtail admin. So based on the roles you defined, users that have authority can access the redirects section of the admin.

Adding Bread Crumbs to the front end in Wagtail CMS

Wagtail is a new¬†Django based CMS, it has a rather slick admin interface including a bread crumbs. Due to the fact Wagtail can order its content in a tree structure (unlike most other Django CMSs) you can also add a bread crumbs style navigation in the front end too. Here’s what I did.

I added this code into my base.html template:

{% if self.get_ancestors|length > 1 %}
<ul class=”breadcrumb”>

{% for page in self.get_ancestors %}
{% if page.is_root == False %}
<li><a href=”{% pageurl page %}”>{{ page.title }}</a></li>
{% endif %}
{% endfor %}

<li class=”active”>{{ self.title }}</li>

</ul>
{% endif %}

Don’t forget to ¬†{% load wagtailcore_tags %}¬†at the top of base.html too.

The Breadcrumbs should work fine as long as your home page is below the wagtail root!

Installing Django on Mavericks Mac Os X (Simple install for development)

I was having a real problem installing Django and mySql on a mac, there were lots of guides but many of them wanted you to install another version of Python and stuff like virtualenv! The following commands are what I finally used to get it all installed using mainly homebrew. Hope this saves someone a lot of time and messing around and having to wipe their mac to clean off bad previous installs etc.

 

Open a terminal and Install Homebrew! (Updated)

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

then

brew doctor

Install Pip

sudo easy_install pip

Install Django

sudo pip install django

Install mySQL and mysql python module

brew install mysql

sudo pip install MySQL-python

Other useful Commands

To Start mySQL (Remember to change the root password)

/usr/local/bin/mysql.server start

Check Django Version

python -c “import django; print(django.get_version())”