-
Notifications
You must be signed in to change notification settings - Fork 21
Member creation 3 of 3 ‐ Creating an account
This third step is optional because not every member needs an account. Typically, investing members can't do much on Tapir except look at their information, so we usually don't create an account for them.
The button to create the account is on the member's page (ShareOwnerDetailView
) and links to the CreateUserFromShareOwnerView
.
This view is does not have a form class but instead has the form information built-in with the fields
class variable.
It's code is fairly short but quite a lot is actually happening. The interesting part is inside form_valid()
, which is called once the form has been confirmed valid, so that's when we know we can perform the relevant actions.
A simple summary of what we can directly see happening inside the view:
- Link the ShareOwner to the TapirUser. We can already blank the info fields because their value has been copied in the tapir_user in
get_form_kwargs()
. TapirUser is already created because our view inherits fromCreateView
.
tapir_user = form.instance
share_owner = self.get_shareowner()
share_owner.user = tapir_user
share_owner.blank_info_fields()
share_owner.save()
- Callbacks are called, if any. Callbacks are like events that other apps can register to. They are an attempt to remove dependencies on the shift app inside other apps. You can see where the shift app registers a listener in
tapir.shifts.apps.ShiftConfig
for callback in on_welcome_session_attendance_update:
callback(share_owner)
- Any log that was previously linked to the
ShareOwner
gets linked to theTapirUser
instead.
LogEntry.objects.filter(share_owner=share_owner).update(
user=tapir_user, share_owner=None
)
- An email gets sent to the member:
tapir_user.refresh_from_db()
email = TapirAccountCreatedEmail(tapir_user=tapir_user)
email.send_to_tapir_user(actor=self.request.user, recipient=tapir_user)
A few more things happen when CreateUserFromShareOwnerView
runs that are not directly visible in it's code.
In the shifts's app models.py file, a signal listener is created. There are different signals, this one says "Every time a TapirUser
is created, call the create_shift_user_data()
" function. This way, we can be sure that there always is a ShiftUserData
for each TapirUser
models.signals.post_save.connect(create_shift_user_data, sender=TapirUser)
While signals are sometimes useful (in our case they allow the shift app to do somethig without creating a dependency inside the coop app), they are easy to overlook as illustrated here: nothing in the CreateUserFromShareOwnerView
shows that a ShiftUserData
gets created. So they should only be used when strictly necessary.
Another pitfall with signals is with that they are not always called when you would expect it. Namelly, if we created many TapirUser
s with TapirUser.objects.bulk_create(...)
, our post_save signal would not get called. Similarly, if you update several objects with Model.objects.update(...)
, signals would not get called.
For this we have to notice that TapirUser
inherits from LdapUser
, which overrides the save()
function that comes with Django's Model
.
The library we use to connect to LDAP (django-ldapdb
) allows us to define models such as LdapPerson
and LdapGroup
that behave almost like normal Django models, except that fetching and saving happens relatively to LDAP's database instead of the usual one Django connects to.
It's a bit hidden in tapir.accounts.models.LdapUser.save
but we can see that an LdapPerson object gets created for our user if there is none yet:
ldap_user = LdapPerson(uid=self.username)
This line only creates the object in Django's memory, it is not persited to the database yet. That happens a bit further with ldap_user.save()
At the time of writing the documentation on LDAP is not written yet. In short. LDAP handles user authentificaton outside of Django. This allows us to use the same account for Tapir, the Wiki, Metabase... because they all connect to LDAP
We created the account, but it currently doesn't have a password. The member receives an email (TapirAccountCreatedEmail
) that behaves like a password-reset email. See these lines.