Django - robbiehume/CS-Notes GitHub Wiki

General

Overview

  • Uses a design similar to MVC, called MVT (model, view, template)
    • The view and template in Django’s MVT pattern make up the view in the MVC pattern of other web frameworks
    • Although it’s based on the MVC pattern, Django handles the controller part itself
      • There’s no need to define how the database and views interact, it’s all done for you
  • Benefits of Django: rapid development, provides extensive library (security, sessions, authentication, etc.), it's very scalable
  • Django Rest Framework (DRF) allows for REST APIs:
    • It has things such as serializers, views, routers, authentication, etc. to make writing code easier and cleaner

Project structure

  • A Django website consists of a single project that's split into separate apps
    • The idea is that each app handles a self-contained task that the site needs to preform
  • A Django project contains at least one app. But even when there are more apps in the Django project, you commonly refer to a Django project as a web app
  • A Django site starts off as a project, and you build it up with a number of applications that each handle separate functionality. Each app follows the model-view-template pattern
  • Project:
    • The Django project holds some configurations that apply to the project as a whole, such as project settings, URLs, shared templates and static files
  • App:
    • Each app can have its own database, and it’ll have its own functions to control how it displays data to the user in HTML templates
    • Each app also has its own URLs as well as its own HTML templates, and static files, such as JavaScript and CSS
  • Django apps are "pluggable": You can use an app in multiple projects, and you can distribute apps, because they don’t have to be tied to a given Django installation.

Files / commands

  • django-admin start-project <project_name>: creates a new project
  • __init__.py: tells Python to treat the directory
  • asgi.py/wsgi.py: they allow django to communicate with the web server; you don't have to deal with them yourself
  • settings.py: overall settings file
  • urls.py: used to configure URL routes
    • This can be at the project- or app-level
  • manage.py: a special command line tool to do different things
  • python manage.py startapp <app_name> (run from within project): create a new app
    • Then go into the project subdirectory (<proj_name>/<proj_name>) and add the app_name to settings.py INSTALLED_APPS list
  • python manage.py runserver <optional_port_num>: starts the project server

Project and App management

Removing an app from a project

  1. Remove all references / imports / foreign keys to the app
  2. Remove models from models.py
  3. Run makemigrations to generate table-deleting migration
  4. Run tests locally and verify correctness
  5. (Optional) Squash references in other apps' migrations if needed
  6. Apply migrations using python manage.py migrate
  7. Remove the app from INSTALLED_APPS
  8. Delete the app folder from the project

Components:

  • Apps: At the heart of every Django website is apps, which are small pieces of the project that make up the entire website
    • Apps typically contain some kind of database models, URLs, views, and other info about about a particular part of the website
  • Views: functions that are in charge of processing a users request when they visit a certain URL or endpoint on the website.
  • URL Routing: to handle URL routing in a Django app, you create URL patterns in a list and attach different paths to those views
    • This is how Django knows which view to fire off when a user visits a URL on the website

Files

urls.py: URL paths mapped to different views

  • This can be at the project or app level
  • project-level (my_project/my_project/urls.py)
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
      path('admin/', admin.site.urls),
      path('', include('my_app.urls')) # this will forward all URLs that start with / to the my_app urls.py and be routed from there
    ]
  • app-level (my_project/my_app/urls.py)
    from django.urls import path
    from . import views  # gets all the local views
    
    urlpatterns = [
      path('', views.hello_world, name='hello'),  # this will map / to the views.hello_world() function
      path('todos/', views.todos, name='Todos')
    ]

Paths

  • Path variables
    • path('<int:id>, views.index, name='index'): looks for an integer in the path and passes it to index()
    • Other types:
      • <str:name> (name is just the variable name)

views.py: handle different URL paths

  • from django.shortcuts import render, HttpResponse
    from .models import TodoItem
    
    def hello_world(request):
      return HttpResponse('hello world')
      # or you can render a template, e.g. return render(request, 'home.html')
    
    def todos(request):
      items = TodoItem.objects.all()
      return render(request, 'todos.html', {'todos': items})
  • Different modules:
    • HttpResponse

templates/: holds different HTML templates (can be at the project or app level)

  • For app templates, they need to be under app_name/templates/app_name
  • Can use blocks to extend templates and fill in variables
    • home.html (extends a base.html template that already exists: link)
      {% extends 'base.html' %} 
      {% block title %} Home Page {% endblock%}
      {% block content %}
        <p>this is the home page</p>
      {% endblock %}

models.py: holds the database models

  • Need to register models in admin.py
  • Any time you make a change to your database models, you need to make a "migration"
    • python manage.py makemigrations
      • Can also specify an app to only migrate: python manage.py makemigrations my_app
    • python manage.py migrate
    • The migration is some automated code that Django will apply to the database, which allows you to change your models and update them, while maintaining that data and not breaking any previous schema or data
      • It's like git commit history
    • See exact SQL equivalent of a migration: python manage.py sqlmigrate my_app <migration_number>
      • Example: python manage.py sqlmigrate my_app 0002
    • Revert back to a specific migration:
      • Example: python manage.py migrate my_app 0002
      • NOTE: this only affects the database, not the migration files themselves
        • If you also want to delete migration files, delete them manually and run python manage.py makemigrations
  • from django.db import models
    
    class TodoItem(models.Model):
      title = models.CharField(max_length=200)
      completed = models.BooleanField(default=False)
    
      def __str__(self):  # method for printing TodoItem
          return self.text

admin.py: registers your database models from models.py

  • from django.contrib import admin
    from .models import TodoItem  # or my_app.models if the model is within the my_app app
    
    
    admin.site.register(TodoItem)

forms.py: define forms for HTML templates

  • from django import forms
    
    class CreateNewList(forms.Form):
      name = forms.CharField(label='Name', max_length=200)
      check = forms.BooleanField()

Views

  • Types of views (DRF and built-in Django)
    Scenario Recommended View Abstraction Level
    Super fast one-off JSON endpoint @api_view() 1️⃣ Function-based
    Need full control over HTTP methods APIView 2️⃣ Manual CBV
    Want less boilerplate but still control GenericAPIView + Mixins 3️⃣ Semi-automated CBV
    Need single-action CRUD views <Action>APIView (e.g., ListAPIView) 4️⃣ Specialized CBV
    Clean, standard full CRUD API ModelViewSet (or ViewSet) 5️⃣ Fully abstracted
    Serving HTML web pages Django ListView, DetailView, etc. Separate stack (Django CBV)

Models in Django

  • A Model in Django is a python class that generally maps to a single database table
  • SQLite comes with Django and is the default database
    • To change to another DB, update DATABASES in settings.py

Creating / defining a Model (in models.py)

  • To create a Model, we use a class structure inside of the relevant application’s models.py file
  • This class object will be a subclass of Django’s built-in class: django.db.models.Model
  • Django automatically handles the id field and uses it as the primary key
  • But if you want, you can set a different primary key:
    • E.g. for a product, set it as a SKU number: sku = models.CharField(max_length=10, primary_key=True)
  • The model fields have specific types such as:
  • Each field can also have constraints or options. For example:
    • A CharField can have a max_length constraint, indicating the maximum numbers of characters allowed.
    • Any field can have a unique option specifying that only unique instances of that field are allowed in the table.

Model class structure

  • Example of a blog post Model class:
    • from django.db import models
      
      class Post(models.Model):
          author = models.ForeignKey(User, on_delete=models.CASCADE)
          title = models.CharField(max_length=200)
          text = models.TextField()
          created_date = models.DateTimeField(default=timezone.now)
          published_date = models.DateTimeField(blank=True, null=True)
  • Field types:
    • Strings:
      • CharField: this is how we define text with a limited number of characters.
      • TextField: this is for long text without a limit
    • Numbers:
      • IntegerField
      • DecimalField
    • DateField/DateTimeField: for dates with and without times
      • Specific options:
        • auto_now:
        • auto_now_add:
    • ForeignKey: this is a link to another model
  • Field validation options:
    • null: if True, Django will store empty values as NULL in the database. Default is False
    • blank: if True, the field is allowed to be blank. Default is False
    • max_length:
    • db_column: the name of the database column to use for the field it is applied to. If this isn’t given, Django will use the field’s name
    • default: the default value for the field. This can be a value or a callable object. If callable, it will be called every time a new object is created
    • help_text: extra “help” text to be displayed with the form widget. This is useful for documentation even if our field isn’t used on a form
    • primary_key: if True, this field is the primary key for the model

Class (AKA Entity or Model) relationships:

  • You can define relationships in either class, as Django will automatically create the reverse relationship
    • It will use the default names below, or you can pass in related_name=<attr_name_for_related_model>
      • If you do use related_names, it's best to do it consistently across your project or it will get ugly / hard to maintain

Relationship types:

  • One-to-one: customer = models.OneToOneField(Customer, on_delete=models.CASCADE, primary_key=True)
  • One-to-many: customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
    • Use PROTECT if you don't want to delete the object if the customer is deleted
  • Many-to-many: customer = models.ManyToManyField(Customer)
  • Relationship Forward Reverse Reverse Name (default)
    ForeignKey Many-to-One One-to-Many <related_model>_set
    ManyToManyField Many-to-Many Many-to-Many <related_model>_set
    OneToOneField One-to-One One-to-One <related_model> (no _set)

Handling circular dependencies:

  • A circular dependency occurs when two classes depend on each other at the same time
  • To handle this, you can set related_name to another name that doesn't clash
    • Or if you don't care about the reverse relationship, you can set related_name='+'

Generic relationships

  • When possible, we want relationships to be app agnostic (loosely coupled) and be able to be used in multiple places without having to customize it
  • What we need is a generic way to define an object: type and ID
    • This allows us to find the table and then use the ID to find the record (object) within the table (class)
  • To do this, we need 3 things:
    • A special abstract model class called ContentType when defining the foreign key
      • content_type = models.ForeignKey(ContentType)
    • An object ID
      • object_id = models.PositiveIntegerField()
    • The content object
      • content_object = GenericForeignKey()
  • For an example, see models.py in the Mosh tutorial tags app
  • Use GenericRelation on target models:
    • comments = GenericRelation(Comment)
    • This adds a reverse accessor: post.comments.all()

on_delete options:

  • These 4 cover 90%+ of real-world cases you'll encounter when working with Django models
    • models.CASCADE: Deletes the related objects too (cascades deletion)
    • models.PROTECT: Prevents deletion by raising a ProtectedError
    • models.SET_NULL: Sets the foreign key field to NULL (requires null=True)
    • models.RESTRICT: Like PROTECT, but less strict and only raises an error if there are dependent objects

Creating and querying class model instances

TodoList is just an example class for the below code snippets

Creating an object

  • t = TodoList(name='List 1')
    t.save()
  • Or to create with saving automatically: t = TodoList.objects.create(name='List 1')
  • Still need to save when updating the object:
    • t.name = 'New name'
      t.save()

Object methods

  • Get all objects: TodoList.objects.all()
  • Get objects by attribute: TodoList.objects.get(<attr>=<attr_val>)
    • Get by id: TodoList.objects.get(id=1)
    • Get by name (if it has that attribute): TodoList.objects.get(name='List 1')
  • Filter: TodoList.objects.filter(<attr_functions>)
    • TodoList.objects.filter(name__startswith="Robbie")
  • Delete: TodoList.objects.get(id=1).delete()

Foreign Key Objects

  • If a class has a foreign key of another object, then you can create that class through the foreign key class
  • Ex:
    • class Item(models.Model):
         todolist = models.ForeignKey(TodoList, on_delete=models.CASCADE)
         text = models.CharField(max_length=300)
    • Create Item for TodoList t: t.item_set.create(text='groceries')
    • Query Items: t.item_set.all()
      • NOTE: item_set is because the class name is Item; it follows the structure of <lowercase_class_name>_set

Routing and URL mapping

  • Routing can be two types static or dynamic
  • Static: a specific URL that leads to a specific page
    • Ex: /home --> home page, /about --> about page
  • Dynamic: URLs that lead to the same page, but depending on the variable part of the URL, the content shown can be different
    • Ex: /profile/101011 and /profile/102045 lead to the same endpoint but the page is rendered according to the profile id

Static routing

  • In static routing, we specify a constant URL string as a path in the urls.py file
  • NOTE: the path is a case-sensitive string. Therefore, /home and /Home are two different URLs

Dynamic routing

  • In dynamic routing, we don’t specify a constant URL path. Instead, we pass a URL with variable parameters in it
  • The parameters may already be present in the URL itself, or they may be the result of user input
  • Syntax of dynamic routes
    • To add a variable in a path, we use it between the angle brackets
    • For example, <question_id>, where question_id is the name of the variable we pass to the path as the first parameter

Path converters

  • For the dynamic routes mentioned above, we can also convert the variable to another data type:
    • str: Matches any non-empty string, excluding the path separator, '/'
    • int: Matches zero or any positive integer
    • slug: Matches any slug string consisting of ASCII letters or numbers, plus the hyphen and underscore characters
      • For example, building-your-1st-django-site
    • uuid: Matches a formatted UUID
    • path: Matches any non-empty string, including the path separator, '/'
      • This allows us to match against a complete URL path rather than a segment of a URL path, as with str

URL mappings & include() function

  • The include() function allows us to look for a match with the path defined in the urls.py file of our project, and link it back to our application’s own urls.py file
  • Whenever Django encounters include(), it takes whatever part of the URL matched up to that point and sends the remaining string to the urls.py file of the specified application
    • Ex:

Admin Panel

Create a user

  • python manage.py createsuperuser

Forms

  • Click above link to see Python templating and differences between Django Template Language (DTL) and Jinja2
  • The syntax of the Django template language involves four constructs:
    • Variables: get replaced with values when the template is evaluated and rendered
    • Tags: used to control the logic of the template
      • These can be conditions, loops, or filters
    • Filters: used to modify variables for display
    • Comments: used to comment-out part of a line in a template

Variables

  • Syntax: {{ variable }}
  • Use: variables are used inside templates by passing any Python object to the render() function
    • return render(request,"index.html", context = any_object)
    • In the example above, index.html is the name of the template and context is the name of the object we are passing
      • Context can be a a JSON object, class, etc.

Tags and Loops

  • NOTE: tags must always have an end tag

    • Ex: {% endfor %}, {% endif %}, {% endfor %}
  • for tag: used to loop over each item in an iterable

    • <ul>
      {% for element in name_of_list %}
          <li>{{ element }}</li>
      {% endfor %}
      </ul>
  • if, elif, and else tags:

    •   {% if condition %}
            ....
        {% elif condition %}
            ....
        {% else %}
            ....
        {% endif %}
  • Filters:

    • Syntax: {{ variable | filter_name }}
    • A | (called a pipe) is used to apply a filter
      • Basically, the syntax above will display the value of the {{variable}} after being filtered through the filter_name filter
    • Some filters can also take arguments. A filter argument looks like this: {{ bio|truncatewords:30 }}
      • This will display the first 30 words of the bio variable
    • Django provides about sixty built-in template filters
    • Below are some of the more commonly used template filters:
      • default filter:
        • {{ value | default:"Value not provided" }}
        • Ex: for above, if value isn't provided, then it displays 'Value not provided.'
      • length filter: filters the length of the variable. Works for both strings and lists
        • {{ value | length }}
        • Ex: if value is ['north', 'south', 'east', 'west'], the output will be 4
      • lower filter:
        • {{ value | lower }}
        • Ex: if value is 'HeLlO tHeRe', then the output will be: 'hello there'
  • Comments:

    • Syntax: {# #}
    • Ex: {# My first comment #} Educative
    • A comment can also contain any template code, invalid or not
      • {# {% if foo %} bar {% else %} #}
    • This syntax can only be used for single-line comments (no newlines are permitted between the {# and #} delimiters)
    • If you need to comment-out a multi-line portion of the template, see the comment tag

Django REST Framework (DRF)

  • Must add 'rest_framework' to the INSTALLED_APPS in settings.py

Serializers (serializers.py)

  • Serializers allow us to convert complex Django data structures such as QuerySet or model instances into Python native objects that can be easily converted to JSON or XML format
    • A serializer also serializes JSON or XML to native Python
  • The DRF provides a serializers package we can use to write serializers and validations when API calls are made to an endpoint using this serializer
  • To use serializers package, import it in serializers.py: from rest_framework import serializers
  • Example serializers.py:
    • from rest_framework import serializers
      from my_app.user.models import User  # User class from my_app/user/models.py
      
      class UserSerializer(serializers.ModelSerializer):
          # Rewriting some fields like the public id to be represented as the id of the object
          id = serializers.UUIDField(source='public_id', read_only=True, format='hex')
          created = serializers.DateTimeField(read_only=True)
          updated = serializers.DateTimeField(read_only=True)
      
          class Meta:
              model = User
              # List of all the fields that can be included in a request or a response
              fields = ['id', 'username', 'first_name', 'last_name', 'bio', 'avatar', 'email', 'is_active', 'created', 'updated']
              read_only_field = ['is_active']  # List of all the fields that can only be read by the user

Viewsets (in views.py)

  • Django at its core is based on the Model-View-Template (MVT) architecture
    • The model communicates with the views (or controllers) and the template displays responses or redirects requests to the views
  • When Django is coupled with DRF, the model can be directly connected to the view
    • However, it's good practice to use a serializer between a model and a viewset
⚠️ **GitHub.com Fallback** ⚠️