Django deserialize to model instance without saving to DB - python

I want to serialize an object to Json, then deserialize it as the object, without save it to DB (it's already saved). The reason is that the current state of the model may be different from the state when I serialized it.
This is how I currently serialize the object:
ser_obj = serializers.serialize("json", [self.instance])
Now, as I understand, in order to deserialize I can do something like:
for obj in serializers.deserialize("json", ser_obj):
obj.save()
But that will save the object to DB, which I don't want to.
I guess I can also do something like:
MyModel(field1 = ser_obj['field1'],
field2 = ser_obj['field2']
)
But it seems wrong.
So any idea how can I deserialize json object into Django model without saving to DB?
I did notice that if I use the 'save()', I can then use the obj.object to get the object.

You can deserialise without saving to the database.
for obj in serializers.deserialize("json", ser_obj):
do_something_with(obj)
This will return DeserializedObject instances. You can find more info in the Django docs

I wanted to solve a similar problem and found you can access the objects without saving by accessing the .object property:
for deserializedobject in serializers.deserialize("json", ser_obj):
obj = deserializedobject.object
print(object.field1)
Note that if objects accessed in this way have a related field (eg ForeignKey) then that will point to the current database object with that pk even if one has just been deserialised.
ie if A points to B and both A and B are serialised and later deserialised then A will point to B on the database which may have different values than the deserialised.
Essentially you cannot use this approach to freeze a snapshot of a group of related objects and interrogate them at a later date without executing a save().
(as at Django 3.2.3)

Related

How does Django loaddata know which fields make the natural key?

I am using Django's dumpdata to save data and loaddata to reload it. I am also using natural keys. My model looks similar to this:
class LinkManager(models.Manager):
def get_by_natural_key(self, url):
return self.get(url=url)
class Link(models.Model):
objects = LinkManager()
title = models.CharField(max_length=200)
url = models.URLField()
def natural_key(self):
return (self.url, )
If I export and reimport the data, Django recognizes that the objects already exist and doesn't create duplicates. If I change the title, it correctly updates the objects. However, if I change the URL, it correctly treats it as a new object - although I forgot to mark url unique! How does it guess my intent?
How does django know that my url field is the natural key? There is no get_natural_fields function. Django could call natural_key on the class instead of an instance to get the fields, but that seems really brittle:
>>> [f.field_name for f in Link.natural_key(Link)]
['url']
The reason I want to know this is that I am writing my own special importer (to replace my use of loaddata), and I would like to take advantage of natural keys without hardcoding the natural key (or the "identifying" fields) for each model. Currently, I "identify" an object by it's unique fields - I do:
obj, created = Model.objects.update_or_create(**identifying, defaults=other)
but Django seems to be choosing it's "identifying" fields differently.
I think I've found it out. Django does not just call get_by_natural_key, it first calls natural_key. How does it do that, if it doesn't have an instance of the model?
It simply creates an instance, not backed by the database, from the constructor (d'oh!): Model(**data). See build_instance in django.core.serializers.base. Then it calls natural_key on the newly created object, and immediately get_by_natural_key to retrive the pk that belongs to the object, if present in the database. This way, Django does not need to know what fields the natural key depends on, it just needs to know how to get it from data. You can just call save() on the retrieved instance, if it is in the database it will have a pk and will update, if not it will create a new row.
Source of the build_instance function (Django 1.11.2):
def build_instance(Model, data, db):
"""
Build a model instance.
If the model instance doesn't have a primary key and the model supports
natural keys, try to retrieve it from the database.
"""
obj = Model(**data)
if (obj.pk is None and hasattr(Model, 'natural_key') and
hasattr(Model._default_manager, 'get_by_natural_key')):
natural_key = obj.natural_key()
try:
obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
except Model.DoesNotExist:
pass
return obj

Making a copy of an object's initial state before saving

For my Django application, I'm looking to keep a full edit history for all objects. As part of this, I've overridden the model's save() method, part of which is shown below:
# Replicate the current version (from the db) with all attributes unchanged
new_ver = self.__class__.objects.get(pk=self.pk).save(force_insert=True)
# Update the current version in the database with the new attributes
super(CodexBaseClass, self).save(*args, force_update=True, **kwargs)
The 'self' that's passed to the save() method is the NEW version of the object that's been generated by the form. What this code is attempting to do is
(1) Make a copy of the object as it currently appears in the database (ie: copy the data as it was before the form modified it), then force an insert of this data so it's copied as a new row
(2) Update the existing row with the new version of the object that's been submitted through the form.
The problem is with the first line of the two lines of code - It generates a DoesNotExist exception. The object does exist, so I'm currently thinking that the issue is that the database row it's trying to read is currently locked.
So my question is: Is there a way I can modify/replace the first line so that I have a copy of the initial data, as it was before the form modified it?
Thanks.
If you want to insert a new object with same attributes, you only need to change the primary key of your object, and save it.
new_ver = self.__class__.objects.get(pk=self.pk)
new_ver.pk = None
new_ver.save()
Using None as primary key will auto generates it. You can have more information if you look at the django documentation.
If you need you can also make a copy of your object, be careful the cost can be expensive :
from copy import deepcopy
ver = self.__class__.objects.get(pk=self.pk)
new_ver = deepcopy(ver)
new_ver.pk = None
new_ver.save()
# Do what you need with ver object
You should take a look at django-reversion.
django-reversion is an extension to the Django web framework that
provides version control for model instances.
Documentation: link
Features
Roll back to any point in a model instance’s history.
Recover deleted model instances.
Simple admin integration.

How to store django objects as session variables ( object is not JSON serializable)?

I have a simple view
def foo(request):
card = Card.objects.latest(datetime)
request.session['card']=card
For the above code I get the error
"<Card: Card object> is not JSON serializable"
Django version 1.6.2. What am I doing wrong ?
In a session, I'd just store the object primary key:
request.session['card'] = card.id
and when loading the card from the session, obtain the card again with:
try:
card = Card.objects.get(id=request.session['card'])
except (KeyError, Card.DoesNotExist):
card = None
which will set card to None if there isn't a card entry in the session or the specific card doesn't exist.
By default, session data is serialised to JSON. You could also provide your own serializer, which knows how to store the card.id value or some other representation and, on deserialization, produce your Card instance again.
Unfortunately the suggested answer does not work if the object is not a database object but some other kind of object - say, datetime or an object class Foo(object): pass that isn't a database model object.
Sure, if the object happen to have some id field you can store the id field in the database and look up the value from there but in general it may not have such a simple value and the only way is to convert the data to string in such a way that you can read that string and reconstruct the object based on the information in the string.
In the case of a datetime object this is made more complicated by the fact that while a naive datetime object can print out format %Z by simply not printing anything, the strptime object cannot read format %Z if there is nothing, it will choke unless there is a valid timezone specification there - so if you have a datetime object that may or may not contain a tzinfo field you really have to do strptime twice once with %Z and then if it chokes without the %Z. This is silly. It is made even sillier by the fact that datetime objects have a fromtimestamp function but no totimestamp function that uniformly produces a timestamp that fromtimestamp will read. If there is a format code that produces timestamp number I haven't found one and again, strftime/strptime suffer from the fact that they are not symmetric as described above.
There are two simple ways to do this.
If each object belongs to a single session at the same time, store session id as a model field, and update models.
If an object can belong to multiple sessions at the same time, store object.id as a session variable.
#Martijn is or might be the correct way to save the object in session variables.
But the issue was solved by moving back to Django 1.5. So this issue is specifically for django 1.6.2.
Hope this helps.
Objects cannot be stored in session from Django 1.6 or above.
If you don't want to change the behavior of the cookie value(of a object), you can add a dictionary there. This can be used for non database objects like class object etc.
from django.core.serializers.json import DjangoJSONEncoder
import json
card_dict = card.__dict__
card_dict .pop('_state', None) #Pop which are not json serialize
card_dict = json.dumps(card_dict , cls=DjangoJSONEncoder)
request.session['card'] = card_dict
Hope it will help you!
--> DON'T EVER FEEL LIKE DOING SOMETHING LIKE THIS! <--
Django using session and ctypes: Plz read Martijn Pieters explanation comment below.
class MyObject:
def stuff(self):
return "stuff..."
my_obj = MyObject()
request.session['id_my_obj'] = id(my_obj)
...
id_my_obj = request.session.get('id_my_obj')
import ctypes
obj = ctypes.cast(id_my_obj, ctypes.py_object).value
print(obj.stuff())
# returns "stuff..."
In my case, as the object is not serializable (selenium webdriver), I had to use global variables, which is working great.

django save() method saving the manytomany field why do we need save_m2m()

I have refered this documentation page for save() method
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
But in my view I have the following code
def saveEvent(request):
eventForm = EventForm(request.POST)
if eventForm.is_valid():
event=eventForm.save(commit=False)
requestor=None
if(event.is_hp_requestor):
#Save and get requestor
requestorHPPersonForm = PersonHiddenForm(request.POST, prefix = 'hp_requestor')
requestorHPEmployeeForm = HPEmployeeForm(request.POST, prefix = 'hp_requestor')
requestor=saveHPEmployeeHelper(requestorHPEmployeeForm, requestorHPPersonForm).person
else:
requestorHPPersonForm = PersonHiddenForm(request.POST, prefix = 'hp_requestor')
requestor=get_object_or_404(Person, pk=requestorHPPersonForm.data[requestorHPPersonForm.prefix+'-'+'email'])
if (requestor is not None) and eventForm.is_valid():
event.requestor_new=requestor
event.save()
if request.POST['opportunities']:
for str_sfid in request.POST['opportunities'].split(','):
sfid = int(str_sfid)
opportunity_object, dummy = Opportunity.objects.get_or_create(sfdc_id=sfid)
event.opportunities.add(opportunity_object)
event.save()
return HttpResponseRedirect(reverse('dashboard'))
else:
errors = eventForm.errors
return HttpResponse(json.dumps(errors), status=500, mimetype='application/json')
In my view I do not invoke the save_m2m() method, but still it save many2many field data.
How is it works, if this is working then why we need the save_m2m() method?
The docs you linked to explain this:
Calling save_m2m() is only required if you use save(commit=False). When you use a simple save() on a form, all data – including many-to-many data – is saved without the need for any additional method calls.
Also, it looks like event is itself a model instance rather than a form. save_m2m is required for forms, not model instances.
To paraphrase the explanation in the docs: a form's save method, if called with commit=True (the default) does two things - it creates a new model instance using the form's cleaned data, and it writes that model instance to the database. Then, if there are any many-to-many relationships, it writes those to the database as well. It does that after writing the instance because you can't write a many-to-many relationship until the instance has a primary key, which is auto-generated when you write it to the database.
If you call the form's save method with commit=False, it creates the new model instance but it does not write it to the database. Thus, it has no primary key yet and the many-to-many information can't be saved either. After you save the model instance and therefore generate a primary key for it, the many-to-many information is still stored only in the form object. So you need to notify the form object that it's now OK to save the many-to-many information, by calling save_m2m.
edit Since you've added more of your code I can see more clearly what you're asking. The above code will not save many-to-many relationships that are set in your EventForm instance. Is that what your loop to set opportunities is doing?

Django save or update model

I am using Django 1.5.1 and I want to save or update model.
I read the django document and I met the get_or_create method which provides saving or updating. There is a usage like;
Model.objects.get_or_create(name='firstName',surname='lastName',defaults={'birthday': date(1990, 9, 21)})
defaults field is using only for getting. While it is setting phase, name and surname are only set. That is what I understand from the document.
So I want to do something different that setting name,surname and birthDay, but getting name and surname excluding birthdate. I could not see the way to do that in the document and another place.
How can I do this?
Thank you!
get_or_create provides a way of getting or creating. Not saving or updating. Its idea is: I want to get a model, and if it doesn't exist, I want to create it and get it.
In Django, you don't have to worry about getting the name or the surname or any attribute. You get an instance of the model which has all the attributes, I.e.
instance = Model.objects.get(name='firstName',surname='lastName')
print instance.birthday
print instance.name
print instance.surname
An overview of the idea could be: a Model is a data structure with a set of attributes, an instance is a particular instance of a model (uniquely identified by a primary_key (pk), a number) which has a specific set of attributes (e.g. name="firstName").
Model.objects.get is used to go to the database and retrieve a specific instance with a specific attribute or set of attributes.
Since Django 1.7 there's update_or_create:
obj, created = Person.objects.update_or_create(
first_name='John',
last_name='Lennon',
defaults=updated_values
)
The parameters you give are the ones that will be used to find an existing object, the defaults are the parameters that will be updated on that existing or newly created object.
A tuple is returned, obj is the created or updated object and created is a boolean specifying whether a new object was created.
Docs: https://docs.djangoproject.com/en/1.8/ref/models/querysets/#update-or-create

Categories

Resources