If you run into a problem when you’re coding that already has a documented solution, don’t reinvent the wheel. It’s better and faster to take advantage of a known solution than to spend time creating a new one. There are many open-source projects that perform common tasks to solve general problems. You can use them for free on your apps, and you can also make contributions and propose improvements!
Django is a solid framework for developing web apps with Python language. It’s fast, secure, scalable, and well documented. If you want to build REST APIs, you can combine Django with the Django REST framework to generate a base project in just a few seconds.
Understand the main concepts of Django and Django REST
To work with Django and Django REST, it’s important to grasp these concepts:
- Model classes provide an object-relational map (ORM) for the underlying database. A model is mapped to a table in the database. You can query the databases without any SQL programming. With models, it’s easy to define tables and relationships between them.
- Views are in charge of the process of requests. They function as controllers. You can implement them in many ways, such as functions or classes.
- Serializer classes provide control of data types and structures of requests and responses. I like to define them as interfaces for the backend of the web app.
- Templates are files with static and dynamic content. They’re made up of some static code and other elements that depend on the context.
There are many ways to implement the functionalities of a REST API by using the Django frameworks. There are also many ways to implement views and serializers. For general design and any bugs you might run into, you could consider the generic tested and maintained solutions, such as Generic views.
The Django REST framework has generic views that perform common tasks related to the model instances. Some examples of retrieving information, creating, destroying, listing, and updating. With Django’s generic views, you only need to define the class view as a child of the generic view, depending on your needs. The rest is solved by the framework.
The many generic views in the Django documentation meet a wide range of functionality needs. Some examples are CreateAPIView, ListAPIView, RetrieveAPIView, and RetrieveDestroyAPIView. But what if you want to customize the functions and change the default behavior? You can redefine get, post, put, delete, and other methods. You can also use the mixins to redefine the methods and specific functions like create, update, destroy, and others. The generic views and serializers aren’t required, but they can simplify a lot of work. Serializers also cover some issues related to requests and responses, so you don’t have to worry about them.
The next good practice is how to locate the validation of incoming data. For example, you might have values for the fields of serializers in your web app. You want to run some checks on them when a new request comes into the REST API. In the PersonDetailSerializer, you can do a field-level validation that just defines a function named validate_<field_name>.
Sometimes you need to define a more complex validation inside the serializer class. An example is validating constraints between fields. A good way to do that is with validating function: validate(self, data).
In summary, serializers are a good way to validate incoming data. In the previous example, PersonDetailSerializer is attached to the Person model. So by default, it does basic validation related to the model. For example, if the incoming name length is bigger than 50 characters, then the serializer raises an exception, because the maximum length was defined in the model. You don’t need to make that explicit in the serializer. With Django, you can also define serializers that aren’t attached to models, nest serializers and relationships between them, and more. Serializers are very powerful.
Finally, let’s talk about templates. They’re great for generating dynamic code in Django. For example, template files can be used by the project frontend or to send emails.
It’s important to take order into account because it’s common for several apps to provide different versions of the same resource as a template or static file. If that happens, Django’s default behavior is to first check in the templates folder of the first app in the list. But that can cause problems. Order matters. By knowing this, you can change the order of the INSTALLED_APPS according to our needs. Or you can just explicitly define the desired paths for specific resources. For a configuration example, read the Django docs.
Most web applications have users and authentication methods. In the spirit of don’t reinvent the wheel, you can use an existing customizable solution for session management: Django-rest-auth. You can easily integrate this set of REST API endpoints with your project. It resolves user registration, sign in, and sign out, as well as social account integration. Just use the provided endpoints and customize the models and functions to adapt them to your needs.
Create efficient programs
No matter what technology you use, it’s always a good practice to create efficient programs.
Django has a database-abstraction API to make queries easily. But, to save time and resources, write code that minimizes the number of accesses to the database. The Django API has lots of functions that do the exact same thing in lots of different ways. Choose the ones that minimize the number of queries because it’s always faster to work in memory. Learn to write efficient code by exploring Django’s documentation about attributes and functions.
Appreciate the importance of testing
Testing is a good practice, even more than that. Testing is essential for every project and every application needs testing.
In most cases, you’ll need to generate initial data for testing. So a programmer might run tests that have an “initial system” with previously loaded instances and relationships between them. This improves the quality, maintainability, and velocity of testing.
You can load initial data with Django, using fixtures, migrations, and other functionalities.
With Factory boy, you can generate test data with a just few lines, which improves maintainability. You can also define classes attached to the model classes. So at the same time that you run tests, you can easily create instances in memory or in the database and also replace any static hard-to-maintain fixtures.
The Faker tool is a package that generates fake random and realistic data. When you integrate it with Factory boy, it generates more significant tests with more realistic initial data.
Define good initial data for testing and keep it maintainable. For more ways to integrate these tools with Django, take a look at the common recipes in the Factory boy documentation.
Avoid excessive use of signals
Django and Django REST have many tools and methods to attack several problems in a lot of ways. Signals are a good tool, but they can cause problems if they’re used the wrong way.
Signals implement the observer design pattern, and it’s important to understand these concepts:
- A Signal is an element that corresponds to an event. A signal can send notifications about the related event to it. In the observer pattern, the signal corresponds to the subject.
- Receivers are the callables connected to a given signal. In the observer pattern, they correspond to the observer. After the associated event occurs and triggers the signal, each receiver is notified by that signal.
Overuse of signals often generates mysterious side effects that degrade maintainability. These problems happen because signals are triggered after selected events. Programmers who forget that or simply don’t know it sometimes run unwanted code in the wrong places.
Whenever you change something about the events that trigger a signal, you might need to make changes in the corresponding signals. It’s easy to forget to check that because these signals are defined in other files. To save time and effort, avoid the overuse of signals. For example, you might have two apps A and B, where A needs to trigger a function in B. If app A already knows app B, then don’t use signals. In this case, app A will import the needed functions from app B and make the calls directly.
For more information, see the Django REST authentication docs.