summaryrefslogtreecommitdiff
path: root/old/published/Webmonkey/Frameworks/django-firstapp.txt
blob: 3d01b52e2929f6f15b4521e22b5e94b6fc1119ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
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:

<pre>
svn co http://code.djangoproject.com/svn/django/trunk/ django-trunk
</pre>

Once 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

<pre>
ln -s `pwd`/django-trunk/django /path/to/python_site_packages/django
</pre>

If you don't know where your Python site directory is, here's a handy bit of Python that will tell you:

<pre>
python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"
</pre>

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:

<pre>
ln -s `pwd`/path/to/django-trunk/django/bin/django-admin.py /usr/local/bin
</pre>

So 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 <code>svn update</code>.

== Set up our First Project ==

Okay, let's get started. From the command line switch to your web development directory, something like this:

<pre>
cd ~/sites/dev
</pre>

Now we're going to run the <code>django-admin</code> 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:

<pre>
python /path/to/django-trunk/django/bin/django-admin.py startproject djangoblog
</pre>

Yes, we're going to build a blog.

Now cd over to the new folder:

<pre>
cd ~/sites/dev/djangoblog
</pre>

This 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:

<pre>
import myapp
</pre>

rather than 

<pre>
import myproject.myapp
</pre>

It'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:

<pre>
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.
</pre>

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:


<pre>
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
)
</pre>

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:

<pre>
'django.contrib.admin',
</pre>

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:

<pre>
import os.path
TEMPLATE_DIRS = (
	os.path.join(os.path.dirname(__file__), 'templates'),
)
</pre>

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 <code>os.path</code> 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 <code>syncdb</code> tool. Paste this line in your terminal:

<pre>
python manage.py syncdb
</pre>

The <code>syncdb</code> 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:

<pre>
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/ $ 
</pre>

Once you've created your username and password, it's time to fire up Django's built-in server:

<pre>
/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.
</pre>

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:

<pre>
urlpatterns = patterns('',
    # Example:
    # (r'^djangoblog/', include('djangoblog.foo.urls')),

    # Uncomment this for admin:
#     (r'^admin/', include('django.contrib.admin.urls')),
)

</pre>

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:

<pre>
python manage.py createapp blog
</pre>

If 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:

<pre>
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)
    
</pre>


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 <code>body_html = models.TextField(blank=True)</code> line. What's up with that <code>blank=True</code> 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 <code>null=True</code>. But adding <code>null=True</code> only affects the database, Django's admin would still complain that it needs the information. To get around that we simple add the <code>blank=True</code>.

In this case what we're going to do is fill in the <code>body_html</code> 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 <code>body_html</code> 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:

<pre>
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'djangoblog.tagging',
    'djangoblog.blog',
)
</pre>


Okay, head over to the terminal and run <code>manage.py syncdb</code>. 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:

<pre>
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()

</pre>

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 <code>pass</code> 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 <code>get_absolute_url</code>. 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 <code>body_markdown</code> field (which is exposed in the admin), run it through the markdown filter and store it in <code>body_html</code>. 

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:

<pre>
    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)
    
</pre>

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 <code>get_previous_by_</code> with the last bit of the function being the name of your datetime field, in our case <code>pub_date</code>. 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. 

<pre>
    def get_previous_published(self):
        return self.get_previous_by_pub_date(status__exact=1)
</pre>

All we do is wrap the django function with a new name <code>get_next_published</code>, call the original <code>get_previous_by_</code> 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].