Advanced Customization¶
Customizing the Registration Form¶
Since all danceschools operate somewhat differently, it is common for schools to wish to collect custom information during the registration process. By default, this project’s registration process proceeds in three steps:
- Choose the classes/events that you wish to register for
- Enter your contact information, any voucher codes that you wish to use, etc.
- Finalize your payment (using Paypal’s pay now functions, or by submitting information in a door registration)
Most of the time, when a studio wants to customize the information that they collect, they wish to do so in step 2. So, this project has been designed to make it relatively easy to do this, using the power of Python’s class inheritance.
Before proceeding, if you are unfamiliar with Django (or with object-oriented programming), you will need to understand the meaning of a couple of terms:
- A class is a generic type of object, which you can often think of
as representing a type of real world object. Classes can contain
properties (e.g. if we had a Dancer class, it could have a property
defaultRoles
that provides a list of roles that the dancer dances, such as “Lead” and “Follow”) as well as methods, which are, in essence, functions within the class that define ways of interacting with the class (e.g. our Dancer class could have a methodaskToDance()
that responds with either “Yes” or “No” depending on whatever logic we want to implement). - An instance of a class represents one object within the class. So,
each dancer in a ballroom might be associated with one instance of a
Dancer class. Properties are stored for each instance. So, for
example, one Dancer instance might have only “Follow” in
defaultRoles
, while another might have both “Lead” and “Follow.” - A Form refers to the class which defines which fields are displayed, how they are displayed, and how they should be validated.
- A View refers to the class or function which decides what is displayed when a request is made, including (for example), the displaying of form. In the case of a page displaying a form, it also determines what should be done when a form is valid.
- A Model refers to the class which is used to define a specific piece of data (like a row in a table representing a Registration, for example).
One last very important thing: classes can inherit from other classes. So, for example, if I wanted to create a DanceCompetitor class, with properties and methods that are specific to competitors, I wouldn’t need to redefine all of the properties and methods associated with a DanceCompetitor. I could, instead, have the DanceCompetitor class inherit those things from the Dancer class. In that case, all DanceCompetitor instances would also be Dancer instances, while not all Dancer instances would necessarily be DanceCompetitor instances.
Now that we have that out of the way, here are the steps to customizing
your registration form. These should all be added to your custom
application, and that application must be listed before the
danceschool.core
app under INSTALLED_APPS
.
Subclass the RegistrationContactForm (located in
danceschool.core.forms
) to create your own custom form in its place.The RegistrationContactForm class, like several other forms in this project, uses the app django-crispy-forms to make it easier to customize functionality and display. So that you do not need to re-specify all of the fields in the form, the RegistrationContactForm conveniently provided three methods,
get_top_layout()
,get_mid_layout()
, andget_bottom_layout()
, each of which provides a django-crispy-forms Layout object that includes the fields in that portion of the form. So, for example, if I want to add a new field called “favoriteDancer” to the bottom portion of the form, I can simply override the methodget_bottom_layout()
as follows:from django import forms from danceschool.core.forms import RegistrationContactForm class MyCustomForm(RegistrationContactForm): favoriteDancer = forms.CharField(label='Name Your Favorite Dancer', required=False) def get_bottom_layout(): layout = super(MyCustomForm,self).get_bottom_layout() layout.append('favoriteDancer') return layout
Additional details on working with Django-crispy-forms for form customization can be found in its documentation on Layouts.
In your app’s
urls.py
, override the default URL for the viewgetStudentView
to use the newly-created form. For example, if the registration contact form is normally found at the url /register/getinfo/, then you can add the following to your app’surls.py
:from django.conf.urls import url from danceschool.core.classreg import StudentInfoView from .forms import MyCustomForm urlpatterns = [ # This should override the existing student info view to use our custom form. url(r'^register/getinfo/$', StudentInfoView.as_view(form_class=MyCustomForm), name='getStudentInfo'), ]
That’s it!
But what happens to the data from my custom form field?¶
In anticipation of the fact that many dance schools need to ask custom questions at registration time, the TemporaryRegistration and Registration models have a field called data which can hold arbitrary form data from the registration process. The contents of the data field are serialized into a JSON object, so the data are stored as a set of key-value pairs. By default, any additional data that you collect during the registration process will be saved to the data field of the associated TemporaryRegistration. When that customer has completed their payment, then the data are transferred to the Registration object as well.
Processing custom fields in the registration form using built-in signals¶
When a TemporaryRegistration is created (right before the user is given options for payment), and when a Registration is finalized after payment has been processed, the registration system sends a Signal, which can be handled by your own custom signal handlers to do further processing based on the data.
For example, suppose that you have some mailing list functionality in a
separate app, and when a registration is complete, you want to see
whether they checked the box requesting to be added to the mailing list,
so that you can add them to the mailing list. In your custom app, define
a signal handler that listens for and receives signals from the
post_registration
signal. That signal will automatically pass the
finalized registration information to your handler function, and from
there, you can proceed to sign the user up for the mailing list if they
requested it.
For more details on Django signals and signal handlers, see the Django documentation.
Adding a Custom Payment Processor¶
The danceschool project supports two of the most popular online payment processors (Paypal and Stripe). It also contains basic functionality for keeping track of cash payments. However, depending on your location, you may want or need to accept online payments from other payment processors.
Since payment processors vary in the way that they handle transactions from
websites, this documentation cannot provide comprehensive instructions.
However, this document provides a starting point for understanding how to
implement a custom payment processor. If you are attempting to do this,
it is highly recommended to look at the code for the existing payment processor
apps, danceschool.payments.paypal
and danceschool.payments.stripe
, to
see how they work.
The PaymentRecord Model¶
The danceschool.core
app provides a PaymentRecord model that is designed
to be a polymorphic model using the
Django-polymorphic <http://django-polymorphic.readthedocs.io/en/stable/>
app. It provides several key fields that are common to all payment
processors:
- A foreign key relationship to the Invoice model (since payments are associated with invoices).
- Creation date and modification date fields
- Several methods and descriptive properties that may need to be overridden
on a per-payment processor basis, such as the
refundable
property to indicate whether a payment is refundable, and therefund()
method to actually process a refund.
To create a new payment processor, first create a new model in your app’s
models.py
that simply subclasses the danceschool.core.models.PaymentRecord
model.
Because of the way Django-polymorphic works, your payments will now be recognized just as
payments from other payment processors.
Add any fields that your particular payment processor may need (for example some kind of transaction identifier field). Then, be sure to override the following from the parent model:
- The
refundable
property (decorated method): defaults to False. This can usually just return True if your payment method is refundable. - The
recordId
property (decorated method). This will usually just return the identifier used by the payment processor, but since different payment processor apps must store this information differently,recordId
ensures that the information is always available to the parent app. - The
methodName
property (decorated method). This just returns a readble name for the type of payment processor used, such as “Paypal Express Checkout” or “Stripe Checkout.” - The
netAmountPaid
property (decorated method). This should return the amount that was paid net of any refunds. - The
refund()
method. If your API allows refunds of transactions, this should be handled here. Anamount
parameter should be accepted to permit partial refunds. - The
getPayerEmail()
method. This method should return the email address of the person who paid, in case they need to be contacted. Many payment processors store this information automatically, but if yours does not, then you can potentially create a model field to store it.
Payment Processor Views¶
Your payment processor will need to define a view that receives data from the processor’s website. The view also needs to do the following:
- Determine whether a payment is being made on a Temporary Registration or an existing Invoice.
- If a payment is being made on a Temporary Registration, then it needs to create a new Invoice using
the
get_or_create_from_registration()
class method of the Invoice class. - If the payment is successful, then your view should call the
processPayment()
method of the associated invoice to record that the payment has been made. TheprocessPayment()
method will handle finalizing the registration if applicable, sending the appropriate email notifications, etc. - Your view will either need to return an
HttpResponseRedirect()
to an appropriate success URL, or your plugin template will need to use Javascript to redirect the user after a successul payment is made (or notify the user if a payment is unsuccessful). - Additional steps may be necessary if you intend to use your payment processor to allow customers to purchase gift certificates (as the Paypal and Stripe apps allow).
It is highly recommended that you follow along with the views.py
of the existing payment processors to
be sure that you follow the appropriate steps.
Creating a CMS Plugin¶
To add a checkout button for your payment processor, you will need to create a CMS plugin that can
be added to the page Placeholder where checkout happens. To do this, create a file in your app called
cms_plugins.py
, and define a new plugin here as a class that inherits from cms.plugin_base.CMSPluginBase
.
Within the plugin class, specify template the contains whatever is needed for your payment processor, and use
a custom render()
method to add any additional context data that may be needed for your page.
The existing payment processor apps are a good resource for understanding how to implement one of these plugins. It is also a good idea to read the Django CMS documentation <http://docs.django-cms.org/en/release-3.4.x/how_to/custom_plugins.html> to learn more.
Once you have created your CMS plugin, you will need to manually add it to the “Registration Summary” page. To do so, follow these steps:
- Log in as a user with appropriate permissions to edit pages and other CMS content (the superuser is fine)
- Proceed through the first two pages of the registration process. Entering fake information is fine, as you will not be completing this registration.
- When you get to the registration summary page, click the button in the toolbar labeled “Edit Page,” then choose “Structure” mode to edit the layout of the page.
- You will see a placeholder for the payment button, called “Registration_Payment_Placeholder”. Click the plus sign (+) next to this placeholder to add a plugin, and from the available plugins choose your payment processor’s plugin.