I came across a tricky trouble about django Queryset - python

Tricky code:
user = User.objects.filter(id=123)
user[0].last_name = 'foo'
user[0].save() # Cannot be saved.
id(user[0]) # 32131
id(user[0]) # 44232 ( different )
user cannot be saved in this way.
Normal code:
user = User.objects.filter(id=123)
if user:
user[0].last_name = 'foo'
user[0].save() # Saved successfully.
id(user[0]) # 32131
id(user[0]) # 32131 ( same )
So, what is the problem?

In first variant your user queryset isn't evaluated yet. So every time you write user[0] ORM makes independent query to DB. In second variation queryset is evalutaed and acts like normal Python list.
And BTW if you want just one row, use get:
user = User.objects.get(id=123)

when you index into a queryset, django fetches the data (or looks in its cache) and creates a model instance for you. as you discovered with id(), each call creates a new instance. so while you can set the properties on these qs[0].last_name = 'foo', the subsequent call to qs[0].save() creates a new instance (with the original last_name) and saves that
i'm guessing your particular issue has to do with when django caches query results. when you are just indexing into the qs, nothing gets cached, but your call if users causes the entire (original) qs to be evaluated, and thus cached. so in that case each call to [0] retrieves the same model instance

Saving is possible, but everytime you access user[0], you actually get it from the database so it's unchanged.
Indeed, when you slice a Queryset, Django issues a SELECT ... FROM ... OFFSET ... LIMIT ... query to your database.
A Queryset is not a list, so if you want to it to behave like a list, you need to evaluate it, to do so, call list() on it.
user = list(User.objects.filter(id=123))
In your second example, calling if user will actually evaluate the queryset (get it from the database into your python program), so you then work with your Queryset's internal cache.
Alternatively, you can use u = user[0], edit that and then save, which will work.
Finally, you should actually be calling Queryset.get, not filter here, since you're using the unique key.

Related

Odoo computed fields: works without store=True, doesn't work with store=True

I have a computed field in Odoo with a function. Everything works fine when I don't add the store argument. When I add the store argument, it doesn't execute the code at all.
My code:
class opc_actuelewaardentags(models.Model):
_name = 'opc_actuelewaardentags'
unit = fields.Char(compute='changeunit')
def changeunit(self):
print "print"
allrecords_actwaardent = self.search([])
obj_taginst = self.env['opc_taginstellingen']
allrecords_taginst = obj_taginst.search([])
for i in allrecords_actwaardent:
for j in allrecords_taginst:
if i.tagnaam == j.tagnaam and i.unit != j.unit:
i.unit = j.unit
So: when I call the code like this:
unit = fields.Char(compute='changeunit')
The code is executed (shows "print").
When I call the code like this:
unit = fields.Char(compute='changeunit', store=True)
The code is not executed (doesn't show "print").
Is there anything wrong in my code? Or is this a bug? It seems so strange to me...
I need to be able to store the values in the database so I can filter on unit in the tree view.
edit: I applied Juan Salcedo's tip. Didn't work...
This is how I did it:
unit = fields.Char(default = changeunit)
def changeunit(self):
print "print"
allrecords_actwaardent = self.search([])
obj_taginst = self.env['opc_taginstellingen']
#Hier dan i.p.v. self werken met dat obj_taginst
allrecords_taginst = obj_taginst.search([])
for i in allrecords_actwaardent:
for j in allrecords_taginst:
if i.tagnaam == j.tagnaam and i.unit != j.unit:
i.unit = j.unit
return i.unit
Gives error:
NameError: name 'changeunit' is not defined
I also tried putting the unit field below def changeunit(self), but didn't work either.
Store=True without #api.depends means it will execute only once while the column/field is going to be created.
so the effect you want to fire that method everytime will not be achieve with store=True without #api.depends or you need to remove store=True then it will calculate everytime when you access this field.
This are the changes you required to update in your code but before that you need to remove that column from database and after that restart server and upgrade module then it will come to there.
class opc_actuelewaardentags(models.Model):
_name = 'opc_actuelewaardentags'
unit = fields.Char(compute='changeunit')
#api.multi
def changeunit(self):
print "print"
for obj in self:
allrecords_actwaardent = self.search([])
obj_taginst = self.env['opc_taginstellingen']
allrecords_taginst = obj_taginst.search([])
for i in allrecords_actwaardent:
for j in allrecords_taginst:
if i.tagnaam == j.tagnaam and i.unit != j.unit:
obj.unit = j.unit
break
Another way:
store = False never stored any value in database so if you want to store that value in database and don't won't to be updated (means it's fixed when create or update record) then you just need to override create/write method and inside update this field's value.
#api.model
def create(self, vals):
vals.update({'field':value})
return super(class_name,self).create(vals)
When we set store=True then we need to specify when we need to compute that function. Using #api.depends('fields') in that you specify field name when the change the value of the fields then compute method is call.
name = fields.Char('name')
length = fields.Integer(compute='get_length','Length',store=True)
#api.depends('name')
def get_length(self):
self.length=len(name)
In this example when you change the name then get_length function is call.
This is not an issue, because Store = True (by the way it's recommended) tells odoo that when you compute the field store it's value to the database, so when you call this record next time the framework will retrieve the value from the database, and this value will be recomputed when any of the fields in the depends annotation is updated in the database or the UI.
so the code is not warking because when you specify the store = True after creating the value the value will be False and odoo will not recompute it until you change one of the fields that trigger the function.
you need to compute this field manually with a query to set the value for the existing records that you have in the database, don't worry about the new records odoo will compute and store the values.
so store = True. means compute and store the value in the database and don't compute it again until one of the field is edited you need to compute the value for the existing records manually for the first time.
Here's a solution that might work for you, it's not perfect because it will could call the method often yet without real need most of the time.
First add a new field that is computed.. Add that field to the UI where you need it and also hide it. It's not necessary to show it. You have to have it in the ui to force odoo to compute it.
When you compute the value of the field, also change the value of the field you really wanted to edit. Since a compute method can update multiple fields, it will work.
You don't have to make the original field as computed as far as I can say...
But since you're modifying some fields using sql which isn't really good as you admitted yourself... Why don't you change that field with SQL at the same time? Make it easier on the Odoo side without hacks and make sure that your method that changes SQL kind of changes everything as it should. It's like you're editing one half of the problem and expecting odoo to catch data changes on itself. In order to achieve that, you'd need to have some way to have the database notify odoo that something changed... unfortunately, Postgresql doesn't do that so make sure that while you're updating something from SQL, that you have consistent data after doing your changes.
i noticed this issue too, but in my case it was not necessary to store it in the database. so i kept it as store=false and the computed field worked and it has a value in the view and that's what mattered, only to have the values in the view..
so when you set store=true, only new records will have a value in the computed field, while old data won't have values in the computed field
therefore you have to reset the values of the fields used in the computation of the field(depend fields)
by writing a write statement inside the compute function, to reassign these fields their own values again, as if they are just created
for record in self.env['class'].search([]):
record.field= record.field
record.field ->>> the fields used in field computation or api.depends(fields)

How to modify a model after bulk update in django?

I try some code like this:
mymodels = MyModel.objects.filter(status=1)
mymodels.update(status=4)
print(mymodels)
And the result is an empty list
I know that I can use a for loop to replace the update.
But it will makes a lot of update query.
Is there anyway to continue manipulate mymodels after the bulk update?
Remember that Django's QuerySets are lazy:
QuerySets are lazy – the act of creating a QuerySet doesn’t involve any database activity. You can stack filters together all day long, and Django won’t actually run the query until the QuerySet is evaluated
but the update() method function is actually applied immediately:
The update() method is applied instantly, and the only restriction on the QuerySet that is updated is that it can only update columns in the model’s main table, not on related models.
So while - in your code - are applying the update call after your filter, in reality it is being applied beforehand and therefore your objects status is being changed before the filter is (lazily) applied, meaning there are no matching records and the result is empty.
mymodels = MyModel.objects.filter(status=1)
objs = [obj for obj in mymodels] # save the objects you are about to update
mymodels.update(status=4)
print(objs)
should work.
Explanations why had been given by Timmy O'Mahony.

Model save update only specific fields

I'm trying to write a webservice which performs inserts or updates.
The request is a post with headers,value which contains table name, column name and the value to be set for each column, I'm parsing the request headers and forming a parameter dict
def handel_request(request): if request.method == "POST":
param_dict = formParmDict(request)
##if insert param_dict["Model"] is {'pk':1,'field1':100,'field2':200}
##if update param_dict["Model"] is {'pk':1,'field1':100}
Model(**param_dict["Model"]).save() ## if update then sets field2 to null
return HttpResponse()
else:
return HttpResponseBadRequest()
This works fine while the .save() performs an insert.
In case of update ie if param_dict["Model"] contains {pk:1, field1:somevalue} to be updated then it sets the rest of the fields other than the ones specified in param_dict["Model"] to null. why is that? am I doing something wrong? isn't save suppose to update only the fields specified?
This is not how you're supposed to update.
Model(**param_dict["Model"]).save()
You're creating a new instance with the same id. Instead, you should get the instance, and then update it appropriately.
m = Model.objects.get(id=param_dict['id'])
m.field = param_dict['some_field']
m.save()
Or, you can use the Manager update method:
Model.objects.filter(id=param_dict['id']).update(**param_dict['Model'])
There's also the get_or_create method if you're not sure whether or not the record already exists.
You can try using a REST framework, like tasty-pie or django-rest-framework, which might alleviate some problems you're having.
Edit:
A brief summary about how save works in django. This is what I meant about whether or not an INSERT or an UPDATE is happening. Unless your post_data dict contains empty values for all the fields, read the documentation on how save works for a more thorough understanding of how django works.
So, what is happening in your case is this:
dict = {'id': 1, 'field1': 'my_value'}
m = Model(**dict)
m.id # 1
m.field1 # my_value
m.field2 # None (because you haven't set it, it defaults to None
m.save() # UPDATEs the existing instance with id 1 with ALL of the values of `m`
So, you're saving an instance that contains None values. That's why I'm suggesting you do a get, so that all the correct values are filled, before saving to the database.
Maybe you shoul use some function like this:
def insert_or_update(param_dict):
pk = param_dict.get('pk', None)
if pk:
Model.objects.filter(pk=pk).update(**param_dict)
else:
Model(**param_dict)
Model.save()

Why does put() not work when used with indexed element in Query?

I have an issue that put() does not appear to work if I access the model class as an indexed member of the Query directly; however, when I explicitly extract the class from the Query it seems to work fine. Why is this?
This code works:
class Record:
field = db.StringProperty()
rs = Record.all().filter('name = ', name_str)
if rs.count() == 1:
# assume that we only get one record in return...
r = rs[0]
r.field = some_value
r.put()
and this code doesn't (and does not raise any errors)
class Record:
field = db.StringProperty()
rs = Record.all().filter('name = ', name_str)
if rs.count() == 1:
# assume that we only get one record in return...
rs[0].field = some_value
rs[0].put()
Every time you index a query like this, it performs the query all over again, fetches the relevant result, decodes it, and returns it to you. In your second snippet, you modify one instance of the entity, immediately discard it, then fetch and store (unmodified) a second copy.
Generally, you should avoid indexing queries like this - call .get() or .fetch() instead.
For the same reason, you should avoid using .count() where possible, since it also requires running another query. If you only want one result, call .get(); if you need more, call .fetch() and then count the number of results returned.

How to delete a record in Django models?

I want to delete a particular record like:
delete from table_name where id = 1;
How can I do this in a django model?
There are a couple of ways:
To delete it directly:
SomeModel.objects.filter(id=id).delete()
To delete it from an instance:
instance = SomeModel.objects.get(id=id)
instance.delete()
MyModel.objects.get(pk=1).delete()
this will raise exception if the object with specified primary key doesn't exist because at first it tries to retrieve the specified object.
MyModel.objects.filter(pk=1).delete()
this wont raise exception if the object with specified primary key doesn't exist and it directly produces the query
DELETE FROM my_models where id=1
if you want to delete one instance then write the code
entry= Account.objects.get(id= 5)
entry.delete()
if you want to delete all instance then write the code
entries= Account.objects.all()
entries.delete()
If you want to delete one item
wishlist = Wishlist.objects.get(id = 20)
wishlist.delete()
If you want to delete all items in Wishlist for example
Wishlist.objects.all().delete()
You can also use get_object_or_404(), rather than directly using get() as while using get() we explicitly raise the error of 404.
But, while using get_object_or_404 it is automatically done.
get_object_or_404 According to 4.0 docs, Calls get() on a given model manager, but it raises Http404 instead of the model’s DoesNotExist exception.
Use it like:
For bulk deletion:
AnyModel.objects.filter(id=id).delete()
For deleting single instance, use get_object_or_404() instead of get() in the following way:
instance=get_object_or_404(anyModel,id=id)
instance.delete()
If, not found raises 404 automatically.
Extending the top voted answer by wolph
Note that you should pass request as a parameter to your delete function in your views. An example would be like:
from django.shortcuts import redirect
def delete(request, id):
YourModelName.objects.filter(id=id).delete()
return redirect('url_name')
The delete() method is used to delete model instances from a database.This method immediately deletes the object. this method returns the number of object deleted.
Example:
For deleting one record:
data_to_be_deleted = Modelname.objects.get(fieldname = value)
data_to_be_deleted.delete()
As get method returns a single object from queryset only single record will be deleted.If value supplied doesn't exist this will throw an error.If there are multilpe records in table for same value then also it will throw an error so good practice is to use a single unique value while using get.
For deleting multiple record according to a condition:
For condition based deletion filter method is used on queryset and then delete is called.
data_to_be_deleted = Modelname.objects.filter(fieldname = value)
data_to_be_deleted.delete()
For deleting all records:
For deletion of all model instances/records from database table you need to call delete method on all
data_to_be_deleted = Modelname.objects.all()
data_to_be_deleted.delete()
Note: code can be written in single line as Modelname.objects.all().delete(), but for clear understanding, I have used multiple lines.
It is as simple as calling the following.
SomeModel.objects.get(pk=1).delete()
# Or
SomeModel.objects.filter(pk=1).delete()
# SQL equivalent
# delete from table_name where id = 1;
In case you want to remove multiple records based on id,
use the __in query lookup
SomeModel.objects.fitler(pk__in=[1,2,3,4,5,...]).delete()
# SQL equivalent
# delete from table_name where id in (1,2,4,5,...);
In case you want to delete all records, use .all() to retrieve all queries,
then .delete().
SomeModel.objects.all().delete()
# SQL equivalent
# delete from table_name;
The way I do it:
instance = SomeModel.objects.get(id=1)
instance.delete()
For me it looks easy to understand, that's why I use this approach.
you can delete the objects directly from the admin panel or else there is also an option to delete specific or selected id from an interactive shell by typing in python3 manage.py shell (python3 in Linux).
If you want the user to delete the objects through the browser (with provided visual interface) e.g. of an employee whose ID is 6 from the database, we can achieve this with the following code,
emp = employee.objects.get(id=6).delete()
THIS WILL DELETE THE EMPLOYEE WITH THE ID is 6.
If you wish to delete the all of the employees exist in the DB instead of get(), specify all() as follows:
employee.objects.all().delete()

Categories

Resources