2

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

Community
  • 1
  • 1
Samuele Mattiuzzo
  • 10,760
  • 5
  • 39
  • 63

2 Answers2

2

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.

Tim Hoffman
  • 12,976
  • 1
  • 17
  • 29
  • Let's say later on user's phone changes (just to reuse the example, my use case is a bit different). We put() a new phone number. But now the key doesn't reflect the data stored on ndb. Someone tries to add again the same values from the first time: (name,surname,phone). It fails, because that combination has been used before, but if you search for the first phone number nothing is found. To circumvent this I thought about deleting the key and creating a new obj, but I'd loose the reference other models might have to it. Any ideas on how to do this still using the key for uniquiness? – alfetopito Dec 01 '14 at 20:25
0

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()
Jamie Niemasik
  • 755
  • 6
  • 16