Samuele Mattiuzzo
Samuele Mattiuzzo

Reputation: 11038

Ndb models: assure uniqueness in datastore using custom key_name

I'm trying to mimic Django's unique_together feature, but I can't seem to get it straight

class MyClass(ndb.Model):
    name = 'name'
    surname = 'surname'
    phone = 'phone'

    def get_unique_key(self):
        return self.name + "|" + self.surname + "|" + self.phone

"Yeah, pretty easy" NOT

According to the accepted answer in this post, simply assigning the id param in the obj constructor was enough. But I don't want to handle that in a view. Ideally, I'd do this:

object = MyClass()
object = object.custom_populating_method(form.cleaned_data)
object.id = object.get_unique_key()
object.put()

Or even better, place that in a _pre_put_hook, so that the id would be set as last thing before saving (and maybe do some checking enforcing the uniqueness of the data across the datastore).

Apparently, I was wrong. The only way to achieve this is by hacking the view:

unique_id = "|" + form.cleaned_data['bla'] + "|" + form.cleaned_data ...
object = MyClass(id=unique_id)

which is awful and completely wrong (since every change to the model's requirements needs to be inspected also in the views). Plus, I'd end up doing a couple of ugly calls to fetch some related data. I've spent too much time, probably, on this problem to see an exit and I really hope I'm missing something obvious here, but I can't really find any good example nor proper documentation around this subject. Has anyone any hint or experience with something similar?

tl;dr: is there a nice way to achieve this without adding unnecessary code to my views?

(I'm "using" Django and ndb's Models on the Datastore)

Thanks

Upvotes: 2

Views: 205

Answers (2)

Tim Hoffman
Tim Hoffman

Reputation: 12986

Use a factory or class method to construct the instance.

class MyClass(ndb.Model):
    name = ndb.StringProperty()
    surname = ndb.StringProperty()
    phone = ndb.StringProperty()

    @staticmethod
    def get_unique_key(name,surname,phone):
        return '|'.join((name,surname,phone))

    @classmethod
    @transactional
    def create_entity(cls,keyname,name,surname,phone):
        key = ndb.Key(cls, cls.get_uniquekey())
        ent = key.get()
        if ent:
             raise SomeDuplicateError() 
        else:
            ent = cls(key=key, name=name,surname=surname,phone=phone)
            ent.put()

newobj = MyClass.create_entity(somename, somesurname, somephone)

Doing it this way allows you to also ensure the key is unique by creatin the key and tring to fetch it first.

Upvotes: 2

Jamie Niemasik
Jamie Niemasik

Reputation: 755

I think you can do it if you assign the entire key, rather than just the id:

object = MyClass()
object = object.custom_populating_method(form.cleaned_data)
object.key = ndb.Key(MyClass, object.get_unique_key())
object.put()

Upvotes: 0

Related Questions