Skip to content

Instantly share code, notes, and snippets.

@1UC1F3R616
Last active April 2, 2024 10:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1UC1F3R616/949a27ea56f28b880f97e46c40fc557c to your computer and use it in GitHub Desktop.
Save 1UC1F3R616/949a27ea56f28b880f97e46c40fc557c to your computer and use it in GitHub Desktop.
Yet another cheat sheet
from django.db.models import Model, DateTimeField, CharField

class BlogPost(Model):
    title = CharField(max_length=100)
    content = CharField(max_length=1000)
    created_at = DateTimeField(auto_now_add=True)

class Comment(Model):
    comment_text = CharField(max_length=500)
    created_at = DateTimeField(auto_now_add=True)

Display the latest activities, including both blog posts and comments, sorted by their creation date.

from itertools import chain

# Fetch the latest 5 blog posts and comments
recent_blog_posts = BlogPost.objects.order_by('-created_at')[:5]
recent_comments = Comment.objects.order_by('-created_at')[:5]

# Combine the querysets
combined_queryset = list(chain(recent_blog_posts, recent_comments))

# Sort the combined list by the 'created_at' attribute
combined_sorted_list = sorted(combined_queryset, key=lambda instance: instance.created_at, reverse=True)

Explain How A Request Is Processed In Django?

1. The Request Hits the Web Server
The journey begins when a user's browser sends a request to the web server where your Django application is hosted. This could be for any type of request (GET, POST, etc.) directed at a specific URL.

2. The Web Server Forwards the Request to Django
The web server (e.g., Nginx, Apache) passes the request to a WSGI (Web Server Gateway Interface) application. Django comes with its own WSGI application (wsgi.py file in your project directory) that acts as the entry point for the application, interfacing with the web server.

3. URL Routing
Django uses a URL dispatcher to determine which view should handle the request. The URL dispatcher looks at the requested URL and matches it against the patterns defined in the urls.py files throughout your Django project. If a match is found, Django forwards the request to the associated view function or class.

4. Middlewares
Before the request reaches the view, it passes through a sequence of middleware layers. Middlewares are hooks into Django's request/response processing. They're a framework of hooks into Django's request/response processing. It's a lightweight, low-level plugin system for globally altering Django’s input or output. Each middleware component can process a request when it comes in, process the response before it’s sent out, or both. Middlewares can be used for tasks like request logging, user authentication, and cross-site request forgery protection.

5. The View
Once the request passes through all the appropriate middleware layers, it arrives at the view. The view is a Python function or class that takes a web request and returns a web response. Views access the data needed to satisfy the request via models and delegate formatting the response to templates.

Serializer: DeSerialize

6. Models and Database Interaction
If the view needs to interact with the database (e.g., retrieving objects, saving new data), it will use Django’s ORM (Object-Relational Mapping) system. The ORM translates Python classes (models) into database queries and vice versa. This means you can work with high-level Python objects instead of writing raw SQL.

Serializer: Serialize - python model to json

7. Templates and Rendering
For views intended to return HTML, Django uses a templating system. A template contains the static parts of the desired HTML output as well as some special syntax describing how dynamic content will be inserted. The view renders the template, meaning it replaces the template tags with the appropriate content to produce a final HTML response.

8. The Response
The view returns an HttpResponse object containing the content for the request, along with details like the MIME type and HTTP status code. This response object is then passed back through the middleware layers, which can modify the response on the way out.

9. Back Through Middlewares
As the response travels back through the middleware stack, each middleware has another chance to modify the response before it is finally sent back to the user’s browser.

10. Response Returns to the User
The WSGI server sends the response back through the web server, which then delivers it to the user’s browser. The browser renders the HTML content, and the request cycle is complete.

select_related and prefetch_related

select_related is used for forward ForeignKey and OneToOne relationships. It works by creating a SQL join and including the fields of the related object in the SELECT statement of the query. This means you get all the data you need in a single database query, which is very efficient.

For example, if you have a model Book with a ForeignKey to Author, and you want to list books and their authors' names, using select_related will fetch each book along with its associated author in a single query:
books = Book.objects.select_related('author').all()
for book in books:
    print(book.author.name)  # Doesn't hit the database again for each book
Without select_related, each time you access book.author.name, Django would hit the database to fetch the author for each book, resulting in many queries and a significant performance hit.
prefetch_related, on the other hand, is used for ManyToMany and reverse ForeignKey relationships. It works differently from select_related. Django executes a separate query for each relationship to fetch the related objects, and then does the 'joining' in Python. This means more than one query is executed, but it still drastically reduces the total number of queries compared to not using it.

For example, if you have a Book model with a ManyToMany relationship to Author, and you want to list books and all their authors, using prefetch_related will fetch all books and all their authors but in separate queries:
books = Book.objects.prefetch_related('authors').all()
for book in books:
    authors = [author.name for author in book.authors.all()]  # Doesn't hit the database for each book
    print(", ".join(authors))
Without prefetch_related, accessing book.authors.all() would hit the database for each book to get its authors, which can be very inefficient if you have many books.
Use select_related when you're querying for objects that have a single ForeignKey or OneToOne relationship. It reduces the query count to one by using a SQL join, making it highly efficient.
Use prefetch_related for reverse ForeignKey or ManyToMany relationships, where you need to get a set of related objects. It fetches related objects in separate queries and does the joining in Python, which can still significantly reduce the overall number of database queries, especially in cases where you'd otherwise have a large number of repetitive queries hitting the database.

Dynamic Queries: They enable the construction of complex query conditions dynamically, which is harder to do with regular filtering methods.

from django.db.models import Q
from myapp.models import Employee

# Find employees who are in the "IT" department OR have the name "John"
query = Employee.objects.filter(Q(department='IT') | Q(name='John'))

# Employees who are in the "IT" department AND have a salary greater than 50000
query = Employee.objects.filter(Q(department='IT') & Q(salary__gt=50000))

# Employees who are NOT in the "HR" department
query = Employee.objects.filter(~Q(department='HR'))

q_objects = Q()  # Start with an empty Q object

# Dynamically add conditions
if some_condition:
    q_objects |= Q(department='IT')  # OR condition
if another_condition:
    q_objects &= Q(salary__gt=50000)  # AND condition

query = Employee.objects.filter(q_objects)

# Exclude employees who are in the "IT" department OR named "John"
query = Employee.objects.exclude(Q(department='IT') | Q(name='John'))

Signals: decouple applications by sending notifications from one piece of code to another. Signals are especially useful for creating side effects (e.g., automatically creating a user profile when a user account is created).

Django includes a set of built-in signals that you can use, such as pre_save, post_save, pre_delete, and post_delete that are sent by Django's model system.

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from myapp.models import UserProfile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

Alternatively, you could manually register a receiver with post_save.connect(create_user_profile, sender=User).

Custom Signals: Signals can be sent using the .send() or .send_robust() methods.

image

ForeignKey many-to-one relationship multiple instances of the model where the ForeignKey is defined can be associated with a single instance of the model that the ForeignKey points to

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    content = models.TextField()

OneToOneField one-to-one relationship only one instance of the model where the OneToOneField is defined can be associated with a single instance of the model that the OneToOneField points to, and vice versa.

from django.contrib.auth.models import User
from django.db import models

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()

ManyToManyField many-to-many each entity on one side of the relationship can relate to many entities on the other side, and vice versa

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField('Course', related_name='students')

class Course(models.Model):
    title = models.CharField(max_length=100)

Here, ManyToManyField creates a separate "association table" (also known as a "join table" or "intermediate table") in the database to manage the relationship between Student and Course. You don't have to manually define this table; Django handles it for you.

Self-referential Relationships

A model can also have a relationship with itself. For example, in a social network app, a User might have many "friends," who are also User instances. This can be implemented as a many-to-many relationship with the same model.

class User(models.Model):
    name = models.CharField(max_length=100)
    friends = models.ManyToManyField('self')

Through Models

For more complex many-to-many relationships where you need to store additional information about the relationship itself, Django allows you to specify a custom "through" model using the through parameter of ManyToManyField.

class Student(models.Model):
    name = models.CharField(max_length=100)

class Course(models.Model):
    title = models.CharField(max_length=100)
    students = models.ManyToManyField(Student, through='Enrollment')

class Enrollment(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    date_enrolled = models.DateField()

GenericForeignKey For features like tagging

Serializer Override

Method Name Purpose Use Case & Example
create(self, validated_data) Customizes how new instances are created. Use this method for custom creation logic.
Example:
python <br>def create(self, validated_data): <br> profile_data = validated_data.pop('profile') <br> user = User.objects.create(**validated_data) <br> Profile.objects.create(user=user, **profile_data) <br> return user
update(self, instance, validated_data) Customizes how existing instances are updated. Apply custom update logic, especially for nested structures.
Example:
python <br>def update(self, instance, validated_data): <br> instance.name = validated_data.get('name', instance.name) <br> instance.save() <br> return instance
to_representation(self, instance) Controls the serialization process. Customize instance conversion to data types for rendering.
Example:
python <br>def to_representation(self, instance): <br> ret = super().to_representation(instance) <br> ret['custom_field'] = "Custom Data" <br> return ret
to_internal_value(self, data) Deals with deserialization to an internal Python representation. Add custom parsing logic for incoming data.
Example:
python <br>def to_internal_value(self, data): <br> data['name'] = data.get('firstname') + " " + data.get('lastname') <br> return super().to_internal_value(data)
validate_<field_name>(self, value) Field-specific validation. Add custom validation for a specific field.
Example:
python <br>def validate_name(self, value): <br> if 'django' not in value.lower(): <br> raise serializers.ValidationError("Name must include 'django'") <br> return value
save(self, **kwargs) Overrides the serializer's save method. Include additional data when saving.
Example:
python <br>def save(self, **kwargs): <br> kwargs['modifier'] = self.context['request'].user <br> super().save(**kwargs)
get_fields(self) Dynamically modify the fields used by the serializer. Modify fields based on context.
Example:
python <br>def get_fields(self): <br> fields = super().get_fields() <br> if self.context['request'].user.is_admin: <br> fields['secret_info'] = serializers.CharField() <br> return fields
get_validators(self) Customize the set of validators applied to the serializer. Dynamically adjust validators.
Example:
Not typically overridden in practice; used more for framework extensions.
is_valid(self, raise_exception=False) Override the validation behavior. Custom validation process.
Example:
python <br>def is_valid(self, raise_exception=False): <br> # Custom validation logic <br> return super().is_valid(raise_exception=raise_exception)

Model Override

Method Name Purpose Use Case & Example
save(self, *args, **kwargs) Customizes the model instance saving process. Use for custom save logic, like auto-generating fields or modifying related objects.
Example:
python <br>def save(self, *args, **kwargs): <br> self.full_name = f"{self.first_name} {self.last_name}" <br> super().save(*args, **kwargs)
delete(self, *args, **kwargs) Customizes the model instance deletion process. Use to perform actions before or after an instance is deleted, such as cleaning up related objects.
Example:
python <br>def delete(self, *args, **kwargs): <br> related_objects.delete() <br> super().delete(*args, **kwargs)
clean(self) Provides an instance-specific validation method. Use for additional validation that requires access to model instance fields.
Example:
python <br>def clean(self): <br> if self.start_date > self.end_date: <br> raise ValidationError("End date must be after start date.")
__str__(self) Determines the default human-readable representation of the object. Use to define a friendly representation of the model instance, which is displayed in the Django admin and shell.
Example:
python <br>def __str__(self): <br> return self.name
get_absolute_url(self) Returns the URL to access a particular instance of the model. Useful for redirecting users to the detail view of an instance after they create or update it.
Example:
python <br>from django.urls import reverse <br>def get_absolute_url(self): <br> return reverse('model_detail', kwargs={'pk': self.pk})
get_field_display(self, field_name) Retrieves the human-readable value for a field with choices. While not an overridable method, it's a built-in Model method often used to get the display value of choice fields in templates and views.
Example usage in a template:
{{ object.get_field_display('field_name') }}

Views Override

Method Name Applicable To Purpose Use Case & Example
get_queryset(self) Views, ViewSets Customize the queryset used for list, detail views, etc. Dynamically select a queryset based on the user or request parameters.
Example:
python <br>def get_queryset(self): <br> user = self.request.user <br> return MyModel.objects.filter(owner=user)
get_serializer_class(self) Views, ViewSets Determine the serializer class to use for the current request. Use a different serializer for read and write operations.
Example:
python <br>def get_serializer_class(self): <br> if self.action == 'list': <br> return MyModelListSerializer <br> return MyModelDetailSerializer
perform_create(self, serializer) ViewSets Customize instance creation. Modify the instance save process, e.g., add additional fields not provided in the request.
Example:
python <br>def perform_create(self, serializer): <br> serializer.save(user=self.request.user)
perform_update(self, serializer) ViewSets Customize instance update. Similar to perform_create, but for updates.
Example:
python <br>def perform_update(self, serializer): <br> instance = serializer.save() <br> instance.modified_by = self.request.user <br> instance.save()
perform_destroy(self, instance) ViewSets Customize instance deletion. Perform actions before an instance is deleted, e.g., soft delete or logging.
Example:
python <br>def perform_destroy(self, instance): <br> instance.is_active = False <br> instance.save()
create(self, request, *args, **kwargs) Generic Views, ViewSets Customize the creation process for a new instance. Override default create behavior, e.g., to add extra response data or handle exceptions.
Example:
python <br>def create(self, request, *args, **kwargs): <br> # Custom creation logic here <br> return Response({ 'data': 'Custom response' }, status=status.HTTP_201_CREATED)
update(self, request, *args, **kwargs) Generic Views, ViewSets Customize the update process for an instance. Override default update behavior, similar to create.
Example:
python <br>def update(self, request, *args, **kwargs): <br> # Custom update logic here
destroy(self, request, *args, **kwargs) ViewSets Customize the deletion process for an instance. Override default delete behavior, similar to create and update.
Example:
python <br>def destroy(self, request, *args, **kwargs): <br> # Custom deletion logic here
list(self, request, *args, **kwargs) Generic Views, ViewSets Customize the behavior of the list action. Customizing response of a list view, e.g., adding extra context or modifying the response structure.
Example:
python <br>def list(self, request, *args, **kwargs): <br> queryset = self.filter_queryset(self.get_queryset()) <br> page = self.paginate_queryset(queryset) <br> if page is not None: <br> serializer = self.get_serializer(page, many=True) <br> return self.get_paginated_response(serializer.data) <br> serializer = self.get_serializer(queryset, many=True) <br> return Response(serializer.data)

Functional Views

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

@api_view(['GET', 'POST', 'PUT', 'DELETE'])
def item_crud(request, pk=None):
    if request.method == 'GET':
        if pk:
            # Fetch a single item logic here
            return Response({"message": "Single item fetched."})
        # Fetch all items logic here
        return Response({"message": "All items fetched."})
    
    elif request.method == 'POST':
        # Create an item logic here
        return Response({"message": "Item created."}, status=status.HTTP_201_CREATED)
    
    elif request.method == 'PUT':
        # Update item logic here
        return Response({"message": "Item updated."})
    
    elif request.method == 'DELETE':
        # Delete item logic here
        return Response({"message": "Item deleted."}, status=status.HTTP_204_NO_CONTENT)

Class-based Views

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ItemAPIView(APIView):
    def get(self, request, pk=None, format=None):
        if pk:
            # Fetch a single item logic here
            return Response({"message": "Single item fetched."})
        # Fetch all items logic here
        return Response({"message": "All items fetched."})

    def post(self, request, format=None):
        # Create an item logic here
        return Response({"message": "Item created."}, status=status.HTTP_201_CREATED)

    def put(self, request, pk, format=None):
        # Update item logic here
        return Response({"message": "Item updated."})

    def delete(self, request, pk, format=None):
        # Delete item logic here
        return Response({"message": "Item deleted."}, status=status.HTTP_204_NO_CONTENT)

Generic Views

from rest_framework import generics

class ItemListCreateAPIView(generics.ListCreateAPIView):
    # queryset and serializer_class definitions here
    queryset = Item.objects.all()
    serializer_class = ItemSerializer

class ItemRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    # queryset and serializer_class definitions here
    queryset = Item.objects.all()
    serializer_class = ItemSerializer
    
    # you'll need to wire up these views to URLs in your urls.py. With generic views, you explicitly set the URL patterns for each action since they're not automatically generated like they are for viewsets with routers.
    
    '''
    urlpatterns = [
    path('items/', ItemListCreateAPIView.as_view(), name='item-list-create'),
    path('items/<int:pk>/', ItemRetrieveUpdateDestroyAPIView.as_view(), name='item-retrieve-update-destroy'),
    ]
    '''
    '''
    items/ (GET, POST): Lists all items and allows for creating a new item.
    items/<int:pk>/ (GET, PUT, PATCH, DELETE): Retrieves, updates, or deletes a specific item identified by its primary key (pk).
    '''

Mixins with Generic Views

from rest_framework import mixins
from rest_framework.generics import GenericAPIView

class ItemListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView):
    # queryset and serializer_class definitions here

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class ItemRetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView):
    # queryset and serializer_class definitions here

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

View Sets

from rest_framework import viewsets

class ItemViewSet(viewsets.ModelViewSet):
    # queryset and serializer_class definitions here
    queryset = Item.objects.all()
    serializer_class = ItemSerializer
    # ModelViewSet class automatically provides list, create, retrieve, update, partial_update, and destroy actions.
    # you'll need to set up the URL routing. DRF provides a Router class that automatically maps the appropriate URLs to your viewset's methods.
    
    '''
    router = DefaultRouter()
    router.register(r'items', ItemViewSet)

    urlpatterns = [
        path('', include(router.urls)),
    ]
    '''
    '''
    GET /items/: List all items.
    POST /items/: Create a new item.
    GET /items/<pk>/: Retrieve an item by its id (primary key).
    PUT /items/<pk>/: Update an item by its id.
    PATCH /items/<pk>/: Partially update an item by its id.
    DELETE /items/<pk>/: Delete an item by its id.
    '''
@1UC1F3R616
Copy link
Author

Mixins provide Targeted Functionality. Freedom.

Combining Mixins with Base Views

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from .models import Product
from .serializers import ProductSerializer

class ProductListCreateView(APIView, ListModelMixin, CreateModelMixin):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

    def get(self, request, *args, **kwargs):
        # Directly use the ListModelMixin logic for listing products
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        # Here, you could add custom logic before saving
        # For example, custom validation or logging
        print("Custom logic before saving a product")
        # Then, use CreateModelMixin logic for creating a product
        return self.create(request, *args, **kwargs)

    def perform_create(self, serializer):
        # This method is called by CreateModelMixin's create method
        # You can add any custom logic here that needs to execute before saving the object
        # For example, modifying the save method to include extra fields
        serializer.save(extra_field="Value")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment