Django for Designers: Difference between revisions

imported>Paulproteus
(→‎Curriculum: Add new models-y link)
imported>Paulproteus
Line 36:
* [[/Sharing|Part 6: Sharing with others]]
* [[/Whats_next|Part 7: Exercises for the reader]]
 
=== Part 3: Models, our database, and making it visible ===
 
Remember to make a new branch for section 3 based off the official branch!
 
<source lang="bash">
# in django-for-designers/myproject
$ git branch my-branch-3 origin/pre-part-3
$ git checkout my-branch-3
</source>
 
====Introduction to databases and the ORM, or: finally, something we couldn't've done with plain HTML/CSS/JS!====
 
Okay, being able to design your URLs however you want, without them having to correspond to your actual file structure, is pretty neat. But besides that, we haven't done anything yet that you couldn't do with just HTML, CSS, and JavaScript. Time to fix that.
 
One key thing that separates web ''apps'' from web ''pages'' is that apps typically store data somewhere. The code that runs the app chooses what data seems most important to show at the moment. When writing an app using Django, we configure the storage through Django ''models''.
 
Every Django app comes with a ''models.py'' in which you list each kind of data you want to store. These are configured through Python classes (to name the kind of data) with a sequence of attributes (which control the pieces of data that make up the model). If you're familiar with, or interested in, SQL, these correspond to ''tables'' and ''columns'' respectively.
 
The models that you define here are built on top of Django's ''object-relational mapper'', or ORM. Because you define your models as Python ''objects'', you can effectively write queries against a ''relational'' database by instead coding in Python with reference to your objects. Django then ''maps'' your Python code into relational database queries in SQL.
 
By configuring your data access in Python, Django makes it easy to take advantage of your data layout in other places. For example, you'll see later how the ORM makes it easy to automatically generate forms that ask for exactly the information you need. It is also generally convenient to write your entire app in one language, rather than being required to switch to SQL to do queries. The ORM also handles "escaping," which makes your SQL queries handle Unicode and other strange characters properly. These conveniences let you avoid [http://en.wikipedia.org/wiki/Mojibake mojibake]-style data corruption and [http://en.wikipedia.org/wiki/SQL_injection SQL injection] attacks effortlessly.
 
TODO (models.py --> ORM --> DB chart)
 
====Creating a basic model====
 
Let's make it so we can store some real live data in our application! In our simple bookmarking app, we’ll create two models: Bookmarks and Tags. What information do we need to store about these objects?
 
A bookmark has:
* A URL
* A title (optionally)
* A timestamp for when the bookmark was made
 
A tag has:
* The tag slug/name
 
A tag also needs to know which bookmarks it applies to.
 
How do we represent this information in a Django model?
 
Open up bookmarks/models.py in your editor.
 
<source lang="python">
from django.db import models
 
 
class Bookmark(models.Model):
url = models.URLField()
timestamp = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=200, blank=True, default="")
</source>
 
What's going on here?
* We've made a Bookmark class that inherits from Django's model class.
* We gave it a url, which is a URLField (a field that expects a string that parses as a valid URL).
* We gave it a timestamp, which is a DateTimeField (a field that expects a Python datetime object). auto_now_add means that the field will automatically set itself to the time when the model is created.
* Finally, we gave it a title, which is a CharField (a field that expects a string of some length or less, in this case less than 200 characters). "blank=True" means that Django will permit this field to be blank. We also set a default of the empty string, so that if someone fails to specify a title we permit that.
 
Now let's add a tag model! In the same file, below the Bookmark model:
 
<source lang="python">
class Tag(models.Model):
bookmark = models.ManyToManyField(Bookmark)
slug = models.CharField(max_length=50, unique=True)
</source>
 
The tag slug is stored in another CharField, which we've seen before. But what's this ManyToManyField? Well, it's one type of field for denoting a relationship between two models.
 
In relational databases, there's two basic types of relationships. In a ForeignKey relationship, one model relates to one and only one other model. You could imagine this like a Car model and a Wheel model. Each Wheel instance belongs to one and only one Car, so it would have a ForeignKey field for its car. The car, of course, can have multiple wheels.
 
With a ManyToManyField, on the other hand, the relationship isn't exclusive for either of the models involved. For instance, you could imagine having a Pizza model and a Topping model. Each Pizza can have multiple Toppings, and each Topping can be on multiple Pizzas.
 
(There are other variants of these fields, but this is the basic concept. See https://docs.djangoproject.com/en/dev/ref/models/fields/#module-django.db.models.fields.related for more details for how these relationship fields work in Django.)
 
In this case, each Bookmark can have multiple Tags, and each Tag can apply to multiple Bookmarks. So we use a ManyToManyField to store that information.
 
==== Creating database tables for your app (carefully, with South) ====
 
When you first create your models, you might not always know exactly what fields your models will need in advance. Maybe someday your bookmarks app will have preview thumbnail images! Then you would want to add another field to the model to store that information. Maybe someday you'll decide that tracking all the timestamps is silly, and want to delete that field.
 
Unfortunately, Django (and most database-using software) can’t figure out how to handle model changes very well on its own. Fortunately, a Django app called South that we installed earlier can handle these database changes--called ‘migrations’--for us.
 
Now that we’ve made our first version of our models file, let’s set up our bookmarks app to work with South so that we can make migrations with it in the future!
 
On the command line, write:
 
<source lang="bash">
# in django-for-designers/myproject
$ python manage.py schemamigration bookmarks --initial
</source>
 
As you can see, that’s created a migrations directory for us, and automatically made a new migration file inside it.
 
<source lang="bash">
# in django-for-designers/myproject
$ ls bookmarks/migrations/
0001_initial.py __init__.py
</source>
 
All we need to do now is apply our new migration:
 
<source lang="bash">
# in django-for-designers/myproject
$ python manage.py migrate bookmarks
</source>
 
Great! Now our database file knows about bookmarks and its new models, and if we need to change our models, South is set up to handle those changes. We’ll come back to South later.
 
IMPORTANT: You can't migrate an app if it's already been synced in the database using
 
<source lang="bash">
python manage.py syncdb
</source>
 
. But you do need to run syncdb at least once before you use south (since south itself uses syncdb to give itself space in your database). That's why it's super important that when you run syncdb, south should be listed under INSTALLED_APPS, but none of your own apps should be, and after you add your app to INSTALLED_APPS, you must not run syncdb again until after you've already set up migrations with that app.
 
Add and commit all your work, including the migrations folder that South generated for you!
 
==== Add some bookmarks via the command line ====
 
Now, let’s hop into the interactive Python shell and play around with the free API ("Application programming interface" -- APIs aren't just data doodads that hip web startups provide for you, they're an important concept in software architecture.) that Django gives you. To invoke the Python shell, use this command:
 
<source lang="bash">
# in django-for-designers/myproject
$ python manage.py shell</source>
 
We’re using this instead of simply typing “python”, because manage.py's shell sets up the project’s environment for you. “Setting up the environment” involves two things:
* Making sure bookmarks (and any other apps you might have) are on the right path to be imported.
* Setting the DJANGO_SETTINGS_MODULE environment variable, which gives Django the path to your settings.py file.
 
Once you’re in the shell, let's explore the database API. Let's import the model classes we just wrote:
<source lang="python">
>>> from bookmarks.models import Bookmark, Tag
</source>
 
List all the current Bookmarks:
 
<source lang="python">
>>> Bookmark.objects.all()
[]
</source>
 
How many bookmarks is this? It's an empty list, so zero!
 
Let's add a bookmark:
 
<source lang="python">
>>> b = Bookmark(url="http://www.bringinthecats.com/", title="Funny link")
</source>
 
We could specify the value for timestamp as well here, but we don't have to since when we wrote its model definition, we specified that timestamp has a default value (of when the bookmark is created). We did specify a title for the bookmark, even though we didn't have to since it's an optional field.
 
Try getting the ID number of our new bookmark 'b' by typing:
 
<source lang="python">>>> b.id</source>
 
Python has given you back your prompt because the value of b.id is ''None''. That is because we have not yet saved the object; to the database, it does not exist.
 
Save your bookmark to the database. In Django, you have to call save() explicitly.
 
<source lang="python">>>> b.save()</source>
 
Now, try again to get the id of the Bookmark instance. Because it’s been saved, it has an ID in the database now! Even though we didn't specify one in our models.py, every saved model instance automatically has an id field.
 
<source lang="python">>>> b.id
1</source>
 
Access the database columns (Fields, in Django parlance) as Python attributes:
 
<source lang="python">
>>> b.title
"Funny link"
>>> b.timestamp
datetime.datetime(2011, 12, 1, 3, 3, 55, 841929)
</source>
 
We can change the bookmark title by changing its title attribute, then calling save().
 
<source lang="python">
>>> b.title = "WHEEEE"
>>> b.save()
>>> b.title
"WHEEEE"
</source>
 
If we now ask Django to show a list of all the Bookmark objects available, we can see it's no longer an empty list!
 
<source lang="python">
>>> Bookmark.objects.all()
[<Bookmark: Bookmark object>]
</source>
 
==== Fix the hideous default model representation====
Wait a minute! <Bookmark: Bookmark object> is an utterly unhelpful representation of this object. Let’s fix that by editing the Bookmark model. Use your text editor to open the bookmarks/models.py file and adding a __unicode__() method to both Bookmark and Tag:
 
<source lang="python">
class Bookmark(models.Model):
url = models.URLField()
timestamp = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=200, blank=True, default="")
 
def __unicode__(self):
return self.url
 
 
class Tag(models.Model):
bookmark = models.ManyToManyField(Bookmark)
slug = models.CharField(max_length=50, unique=True)
 
def __unicode__(self):
return self.slug
</source>
 
Let's see our new shiny __unicode__ methods in action. Start a new Python interactive shell by running:
 
<source lang="bash">
# in django-for-designers/myproject
$ python manage.py shell</source>
 
<source lang="python">
>>> from bookmarks.models import Bookmark, Tag
>>> Bookmark.objects.all()
[<Bookmark: http://www.bringinthecats.com/>]
</source>
 
Save and commit your changes.
 
==== Adding more data via the shell ====
 
<source lang="python">
>>> b = Bookmark(url="http://www.google.com")
>>> b.save()
>>> b = Bookmark(url="https://us.pycon.org/2013/", title="PyCon US website")
>>> b.save()
>>> b = Bookmark(url="https://www.djangoproject.com")
>>> b.save()
</source>
 
We've created a bunch of bookmarks, but no tags! Let's change that.
 
<source lang="python">
>>> b = Bookmark.objects.all()[0]
>>> b
<Bookmark: http://www.bringinthecats.com/>
>>> b.tag_set.create(slug="cats")
<Tag: cats>
>>> b.tag_set.create(slug="music")
<Tag: music>
>>> b.tag_set.create(slug="funny")
<Tag: funny>
>>> b.save()
</source>
 
==== Slicing and dicing Django data ====
 
There are many methods for searching across your Django models.
 
We can filter our bookmarks, for instance for ones with a particular URL:
 
<source lang="python">
>>> foo = Bookmark.objects.filter(url="http://www.bringinthecats.com/")
>>> foo
[<Bookmark: http://www.bringinthecats.com/>]
</source>
 
Or for bookmarks which have titles:
 
<source lang="python">
>>> titled_bookmarks = Bookmark.objects.exclude(title="")
>>> titled_bookmarks
[<Bookmark: http://www.bringinthecats.com/>, <Bookmark: https://us.pycon.org/2013/>]
</source>
 
If you try to use filter to search for a question that does not exist, filter will give you the empty list.
 
<source lang="python">
>>> Bookmark.objects.filter(title="Who framed Roger Rabbit?")
[]
</source>
 
The get method, on the other hand, returns exactly one hit. If it finds zero matches, or more than one match, it will raise an exception.
 
<source lang="python">
>>> Bookmark.objects.get(id=1)
<Bookmark: http://www.bringinthecats.com/>
>>> Bookmark.objects.get(id=4)
Traceback (most recent call last):
...
DoesNotExist: Bookmark matching query does not exist.
</source>
 
More information on making queries with Django's ORM can be found in the Django docs at https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-objects.
 
=== Part 3.5: Changing our mind and adding users ===
Anonymous user