Hello! In our last lesson, we covered the high-level concepts of Django: what it is, its MVT architecture, and how its ORM works. Now, we're going to dive into the practical, day-to-day building blocks you'll use to create an application.
This lesson covers the absolute essentials. We'll look at Models (how you define your data), Migrations (how you safely update your database), Forms (how you handle user input), and Views (how you write the logic for your pages). Understanding this workflow is the key to being a productive Django developer.
This pair of features is at the heart of Django's power. They work together to link your Python code directly to your database schema.
A Model is a Python class that defines the blueprint for your data. It is the "single source of truth" for your database structure.
Each `class` you write in `models.py` maps to a table in your database. Each "attribute" (or `field`) in that class maps to a column in that table.
# my_app/models.py
from django.db import models
class Product(models.Model):
# This will become a VARCHAR column
name = models.CharField(max_length=200)
# This will become a DECIMAL column
price = models.DecimalField(max_digits=10, decimal_places=2)
# This will become a BOOLEAN column with a default
in_stock = models.BooleanField(default=True)
def __str__(self):
return self.name
Migrations are Python files that act as version control for your database schema.
You never write SQL `CREATE TABLE` or `ALTER TABLE` commands by hand. Instead, you just change your `models.py` file. Then, you tell Django to "look for changes" (using `makemigrations`), and it writes the necessary migration file for you. Then you tell it to "apply the changes" (using `migrate`).
Analogy:
Newer model features worth knowing (Django 4.2 / 5.x)
These come up when an interviewer wants to check you have used a recent Django rather than learned it years ago.
GeneratedField (5.0) - a column the database computes from other columns. The value is always consistent because it is never written by application code.db_default (5.0) - a default applied by the database rather than by Python. Unlike default, it also applies to rows created outside the ORM, and it lets you use database functions such as Now().Meta.constraints - prefer database-level UniqueConstraint and CheckConstraint over the older unique_together, which is effectively superseded.from django.db import models
from django.db.models import F, Q, Value
from django.db.models.functions import Now
class OrderLine(models.Model):
quantity = models.IntegerField()
unit_price = models.DecimalField(max_digits=8, decimal_places=2)
# Computed by the database, never assigned in Python
total = models.GeneratedField(
expression=F("quantity") * F("unit_price"),
output_field=models.DecimalField(max_digits=12, decimal_places=2),
db_persist=True,
)
# Database-side default: applies even to raw SQL inserts
created_at = models.DateTimeField(db_default=Now())
status = models.CharField(max_length=20, db_default=Value("pending"))
class Meta:
constraints = [
models.CheckConstraint(
condition=Q(quantity__gt=0), name="quantity_positive"
),
]Two more recent changes that catch people out: STORAGES (4.2) replaced the old DEFAULT_FILE_STORAGE and STATICFILES_STORAGE settings with a single dict, and {% querystring %} (5.1) removed the need for the hand-rolled template tag almost every project used to write for pagination links.
# settings.py (Django 4.2+)
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
},
}This is the two-step process for safely changing your database schema.
Analogy: "Writing the Plan"
This command compares your current `models.py` files to the last-applied migration file.
Crucially, this command does not touch your database. It just creates the plan.
# You add a 'description' field to your Product model
# in models.py. Then you run this:
$ python manage.py makemigrations my_app
Migrations for 'my_app':
my_app/migrations/0002_product_description.py
- Add field description to product
Analogy: "Executing the Plan"
This command looks at all the migration files that have not yet been applied to the database.
$ python manage.py migrate
Operations to perform:
Apply all migrations: my_app
Running migrations:
Applying my_app.0002_product_description... OK
A Django Form is a Python class that helps you create HTML forms, validate submitted data, and clean it up.
Handling forms is one of the hardest parts of web development. You have to:
The Django `forms` library handles all of these steps for you. You just define a form class that looks similar to a model.
# my_app/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
# You can even add custom validation logic
def clean_name(self):
name = self.cleaned_data.get('name')
if name.lower() == 'admin':
raise forms.ValidationError("Name cannot be 'admin'.")
return name
This is a very common and practical question.
| Feature | `forms.Form` | `forms.ModelForm` |
|---|---|---|
| What it is | A generic, flexible form class. | A subclass of `Form` that is linked to a Model. |
| Use Case | When the form does not map to a database table (e.g., a contact form, a search bar). | When you want to create or update a database object (e.g., a "New Product" form). |
| Fields | You must manually define every field. | It automatically generates form fields from your Model. |
| Saving | Has no `.save()` method. You get the data and handle it manually. | Has a `.save()` method that will create/update the database object. |
Example `ModelForm`:
This form automatically builds "name", "price", and "in_stock" fields from the `Product` model we defined earlier.
# my_app/forms.py
from django import forms
from .models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'price', 'in_stock'] # or __all__
A View is the "chef" in our MVT analogy. It's a Python function (or class) that takes an HTTP request (like `GET /products/5/`) and returns an HTTP response (like the product's HTML page, or some JSON).
The View is the logic that runs. It's responsible for fetching data from the Model, processing it, loading a Template, and sending the result back to the user.
There are two ways to write views in Django:
This is the simplest, most explicit way. A view is literally a single function.
# my_app/views.py
from django.shortcuts import render, get_object_or_404
from .models import Product
def product_detail_fbv(request, product_id):
# 1. Get data from the Model
product = get_object_or_404(Product, pk=product_id)
# 2. Pass data to the Template
context = {'product': product}
# 3. Return the rendered response
return render(request, 'my_app/product_detail.html', context)
This is the modern, more powerful way. A view is a class that inherits from one of Django's built-in "generic" views.
# my_app/views.py
from django.views.generic import DetailView
from .models import Product
class ProductDetailCBV(DetailView):
# That's it!
# Django's DetailView handles all the logic:
# 1. It knows to get the Product with the 'pk'
# 2. It knows to pass it to a template
# 3. It even knows the template name is
# 'my_app/product_detail.html'
model = Product
context_object_name = 'product'
Tip: In an interview, show that you understand both. Explain that you might use an FBV for a complex, unique page (like a dashboard), but you would always use a CBV for standard CRUD (Create, Read, Update, Delete) operations, because it's faster and cleaner.
2 questions · no sign-up, nothing stored
Progress is stored in this browser only - no sign-up, nothing sent anywhere.