Поддерживать уникальность свойства в базе данных NDB
Модель NDB содержит два свойства: email
и password
. Как избежать добавления в базу данных двух записей с тем же email
? NDB не имеет опции UNIQUE для свойства, как это делают реляционные базы данных.
Проверка того, что новый email
отсутствует в базе данных до добавления — не удовлетворит меня, потому что два параллельных процесса могут одновременно выполнять проверку, и каждый из них добавляет те же email
.
Я не уверен, что транзакции могут помочь здесь, я испытываю такое впечатление после прочтения некоторых руководств. Может быть, синхронные транзакции? Означает ли это одно за раз?
Ответы
Ответ 1
Создайте ключ объекта по электронной почте, затем используйте get_or_insert, чтобы проверить, существует ли.
Также читайте о ключах, сущностях и моделях
#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()
#Insert unique
a = Person.get_or_insert(email)
или если вы хотите просто проверить
#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()
#Check if it added
new_key_a =ndb.Key(Person, email);
a = new_key_a.get()
if a is not None:
return
Позаботьтесь. Изменение электронной почты будет очень сложно (нужно создать новую запись и скопировать все записи в новый родитель).
Для этого возможно, вам нужно сохранить электронное письмо в другом объекте и предоставить ему родительский элемент.
Другой способ - использовать транзакции и проверить свойство электронной почты. Транзакция работает так: во-первых, это первая победа. Концепция, которая означает, что если 2 пользователя проверяют электронную почту, только первая (удачливая) будет успешной, поэтому ваши данные будут согласованы.
Ответ 2
Возможно, вы ищете модуль проверки подлинности webapp2, который может справиться с этим для вас. Его можно импортировать следующим образом import webapp2_extras.appengine.auth.models
. Посмотрите здесь для полного примера.
Ответ 3
Я также столкнулся с этой проблемой, и решение выше не решило мою проблему:
- сделать это ключ был неприемлем в моем случае (мне нужно, чтобы свойство было изменчивым в будущем)
- Использование транзакций в свойстве электронной почты не работает AFAIK (вы не можете делать запросы по не-ключевым именам внутри транзакций, поэтому вы не можете проверить, существует ли почта уже).
В результате я создал отдельную модель без свойств и уникальное свойство (адрес электронной почты) в качестве имени ключа. В основной модели я храню ссылку на модель электронной почты (вместо сохранения электронной почты в виде строки). Затем я могу сделать "change_email" транзакцию, которая проверяет уникальность, просматривая электронную почту по ключу.
Ответ 4
Это то, с чем я столкнулся, и я остановился на вариации решения @Remko. Моя основная проблема с проверкой существующего объекта с данным адресом электронной почты - это потенциальное состояние гонки, как указано. Я добавил отдельную модель, которая использует адрес электронной почты в качестве ключа и имеет свойство, содержащее токен. Используя get_or_insert
, маркер возвращаемых объектов может быть проверен на переданном токене, и если они совпадают, тогда была вставлена модель.
import os
from google.appengine.ext import ndb
class UniqueEmail(ndb.Model):
token = ndb.StringProperty()
class User(ndb.Model):
email = ndb.KeyProperty(kind=UniqueEmail, required=True)
password = ndb.StringProperty(required=True)
def create_user(email, password):
token = os.urandom(24)
unique_email = UniqueEmail.get_or_insert(email,
token=token)
if token == unique_email.token:
# If the tokens match, that means a UniqueEmail entity
# was inserted by this process.
# Code to create User goes here.
# The tokens do not match, therefore the UniqueEmail entity
# was retrieved, so the email is already in use.
raise ValueError('That user already exists.')
Ответ 5
Я реализовал общую структуру для управления уникальными свойствами. Это решение может использоваться для нескольких видов и свойств. Кроме того, это решение является прозрачным для других разработчиков, они используют методы и методы NDB как обычно.
1) Kind UniqueCategory: список уникальных свойств для группировки информации. Пример:
‘User.nickname
2) Kind Уникальный: он содержит значения каждого уникального свойства. Ключ - это собственное значение свойства, которое вы хотите контролировать. Я сохраняю urlsafe основной сущности вместо ключа или key.id(), потому что это более практично, и у него нет проблемы с родителем, и он может использоваться для разных видов. Пример:
parent: User.nickname
key: AVILLA
reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)
3) Добрый пользователь: например, я хочу управлять уникальными значениями для электронной почты и псевдонима. Я создал список, называемый уникальностью с уникальными свойствами. Я перезаписал метод, поставленный в транзакционном режиме, и я написал hook_post_delete_hook, когда один объект удален.
4) Исключение ENotUniqueException: настраиваемый класс исключений, возникающий при дублировании некоторого значения.
5) Проверка процедуры: проверьте, дублируется ли значение.
6) Процедура delete_uniqueness: удалить уникальные значения при удалении основного объекта.
Любые советы или улучшения приветствуются.
class UniqueCategory(ndb.Model):
# Key = [kind name].[property name]
class Unique(ndb.Model):
# Parent = UniqueCategory
# Key = property value
reference_urlsafe = ndb.StringProperty(required=True)
class ENotUniqueException(Exception):
def __init__(self, property_name):
super(ENotUniqueException, self).__init__('Property value {0} is duplicated'.format(property_name))
self. property_name = property_name
class User(ndb.Model):
# Key = Firebase UUID or automatically generated
firstName = ndb.StringProperty(required=True)
surname = ndb.StringProperty(required=True)
nickname = ndb.StringProperty(required=True)
email = ndb.StringProperty(required=True)
@ndb.transactional(xg=True)
def put(self):
result = super(User, self).put()
check_uniqueness (self)
return result
@classmethod
def _post_delete_hook(cls, key, future):
delete_uniqueness(key)
uniqueness = [nickname, email]
def check_uniqueness(entity):
def get_or_insert_unique_category(qualified_name):
unique_category_key = ndb.Key(UniqueCategory, qualified_name)
unique_category = unique_category_key.get()
if not unique_category:
unique_category = UniqueCategory(id=qualified_name)
unique_category.put()
return unique_category_key
def del_old_value(key, attribute_name, unique_category_key):
old_entity = key.get()
if old_entity:
old_value = getattr(old_entity, attribute_name)
if old_value != new_value:
unique_key = ndb.Key(Unique, old_value, parent=unique_category_key)
unique_key.delete()
# Main flow
for unique_attribute in entity.uniqueness:
attribute_name = unique_attribute._name
qualified_name = type(entity).__name__ + '.' + attribute_name
new_value = getattr(entity, attribute_name)
unique_category_key = get_or_insert_unique_category(qualified_name)
del_old_value(entity.key, attribute_name, unique_category_key)
unique = ndb.Key(Unique, new_value, parent=unique_category_key).get()
if unique is not None and unique.reference_urlsafe != entity.key.urlsafe():
raise ENotUniqueException(attribute_name)
else:
unique = Unique(parent=unique_category_key,
id=new_value,
reference_urlsafe=entity.key.urlsafe())
unique.put()
def delete_uniqueness(key):
list_of_keys = Unique.query(Unique.reference_urlsafe == key.urlsafe()).fetch(keys_only=True)
if list_of_keys:
ndb.delete_multi(list_of_keys)