Django - разработка приложения с использованием модульной архитектуры
Я создал систему управления обучением, используя Django REST в качестве бэкенд-приложения. Приложение довольно сильно выросло, как и его пользовательская база, и все больше и больше людей проявляют к нему интерес.
Я понял, что современные приложения LMS имеют нечто общее: модульность - люди могут разрабатывать плагины, расширения и легко добавлять функции. Мое приложение монолитно и не позволяет этого, поэтому я рассматриваю возможность переработки кодовой базы, чтобы сделать ее более открытой для расширения; в частности, я пытаюсь встроить систему плагинов.
Как бы вы подошли к чему-то подобному в Django? Даже какие-то общие мысли, не обязательно специфичные для Django или Python, очень приветствуются.
Я приведу пример функции, которая должна перейти от жесткого кодирования в ядре к чему-то подключаемому.
Рассмотрим следующую модель:
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)
Сейчас типы упражнений жестко закодированы в поле с вариантами ответов. Модель Exercise
имеет некоторые свойства и методы, которые позволяют, например, получить максимально достижимый балл за упражнение или оценить ответ на это упражнение. Эти методы содержат условия, основанные на exercise_type
. Например:
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
# ...
Теперь, допустим, я хочу сделать упражнения по программированию в виде плагина.
Как бы вы подошли к этому?
Первое, о чем я подумал, - это то, что для получения доступных типов упражнений необходим уровень косвенности. Вместо проверки принадлежности к набору вариантов для поля exercise_type
на уровне db, значение должно быть проверено во время выполнения и следовать некоторому соглашению, которое говорит моему приложению искать этот тип внутри плагина, так что, например, где-то должна быть функция load_exercise_types
.
Тогда бизнес-логика должна быть перенесена в место, которое может динамически вызывать правильный код для типа упражнения.
Например, я мог бы создать ExerciseBusinessLogic
абстрактный базовый класс со статическим методом from_model_instance
-- разработчики подключаемых модулей могли бы создать его подкласс и реализовать соответствующие методы (как get_max_score
, которые я показал выше), а экземпляр модели позаботился бы о создании нужного подкласса в зависимости от типа упражнения.
Я взглянул на эту статью, в которой показан пример концепции того, как реализовать простую систему плагинов в Django, но она кажется ограниченной в том, что может сделать такой плагин; здесь я ищу более сложные решения, которые позволили бы добавлять модели, расширять существующие модели новыми функциями и расширять уже существующую основную бизнес-логику, позволяя больше действий, которые интегрируются со всем остальным в приложении.
Как бы вы подошли к решению этой проблемы?
Это зависит от того, имеет ли плагин только логику (и может работать с той же персистентной структурой данных базовой системы) или ему необходимо добавить собственные персистентные поля.
Плагины только для логики
Exercise
будет определять API, которого должен придерживаться любой подключаемый модуль типа упражнения.
Например, этот API требует наличия метода get_max_score
с определенной сигнатурой и значением.
- Тогда подключаемый модуль - это класс, который может жить где угодно.
- Пользователь плагина добавляет этот класс в
EXERCISE_TYPES
настройку Django. Exercise
читает эту настройку и собирает тип, который объявляет каждый из них.- Когда такой подключаемый тип необходим,
Exercise
инстанцирует его и вызывает его функции путем делегирования.
Альтернативно, такие подключаемые типы могут быть просто подклассами Exercise
.
Плагины Logic-plus-persistent-fields
Это более сложная задача.
Самый простой подход, как мне кажется, заключается в том, что подключаемый модуль объявляет свою собственную модель M
для всех дополнительных полей, которые ему требуются, и Exercise
использует GenericForeignKey
для хранения отношений один-к-одному с экземпляром M
.
Затем Exercise
может вызывать соответствующую логику загрузки/хранения по мере необходимости.
По сути, ОП ищет Как писать многократно используемые приложения. Эта страница из Django doc направит его в
превращение нашего веб-опроса в отдельный пакет Python, который можно использовать в новых проектах и делиться с другими людьми.
Если ОП хочет узнать больше о превращении приложения Django в пакет, рассмотрите также статью Making Your Installable Django App.