Недостатки метода сохранения клиента, позволяющего избежать получения пользователем userprofile_id с определенным значением. (Django/Heroku)
В моем Django-проекте мне нужно избежать создания пользователями userprofile_id со значением 3 (3, 13, 203...).
Это временное решение другой проблемы, которое, вероятно, потребует больше времени для достижения результата.
Я разместил код ниже.
Но мой вопрос больше относится к недостаткам этого промежуточного исправления.
Какие проблемы это может породить. Вот список, который я составил, и мои ответы на каждый из них:
- User_id будет отличаться от userprofile_id - не вижу здесь никаких проблем.
- Влияние на целостность базы данных при удалении пользовательского метода - я не вижу, как удаление метода может повлиять на базу данных. Надеюсь, я прав; .
- Некоторые проблемы с производительностью - Сохранение происходит на уровне базы данных, что может вызвать некоторые задержки, я полагаю. Это для пилота+. Я не ожидаю 1000 новых пользователей в день. Могут ли два пользователя, зарегистрировавшиеся в одно и то же время, получить одинаковый userprofile_id? Должно ли быть ожидание, чтобы ограничить возможность тупиковых ситуаций?
код для тех, кому интересно:
class UserProfile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
def save(self, *args, **kwargs):
if not self.pk:
# Logic for generating userprofile_id without '3'
last_userprofile = UserProfile.objects.all().order_by('-id').first()
print(f'last_userprofile: {last_userprofile.id}')
if last_userprofile:
last_id = last_userprofile.id
print(f'last_id: {last_id}')
next_id = last_id + 1
print(f'next_id: {next_id}')
while '3' in str(next_id):
next_id += 1
self.id = next_id
else:
self.id = 1
Ответ 1 Различные идентификаторы пользователей и идентификаторы профилей пользователей не должны иметь значения, если вы следите за ними. На самом деле, мне нравится идея использования другого идентификатора пользователя на бэкенде, который просто представляет собой число, а не идентификатор пользователя, который он создал. Я делаю это на своих сайтах, чтобы использовать файлы cookie, которые защищают конфиденциальность пользователей. Я обращаюсь к ним только по их номеру, никогда не называя их по имени, которое они мне дали.
Ответ 2
Я не вижу никаких проблем с базой данных, но помните, что преимущество Django в том, что мы можем генерировать базу данных SQL с помощью моделей. В производстве нам может понадобиться изменить нашу базу данных для увеличения функциональности или масштаба, но в разработке я рекомендую убедиться, что Django правильно генерирует SQL-таблицу, прежде чем пытаться ее использовать. Поэтому, возможно, убедитесь, что ваша база данных SQL генерируется правильно из новой базы данных при вызове migrate
.
Ответ 3. Ну, строго говоря, программа на Python, скомпилированная с помощью CPython и фактически выполняющаяся как машинный код на языке C, не приведет к тупику процессора. В принципе, эти проблемы решаются на гораздо более низком уровне с помощью конвейеризации и параллелизма.
Вы можете попросить двух пользователей отправить один и тот же идентификатор пользователя одновременно, один из них будет создан первым, а другой столкнется с ошибкой. Обработайте эту ошибку в блоке try/except и используйте HTTPResponseRedirect
, чтобы направить пользователя обратно на страницу, где ему будет предложено повторить попытку с другим идентификатором пользователя.
При использовании вашего решения возможно, что у нескольких пользователей будет один и тот же профиль при одновременном создании пользователей.
Это происходит из-за того, как метод save
решает, делать ли запрос на обновление или вставку. Из документации следует, что Django делает следующее:
- Если атрибут первичного ключа объекта установлен в значение, которое оценивается как True (т.е. значение, отличное от None или пустой строки), Django выполняет UPDATE.
- Если атрибут первичного ключа объекта не установлен или UPDATE ничего не обновил (например, если первичный ключ установлен в значение, которое не существует в базе данных), Django выполняет INSERT.
Учитывая, что вы сами установили первичный ключ, возможно, что:
- Начинается запрос 1 на создание профиля пользователя для одного пользователя.
- Начинается запрос 2 на создание профиля пользователя для другого пользователя.
- Оба получают один и тот же вычисленный идентификатор.
- Один из них заканчивает вставку первым.
- Другой заканчивает обновление только что вставленной строки.
Это приводит к тому, что два пользователя имеют один и тот же профиль. Вы можете обойти эту проблему, заставив Django выполнить вставку, установив аргумент ключевого слова force_insert
в True
:
def save(self, *args, **kwargs):
if not self.pk:
# Logic for generating userprofile_id without '3'
last_userprofile = UserProfile.objects.all().order_by('-id').first()
print(f'last_userprofile: {last_userprofile.id}')
if last_userprofile:
last_id = last_userprofile.id
print(f'last_id: {last_id}')
next_id = last_id + 1
print(f'next_id: {next_id}')
while '3' in str(next_id):
next_id += 1
self.id = next_id
else:
self.id = 1
# Force the save to be an insert, an error is better than two users having same profile
kwargs["force_insert"] = True
Это приведет к ошибке одного из сохранений, но это лучше, чем если бы у двух пользователей был один и тот же профиль.
Примечание: Я не рекомендую делать то, что вы делаете, рассматривайте это только как временное обходное решение, как вы упомянули, и удалите его как можно скорее.