Skip to content
Snippets Groups Projects
introduction.md 14.1 KiB
Newer Older
Holger Dinkel's avatar
Holger Dinkel committed
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 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
Tutor: 

 - Holger Dinkel

Tutees: 

 - Petr Strnad
 - Charles Girardot
 - Joachim Weischenfeld
 - Guangyou Duan
 - Matthias Monfort
 - Serge Dmitrieff
 - Lucas Tafur
 - Jin Wang
 - Maia Segura Wang
 - Thomas Schwarzl
 - Sajoscha Sauer

# Django Introductory Tutorial

These steps are pretty much taken and adapted from the official [Django Tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/)

In this tutorial we want to create a simple webpage which allows users to
register to certain events by providing some information about themselves and
choosing a an event from a list of created entries. We also want to use the
builtin django-admin system to easily create/edit/remove entries in our
database.

First, we start by creating a new project using the django-admin tool:

    django-admin.py startproject mysite

This will create the folder 'mysite' along with some files in it.

    mysite/
        manage.py
        mysite/
            __init__.py
            settings.py
            urls.py
            wsgi.py

These files are:

* The outer **mysite**/ root directory is just a container for your project. Its name doesn't matter to Django; you can rename it to anything you like.
* **manage.py**: A command-line utility that lets you interact with this Django project in various ways. 
* The inner **mysite**/ directory is the actual Python package for your project. Its name is the Python package name you'll need to use to import anything inside it (e.g. mysite.urls).
* **mysite/__init__.py**: An empty file that tells Python that this directory should be considered a Python package. 
* **mysite/settings.py** : Settings/configuration for this Django project. Django settings will tell you all about how settings work.
* **mysite/urls.py**: The URL declarations for this Django project; a 'table of contents' of your Django-powered site. 
* **mysite/wsgi.py**: An entry-point for WSGI-compatible web servers to serve your project. 


Let's have a look at the settings.py file first and in particular inspect the database settings (we will be using sqlite for now):

    DATABASES = { 
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }   
    }

Now we initialize the database by running the migrate command:

    python manage.py migrate

## Running Django

Finally let's see if we can run the development server to verify everything worked as expected.
Change into the outer mysite directory, if you haven't already, and run the following commands:

    python manage.py runserver

## Creating apps

Projects vs. apps

What's the difference between a project and an app? An app is a Web application
that does something - e.g., a Weblog system, a database of public records or a
simple registrations app. A project is a collection of configuration and apps for a
particular Web site. A project can contain multiple apps. An app can be in
multiple projects.


To create your app, make sure you're in the same directory as manage.py and type this command:

    python manage.py startapp registrations

That'll create a directory registrations, which is laid out like this:

    registrations/
        __init__.py
        admin.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py

### Philosophy

A model is the single, definitive source of data about your data. It contains
the essential fields and behaviors of the data you're storing. Django follows
the DRY Principle. The goal is to define your data model in one place and
automatically derive things from it.

This includes the migrations - unlike in Ruby On Rails, for example, migrations
are entirely derived from your models file, and are essentially just a history
that Django can roll through to update your database schema to match your
current models.


## Creating models

In our simple registrations app, we'll create two models: 
Events and Registrations. An Event has a date, a title and a description. A Registration has three fields: First & lastname and a relational field to registrations.

These concepts are represented by simple Python classes. Edit the registrations/models.py file so it looks like this:

registrations/models.py

from django.db import models

    class Event(models.Model):
        id = models.AutoField(primary_key=True)
        title = models.CharField(max_length=128)
        description = models.TextField(blank=True)
        date = models.DateTimeField()

    class Registration(models.Model):
        id = models.AutoField(primary_key=True)
        firstname = models.CharField(max_length=128)
        lastname = models.CharField(max_length=128)
        event = models.ForeignKey(Event)

Note that Events has to be declared before Registrations as the latter references the former...
(see the django documentation on the list of possible [field types](https://docs.djangoproject.com/en/1.7/ref/models/fields/#field-types)

Each model will be represented in the database by an individual table. Django
will create these for us when we invoke the migration commands, but first we
need to tell django that it should actually search for and use these new
models. We do this by adding our app 'registrations' to the list of
"INSTALLED_APPS" in the settings.py

mysite/settings.py

    INSTALLED_APPS = (
        ...
        'django.contrib.staticfiles',
        'registrations',
    )

Now we can run the migration steps which in the background will create all the necessary complicated SQL statements to populate the database:

    python manage.py makemigrations registrations

If you want, you can run a sanity check to see if django is ok with your configurations 

    python manage.py check

before you actually commit the migration:

    python manage.py migrate


## Playing with the API

Now, with the database in place, let's drop into the interactive Python shell and play around with the free API Django gives you. To invoke the Python shell, use this command:

    python manage.py shell

Once you're in the shell, explore the database API:

    >>> from registrations.models import Registration, Event

    >>> print Event.objects.all()
    []

    >>> e = Event(title="HUB15", description="User Experience workshop")
    >>> e.save()

    >>> e.id
    1

    >>> print Event.objects.all()
    [Event object <...>]

    >>> r = Registration(firstname='Holger', lastname='Dinkel', even=e)
    >>> r.save()

## Modifying models

Let's beautify the way that models are printed.
For this we can add a function "\_\_str\_\_" to each model:

    class Event(models.Model):
        ...
        def __str__(self):
            return "%s" % self.title

    class Registration(models.Model):
        ...
        def __str__(self):
            return "%s %s" % (self.firstname, self.lastname)

So next time we print an object we will get something more readable. Note how
with this easy way you just effectively added a method to a database structure
(both represented in Django).



## Using the admin interface

    python manage.py createsuperuser

Now start the development server like so:

    python manage.py runserver

and navigate your browser to http://127.0.0.1:8000/admin/ and provide your newly created credentials.

However our two models, Event & Registration do not show up!?
We first need to tell django to automatically include these in the admin interface:

registrations/admin.py

    from django.contrib import admin
    from registrations.models import Event, Registration

    admin.site.register(Event)

Reload the admin page in your browser and both models should be there.
Feel free to add/edit more entries for each...


## Write your first view

Let's write the first view. Open the file registrations/views.py and put the following Python code in it:

registrations/views.py

    from django.http import HttpResponse

    def index(request):
        return HttpResponse("Hello, world. You're at the registrations index.")

This is the simplest view possible in Django. To call the view, we need to map it to a URL - and for this we need a URLconf.

To create a URLconf in the registrations directory, create a file called urls.py. Your app directory should now look like:

    registrations/
        __init__.py
        admin.py
        models.py
        tests.py
        urls.py
        views.py

In the registrations/urls.py file include the following code:

registrations/urls.py

    from django.conf.urls import patterns, url

    from registrations import views

    urlpatterns = patterns('',
        url(r'^$', views.index, name='index'),
    )

The next step is to point the root URLconf at the registrations.urls module. In mysite/urls.py insert an include(), leaving you with:
mysite/urls.py

    from django.conf.urls import patterns, include, url
    from django.contrib import admin

    urlpatterns = patterns('',
        url(r'^registrations/', include('registrations.urls')),
        url(r'^admin/', include(admin.site.urls)),
    )



registrations/views.py

    from django.http import HttpResponse
    from registrations.models import Event

    def index(request):
        latest_event_list = Event.objects.order_by('-date')[:5]
        output = ', '.join([p.title for p in latest_event_list])
        return HttpResponse(output)


There's a problem here, though: the page's design is hard-coded in the view. If
you want to change the way the page looks, you'll have to edit this Python
code. So let's use Django's template system to separate the design from Python
by creating a template that the view can use.

First, create a directory called templates in your registrations directory. Django will look for templates in there.

Django's TEMPLATE_LOADERS setting contains a list of callables that know how to
import templates from various sources. One of the defaults is
django.template.loaders.app_directories.Loader which looks for a 'templates'
subdirectory in each of the INSTALLED_APPS - this is how Django knows to find
the registrations templates even though we didn't modify TEMPLATE_DIRS.

Within the templates directory you have just created, create another directory
called registrations, and within that create a file called index.html. In other words,
your template should be at registrations/templates/registrations/index.html. Because of how the
app_directories template loader works as described above, you can refer to this
template within Django simply as registrations/index.html.

Put the following code in that template:

registrations/templates/registrations/index.html

    {% if latest_event_list %}
        <ul>
        {% for event in latest_event_list %}
            <li><a href="/registrations/{{ event.id }}/">{{ event.title }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No registrations are available.</p>
    {% endif %}



registrations/views.py

    from django.http import HttpResponse
    from django.template import RequestContext, loader
    from registrations.models import Event

    def index(request):
        latest_event_list = Event.objects.order_by('-date')[:5]
        template = loader.get_template('registrations/index.html')
        context = RequestContext(request, {
            'latest_event_list': latest_event_list,
        })
        return HttpResponse(template.render(context))



It's a very common idiom to load a template, fill a context and return an
HttpResponse object with the result of the rendered template. Django provides a
shortcut:  

The 'render()' function takes the request object as its first argument, a
template name as its second argument and a dictionary as its optional third
argument. It returns an HttpResponse object of the given template rendered with
the given context.

the 'get_object_or_404' function is handy to retrieve an object from the database
or automatically return a 404 error message saying 'object not found'.

registrations/views.py

    from django.http import HttpResponse
    from django.template import RequestContext, loader
    from django.shortcuts import get_object_or_404, render
    from registrations.models import Event

    (...)

    def detail(request, event_id):
        event = get_object_or_404(Event, pk=event_id)
    #   Equivalent to:
    #    try:
    #        event = Event.objects.get(pk=event_id)
    #    except Event.DoesNotExist:
    #        raise Http404
        return render(request, 'registrations/detail.html', {'event': event})

Next, we create a very simple 'detail' page like so:

registrations/templates/registrations/detail.html

    {{ event }}

In order for this detail view to work, we need to update the urls.py

    url(r'^(?P<event_id>\d+)/$', views.detail, name='detail'),


## Writing a simple form


In order to be able to interact with the page, we'll create a simple form to enter data into the database:

registrations/detail.html

    <h1>{{ event.title }}</h1>
    {{ event.title }}: {{ event.description }} on {{ event.date}}

    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

    <p>
    Registered People:
    <ul>
    {% for registration in event.registration_set.all %}
    <li>{{ registration }}</li>
    {% endfor %}
    </ul>
    </p>

    <p>
    <form action="{% url 'register' event.id %}" method="post">
    {% csrf_token %}
    Firstname:<input type="text" name="firstname" /><br/>
    Lastname:<input type="text" name="lastname" /><br/>
    <input type="submit" value="Register" />
    </form>
    </p>

Add another url to urls.py:

    url(r'^(?P<event_id>\d+)/register$', views.register, name='register'),

and add another view to views.py:

    def register(request, event_id):
        event = get_object_or_404(Event, pk=event_id)
        firstname=request.POST['firstname']
        lastname=request.POST['lastname']
        try:
            selected_registration = event.registration_set.get(firstname=firstname, lastname=lastname)
            # Redisplay the question voting form.
            return render(request, 'registrations/detail.html', {
                'event': event,
                'error_message': "Already registered",
            })
        except (KeyError, Registration.DoesNotExist):
            r = Registration(event = event, firstname = firstname, lastname= lastname)
            r.save()
        return render(request, 'registrations/detail.html', {'event': event})