Model save update only specific fields - python

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

Related

Override create method to Restrict user from creating a new record based on condition Odoo13

I want to restrict the user from creating a new record on a model based on a specific condition. for instance, I have two fields in the model both of them is Integer (move_item_count,item_number ) and they are computed field I compute their values. If their move_item_count != item_number I should prevent the user from creating the record and raise an error message. here is my code it gives me this error 'NoneType' object has no attribute 'id'.
here is my code :
# Overriding the Create Method in Odoo
#api.model
def create(self, vals):
# overriding the create method to add the sequence and prevent the user from
# creating a new record if a condition didn't fulfilled
for rec in self:
if rec.move_item_count != rec.item_number:
raise UserError(_("Some Item Are Missing."))
if vals.get('name', _('New')) == _('New'):
vals['name'] = self.env['ir.sequence'].next_by_code('move.sequence') or _('New')
result = super(LogisticMove, self).create(vals)
return result
Overriding the create is not recommended when applying a constraint on creation of a record as this can get really messy when 2 or more modules override the same create. You'd be better off using actual constraints (see doc right here for example).
If you feel like you must, then keep in mind that until the actual create (the super) has been called, the record is NOT in the database and also NOT present in self. All you have to go with are the values for creating this which are stored in your vals parameter.
If you inspect those values you can do the comparison and provide feedback to the user.

(Django) How to clean an unbound form?

I have a problem. I looked at the web for a solution but did not find one. If my problem already have an answer, please give me the links.
The Problem
Here is the problem:
I create a form inside one of my view with some initial values:
form = MyForm(initial=initial)
However, I do not have a full control over these initial values, thus I need to check is the form is valid. However, because the form in unbound .is_valid() always return False.
So, I tried to create an bound form from the first step:
form = MyForm(initial)
However, I do not initialize all the field, simply because I do not know their name and how many they are, thus I cannot initialize all of them.
The problem is that some of the field I do not initialize are required. Thus, the .is_valid() always return False because I do not provide required field(s).
What I know is that all of the required field have a default value.
When I created the form with initial values (i.e. MyForm(initial=initial)), the defaults value are provided. So I wish I could do something like:
form = MyForm(initial=initial)
form = MyForm(form)
However, that obviously doesn't work.
I see two potential solution to my problem:
Validate an unbound form
Get a list with all the fields and their default values (if one) (I do not know this list in advance)
I do not know how to make 2, however, I tried this for 1:
form = MyForm(initial=initial)
form.clean()
form.clean() calls a custom function where I stated (I had no problem with a bound form):
cleaned_date = super().clean()
That returns the following errors:
AttributeError at XXX
'MyForm' object has no attribute 'cleaned_data'
And well, that is kind of logical, the form is unbound, thus the data are not cleaned yet.
Any piece of advice (or complete solution) will be greatly appreciated.
Thank you for having reading me.
Wrong Solution (that works, but too wordly/ugly)
There is one solution to this problem but I am sure this is not the could one (it is way too ugly).
I first create a unbound field:
form = MyForm()
Then I read the initial value of the fields inside the string representation of the form.
for field in form:
index_name = str(field.find("name"
name = field[index_name+len('name="':]
name = name[:name.find('"')]
index = str(field).find("value")
if index >= 0: # if their is a default value
value = field[index+len('value="'):]
value = value[:value.find('"')]
initial[name] = value
Then I just need to remake the form, bound this time:
form = MyForm(initial)
However, this solution is overwhelming and I am sure there is a better way to go.
Ideally you would get the default values and do an initial.update(defaults) and hand it to the form.
If the defaults are not available to you, you can try to remove fields from the form that are not in initial.
form = MyForm(data=initial)
field_keys = list(form.fields.keys())
for field_name in field_keys:
if field_name not in initial:
form.fields.pop(field_name)
form.is_valid() # <- only validates fields in initial
Maybe you can initialize your form with your initial values with no full control, and run form.is_valid() and use the form.cleaned_data to initial another form? But Why would you have to do validating on the unbound form? I think this scenario is rare.

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 update() a single model instance retrieved by get() on Django ORM?

I have a function which currently calls Models.object.get(), which returns either 0 or 1 model objects:
if it returns 0, I create a new model instance in the except DoesNotExist clause of the function.
Otherwise, I would like to update the fields in the pre-existing
instance, without creating a new one.
I was originally attempting to
call .update() on the instance which was found, but .update()
seems to be only callable on a QuerySets. How do I get around
changing a dozen fields, without calling .filter() and comparing
the lengths to know if I have to create or update a pre-existing
instance?
With the advent of Django 1.7, there is now a new update_or_create QuerySet method, which should do exactly what you want. Just be careful of potential race conditions if uniqueness is not enforced at the database level.
Example from the documentation:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
The update_or_create method tries to fetch an object from database
based on the given kwargs. If a match is found, it updates the
fields passed in the defaults dictionary.
Pre-Django 1.7:
Change the model field values as appropriate, then call .save() to persist the changes:
try:
obj = Model.objects.get(field=value)
obj.field = new_value
obj.save()
except Model.DoesNotExist:
obj = Model.objects.create(field=new_value)
# do something else with obj if need be
if you want only to update model if exist (without create it):
Model.objects.filter(id = 223).update(field1 = 2)
mysql query:
UPDATE `model` SET `field1` = 2 WHERE `model`.`id` = 223
As of Django 1.5, there is an update_fields property on model save. eg:
obj.save(update_fields=['field1', 'field2', ...])
https://docs.djangoproject.com/en/dev/ref/models/instances/
I prefer this approach because it doesn't create an atomicity problem if you have multiple web app instances changing different parts of a model instance.
I don't know how good or bad this is, but you can try something like this:
try:
obj = Model.objects.get(id=some_id)
except Model.DoesNotExist:
obj = Model.objects.create()
obj.__dict__.update(your_fields_dict)
obj.save()
Here's a mixin that you can mix into any model class which gives each instance an update method:
class UpdateMixin(object):
def update(self, **kwargs):
if self._state.adding:
raise self.DoesNotExist
for field, value in kwargs.items():
setattr(self, field, value)
self.save(update_fields=kwargs.keys())
The self._state.adding check checks to see if the model is saved to the database, and if not, raises an error.
(Note: This update method is for when you want to update a model and you know the instance is already saved to the database, directly answering the original question. The built-in update_or_create method featured in Platinum Azure's answer already covers the other use-case.)
You would use it like this (after mixing this into your user model):
user = request.user
user.update(favorite_food="ramen")
Besides having a nicer API, another advantage to this approach is that it calls the pre_save and post_save hooks, while still avoiding atomicity issues if another process is updating the same model.
As #Nils mentionned, you can use the update_fields keyword argument of the save() method to manually specify the fields to update.
obj_instance = Model.objects.get(field=value)
obj_instance.field = new_value
obj_instance.field2 = new_value2
obj_instance.save(update_fields=['field', 'field2'])
The update_fields value should be a list of the fields to update as strings.
See https://docs.djangoproject.com/en/2.1/ref/models/instances/#specifying-which-fields-to-save
I am using the following code in such cases:
obj, created = Model.objects.get_or_create(id=some_id)
if not created:
resp= "It was created"
else:
resp= "OK"
obj.save()
update:
1 - individual instance :
get instance and update manually get() retrieve individual object
post = Post.objects.get(id=1)
post.title = "update title"
post.save()
2 - Set of instances :
use update() method that works only with queryset that what would be returned by filter() method
Post.objects.filter(author='ahmed').update(title='updated title for ahmed')

I came across a tricky trouble about django Queryset

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.

Categories

Resources