Tutorial: Writing a bare bone theme from scratch
Starting with a pricing page
- templates/saas/pricing.html
- displays the plans available for subscription.
The homepage and other static landing pages can be hosted on DjaoDjin, but there is nothing special about them.
The first page interacting with the djaoapp proxy that we will customize is the pricing page.
First we wrap the HTML displaying a single plan into a for
loop,
replacing hardcoded plan title, price amount and description with their actual
value taken form the database.
+{% for plan in plan_list %} +<div> + <h2>{{plan.title}}</h2> + <h2>{{plan.period_amount}}</h2> + <p>{{plan.description}}</p> +</div> +{% endfor %}
With a few plans created for our site, we see they show up on the pricing page. The prices are displayed in cents and the description are not HTML formatted. In order to fix that, we add some filters.
+{% for plan in plan_list %} <div> <h2>{{plan.title}}</h2> + <h2>{{plan.period_amount|htmlize_money}}</h2> + <p>{{plan.description|md}}</p> </div> {% endfor %}
It looks good now! It is time to add a subscribe button so users can subscribe to a plan.
+<form method="post" action="."> + <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> {% for plan in plan_list %} <div> <h2>{{plan.title}}</h2> <h2>{{plan.period_amount}}</h2> <p>{{plan.description}}</p> + <button type="submit" name="submit" value="{{plan.slug}}">Subscribe</button> </div> {% endfor %} +</form>
Registering the first user
Automatically when visitors click subscribe on a plan, they are redirected
to the registration page
(see Workflows: Checkout).
We will thus create a template page, i.e. accounts/register.html
.
Registering a user is a simple form asking for at least an email
and password
. A few other optional information can also be asked
on registration.
First, as with all other form pages, we make sure the form declares
method="post"
and action="."
, as well
as passing the authenticity token back to the server by adding
<input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
.
Many times, registration and login pages are inserted into a page flow,
moving from a public page to a page that requires an authenticated user.
It would be very annoying to a user that provides credentials to be
redirected to a standard landing page instead of the page she intended
to reach when she clicked on a link on the public page.
The page context always contains the intended redirect url as variable
next
. We just need to append it to the action.
+<form method="post" action=".{% if next %}/?next={{next}}{% endif %}"> + <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> + <input class="btn btn-primary" name="commit" type="submit" value="Sign up"> +</form>
Inside the form, we create input fields for email, first name, last name, password and password confirmation.
<form method="post" action="."> <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> + <input name="email" type="email"> + <input name="first_name" type="text"> + <input name="last_name" type="text"> + <input name="password" type="password"> + <input name="password2" type="password"> <input class="btn btn-primary" name="commit" type="submit" value="Sign up"> </form>
When we try this form, we quickly realize that when something wrong happens
on submit, we get back a blank form. No error message is displayed. This is
a little inconvienient. We will add some HTML at the top of the
<body>
to display error messages back to the user.
+<div class="messages"> +{% for message in messages %} + <p class="{{message.tags}}">{{message}}</p> +{% endfor %} +</div>
We also add code to display field specific errors. For example, here is how errors related to the e-mail field are displayed back to the user.
<form method="post" action="."> <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> <input name="email" type="email"> +{% for error in form.email.errors %} + <span>{{error}}</span> +{% endfor %}
At this point, errors when the form is submitted are displayed back to the user. The form fields are blank though. We would like users to avoid re-typing correct information they already entered. In order to do that we add the following code:
... <form method="post" action="."> <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> - <input name="email" type="email"> + <input name="email" type="email" {{form.email|value_attr}}> {% for error in form.email.errors %} <span>{{error}}</span> {% endfor %}
Going forward, all forms can be built on the same model.
Email notifications
To communicate with the user through e-mail, the system sends notifications. Email templates are structured in 3 blocks:
- subject
- html_content
- plain_content (optional)
The subject
block contains the subject line of the e-mail.
The html_content
block contains the content of the e-mail, HTML
formatted. The plain_content
block contains the content of
the e-mail in plain text. In case there are no plain_content block, the system
will create it as a stripped down version of the html_content block.
The first e-mail notification we will customize is the password reset e-mail.
- notification/password_reset.eml (example)
- Email sent to a user with a clickable link to reset her password.
Here in its bare bone version with a subject
and
html_content
blocks.
{% block subject %} Reset your password on example.com {% block html_content %} Please reset your password on example.com by visiting the following url: {{back_url}} Link is valid for {{expiration_days}} days. {% endblock %}
Here the context contains back_url
, the URL generated
by the system to reset a user password. The expiration_days
contains the number of days the link is valid.
Checkout pipeline pages
- saas/billing/cart.html
- saas/billing/cart-periods.html
- saas/billing/cart-seats.html
- Pages to enter a credit card, number of pre-paid periods, etc.
Once a user can register and do the basic login/logout operations and our pricing page is up and running, we move on to the checkout pipeline. The checkout pipeline can be as simple as a form to enter a credit card number or as complex as a multi-page experience with multi-periods at a discount and group billing.
Customizing the saas/billing/cart.html template consists of adding a form with purposefully named input fields, and including a saas/_payment_head.html and saas/_payment_body.html boiler plate in the <head> and <body> nodes respectively.
The minimum form should look like:
<form id="payment-form" method="post" action=".{% if next %}/?next={{next}}{% endif %}"> <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}"> <div id="card-use"> <!-- BE CAREFULL: Do not add name="" to the #card-number, #card-cvc, #card-exp-month, #card-exp-year input nodes, else values will hit the server and break PCI compliance. --> <div> <input id="card-number" type="text" autocomplete="off" /> </div> <div> <input id="card-cvc" type="text" autocomplete="off" /> </div> <div> <input id="card-exp-month" type="text" autocomplete="off" /> </div> <div> <input id="card-exp-year" type="text" autocomplete="off" /> </div> <div> <input name="card_name" type="text" /> </div> <div> <input name="card_address_line1" type="text" /> </div> <div> <input name="card_city" type="text" /> </div> <div> <input name="region" type="text" /> </div> <div> <input name="card_address_zip" type="text" /> </div> <div> <input name="country" type="text" /> </div> </div> </form>
We then add the following boiler plate code in the <head> node:
<script src="//code.jquery.com/jquery-1.11.1.min.js" type="text/javascript"></script> <meta name="csrf-token" content="{{csrf_token}}"> {% include "saas/_payment_head.html" %}
We add the following boiler plate code in the <body> node:
{% include "saas/_payment_body.html" %}
Of course, to display error messages, we follow a similar pattern as seen earlier in: Registering the first user.
For simplicity and consistency, both saas/billing/cart-periods.html and saas/billing/cart-seats.html can be directly derived from the saas/billing/cart.html. All three templates share the same context.
Charge Receipt
- saas/billing/receipt.html
- HTML page displayed once the charge is confirmed.
- saas/printable_charge_receipt.html
- PDF printable version of the charge confirmation.
- notification/charge_receipt.eml
- Email sent to the user to confirm the charge.
Once the checkout pipeline is completed, a charge on Stripe and receipt are generated. Typically we want to add a link on the charge receipt so that a user can directly browse to the next part of the site that makes sense in the on-boarding.