Ndb models: assure uniqueness in datastore using custom key_name - django

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

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.

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()

Related

Customizing the entry uniqueness in Django

I have a database containing a list of ingredients. I'd like to avoid duplicate entries in this table. I don't want to use the unique keyword for 2 reasons :
My uniqueness constraints are a bit more sophisticated than a mere =
I don't want to raise an exception when a pre-existing ingredient model is created, instead I just want to return that model, so that I can write Ingredient(ingredient_name='tomato') and just go on with my day rather than encapsulating all of that in a try clause. This will allow me to easily add ingredients to my recipe table on the fly.
One solution is simply to have a wrapper function like create_ingredient, but I don't find that to be particularly elegant and more specifically it's not robust to some other developer down the line simply forgetting to use the wrapper. So instead, I'm playing around with the pre_init and post_init signals.
Here's what I have so far :
class Ingredient(models.Model):
ingredient_name = models.CharField(max_length=200)
recipes = models.ManyToManyField(Recipe,related_name='ingredients')
def __str__(self):
return self.ingredient_name
class Name(models.Model):
main_name = models.CharField(max_length=200, default=None)
equivalent_name = models.CharField(max_length=200, primary_key=True, default=None)
def _add_ingredient(sender, args, **kwargs):
if 'ingredient_name' not in kwargs['kwargs'] :
return
kwargs['kwargs']['ingredient_name'] = kwargs['kwargs']['ingredient_name'].lower()
# check if equivalent name exists, make this one the main one otherwise
try:
kwargs['kwargs']['ingredient_name'] = Name.objects.filter(
equivalent_name=kwargs['kwargs']['ingredient_name']
)[0].main_name
except IndexError:
name = Name(main_name=kwargs['kwargs']['ingredient_name'],
equivalent_name=kwargs['kwargs']['ingredient_name'])
name.save()
pre_init.connect(_add_ingredient, Ingredient)
So far so good. This actually works and will replace ingredient_name when needed before the model is initialized. Now what I'd like is to check if the ingredient in question already exists and have the initializer return it if it does. I think I need to play around with post_init to do this but I don't know how to modify the particular instance that's being created. Here's what I mean by that :
def _finalize_ingredient(sender, instance, **kwargs):
try:
# doesn't work because of python's "pass arguments in python's super unique way of doing things" thing
instance = Ingredient.objects.filter(ingredient_name=instance.ingredient_name)[0]
except IndexError:
pass
post_init.connect(_finalize_ingredient, Ingredient)
As I've commented, I don't expect this to work because instance = ... doesn't actually modify instance, it just reassigns the variable name (incidentally if you try to run this all sorts of terrible things happen which I don't care to understand because I know this is flat out wrong). So how do I actually do this ? I really hope wrapper functions aren't the cleanest option here. I'm a big fan of OOP and gosh darn it I want an OOP solution to this (which, as I've said, I think in the long run would be much more robust and safer than wrappers).
I realize of course that I can add an add_ingredient method to Recipe which will do all of this for me, but I really like the idea of containing all of this in my Ingredient class as it will guarantee the proper database behavior under any circumstance. I'm also curious as to know if/how the post_init method can be used to completely override the created object for a given circumstance.
By the way, some of you may be wondering why I don't have a ForeignKey entry in my Name class that would connect the Name table to the Ingredient table. After all, isn't this what my check is essentially accomplishing in my _add_ingredient method ? One of the reasons is that if I do this then I end up with the same problem I'm trying to solve here : If I want to create an ingredient on the fly to add it to my recipe, I could simply create a Name object when creating an Ingredient object, but that would raise an exception if it corresponds to a main_name that is already in use (rather than simply returning the object I need).
I believe you are looking for get_or_create(), which is already a built-in in Django.
You mention:
One solution is simply to have a wrapper function like create_ingredient, but I don't find that to be particularly elegant and more specifically it's not robust to some other developer down the line simply forgetting to use the wrapper.
Well, look at it the other way around. What if you actually need to create a "duplicate" ingredient? Then it is nice to have the possibility.
I've come up with something that is as elegant and robust as I think it's possible to be given what I'm after. I've still had to define an add_ingredient method, but I still have the robustness that I need. I've made it so that it can be generalized to any class with a primary key, and the Name table will contain the info that will define the name uniqueness of any table :
class Name(models.Model):
main_name = models.CharField(max_length=200, default=None)
equivalent_name = models.CharField(max_length=200, primary_key=True, default=None)
def _pre_init_unique_fetcher(sender, args, **kwargs):
pk_name = sender._meta.pk.name
if pk_name not in kwargs['kwargs'] :
return
kwargs['kwargs'][pk_name] = kwargs['kwargs'][pk_name].lower()
# check if equivalent name exists, make this one the main one otherwise
try:
kwargs['kwargs'][pk_name] = Name.objects.filter(
equivalent_name=kwargs['kwargs'][pk_name]
)[0].main_name
except IndexError:
name = Name(main_name=kwargs['kwargs'][pk_name],
equivalent_name=kwargs['kwargs'][pk_name])
name.save()
sender._input_dict = kwargs['kwargs']
def _post_init_unique_fetcher(sender, instance, **kwargs):
pk_name = sender._meta.pk.name
pk_instance = instance.__dict__[pk_name]
filter_dict = {}
filter_dict[pk_name] = pk_instance
try:
post_init.disconnect(_post_init_unique_fetcher,sender)
instance.__dict__ = sender.objects.filter(**filter_dict)[0].__dict__
post_init.connect(_post_init_unique_fetcher, sender)
for key in sender._input_dict:
instance.__dict__[key] = sender._input_dict[key]
del sender._input_dict
except IndexError:
post_init.connect(_post_init_unique_fetcher, sender)
except:
post_init.connect(_post_init_unique_fetcher, sender)
raise
unique_fetch_models = [Ingredient, Recipe, WeekPlan]
for unique_fetch_model in unique_fetch_models :
pre_init.connect(_pre_init_unique_fetcher, unique_fetch_model)
post_init.connect(_post_init_unique_fetcher, unique_fetch_model)
Now what this will do is load up any new model with the pre-existing data of the previous model (rather than the default values) if one with the same name exists. The reason I still need an add_ingredient method in my Recipe class is because I can't call Ingredient.objects.create() for a pre-existing ingredient without raising an exception despite the fact that I can create the model and immediately save it. This has to do with how Django handles the primary_key designation : if you create the model then save it, it assumes you're just updating the entry if it already exists with that key, and yet if you create it, it tries to add another entry and that conflicts with the primary_key designation. So now I can do things like recipe.add_ingredient(Ingredient(ingredient_name='tomato', vegetarian=True)).

How to validate whether a ManyToMany relationship exists Django?

class Interest(models.Model):
name = models.CharField(max_length=255)
class Person(models.Model):
name = models.CharField(max_length=255)
interests = models.ManyToManyField(Interest, related_name='person_interests')
def add_interest(self, interest_pk):
interest = Interest.objects.get(pk=interest_pk)
if not interests in self.interests:
self.interests.add(interest)
The above code does not work, but it indicates what I want to do.
In short, I want to validate whether a relation exists or not, if it does not, then I add the relationship. What is the efficient way to do this using django models.
Thanks.
If you look at the _add_items function of ManyRelatedManager, it already takes care of what you want here:
vals = (self.through._default_manager.using(db)
.values_list(target_field_name, flat=True)
.filter(**{
source_field_name: self.related_val[0],
'%s__in' % target_field_name: new_ids,
}))
new_ids = new_ids - set(vals)
It removes all the ids which are already present in the through table. So you don't really need to check anything. You can directly use add function:
def add_interest(self, interest_pk):
self.interests.add(interest_pk)
And, of course it will throw error if the interest_pk doesn't exist yet because that's the basic requirement of add function.
try this
def add_interest(self, interest_pk):
interest = Interest.objects.get(pk=interest_pk)
if interest not in self.interests.all():
self.interests.add(interest)
Please check if this helps.
person = Person.objects.filter(name = 'something):
if person.count() > 0:
interests = person[0].interests.all()
if interests.count() > 0:
###DO something..
In this case,
def add_interest(self, interest_pk):
interests = self.interests.all()
if interests.count():
### Add

How to check if a set field contains an specific value in Django?

This may be a really easy question but I've been looking for a while at django documentation and didn't find the answer.
My problem is that I want to check if, given a language, a user, who can speak multiple, speaks the given one.
My relevant classes:
class Language(models.Model):
idiom = models.CharField(max_length=40, unique=True)
class Profile(UserenaBaseProfile):
spoken_languages = models.ManyToManyField(Language, blank = True)
Given: query_set = Profile.objects.all()
I've tried things like:
ls = Language.get(idiom="some language here")
query_set.filter(spoken_languages__idiom__contains=ls.idiom)
query_set.filter(spoken_languages__contains=ls)
or
ls = Language.objects.filter(idiom="some language")
query_set.filter(spoken__languages__in=ls)
Some more but without success, it seems it should be quite easy but still I cannot find the correct approach. Any idea would be indeed appreciated.
This should work:
profiles = Profile.objects.filter(spoken_languages__idiom="language here")
Note that calling .filter() on a queryset does not change the queryset object. Instead, it creates and returns a clone with the new filters applied. So if you want to filter an existing queryset, you should do:
query_set = query_set.filter(spoken_languages__idiom="language here")

Django QuerySets - with a class method

Below is a stripped down model and associated method. I am looking for a simple way upon executing a query to get all of the needed information in a single answer without having to re-query everything. The challenge here is the value is dependent upon the signedness of value_id.
class Property(models.Model):
property_definition = models.ForeignKey(PropertyDefinition)
owner = models.IntegerField()
value_id = models.IntegerField()
def get_value(self):
if self.value_id < 0: return PropertyLong.objects.get(id=-self.value_id)
else: return PropertyShort.objects.get(id=self.value_id)
Right now to get the "value" I need to do this:
object = Property.objects.get(property_definition__name="foo")
print object.get_value()
Can someone provide a cleaner way to solve this or is it "good" enough? Ideally I would like to simply just do this.
object = Property.objects.get(property_definition__name="foo")
object.value
Thanks
Given this is a bad design. You can use the builtin property decorator for your method to make it act as a property.
class Property(models.Model):
property_definition = models.ForeignKey(PropertyDefinition)
owner = models.IntegerField()
value_id = models.IntegerField()
#property
def value(self):
if self.value_id < 0: return PropertyLong.objects.get(id=-self.value_id)
else: return PropertyShort.objects.get(id=self.value_id)
This would enable you to do what you'd ideally like to do: Property.objects.get(pk=1).value
But I would go as far as to call this "cleaner". ;-)
You could go further and write your own custom model field by extending django.models.Field to hide the nastiness in your schema behind an API. This would at least give you the API you want now, so you can migrate the nastiness out later.
That or the Generic Keys mentioned by others. Choose your poison...
this is a bad design. as Daniel Roseman said, take a look at generic foreign keys if you must reference two different models from the same field.
https://docs.djangoproject.com/en/1.3/ref/contrib/contenttypes/#generic-relations
Model inheritance could be used since value is not a Field instance.

Django model fields getter / setter

is there something like getters and setters for django model's fields?
For example, I have a text field in which i need to make a string replace before it get saved (in the admin panel, for both insert and update operations) and make another, different replace each time it is read. Those string replace are dynamic and need to be done at the moment of saving and reading.
As I'm using python 2.5, I cannot use python 2.6 getters / setters.
Any help?
You can also override setattr and getattr. For example, say you wanted to mark a field dirty, you might have something like this:
class MyModel:
_name_dirty = False
name = models.TextField()
def __setattr__(self, attrname, val):
super(MyModel, self).__setattr__(attrname, val)
self._name_dirty = (attrname == 'name')
def __getattr__(self, attrname):
if attrname == 'name' and self._name_dirty:
raise('You should get a clean copy or save this object.')
return super(MyModel, self).__getattr__(attrname)
You can add a pre_save signal handler to the Model you want to save which updates the values before they get saved to the database.
It's not quite the same as a setter function since the values will remain in their incorrect format until the value is saved. If that's an acceptable compromise for your situation then signals are the easiest way to achieve this without working around Django's ORM.
Edit:
In your situation standard Python properties are probably the way to go with this. There's a long standing ticket to add proper getter/setter support to Django but it's not a simple issue to resolve.
You can add the property fields to the admin using the techniques in this blog post
Overriding setattr is a good solution except that this can cause problems initializing the ORM object from the DB. However, there is a trick to get around this, and it's universal.
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
The secret is 'attrname in self.__dict__'. When the model initializes either from new or hydrated from the __dict__!
While I was researching the problem, I came across the solution with property decorator.
For example, if you have
class MyClass(models.Model):
my_date = models.DateField()
you can turn it into
class MyClass(models.Model):
_my_date = models.DateField(
db_column="my_date", # allows to avoid migrating to a different column
)
#property
def my_date(self):
return self._my_date
#my_date.setter
def my_date(self, value):
if value > datetime.date.today():
logger.warning("The date chosen was in the future.")
self._my_date = value
and avoid any migrations.
Source: https://www.stavros.io/posts/how-replace-django-model-field-property/