Tips and Tricks

Python - monthdatescalendar()


Python tip:

To get a list of weeks in a certain month, you can use the monthdatescalendar method.

You need to provide the date and month as arguments.

A list of lists, each containing 7 datetime.date objects is returned.

import calendar

cal = calendar.Calendar()
weeks = cal.monthdatescalendar(2022, 2)

print(weeks)

"""
Results:

[
    [
        datetime.date(2022, 1, 31),
        datetime.date(2022, 2, 1),
        datetime.date(2022, 2, 2),
        datetime.date(2022, 2, 3),
        datetime.date(2022, 2, 4),
        datetime.date(2022, 2, 5),
        datetime.date(2022, 2, 6),
    ],
    [
        datetime.date(2022, 2, 7),
        datetime.date(2022, 2, 8),
        datetime.date(2022, 2, 9),
        datetime.date(2022, 2, 10),
        datetime.date(2022, 2, 11),
        datetime.date(2022, 2, 12),
        datetime.date(2022, 2, 13),
    ],
    [
        datetime.date(2022, 2, 14),
        datetime.date(2022, 2, 15),
        datetime.date(2022, 2, 16),
        datetime.date(2022, 2, 17),
        datetime.date(2022, 2, 18),
        datetime.date(2022, 2, 19),
        datetime.date(2022, 2, 20),
    ],
    [
        datetime.date(2022, 2, 21),
        datetime.date(2022, 2, 22),
        datetime.date(2022, 2, 23),
        datetime.date(2022, 2, 24),
        datetime.date(2022, 2, 25),
        datetime.date(2022, 2, 26),
        datetime.date(2022, 2, 27),
    ],
    [
        datetime.date(2022, 2, 28),
        datetime.date(2022, 3, 1),
        datetime.date(2022, 3, 2),
        datetime.date(2022, 3, 3),
        datetime.date(2022, 3, 4),
        datetime.date(2022, 3, 5),
        datetime.date(2022, 3, 6),
    ],
]
"""

Python - itermonthdays4()


Python tip:

To get complete dates (including a day in a week) for a certain month, you can use the itermonthdays4 method.

Returned days will be tuples, consisting of a year, a month, a day of the month, and a week day number.

import calendar

cal = calendar.Calendar()
days = cal.itermonthdays4(2022, 2)

for day in days:
    print(day)

"""
Results:

(2022, 1, 31, 0)
(2022, 2, 1, 1)
(2022, 2, 2, 2)
(2022, 2, 3, 3)

...

(2022, 3, 4, 4)
(2022, 3, 5, 5)
(2022, 3, 6, 6)
"""

Python - itermonthdays2()


Python tip:

To get a date and a day in a week for a specific month, you can use the itermonthdays2 method.

Returned days will be tuples, consisting of a day of the month number and a week day number.

Day numbers outside this month are zero.

import calendar

cal = calendar.Calendar()
days = cal.itermonthdays2(2022, 7)

for day in days:
    print(day)

"""
Results:

(0, 0)
(0, 1)
(0, 2)
(0, 3)
(1, 4)
(2, 5)
(3, 6)

...

(29, 4)
(30, 5)
(31, 6)
"""

Python - itermonthdates()


Python tip:

You can get an iterator for a certain month by using the itermonthdates method. You need to provide a year and a month as parameters.

The iterator returns all days before the start / after the end of the month that are required to get a full week.

import calendar

cal = calendar.Calendar()

for day in cal.itermonthdates(2022, 7):
    print(day)

"""
Results:

2022-06-27
2022-06-28
2022-06-29
2022-06-30
2022-07-01
2022-07-02

...

2022-07-30
2022-07-31
"""

How do you "clear" only specific Flask session variables?


In Flask, data stored in the session object can be deleted by popping a specific element from the object.

πŸ‘‡

from flask import session

@app.route('/delete_email')
def delete_email():
    # Clear the email stored in the session object
    session.pop('email', default=None)
    return '<h1>Session deleted!</h1>'

For more, review Sessions in Flask.

Accessing Flask Session Variables in Jinja Templates


In Flask, the session object can be read (in the same manner as a dictionary) to retrieve data unique to the session. It's conveniently available in Jinja templates as well.

πŸ‘‡

from flask import render_template_string


@app.route('/get_email')
def get_email():
    return render_template_string("""
        {% if session['email'] %}
            <h1>Welcome {{ session['email'] }}!</h1>
        {% else %}
            <h1>Welcome! Please enter your email <a href="{{ url_for('set_email') }}">here.</a></h1>
        {% endif %}
    """)

For more, review Sessions in Flask.

Working with Sessions in Flask


In Flask, you can store information specific to a user for the duration of a session using the session object.

Saving data for use throughout a session allows the Flask app to keep data persistent over multiple requests.

πŸ‘‡

from flask import request, session


@app.route('/set_email', methods=['GET', 'POST'])
def set_email():
    if request.method == 'POST':
        # Save the form data to the session object
        session['email'] = request.form['email_address']
        return redirect(url_for('get_email'))

    return """
        <form method="post">
            <label for="email">Enter your email address:</label>
            <input type="email" id="email" name="email_address" required />
            <button type="submit">Submit</button
        </form>
        """

For more, review Sessions in Flask.

Client-side Sessions in Flask


Sessions in Flask can be considered "client-side", as sessions are stored client-side in browser cookies.

Pros:

  1. Validating and creating sessions is fast (no data storage)
  2. Easy to scale (no need to replicate session data across web servers)

Cons:

  1. Sensitive data cannot be stored in session data, as it’s stored on the web browser
  2. Session data is limited by the size of the cookie (usually 4 KB)
  3. Sessions cannot be immediately revoked by the Flask app

For more, review Sessions in Flask.

How are sessions implemented in Flask?


In order to store data across multiple requests, Flask utilizes cryptographically-signed cookies (stored on the web browser) to store the data for a session. This cookie is sent with each request to the Flask app on the server-side where it’s decoded.

Since session data is stored in cookies that are cryptographically signed (not encrypted!), sessions should NOT be used for storing any sensitive information. You should never include passwords or personal information in session data.

For more, review Sessions in Flask.

Context local objects in Flask with Werkzueg


Werkzueg provides a library for local data storage (context locals) in "werkzeug.local". Context locals expand on thread-local data in Python to work with threads, processes, or coroutines. Each context accesses the data in a context-safe manner.

πŸ‘‡

import random
import threading
import time
from werkzeug.local import LocalStack


# Create a global LocalStack object for storing data about each thread
thread_data_stack = LocalStack()


def long_running_function(thread_index: int):
    """Simulates a long-running function by using time.sleep()."""

    thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})
    print(f'Starting thread #{thread_index}... {thread_data_stack}')

    time.sleep(random.randrange(1, 11))

    print(f'LocalStack contains: {thread_data_stack.top}')
    print(f'Finished thread #{thread_index}!')
    thread_data_stack.pop()


if __name__ == "__main__":
    threads = []

    # Create and start 3 threads that each run long_running_function()
    for index in range(3):
        thread = threading.Thread(target=long_running_function, args=(index,))
        threads.append(thread)
        thread.start()

    # Wait until each thread terminates before the script exits by 'join'ing each thread
    for thread in threads:
        thread.join()

    print('Done!')

Hashing Passwords in Flask with Werkzeug Utils


Werkzueg (a key component of Flask) provides a library for hashing passwords.

πŸ‘‡

from werkzeug.security import generate_password_hash, check_password_hash


class User(database.Model):

    ...

    def is_password_correct(self, password_plaintext: str):
        return check_password_hash(self.password_hashed, password_plaintext)

    def set_password(self, password_plaintext: str):
        self.password_hashed = generate_password_hash(password_plaintext)

What is Werkzeug?


Werkzeug (a key component of Flask) provides a set of utilities for creating a Python application that can talk to a WSGI server (e.g., Gunicorn).

Werkzeug provides the following functionality:

  1. Request processing
  2. Response handling
  3. URL routing
  4. Middleware
  5. HTTP utilities
  6. Exception handling

Example:

from werkzeug.wrappers import Request, Response


class HelloWorldApp(object):
    """Implements a WSGI application."""
    def __init__(self):
        pass

    def dispatch_request(self, request):
        """Dispatches the request."""
        return Response('Hello World!')

    def wsgi_app(self, environ, start_response):
        """WSGI application that processes requests and returns responses."""
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        """The WSGI server calls this method as the WSGI application."""
        return self.wsgi_app(environ, start_response)


def create_app():
    """Application factory function"""
    app = HelloWorldApp()
    return app


if __name__ == '__main__':
    # Run the Werkzeug development server to serve the WSGI application (HelloWorldApp)
    from werkzeug.serving import run_simple
    app = create_app()
    run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)

Template Inheritance in Jinja and Flask


Template inheritance is an amazing feature in Jinja (and Flask)! It allows a base template to define a structure and then child templates to define the details. It's similar to classes in object-oriented design.

πŸ‘‡

<!-- base.html -->
<body>
  <main>
    <!-- child template -->
    {% block content %}
    {% endblock %}
  </main>
</body>


<!-- index.html -->
{% extends "base.html" %}

{% block content %}
<h1>Welcome to the Flask App!</h1>
{% endblock %}

Flask - pass variables to templates


Flask Tip - Jinja Templates

You can pass variables as arguments to render_template() to use those variables in a template file.

πŸ‘‡

# app.py
@app.route('/about')
def about():
    return render_template('about.html', organization='TestDriven.io')


# about.html
<h2>Course developed by {{ organization }}.</h2>

Jinja Templates in Flask


The Jinja templating engine is one of the key building blocks of a Flask application.

With them, you can:

  1. Use static HTML template files to decouple routes from HTML
  2. Separate HTML structure from content
  3. Use programming constructs -- variables, conditionals, and for loops to control shown content -- in your templates

A template file (containing variables and logic) is rendered into an output file (typically HTML).

πŸ‘‡

from flask import render_template

@app.route('/')
def index():
    return render_template('index.html')

Parse URL Parameters in Flask


The request object in Flask stores any parsed URL parameters in request.args.

For example: http://localhost/users/login?next=%2Fprofile

πŸ‘‡

from urllib.parse import urlparse
from flask import request, current_app, abort

@users_blueprint.route('/login')
def login():

    ...

    # Redirect the user to the specified URL after login
    if 'next' in request.args:
        next_url = request.args.get('next')

        # Only accept relative URLs
        if urlparse(next_url).scheme != '' or urlparse(next_url).netloc != '':
            current_app.logger.info(f'Invalid next path in login request: {next_url}')
            return abort(400)

        current_app.logger.info(f'Redirecting after valid login to: {next_url}')
        return '<p>User logged in!</p>'

Be careful to avoid URLs when parsing user inputs: http://localhost/login?next=http://givemeyourcreditcard.com.

Flask File Uploads


The request object in Flask can be used for handling file uploads via request.files.

πŸ‘‡

import os
from flask import request, current_app
from werkzeug.utils import secure_filename

@journal_blueprint.route('/upload_file', methods=['POST'])
def upload_file():
    if 'file' in request.files:
        file = request.files['file']
        filename = secure_filename(file.filename)
        file.save(os.path.join(current_app.config['UPLOAD_FOLDER'], filename))
        return '<p>File uploaded!</p>'

Flask Request Object - check if the request was made from a secure protocol


The Flask request object can be used to check that a request was made using a secure protocol via request.is_secure:

  • HTTPS - HTTP Secure
  • WSS - WebSockets over SSL/TLS

πŸ‘‡

from flask import request, current_app


@journal_blueprint.route('/journal', methods=['GET'])
def get_journal():
    # Only support secure protocols (HTTPS or WSS)
    if request.is_secure:
        current_app.logger.info(f'Journal request using protocol: {request.scheme}')
        return '<p>Journal Entries</p>'

How to get form data in Flask?


In Flask, the request object contains any form data submitted, which can then be processed in a view function.

πŸ‘‡

from flask import request

@journal_blueprint.route('/<int:index>', methods=['PUT'])
def update_journal_entry(index):
    if request.method == 'PUT':
        # Update the journal entry in the database
        ...
        entry.update(request.form['entry_title'],
                     request.form['entry_text'])

Flask Request Object - Sender's IP Address


In Flask, the request object can be used to log the IP address of the sender using request.remote_addr.

πŸ‘‡

@users_blueprint.route('/login')
def login():
    if request.method == 'POST':
        # Log in the user
        current_app.logger.info(
            f'New login request from from IP address: {request.remote_addr}'
        )
        return '<p>User logged in!</p>'

    return '<h2>Login Form</h2>'

Flask Request Object - HTTP method used


In Flask, the request object provides information about the request being processed in the view functions.

For example, the request object provides the HTTP method used.

πŸ‘‡

from flask import request

@users_blueprint.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        # Validate request
        return '<p>New user registered!</p>'

    return '<h2>Registration Form</h2>'

Flask Application Factory Function


The application factory function for a Flask application initializes the Flask application.

The biggest benefit of this approach is being able to create different versions of the Flask application using the same interface (the application factory).

πŸ‘‡

# ----------------------------
# Application Factory Function
# ----------------------------

def create_app():
    # Create the Flask application
    app = Flask(__name__)

    initialize_extensions(app)
    register_blueprints(app)
    configure_logging(app)
    register_app_callbacks(app)
    register_error_pages(app)
    register_cli_commands(app)
    return app

Flask Custom Error Pages


Flask allows custom error pages for specific status codes, which can provide a better user experience by allowing users to easily navigate back through your application

πŸ‘‡

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

Flask Abort


The abort() function in Flask raises an HTTP exception for the given status code. It's helpful for exiting a view function when an error is detected

πŸ‘‡

@journal_api_blueprint.route('/<int:index>', methods=['GET'])
def get_journal_entry(index):

    ...

    # Check that the journal entry is associated with the current user
    if entry.user_id != user.id:
        abort(403)

    return entry

Flask URL Variables


Flask supports URL variables with the route decorator.

Type Description
string (Default) Accepts any text without slashes
int Accepts positive integers
path Similar to string, but also accepts slashes
uuid Accepts UUID strings

πŸ‘‡

@users_blueprint.route('/<int:id>')
def get_user(id):
    return f'<h2>Data for user #{id}</h2>'

Flask Blueprint CLI commands


Flask Tip - Custom CLI Commands

Custom CLI commands in Flask can be added to specific blueprints, not just to the Flask application.

πŸ‘‡

import click
from flask import Blueprint

users_blueprint = Blueprint('users', __name__)

@users_blueprint.cli.command('init_db')
@click.argument('email')
def create(email):
    """Create a new user."""
    ...
    click.echo(f'Added new user (email: {email}!')


# in terminal
$ flask users --help

Commands:
  create  Create a new user.

Flask CLI Commands


Flask Tip - CLI Commands

The flask command is written using Click. Click can be used to create sub-commands for the flask command.

For example, you can create a CLI command for initializing the database:

from click import echo

@app.cli.command('init_db')
def initialize_database():
    """Initialize the SQLite database."""
    database.drop_all()
    database.create_all()
    click.echo('Initialized the SQLite database!')


# in terminal
$ flask init_db

Custom CLI commands are automatically included in the help information:

$ flask --help

Commands:
  init_db  Initialize the SQLite database.
  run      Run a development server.

The Flask-Migrate package is a great example of using custom CLI commands.

Serving Static Files with Flask


Flask Tip - Static Files

Flask automatically creates a static endpoint to serve static files (like HTML templates, CSS stylesheets, JS files, and images).

For example, to serve an image, copy the image into the "static" folder of the Flask project. Create a new route and navigate to http://127.0.0.1:5000/logo.

πŸ‘‡

from flask import current_app

@app.route('/logo')
def flask_logo():
    return current_app.send_static_file('flask-logo.png')

Get a list of all routes defined in a Flask app


Flask Tip - Flask Routes Command

To easily see all the routes defined in a Flask application, use the routes command.

πŸ‘‡

$ flask routes

Endpoint  Methods  Rule
--------  -------  -----------------------
index     GET      /
static    GET      /static/<path:filename>

Adding Automatic Imports to the Flask Shell


Flask Tip:

Additional automatic imports can be added to the Flask shell using shell_context_processor().

πŸ‘‡

# ... After creating the Flask application (`app`) ...
@app.shell_context_processor
def shell_context():
    return {'database': database}


# in terminal
$ flask shell
>>> print(database)

Prototyping with the Flask Shell


Flask Tip - Flask Shell

Prototyping with the Python interpreter is really beneficial with a Flask application too!

Start the Python interpreter with the Flask application loaded to prototype with it.

πŸ‘‡

$ flask shell
>>> print(app.url_map)
>>> print(app.blueprints)

Django REST Framework - writeable nested serializers


DRF tip:

ModelSerializer's .create() method does not support writable nested fields by default.

For the nested serializer to be writable, you'll need to create create() and/or update() methods to explicitly specify how the child relationships should be saved.

πŸ‘‡

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ("title", "content")


class TagSerializer(serializers.ModelSerializer):
    posts = PostSerializer(many=True)

    class Meta:
        model = Tag
        fields = ['name', 'posts']

    def create(self, validated_data):
        posts_data = validated_data.pop('posts')
        tag = Tag.objects.create(**validated_data)

        for post in posts_data:
            Post.objects.create(tag=tag, **post)

        return tag

Django REST Framework - Nested Serializers


DRF tip:

To easily join parent and child objects inside a single response body, you can use a nested serializer.

πŸ‘‡

# serializers:
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ("title", "content")


class TagSerializer(serializers.ModelSerializer):
    posts = PostSerializer(many=True, read_only=True)

    class Meta:
        model = Tag
        fields = ['name', 'posts']

# result:
{
    "name": "Stories",
    "posts": [
        {
            "title": "My first story",
            "content": "She opened the door and..."
        },
        {
            "title": "Story about a dog",
            "content": "I met Chase when I was..."
        }
    ]
}

For more, review Nested Serializers.

Django REST Framework - HyperlinkedIdentityField


DRF tip:

To include a hyperlink that points to the current object in the serializer, you can use HyperlinkedIdentityField.

HyperlinkedIdentityField extends HyperlinkedRelatedField. Additionally, it makes the field read-only and sets the current object as the source.

πŸ‘‡

# serializer:
class TagSerializer(serializers.ModelSerializer):
    tag_detail = serializers.HyperlinkedIdentityField(view_name='tag-detail')

    class Meta:
        model = Tag
        fields = ['name', 'tag_detail']

# result:
{
    "name": "Stories",
    "tag_detail": "http://127.0.0.1:8000/tags/1/"
}

Django REST Framework - HyperlinkedRelatedField


DRF tip:

According to Roy T. Fielding, RESTful APIs should be driven by hypertext.

To represent the target of the relationship with a hyperlink, you can use HyperlinkedRelatedField in the serializer.

πŸ‘‡

# serializer:
class TagSerializer(serializers.ModelSerializer):
    posts = serializers.HyperlinkedRelatedField(
        many=True, read_only=True, view_name='post-detail'
    )

    class Meta:
        model = Tag
        fields = ['name', 'posts']

# result:
{
    "name": "Stories",
    "posts": [
        "http://127.0.0.1:8000/1/",
        "http://127.0.0.1:8000/2/"
    ]
}

Django REST Framework - SlugRelatedField


DRF tip:

To represent the target of the relationship with one of its fields, you can use SlugRelatedField in the serializer.

πŸ‘‡

# serializer:
class TagSerializer(serializers.ModelSerializer):
    posts = serializers.SlugRelatedField(many=True, read_only=True, slug_field='title')

    class Meta:
        model = Tag
        fields = ['name', 'posts']

# result:
{
    "name": "Stories",
    "posts": [
        "My first story",
        "Story about a dog"
    ]
}

Django REST Framework - PrimaryKeyRelatedField


DRF tip:

To represent the target of the relationship with its primary key, you can use PrimaryKeyRelatedField in the serializer.

πŸ‘‡

# serializer:
class TagSerializer(serializers.ModelSerializer):
    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Tag
        fields = ['name', 'posts']

# result:
{
    "name": "Stories",
    "posts": [
        1,
        2
    ]
}

Django REST Framework - StringRelatedField


DRF tip:

To represent the target of the relationship with its __str__ method, you can use StringRelatedField in the serializer. πŸ‘‡

# model:
class Post(models.Model):
    # ...

    def __str__(self):
        return f"{self.date_published} - {self.title}"


# serializer:
class TagSerializer(serializers.ModelSerializer):
    posts = serializers.StringRelatedField(many=True)

    class Meta:
        model = Tag
        fields = ['name', 'posts']

# result:
{
    "name": "Stories",
    "posts": [
        "2022-01-09 - My first story",
        "2022-04-09 - Story about a dog"
    ]
}

Related Fields in Django REST Framework


DRF tip:

To represent model relationships in a serializer, you can use various related fields that represent the target of the relationship in different ways:

  • StringRelatedField
  • PrimaryKeyRelatedField
  • HyperlinkedRelatedField
  • SlugRelatedField
  • HyperlinkedIdentityField

Examples:

class TagSerializer(serializers.ModelSerializer):

    posts = serializers.StringRelatedField(many=True)
    # result: "My story" (from __str__ method)


    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    # result: 1


    posts = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='post-detail'
    )
    # result: "http://127.0.0.1:8000/1/"


    posts = serializers.SlugRelatedField(
        many=True,
        read_only=True,
        slug_field='title'
     )
    # result: "My story" (from title field)


    tag_detail = serializers.HyperlinkedIdentityField(view_name='tag-detail')
    # result: "http://127.0.0.1:8000/tags/1/"
    # *HyperlinkedIdentityField is used for current object, not related objects

For more, review Serializer relations.

Django REST Framework - how to disable the Browsable API in production


DRF tip:

If you want to use the Browsable API in development but not in production, you can set DEFAULT_RENDERER_CLASSES in settings conditionally, like so:

if not DEBUG:
    REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = (
            "rest_framework.renderers.JSONRenderer",
        )

Django - using Q objects for complex queries


Django tip:

If you need to execute more complex queries, you can use Q objects -- objects used to encapsulate a collection of keyword arguments.

Q objects can be combined using the & and | operators.

For example:

Inventory.objects.filter(
    Q(quantity__lt=10) &
    Q(next_shipping__gt=datetime.datetime.today()+datetime.timedelta(days=10))
)

Django Messages - message levels


Django tip:

By default, the lowest recorded message level, from Django's messages framework, is 20 (INFO).

That means that all message levels but DEBUG (with value 10) are displayed.

You can change the default MESSAGE_LEVEL to a higher or lower level in the settings:

# settings.py
from django.contrib.messages import constants as messages

# all message levels will be displayed:
MESSAGE_LEVEL = messages.DEBUG

# only messages with level WARNING and ERROR will be displayed:
MESSAGE_LEVEL = messages.WARNING

Django Messages - message tags for CSS classes


Django tip:

You can change Django message tags so they correspond to the CSS class you want to use for displaying the flash message.

This makes it easy to combine Django messages with a CSS framework (e.g., Bootstrap).

For example, this is how you can use Bootstrap with Django messages:

# settings.py
from django.contrib.messages import constants as messages

MESSAGE_TAGS = {
        messages.DEBUG: 'alert-secondary',
        messages.INFO: 'alert-info',
        messages.SUCCESS: 'alert-success',
        messages.WARNING: 'alert-warning',
        messages.ERROR: 'alert-danger',
}

# template
{% for message in messages %}
    <div class="alert {{ message.tags }}" role="alert">
        {{ message }}
    </div>
{% endfor %}

Django Messages - message levels


Django tip:

There are five message levels in Django's message framework that correspond to the same tags from Python's logging module (in lowercase):

  1. DEBUG
  2. INFO
  3. SUCCESS
  4. WARNING
  5. ERROR
messages.debug(request, "I'm debugging.")
messages.info(request, "I'm informing.")
messages.success(request, "You were successful.")
messages.warning(request, "This is a warning.")
messages.error(request, "This is an error.")

You can set a minimum message level that will be recorded (default is INFO).

Django Messages Example


Django tip:

For displaying a one-time notification message, you can use Django's built-in messages framework.

(The default settings.py file created with startproject contains all the required settings.)

For example, your view and template file would look something like this:

# views.py

def contact(request):
    if request.method == "POST":
        form = ContactForm(request.POST)

        if form.is_valid():
            form.save()

            messages.success(request, "Your message was sent.") # message

            return redirect("cart")

    form = ContactForm(request.POST)
    return render(request, "contact.html", {"form": form})


# contact.html

# ...
{% if messages %}
<ul class="messages">
    {% for message in messages %}
        <div {% if message.tags %} class="{{ message.tags }}"{% endif %}>
            {{ message }}
        </div>
    {% endfor %}
</ul>
{% endif %}
# ...

Django Signals - m2m_changed()


Django tip:

To send a Django signal when a ManyToManyField is changed on a model instance, you can use the m2m_changed signal.

For example, this signal is sent if an item is added to the cart:

@receiver(m2m_changed, sender=Cart.items.through)
def cart_update_total_when_item_added(sender, instance, action, *args, **kwargs):
    if action == 'post_add':
        total = Decimal(0.00)

        for item in instance.items.all():
            total += item.price

        instance.total = total
        instance.save()

Django Signals - post_delete()


Django tip:

To notify another part of the application after the delete event of an object happens, you can use the post_delete signal.

For example:

from django.db.models.signals import post_delete
from django.dispatch import receiver


@receiver(post_delete, sender=Order)
def add_to_inventory(sender, instance, **kwargs):
    inventory_item = Inventory.objects.get(id=instance.inventory_item.id)
    inventory_item.quantity = inventory_item.quantity + instance.quantity

    inventory_item.save()

Django Signals - pre_delete()


Django tip:

To notify another part of the application before the delete event of an object happens, you can use the pre_delete signal.

For example:

from django.db.models.signals import pre_delete
from django.dispatch import receiver


@receiver(pre_delete, sender=Inventory)
def allow_inventory_delete_if_no_order(sender, instance, **kwargs):
    if Order.objects.filter(inventory_item=instance.id).count() > 0:
        raise Exception("There are orders for this item.")

Django Signals - post_save()


Django tip:

To impact a different part of your application after the object is saved to the database, you can use a post_save signal.

For example:

from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=Order)
def remove_from_inventory(sender, instance, **kwargs):
    inventory_item = Inventory.objects.get(id=instance.inventory_item.id)
    inventory_item.quantity = inventory_item.quantity - instance.quantity

    inventory_item.save()

Django Signals - pre_save()


Django tip:

To execute some code dealing with another part of your application before the object gets saved to the database, you have to use a pre_save signal.

That way, the signal is sent at the beginning of a model's save() method.

For example:

from django.db.models.signals import pre_save
from django.dispatch import receiver


@receiver(pre_save, sender=Order)
def valid_order(sender, instance, **kwargs):
    inventory_item = Inventory.objects.get(id=instance.inventory_item.id)

    if instance.quantity > inventory_item.quantity:
        raise Exception("There are not enough items in the inventory.")

Django Signals Example


Django tip:

If you need a decoupled application to get notified when actions occur elsewhere in the framework, you can use a Django signal.

For example:

from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=Book)
def last_reading_from_reading_list(sender, instance, **kwargs):
    ReadingList.objects.get(id=instance.reading_list.id).save(
        update_fields=["last_reading_at"]
    )

Django REST Framework Serializers - to_internal_value()


DRF tip:

If your API receives unnecessary data, you can override to_internal_value() to extract the resource data.

For example:

class ResourceSerializer(serializers.ModelSerializer):
    # ...

    def to_internal_value(self, data):
        resource_data = data['resource']

        return super().to_internal_value(resource_data)

For more, review Custom Outputs.

Django REST Framework Serializers - to_representation()


DRF tip:

If you want to change the output of the serializer, you can override the to_representation function of the serializer.

For example:

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['likes'] = instance.liked_by.count()

        return representation

For more, review Custom Outputs.

Django REST Framework Serializers - function validators


DRF tip:

If you need the same validation in multiple serializers, you can create a function validator instead of repeating the code.

For example:

# function validator:
def is_rating(value):
    if value < 1:
        raise serializers.ValidationError('Value cannot be lower than 1.')
    elif value > 10:
        raise serializers.ValidationError('Value cannot be higher than 10')


# function validator used in a serializer
class MovieSerializer(serializers.ModelSerializer):
    rating = IntegerField(validators=[is_rating])

For more, review Custom Data Validation.

Django REST Framework Serializers - object-level validation


DRF tip:

You can use object-level validation to validate fields in comparison to one another.

For example:

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

    def validate(self, data):
        if data['us_gross'] > data['worldwide_gross']:
            raise serializers.ValidationError(
                'us_gross cannot be bigger than worldwide_gross'
            )
        return data

For more, review Custom Data Validation.

Django REST Framework Serializers - custom field validation


DRF tip:

You can add custom field validation to your serializer.

The validation method needs to be named like so: validate_<field_name>.

For example:

class PersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ['name', 'age']

    def validate_age(self, value):
        if value < 18:
            raise serializers.ValidationError('The person has to be at least 18 years old.')
        return value

For more, review Custom Data Validation.

Django REST Framework - RetrieveUpdateDestroyAPIView


DRF tip:

RetrieveUpdateDestroyAPIView is used for read-update-delete single instance endpoints. It accepts GET, PUT, PATCH, and DELETE requests. It combines RetrieveModelMixin, UpdateModelMixin, and DestroyModelMixin.

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - RetrieveDestroyAPIView


DRF tip:

RetrieveDestroyAPIView is used for read-delete single instance endpoints. It accepts GET and DELETE requests. It combines RetrieveModelMixin and DestroyModelMixin.

class RetrieveDeletePost(generics.RetrieveDestroyAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - RetrieveUpdateAPIView


DRF tip:

RetrieveUpdateAPIView is used for read-update single instance endpoints. It accepts GET, PUT, and PATCH requests. It combines RetrieveModelMixin and UpdateModelMixin.

class RetrieveUpdatePost(generics.RetrieveUpdateAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - ListCreateAPIView


DRF tip:

ListCreateAPIView is used for read-write collection endpoints. It accepts GET and POST requests. It combines CreateModelMixin and ListModelMixin.

class ListAddPost(generics.ListCreateAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - UpdateAPIView


DRF tip:

UpdateAPIView is used for update-only single instance endpoints and accepts PUT and PATCH requests. It extends UpdateModelMixin.

class UpdatePost(generics.UpdateAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - DestroyAPIView


DRF tip:

DestroyAPIView is used for delete-only single instance endpoints and only accepts DELETE requests. It extends DestroyModelMixin.

class DeletePost(generics.DestroyAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - RetrieveAPIView


DRF tip:

RetrieveAPIView is similar to the ListAPIView -- it's used for read-only endpoints and only accepts GET requests, but it returns a single instance instead of a list. It extends RetrieveModelMixin.

class PostDetail(generics.RetrieveAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - ListAPIView


DRF tip:

ListAPIView is used for read-only list endpoints and only accepts GET requests. It extends ListModelMixin.

class ListPost(generics.ListAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework - CreateAPIView


DRF tip:

CreateAPIView is used for create-only endpoints and only accepts POST requests. It extends CreateModelMixin.

class CreatePost(generics.CreateAPIView):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

For more, review Concrete Views.

Django REST Framework Views - Generic Views


DRF tip:

All concrete generic views are a combination of the GenericAPIView and one or multiple mixins.

GenericAPIView provides methods like get_object/get_queryset and get_serializer, whereas mixins provide create/retrieve/update/delete actions.

For more, check out Django REST Framework Views - Generic Views.

Django REST Framework - DefaultRouter API Root


Django REST Framework tip:

If you're using DefaultRouter, the API root will automatically be included.

The API root is an endpoint that returns a response containing hyperlinks to all the list views.

(Unlike DefaultRouter, SimpleRouter doesn't include the API root.)

DRF Browsable API

ViewSet Actions in Django REST Framework


Django REST Framework tip:

If you're using ModelViewSet and want to create a custom endpoint, you can add it to the ViewSet as a function decorated with the @action decorator.

class PostModelViewSet(ModelViewSet):
    serializer_class = PostSerializer
    queryset = Post.objects.all()

    @action(detail=False, methods=['get'])
    def unpublished_posts(self, request):
        unpublished = Post.objects.filter(published=False)
        serializer = PostSerializer(unpublished, many=True)

        return Response(serializer.data)

# available at:
http://127.0.0.1:8000/unpublished_posts/

Django REST Framework's ModelViewSet


Django REST Framework tip:

If your API endpoints map close to your models, you can save yourself quite a few lines of code by using ModelViewSet in combination with a router.

# viewsets.py
class PostModelViewSet(ModelViewSet):
    serializer_class = PostSerializer
    queryset = Post.objects.all()


# urls.py
router = routers.DefaultRouter()
router.register(r'', PostModelViewSet)

urlpatterns = [
    path('', include(router.urls)),
]


# yields:
http://127.0.0.1:8000/      # for list of posts
http://127.0.0.1:8000/1/  # for post detail

(ViewSets should be stored in a file named viewsets.py rather than views.py.)

How to create views in Django REST Framework


Django REST Framework tip:

There are three core ways to create views:

  1. extending APIView class
  2. Extending one of the seven concrete API views (e.g., ListCreateAPIView)
  3. ViewSet (e.g., ModelViewSet)

There are also some sub-possibilities:

DRF Views Overview

Django's length template filter


Django tip:

If you want to show the length of a string or list, you can use the length template filter.

{{ friends|length }}

Django's pluralize template filter


Django tip:

Sometimes you need to use the single or plural form based on the number you're displaying. You can handle this by using the pluralize filter.

{{ number_of_friends }} friend{{ number_of_friends|pluralize }}

# 1 friend
# 2 friends 

An "s" is automatically used as the suffix, but you can also provide your own suffix (for both singular and plural versions).

{{ number_of_mice }} {{ number_of_mice|pluralize:"mouse,mice" }}

# 1 mouse
# 2 mice

Selecting a random element from a list in a Django template


Django tip:

You can use the random Django template filter to return a random item from the given list.

{{ inspirational_quote|random }}

Slicing a list in a Django template


Django tip:

You can return only part of the list in a Django template by using the slice filter.

The syntax for slicing is the same as for Python’s list slicing.

{{ friends|slice:":3" }}

Django Date Template Filter


Django tip:

You can use the date filter to format a given date/time.

Example:

{{ post.datetime|date:"jS F Y" }}  # => 1st January 2022

After the colon, you can provide the desired format inside the string. If the format is not provided, the filter will use the default one, which can be specified in the settings.

Convert letter case in a Django template


Django tip:

There are four template filters you can use to change letter case:

  1. title
  2. capfirst
  3. lower
  4. upper
{{ item.name|title }}
{{ item.name|capfirst }}
{{ item.name|lower }}
{{ item.name|upper }}

Check if a For Loop Variable Is Empty in a Django Template


Django tip:

When looping through a list in a Django template, you can use the empty tag to cover cases when the list is empty:

{% for item in list %}
    {{ item }}
{% empty %}
    <p>There are no items yet.</p>
{% endfor %}

Create Custom Django Admin Actions


Django tip:

You can create custom bulk actions for the Django admin.

Example:

@admin.action(description='Mark selected items purchased')
def make_purchased(modeladmin, request, queryset):
    queryset.update(purchased=True)


@admin.register(ShoppingItem)
class ShoppingItemAdmin(admin.ModelAdmin):
    actions = [make_purchased]

How to exclude Django Modelform fields


Django tip:

You can use either exclude or fields to impact which fields will be available in the Django admin model forms.

For example:

# models.py

class Child(models.Model):
     name = models.CharField(max_length=200)
     last_name = models.CharField(max_length=200)
     grade = models.CharField(max_length=200)



# admin.py  

@admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
    exclude = ('grade',)

# yields the same result as

@admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
    fields = ('name', 'last_name')

How to check for first or last iteration of a for loop in a Django template


Django tip:

You can use forloop.first or forloop.last to check if the current iteration is the first or the last time through a for loop in your Django templates like so:

{% for item in item_list %}
    {{ forloop.first }}  # True if this is the first time through the loop
    {{ forloop.last }}   # True if this is the last time through the loop
{% endfor %}

Current iteration from the end of a for loop in a Django template - forloop.revcounter


Django tip:

You can use revcounter to get the number of iterations from the end of a for loop in your Django templates like so:

{% for item in item_list %}
    {{ forloop.revcounter }}  # starting index 1
    {{ forloop.revcounter0 }} # starting index 0
{% endfor %}

Current iteration of a for loop in a Django template - forloop.counter


Django tip:

You can use counter to get the current iteration of a for loop in your Django templates like so:

{% for item in item_list %}
    {{ forloop.counter }}  # starting index 1
    {{ forloop.counter0 }} # starting index 0
{% endfor %}

Django - Custom verbose plural name for admin model class


Django tip:

Django automatically creates a plural verbose name from your object by adding and "s" to the end.

child -> childs

To change the plural verbose name, you can define the verbose_name_plural property of the Meta class like so:

class Child(models.Model):

    ...

    class Meta:
        verbose_name_plural = "children"

Django - Custom Database Constraints


Django tip:

You can add custom database constraints to your Django models like so:

class Child(models.Model):  

    ....

    class Meta:
        constraints = [
            models.CheckConstraint(check=models.Q(age__lt=18))
        ]

Custom Django Management Commands


Django tip:

You can create your own custom Django management commands that you can run with manage.py. For example:

python manage.py your_command

Simply add a Python module to a "management/commands" folder in a Django app.

Example:

your_app/
    __init__.py
    models.py
    management/
        __init__.py
        commands/
            __init__.py
            your_command.py
    tests.py
    views.py

(Django will ignore any module that begins with an underscore.)

Example command:

from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def handle(self, *args, **options):
        self.stdout.write('pong!')

Django Admin - custom filters with list_filter


Django tip:

With list_filter you can add custom filters to the right sidebar of the Django admin list page.

For example:

@admin.register(Child)
class ItemAdmin(admin.ModelAdmin):
    list_filter = ("grade", )

Django - reference method in the list_display tuple for the admin


Django tip:

Besides model fields, the list_display tuple can reference methods from ModelAdmin:

@admin.register(ShoppingList)
class ShoppingListAdmin(admin.ModelAdmin):
    list_display = ("title", "number_of_items")

    def number_of_items(self, obj):
        result = ShoppingItem.objects.filter(shopping_list=obj.id).count()
        return result

Customize the Django admin with list_display


Django tip:

You can make your admin list page friendlier to the user by specifying which fields should be displayed:

@admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
    list_display = ("last_name", "first_name")

Custom field for search in the Django admin


Django tip:

search_fields sets which model fields will be searched when a search is performed in the Django admin.

You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API "follow" notation (double underscore syntax):

@admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
    search_fields = ['parent__name']

Registering models with the Django Admin


Django tip:

Instead of using admin.site.register for registering models with the Django admin, you can use a decorator.

https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#the-register-decorator

πŸ‘‡

## option 1 ##

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title')

admin.site.register(Author, AuthorAdmin)


## option 2 ##

# you can use a decorator instead
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title')

Testing tip - focus testing efforts on testing private methods


Testing tip:

Focus the majority of your testing efforts on testing methods that you (or other stakeholders) intend to call from other packages/modules. Everything else is just an implementation detail.

Testing tip - use mocks only when necessary


Testing tip:

Use mocks only when necessary (like for third-party HTTP APIs). They make your test setup more complicated and your tests overall less resistant to refactoring.

Plus, they can result in false positives.

Shorten development cycles with pytest markers


Testing tip:

The faster you notice regressions, the faster you can intercept and correct them. The faster you correct them, the shorter the development cycle.

You can use pytest markers to exclude e2e and other slow tests during development. You can run them less frequently.

https://docs.pytest.org/en/6.2.x/example/markers.html

Writing Valuable Tests


Testing tip:

A test is valuable only when it protects you against regressions, allows you to refactor, and provides you with fast feedback.

Testing tip - create more loosely coupled components and modules


Testing tip:

There's no single right way to test your software.

Nonetheless, it's easier and faster to test logic when it's not coupled with your database.

Python Testing - Mocking different responses for consecutive calls


Python testing tip:

You can specify different responses for consecutive calls to your MagicMock.

It's useful when you want to mock a paginated response.

πŸ‘‡

from unittest.mock import MagicMock


def test_all_cars_are_fetched():
    get_cars_mock = MagicMock()
    get_cars_mock.side_effect = [
        ["Audi A3", "Renault Megane"],
        ["Nissan Micra", "Seat Arona"]
    ]

    print(get_cars_mock())
    # ['Audi A3', 'Renault Megane']
    print(get_cars_mock())
    # ['Nissan Micra', 'Seat Arona']

Type Hints - How to Use typing.cast() in Python


Python tip:

You can use cast() to signal to a type checker that the value has a designated type.

https://docs.python.org/3/library/typing.html#typing.cast

πŸ‘‡

from dataclasses import dataclass
from enum import Enum
from typing import cast


class BoatStatus(int, Enum):
    RESERVED = 1
    FREE = 2


@dataclass
class Boat:
    status: BoatStatus


Boat(status=2)
# example.py:16: error: Argument "status" to "Boat" has incompatible type "int"; expected "BoatStatus"
# Found 1 error in 1 file (checked 1 source file)

Boat(status=cast(BoatStatus, 2))

SQLAlchemy with_for_update


SQLAlchemy tip:

You can use with_for_update() to use SELECT FOR UPDATE. This will prevent changes in selected rows before you commit your work to the database.

https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.with_for_update

πŸ‘‡

user = session.query(User).filter(User.email == email).with_for_update().first()

user.is_active = True
session.add(user)
session.commit()

Mount a Flask or Django app inside a FastAPI application


FastAPI tip:

You can use WSGIMiddleware to mount WSGI applications (like Flask and Django) to your FastAPI API.

https://fastapi.tiangolo.com/advanced/wsgi/

πŸ‘‡

from fastapi import FastAPI
from fastapi.middleware.wsgi import WSGIMiddleware
from flask import Flask, escape, request

flask_app = Flask(__name__)


@flask_app.route("/")
def flask_main():
    name = request.args.get("name", "World")
    return f"Hello, {escape(name)} from Flask!"


app = FastAPI()


@app.get("/v2")
def read_main():
    return {"message": "Hello World"}


app.mount("/v1", WSGIMiddleware(flask_app))

FastAPI - disable OpenAPI docs


FastAPI tip:

You can disable OpenAPI docs by setting openapi_url to an empty string.

https://fastapi.tiangolo.com/advanced/conditional-openapi/#conditional-openapi-from-settings-and-env-vars

πŸ‘‡

from fastapi import FastAPI
from pydantic import BaseSettings


class Settings(BaseSettings):
    openapi_url: str = ""


settings = Settings()

app = FastAPI(openapi_url=settings.openapi_url)


@app.get("/")
def root():
    return {"message": "Hello World"}