I use Django JSONField to save custom objects (that are callable) and i am confused by update_or_create. My custom objects could be realized by nesting enough dictionarys but that is a pain to work with. Thats why i want to use custom objects. I didn't want to use a Django Model for that object, as i worry about the amount of database requests.
My Minimum example:models.py
from django.db import models
import json
# Create your models here.
class F:
def __init__(self, val) -> None:
self.val = val
def __call__(self):
return "call_return_string"
def to_serializable(self) -> dict:
return {"class": "F", "val": self.val}
@classmethod
def from_serialized(classtype, dic):
assert "class" in dic.keys()
assert dic["class"] == "F"
return F(dic["val"])
class FEncoder(json.JSONEncoder):
def default(self, f: F) -> dict:
return f.to_serializable()
class FDecoder(json.JSONDecoder):
def F_obj_hook(self,dic:dict):
if "class" in dic.keys():
if dic["class"] == "F":
return F.from_serialized(dic)
return dic
def __init__(self,*args,**kwargs):
super().__init__(object_hook=self.F_obj_hook,*args,**kwargs)
class Tester(models.Model):
id = models.BigAutoField(primary_key=True)
data= models.JSONField(default=dict,encoder=FEncoder,decoder=FDecoder)
Let f = F("1")
.
Now when i run any of these:
#Tester.objects.create(data=f) #works as expected
#Tester.objects.filter(id=1).update(data=f)#works as expected
#Tester.objects.get(data=f)#works as expected
#Tester.objects.update_or_create(data=f,defaults={"data":f}) #not as expected
The first will create a database entry where the data column is filled with {"class": "F", "val": "1"}.
The second would update an existing entry, so that the data column is filled with {"class": "F", "val": "1"}.
The third gets all the database entrys where the data column is filled with {"class": "F", "val": "1"}.
But the fourth will always write a database entry where the data column is filled with "call_return_string".
It is finding the correct entrys (it will throw an Exception if there are atleast 2 rows with data column {"class": "F", "val": "1"}). But it seems like it is creating a new entry where it gets the value for the data column by evaluating f?
How can i use update_or_create so that the expected will happen (update the data colum to {"class": "F", "val": "1"} but without having to manually put in F.to_serializable())
It says in the Api reference Link that
For a detailed description of how names passed in kwargs are resolved, see get_or_create()
There some pseudocode is given for get_or_create()
params = {k: v for k, v in kwargs.items() if "__" not in k}
params.update({k: v() if callable(v) else v for k, v in defaults.items()})
obj = self.model(**params)
obj.save()
We see that v is called and there seems no way to prevent this. We can use something like this to write our own update_or_create via using the "create" function which doesn't seem to call. Use filter in combination with Tester.objects.create() and the update() function with save() to mimic the behaviour you want