Making my app modular

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?

I’d start by looking at the other frameworks / tools that could be considered similar in their application to what you’re trying to achieve.

You mention you want your system to be “plug-in friendly” but you don’t really define what it is you want to extend from a functionality perspective, so I don’t have any specific recommendations in this area besides looking at things like the Django admin, Wagtail, Django CMS, etc.

Also look at the Django-provided generic CBVs and how they create a class structure to be extended in the application layer.

(I’d also be looking at externalizing data like your EXERCIZE_TYPES to become models, that’s one of the easiest ways to allow for extension and modification.)