Few days back, Django 1.7 release candidate 1 was announced. I have never been so excited about a release in recent memory. It has a new app loading framework and schema migrations baked right in!

It is also the significant release that I had been waiting for to update my original Django screencast. With more than 77 thousand views, it is by far the most popular Django screencast on YouTube.

Back in 2012, when I was working in Django, there were a lot of comparisons between Rails and Django. One of the biggest selling points of Ruby on Rails was their official 15-minute blog screencast. It was fascinating to watch an expert Rails developer build an entire site from scratch especially how deftly they use TextMate, which could nearly read his mind.

Armed with a Linux terminal and Emacs, I set out to create a similar screencast for anyone interested in Django. Live coding was something that I had not tried before. I remember starting at 11 pm one night, hoping to complete my recording by midnight.

But by the time I finished a recording without any major goofups it was 5 am in the morning. So with barely any editing, I directly uploaded the raw footage to YouTube and hit the bed.

Over time, a lot of people have loved the screencast, often asking how they can setup their development environment to resemble mine. Emacs despite not being an IDE is often as productive or more, depending on how well you customise it.

Why the Remake?

I have done several more Django screencasts after that, preferring to demonstrate entire projects in Django rather than focus on a specific feature. But the original still remains a favourite as it is beginner-friendly and short.

Plenty of new cool features like Class-based Views had come to Django since then. I also noted that there were very few tutorials which used Python 3. For starting a new Django project, I would definitely recommend Python 3.

So, I decided to remake it but even shorter. Crazily enough, I wanted to cover a lot more than the first screencast. Here are the topics I wanted to touch upon (ones which were not covered in the first screencast are in bold):

New 1.7 defaults

Syntax changes in Python 3.4

QuerySets and Custom Managers

Admin and ModelAdmin

Rich-text posts with django-markdown

Generic Class-based Views

Tags

RSS Feeds

Migrations

Running testcases

Thankfully, Django 1.7 comes with great project defaults like a predefined SQLite 3 database and admin URLs setup, which gives you a running start. Migrations is a real time-saver as you don’t have to keep re-entering your test data.

The entire recording clocked slightly above 15 mins. Here is the final video and the text version below it:

People have translated my tutorials into various languages in the past. So I have added subtitles to this screencast. Enjoy the automatic translations in your preferred language!

Text Version

Some prefer to read the transcript of the screencast. Assuming you already have Django 1.7 installed in Python 3.4, you can follow these steps.

Start the project

django-admin startproject qblog cd qblog ./manage.py migrate ./manage.py createsuperuser ./manage.py runserver

Add an app called blog

./manage startapp blog

Open blog/models.py and change its contents to:

from django.db import models from django.core.urlresolvers import reverse class EntryQuerySet ( models . QuerySet ): def published ( self ): return self . filter ( publish = True ) class Entry ( models . Model ): title = models . CharField ( max_length = 200 ) body = models . TextField () slug = models . SlugField ( max_length = 200 , unique = True ) publish = models . BooleanField ( default = False ) created = models . DateTimeField ( auto_now_add = True ) modified = models . DateTimeField ( auto_now = True ) objects = EntryQuerySet . as_manager () def __str__ ( self ): return self . title class Meta : verbose_name = "Blog Entry" verbose_name_plural = "Blog Entries" ordering = [ "-created" ]

In qblog/settings.py add "blog" to INSTALLED_APPS and migrate:

./manage.py makemigrations blog ./manage.py migrate blog

Change blog/admin.py to:

from django.contrib import admin from . import models class EntryAdmin ( admin . ModelAdmin ): list_display = ( "title" , "created" ) prepopulated_fields = { "slug" : ( "title" ,)} admin . site . register ( models . Entry , EntryAdmin ) Go to admin . Add an unpublished post and published one . Then open `./manage shell` and check the difference between `Entry.objects.all()` and `Entry.objects.published()` .

Markdown

Install django-markdown

pip install django-markdown

Add "django_markdown" to INSTALLED_APPS. Add a second line to qblog/urls.py :

url ( r '^markdown/' , include ( 'django_markdown.urls' )),

Change blog/admin.py to:

from django_markdown.admin import MarkdownModelAdmin class EntryAdmin ( MarkdownModelAdmin ): ... Enter test entries now .

Markdown Editor Toolbar Not Showing in Admin? ~~~ Despite this being a Python 3.x tutorial, many have tried this in Python 2.x. Which is great, but the snazzy Markdown Editor in admin seems to be missing for them. When I had checked, I found that the Markdown widget's media i.e. javascript and stylesheets were not getting added in admin. A workaround is to explicitly define form_overrides as follows. from django_markdown.widgets import AdminMarkdownWidget from django.db.models import TextField class EntryAdmin(MarkdownModelAdmin): list_display = (“title”, “created”) prepopulated_fields = {“slug”: (“title”,)} # Next line is a workaround for Python 2.x formfield_overrides = {TextField: {‘widget’: AdminMarkdownWidget}} </div> #### Index View 10. Change `blog/views.py` to: ~~~python from django.views import generic from . import models class BlogIndex(generic.ListView): queryset = models.Entry.objects.published() template_name = "home.html" paginate_by = 2 Create a url mapping in qblog/urls.py : urlpatterns = patterns ( '' , url ( r '^admin/' , include ( admin . site . urls )), url ( r '^markdown/' , include ( 'django_markdown.urls' )), url ( r '^' , include ( 'blog.urls' )), ) Create blog/urls.py : from django.conf.urls import patterns , include , url from . import views urlpatterns = patterns ( '' , url ( r '^$' , views . BlogIndex . as_view (), name = "index" ), ) Index Template Make template and static directories. Then, copy the files: mkdir templates cp -R /somewhere/templates/* templates mkdir static cp -R /somewhere/static/* static Create templates/home.html with: {% extends "base.html" %} {% load django_markdown %} {% block blog_entries %} {% for object in object_list %} < div class = "post" > < h2 > {{ object.title }} </ h2 > < p class = "meta" > {{ object.created }} </ p > {{ object.body|markdown }} </ div > {% endfor %} {% endblock %} Add to qblog/settings.py : TEMPLATE_DIRS = ( os . path . join ( BASE_DIR , "templates" ), ) STATICFILES_DIRS = ( os . path . join ( BASE_DIR , "static" ), ) Refresh the home page now . Enjoy the first non - admin page . Feed Create blog/feed.py with: from django.contrib.syndication.views import Feed from blog.models import Entry class LatestPosts ( Feed ): title = "Q Blog" link = "/feed/" description = "Latest Posts" def items ( self ): return Entry . objects . published ()[: 5 ] Add to blog/urls.py (last but one line): from . import views , feed url ( r '^feed/$' , feed . LatestPosts (), name = "feed" ), Mention in the templates/base.html template’s HEAD : < link href = "/feed/" rel = "alternate" type = "application/rss+xml" title = "Q Blog Feed" /> Entry View Add to blog/views.py : class BlogDetail ( generic . DetailView ): model = models . Entry template_name = "post.html" Add to blog/urls.py : url ( r '^entry/(?P<slug>\S+)$' , views . BlogDetail . as_view (), name = "entry_detail" ), Add to blog/models.py : objects = EntryQuerySet . as_manager () def get_absolute_url ( self ): return reverse ( "entry_detail" , kwargs = { "slug" : self . slug }) Entry Template Open templates/home.html and save it as post.html while removing the for loop, like this: {% extends "base.html" %} {% load django_markdown %} < div class = "post" > < h2 >< a href = "{% url " entry_detail " slug = object.slug %}" > {{ object.title }} </ a ></ h2 > < p class = "meta" > {{ object.created }} | Tagged under {{ object.tags.all|join:", " }} </ p > {{ object.body|markdown }} </ div > Update `templates/home.html` with the same ` < h2 > ` line. Add tags to blog/models.py : class Tag ( models . Model ): slug = models . SlugField ( max_length = 200 , unique = True ) def __str__ ( self ): return self . slug class Entry ( models . Model ): .... tags = models . ManyToManyField ( Tag ) Migrate: ./manage.py makemigrations blog ./manage.py migrate blog Add to blog/admin.py : admin . site . register ( models . Tag ) Change template templates/post.html to: < p class = "meta" > {{ object.created }} | Tagged under {{ object.tags.all|join:", " }} </ p > Testing Add these two test cases in blog/tests.py : from django.test import TestCase from django.contrib.auth import get_user_model from .models import Entry class BlogPostTest ( TestCase ): def test_create_unpublished ( self ): entry = Entry ( title = "Title Me" , body = " " , publish = False ) entry . save () self . assertEqual ( Entry . objects . all () . count (), 1 ) self . assertEqual ( Entry . objects . published () . count (), 0 ) entry . publish = True entry . save () self . assertEqual ( Entry . objects . published () . count (), 1 ) class BlogViewTests ( TestCase ): def test_feed_url ( self ): response = self . client . get ( '/feed/' ) self . assertIn ( "xml" , response [ 'Content-Type' ]) Run the tests by ./manage.py test blog and they should both pass. Final Notes The entire source is available on Github. If you face any issues, make sure that you are running the same Python/Django versions and compare your code with mine. As always, I would love to hear your comments and feedback.

Share on: 📤 Twitter, Hacker News, Reddit, Facebook, Google+

Next: ▶ Introducing Edge - a Modern Django Project Template

Prev: ◀ Understanding Test Driven Development with Django

Up: ▲ Blog

Hi! Welcome to ArunRocks, an odd collection of writeups on programming, travel, gadgets and practically anything under the sun. This state of affairs could be blamed on the ecelectic interests of your host, Arun Ravindran. He loves programming in several languages especially Python.

Frequent Tags

python (36) django (19) movies (11) productivity (11) tutorial (10) gaming (8) humour (8) creativity (7) featured (7) puray (7) ray-tracer (7) screencast (7) python3 (6) review (6) programming (5) wordpress (5)

Please enable JavaScript to view the comments powered by Disqus.

Disqus