Доступ к экземпляру модели внутри поля модели

У меня есть модель (Event), которая имеет ForeignKey к модели User (владелец Event). Этот пользователь может приглашать других пользователей, используя следующее поле ManyToManyField:

    invites = models.ManyToManyField(
                  User, related_name="invited_users", 
                  verbose_name=_("Invited Users"), blank=True
              )

Поле приглашения создает простую таблицу, содержащую ID, event_id и user_id.

В случае, если владелец события удалит свой профиль, я не хочу, чтобы событие было удалено, а вместо этого передать право собственности первому пользователю, который был приглашен.

Поэтому я придумал такую функцию:

    def get_new_owner():
        try:
            invited_users = Event.objects.get(id=id).invites.order_by("-id").filter(is_active=True)
            if invited_users.exists():
                return invited_users.first()
            else:
                Event.objects.get(id=id).delete()
        except ObjectDoesNotExist:
            pass

Это находит экземпляр Event и возвращает активных приглашенных пользователей, упорядоченных по ID таблицы Invite, так что я могу получить первый элемент этого набора запросов, который соответствует первому приглашенному пользователю.

Для запуска функции при удалении пользователя, я использовал on_delete=models.SET:

    owner = models.ForeignKey(User, related_name='evemt_owner', verbose_name=_("Owner"), on_delete=models.SET(get_new_owner()))

Затем я столкнулся с некоторыми проблемами:

  1. It can't access the ID of the field I'm passing
  2. I could'n find a way to use it as a classmethod or something, so I had to put the function above the model. Obviously this meant that it could no longer access the class below it, so I tried to pass the Event model as a parameter of the function, but could not make it work.

Есть идеи?

Сначала мы можем определить стратегию для поля Owner, которая будет вызывать функцию с объектом, который был обновлен. Мы можем определить такое удаление, например, в файле <i.app_name/deletion.py:

# app_name/deletion.py

def SET_WITH(value):
    if callable(value):
        def set_with_delete(collector, field, sub_objs, using):
            for obj in sub_objs:
                collector.add_field_update(field, value(obj), [obj])
    else:
        def set_with_delete(collector, field, sub_objs, using):
            collector.add_field_update(field, value, sub_objs)
    set_with_delete.deconstruct = lambda: ('app_name.SET_WITH', (value,), {})
    return set_with_delete

Вы должны передать callable в SET, а не вызвать функцию, поэтому вы реализуете это как:

from django.conf import settings
from django.db.models import Q
from app_name.deletion import SET_WITH

def get_new_owner(event):
    invited_users = event.invites.order_by(
        'eventinvites__id'
    ).filter(~Q(pk=event.owner_id), is_active=True).first()
    if invited_users is not None:
        return invited_users
    else:
        event.delete()


class Event(models.Model):
    # …
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name='owned_events',
        verbose_name=_('Owner'),
        on_delete=models.SET_WITH(get_new_owner)
    )

Таким образом, мы рассмотрим приглашения, чтобы найти пользователя для передачи объекта. Возможно, вам нужно исключить текущее .owner событие в вашем get_new_owner из коллекции .inivites.

Мы можем, как говорит @AbdulAzizBarkat, лучше работать с CASCADE, чем явно удалять объект Event, так как это позволит избежать бесконечной рекурсии, где удаление User вызывает удаление Event, которое может вызвать удаление User: в данный момент это невозможно, но позже, если будет реализована дополнительная логика, можно попасть в такой случай. В этом случае мы можем работать с:

from django.db.models import CASCADE

def SET_WITH(value):
    if callable(value):
        def set_with_delete(collector, field, sub_objs, using):
            for obj in sub_objs:
                val = value(obj)
                if val is None:
                    CASCADE(collector, field, [obj], using)
                else:
                    collector.add_field_update(field, val, [obj])
    else:
        def set_with_delete(collector, field, sub_objs, using):
            collector.add_field_update(field, value, sub_objs)
    set_with_delete.deconstruct = lambda: ('app_name.SET_WITH', (value,), {})
    return set_with_delete

и перепишите get_new_owner на:

def get_new_owner(event):
    invited_users = event.invites.order_by(
        'eventinvites__id'
    ).filter(~Q(pk=event.owner_id), is_active=True).first()
    if invited_users is not None:
        return invited_users
    else:  # strictly speaking not necessary, but explicit over implicit
        return None
Вернуться на верх