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" для простоты использования. Это упростит ваши формы и код.

Вернуться на верх