By the end of this, developers should be able to:
- Create one-to-many relationships with Django
- Create many-to-many relationships with Django
- Diagram relationships with ERDs
- Fork and clone this repository into the
django-env
folder. FAQ - Run
pipenv shell
inside thedjango-env
folder to start up your virtual environment. - Change into this repository's directory
- Create a new branch,
training
, for your work. - Checkout to the
training
branch. - Create a psql database with
createdb "django-relationships"
- Generate and run migrations with
python manage.py makemigrations
andpython manage.py migrate
- Run the server with
python manage.py runserver
So far we have built some pretty basic applications with Django and Python, but now we are going to step it up and add in relationships to our models. To guide us, we have plenty of documentation about the API we are building, including user stories and ERDs (Entity Relationship Diagrams).
This repository has our V1 for a library and hospital app. Our docs show us that for V2 and V3, we are adding relationships to the different models.
For V2 of our library app we need to add a new resource called Author
and
connect it to our Book
resource. We will be building a one-to-many
relationship where a Book
has one Author
and an Author
can be referenced
on many Book
resources.
We can also think of this as an Author
having multiple Book
s, which is how
we diagram our ERD:
Author
-|---< Book
In order to get started we should work methodically and build out Author
and
make sure that resource works on it's own before connecting it to Book
.
This repo has starter code for our Author
resource, so right now we can CRUD
on that resource successfully on it's own. This is good - going methodically
and making sure Author
works properly before adding a relationship will
ultimately help us write better code.
Now, to connect Book
and Author
we have to look to the model first. This
step will involve us adding a field to Book
that references the Author
resource.
author = models.ForeignKey('Author', related_name='books', on_delete=models.CASCADE)
Let's break this down:
-
models.ForeignKey
: creates a foreign key reference to the'Author'
model. This will allow theauthor
field onBook
to point to a specificAuthor
in our database.- Note: Foreign keys are used in databases to connect resources.
-
related_name
: this is what we will be able to access on the other side of our relationship to see what books an author wrote. -
on_delete=models.CASCADE
: this will cause the books our author wrote to be deleted if the author is as well.- We have lots of options of what we can put here.
That's all we need to do to actually connect these resources!
We can ping our endpoint to create or update a book and send along an ID value
for the author
field. This will be that "foreign key" that points to the
author who wrote the book.
However, we can't see the books that our authors wrote on the other side yet!
The AuthorSerializer
can be updated to reference the BookSerializer
and
then return the 'books'
field. This should match the name on the Book
model
for the related_name
.
class AuthorSerializer(serializers.ModelSerializer):
books = BookSerializer(many=True)
class Meta:
model = Author
fields = ('first_name', 'last_name', 'books')
Now, when an Author
is returned from the database it will include a reference
to that author's books
.
Time to work on our next app's V2: hospital.
The hospital docs lay out the details for two resources,
Doctor
and Patient
. This repository has some starter code for an app
hospital
. Just like with the library's starter code, right now both
Doctor
and Patient
support successful CRUD on their own.
Let's connect these two resources:
- Add
ForeignKey
field toPatient
(see below section for how to migrate) - Update
DoctorSerializer
to returnpatients
When you add a new ForeignKey
to Patient
and then go to make the migration
files, you'll find that Django will stop you with a prompt something like this:
$ python manage.py makemigrations
You are trying to add a non-nullable field 'primary_care_physician' to patient without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option:
As Django is explaining, the migration files won't generate without being provided some reasonable default for this new field that isn't allowed to be empty. This is because the migration files don't know the state of our database, so even if it's empty we still need to provide some value for any "existing rows" that don't already have this field.
We have two options - give Django a value to use for the existing rows in the database, or go back and modify the field in the models file. We'll choose the first one for right now.
Go ahead and type 1
to select this option and hit enter
to continue.
Once you do this, you'll be moved on to a message like this:
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>>
You might recognize this - it's the Python REPL! The >>>
prompt is telling us we can
provide Python code to be interpreted. In this case, we want to provide a valid ID
of some other row in the database. Assuming our database is empty, the safest value
to provide would be 1
, since that's the first ID that will ever exist.
Go ahead and type 1
to provide this value and hit enter
to continue.
Django should spit you back out to the $
filesystem where you can run your migrations.
Note: Use your judgement when providing defaults. If your database isn't empty, there may be more to consider when picking a good default value.
Time to see how we can add a many-to-many relationship to Book
. Following the
library documentation, Book
should have a field that
references who has or is borrowing that books from the library.
Book
>---< Borrower
Borrower
s can borrow many Book
s, and Book
s can be borrowed by many
Borrower
s!
As before, there is already a Borrower
resource that supports full CRUD.
In the Book
model we will add a new field to reference the Borrower
model.
The borrowers
field will be a ManyToManyField
that will allow Book
to
store multiple borrowers
.
This field might be empty at some point, and that's okay, so the blank=True
option sets borrowers
to be optional.
On the Borrower
resource, the books borrowed will be referenced with the
related_name
which is loans
.
In the BorrowerSerializer
we can reference the BookSerializer
to be able to
see the books a borrower has borrowed.
Sometimes, we want a table to keep track of the connections between resources.
For example, a resource called Loan
could keep track of the ID of the
Borrower
and the Book
in our many-to-many relationship. In relational
databases, this kind of resource/table that "joins" the relationship between
two other resources is known as a join table. This table could also hold
information about the loan, like due date and checkout date.
We often talk about building a relationship that goes "through" this join
table, which is why you'll often see the keyword through
when building out
join tables in different frameworks.
In Django we can accomplish a join table for our Book
>---< Borrower
relationship using the current Loan
model. We will need to modify our models
so that Loan
can be used to represent this relationship.
- Add two
ForeignKey
s toLoan
(join table)- This will keep track of the relationship to the
Book
andBorrower
- Note: We can also give it any other fields we want, like a due date!
- This will keep track of the relationship to the
- Update the
borrower
field onBook
to go "through" theLoan
table- Use
through
to reference theLoan
model - Use
through_fields
to reference the fields onLoan
that should refer to theBorrower
andBook
IDs
- Use
- Add a
LoanReadSerializer
to display the book and borrower objects
Book
-|--< Loan
>--|- Borrower
We should be able to create a Loan
and have that translate over to both
Book
and Borrower
, based on the data in the Loan
we created. We actually
don't end up interacting with the Book
or Borrower
, but instead just CRUD on
Loan
s.
Time to plug in a many-to-many relationship between Doctor
and their
Patient
s. The hospital documentation shows us that we
should have a resource called Appointment
to join Doctor
and Patient
.
We need our Appointment
before we can use it to "join" Doctor
and Patient
.
Use the current files for doctors and patients to help guide you.
- Create an
Appointment
model - Add a new serializer class for
Appointment
s - Add CRUD views for
Appointment
s - Test CRUD on
Appointment
- Connect
Patient
andDoctor
using theAppointment
table