Released · improving
Django guide · 4/6
In Django you describe data with ORM models, apply them to the database with migrations, and read and write it through QuerySets. User input is validated with forms, and state that lasts across requests is handled by sessions and the authentication system.
Each attribute on a model class becomes a column in its table. ForeignKey creates a many-to-one relationship, ManyToManyField a many-to-many one, and OneToOneField a one-to-one one.
# polls/models.py
from django.db import models
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ["-pub_date"]
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices")
choice_text = models.CharField(max_length=200)
votes = models.PositiveIntegerField(default=0)on_delete decides what happens when the referenced row is deleted: CASCADE deletes this one too, PROTECT blocks it, and SET_NULL clears the field (needs null=True). related_name sets the name for the reverse lookup, as in question.choices.all().
After changing your models, apply the change to the database in two steps.
# Detect model changes and write a file into migrations/
python manage.py makemigrations polls
# Show the SQL that will run
python manage.py sqlmigrate polls 0001
# Apply it to the database
python manage.py migrate
# List migrations and whether they are applied
python manage.py showmigrationsCommit migration files with your code so teammates and servers get the same schema from migrate. The default database is SQLite; production deployments often use PostgreSQL, and switching is a matter of changing DATABASES.
Model.objects is a manager, and the QuerySets it returns are lazy. Chaining filters runs no queries; the database is hit once, when you iterate over the QuerySet or turn it into a list.
from datetime import timedelta
from django.db.models import Count, F, Q
from django.utils import timezone
from polls.models import Choice, Question
# Create and save
q = Question.objects.create(question_text="What is your favorite language?")
q.choices.create(choice_text="Python")
# Filter with field__lookup syntax
recent = Question.objects.filter(pub_date__gte=timezone.now() - timedelta(days=7))
python_q = Question.objects.filter(question_text__icontains="python")
# Follow relations and annotate with a count
popular = (
Question.objects.annotate(total=Count("choices"))
.filter(total__gt=1)
.order_by("-total")
)
# OR conditions and an atomic increment
Question.objects.filter(Q(question_text__startswith="Why") | Q(question_text__startswith="How"))
Choice.objects.filter(pk=1).update(votes=F("votes") + 1)
# Avoid N+1 queries
for choice in Choice.objects.select_related("question"):
print(choice.question.question_text)select_related fetches foreign keys in the same query with a JOIN, while prefetch_related loads many-to-many and reverse relations with one extra query. Try queries in python manage.py shell, and wrap writes that must succeed or fail together in transaction.atomic().
A form takes input, validates it and hands back clean values in cleaned_data. When the fields match a model, ModelForm saves you most of the work.
# polls/forms.py
from django import forms
from .models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ["question_text"]
def clean_question_text(self):
text = self.cleaned_data["question_text"].strip()
if not text.endswith("?"):
raise forms.ValidationError("A question must end with a question mark.")
return text# polls/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from .forms import QuestionForm
@login_required
def create(request):
if request.method == "POST":
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save()
return redirect("polls:detail", question_id=question.pk)
else:
form = QuestionForm()
return render(request, "polls/create.html", {"form": form})Every POST form needs {% csrf_token %} to pass the CSRF middleware. Redirecting after a save stops a refresh from resubmitting the data.
Sessions keep per-visitor state on the server and store only a session ID cookie in the browser. In a view, request.session behaves like a dictionary.
def add_to_cart(request, item_id):
cart = request.session.get("cart", [])
cart.append(item_id)
request.session["cart"] = cart
return redirect("shop:cart")The authentication system, django.contrib.auth, provides user, group and permission models plus views for login, logout and password changes. Adding path("accounts/", include("django.contrib.auth.urls")) gives you the standard auth URLs. In views, request.user is the current user, and @login_required or LoginRequiredMixin restricts access. For a new project, the official docs recommend creating a custom user model that subclasses AbstractUser and pointing AUTH_USER_MODEL at it from the start, because switching later is very hard.
ForeignKey and friends to relationships.makemigrations records changes and migrate applies them.select_related and prefetch_related cut down the number of queries.django.contrib.auth.
0 comments
Sign in · Sign in to leave a comment.
Be the first to comment.