I am working on a Learning Management System using Django. It started fairly simply, with startapp lms, a few models defined in a single models.py file, and a basic Django file structure. Eventually, I had to split my models file using Python subpackages to manage the complexity.
The problem started when my models.py file contained just a few models:
from django.db import models
class Course(models.Model):
...
class Lesson(models.Model):
...
class Quiz(models.Model):
...
class Question(models.Model):
...
class Answer(models.Model):
...As my project grew in size and complexity I found that the models.py, originally concise and manageable, grew into hundreds of lines with multiple semi-related models. For example, at some point I realized that I needed to include a Content model to manage images and videos embedded in the courses. It started simple:
...
class Content(models.Model):
...After a while I realized that are different types of content that the application will use. Around the same time I also realized that I was repeating myself a lot in the LMS models themselves - all of them have similar properties, after all - so I refactored some of the code and created a new file, base_models.py that included BaseModel and BaseContent models.
This worked for a while, but then I also added three sub-classes to models.py: TextContent, ImageContent, and VideoContent. I expanded the models to include student tracking, using UserQuizAttempt and UserLessonProgress. I started to dread opening the models file: a wall of text, a ton of models, and a lot of mental overhead whenever I wanted to make any change to any one given class.
This, of course, is a symptom of increasing complexity.
And because I am in the middle of reading A Philosophy of Software Design I realized that I should embrace one of the first lessons that John Ousterhout teaches: have a zero-tolerance policy towards complexity.
The problem is that having a single models.py file might suggest that things are less complex; a single file, presumably, is less complex than a multitude of files. As the file grows, however, the maintenance of that file becomes a challenge - from locating specific models, managing merge conflicts, and separating logic related to each part of the program.
One solution is to create separate Django apps inside the project, but this creates a rather large series of imports I’ll need all over the place, with models that depend on each other scattered across multiple apps, and a multitude of empty and mostly-empty files. Perhaps this solution works for a large software organization, but in my case I suspect it is overkill.
Python Subpackages
The solution I settled with was, first of all, to remember that Django is still just Python. And because it’s just Python I can use packages to manage my code. I don’t mean creating something and placing it in the PyPi; I mean creating a folder called models, adding separate files that contain logic for each part of the LMS, and then importing all of them in __init__.py:
lms/
├── models/
│ ├── __init__.py
│ ├── base.py
│ ├── choices.py
│ ├── content.py
│ ├── courses.py
│ ├── quizzes.py
│ └── progress.pyTo reference A Philosophy Of Software Design again, this organization allows me to maintain the same interface to the models while hiding away the complexity of the code. Because of the __init__.py file, Python seems the models folder as a package which can be imported - the specifics of the package itself (what classes and such are loaded) are then handled by __init__:
# __init__.py
from .courses import Course, Lesson, Topic
from .quizzes import Quiz, Question, Answer
from .content import TextContent, ImageContent, VideoContent
from .progress import UserLessonProgress, UserQuizAttemptWith this, the rest of the code in the project can continue to make calls to lms.models and import data using from lms.models import Course without having to know that the structure of the models code has changed. This means I don’t have to installed a new app in settings.py, I don’t have to rename models, or really do any extra work. At the same time, I have separate files that deal with each individual part of the LMS components.