Assuming you've had a read through our [Django Overview], you're probably ready to dive into some code. But before we start installing we need to grab a copy of Django and set it up. As we mentioned in the overview, Django doesn't have a 1.0 release yet. The latest official release was .96, but that version lacks many of the features we're going to use in this project. In order to use the latest and greatest Django tools (like model inheritance) we're going to checkout a copy of the trunk build using Subversion. If you don't have [http://subversion.tigris.org/ Subversion] installed, go grab a copy. Then fire up your terminal and paste in this line:
svn co http://code.djangoproject.com/svn/django/trunk/ django-trunkOnce that finishes downloading all the files, we need to make sure Python is aware of Django. There's a couple ways to go about that, but a symbolic link to your Python site packages directory is probably the easiest. Assuming you're on a *nix system, this line will do the trick
ln -s `pwd`/django-trunk/django /path/to/python_site_packages/djangoIf you don't know where your Python site directory is, here's a handy bit of Python that will tell you:
python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"If you're on Windows the easiest thing to do is add Django to your PythonPath environment variable. On Windows you can define environment variables in the Control Panel, see Microsoft's [http://technet.microsoft.com/en-us/library/bb491071.aspx Command Line Reference] for more details. The excellent Django [http://www.djangoproject.com/documentation/install/ installation docs] suggest creating a symbolic link to the file django-trunk/django/bin/django-admin.py in a directory on your system path, such as /usr/local/bin. I find that I don't use django-admin.py all that often, but you can create the link if you like, just paste this code in your shell:
ln -s `pwd`/path/to/django-trunk/django/bin/django-admin.py /usr/local/binSo we've got Django installed and Python knows where it lives, let's get started. Remember that we have a Subversion checkout so if you ever want to update to the latest release, just head to the django-trunk folder and run
svn update
.
== Set up our First Project ==
Okay, let's get started. From the command line switch to your web development directory, something like this:
cd ~/sites/devNow we're going to run the
django-admin
tool we mentioned earlier. If you created the symlink, you don't need the full path, but if you didn't here's the code:
python /path/to/django-trunk/django/bin/django-admin.py startproject djangoblogYes, we're going to build a blog. Now cd over to the new folder:
cd ~/sites/dev/djangoblogThis is going to be our project folder into which we will add various apps, some we'll create and some we'll be downloading from Google code projects. I like to keep my Python import statements clean and free of project specific module names, so I always make sure my root project folder (in this case djangoblog) is on my python path. To do that just add the path to your PythonPath variable. That way we can write statements like:
import myapprather than
import myproject.myappIt's not a huge thing, but it does make your code more portable. Okay, we're getting there. The next step is to fill out our project settings file. Fire up your favorite text editor and open up the settings.py file inside the djangoblog directory. The core of what we need to set up is at the top of the file. Look for these lines:
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. DATABASE_NAME = '/path/to/djangoblog' # Or path to database file if using sqlite3. DATABASE_USER = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # Not used with sqlite3. DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.Note that we're using SQLite as a database for development purposes. Assuming you have Python 2.5 installed you don't need to do anything to use SQLite. If you're on either Python 2.3 or Python 2.4, you'll need [http://oss.itsystementwicklung.de/trac/pysqlite/ pysqlite] -- make sure you install version 2.0.3 or higher. If you have MySQL or PostgreSQL already installed, feel free to use them. The other settings are well documented in the settings.py file and most of them can be ignored for now. But there is one other thing we need to do. If you look at the bottom of the settings.py file you'll notice this bit of code:
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', )This where we tell our django project, which apps we want to install. In a minute we'll add our blog app, but for now let's just add Django's built-in admin section. Paste in this line, just below the sites app:
'django.contrib.admin',Before we finish with settings.py, here's a handy trick for the template directories. I generally keep all my templates in a folder named templates within my project folder (in this case djangoblog). But I generally move between development and live servers quite a bit and I hate having to change the path to the templates folder. This trick takes care of that:
import os.path TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates'), )Instead of hard coding the path to our templates folder this is dynamic -- and it showcases how easy it is to tweak django using Python. We just import the
os.path
Python module and then find the path to the directory where settings.py is and then appends 'templates' to that path.
Now when we push the site live, there's no need to change the settings.py file. (Actually you'd probably want to change to a more robust database, but we'll get to that much later).
So now let's use one of the tools included in manage.py, the syncdb
tool. Paste this line in your terminal:
python manage.py syncdbThe
syncdb
tool tells Django to translate all our installed apps' models.py files into actual database table. In this case they only thing we have installed are some of the built-in Django tools, but fear not, we'll get to writing our own models in just a minute.
Once you enter the line above, you'll get some feedback from Django telling you you've just installed the auth system and walking you through setting up a user. The output looks like this:
sng: /djangoblog/ $ python manage.py syncdb Creating table auth_message Creating table auth_group Creating table auth_user Creating table auth_permission Creating table django_content_type Creating table django_session Creating table django_site You just installed Django's auth system, which means you don't have any superusers defined. Would you like to create one now? (yes/no): no Installing index for auth.Message model Installing index for auth.Permission model sng: /djangoblog/ $ python manage.py syncdb Creating table auth_message Creating table auth_group Creating table auth_user Creating table auth_permission Creating table django_content_type Creating table django_session Creating table django_site Creating table django_admin_log You just installed Django's auth system, which means you don't have any superusers defined. Would you like to create one now? (yes/no): yes Username (Leave blank to use 'luxagraf'): E-mail address: none@none.com Password: Password (again): Superuser created successfully. Installing index for auth.Message model Installing index for auth.Permission model Installing index for admin.LogEntry model sng: /djangoblog/ $Once you've created your username and password, it's time to fire up Django's built-in server:
/djangoblog/ $ python manage.py runserver Validating models... 0 errors found Django version 0.97-pre-SVN-6920, using settings 'djangoblog.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.Now open up your browser and head to [http://127.0.0.1:8000/ http://127.0.0.1:8000/]. You should see a page like this: [djangocreen1.jpg] It works! But that isn't very exciting, let's check out the admin. However, before we do that, we need to tell django about the admin url. Fire up your text editor and open the file urls.py in your djangoblog folder. You should see some code like this:
urlpatterns = patterns('', # Example: # (r'^djangoblog/', include('djangoblog.foo.urls')), # Uncomment this for admin: # (r'^admin/', include('django.contrib.admin.urls')), )Go ahead and uncomment the admin url line (just delete the hash mark at the beginning of the line) and now head to [http://127.0.0.1:8000/admin/ http://127.0.0.1:8000/admin/]. Login with the user/pass combo you created earlier and you should see something like this: [djangocreen2.jpg] Now that's pretty cool. If you've ever labored over creating an admin system in Ruby on Rails or, god forbid, PHP, you're going to love Django's built-in admin. But at the moment there isn't much to see in the Admin, so let's get started building our blog. Now we could just throw in some code that creates a date field, title, entry and other basics, but that wouldn't be a very complete blog would it? What about tags? An RSS feed? A sitemap? Maybe some [http://daringfireball.net/projects/markdown/ Markdown] support for easier publishing? Yeah, let's add all that. But remember Django's DRY principles -- sure someone else has already created a Feed app? A Sitemap app? As a matter of fact Django ships with those built-in. Nice. But what about tags? Well there's one of those available as well -- the cleverly named [http://code.google.com/p/django-tagging/ django-tagging]. Grab the source from Google code and drop it in your djangoblog folder. There's also a handy [https://sourceforge.net/project/showfiles.php?group_id=153041 Python implementation of Markdown], so grab that as well (of course Markdown is entirely optional, feel free to skip it). Got all that stuff stashed in your djangoblog folder? Good. Now let's go ahead and create our first Django application. To do that we'll use Django's app creating script, which lives inside manage.py in our project folder. Paste this line into your shell:
python manage.py createapp blogIf you look inside djangoblog you should now see a new "blog" folder. Open that up and find the models.py file. Open models.py in your favorite text editor and paste in this code:
from django.db import models from django.contrib.syndication.feeds import Feed from django.contrib.sitemaps import Sitemap import markdown from tagging.fields import TagField from tagging.models import Tag # Create your models here. class Entry(models.Model): title = models.CharField(max_length=200) slug = models.SlugField( unique_for_date='pub_date', prepopulate_from=('title',), help_text='Automatically built from the title.' ) body_html = models.TextField(blank=True) body_markdown = models.TextField() pub_date = models.DateTimeField('Date published') tags = TagField() enable_comments = models.BooleanField(default=True) PUB_STATUS = ( (0, 'Draft'), (1, 'Published'), ) status = models.IntegerField(choices=PUB_STATUS, default=0)Let's step through the code line by line and we'll talk about what's going on. First we import the basic stuff from django, including the model class, the Feed class and the Sitemap class. Then we import the tagging and markdown files we just saved in our project folder. Once we have all the modules we're going to use, we can create our blog model. I elected to call it Entry, you can change that name if you like, but remember to substitute your name everywhere I refer to Entry. Entry extends Django's built-in model.Model class, which handles all the basic create read update and delete (CRUD) tasks. In other words all we have to do is tell Django about the various elements of the database table (like the title field, the entry slug, etc) and all the hard work is handled behind the scenes. The first bit of our Entry class definition just defines all our various blog entry components. Django will use this information to create our database tables and structure, and also to generate the Admin interface. Note that we're using Django's various model fields. Most of it should self explanatory, but if you want to learn more about each type check out the [http://www.djangoproject.com/documentation/model-api/ Django documentation]. Also be aware that there are quite a few more field types available. One thing worth mentioning is the
body_html = models.TextField(blank=True)
line. What's up with that blank=True
bit? Well that information is part of Django's built-in Admin error checking.
Unless you tell it otherwise all fields in your model will create NOT NULL columns in your database. To allow for null columns, we would just add null=True
. But adding null=True
only affects the database, Django's admin would still complain that it needs the information. To get around that we simple add the blank=True
.
In this case what we're going to do is fill in the body_html
field programatically, after we hit save in the admin and before Django actually writes to the database. So we need the Admin section to allow body_html
to be blank, but not null.
== Check Your Head ==
Now we need to tell Django about our new apps. Open up settings.py again and add these lines to your list of installed apps:
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'djangoblog.tagging', 'djangoblog.blog', )Okay, head over to the terminal and run
manage.py syncdb
. Refresh your admin section and you should see the tagging application we downloaded. Very cool.
But where's our blog model? Well, even though Django knows about our blog app, we haven't told the app what to do in the Admin section.
So head back over to your text editor and add these lines to bottom of the model definition:
class Entry(models.Model): """ ...earlier stuff omitted... """ class Meta: ordering = ('-pub_date',) get_latest_by = 'pub_date' verbose_name_plural = 'entries' class Admin: list_display = ('title', 'pub_date','enable_comments', 'status',) search_fields = ['title', 'body_markdown'] list_filter = ('pub_date', 'enable_comments', 'status') fields = ( (None, { 'fields': (('title', 'status'), 'body_markdown', ('pub_date', 'enable_comments'), 'tags', 'slug') }), ) def __unicode__(self): return u'%s' %(self.title) def get_absolute_url(self): return "/%s/%s/" %(self.pub_date.strftime("%Y/%b/%d").lower(), self.slug) def save(self): self.body_html = markdown.markdown(self.body_markdown, safe_mode = False) super(Entry, self).save()Okay, what does all that do? Well let's start with the Meta class. Meta handles things like how Django should order our entries and what the name of the class would be. By default Django would refer to our class as 'Entrys', and that offends my grammatical senses so we just explicitly tell Django the plural name. The next class, 'Admin,' as you might suspect, controls how the admin interface looks. Now these customizations are entirely optional. You could simple write
pass
and go with the default admin layout. However I've customized a few things and added some filters to the admin list view so we can sort and filter our entries.
Then we have a few function definitions. All Python objects should return their name. Django recently added unicode support so we'll return our name in unicode. Then there's get_absolute_url
. As you might imagine this refers to the entry's permalink page.
When we get to creating templates we'll use this to put in our permalinks. That way if you ever decide to change your permalinks you only have to change one line and your entire site will update accordingly -- very slick.
The last function simply overrides Django's save function. Every Django model has a save function and since we didn't expose the bogy_html field we need to fill it in. So we grab the text from our body_markdown
field (which is exposed in the admin), run it through the markdown filter and store it in body_html
.
That way we can just call this field in our templates and we'll get nicely formatted html and yet still keep the process transparent -- write in markdown, display html.
Now we're going to add a few more line to our models.py file and then we're done:
def get_previous_published(self): return self.get_previous_by_pub_date(status__exact=1) def get_next_published(self): return self.get_next_by_pub_date(status__exact=1) def get_tags(self): return Tag.objects.get_for_object(self)So what's going on here? Django includes a bunch of built-in methods for common tasks, like displaying next and previous links. The function is called
get_previous_by_
with the last bit of the function being the name of your datetime field, in our case pub_date
. However, we included the ability to save drafts in our model, unfortunately Django's built-in function doesn't know about our drafts and will include them in our next/previous links which isn't what we want.
So what to do? How about just wrapping the Django function with a one-liner? That's what we've done here.
def get_previous_published(self): return self.get_previous_by_pub_date(status__exact=1)All we do is wrap the django function with a new name
get_next_published
, call the original get_previous_by_
function, but add a filter so that only published entries are included in the results.
The last function is just a time saver. There's a good chance you'll want to list all the tags you've added to your entry, so I've included a convenience method that does just that.
Whew. That's a lot of code to sort through and we've glassed over a few things, but when you look at the models.py file and consider that from these 49 lines of code Django is going to create an entire blog website, it doesn't seem like so much code at all does it.
So save the file and head back over to your browser. Refresh the admin page and click "add new." Feel free to create a few entries -- blog monkey blog!
So now we've got our back-end blogging system set up and everything in in place to create a public site. Feel free to take a well deserved break. When you're ready, head over to the next section and we'll [build the public facing side of our blog].