I made a learning management system using Django REST as its backend app. The app has grown quite a bit, so has its user base, and more and more people are showing interest in it.
Something I have realized is that the state-of-art LMS applications have something in common: modularity–people can develop plug-ins, extensions, and easily add features. My app is monolithic and doesn’t really allow that, so I’m considering reworking the code base to be more open to extension; especially, I’m trying to fit in a plug-in system.
How would you approach something like that, in Django? Even some general thoughts, not necessarily Django- or Python-specific are very welcome.
I’ll give an example of a feature that would need to move from being hard-coded in the core to being something pluggable.
Consider the following model:
class Exercise(TimestampableModel, OrderableModel, LockableModel): """ An Exercise represents a question, coding problem, or other element that can appear inside of an exam. """ MULTIPLE_CHOICE = 1 OPEN_ANSWER = 2 CLOZE = 3 PROGRAMMING = 4 EXERCISE_TYPES = ( (MULTIPLE_CHOICE "Multiple choice"), (OPEN_ANSWER, "Open answer"), (CLOZE, "Cloze"), (PROGRAMMING, "Programming exercise"), ) course = models.ForeignKey( Course, on_delete=models.PROTECT, related_name="exercises", ) exercise_type = models.PositiveSmallIntegerField(choices=EXERCISE_TYPES) name = models.CharField(max_length=75, blank=True) text = models.TextField(blank=True)
Right now, the types of exercises are hard-coded in a field with choices. The
Exercise model has some properties and methods that allow things like getting the max attainable score for that exercise or to grade an answer to that exercise. Those methods contain conditionals based on the
exercise_type. For example:
def get_max_score(self): if self.exercise_type == Exercise.PROGRAMMING: return self.testcases.count() if self.exercise_type == Exercise.MULTIPLE_CHOICE: max_score = (self.choices.all().aggregate(Max("correctness")))[ "correctness__max" ] return max_score # ...
Now, let’s say I wanted to make programming exercises into a plug-in.
How would you approach this?
The first thing I thought of is there would need to be a layer of indirection for getting the exercise types that are available. Instead of checking for membership to a set of choices for the
exercise_type field at db level, the value would have to be validated at run time and follow some convention that tells my app to search for that type inside of a plug-in, so for example there would be some
load_exercise_types function somewhere.
Then the business logic would have to be moved to a place that can dynamically call into the correct code for the exercise type.
For example, I could create an
ExerciseBusinessLogic abstract base class with a static method
from_model_instance—plug-in developers would subclass it and implement the relevant methods (like the
get_max_score I showed above), and the model instance would take care of instantiating the correct subclass based on the exercise type.
I took a look at this article which shows a proof of concept about how to implement a simple plug-in system in Django, but it seems limited in what such a plug-in can do; here I’m looking for more complex solutions that would allow adding models, extending existing models with new functionalities, and expanding on the already present core business logic enabling more actions that integrate with whatever else is in the app.
How would you approach this problem?