Theory
SOLID
The SOLID principles are a set of five basic principles of object-oriented programming
Single Responsibility Principle (SRP):
This principle states that each class should be responsible for only one task or function. If a class does too much, it becomes difficult to maintain and change. Therefore, it is important to divide functionality into smaller, logically connected parts.
Open/Closed Principle (OCP):
According to this principle, software entities such as classes, modules, and functions should be open for extension but closed for modification. This means that changing the behavior of an entity should be possible by adding new code rather than changing existing code.
Barbara Liskov Substitution Principle (LSP):
This principle states that objects of base classes should be replaceable by any objects of their derived classes without affecting the correctness of the program. In other words, derived classes must conform to the interface of base classes.
Interface Segregation Principle (ISP):
This principle states that clients should not depend on interfaces that they do not use. Instead, interfaces should be broken down into smaller, more specific ones so that clients can only use the parts of the interface that they actually need.
Dependency Inversion Principle (DIP):
According to this principle, top-level modules should not depend on lower-level modules. Both must depend on abstractions. It also states that abstractions should not depend on details, but details should depend on abstractions.
Dependency Injection
Dependency Injection (DI) is a design pattern that is used to manage dependencies between components in software. It involves passing objects (dependencies) instead of creating them inside a class or function. This makes the code more flexible, modular, and reduces coupling between components.
Basics of Dependency Injection:
- Inversion of Control:
When using Dependency Injection, control over the creation and management of dependencies is transferred from the class or function to an external source, such as a dependency container. This makes it easy to replace or change dependencies without changing the code. - Dependency Creation and Exploitation Department:
Dependency Injection separates the process of creating objects from their use. This means that objects that depend on other objects obtain them from outside, instead of creating them themselves. DI implementation examples:
- Constructor injection:
Dependencies are passed to the object's constructor.
class MyClass:
def __init__(self, dependency):
self.dependency = dependency
- Methodical injection:
Dependencies are passed through object methods.
class MyClass:
def set_dependency(self, dependency):
self.dependency = dependency
- Injection through function arguments:
Dependencies are passed to functions as arguments.
def my_function(dependency):
# используем зависимость
pass
Example in python
class Engine:
def start(self):
print("Engine started")
class Car:
def __init__(self, engine):
self.engine = engine
def start(self):
self.engine.start()
# Создаем объекты зависимостей
engine = Engine()
car = Car(engine)
# Запускаем автомобиль
car.start()
Example in fastAPI
from fastapi import Depends, FastAPI
app = FastAPI()
async def get_db_connection():
# Логика для получения подключения к базе данных
return "FakeDBConnection"
@app.get("/")
async def read_root(db: str = Depends(get_db_connection)):
return {"message": f"Database connection: {db}"}
Comparison of Django and FastAPI
- Application type:
- Django: A full-featured framework focused on creating full-scale web applications. It includes many built-in tools for database management, authentication, administrative interface, etc.
- FastAPI: A framework for creating APIs based on ASGI, type annotations and OpenAPI. It supports both synchronous and asynchronous handlers.
- Synchronicity and asynchrony:
- Django: Supports ASGI, asynchronous views, middleware and part of the asynchronous ORM API. Some components and third-party libraries remain synchronous; when calling them from async code, the correct adapter is required.
- FastAPI: Allows handlers to be declared via
defandasync def. Asynchrony is useful primarily when there are a large number of concurrent I/O operations and does not in itself speed up CPU-bound code.
- Development speed:
- Django: Has high development speed thanks to many built-in tools and development conventions.
- FastAPI: Provides a simple and intuitive interface for creating web APIs. With automatic documentation generation and built-in data validation using Pydantic, development speed can also be high.
- Productivity:
- Django and FastAPI can serve production load. Actual performance depends on the database, external services, concurrency model, application code, and infrastructure; The choice of framework cannot be reduced to synchronicity or microbenchmark results.
- General Use:
- Django: Widely used to create web applications of all kinds, including blogs, e-commerce, CRM and many more.
- FastAPI: Often used to create microservices and web APIs, especially in areas where speed and efficiency are important, such as machine learning and large data processing.
TOP-4 questions at Django interviews
You need to know questions from this category by heart and be able to answer them at any time of the day or night.
select_related, prefetch_related, select_for_update
select_related() and prefetch_related() are database query optimization techniques in Django that allow you to preload related objects to reduce the number of database queries. Here are examples of use select_related() and prefetch_related():
- select_related():
from myapp.models import Author, Book
# Выборка всех книг вместе с информацией об авторах
books = Book.objects.select_related('author').all()
for book in books:
print(book.title, book.author.name)
This code runs a single SQL query to select all the books along with their author information. select_related('author') tells Django to preload associated author objects. 2. prefetch_related():
from myapp.models import Author, Book
# Выборка всех авторов вместе с их книгами
authors = Author.objects.prefetch_related('book_set').all()
for author in authors:
print(author.name)
for book in author.book_set.all():
print(book.title)
This code typically runs two SQL queries, one for the authors and one for the linked books, and then Django concatenates the results in Python. Method prefetch_related('book_set') especially useful for many-to-many and callbacks that cannot be loaded with a regular SQL JOIN via select_related().
3. select_for_update():
select_for_update() performs SELECT ... FOR UPDATE on supported DBMSs and locks selected rows for competing changes until the end of the transaction. The method itself does not make an arbitrary set of operations atomic: it is used internally transaction.atomic(), and the exact semantics depends on the DBMS and query parameters.
from django.db import transaction
from myapp.models import MyModel
# Начинаем транзакцию
with transaction.atomic():
# Выполняем выборку строк с блокировкой для чтения и обновления
queryset = MyModel.objects.select_for_update().filter(id=1)
# Выполняем обновление строк в рамках транзакции
for obj in queryset:
obj.field = 'new_value'
obj.save()
In this example:
- We start the transaction with
transaction.atomic()to ensure atomicity of operations. - We fetch rows from the model
MyModelwith the helpselect_for_update()to lock those rows for reading and later updating. - We then update the field values in the selected rows and save the changes within the transaction. Usage
select_for_updateimportant in situations where you need to ensure that selected rows are not modified by other processes or threads before the current operation completes. This avoids resource races and ensures data consistency.
What is the architecture of Django? Where to write view logic, model, maybe somewhere else?
Django's architecture is MVC-like, but has some features that make it sometimes called MTV (Model-Template-View).
Models:
Django models are Python classes that map to tables in a database. They define your application's data structure and enable interaction with the database.
Views:
Django views are responsible for processing requests and returning responses. They receive data from models, process it, and pass the results to templates or return them as HTTP responses.
Templates:
Django templates are used to generate dynamic HTML pages. They contain HTML code with built-in directives and templating variables that are populated with data from the views.
What components does a request go through in Django?

What disadvantages do you see in Django, where would you use it and where not?
Heaviness:
Due to the large number of built-in functions and components, Django can seem overly heavy for small projects or prototypes. Some developers prefer more lightweight frameworks for such tasks.
Difficulty of learning:
Django has a fairly steep learning curve, especially for those new to web development. It provides many features and components that can be challenging to master, especially if you're just starting out.
Solidity:
Django assumes a specific project structure and development methodology, which can limit the flexibility and extensibility of your application, especially if you need to integrate it with existing systems or use other development approaches.
Where to use Django:
- Web applications with database: Django is great for creating web applications that require database work, such as blogs, online stores, CRM systems, etc. Its ORM and database administration tools make working with data much easier.
- Quick start projects: If you need to quickly deploy a web application with minimal time and effort, Django provides pre-built components and templates that let you start developing quickly.
- Large projects with a development team: Django is well suited for large projects with many developers. It provides good code organization and separation of responsibilities, making it easier to collaborate on a project.
Where you shouldn't use Django:
- Microservices and event-driven systems architecture: For individual small services, it may be more convenient to choose a more minimalistic framework, such as FastAPI. You can also build an API or event system in Django; The choice depends on the requirements, the team and the infrastructure already in use.
- Projects requiring high performance and scalability: For projects that require high performance and scalability, especially if they involve a large volume of queries or real-time operations, frameworks specialized in fast and efficient query processing, such as FastAPI, Node.js or Go, may be more suitable alternatives.
- Bare API services: Services that do not require Django's built-in features: no admin panels or other extras, just API endpoints and an ORM for database access
Database and ORM
F, Q objects, Annotate.
- F-object:
The F object allows you to access model field values and use them in expressions for filtering, updating, and annotation without having to retrieve them from the database. It represents a reference to the value of a model field and can be used to compare, add, and subtract with other fields or constants.
from myapp.models import MyModel
from django.db.models import F
# Увеличение значения поля на 1
MyModel.objects.filter(id=1).update(count=F('count') + 1)
# Сравнение значений двух полей
MyModel.objects.filter(count__gt=F('limit'))
- Q-object:
The Q object allows you to build complex queries using the logical operators AND, OR and NOT. It is useful when you need to create a query with multiple conditions that can be combined according to different rules.
from myapp.models import MyModel
from django.db.models import Q
# Запрос с условием ИЛИ
MyModel.objects.filter(Q(name='John') | Q(name='Alice'))
# Запрос с условием И и ИЛИ
MyModel.objects.filter(Q(name='John') & ~Q(age=30))
- objects manager:
The objects manager provides convenience methods for performing various operations on data models, such as creating, updating, deleting, and retrieving. This is the basic manager that is used by default in Django models.
from myapp.models import MyModel
# Создание нового объекта
MyModel.objects.create(name='John', age=30)
# Обновление объекта
obj = MyModel.objects.get(id=1)
obj.name = 'Alice'
obj.save()
# Удаление объекта
MyModel.objects.filter(name='John').delete()
# Выборка объектов
MyModel.objects.all()
- annotate:
The annotate method allows you to add calculated aggregate values to each object in the query. This is useful when you need to aggregate across field values and add the results to each query result object. Then howaggregate()returns one aggregation value for the entire dataset.
from myapp.models import MyModel
from django.db.models import Count
# Подсчет количества объектов для каждого значения поля
queryset = MyModel.objects.annotate(num_items=Count('name'))
# Получение агрегированных значений для каждого объекта
for obj in queryset:
print(obj.name, obj.num_items)
How are queryset and lifecycle executed?
In Django
QuerySetis an object that provides an interface for querying and retrieving data from a database. Cycle of lifeQuerySetincludes several stages:
- Creating a QuerySet:
QuerySetcreated when calling model object manager methods, such asall(),filter(),exclude()and others. - Formation of a request:
When creatingQuerySetDjango builds an SQL query based on the methods called and filters applied toQuerySet. At this stage, conditions are formed, fields are selected, the sorting order and other query parameters are determined. - Executing the request:
QuerySetLAZILY is used, for example, in a loop or when calling the methodlist(), Django sends an SQL query to the database to retrieve data. Query results are returned as model objects or field values. - Data extraction:
Once a query is executed, Django retrieves data from the database and transforms it into model objects or other data structures, depending on the query. - Data Usage:
The resulting data can be used in the application, for example to be displayed on a web page, processed in a view, or passed to a template for display. - Reusing the result:
After calculationQuerySetDjango usually stores the results in this object's internal cache. Specially callclose()no need butdelete()deletes rows from the database and is not related to resource release. It's important to understand lazy evaluation and cachingQuerySet, so as not to create unnecessary queries and not to keep large results in memory longer than necessary.
bulk_create() and bulk_update() - how they work and what happens at the SQL level
- bulk_create(): Method
bulk_create()allows you to create multiple model objects at the same time, which can be much faster than creating each object separately using thecreate(). Usage examplebulk_create():
# Django массово вставит объекты одним запросом или несколькими batches — зависит от backend и batch_size.
from myapp.models import MyModel
# Создание списка объектов модели MyModel
objects_to_create = [
MyModel(name='Object 1'),
MyModel(name='Object 2'),
MyModel(name='Object 3'),
]
# Массовое создание объектов
MyModel.objects.bulk_create(objects_to_create)
- bulk_update(): Method
bulk_update()allows you to update multiple model objects at the same time, which can be useful when you want to update many objects at once, for example when processing data in bulk. Usage examplebulk_update():
# Django массово обновит объекты одним запросом или несколькими batches — зависит от backend и batch_size.
from myapp.models import MyModel
# Получение списка объектов для обновления
objects_to_update = MyModel.objects.filter(some_filtering_condition)
# Массовое обновление полей в объектах
for obj in objects_to_update:
obj.some_field = 'new_value'
MyModel.objects.bulk_update(objects_to_update, ['some_field'])
bulk_create()andbulk_update()can significantly reduce the number of requests, but do not causesave()models and do not send signalspre_save/post_save. They also have limitations for multi-table inheritance and many-to-many relationships - check the documentation for your version of Django before using them.
How to implement having or distinct in ORM?
In Django ORM you can implement functionality similar to
HAVINGin SQL, using methodsannotate()andfilter(), and alsodistinct()for functionalityDISTINCT.
- HAVING: In SQL queries
HAVINGused to filter grouped data. In Django ORM, similar functionality can be obtained usingannotate()andfilter(). For example, to filter groups that have more than a certain number of objects:
# Этот запрос вернет группы объектов модели MyModel, у которых количество объектов больше 5.
from django.db.models import Count
# Фильтрация групп, у которых количество объектов больше 5
result = MyModel.objects.values('group').annotate(count=Count('id')).filter(count__gt=5)
- DISTINCT: In SQL
DISTINCTused to select unique values from a column. In Django ORM, similar functionality can be obtained using the methoddistinct().
# Этот запрос вернет уникальные значения поля field из модели MyModel.
# Выбор уникальных значений поля field
unique_values = MyModel.objects.values_list('field', flat=True).distinct()
Have you done django migrations or written your own?
Django Migrations is a mechanism that allows you to automatically create and apply changes to the database when the structure of Django models changes.
# models.py
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
# Выполнение миграции в терминале:
# python manage.py makemigrations >>
# python manage.py migrate
This code defines the model MyModel with two fields: name and age. After executing the commands makemigrations and migrate, these changes will be applied to the database and the table myapp_mymodel will be created accordingly.
Locks, transaction.atomic() - what did you use? When does it occur?
Locks and transaction.atomic() are tools used in Django to manage database transactions.
- Locks:
Database locks are used to control access to database resources to prevent conflicts and maintain data integrity. In Django, you can use locks to protect critical sections of code from concurrent writes. - transaction.atomic():
transaction.atomic()is a decorator or context manager that ensures the atomicity of execution of a block of code within a database transaction. Needed for: - consistency of data in the database
- several operations must be performed atomically
- if simultaneous access to database resources could lead to undesirable consequences
Architecture and Design Patterns
What is the architecture of Django?
Django's architecture is MVC-like, but has some features that make it sometimes called MTV (Model-Template-View). Here are the main components of the Django architecture:
- Models:
- Views:
- Templates:
- Routing (URL Dispatcher):
- ORM (Object-Relational Mapping):
- Forms:
- Administrative panel (Admin):
Did you use signals?
Django Signals is a mechanism that allows you to send signals when certain events occur in your Django application. Django Signals concepts:
- Creating a Signal:
To create a signal in Django you can usedjango.dispatch.Signal. This is usually done in a filesignals.pyyour application.
from django.dispatch import Signal
# Определение сигнала
my_signal = Signal()
- Subscribe to signals:
Once the signal is created, you can subscribe to it using a decoratorreceiveror methodconnect. This is usually done in a fileapps.pyyour application or in a filesignals.py.
from django.dispatch import receiver
from myapp.signals import my_signal
# Подписка на сигнал
@receiver(my_signal)
def my_signal_handler(sender, **kwargs):
# Обработка сигнала
pass
- Sending a signal:
To send a signal you call the methodsend()for a signal object, passing the sender and optional arguments.
from myapp.signals import my_signal
# Отправка сигнала
my_signal.send(sender=self, arg1=value1, arg2=value2)
- Signal Processing:
When a signal is sent, all registered handlers are fired. Handlers receive the sender of the signal and any additional arguments passed when the signal was sent. - Standard Django signals:
Django also provides a number of built-in signals that can be used to handle various events in your application, such as object creation, object update, object deletion, and others.
What is model inheritance in Django?
Django has three main options for model inheritance: abstract base classes, multi-table inheritance, and proxy models.
- Abstract inheritance:
When using abstract inheritance, the base model class does not create a separate table in the database. Instead, its fields are simply copied into the table of each child model. Child models can add additional fields or override existing ones.
from django.db import models
class BaseModel(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class ChildModel(BaseModel):
description = models.TextField()
When using abstract field inheritance name and created_at will be copied to the table ChildModel.
2. Multi-table inheritance:
A separate table is created for each specific model. Django associates child row with parent auto-generated association OneToOneField with parent_link=True.
from django.db import models
class ParentModel(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class ChildModel(ParentModel):
description = models.TextField()
In this case, Django will create two tables and automatically add a link to the parent in the child model. The default column name is associated with the parent model and does not have to be simply id.
- Proxy model:
The proxy model uses the same table as the original model, but can change Python behavior: methods, manager, and default sorting. A new table and new fields are not created.
Official sources
Django: asynchronous support
Django QuerySet API
Django: model inheritance
FastAPI: concurrency and async / await
Have you worked with DRF, what problems do you see in it?
What do you think about async Django?
Take a look at the popular things in Django and name what programming patterns were used? Type settings, queryset, django-admin models, signals