Django Model() vs Model.objects.create() - python

What it the difference between running two commands:
foo = FooModel()
and
bar = BarModel.objects.create()
Does the second one immediately create a BarModel in the database, while for FooModel, the save() method has to be called explicitly to add it to the database?

https://docs.djangoproject.com/en/stable/topics/db/queries/#creating-objects
To create and save an object in a single step, use the create() method.

The differences between Model() and Model.objects.create() are the following:
INSERT vs UPDATE
Model.save() does either INSERT or UPDATE of an object in a DB, while Model.objects.create() does only INSERT.
Model.save() does
UPDATE If the object’s primary key attribute is set to a value that evaluates to True
INSERT If the object’s primary key attribute is not set or if the UPDATE didn’t update anything (e.g. if primary key is set to a value that doesn’t exist in the database).
Existing primary key
If primary key attribute is set to a value and such primary key already exists, then Model.save() performs UPDATE, but Model.objects.create() raises IntegrityError.
Consider the following models.py:
class Subject(models.Model):
subject_id = models.PositiveIntegerField(primary_key=True, db_column='subject_id')
name = models.CharField(max_length=255)
max_marks = models.PositiveIntegerField()
Insert/Update to db with Model.save()
physics = Subject(subject_id=1, name='Physics', max_marks=100)
physics.save()
math = Subject(subject_id=1, name='Math', max_marks=50) # Case of update
math.save()
Result:
Subject.objects.all().values()
<QuerySet [{'subject_id': 1, 'name': 'Math', 'max_marks': 50}]>
Insert to db with Model.objects.create()
Subject.objects.create(subject_id=1, name='Chemistry', max_marks=100)
IntegrityError: UNIQUE constraint failed: m****t.subject_id
Explanation: In the example, math.save() does an UPDATE (changes name from Physics to Math, and max_marks from 100 to 50), because subject_id is a primary key and subject_id=1 already exists in the DB. But Subject.objects.create() raises IntegrityError, because, again the primary key subject_id with the value 1 already exists.
Forced insert
Model.save() can be made to behave as Model.objects.create() by using force_insert=True parameter: Model.save(force_insert=True).
Return value
Model.save() return None where Model.objects.create() return model instance i.e. package_name.models.Model
Conclusion: Model.objects.create() does model initialization and performs save() with force_insert=True.
Excerpt from the source code of Model.objects.create()
def create(self, **kwargs):
"""
Create a new object with the given kwargs, saving it to the database
and returning the created object.
"""
obj = self.model(**kwargs)
self._for_write = True
obj.save(force_insert=True, using=self.db)
return obj
For more details follow the links:
https://docs.djangoproject.com/en/stable/ref/models/querysets/#create
https://github.com/django/django/blob/2d8dcba03aae200aaa103ec1e69f0a0038ec2f85/django/db/models/query.py#L440

The two syntaxes are not equivalent and it can lead to unexpected errors.
Here is a simple example showing the differences.
If you have a model:
from django.db import models
class Test(models.Model):
added = models.DateTimeField(auto_now_add=True)
And you create a first object:
foo = Test.objects.create(pk=1)
Then you try to create an object with the same primary key:
foo_duplicate = Test.objects.create(pk=1)
# returns the error:
# django.db.utils.IntegrityError: (1062, "Duplicate entry '1' for key 'PRIMARY'")
foo_duplicate = Test(pk=1).save()
# returns the error:
# django.db.utils.IntegrityError: (1048, "Column 'added' cannot be null")

UPDATE 15.3.2017:
I have opened a Django-issue on this and it seems to be preliminary accepted here:
https://code.djangoproject.com/ticket/27825
My experience is that when using the Constructor (ORM) class by references with Django 1.10.5 there might be some inconsistencies in the data (i.e. the attributes of the created object may get the type of the input data instead of the casted type of the ORM object property)
example:
models
class Payment(models.Model):
amount_cash = models.DecimalField()
some_test.py - object.create
Class SomeTestCase:
def generate_orm_obj(self, _constructor, base_data=None, modifiers=None):
objs = []
if not base_data:
base_data = {'amount_case': 123.00}
for modifier in modifiers:
actual_data = deepcopy(base_data)
actual_data.update(modifier)
# Hacky fix,
_obj = _constructor.objects.create(**actual_data)
print(type(_obj.amount_cash)) # Decimal
assert created
objs.append(_obj)
return objs
some_test.py - Constructor()
Class SomeTestCase:
def generate_orm_obj(self, _constructor, base_data=None, modifiers=None):
objs = []
if not base_data:
base_data = {'amount_case': 123.00}
for modifier in modifiers:
actual_data = deepcopy(base_data)
actual_data.update(modifier)
# Hacky fix,
_obj = _constructor(**actual_data)
print(type(_obj.amount_cash)) # Float
assert created
objs.append(_obj)
return objs

Model.objects.create() creates a model instance and saves it. Model() only creates an in memory model instance. It's not saved to the database until you call the instance's save() method to save it. That's when validation happens also.

Related

Flask Babel convert to original language when insert to database

I have a set of SelectField with Flask-WTF and I convert the default language with Flask-Babel.
Here is the snippet of my code:
from flask_babel import _, lazy_gettext as _l
class PaymentStatus(enum.Enum):
PENDING = _l('PENDING')
COMPLETED = _l('COMPLETED')
EXPIRED = _l('EXPIRED')
def __str__(self):
return '{}'.format(self.value)
payment_status = [(str(_l(y)), y) for y in (PaymentStatus)]
class PaymentForm(FlaskForm):
status_of_payment = SelectField(_l('Payment Status'), choices=payment_status)
# ...
# ...
And here is my model look like:
class Payment(db.Model):
__tablename__ = 'payment'
id = db.Column(db.Integer, primary_key=True)
status_of_payment = db.Column(db.Enum(PaymentStatus, name='status_of_payment'))
# ...
# ...
And when I try to insert the value from the Flask-WTF form to my database, I got some error.
Here is the snippet how I insert it to database:
if form.validate_on_submit():
payment = Payment(
# payment_status=form.status_of_payment.data
payment_status=PaymentStatus.PENDING.value
# ...
# ...
)
The value of the enum PENDING also converted to the language on preferred language on my browser, so I got this error message:
sqlalchemy.exc.StatementError: (builtins.LookupError) "MENGUNGGU" is
not among the defined enum values
for more information: "MENGUNGGU" = is Indonesian language for "PENDING" in English.
So the problem here is, when I insert the SelectField value, it also converts the language to my preferred browser language, and my database which is PostgreSQL block it, because I don't define the value on my enum type.
So, the point of my question is, can we excluded the i18n & l10n value from Flask-Babel when we want to insert the value to a database..?, or what should I do to face this..?
You should swap values in your choices because the first element in the tuple is the actual value that will be submitted and the second one is the presentation:
payment_status = [(y.name, _l(str(y.value))) for y in PaymentStatus]
Thanks to this you'll have translated names and proper values submitted.
Enum's name should be stored in your database instead of the value.

Is there a way to check whether a related object is already fetched?

I would like to be able to check if a related object has already been fetched by using either select_related or prefetch_related, so that I can serialize the data accordingly. Here is an example:
class Address(models.Model):
street = models.CharField(max_length=100)
zip = models.CharField(max_length=10)
class Person(models.Model):
name = models.CharField(max_length=20)
address = models.ForeignKey(Address)
def serialize_address(address):
return {
"id": address.id,
"street": address.street,
"zip": address.zip
}
def serialize_person(person):
result = {
"id": person.id,
"name": person.name
}
if is_fetched(person.address):
result["address"] = serialize_address(person.address)
else:
result["address"] = None
######
person_a = Person.objects.select_related("address").get(id=1)
person_b = Person.objects.get(id=2)
serialize_person(person_a) #should be object with id, name and address
serialize_person(person_b) #should be object with only id and name
In this example, the function is_fetched is what I am looking for. I would like to determine if the person object already has a resolves address and only if it has, it should be serialized as well. But if it doesn't, no further database query should be executed.
So is there a way to achieve this in Django?
Since Django 2.0 you can easily check for all fetched relation by:
obj._state.fields_cache
ModelStateFieldsCacheDescriptor is responsible for storing your cached relations.
>>> Person.objects.first()._state.fields_cache
{}
>>> Person.objects.select_related('address').first()._state.fields_cache
{'address': <Address: Your Address>}
If the address relation has been fetched, then the Person object will have a populated attribute called _address_cache; you can check this.
def is_fetched(obj, relation_name):
cache_name = '_{}_cache'.format(relation_name)
return getattr(obj, cache_name, False)
Note you'd need to call this with the object and the name of the relation:
is_fetched(person, 'address')
since doing person.address would trigger the fetch immediately.
Edit reverse or many-to-many relations can only be fetched by prefetch_related; that populates a single attribute, _prefetched_objects_cache, which is a dict of lists where the key is the name of the related model. Eg if you do:
addresses = Address.objects.prefetch_related('person_set')
then each item in addresses will have a _prefetched_objects_cache dict containing a "person' key.
Note, both of these are single-underscore attributes which means they are part of the private API; you're free to use them, but Django is also free to change them in future releases.
Per this comment on the ticket linked in the comment by #jaap3 above, the recommended way to do this for Django 3+ (perhaps 2+?) is to use the undocumented is_cached method on the model's field, which comes from this internal mixin:
>>> person1 = Person.objects.first()
>>> Person.address.is_cached(person1)
False
>>> person2 = Person.objects.select_related('address').last()
>>> Person.address.is_cached(person2)
True

How to use make_transient() to duplicate an SQLAlchemy mapped object?

I know the question how to duplicate or copy a SQLAlchemy mapped object was asked a lot of times. The answer always depends on the needs or how "duplicate" or "copy" is interpreted.
This is a specialized version of the question because I got the tip to use make_transient() for that.
But I have some problems with that. I don't really know how to handle the primary key (PK) here. In my use cases the PK is always autogenerated by SQLA (or the DB in background). But this doesn't happen with a new duplicated object.
The code is a little bit pseudo.
import sqlalchemy as sa
from sqlalchemy.orm.session import make_transient
_engine = sa.create_engine('postgres://...')
_session = sao.sessionmaker(bind=_engine)()
class MachineData(_Base):
__tablename__ = 'Machine'
_oid = sa.Column('oid', sa.Integer, primary_key=True)
class TUnitData(_Base):
__tablename__ = 'TUnit'
_oid = sa.Column('oid', sa.Integer, primary_key=True)
_machine_fk = sa.Column('machine', sa.Integer, sa.ForeignKey('Machine.oid'))
_machine = sao.relationship("MachineData")
def __str__(self):
return '{}.{}: oid={}(hasIdentity={}) machine={}(fk={})' \
.format(type(self), id(self),
self._oid, has_identity(self),
self._machine, self._machine_fk)
if __name__ == '__main__':
# any query resulting in one persistent object
obj = GetOneMachineDataFromDatabase()
# there is a valid 'oid', has_identity == True
print(obj)
# should i call expunge() first?
# remove the association with any session
# and remove its “identity key”
make_transient(obj)
# 'oid' is still there but has_identity == False
print(obj)
# THIS causes an error because the 'oid' still exsits
# and is not new auto-generated (what should happen in my
# understandings)
_session.add(obj)
_session.commit()
After making a object instance transient you have to remove its object-id. Without an object-id you can add it again to the database which will generate a new object-id for it.
if __name__ == '__main__':
# the persistent object with an identiy in the database
obj = GetOneMachineDataFromDatabase()
# make it transient
make_transient(obj)
# remove the identiy / object-id
obj._oid = None
# adding the object again generates a new identiy / object-id
_session.add(obj)
# this include a flush() and create a new primary key
_session.commit()

How to update object with another object in get_or_create?

I have to tables wit similar fields and I want to copy objects from one table to another.
Problem that object could be absent in second table, so I have to use get_or_create() method:
#these are new products, they all are instances of NewProduct model, which is similar
#to Product model
new_products_list = [<NewProduct: EEEF0AP>, <NewProduct: XR3D-F>,<Product: XXID-F>]
#loop over them and check if they are already in database
for product in new_products_list:
product, created = Products.objects.get_or_create(article=product.article)
if created:
#here is no problem because new object saved
pass
else:
# here I need to code that will update existing Product instance
# with values from NewProduct instance fields
The case is that I don't want to list all fields for update manually, like this,, because I have about 30 of them:
update_old_product = Product(name=new_product.name,article= new_product.article)
Please advise more elegant way than above
You can loop over the field names and update them in the the other Product instance:
for new_product in new_products_list:
# use different variable names, otherwise you won't be able to access
# the item from new_product_list here
product, created = Products.objects.get_or_create(article=new_product.article)
if not created:
for field in new_product._meta.get_all_field_names():
setattr(product, field, getattr(new_product, field))
product.save()
You could try something like this.
def copy_fields(frm, to):
id = to.id
for field in frm.__class__._meta.fields:
setattr(to, field.verbose_name, field._get_val_from_obj(frm))
to.id = id
This is similar to Ashwini Chaudhary, although I think it will take care of that error that you mentioned in the comments.
new_products_list= (
# obj1, obj2, obj3 would be from [<NewProduct: EEEF0AP>, <NewProduct: XR3D-F>,<Product: XXID-F>] in your question
# NewProduct would just be the model that you import
# NewProduct._meta.fields would be all the fields
(obj1, NewProduct, NewProduct._meta.fields,),
(obj2, NewProduct, NewProduct._meta.fields,),
(obj3, NewProduct, NewProduct._meta.fields,),
)
for instance, model, fields in new_products_list:
new_fields = {}
obj, created = model.objects.get_or_create(pk=instance.article) # this is pretty much just here to ensure that it is created for filter later
for field in fields:
if field != model._meta.pk: # do not want to update the pk
new_fields[field.name] = request.POST[field.name]
model.objects.filter(pk=question_id).update(**new_fields) # you won't have to worry about updating multiple models in the db because there can only be one instance with this pk
I know this was over a month ago, but I figured I would share my solution even if you have already figured it out

Error with dynamic classes and sqlalchemy

I am trying to write a logging system, which uses dynamic classes to make tables. Getting the classes created, and the tables created seems to be working fine, but trying to put entries into them is lead to an error message regarding mapping, below is the sample code and the error message.
Base = declarative_base()
#my init function
def tableinit(self,keyargs):
self.__dict__ = dict(keyargs)
#table creation
tableName = "newTable"
columnsDict["__tablename__"] = tableName
columnsDict["__init__"] = tableinit
columnsDict["id"] = Column("id",Integer, autoincrement = True, nullable = False, primary_key=True)
columnsDict["pid"] = Column("pid",Integer, ForeignKey('someparenttable.id')) #someparenttable is created with a hard coded class
newTable = type(tableName,(Base,),columnsDict)
tableClassDict[tableName]=newTable
#when doing an entry
newClassInst = subEntryClassDict[tableName]
newEntry = newClassInst(dataDict)
entryList.append(newEntry) # this is called in a for loop with the entries for someparenttable's entries also
self.session.add_all(entryList) # at this point the error occurs
The error:
UnmappedInstanceError: Class 'newTable' is mapped, but this instance lacks instrumentation. This occurs when the instance is created before sqlalchemy.orm.mapper(module.newTable) was called.
This is easier if you create a function to return a class that you set up normally. I've tried something like this and it works:
def getNewTable( db, table ):
class NewTable( Base ):
__tablename__ = table
__table_args__ = { 'schema': db }
id = Column( ...
return NewTable
newClassInst = getNewTable( 'somedb', 'sometable' )
newRow = newClassInst( data )
This problem is caused by lack of instruments function interfaces for the orm as the error description says. And it is actually caused by self.__dict__ = dict(keyargs) I think.
So this can be solved by reconstruct the init, which do not modify the injected functions by ORM.
Turn this
#my init function
def tableinit(self,keyargs):
self.__dict__ = dict(keyargs)
To
#my init function
def tableinit(self,**kwargs):
self.__dict__.update(kwargs)

Categories