Можно ли в Django увидеть, какой метод генерирует SQL-запрос?
При создании пользовательского пользователя (наследуется от AbstractUser
) у нас есть сигнал, который создает рандомизированный пароль (из Django's get_random_string
) и посылает его по электронной почте в задаче celery пользователю:
# signals.py
@receiver(post_save, sender=User, dispatch_uid="c_employee")
def create_employee(sender, instance: User, created, **kwargs):
if not instance.has_usable_password():
password = get_random_string(length=12)
email_args = { # we're collecting information for the celery task
"password": password,
}
email_send_as_task.delay(
email_args, "Sending PASSWORD CREATE email to {{ instance.email }}"
)
if password:
logger.debug(f"CREATE PASSWORD FOR INSTANCE: {instance}")
sender.objects.filter(pk=instance.pk).update(password=make_password(password)) # .update so we don't trigger signal again
Просматривая мои (уровень регистрации DEBUG
) журналы, я вижу следующее:
D0121 18:55:35.434 accounts.signals:81 CREATE PASSWORD FOR INSTANCE: Employee @ Example
D0121 18:55:35.641 django.db.backends:123 (0.000) UPDATE "accounts_user" SET "password" = 'pbkdf2_sha256$260000$FKRktQOZAwQ4OjcvD3QHGn$dmg9T1Y3mEwN1nbI5W2EyOAHp2chU4MGvSlaOTORNxY=' WHERE "accounts_user"."id" = 394; args=('pbkdf2_sha256$260000$FKRktQOZAwQ4OjcvD3QHGn$dmg9T1Y3mEwN1nbI5W2EyOAHp2chU4MGvSlaOTORNxY=', 394)
Пока все хорошо.
Но затем, позже в журналах появляется этот запрос:
D0121 18:55:35.770 django.db.backends:123 (0.015) UPDATE "accounts_user" SET "password" = '', "last_login" = NULL, "is_superuser" = false, "username" = 'employee@example.com', "first_name" = 'First name', "last_name" = 'Employee', "email" = 'employee@example.com', "is_staff" = false, "is_active" = true, "date_joined" = '2023-01-21T17:55:35.044046+00:00'::timestamptz, "company_id" = 20, "venue_id" = 297, "avatar" = 'users/avatar.jpg', "is_admin" = false, "is_developer" = true, "role" = 'event', "allow_update" = true, "device_id" = NULL WHERE "accounts_user"."id" = 394; args=('', False, 'employee@example.com', 'First name', 'Employee', 'employee@example.com', False, True, datetime.datetime(2023, 1, 21, 17, 55, 35, 44046, tzinfo=<UTC>), 20, 297, 'users/avatar.jpg', False, True, 'event', True, 394)
Мой вопрос заключается в следующем: почему пароль переустанавливается в пустую строку? И как мне отладить/понять, откуда исходит этот запрос?
TYIA
Похоже, что мы сталкиваемся с гоночным условием при записи в БД. Похоже, что вызов метода вне сигнала не вызывает другой вызов сигнала post_save
, когда мы уже находимся внутри него.
Вот как выглядит рабочее решение на данный момент:
# accounts/signals.py
from accounts.utils import create_password
@receiver(post_save, sender=User, dispatch_uid="c_employee")
def create_employee(sender, instance: User, created, **kwargs):
if not instance.has_usable_password():
logger.debug(f"CREATE PASSWORD FOR INSTANCE: {instance}")
password = create_password(obj=instance)
email_args = { # we're collecting information for the celery task
"password": password,
}
email_send_as_task.delay(
email_args, "Sending PASSWORD CREATE email to {{ instance.email }}"
)
и в utils.py
файле:
# accounts/utils.py
from django.utils.crypto import get_random_string
def create_password(obj=None, length=12):
password = get_random_string(length=length)
if obj is not None:
obj.set_password(password)
obj.save()
return password
Я вынужден предположить, что есть какая-то магия Django, которую я еще не понял. К сожалению, это не отвечает на вопрос в заголовке, но использование .save()
вместо .update()
решило мою проблему.
Комментарий от @peter-deglopper направил меня в правильном направлении. Спасибо!