I am working on an enterprise LMS powered by Django REST framework.
Authentication is done via Google OAuth 2.0 using the package drf-social-oauth2, and the target organizations for my software are schools and universities.
Some models have foreign keys to the user model; however, due to the nature of my application, oftentimes a user may want to reference a user that isn’t present in the database yet. This happens because users are created in the database upon their first login in the application via OAuth, yet the person who wants to create a specific model instance referencing another user may want to do so before they’ve logged in for the first time: for example, a teacher may want to pre-enroll a list of students into their new course, but those users might not have logged in for the first time yet, and therefore might not exist in the database.
I’ll give a concrete example with a model in my application:
class UserCoursePrivilege(models.Model):
"""
Represents the administrative permissions a user has over a course.
See logic.privileges.py for the available permissions.
"""
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="privileged_courses",
)
course = models.ForeignKey(
Course,
on_delete=models.CASCADE,
related_name="privileged_users",
)
allow_privileges = models.JSONField(default=list, blank=True)
deny_privileges = models.JSONField(default=list, blank=True)
This object is created in the frontend by accessing a table which shows all registered users and allows turning on switches that correspond the specific permissions for that user.
More than once have I found myself in the situation in which a teacher would email me telling me they couldn’t find their colleague to add their permissions for a course, and I would tell them to have them log in first and then come back to find the in the user table.
However, this isn’t very user-friendly and somehow counterintuitive considering that my application doesn’t provide an explicit user creation process, so the mental model for users is that their account somehow “already exists” and they just need to sign in.
I’m looking for a way to handle this in as transparent way as possible.
The target user experience is something like this: if the user cannot find the person they want to create the object for, the interface shows them a banner like “Can’t find the person you’re looking for?” and allows them to type in the email address of that person and proceed like normal (in the example above, that would entail showing them all the toggles to select permissions to grant).
Then, an instance of the correct model would be created, but with a null foreign key to user.
Then I would have a model that looks like this:
class PendingModelInstance(models.Model):
"""
Represents a model instance which references a user that doesn't exist yet
"""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.TextField()
content_object = GenericForeignKey("content_type", "object_id")
email_address = models.TextField()
an instance of this would be created referencing the “partial” instance with the missing FK and with the email address of the user.
Then, upon user creation, a query is made to retrieve all instances of PendingModelInstance
which have the email of the newly created user, their referenced models are then retrieved and updated with a FK to the new user instance.
This approach seems like it could work fine, but it introduces an issue I don’t really like: it makes foreign keys nullable, which they don’t need to be and shouldn’t be.
Can you think of a better alternative? Have you ever faced this kind of situation?