Chapter 11 ■ the World Wide Web
209
def make_payment_views(payments, username):
for p in payments:
yield {'dollars': p.dollars, 'memo': p.memo,
'prep': 'to' if (p.debit == username) else 'from',
'account': p.credit if (p.debit == username) else p.debit}
@require_http_methods(['GET'])
@login_required
def index_view(request):
username = request.user.username
payments = Payment.objects.filter(Q(credit=username) | Q(debit=username))
payment_views = make_payment_views(payments, username)
return render(request, 'index.html', {'payments': payment_views})
@require_http_methods(['GET', 'POST'])
@login_required
def pay_view(request):
form = PaymentForm(request.POST or None)
if form.is_valid():
payment = form.save(commit=False)
payment.debit = request.user.username
payment.save()
messages.add_message(request, messages.INFO, 'Payment successful.')
return redirect('/')
return render(request, 'pay.html', {'form': form})
@require_http_methods(['GET'])
def logout_view(request):
logout(request)
return redirect('/')
The big question you should ask is, where is the cross-site scripting protection? The answer is that it was
automatically added to settings.py and turned on when I asked Django to build the skeleton for this application
with the manage.py startapp command!
Without your even having to know that CSRF protection exists, your forms will refuse to work unless you
remember to add {% csrf_token %} to your form template. And if you forget, the Django error message displayed
by its runserver development mode explains the requirement. This is an extremely powerful pattern for new web
developers who do not understand the issues at stake: the Django default will generally keep them safe from the most
common catastrophic errors with forms and fields, in a way that microframeworks rarely match.
The views in this application are conceptually simpler than their equivalents in the Flask-powered listings
because this code leans on built-in Django features for almost everything, instead of having to implement things such
as login and session manipulation. The login page does not even appear because urls.py simply uses Django’s. The
logout page can just call logout() and not worry about how it works. Views can be marked with @login_required and
skip having to worry about whether the user is logged in.
The only helper that corresponds directly to a similar feature in our Flask application is the @requirehttp
methods() decorator, which is giving you the same protection against invalid or unsupported HTTP methods that
Flask gave you built in to its own view decorators.
Working with the database is now beautifully simple. The bank.py module with its SQL has disappeared entirely.
Django has already chosen to set up a SQLite database—that is one of the defaults already present in settings.py—and
it is ready to open a session to the database the moment that the code queries the model class from the models.py file.
It is also calling COMMIT automatically when the code calls save() on a new Payment because the code has not asked
Django to open an extended database transaction for you.