django package for accessing original instance values - python

I want to override the save() method of my model and check changes to some of the fields:
def save(self):
if self.counter != self.original_counter: # that's what I want
...
I saw this question was asked before and the answer was to get the object from the db and compare the db value with the current value:
def save(self):
original = MyModel.objects.get(pk=self.pk)
if self.counter != original.counter:
...
but that's a waste of a db query, it's easy to get what I want if on every instance initialization the __init__ method will initialize 2 attributes for each field - obj.<attr> and also obj.original_<attr>, do I need to implement it myself or is there a django package that can do it for me?

I don't think there is a way you can get the original values like that. Even if you implement the pseudo original_* fields yourself, you'd end up doing a MyModel.objects.get(...) anyways.
The issue is that inside the save() method, the object has already been saved. So you see the new values. There is no way to see the original values without querying the database.

Related

Why does the attribute get set but does not persist

I am trying to dynamically add an attribute to at runtime using the following snippets of code:
View
...
for appellation in queryset:
if appellation.id in used_id_set:
appellation.is_used_flag()
print(appellation.is_used)
# prints true as expected
else:
appellation.is_not_used_flag()
first = queryset.first()
print(first.is_used)
# prints AttributeError: 'Appellation' object has no attribute 'is_used'
In Model
...
def is_used_flag(self):
self.is_used = True
def is_not_used_flag(self):
self.is_used = False
Why does it work correctly when in the loop but when I try to retrieve the attribute from an instance after it does not work? I have run into the same issue using setattr, appellation.is_used = True and modifying __dict__. Also is there a better way to do this?
I have referenced these posts:
Why can't you add attributes to object in python? I do have a dict but it does not seem to "persist" after the loop
How to assign a new class attribute via __dict__? Same issue as mentioned above
Dynamically defining instance fields in Python classes Same as above
Update
Since both answers mention similar things, I should clarify what my intentions are. I do not want to actually persist the value in the DB. I just want to serialize it and use it in the front end.
The Queryset API in django (often) returns other querysets, which are in turn evaluated by accessing the database. By doing queryset.first() you're executing another database call, where your attributes have not been yet set.
If you need to save this is_used flag between querysets (persist the change), I suggest you add a BooleanField on your model, or perhaps find another way to do what you want, as in memory attributes will not get returned by using the queryset API.
If you want the change to persist you will need to call self.save() after setting is_used, assuming that is_used is a field on the Appellation model.
models.py
from django.db import models
class Appellation(models.Model):
# ... rest of code ...
is_used = models.BooleanField(default=False)
def is_used_flag(self):
self.is_used = True
self.save()
def is_not_used_flag(self):
self.is_used = False
self.save()
Note that Django instances are still Python objects so adding an attribute dynamically will work in the same way, this is why it prints True as expected in the code you provided.

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)

Django: Can you tell if a related field has been prefetched without fetching it?

I was wondering if there is a way in Django to tell if a related field, specifically the "many" part of a one-to-many relationship, has been fetched via, say, prefetch_related() without actually fetching it?
So, as an example, let's say I have these models:
class Question(Model):
"""Class that represents a question."""
class Answer(Model):
"""Class the represents an answer to a question."""
question = ForeignKey('Question', related_name='answers')
Normally, to get the number of answers for a question, the most efficient way to get this would be to do the following (because the Django docs state that count() is more efficient if you just need a count):
# Note: "question" is an instance of class Question.
answer_count = question.answers.count()
However in some cases the answers may have been fetched via a prefetch_related() call (or some way, such as previously having iterated through the answers). So in situations like that, it would be more efficient to do this (because we'd skip the extra count query):
# Answers were fetched via prefetch_related()
answer_count = len(question.answers.all())
So what I really want to do is something like:
if question.answers_have_been_prefetched: # Does this exist?
answer_count = len(question.answers.all())
else:
answer_count = question.answers.count()
I'm using Django 1.4 if it matters. Thanks in advance.
Edit: added clarification that prefetch_related() isn't the only way the answers could've been fetched.
Yes, Django stores the prefetched results in the _prefetched_objects_cache attribute of the parent model instance.
So you can do something like:
instance = Parent.objects.prefetch_related('children').all()[0]
try:
instance._prefetched_objects_cache[instance.children.prefetch_cache_name]
# Ok, it's pefetched
child_count = len(instance.children.all())
except (AttributeError, KeyError):
# Not prefetched
child_count = instance.children.count()
See the relevant use in the django source trunk or the equivalent in v1.4.9

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

Diff django model objects with ManyToMany fields

I have a situation where I need to notify some users when something in DB changes. My idea is to catch pre_save and post_save signal and make some kind of diff and mail that. Generally it works good, but I don't know how to get diff for m2m fields.
At the moment I have something like this:
def pre_save(sender, **kwargs):
pk = kwargs['instance'].pk
instance = copy.deepcopy(sender.objects.get(pk=pk))
tracking[sender] = instance
def post_save(sender, **kwargs):
instance = copy.deepcopy(kwargs['instance'])
print diff(instance, (tracking[sender])) # TODO: don't print, save diff somewhere
Diff function should work for every model (at the mommet I have four model classes). With deepcopy I can save old model, but I don't know how to save m2m fields because they are in separate table (yes, I know I can get this data, but at the momment of execution I don't know what fields are m2m and I wouldn't like to create different slot for every model). What I would like is generic solution, so I can just add models later without thinking about notification part.
My plan is to call get_data() and clear_data() functions after save() in view to clean diff that slots have generated.
Is this good way of doing this? Is there a better way? Is there django application that can do this job for me?
Excuse my English, it's not my native language.
First of all, you don't need to use deepcopy for this. Re-querying the sender from the database returns a "fresh" object.
def pre_save(sender, **kwargs):
pk = kwargs['instance'].pk
instance = sender.objects.get(pk=pk)
tracking[sender] = instance
You can get a list of all the many-to-many fields for a class, and check the values related to the current instance:
for field in sender._meta.local_many:
values = field.value_from_object(instance).objects.all()
# Now values is a list of related objects, which you can diff

Categories

Resources