Adding a string representation of a model
To create a human-readable presentation of a model object you need to implement Model.__str__()
method (or Model.__unicode__()
on python2). This method will be called whenever you call str()
on your model. Here's an example:
-
Create a book model
from django.db import models class Book(models.Model): name = models.CharField(max_length=50) author = models.CharField(max_length=50)
-
Create an instance of the model in the database:
>>> himu_book = Book.object.create(name='Himu Mama', author='Humayun Ahmed')
-
Execute
print()
on the instance:>>> print(himu_book) <Book: Book object>
<Book: Book object>, is of no help to us. To fix this, let's add a __str__
method.
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.CharField(max_length=50)
def __str__(self):
return '{} by {}'.format(self.name, self.author)
Note the python_2_unicode_compatible
decorator is is needed only if you want your code to be compatible with python 2. This decorator copies the __str__
method to create a __unicode__
method. Import it from django.utils.encoding
.
Now if we print the book instance:
>>> print(himu_book)
Himu Mama by Humayun Ahmed
Much better!
Advanced models
A model can provide a lot more information than just the data about an object. Let's see an example and break it down into what it is useful for:
from django.db import models
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
slug = models.SlugField()
title = models.CharField(max_length=128)
publish_date = models.DateField()
def get_absolute_url(self):
return reverse('library:book', kwargs={'pk':self.pk})
def __str__(self):
return self.title
class Meta:
ordering = ['publish_date', 'title']
Automatic primary key
You might notice the use of self.pk
in the get_absolute_url
method. If a models don't have a primary key, django will automatically provide one. That's one less thing to worry, it let you set foreign key to any models and get them easily.
Absolute url
The first function that is defined is get_absolute_url
. This way, if you have an book, you can get a link to it without fiddling with the url tag, resolve, attribute and the like. Simply call book.get_absolute_url
and you get the right link. As a bonus, your object in the django admin will gain a button "view on site".
String representation
Have a __str__
method let you use the object when you need to display it. For example, with the previous method, adding a link to the book in a template is as simple as <a href="{{ book.get_absolute_url }}">{{ book }}</a>
. Straight to the point. This method also control what is displayed in the admin drop-down, for foreign key for example.
The class decorator let you define the method once for both __str__
and __unicode__
on python 2 while causing no issue on python 3. If you expect you app to run on both version, that's the way to go.
Slug field
The slug field is similar to a char field but accept less symbols. By default, only letters, numbers, underscores or hyphens. It is useful if you want to identify an object using a nice representation, in url for example.
The Meta class
The Meta
class let us define a lot more of information on the whole collection of item. Here, only the default ordering is set. It is useful with the ListView object for example. It take an ideally short list of field to use for sorting. Here, book will be sorted first by publication date then by title if the date is the same.
Other frequents attributes are verbose_name
and verbose_name_plural
. By default, they are generated from the name of the model and should be fine. But the plural form is naive, simply appending an 's' to the singular so you might want to set it explicitly in some case.
Applying the changes to the database (Migrations)
After creating a new model or modifying existing models, you will need to generate migrations for your changes and then apply the migrations to the specified database. This can be done by using the Django's built-in migrations system. Using the manage.py
utility when in the project root directory:
python manage.py makemigrations <appname>
The above command will create the migration scripts that are necessary under migrations
subdirectory of your application. If you omit the <appname>
parameter, all the applications defined in the INSTALLED_APPS
argument of settings.py
will be processed. If you find it necessary, you can edit the migrations.
You can check what migrations are required without actually creating the migration use the --dry-run option, eg:
python manage.py makemigrations --dry-run
To apply the migrations:
python manage.py migrate <appname>
The above command will execute the migration scripts generated in the first step and physically update the database.
Django >1.10
The new makemigrations --check
option makes the command exit with a non-zero status when model changes without migrations are detected.
See Migrations for more details on migrations.
Creating your first model
Models are typically defined in the models.py
file under your application subdirectory. The Model
class of django.db.models
module is a good starting class to extend your models from. For example:
from django.db import models
class Book(models.Model):
author = models.CharField(max_length=50)
publish_date = models.DateField()
Each attribute in a model, author
and publish_date
in this case, represents a corresponding column in the database.
A basic unmanaged table.
Here is a basic unmanaged model that can be used to represent an existing legacy database or a view.
class Dummy(models.Model):
something = models.IntegerField()
class Meta:
managed = False
This may be mapped to a view defined in SQL as follows.
CREATE VIEW myapp_dummy AS
SELECT id, something FROM complicated_table
WHERE some_complicated_condition = True
UUID Primary key
A model by default will use an auto incrementing (integer) primary key. This will give you a sequence of keys 1, 2, 3.
Different primary key types can be set on a model with a small alterations to the model.
A UUID is a universally unique identifier, this is 32 character random identifier which can be used as an ID. This is a good option to use when you do not want sequential ID's assigned to records in your database.
import uuid
from django.db import models
class ModelUsingUUID(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
The generated key will be in the format 7778c552-73fc-4bc4-8bf9-5a2f6f7b7f47
Basic Django DB queries
Django ORM is a powerful abstraction that lets you store and retrieve data from the database without writing sql queries yourself.
Let's assume the following models:
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.ForeignKey(Author)
Assuming you've added the above code to a django application and run the migrate
command (so that your database is created).
Start the Django shell by
python manage.py shell
This starts the standard python shell but with relevant Django libraries imported, so that you can directly focus on the important parts.
Start by importing the models we just defined (I am assuming this is done in a file models.py
)
from .models import Book, Author
Run your first select query:
>>> Author.objects.all()
[]
>>> Book.objects.all()
[]
Lets create an author and book object:
>>> hawking = Author(name="Stephen hawking")
>>> hawking.save()
>>> history_of_time = Book(name="history of time", author=hawking)
>>> history_of_time.save()
Now lets run the query
>>> Book.objects.all()
[<Book: Book object>]
>>> book = Book.objects.first() #getting the first book object
>>> book.name
u'history of time'
Let's add a where clause to our select query
>>> Book.objects.filter(name='nothing')
[]
>>> Author.objects.filter(name__startswith='Ste')
[<Author: Author object>]
To get the details about the author of a given book
>>> book = Book.objects.first() #getting the first book object
>>> book.author.name # lookup on related model
u'Stephen hawking'
To get all the books published by Stephen Hawking (Lookup book by its author)
>>> hawking.book_set.all()
[<Book: Book object>]
_set
is the notation used for "Reverse lookups" i.e. while the lookup field is on the Book model, we can use book_set
on an author object to get all his/her books.
Creating a model with relationships
Many-to-One Relationship
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
#Book has a foreignkey (many to one) relationship with author
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publish_date = models.DateField()
Most generic option. Can be used anywhere you would like to represent a relationship
Many-to-Many Relationship
class Topping(models.Model):
name = models.CharField(max_length=50)
# One pizza can have many toppings and same topping can be on many pizzas
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Toppings)
Internally this is represented via another table. And ManyToManyField
should be put on models that will be edited on a form. Eg: Appointment
will have a ManyToManyField
called Customer
, Pizza
has Toppings
and so on.
Many-to-Many Relationship using Through classes
class Service(models.Model):
name = models.CharField(max_length=35)
class Client(models.Model):
name = models.CharField(max_length=35)
age = models.IntegerField()
services = models.ManyToManyField(Service, through='Subscription')
class Subscription(models.Model):
client = models.ForeignKey(Client)
service = models.ForeignKey(Service)
subscription_type = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES)
created_at = models.DateTimeField(default=timezone.now)
This way, we can actually keep more metadata about a relationship between two entities. As can be seen, a client can be subscribed to several services via several subscription types. The only difference in this case is that to add new instances to the M2M relation, one cannot use the shortcut method pizza.toppings.add(topping)
, instead, a new object of the through class should be created, Subscription.objects.create(client=client, service=service, subscription_type='p')
One-to-One Relationship
class Employee(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
spouse = models.OneToOneField(Spouse)
class Spouse(models.Model):
name = models.CharField(max_length=50)
Use these fields when you will only ever have a composition relationship between the two models.
Computed Values
Once a model object has been fetched, it becomes a fully realized instance of the class. As such, any additional methods can be accessed in forms and serializers (like Django Rest Framework).
Using python properties is an elegant way to represent additional values that are not stored in the database due to varying circumstances.
def expire():
return timezone.now() + timezone.timedelta(days=7)
class Coupon(models.Model):
expiration_date = models.DateField(default=expire)
@property
def is_expired(self):
return timezone.now() > self.expiration_date
While most cases you can supplement data with annotations on your querysets, computed values as model properties are ideal for computations that can not be evaluated simply within the scope of a query.
Additionally, properties, since they are declared on the python class and not as part of the schema, are not available for querying against.
Model mixins
In same cases different models could have same fields and same procedures in the product life cycle. To handle these similarities without having code repetition inheritance could be used. Instead of inheriting a whole class, mixin design pattern offers us to inherit (or some says include) some methods and attributes. Let's see an example:
class PostableMixin(models.Model):
class Meta:
abstract=True
sender_name = models.CharField(max_length=128)
sender_address = models.CharField(max_length=255)
receiver_name = models.CharField(max_length=128)
receiver_address = models.CharField(max_length=255)
post_datetime = models.DateTimeField(auto_now_add=True)
delivery_datetime = models.DateTimeField(null=True)
notes = models.TextField(max_length=500)
class Envelope(PostableMixin):
ENVELOPE_COMMERCIAL = 1
ENVELOPE_BOOKLET = 2
ENVELOPE_CATALOG = 3
ENVELOPE_TYPES = (
(ENVELOPE_COMMERCIAL, 'Commercial'),
(ENVELOPE_BOOKLET, 'Booklet'),
(ENVELOPE_CATALOG, 'Catalog'),
)
envelope_type = models.PositiveSmallIntegerField(choices=ENVELOPE_TYPES)
class Package(PostableMixin):
weight = models.DecimalField(max_digits=6, decimal_places=2)
width = models.DecimalField(max_digits=5, decimal_places=2)
height = models.DecimalField(max_digits=5, decimal_places=2)
depth = models.DecimalField(max_digits=5, decimal_places=2)
To turn a model into an abstract class, you will need to mention abstract=True
in its inner Meta
class.
Django does not create any tables for abstract models in the database.
However for the models Envelope and Package will have the packages
Furthermore the fields some model methods will be needed at more than one models.
Thus these methods could be added to mixins to prevent code repetition.
For example if we create a method to set delivery date to PostableMixin
it will be accesible from both of its children:
class PostableMixin(models.Model):
class Meta:
abstract=True
...
...
def set_delivery_datetime(self, dt=None):
if dt is None:
from django.utils.timezone import now
dt = now()
self.delivery_datetime = dt
self.save()
This method could be used as following on the children:
>> envelope = Envelope.objects.get(pk=1)
>> envelope.set_delivery_datetime()
>> pack = Package.objects.get(pk=1)
>> pack.set_delivery_datetime()