Create if doesn't exist - django

I have a Django application that reads data from a web API and puts it in a database.
Is there a way to create a new object from a mode but prevent the duplicate exception if the object already exists?
In other words, is there a way to save an object, but to just do nothing if it already exists?

Model.objects.get_or_create()

In Django 1.7, you can also do:
Model.objects.update_or_create()

It can be achieved using Model.objects.get_or_create()
Example
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
Any keyword arguments(here first_name and last_name) passed to get_or_create() — except an optional one called defaults — will be used to query in database(find the object) in database.
It returns a tuple, if an object is found, get_or_create() returns a tuple of that object and False.
Note: Same thing can also be achieved using try except statements
Example:
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
obj.save()

Looks like in newer versions of Django the save() function does an UPDATE or INSERT by default. See here.

Related

django.db.transaction.TransactionManagementError: cannot perform saving of other object in model within transaction

Can't seem to find much info about this. This is NOT happening in a django test. I'm using DATABASES = { ATOMIC_REQUESTS: True }. Within a method (in mixin I created) called by the view, I'm trying to perform something like this:
def process_valid(self, view):
old_id = view.object.id
view.object.id = None # need a new instance in db
view.object.save()
old_fac = Entfac.objects.get(id=old_id)
new_fac = view.object
old_dets = Detfac.objects.filter(fk_ent__id__exact = old_fac.id)
new_formset = view.DetFormsetClass(view.request.POST, instance=view.object, save_as_new=True)
if new_formset.is_valid():
new_dets = new_formset.save()
new_fac.fk_cancel = old_fac # need a fk reference to initial fac in new one
old_fac.fk_cancel = new_fac # need a fk reference to new in old fac
# any save() action after this crashes with TransactionManagementError
new_fac.save()
I do not understand this error. I already created & saved a new object in db (when I set the object.id to None & saved that). Why would creating other objects create an issue for further saves?
I have tried not instantiating the new_dets objects with the Formset, but instead explicitely defining them:
new_det = Detfac(...)
new_det.save()
But then again, any further save after that raises the error.
Further details:
Essentially, I have an Entfac model, and a Detfac model that has a foreignkey to Entfac. I need to instantiate a new Enfac (distinct in db), as well as corresponding new Detfac for the new Entfac. Then I need to change some values in some of the fields for both new & old objects, and save all that to db.
Ah. The code above is fine.
But turns out, signals can be bad. I had forgotten that upon saving Detfac, there is a signal that goes to another class and that depending on the circumstances, adds a record to another table (sort of an history table).
Since that signal is just a single operation. Something like that:
#receiver(post_save, sender=Detfac)
def quantity_adjust_detfac(sender, **kwargs):
try:
detfac_qty = kwargs["instance"].qte
product = kwargs["instance"].fk_produit
if kwargs["created"]:
initial = {# bunch of values}
adjustment = HistoQuantity(**initial)
adjustment.save()
else:
except TypeError as ex:
logger.error(f"....")
except AttributeError as ex:
logger.error(f"....")
In itself, the fact that THIS wasn't marked as atomic isn't problematic. BUT if one of those exception throws, THEN I get the transactionmanagementerror. I am still not 100% sure why, tough the django docs do mention that when wrapping a whole view in atomic (or any chunk of code for that matter), then try/except within that block can yield unexpected result, because DJango does rely on exception to decide whether or not to commit the transaction as a whole. And the data I was testing with actually threw the exception (type error when creating the HistoQuantity object).
Wrapping the try/exception with a transaction.atomic manager worked however. Guessing that this... removed/handled the throw, thus the outer atomic could work.

Django ManyToMany related manager with no objects returns True

Django 1.9, Python 3.6, postgres DB
There exists Calendar and CalendarOwner, where the many-to-many relationship is defined in Calendar.
class Calendar(models.Model):
...
calendar_owners = models.ManyToManyField(
'some_app.CalendarOwner',
blank=True,
related_name='calendars',
)
...
def calendar_method(self):
return self.calendar_owners.calendar_owner_method()
class CalendarOwner(models.Model):
...
def calendar_owner_method(self):
...
# returns a bool, depends on condition
When I am calling Calendar.calendar_method() for Calendar that has CalendarOwner(s) associated with it, CalendarOwner.calendar_owner_method() is called for each CalendarOwner, and works as intended - I get a boolean according to whatever logic I have in there.
I think that the way it works is that from all the method calls, if there is at least one True the execution breaks and the return value is True. Otherwise it will call on all the related objects and finally return False.
The question:
Why is it that when I am calling Calendar.calendar_method() when no CalendarOwner(s) are associated with Calendar it never calls CalendarOwner.calendar_owner_method() and always returns True.
Is that the default behavior?
Example:
>>> obj = Calendar()
<Calendar: Str>
>>> obj.calendar_owners.count()
0
>>> obj.calendar_owners.calendar_owner_method()
True
It makes sense that since there are no CalendarOwner(s) associated with that Calendar the CalendarOwners.calendar_owner_method() would not be called at all. But why does it return True? Can someone point me to the docs?
I can check if Calendar.calendar_owners.count() is zero and return False, but I want to see if there is any better Django way of doing that.
obj.calendar_owners is a Manager and not an instance of CalendarOwner, so it has not calendar_owner_method.

Django call cleaned_data on all fields

Is there a way to call cleaned_data on all fields with some function instead of individually calling it for each field?
Also, why do we even need to call cleaned_data?
I am not sure if I need it here... I am using a for loop to save a formset, but it only saves the last one. Here is the code
for instance in form:
tmp = instance.save(commit=False)
# it throws an error when I try to do tmp[foreign_key] = other_model
setattr(tmp, foreign_key, other_model)
tmp.save()
What are you hoping for? You don't ever call it. cleaned_data gets populated upon validating the form.
form.is_valid() populates form.cleaned_data, which is a dictionary storing all data "cleaned" i.e. validated and converted to their python types.
I don't think one can make data much more accessible than a dictionary of keys mapped to field names.
As for your latest update, that itself is pretty confusing.
You appear to be setting an attribute on a foreign key in your modelform instance based on a local variable named 'gen_house_form_saved' (which I don't understand as well: if it's in the locals() namespace, and you're not using a dynamic name, why use locals at all).

django orm: Check if obj is in queryset

How can I check whether an obj is in a queryset or not?
I tried this:
self.assertIn(obj, list(MyModel.objects.filter(...))
But it does not work in my case.
AssertionError: <MyModel 137 'unclassified'> not found in
[<MyModel 1676 'foo'>, ..., <MyModel 137 'unclassified'>, ...]
I don't understand it, since it is in the list.
How about
self.assertTrue(MyModel.filter(...).filter(pk=obj.pk).exists())
First of all, it should be MyModel.objects.filter(...). If you omit the .objects, you should've gotten a different error, so I'm assuming you did include it but just forgot it in the question.
If obj is actually in the QuerySet returned, what you did should have worked as Django model instances provides an equal comparator which compares both the type and the primary key. list() is not required around the QuerySet, though it should still work if you used it.
From the Django 1.5 source:
def __eq__(self, other):
return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
If it still doesn't work, there are a few possible causes:
The type doesn't match. In this case, it doesn't matter even if the object pk exists in the QuerySet's object pks. (You cannot compare apples to oranges)
There is no such object in the database (i.e. it hasn't been saved yet)
The type matches but the object is not in the QuerySet (i.e. filtered out)
You overrode the __eq__ method and did something weird. Or you overrode the default manager .objects with some custom filter. This scenario is outside the scope of this answer and if you did this, you should probably know how to fix it.
To help you diagnose which is the case, try this:
self.assertTrue(isinstance(obj, MyModel))
# 1. If it fails here, your object is an incorrect type
# Warning: the following tests can be very slow if you have a lot of data
self.assertIn(obj.pk, MyModel.objects.values_list('pk', flat=True))
# 2. If it fails here, the object doesn't exist in the database
self.assertIn(obj.pk, MyModel.objects.filter(...).values_list('pk', flat=True))
# 3. If it fails here, the object did not pass your filter conditions.
self.assertIn(obj, MyModel.objects.filter(...))
# 4. If it fails here, you probably messed with the Django ORM internals. Tsk tsk.
Just with, note the .all()
queryset_result = MyModel.filter(...).all()
if obj in queryset_result:
//obj is in the queryset
The "in" fails because the objects aren't actually equal, as equality is object identity by default. If you want "in" to work, you'd have to implement __eq__ accordingly on your model.
If you don't want to do that, you can check by comparing the pk, like so
self.assertIn(obj.pk, [o.pk for o in MyModel.filter(...)])
I think This is very simple way get to find out object present in queryset or not.
First Example:
obj_list = MyModel.filter(...)
if obj in obj_list:
print "obj in queryset"
else:
print "not in queryset"
Second Example:
obj_list = MyModel.filter(...)
try:
obj_list.get(pk=obj.id)
except:
# If try get success obj is present in query set else your this except get executed.

MongoEngine get_or_create Alternatives

I've been using the get_or_create method with MongoEngine in a Django app. Today, I noticed there were a few duplicate entries. I came across this in the MongoEngine API Reference for get_or_create:
This requires two separate operations and therefore a race condition exists. Because there are no transactions in mongoDB other approaches should be investigated, to ensure you don’t accidentally duplicate data when using this method. This is now scheduled to be removed before 1.0
Should I be using something like this?:
from models import Post
post = Post(name='hello')
try:
Posts.objects.get(name=post.name)
print "exists"
except:
post.save()
print "saved"
Will that solve my problem?
Is there a better way?
To perform an upsert use the following syntax:
Posts.objects(name="hello").update(set__X=Y, upsert=True)
That will add a post with the name "hello" and where X = Y if it doesn't already exist, otherwise it will update an existing post just setting X = Y.
If you need pass a dictionery, can do this:
post = Post.objects(name="hello").first() or Post(name="hello")
then you can update with something like this:
# data = dictionary_with_data
for field, value in data.items():
post[field] = value
post.save()