MongoEngine: Replacing get_or_create with upsert/update_one - django

I understand that get_or_create is now deprecated in favour of using upsert, but how do I make update_one to return the object rather the number of objects modified, and can I just retrieve an object if I don't want to update anything?
e.g.
Model.objects.get_or_create(first_name='John', last_name='Potter', age=40)
# assuming that first_name + last_name + age are enough to uniquiely indentify a person
returns a Model object (a new object if it didn't exist, and existing object if it does). What would be the equivalent of this using the new method?
Model.objects(first_name='John', last_name='Potter', age=40).update_one(upsert=True)
# returns number of objects (1)
Model.objects(first_name='John', last_name='Potter', age=40).update_one(set__first_name='John', set__last_name='Potter', set__age=40,upsert=True)
# returns number of objects (1)
Is there a way to make it return the object, and make it behave exactly like get_or_create?
I couldn't find how to do this in the documentation

You are very close but you need to use a findAndModify command via modify rather than an update command.
NewDoc = Model.objects(first_name='John',
last_name='Potter',
age=40).modify(upsert=True, new=True,
set__first_name='John,
set__last_name='Potter',
set__age=40,
set_on_insert__newUser=True)
Take note of the first 2 modify kwargs - upsert and new. Also take note of the $setOnInsert operator example which will only set a field if the findAndModify does an upsert.

You should look at modify. Passing a new=True you'll get the updated object (or document, in mongodb parlance).

Related

Django - modify a field value just for filtering purpose

I want to modify some_date_field value just for filtering purpose.
Like using models.Lookup or models.Transform but I dont want to make a raw sql expression.
For instance, using a raw ms sql expression I could write:
WHERE CONVERT(date, FORMAT(some_date_field, '2021MMdd')) >= #some_var
But I how I can do that with Django?
class SomeModel(models.Model):
some_date_field = models.DateField()
def replace_year(value):
return value.replace(year=2021)
SomeModel.objects.filter(
# replace_year(some_date_field)__gte=some_var
)
Is it possible?
You can use SomeModel.objects.filter({whatever you want to filter}).update(some_date_field={date_value})
if you have any issues see:
https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.update
If you are trying to bulk update all of the objects returned by a queryset and you are using Django 2.2 or greater you can use 'bulk_update'.
See here: Django Bulk Update
If you are dynamically updating values based off of another field check out F expressions they can be used with an 'update' on querysets.
See here: Update dynamically with F expressions
Something to note though, this won't use ModelClass.save method (so if you have some logic inside it won't be triggered).
Take a look at these answers here as well
you can use filter() and update() methods in django
Assuming we need to filter some known year which is the old_date variable and the new value contains in the new_date variable
# defing mehod to filter and update new date
def update_date(old_date, new_date):
SomeModel.objects.filter(some_date_field=old_date).update(some_date_field=new_date)
return None
you can find some examples using this link.
Hope this will be helpful for you.

get_or_create returning error that multiple value of objects exist when I check same parameters with filter it retuns 0 objects

I am trying to use get_or_create method to see if a record exists. If the record exists do nothing otherwise create it.
This model is basically used on the command that is failing
class modelStudentExamsPerformed(models.Model):
patient = models.ForeignKey(modelPatient)
student = models.ForeignKey(modelStudent,on_delete=models.CASCADE)
normalBodyPart = models.ForeignKey(modelNormalBodyPartResult,default=None,blank=True,null=True)
abnormalBodyPart = models.ForeignKey(modelAbnormalBodyPartResult,default=None,blank=True,null=True)
tool = models.CharField(max_length=128, default="")
Now this is the command that is using the above model and failing
exams.modelStudentExamsPerformed.objects.get_or_create()(patient=patient_qset, student=stud_qset,abnormalBodyPart=qset[0],normalBodyPart=None,date=None)
The above statement gives me the error :
get() returned more than one modelStudentExamsPerformed -- it returned 2!
Now this is where I am having trouble. When I look into the database through my admin I do notice two objects but those objects both have a value associated with normalBodyPart and their abnormalBodyPart is empty.Since I explicitly specified and assigned a value to abnormalBodyPart why does django say that two items already exist ? I hope this makes sense .
Let e explain this another way as well suppose there are two statements 1 and 2.
Statement 1 gets or creates a record based on a specified parameter . This get or create fails as django thinks there are already two records. However statement 2 uses the same exact parameters and returns 0 records. Why is that ? What am I missing and not understanding here ?
Statement 1:
exams.modelStudentExamsPerformed.objects.get_or_create()(patient=patient_qset, student=stud_qset,abnormalBodyPart=qset[0],normalBodyPart=None,date=None)
when clearly none of the objects in this table have a value for abnormalBodyPart. I validated this by doing the following
Statement 2:
k = exams.modelStudentExamsPerformed.objects.filter(patient=patient_qset, student=stud_qset,abnormalBodyPart=qset[0], normalBodyPart=None,date=None)
The above statment 2 does not return anything. My question is why does Statement 2 not return anything while statement 1 is complaining that there are 2 items already thus failing the get call.
You're invoking get_or_create() without arguments and then invoke the __call__() method on the resulting instance.
Put the keywords inside the get_or_create to make it work properly. Also study the method signature again: you're missing a defaults argument. The method works like this:
Filter on provided keyword arguments, except "defaults"
If nothing is returned, create a new instance with those keyword arguments and use the "defaults" keyword argument, which should be a dictionary to fill additional fields.
Sometimes, it's better to illustrate:
instance, created = Players.objects.get_or_create(
username='Lagolas',
defaults={'class': 'ranger', 'gender': 'male'}
)
Is equivalent to:
try:
instance = Players.objects.get(username='Lagolas')
return instance, False
except Players.DoesNotExist:
instance = Players.objects.create(
username='Lagolas', class='ranger', gender='male'
)
return instance, True

Django: Any negative effect of assigning value to related object and save

Example:
user = instance.user
user.updated_at = now()
user.save()
Apart from the "dot optimisation" and "readability", for the sake of curiosity, are there any technical reasons not to do like the following?
instance.user.updated_at = now()
instance.user.save()
No. It is exactly the same. Django will perform the SELECT the first time you access the instance.user object. This object behaves like any other (it doesn't matter if you use a new reference for it).
If you take into account the moment when the query is performed, you can treat your models pretty much like any python object.

Can I safely assume that Django models IDs are unique upon save()

I need to store matches in my database and those matches already have a unique ID where they come from. For further assistance and referring, it is best for me to keep this ID:
match = Match(id=my8digitsid)
match.save()
However, incoming matches (not played yet) don't have an ID yet. Can I safely save my match as follow:
match = Match()
match.save
And then, once the match played modify it as such:
match.id = my8digitsid
When I say safely, I mean whether or not that the default ID generated (auto-incremented I guess) is unique and won't have any conflicts with my self-made IDs.
Yes, you can be sure that the ORM will make unique id's as referred in the documentation here. The database is the one calculating the new number.
If a model has an AutoField — an auto-incrementing primary key — then
that auto-incremented value will be calculated and saved as an
attribute on your object the first time you call save():
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id # Returns None, because b doesn't have an ID yet.
>>> b2.save()
>>> b2.id # Returns the ID of your new object. There’s no way to tell what the value of an ID will be before you call save(), because
that value is calculated by your database, not by Django.
For convenience, each model has an AutoField named id by default
unless you explicitly specify primary_key=True on a field in your
model.
You can also provide the Id if you want using this. I copy below the info from Django documentation.
Explicitly specifying auto-primary-key values If a model has an
AutoField but you want to define a new object’s ID explicitly when
saving, just define it explicitly before saving, rather than relying
on the auto-assignment of the ID:
>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id # Returns 3.
>>> b3.save()
>>> b3.id # Returns 3.
If you assign auto-primary-key values manually, make sure not to use
an already-existing primary-key value! If you create a new object with
an explicit primary-key value that already exists in the database,
Django will assume you’re changing the existing record rather than
creating a new one.
Given the above 'Cheddar Talk' blog example, this example would
override the previous record in the database:
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
b4.save() # Overrides the previous blog with ID=3!
But I don't recommend You to assign that ID yourself. I think more convenient to create a field of the model with the ID from where they come from.
The reason Why I don't recommend this is because you will have to verify always that the id provided has not been used before before inserting it. As a general rule I try to avoid modifying the standard behaviour of Django as much as possible.

Get object from list of objects without extra database calls - Django

I have an import of objects where I want to check against the database if it has already been imported earlier, if it has I will update it, if not I will create a new one. But what is the best way of doing this.
Right now I have this:
old_books = Book.objects.filter(foreign_source="import")
for book in new_books:
try:
old_book = old_books.get(id=book.id):
#update book
except:
#create book
But that creates a database call for each book in new_books. So I am looking for a way where it will only make one call to the database, and then just fetch objects from that queryset.
Ps: not looking for a get_or_create kind of thing as the update and create functions are more complex than that :)
--- EDIT---
I guess I haven't been good enough in my explanation, as the answers does not reflect what the problem is. So to make it more clear (I hope):
I want to pick out a single object from a queryset, based on an id of that object. I want the full object so I can update it and save it with it's changed values. So lets say I have a queryset with 3 objects, A and B and C. Then I want a way to ask if the queryset has object B and if it has then get it, without an extra database call.
Assuming new_books is another queryset of Book you can try filter on id of it as
old_books = Book.objects.filter(foreign_source="import").filter(id__in=[b.id for b in new_books])
With this old_books has books that are already created.
You can use the values_list('id', flat=True) to get all ids in a single DB call (is much faster than querysets). Then you can use sets to find the intersections.
new_book_ids = new_books.values_list('id', flat=True)
old_book_ids = Book.objects.filter(foreign_source="import") \
.values_list('id', flat=True)
to_update_ids = set(new_book_ids) & set(old_book_ids)
to_create_ids = set(new_book_ids) - to_update_ids
-- EDIT (to include the updated part) --
I guess the problem you are facing is in bulk updating rather than bulk fetch.
If the updates are simple, then something like this might work:
old_book_ids = Book.objects.filter(foreign_source="import") \
.values_list('id', flat=True)
to_update = []
to_create = []
for book in new_books:
if book.id in old_book_ids:
# list of books to update
# to_update.append(book.id)
else:
# create a book object
# Book(**details)
# Update books
Book.objects.filter(id__in=to_update).update(field='new_value')
Book.objects.bulk_create(to_create)
But if the updates are complex (update fields are dependent upon related fields), then you can check insert... on duplicated key update option in MySQL and its custom manager for Django.
Please leave a comment if the above is completely off the track.
You'll have to do more than one query. You need two groups of objects, you can't fetch them both and split them up at the same time arbitrarily like that. There's no bulk_get_or_create method.
However, the example code you've given will do a query for every object which really isn't very efficient (or djangoic for that matter). Instead, use the __in clause to create smart subqueries, and then you can limit database hits to only two queries:
old_to_update = Book.objects.filter(foreign_source="import", pk__in=new_books)
old_to_create = Book.objects.filter(foreign_source="import").exclude(pk__in=new_books)
Django is smart enough to know how to use that new_books queryset in that context (it can also be a regular list of ids)
update
Queryset objects are just a sort of list of objects. So all you need to do now is loop over the objects:
for book in old_to_update:
#update book
for book in old_to_create:
#create book
At this point, when it's fetching the books from the QuerySet, not from the databse, which is a lot more efficient than using .get() for each and every one of them - and you get the same result. each iteration you get to work with an object, the same as if you got it from a direct .get() call.
The best solution I have found is using the python next() function.
First evaluate the queryset into a set and then pick the book you need with next:
old_books = set(Book.objects.filter(foreign_source="import"))
old_book = next((book for book in existing_books if book.id == new_book.id), None )
That way the database is not queried everytime you need to get a specific book from the queryset. And then you can just do:
if old_book:
#update book
old_book.save()
else:
#create new book
In Django 1.7 there is an update_or_create() method that might solve this problem in a better way: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.update_or_create