Dev Week -- Introduction to Django Development -- lukasz -- Fri, Mar 4th, 2011

   1 [18:01] <ClassBot> Logs for this session will be available at http://irclogs.ubuntu.com/2011/03/04/%23ubuntu-classroom.html following the conclusion of the session.
   2 [18:01] <lukasz> Hello everybody, my name is Łukasz Czyżykowski, I'm part of ISD (Infrastructure Systems Development) team at Canonical. This will be a short introduction for creating web applications with Django framework.
   3 [18:02] <lukasz> I assume that everybody is using Maverick and have Django installed. If not then:
   4 [18:02] <lukasz> $ sudo apt-get install python-django
   5 [18:02] <lukasz> will do the trick.
   6 [18:03] <lukasz> btw, if I'll be going too fast or something is unclear do not hesitate to ask
   7 [18:03] <lukasz> This is last stable version of Django. All documentation for it can be found at http://docs.djangoproject.com/en/1.2/ .
   8 === cjohnston_ is now known as cjohnston
   9 [18:03] <lukasz> Now it's time to start coding. Or at least start working on the project. In the beginning we need to create a Django project. This something which, in theory, should be connected to the site.
  10 [18:04] <lukasz> For the purpose of this tutorial we'll build simple web application, we'll use most bits of Django. Our app will be partial Twitter/status.net clone.
  11 [18:04] <lukasz> All code for this project is accessible at https://launchpad.net/twitbuntu, you can either download it and look at revisions which moves app forward in (almost) the same way as this session is planned or you can only follow irc session as all required code will be presented here.
  12 === msnsachin12 is now known as msnsachin
  13 [18:04] <lukasz> So, the first step is to create Django project:
  14 [18:04] <lukasz> $ django-admin startproject twitbuntu
  15 [18:04] <lukasz> $ cd twitbuntu
  16 [18:06] <ClassBot> hugohirsch asked: will the sources be available somewhere later? (in case I'm not fast enough to get a database up'n'running)
  17 [18:06] <lukasz> Yes, as I mentioned earlier the code is already on Launchpad
  18 [18:06] <lukasz> Project is container for database connection settings, your web server and stuff like that.
  19 [18:06] <lukasz> Now twitbuntu contains some basic files:
  20 [18:06] <lukasz> - manage.py: you'll use this script to invoke various Django commands on this project,
  21 [18:07] <lukasz> - settings.py: here are all settings connected to your project,
  22 [18:07] <lukasz> - urls.py: mapping between urls of your application and Python code, either created by you or already existing
  23 [18:07] <lukasz> and the last one
  24 [18:07] <lukasz> - __init__.py: which marks this directory as Python package
  25 [18:07] <lukasz> Next is setting up the database
  26 [18:07] <lukasz> Open settings.py file in your favourite text editor.
  27 [18:07] <lukasz> For purpose of this tutorial we'll use very simple sqlite database, it holds all of its data in one file and doesn't require any fancy setup. Django can of course use other databases, MySQL and PostgreSQL being most popular choices.
  28 [18:08] <lukasz> Modify your file so that DATABASE setting looks exactly like this: http://pastebin.ubuntu.com/575582/
  29 [18:08] <lukasz> To test that those settings are correct we'll issue syncdb management command. It creates any missing tables in the database which in our case is exactly what we want to get:
  30 [18:08] <lukasz> $ ./manage.py syncdb
  31 [18:09] <lukasz> If everything went right you should see bunch of "Creating table" messages and query about creating superuser. We want to be able to administer our own application so it's good to create one. Answer yes to first question and proceed with others
  32 [18:09] <lukasz> My answers to those questions are:
  33 [18:09] <lukasz> (just if anybody wonders)
  34 [18:09] <lukasz> Would you like to create one now? (yes/no): yes
  35 [18:09] <lukasz> Username (Leave blank to use 'lukasz'): admin
  36 [18:09] <lukasz> E-mail address: admin@example.com
  37 [18:09] <lukasz> Password: admin
  38 [18:09] <lukasz> Password (again): admin
  39 [18:10] <lukasz> Email address is not too important at that stage
  40 [18:10] <lukasz> later you can configure Django to automatically receive crash reports on that address, but that's something bit more advanced.
  41 [18:10] <lukasz> Next bit is to create an application, something where you put your code.
  42 [18:12] <lukasz> By design you should separate different site modules into their own applications, that way it's easier to maintain it later and also if you create something which can be usable outside of your project you can share it with others without necessary putting all of your project out there.
  43 [18:12] <lukasz> It's pretty popular in Django community, so it's always good idea to check somebody already haven't created something useful. That way you can save yourself reinventing the wheel.
  44 [18:12] <lukasz> For this there's startapp management command
  45 [18:12] <lukasz> $ ./manage.py startapp app
  46 [18:13] <lukasz> In this simple case we're calling our application just 'app', but normally it should be called something more descriptive. Something like 'blog', 'gallery', etc. In out case, it could be called 'updates'.
  47 [18:13] <lukasz> This creates an 'app' directory in your project. Inside of it there are files created for you by Django.
  48 [18:13] <lukasz> - models.py: is where your data model definitions go,
  49 [18:13] <lukasz> - views.py: place to hold your views code.
  50 [18:14] <lukasz> Some short clarification with naming.
  51 [18:14] <lukasz> Django is (sort of) Model/View/Controler framework
  52 [18:15] <lukasz> The idea is that your application into separate layers. But in case of Django the naming of the standard and what creators did is bit confusing
  53 [18:15] <lukasz> Models are called as they should.
  54 [18:15] <lukasz> Views are templates in Django
  55 [18:15] <lukasz> and Controllers are called view functions.
  56 [18:15] <lukasz> ok, quick break for questions
  57 [18:16] <ClassBot> wolfrage76 asked: Is it possible to setup Django to use more than one database, for different sections of the site? For instance SQlite as default for the site, but MySQL for the forums?
  58 [18:16] <lukasz> wolfrage76: Yes, you can do that. The details (as this is bit more advanced topic) are in the documentaion.
  59 [18:16] <ClassBot> abhinav asked: what is a superuser ? a database admin ?
  60 [18:17] <lukasz> abhinav: it's an admin for whole web application, it's separate from database administrator
  61 [18:17] <lukasz> abhinav: basically, this user can access Django admin and do anything there
  62 [18:17] <lukasz> continuing
  63 [18:18] <lukasz> First layer are models, where data definitions lies. That's the thing you put into models.py file. You define objects your application will manipulate.
  64 [18:19] <lukasz> Next bit is to add this new application to list of installed apps in settings.py, that way Django knows from which parts your project is assembled.
  65 [18:19] <lukasz> In settings.py file find variable named INSTALLED_APPS
  66 [18:19] <lukasz> Add to the list: 'twitbuntu.app'
  67 [18:19] <lukasz> It should look like that:
  68 [18:19] <lukasz>    INSTALLED_APPS = (
  69 [18:19] <lukasz>      'django.contrib.auth',
  70 [18:19] <lukasz>      'django.contrib.contenttypes',
  71 [18:19] <lukasz>      'django.contrib.sessions',
  72 [18:19] <lukasz>      'django.contrib.sites',
  73 [18:19] <lukasz>      'twitbuntu.app',
  74 [18:19] <lukasz>    )
  75 [18:19] <lukasz> or pastebin: http://pastebin.ubuntu.com/575583/
  76 [18:20] <lukasz> You can see that there are already things here, mostly things which give your project some out-of-the-box funcionality
  77 [18:20] <ClassBot> chadadavis asked: judging by the tables created, users and authentication are built in. I.e. it doesn't require any external modules (as Catalyst does)?
  78 [18:21] <lukasz> chadadavis: yes, the authentication is build in
  79 [18:21] <lukasz> Names are pretty descriptive so you shouldn't have problem with figuring out what each bit does
  80 [18:21] <lukasz> although contenttypes and sites can be bit confusing
  81 [18:21] <lukasz> as those are bits of underlying machinery required by most of the other django addons
  82 [18:22] <ClassBot> chadadavis asked: my settings.py also has 'django.contrib.messages' (Natty). Is that going to cause any problems?
  83 [18:22] <lukasz> chadadavis: not a problem
  84 [18:22] <lukasz> all the defaults are good to go
  85 [18:22] <lukasz> Now we start making actual application. First thing is to create model which will hold user updates. Open file app/models.py
  86 [18:22] <lukasz> You define models in Django by defining classes with special attributes. That can be  translated by Django into table definitions and create appropriate structures in database.
  87 [18:23] <lukasz> For now add following lines to the end of the models.py file: http://paste.ubuntu.com/575584/
  88 [18:24] <lukasz> Now some explanations. You can see that you define model attributes by using data types defined in django.db.models module
  89 [18:24] <lukasz> Full list of types and options they can take is documented here: http://docs.djangoproject.com/en/1.2/ref/models/fields/#field-types
  90 [18:24] <lukasz> ForeignKey bit links our model with User model supplied by Django that way we can have multiple users having their updated on our site
  91 [18:25] <lukasz> class Meta bit is place for settings for whole model. In this case we are saying that whenever we'll get list of updates we want them to be ordered by create_at field in ascending order (by default order is descending, and '-' means reversing that order).
  92 [18:26] <lukasz> Now we have to synchronize data definition in models.py with what is in database. For that we'll use already known command: syncdb
  93 [18:26] <lukasz> $ ./manage.py syncdb
  94 [18:26] <lukasz> You should get following output:
  95 [18:26] <lukasz> Creating table app_update
  96 [18:26] <lukasz> Installing index for app.Update model
  97 [18:26] <lukasz> As you can see each table name have two parts: the app name and model name.
  98 [18:27] <lukasz> Great thing about Python is it's interactive shell. You can easily use it with Django.
  99 [18:27] <lukasz> But because of bit of setup django requires there's a shortcut of setting up proper environment within your project
 100 [18:27] <lukasz> $ ./manage.py shell
 101 [18:27] <lukasz> This runs interactive shell configured to work with your project. From here we can play with our models and create some updates.
 102 === Ptivalternative is now known as Ptival
 103 [18:28] <lukasz> first we get the user we created when first running syncdb
 104 [18:28] <lukasz> >>> from django.contrib.auth.models import User
 105 [18:28] <lukasz> >>> admin = User.objects.get(username='admin')
 106 [18:28] <lukasz> Here 'admin' is whatever you've chosen when asked for admin username.
 107 [18:29] <lukasz> First thing is to get hold to our admin user, because every update belongs to someone. You can see that we used 'objects' attribute of model class.
 108 [18:29] <lukasz> >>> from twitbuntu.app.models import Update
 109 [18:29] <lukasz> >>> update = Update(owner=admin, status="This is first status update")
 110 [18:29] <lukasz> At that point we have instance of the Update model, but it's not saved in the database you can see that by checking update.id attribute
 111 [18:29] <lukasz> Currently it's None
 112 [18:29] <lukasz> but when we save that object in the database
 113 [18:30] <lukasz> >>> update.save()
 114 [18:30] <lukasz> the update.id attribute has a value
 115 [18:30] <lukasz> >>> udpate.id
 116 [18:30] <lukasz> 1
 117 [18:30] <lukasz> That's only one of many ways to create instances of the models, this one is the easiest one.
 118 [18:30] <lukasz> When we have some data in the database there's time to somehow display it to the user.
 119 [18:31] <lukasz> First bit for a view to work is to tell Django for which url such view should respond to.
 120 [18:31] <lukasz> For that we have to modify urls.py file.
 121 [18:31] <lukasz> Open it and add following line just under line with 'patterns' in it, so whole bit should look like that:
 122 [18:31] <lukasz> urlpatterns = patterns('',
 123 [18:31] <lukasz>     (r'^$', 'twitbuntu.app.views.home'),
 124 [18:31] <lukasz> )
 125 [18:31] <lukasz> First bit there is regular expression for which this view will respond, in our case this is empty string (^ means beginning of the string and $ means end, so there's nothing in it), second bit is name of the function which will be called.
 126 [18:32] <lukasz> Now open app/views.py file. Here all code responsible for responding to users' requests will live.
 127 [18:32] <lukasz> First bit is to import required bit from Django:
 128 [18:32] <lukasz> from django.http import HttpResponse
 129 [18:32] <lukasz> Now we can define our (very simple) view function:
 130 [18:32] <lukasz> def home(request):
 131 [18:32] <lukasz>     return HttpResponse("Hello from Django")
 132 [18:33] <lukasz> As you can see every view function has at least one argument, which is request object
 133 [18:33] <lukasz> It contains lots of useful information about request, but for our simple example we'll not use it.
 134 [18:33] <ClassBot> chadadavis asked: Is that an instance of the *model* or a record in the update table?
 135 [18:33] <lukasz> chadadavis: both
 136 [18:33] <lukasz> saved instances are records in the database
 137 [18:34] <lukasz> but you access all data on that record as convenient python attributes on that object
 138 [18:34] <lukasz> After that we can start our app and check if everything is correct, to do that run:
 139 [18:34] <lukasz> $ ./manage.py runserver
 140 [18:34] <lukasz> If everything went ok you should see following output
 141 [18:34] <lukasz> Validating models...
 142 [18:34] <lukasz> 0 errors found
 143 [18:34] <lukasz> Django version 1.2.3, using settings 'twitbuntu.settings'
 144 [18:34] <lukasz> Development server is running at http://127.0.0.1:8000/
 145 [18:34] <lukasz> Quit the server with CONTROL-C.
 146 [18:35] <lukasz> Now you can access it at provided url
 147 [18:35] <lukasz> What you should see is "Hello from Django" text.
 148 [18:35] <lukasz> any questions/problems/comments?
 149 [18:36] <lukasz> continuing
 150 [18:36] <lukasz> It would be nice to be able to log in to our own application, fortunately Django already has required pieces inside and only thing left for us is to hook them up.
 151 [18:36] <lukasz> Add following two lines to the list of urls:
 152 [18:37] <lukasz> (r'^accounts/login/$', 'django.contrib.auth.views.login'),
 153 [18:37] <lukasz> (r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
 154 [18:37] <lukasz> Next we need to create template directory and enter it's location in settings.py file
 155 [18:37] <lukasz> $ mkdir templates
 156 [18:38] <lukasz> In settings.py file find TEMPLATE_DIRS setting:
 157 [18:38] <lukasz> import os
 158 [18:38] <lukasz> TEMPLATE_DIRS = (
 159 [18:38] <lukasz>     os.path.join(os.path.dirname(__file__), 'templates'),
 160 [18:38] <lukasz> )
 161 [18:38] <lukasz> This will ensure that Django can always find the template directory even if current working directory is not the one containing application (for example when run from Apache web server).
 162 [18:38] <lukasz> Next is to create registration dir in templates directory and put there login.html file with following content: http://paste.ubuntu.com/575631/
 163 [18:39] <lukasz> Last bit is to set up LOGIN_REDIRECT_URL in settings.py to '/':
 164 [18:39] <lukasz> LOGIN_REDIRECT_URL = '/'
 165 [18:39] <lukasz> That way after login user will be redirected to '/' url instead of default '/accounts/profile' which we don't have.
 166 [18:39] <lukasz> Now getting to http://127.0.0.1:8000/accounts/login should present you the login form and you should be able to log in to application.
 167 [18:40] <lukasz> Now it's time to use information about logged in user in our view.
 168 [18:40] <lukasz> Django provides very convenient way of accessing logged in user by adding 'user' attribute to request object.
 169 [18:40] <lukasz> It's either model instance representing logged in user or instance of AnonymousUser class which have same interface as model.
 170 [18:41] <lukasz> Easiest way distinguishing between them is by using user.is_authenticated() method
 171 [18:41] <lukasz> Modify our home view function so it looks like that: http://paste.ubuntu.com/575585/
 172 [18:41] <lukasz> That way logged in users will be greeted and anonymous users will be sent to login form. You should see "Hello username" at http://127.0.0.1:8000/
 173 [18:42] <lukasz> Using that information we can build a functionality to restrict access to some parts of an application.
 174 [18:42] <lukasz> Fortunately Django already has a lot of stuff build for that purpose.
 175 [18:42] <lukasz> Add following line to the top of the views.py file:
 176 [18:42] <lukasz> from django.contrib.auth.decorators import login_required
 177 [18:43] <lukasz> This decorator does exactly what we have done manually but it's less code which doesn't hides what this view is doing, now we can shorten it to: http://paste.ubuntu.com/575586/
 178 [18:43] <lukasz> Test view in your browser, nothing should have changed
 179 [18:43] <lukasz> Now when we have reliable way of getting to the user instance we can return all user's updates.
 180 [18:44] <lukasz> When creating an Update model we have used ForeignKey type, this connects two models together.
 181 [18:44] <lukasz> Later when we've created updates we used user instance as value of this attribute.
 182 [18:44] <lukasz> That's one way of accessing this data (every update has owner attribute).
 183 [18:44] <lukasz> Due to usage of ForeignKey pointing to User model every instance of it got also update_set attribute which contains every update which is assigned to this user.
 184 [18:44] <lukasz> Clean way of getting all user updates is:
 185 [18:45] <lukasz> >>> admin.update_set.all()
 186 [18:45] <lukasz> [<Update: Update object>]
 187 [18:45] <lukasz> But we can also get to the same information from Update model:
 188 [18:45] <lukasz> >>> Update.objects.filter(owner=admin)
 189 [18:45] <lukasz> (btw, those are only examples, you don't have to type them)
 190 [18:45] <lukasz> Both of those will return the same data, but the first one is cleaner.
 191 [18:46] <lukasz> That's just a very simple example of getting data from the database.
 192 [18:46] <lukasz> Django's functionality in that regard is way more sophisticated, but we don't have time now to dive into that.
 193 [18:46] <lukasz> Now when we know how to get to necessary data we can send it to the browser by modifying home function: http://paste.ubuntu.com/575587/
 194 [18:47] <lukasz> Here we set the content type of the response to text/plain so we can see angle brackets in the output, without that, by default, browser would hide it.
 195 [18:47] <lukasz> Now, when we have data we can work on spicing it up a little. For that we'll use templates.
 196 [18:47] <lukasz> Templates in Django have it's own syntax, it's really simple as it was created for designers, people not used to programming languages.
 197 [18:47] <lukasz> We already have templates configured due to requirements of auth system, so it will be very easy to get started.
 198 [18:48] <ClassBot> hugohirsch asked: I'm too slow to follow .... get an error msg: TemplateDoesNotExist at /accounts/login/. Looking for a file in /home/hirsch/twitbuntu/templates/registration/login.html - how can I remove the registration thing?
 199 [18:48] <lukasz> hugohirsch: sorry for being too fast
 200 [18:48] <lukasz> basically you can't, that's hardcoded in the auth application itself
 201 [18:49] <lukasz> that's how the templates are looked up, usually they are specified as apptemplatedir/templatename.html
 202 [18:49] <lukasz> so registration templates are in registration/ directory
 203 [18:49] <lukasz> admin templates land in admin/ dir, etc
 204 [18:50] <lukasz> Any other problems?
 205 [18:50] <lukasz> I'll gladly help to resolve any issues
 206 [18:51] <ClassBot> There are 10 minutes remaining in the current session.
 207 [18:53] <lukasz> ok, continuing
 208 [18:54] <lukasz> Now we need a file for a template
 209 [18:54] <lukasz> create template/home.html and put following content in it: http://paste.ubuntu.com/575588/
 210 [18:54] <lukasz> Every tag in Django templates language is contained between {% %} elements and also ending of every opening thing is done by adding end(thing) to the end (like endfor in that case).
 211 [18:54] <lukasz> To output content of the variable we're using {{ }} syntax.
 212 [18:54] <lukasz> Also we can use something called filters by using | to pass value through the named filter. We're using that to format date to nice text description of time passed.
 213 [18:54] <lukasz> That's template, now let's write view code to use it.
 214 [18:55] <lukasz> There's very convenient function when using templates in views: render_to_response
 215 [18:55] <lukasz> add following line to the top of the view.py file
 216 [18:55] <lukasz> from django.shortcuts import render_to_response
 217 [18:55] <lukasz> This function takes two arguments: name of the template to render (usually it's file name) and dictionary of arguments to pass to template. Having this in mind our home view looks like that: http://paste.ubuntu.com/575589/
 218 [18:56] <ClassBot> There are 5 minutes remaining in the current session.
 219 [18:56] <lukasz> Now running $ ./manage.py runserver you can see that page in the browser has proper title
 220 [18:56] <lukasz> I guess we don't have enough time for the rest of the things I've planned
 221 [18:56] <lukasz> we'll stop here
 222 [18:56] <lukasz> Are there any questions?
 223 [18:57] <lukasz> ok, going forward, going fast
 224 [18:58] <lukasz> It would be really nice to be able to add status updates from the web page. For that we need a form. There are couple ways of doing that in Django, but we'll show a way which is most useful for forms which are used to create/modify instances of the models.
 225 [18:58] <lukasz> By convention form definitions goes to forms.py file in your app directory. Put following bits in there: http://paste.ubuntu.com/575590/
 226 [18:58] <lukasz> This is very simple form which has only one field in it.
 227 [18:58] <lukasz> Now in views.py we need to instantiate this form and pass it to the template. After modifications this file should look like this: http://paste.ubuntu.com/575591/
 228 [18:58] <lukasz> One thing which is new here, the RequestContext thing. This is connected to automatic CSRF (cross site request forgery) protection, which Django enables by default. Basically it provides templates with richer set of accessible data from which we'll going to use only csrf_token tag.
 229 [18:59] <lukasz> Last bit is to display this form in template. Add this bit just after <body> tag:
 230 [18:59] <lukasz> http://paste.ubuntu.com/575650/
 231 [18:59] <lukasz> Now when we have form properly displayed it would be useful to actually create updates based on the data entered by the user. That requires little bit of work inside our home view. Fortunately this is pretty straightforward to do: http://paste.ubuntu.com/575594/
 232 [18:59] <lukasz> First thing is to check weather we're processing POST or GET request, if POST that means that user pressed 'Update' button on our form and we can start processing submitted data.
 233 [18:59] <lukasz> All POST data is conveniently gathered by Django in a dictionary in request.POST. For this case it's not really critical to know what exactly is send, UpdateForm will handle that. Bit with instance= is to automatically set update owner, without that form would not be valid and nothing would be saved in the database.
 234 [19:00] <lukasz> Checking if form is valid is very simple, just invoke .is_valid() method on it. If True is returned then we're saving the form to the database, which returns Update instance. It's not really needed anywhere but I wanted to show you that you can do something with it.
 235 [19:00] <lukasz> Last bit is to create empty form, so that Status field will be clear, ready for next update.
 236 [19:00] <lukasz> If you try to send update without any content you'll see that there's an error message displayed 'This field is required'. All of that is automatically handled by forms machinery.
 237 [19:00] <lukasz> It's nice to be able to see our own status updates but currently it's only viewable by logged user.
 238 [19:00] <lukasz> but that's a homework
 239 [19:00] <lukasz> or a thing to look into existing code
 240 [19:00] <lukasz> thank you for your attention :)

MeetingLogs/devweek1103/IntroductionToDjangoDevelopment (last edited 2011-03-05 04:42:33 by 111)