-
Notifications
You must be signed in to change notification settings - Fork 2
Dev documentation
Inschrijflijst is a web app for the enrolment in activities for study association C.T.S.G. Alembic. This document documents the aims of the project, rough structure of the application and setup of the code.
Inschrijflijst aims to implement the following main features:
- Creation of events ("inschrijflijsten") by chairmen of committees
- Registration by users for these events
- Publicly visible overview of these events by all people, including event info, enrolled people
The frontend makes use of Bootstrap (v3) and jQuery. It is made to be mobile-friendly. Additionally, three Bootstrap plugins are used for specialised inputs: bootstrap-toggle
, bootstrap-datetimepicker
, and bootstrap-tagsinput
. For charts, ChartJS is used and Summernote is used for a WYSIWYG editor.
Here and there, small JS snippets, employing jQuery, are used for dynamic forms.
The backend is written in Python with the Django framework.
The application is built around the MVC (or, as Django calls it, MTV) pattern. The models and relations between them are as follows:
- The central concept is an Event ("inschrijflijst"). An Event is an activity by the study association that people should be able to enroll for. Events are associated by a certain Committee, which is 'owner' of that event.
- Committees can be viewed as user groups. Every Event is associated with the Committee that 'owns' it. Every Committee has exactly one chairman, which is the 'admin' for that Committee and is able to create and edit Events in the name of that Committee. Committees can also have multiple normal members, the purpose of which will be explained later.
- A Registration is a relation between a User and an Event. It records the enrolment of a certain User in an Event, including the relevant dates and extra information (explained later).
- Of course there is a User model, which speaks for itself. However, the default Django user model is swapped with a custom one because of the need of extra fields (
last_seen_at
and a mandatory email field).
An Event can contain a so-called note field. This is an field which meaning is arbitrary and can be set by the creator of the Event. It can be used to record extra information to a registration, such as team names or dietary needs. This can also be a multiple-choice field. The behaviour of this field is controlled by as many as four fields in the Event model:
-
note_field
: Title/description of the field. When this is empty, the field is not used. -
note_field_options
: Available options, in the case of a multiple-choice field. When this is empty, the note field will be open (a simple CharField).note_field_options
is using a custom-written Django field to store a list of strings (which are the options) as a comma-separated string in the database. This can be found inlib/CommaSeparatedStringsField.py
. -
note_field_required
: Determines whether the note field is required to fill in. -
note_field_public
: Determines whether the answers to the fields are visible to everyone on the Event page.
The actual answer to the note field is recorded in Registration.note
.
Note: Django calls controllers views.
Most views in this application are RESTful class-based views. This is implemented by the (generic) ResourceView class, which inherits from BetterView, which is a customised and better, RESTful, class-based view class. ResourceView is basically a wrapper around BetterView for views that directly correspond to a model. ResourceView also comes with ResourceRouter, a class that automatically generates URLConf patterns for ResourceViews.
-
EventView lists, shows, edits, and creates Events. It is a lean view, with most of the code responsible for processing forms residing in the, well, Forms. One exception is
show()
, which contains some logic for determining whether to display the registration form on the deadline of the Event and the registration status. - CommitteeView is a very small, read-only view for listing and showing Committees the user chairs.
-
RegistrationView is a rather large ResourceView that deals with listing, editing, and creating Registrations (showing Registrations is dealt with inside
EventView::show()
).-
index()
does not render a template but is rather used to export a table of Registrations. Currently the only format implemented is CSV. -
store()
at the top level contains code to store new Registrations submitted in various ways ('roles'), hence the if/elif chain.-
cm-committee
refers to the bulk registration of all members of a Committee by the chairman of that Committee. The chairman can do this via the Event page. The function enrolls all members of the Committee viacommittee::enroll()
, a function of the Committee model that handles this. -
cm-admin
refers to the bulk registration of (unrelated) users by the chairman of the Committee that owns the Event. The chairman can do this via the create Registrations page (accessible via the edit Event page). -
user
refers to normal registration (from the Event page) of users by themselves.
-
-
edit()
handles showing the edit Registration page for chairmen. -
update()
handles processing the POSTed form described above, or forms POSTed by users themselves updating their registration from the Event page. -
create()
handles showing the form for bulk registration by chairmen.
-
- StatsView is a view providing an API for statistics used to be used to create graphs on the front-end. Currently it contains only one function that runs a custom SQL query that retrieves the number of registrations per day for an Event from the database and returns the result in JSON.
- AdminView is a view that handles the Settings page. It also contains functions for operations that can be executed on this page, such as the Google Calender integration and forcing an LDAP sync.
- CalenderView is a tiny view that only shows a page with the (linked) Google Calender embedded.
- MailView is a view containing two functions: one for showing the Mass Mail page, and the other for processing the POSTed form on this page and sending the email.
Barring some exceptions, Django Forms are used to render HTML forms as well as process the POSTed data. They map closely to the form fields and are also responsible for any required data processing and validation. Where possible, if the form also closely maps to a model, a ModelForm is employed.
-
EventForm is a ModelForm.
save()
is overridden for two reasons: inserting the event into the calendar and enrolling the Event's committee's members during creation. - MassMailForm is a tiny form for sending mass mails.
-
RegistrationForm is the form shown on an Event's page. It is an form that cannot be implemented as a Django ModelForm because it does not map directly to its model, yet it uses some custom logic to accomplish more of less the same thing by overriding
__init__()
andsave()
. You can pass aninstance
to__init__()
and it will behave in the same way as a ModelForm would, loading the instance's fields in to the form's initial fields. -
RegistrationsForm is a form used for the bulk registration of users. In
clean_username()
it contains code to verify usernames and import users on-the-fly from LDAP if they do not exist in database yet.
URL patterns are defined in urls.py
. Routes to views based on ResourceView are automatically generated by ResourceRouter: the models are first registered using router::register()
, then router::urls()
returns the corresponding URL patterns. Other URL patterns are manually specified as class-based views, with the exception of the (static) FAQ page and the homepage which are simple enough the be implemented as lambda functions.
Inschrijflijst connects to LDAP for importing users and committees. This consists of two mechanisms: user synchronisation through the Django plugin django_auth_ldap
and a custom class LDAPService that syncs application-specific attributes (committees and user-committee relations) using pyldap
directly.
django_auth_ldap
is setup in settings_local.py
. It only needs to know the server address and credentials to connect to the LDAP server, a LDAP path to search, and which LDAP attributes to map to which user fields. That's all, and with that LDAP is used as a secondary authentication backend. This means that if an user tries to login that doesn't exist in the database yet, it will be queried and authenticated through LDAP and inserted into the database. Additionally, on every login the attributes will be synced from LDAP so that email addresses get updated automatically.
The LDAPService is a class for automatically importing (and syncing) committees and their members and chairman from LDAP. Naturally, this is a application- and directory service- specific matter. LDAPService uses LDAP attributes loaned from SALSA, Alembic's membership database.
Inschrijflijst contains a service for handling a connection to Google's Calendar API (GoogleCalendarService). This is for two reasons: embedding a calendar, and inserting events into this calendar.
The GoogleCalendarService uses the FlowService which is a custom wrapper around Google's OAuth2 library. FlowService just encapsulates this OAuth interaction in a self-contained class which makes it nicer to deal with from GoogleCalendarService.
Inschrijflijst sends email messages to users in a couple of cases:
- When someone is enrolled by someone else to an Event.
- When the organisers of an Event use the Mass Mail feature to contact the participants of the Event.
Email is handled by the Mail and Mailer classes, the first being an abstract class for an email message, and the second the class that actually sends Mails. Mailer is a simple wrapper that calls Django Post Office for queueing email messages in later versions of Inschrijflijst. The main advantage of Django Post Office is that messages are sent asynchronously. First of all this avoids problems with rate limits of the SMTP server when many messages are sent at once, and secondly this is better because the user sending the emails does not have to wait for them to be sent before getting a reply from the server.
The interface is completely translated in English and Dutch. This is done through the ubiquitous GNU Gettext system which is supported by Django.