Django cheatsheet - Views
Published: 2022-09-05 11:39:50

View are something that accepts a request, and returns some kind of a response.

In the `render()` function, you can pass a `context` that are variables you can pass to your template.

### how to add a new view

- define the URL in `your-project/your-app/urls.py`
- add a view in `your-projects/your-app/views/new-view.py`
- import the view in `your-projects/your-app/views/__init__.py`

### shortcuts: render, redirect

```python
from django.shortcuts import render, redirect

def index(request):
  students = Students.objects.all()
  context = { "students": students }
  return render("index.html", context)

def new(request):
  # save a new student
  return redirect("your_app:new")
```

Note that you can't pass a context with a `redirect`; you can only pass a URL and query parameters.


### messages

https://docs.djangoproject.com/en/4.1/ref/contrib/messages/#using-messages-in-views-and-templates

If you want to use "messages" (e.g. flash messages, toast messages in other frameworks), you can use the messages framework.

Add this import,

```python
from django.contrib import messages
```

... and call the messages like this in your views.

```python
messages.debug(request, '%s SQL statements were executed.' % count)
messages.info(request, 'Three credits remain in your account.')
messages.success(request, 'Profile details updated.')
messages.warning(request, 'Your account expires in three days.')
messages.error(request, 'Document deleted.')
```

The template can show the messages like this:

```python
{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}
```

It' s important to iterate all the messages, otherwise the messages will not be cleared.

```python
from django.contrib import messages

# view to create new student
def new(request):
  if request.method == "POST":
    # do something with the form 
    student.save()
    # send messages
    messages.success(request, "saved student")
```

### formsets

You can use formsets when you want to handle multiple records of the same model (e.g. multiple students).

Formsets come in handy because they will do the validation on all the records for you.

In your view, you can define your formset like this:

```python
# your_project/your_app/views/new.py

from django.forms import modelformset_factory
from django.shortcuts import redirect, render

from your_app.models import Student

def new(request):
  # fields: defines which fields to show in the form
  # extra: defines how many extra rows you want in the form, on top of existing records
  StudentFormset = modelformset_factory(Student, fields=["last_name", "first_name", "photo"], extra=10)
  
  if request.method == "GET":
    # by default, formset will populate all records in that model
    # specify the set of records to be "no students"
    formset = StudentFormset(query=Student.objects.none())
    context = { "formset": formset }
    return render("your_app/new.html", context)

  if request.method == "POST":
    # populate the formset with user input
    formset = StudentFormset(request.POST, request.FILES)

    if formset.is_valid():
      # save the entire formset
      formset.save()

      # make sure to redirect after a POST request
      return redirect("your_app:new")
```

If you need to specify a foreign key on a formset, you do have to iterate through each record like so:

```python
# your_project/your_app/views/new.py

if formset.is_valid():
  # get the instances via the save method
  # but don't commit yet
  instances = formset.save(commit=False)
  for student in instances:
    student.school_id = 1
    # populate school_id and save one by one
    student.save()
```

In this case, an "atomic transaction" might be needed, where if either all transactions succed, or no transaction succeeds.

You can use the decorator or the `with:` block (please see the documentation for the latter option; I prefer decorators with less indent).

```python
# define a helper function with a decorator
from django.db import transaction

@transaction.atomic
def save_students_with_school(formset, school_id):
  instances = formset.save(commit=False)
  for student in instances:
    student.school_id = school_id
    # populate school_id and save one by one
    student.save()
```

```python
# call them in your view like this

if formset.is_valid():
  # call the atomic transaction
  save_students_with_school(formset, 1)
```