In this article by Aidas Bendoraitis, author of the book Web Development with Django Cookbook - Second Edition we will cover the following topics:
(For more resources related to this topic, see here.)
When the database structure is defined in the models, we need some views to let the users enter data or show the data to the people. In this chapter, we will focus on the views managing forms, the list views, and views generating an alternative output than HTML. For the simplest examples, we will leave the creation of URL rules and templates up to you.
The first argument of every Django view is the HttpRequest object that is usually named request. It contains metadata about the request. For example, current language code, current user, current cookies, and current session. By default, the forms that are used in the views accept the GET or POST parameters, files, initial data, and other parameters; however, not the HttpRequest object. In some cases, it is useful to additionally pass HttpRequest to the form, especially when you want to filter out the choices of form fields using the request data or handle saving something such as the current user or IP in the form.
In this recipe, we will see an example of a form where a person can choose a user and write a message for them. We will pass the HttpRequest object to the form in order to exclude the current user from the recipient choices; we don't want anybody to write a message to themselves.
Let's create a new app called email_messages and put it in INSTALLED_APPS in the settings. This app will have no models, just forms and views.
To complete this recipe, execute the following steps:
# email_messages/forms.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class MessageForm(forms.Form):
recipient = forms.ModelChoiceField(
label=_("Recipient"),
queryset=User.objects.all(),
required=True,
)
message = forms.CharField(
label=_("Message"),
widget=forms.Textarea,
required=True,
)
def __init__(self, request, *args, **kwargs):
super(MessageForm, self).__init__(*args, **kwargs)
self.request = request
self.fields["recipient"].queryset =
self.fields["recipient"].queryset.
exclude(pk=request.user.pk)
# email_messages/views.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from .forms import MessageForm
@login_required
def message_to_user(request):
if request.method == "POST":
form = MessageForm(request, data=request.POST)
if form.is_valid():
# do something with the form
return redirect("message_to_user_done")
else:
form = MessageForm(request)
return render(request,
"email_messages/message_to_user.html",
{"form": form}
)
In the initialization method, we have the self variable that represents the instance of the form itself, we also have the newly added request variable, and then we have the rest of the positional arguments (*args) and named arguments (**kwargs). We call the super() initialization method passing all the positional and named arguments to it so that the form is properly initiated. We will then assign the request variable to a new request attribute of the form for later access in other methods of the form. Then, we modify the queryset attribute of the recipient's selection field, excluding the current user from the request.
In the view, we will pass the HttpRequest object as the first argument in both situations: when the form is posted as well as when it is loaded for the first time.
To make your views clean and simple, it is good practice to move the handling of the form data to the form itself whenever possible and makes sense. The common practice is to have a save() method that will save the data, perform search, or do some other smart actions. We will extend the form that is defined in the previous recipe with the save() method, which will send an e-mail to the selected recipient.
We will build upon the example that is defined in the Passing HttpRequest to the form recipe.
To complete this recipe, execute the following two steps:
# email_messages/forms.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext,
ugettext_lazy as _
from django.core.mail import send_mail
from django.contrib.auth.models import User
class MessageForm(forms.Form):
recipient = forms.ModelChoiceField(
label=_("Recipient"),
queryset=User.objects.all(),
required=True,
)
message = forms.CharField(
label=_("Message"),
widget=forms.Textarea,
required=True,
)
def __init__(self, request, *args, **kwargs):
super(MessageForm, self).__init__(*args, **kwargs)
self.request = request
self.fields["recipient"].queryset =
self.fields["recipient"].queryset.
exclude(pk=request.user.pk)
def save(self):
cleaned_data = self.cleaned_data
send_mail(
subject=ugettext("A message from %s") %
self.request.user,
message=cleaned_data["message"],
from_email=self.request.user.email,
recipient_list=[
cleaned_data["recipient"].email
],
fail_silently=True,
)
# email_messages/views.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from .forms import MessageForm
@login_required
def message_to_user(request):
if request.method == "POST":
form = MessageForm(request, data=request.POST)
if form.is_valid():
form.save()
return redirect("message_to_user_done")
else:
form = MessageForm(request)
return render(request,
"email_messages/message_to_user.html",
{"form": form}
)
Let's take a look at the form. The save() method uses the cleaned data from the form to read the recipient's e-mail address and the message. The sender of the e-mail is the current user from the request. If the e-mail cannot be sent due to an incorrect mail server configuration or another reason, it will fail silently; that is, no error will be raised.
Now, let's look at the view. When the posted form is valid, the save() method of the form will be called and the user will be redirected to the success page.
In this recipe, we will take a look at the easiest way to handle image uploads. You will see an example of an app, where the visitors can upload images with inspirational quotes.
Make sure to have Pillow or PIL installed in your virtual environment or globally.
Then, let's create a quotes app and put it in INSTALLED_APPS in the settings. Then, we will add an InspirationalQuote model with three fields: the author, quote text, and picture, as follows:
# quotes/models.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import os
from django.db import models
from django.utils.timezone import now as timezone_now
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
def upload_to(instance, filename):
now = timezone_now()
filename_base, filename_ext = os.path.splitext(filename)
return "quotes/%s%s" % (
now.strftime("%Y/%m/%Y%m%d%H%M%S"),
filename_ext.lower(),
)
@python_2_unicode_compatible
class InspirationalQuote(models.Model):
author = models.CharField(_("Author"), max_length=200)
quote = models.TextField(_("Quote"))
picture = models.ImageField(_("Picture"),
upload_to=upload_to,
blank=True,
null=True,
)
class Meta:
verbose_name = _("Inspirational Quote")
verbose_name_plural = _("Inspirational Quotes")
def __str__(self):
return self.quote
In addition, we created an upload_to function, which sets the path of the uploaded picture to be something similar to quotes/2015/04/20150424140000.png. As you can see, we use the date timestamp as the filename to ensure its uniqueness. We pass this function to the picture image field.
Execute these steps to complete the recipe:
# quotes/forms.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django import forms
from .models import InspirationalQuote
class InspirationalQuoteForm(forms.ModelForm):
class Meta:
model = InspirationalQuote
fields = ["author", "quote", "picture", "language"]
# quotes/views.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.shortcuts import redirect
from django.shortcuts import render
from .forms import InspirationalQuoteForm
def add_quote(request):
if request.method == "POST":
form = InspirationalQuoteForm(
data=request.POST,
files=request.FILES,
)
if form.is_valid():
quote = form.save()
return redirect("add_quote_done")
else:
form = InspirationalQuoteForm()
return render(request,
"quotes/change_quote.html",
{"form": form}
)
{# templates/quotes/change_quote.html #}
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">{% trans "Save" %}</button>
</form>
{% endblock %}
Django model forms are forms that are created from models. They provide all the fields from the model so you don't need to define them again. In the preceding example, we created a model form for the InspirationalQuote model. When we save the form, the form knows how to save each field in the database as well as upload the files and save them in the media directory.
As a bonus, we will see an example of how to generate a thumbnail out of the uploaded image. Using this technique, you could also generate several other specific versions of the image, such as the list version, mobile version, and desktop computer version.
We will add three methods to the InspirationalQuote model (quotes/models.py). They are save(), create_thumbnail(), and get_thumbnail_picture_url(). When the model is being saved, we will trigger the creation of the thumbnail. When we need to show the thumbnail in a template, we can get its URL using {{ quote.get_thumbnail_picture_url }}. The method definitions are as follows:
# quotes/models.py
# …
from PIL import Image
from django.conf import settings
from django.core.files.storage import default_storage as storage
THUMBNAIL_SIZE = getattr(
settings,
"QUOTES_THUMBNAIL_SIZE",
(50, 50)
)
class InspirationalQuote(models.Model):
# ...
def save(self, *args, **kwargs):
super(InspirationalQuote, self).save(*args, **kwargs)
# generate thumbnail picture version
self.create_thumbnail()
def create_thumbnail(self):
if not self.picture:
return ""
file_path = self.picture.name
filename_base, filename_ext = os.path.splitext(file_path)
thumbnail_file_path = "%s_thumbnail.jpg" % filename_base
if storage.exists(thumbnail_file_path):
# if thumbnail version exists, return its url path
return "exists"
try:
# resize the original image and
# return URL path of the thumbnail version
f = storage.open(file_path, 'r')
image = Image.open(f)
width, height = image.size
if width > height:
delta = width - height
left = int(delta/2)
upper = 0
right = height + left
lower = height
else:
delta = height - width
left = 0
upper = int(delta/2)
right = width
lower = width + upper
image = image.crop((left, upper, right, lower))
image = image.resize(THUMBNAIL_SIZE, Image.ANTIALIAS)
f_mob = storage.open(thumbnail_file_path, "w")
image.save(f_mob, "JPEG")
f_mob.close()
return "success"
except:
return "error"
def get_thumbnail_picture_url(self):
if not self.picture:
return ""
file_path = self.picture.name
filename_base, filename_ext = os.path.splitext(file_path)
thumbnail_file_path = "%s_thumbnail.jpg" % filename_base
if storage.exists(thumbnail_file_path):
# if thumbnail version exists, return its URL path
return storage.url(thumbnail_file_path)
# return original as a fallback
return self.picture.url
In the preceding methods, we are using the file storage API instead of directly juggling the filesystem, as we could then exchange the default storage with Amazon S3 buckets or other storage services and the methods will still work.
How does the creating the thumbnail work? If we had the original file saved as quotes/2014/04/20140424140000.png, we are checking whether the quotes/2014/04/20140424140000_thumbnail.jpg file doesn't exist and, in that case, we are opening the original image, cropping it from the center, resizing it to 50 x 50 pixels, and saving it to the storage.
The get_thumbnail_picture_url() method checks whether the thumbnail version exists in the storage and returns its URL. If the thumbnail version does not exist, the URL of the original image is returned as a fallback.
In this article, we learned about passing an HttpRequest to the form and utilizing the save method of the form.
You can find various book on Django on our website:
Further resources on this subject: