Introduction

Welcome to the first lesson of our course on "Advanced Database Schema Design in Django." In this lesson, we will introduce you to the concept of one-to-one relationships in databases. Understanding how to create and manage these relationships is essential for building robust and efficient applications.

By the end of this lesson, you will know how to:

  1. Create a Note model.
  2. Link the Note model to an existing Todo model using a one-to-one relationship.
  3. Create serializers for both models.
  4. Use fixtures to pre-load data.
  5. Write tests to ensure everything works correctly.

Let's get started!

Recap: Project Setup

Before we dive into creating new models, let's quickly recap our existing setup. In previous lessons, you learned how to create a Todo model. Here is what the model looks like:

This model represents tasks that need to be done, with fields for the task name, completion status, priority, assignee, and group information.

Creating the Note Model

Now, let's create a Note model to represent notes that can be linked to Todo tasks.

Here's how you can create the Note model:

  • content: A TextField to store the note's text content.
  • __str__ method: Returns the first 50 characters of the note's content for easy identification.
Establishing the One-to-One Relationship

Now, imagine that in your app, one note item always corresponds to exactly one todo item. For example, notes can be additional details added to a Todo item card. Why don't we make a note of a simple CharField inside the Todo model? Well, there could be several reasons not to do this. The most simple one is that the note could be created by another user, and the creator of a card shouldn't have access to edit or remove a note, while the note's creator shouldn't have access to modify todo's fields. In this case, to control the access rights, it would be the best option to create a separate Note model and link it to the Todo model.

To link a Note model with a Todo model using a one-to-one relationship, we will add a OneToOneField to the Todo model.

Here is the updated Todo model:

  • note is a OneToOneField that links each Todo instance to a Note instance.
  • on_delete=models.SET_NULL: If the Note instance is deleted, set the note field in the Todo model to None.
  • null=True, blank=True: Allows the note field to be optional.
Other on_delete Options

Django provides several other options for the on_delete argument, which defines what happens when the referenced object (in this case, Note) is deleted. Here are some alternatives:

  • CASCADE: Deletes the Todo instance when the associated Note is deleted.

  • PROTECT: Prevents deletion of the Note if it is referenced by a Todo instance.

  • SET_DEFAULT: Sets the note field to its default value when the Note instance is deleted.

  • SET: Sets the note field to a specified value or function when the Note instance is deleted. Here's a concrete example of using a function:

    In this example, when a Note is deleted, the get_placeholder_note function is called to create and assign a new placeholder Note to the Todo.

  • DO_NOTHING: Does nothing on deletion and lets you handle the deletion logic manually.

Each of these options serves different use cases, and the choice depends on your application's requirements.

Testing on_delete Options

To ensure the on_delete behavior is working as expected, we can write tests using Django's TestCase. For instance, to test the PROTECT option, you can use assertRaises to check that attempting to delete a Note linked with a Todo raises a ProtectedError.

Here's an example test:

In this example:

  • test_protected_note_deletion: A test method to verify the PROTECT option.
  • note = Note.objects.get(pk=1): Retrieves a Note instance with primary key 1.
  • with self.assertRaises(models.ProtectedError): Checks that attempting to execute the code inside this block (which deletes the Note instance) raises a ProtectedError.
Creating Views to Handle Creation of Note Instances

To handle the creation of Note instances via an API, we'll create views in Django using Django Rest Framework.

Here's how to do it:

  • Create a view for the Note model.
  • Include the NoteViewSet in your urls.py.

DefaultRouter is a class provided by Django Rest Framework. It automatically generates URL patterns for the registered viewsets. When you register a viewset with DefaultRouter, it creates a set of URLs that handle standard CRUD operations (create, retrieve, update, and delete) on the registered model.

The r before string patterns like r'notes' denotes a raw string literal in Python. Raw strings treat backslashes as literal characters and do not interpret them as escape characters. This is especially useful in situations where backslashes are common, such as in regular expressions or Windows file paths.

Loading Initial Data with Fixtures

Fixtures allow us to pre-load data into our database. Here’s how to create a JSON fixture file to load initial data for the Note and Todo models.

Create a file named one_to_one.json in the fixtures directory with the following content:

  • model: Specifies the model to which the data belongs (myapp.note or myapp.todo).
  • pk: Primary key for the model instance.
  • fields: Defines the data fields and their values. Note that we represent a todo's note with its primary key. Django will automatically find the todo's note by the provided id and fetch its information.
Testing the One-to-One Relationship

Writing tests ensures our code works as expected. Here are example test cases to validate the one-to-one relationship between the Todo and Note models.

  • class OneToOneRelationshipTestCase(TestCase): Defines a test case for testing our models.
  • fixtures = ['one_to_one.json']: Specifies the JSON fixture file to pre-load initial data.

The first test method checks if the Todo instance retrieved via get(pk=1) has an associated Note. It then asserts that the note field of the Todo is not None and validates the note's content.

The second test method simulates creating a Note instance via an API call, followed by a Todo instance that associates with the newly created Note. First, it constructs the note_data dictionary and sends a POST request to '/api/notes/'. It then retrieves the note_id from the response. Next, it embeds the note_id into the todo_data dictionary and sends another POST request to '/api/todos/' with the todo_data. The test verifies that the response status code is 201 (Created) and also checks that the counts of Todo and Note instances in the database have increased accordingly.

Summary and Preparing for Practice

In this lesson, we covered:

  1. Creating a Note model.
  2. Establishing a one-to-one relationship between Todo and Note.
  3. Creating views to handle creation of Note instances.
  4. Serializing the models.
  5. Using fixtures to load initial data.
  6. Writing test cases to validate the relationship.

Take your time to review the code examples and understand how each part contributes to the overall functionality. Now, it's your turn to practice and reinforce what you've learned. Good luck, and happy coding!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal