Django - добавление функций геймификации
У меня есть приложение Django REST среднего размера , в которое я хочу добавить функции геймификации
Приложение, о котором идет речь, представляет собой школьную веб-страницу, где ученики могут создавать макеты тестов, участвовать в экзаменах, которые публикуют учителя, и писать дидактические материалы, за которые голосуют другие ученики и учителя.
Я хочу добавить некоторые функции геймификации, чтобы сделать приложение более интересным и стимулировать участие и использование различных функций: например, у каждого студента будет личный счет "репутации", и он будет получать очки при выполнении определенных действий - студент может получить очки при завершении теста с высоким баллом, при отправке некоторого контента или при получении "апдейтов" к этому контенту
Сложность в том, что я хочу, чтобы эта логика была максимально отделена от существующей кодовой базы по различным причинам: разделение проблем, возможность подключать/отключать движок при необходимости, возможность легко деактивировать функции для определенных групп пользователей и т.д.
Что я ищу здесь, так это совет по разработке программного обеспечения, который также специфичен для Django. Вот высокоуровневое описание того, что я думаю сделать - я хотел бы получить совет по подходу.
- создайте новое приложение
gamification
. Здесь у меня будут модели, описывающие изменение репутации пользователя и, возможно, другие связанные с этим события. Приложение также должно отправлять уведомления, когда происходят события, связанные с геймификацией .
- из приложения
gamification
предоставлять интерфейс на основе обратного вызова, который другое основное приложение может вызывать для диспетчеризации событий - используйте пакет django-lifecycle для вызова обратных вызовов из
gamification
при возникновении триггеров.
Таким образом, мои существующие модели будут затронуты только для регистрации триггеров из django-lifecycle (аналогично сигналам). Например, допустим, я хочу начислять студентам баллы, когда они сдают задание. Допустим, у меня есть модель AssignmentSubmission
для обработки заданий. С добавленным крючком жизненного цикла она будет выглядеть следующим образом:
class AssignmentSubmission(models.Model):
NOT_TURNED_IN = 0
TURNED_IN = 1
STATES = ((NOT_TURNED_IN, 'NOT_TURNED_IN'), (TURNED_IN, 'TURNED_IN'))
user = models.ForeignKey(user)
assignment = models.ForeignKey(assignment)
state = models.PositiveSmallIntegerField(choices=STATES, default=NOT_TURNED_IN)
@hook(AFTER_UPDATE, when="state", was=NOT_TURNED_IN, is_now=TURNED_IN)
def on_turn_in(self):
get_gamification_interface().on_assignment_turn_in(self.user)
Метод on_assignment_turn_in
может выглядеть примерно так:
def on_assignment_turn_in(user):
ReputationIncrease.objects.create(user, points=50)
notifications.notify(user, "You gained 50 points")
Это практически только набросок, чтобы дать представление.
Я не знаю, как будет работать get_gamification_interface()
. Должен ли он возвращать синглтон? Может быть, инстанцировать объект? Или возвращать класс со статическими методами? Я думаю, что лучше иметь такой геттер, чем вручную импортировать методы из приложения gamification
, но, возможно, это также может создать слишком много накладных расходов.
Какой хороший способ добавить в проект "подключаемые" функции, которые по своей природе связаны с существующими моделями и бизнес-логикой, но при этом затрагивают их как можно меньше?
Ваша идея была хорошей. В приложении gamification добавьте ваше представление, защитите его с помощью LoginRequiredMixin и расширьте его проверкой, существует ли связанная запись в таблице AssignmentSubmission и включена ли она.
Таким образом, у вас есть 100% разделенные представления геймификации, модели, логика, ecc...
В существующих представлениях можно добавить ссылку на представление геймификации.
Подход с использованием внешнего ключа вполне подходит. Вы можете легко связывать запросы, используя информацию из существующих таблиц, и вы даже можете не трогать исходный код, импортируя свои модели в новое приложение. Вы можете использовать сигналы Django в вашем новом приложении и отказаться от расширения django-lifecycle, чтобы не добавлять строки в ваши основные модели. Я использовал следующий подход для отслеживания измененных записей в таблице; возьмем TrackedModel с полями field_one, field_two, field_n..., которые будут отслеживаться одной из моделей вашего нового приложения, а именно RecordTrackingModel:
from parent_app.models import TrackedModel # The model you want to track from a parent app.
from django.db.models.signals import post_save # I'm choosing post_save just to illustrate.
from django.dispatch import receiver
from datetime import datetime
class RecordTrackingModel(models.Model):
record = models.ForeignKey(TrackedModel, verbose_name=("Tracked Model"), on_delete=models.CASCADE)
field_one = models.TextField(verbose_name=("Tracked Field One"), null=True, blank=True) # Use same field type as original
field_two = models.TextField(("Tracked Field Two"))
field_n = ...
notes = models.TextField(verbose_name=("notes"), null=True, blank=True)
created = models.DateTimeField(verbose_name=("Record creation date"), auto_now=False, auto_now_add=True)
@receiver(post_save, sender=TrackedModel) # Here, listen for the save signal coming from a saved or updated TrackedModel record.
def modified_records(instance, **kwargs):
record = instance
tracked_field_one = instance.field_one
tracked_field_two = instance.field_two
tracked_field_n = another_function(instance.field_n) #an external function that could take a field content as input.
...
note = 'Timestamp: ' + str(datetime.now().isoformat(timespec='seconds'))
track_record = RecordTrackingModel.objects.create(record=record, field_one=tracked_field_one, field_two=tracked_field_two, field_n=tracked_field_n, ..., notes=note)
return track_record
Не нужно добавлять функции в уже существующие модели, так как диспетчер сигналов срабатывает всякий раз, когда в TrackedModel появляется сигнал сохранения или удаления. Затем вы можете разместить операторы "if" для того, чтобы определить, выполнять или нет действия, основанные на значениях полей, т.е.: просто передать, если запись AssignmentSubmission имеет статус "Not Turned In".
Посмотрите Django signals reference для получения дополнительной информации о том, когда они срабатывают.
Дополнительно, я бы предложил изменить поле "state" на тип boolean и переименовать его во что-то более явное, например, "is_turned_in" для простоты использования. Это упростит ваши формы и код.