Run a django update but call the save method - python

Is there a way to do the following:
asset, _ = Asset.objects.get_or_create(system=item['system'], system_table=item['system_table'], ...)
Asset.objects.filter(pk=asset.pk).update(**item)
And also call the .save() method? I think I've read somewhere that you can run an update on the actual instance and not go through the objects manager. How would that be done? Currently I'm doing the following, which is quite repetitive and inefficient:
a = Asset.objects.filter(pk=asset.pk).update(**item)
a.save()

Since you already have the asset object, you can just make use of it.
# assuming that you have a `Asset` object in `asset` variable, somehow
item = {"foo": "foo-value"}
for field, value in items.items():
setattr(asset, field, name)
asset.save()
You can also specify the update_fields parameter of save() method as
asset.save(update_fields=list(item.keys()))

The best way to do this is to just call save() directly. You will need to call get() instead of filter(), though.
Asset.objects.get(pk=asset.pk).save(update_fields=item)
This isn't a problem since your existing filter() is guaranteed to return a queryset with at most one Asset anyway. You just have to be sure that the given pk actually exists or wrap the get() call in a try...except block.
But...since you already have the Asset instance in asset, there's no reason to waste time with a DB query. Just call save directly on the object you have:
asset.save(update_fields=item)

Related

How to insert an item into a Queryset which is sorted by a numeric field and increment the value of the field of all subsequent items

Let´s say, there is a Django model called TaskModel which has a field priority and we want to insert a new element and increment the existing element which has already the priority and increment also the priority of the following elements.
priority is just a numeric field without any special flags like unique or primary/foreign key
queryset = models.TaskModel.objects.filter().order_by('priority')
Can this be done in a smart way with some methods on the Queryset itself?
I believe you can do this by using Django's F expressions and overriding the model's save method. I guess you could instead override the model's __init__ method as in this answer, but I think using the save method is best.
class TaskModel(models.Model):
task = models.CharField(max_length=20)
priority = models.IntegerField()
# Override the save method so whenever a new TaskModel object is
# added, this will be run.
def save(self, *args, **kwargs):
# First get all TaskModels with priority greater than, or
# equal to the priority of the new task you are adding
queryset = TaskModel.objects.filter(priority__gte=self.priority)
# Use update with the F expression to increase the priorities
# of all the tasks above the one you're adding
queryset.update(priority=F('priority') + 1)
# Finally, call the super method to call the model's
# actual save() method
super(TaskModel, self).save(*args, **kwargs)
def __str__(self):
return self.task
Keep in mind that this can create gaps in the priorities. For example, what if you create a task with priority 5, then delete it, then add another task with priority 5? I think the only way to handle that would be to loop through the queryset, perhaps with a function like the one below, in your view, and call it whenever a new task is created, or it's priority modified:
# tasks would be the queryset of all tasks, i.e, TaskModels.objects.all()
def reorder_tasks(tasks):
for i, task in enumerate(tasks):
task.priority = i + 1
task.save()
This method is not nearly as efficient, but it will not create the gaps. For this method, you would not change the TaskModel at all.
Or perhaps you can also override the delete method of the TaskModel as well, as shown in this answer, but I haven't had a chance to test this yet.
EDIT
Short Version
I don't know how to delete objects using a similar method to saving while keeping preventing priorities from having gaps. I would just use a loop as I have shown above.
Long version
I knew there was something different about deleting objects like this:
def delete(self, *args, **kwargs):
queryset = TaskModel.objects.filter(priority__gt=self.priority)
queryset.update(priority=F('priority') - 1)
super(TaskModel, self).delete(*args, **kwargs)
This will work, in some situations.
According to the docs on delete():
Keep in mind that this [calling delete()] will, whenever possible, be executed purely in
SQL, and so the delete() methods of individual object instances will
not necessarily be called during the process. If you’ve provided a
custom delete() method on a model class and want to ensure that it is
called, you will need to “manually” delete instances of that model
(e.g., by iterating over a QuerySet and calling delete() on each
object individually) rather than using the bulk delete() method of a
QuerySet.
So if you delete() a TaskModel object using the admin panel, the custom delete written above will never even get called, and while it should work if deleting an instance, for example in your view, since it will try acting directly on the database, it will not show up in the python until you refresh the query:
tasks = TaskModel.objects.order_by('priority')
for t in tasks:
print(t.task, t.priority)
tr = TaskModel.objects.get(task='three')
tr.delete()
# Here I need to call this AGAIN
tasks = TaskModel.objects.order_by('priority')
# BEFORE calling this
for t in tasks:
print(t.task, t.priority)
# to see the effect
If you still want to do it, I again refer to this answer to see how to handle it.

Django model instance from queryset not updating on save()

I have the following code
VocabFromDatabase = Vocab.objects.filter(User = U, IsCard = True).order_by("LastStudyDate")
VocabFromDatabase[0].Mnem = "Passed"
VocabFromDatabase[0].save()
According the docs save() should save the changes, but it seems to silently fail. After some fiddling around it seems the problem isn't with save() but rather with assigning a value to a property of one of the objects from the queryset. However everywhere I look tells me to either use update() (which I think would update all the objects in the queryset, which I do not want), or that save() should work.
Is there something I'm not aware of, or something I'm doing wrong here?
Looks like setting the model's name field and calling the model's save method is being done to 2 separate references of the model. Everytime you select via [index], queryset is returning from its cache.
https://docs.djangoproject.com/en/2.1/topics/db/queries/#when-querysets-are-not-cached
queryset[0].name = "foo" // <Model pk=1 name="foo">
queryset[0].save() // <Model pk=1 name=default> SAVED

Odoo 11+ How to override function args and all calls to it without overriding every call

I want to override the uom._compute_price() and compute_quantity() in odoo to add product as an argument.
the problem is that these functions are called in many other functions in many other modules like stock, account, sale, purchase.
So I have to override each calling function which is about 140 occurrences .
Is there a better way to implement this without modifying the 140 calls ?
for ex. in the overridden functions, can I get the original caller object and its attributes ?
If there is a way to get the product from self or any other object, you can
make product_id a keyword only argument and check
it's not None, else you set it your self from an object.
def _compute_price(self, same_arguments,*,product_id=None)
if product_id is None:
product_id = # get the id from something
# your code here
If you can't get the product_id from anywhere I hope you find
and solution for that or you have to edit all calls.
Changing an upstream method arguments is not recommended, and you are discovering the why.
However, your calls to it can add a key in the context, and the overridden method can react to it. Example:
def _compute_price(self, original_args):
if self.env.context.get("product_id"):
# Do your stuff here
return super()._compute_price(original_args)
Then, from other parts of your module, call the method with that context key:
uom.with_context(product_id=product.id)._compute_price(original_args)
Another option would be to add the context key in the overriden method itself, if you want it to be present under every call:
def _compute_price(self, original_args):
# Get the product somehow here
return super(type(self), self.with_context(product_id=product.id))._compute_price(original_args)
However, keep in mind that only addons that are aware of this context and react to it actually need it. The 1st approach should be the most accurate for most cases.

What is the best way to get an object which is not sure if there is or not in Django model

What is the best way to get an object which is not sure if there is or not.(in Django model)
I think there are three options.
One is to use try and except statement to process the exception.
Second is to use objects.filter() instead of objects.get() and check query_set.count().
Third is to use objects.count() first and then use objects.get() if there is.
You can use .first() operation on a queryset to your advantage to get the first object of the queryset if it exists and None otherwise.
Returns the first object matched by the queryset, or None if there is
no matching object.
my_object = some_queryset.first() # return a object or 'None'
This will return the first object of the queryset if there are objects in the queryset. Otherwise, it returns None. It will automatically handle the case when objects do no exist without you needing to write try-except.
The above code is equivalent to writing:
try:
my_object = some_queryset[0] # access the first element
except IndexError:
my_object = None
Note: Here, we needed the object, so we are using .first(). If we needed to check only if the object exists, then we could have used .exists()
Edited: I think the best solution in this case, is use this third package
django-annoying
This packaged contains a lot of syntactic sugar for things like that.
With this package, you can use the function get_object_or_None, similar to
get_object_or_404.
But, the real code behind this function is :
def get_object_or_None(klass, *args, **kwargs):
"""
Uses get() to return an object or None if the object does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), a MultipleObjectsReturned will be raised if more than one
object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
return None
Note: Don’t use this if all you want to do is determine if at least one result exists. It’s more efficient to use exists().

Python, Django, how to use getattr (or other method) to call object that has multiple attributes?

After trying to get this to work for a while and searching around I am truly stumped so am posting here... I want to make some functions in classes that I am writing for django as generic as possible so I want to use getattr to call functions such as the one below in a generic manner:
the way I do it that works (non-generic manner):
from django.db.models import get_model
mymodel = get_model('appname', 'modelname')
dbobject = mymodel.objects.all()
one of my attempts create this in a generic manner, still not working, it does return something back but its not the proper object type so that i can get the data from it (its a database call for django)
ret = getattr(mymodel,'objects')
dbobject = getattr(ret,'all')
You forgot to call the result.
dbobject = mymodel.objects.all()
Accesses the method mymodel.objects.all and then calls it.
ret = getattr(mymodel,'objects')
self.dbobject = getattr(ret,'all')
accesses the method mymodel.objects.all but does not call it.
All you need is to change the last line to:
self.dbobject = getattr(ret,'all')()
You will need to call the attribute if it's a function, e.g.
ret = getattr(mymodel,'objects')
all = getattr(ret,'all')
self.dbobject = all()

Categories