Saving FK model field by fieldname_id allows non-existing FK relations - python

I have a model consisting of two ForeignKey fields as below. (This is a ManytoMany through field)
class EntityConceptLink(models.Model):
entity = models.ForeignKey(Entity)
standard_concept = models.ForeignKey(StandardConcept)
other fields...
I am trying to create objects like so:
EntityConceptLink.objects.get_or_create(
entity_id=entity_id, # passing in an integer, should be PK of Entity
standard_concept=concept) # passing in a model instance
The problem is, when I pass in an entity_id corresponding to an nonexistent Entity, the above code somehow nonetheless saves the model instance. It's only later when I try to do entityconceptlinkinstance.entity that a DoesNotExist: Entity matching query does not exist is raised.
Shouldn't the model fail validation during the attempt to save? Am I doing something wrong here?

Yeah I think below code should work fine.
EntityConceptLink.objects.get_or_create(
entity__id=entity_id, # Believe `id` would be the primary key
standard_concept=concept)
In the above case it will raise error if entity model do not find the required id. See the use double __ instead of single _ in the entity id.

Related

Django: Get list of model object fields depending on their type?

Listing fields of models and their objects has been answered successfully here. But i want a generalized way of processing on object fields depending on their types, so that i can serialize them manually in dictionary. e.g: ManyToMany,OneToMany...,FileField,...,Relations_field(Field not present in the model but defined as foreign key in some other model).
I don't know how much of a reasonable demand it is to manually serialize your model objects, but my purposes are more pedagogical than performance oriented.
This is a small function where i am converting model objects to JSON serializable dictionaries.
def obj_to_dict(obj):
fields = obj._meta.get_fields()
obj_dict = {}
# Here I want to process fields depending upon their type:
for field in fields:
if field is a relation:
## DO SOMETHING
elif Field is ManyToMany or OneToMany ... :
## DO SOMETHING
elif field is image field:
## DO SOMETHING
else:
## DO SOMETHING
return obj_dict
The answer here, does give a way but not quite what i want, way for more generalization depending upon fields.
Short Answer: get_internal_type() method is exactly what is required here. It returns a string of Field Type, e.g: "FileField".
Caveat: Reverse relations not locally defined in a model and fields defined as a foreign key to some other model, both are returned as "ForeignKey" with this method.
Detailed:
To process over fields you may need to access the field value,you can do so in all cases through getattr(model_object,field.name) inside the loop.
To differentiate between reverse relations and foreign key you can use auto_created property. As ReverseRelations return True on auto_created property and ForeignKey field don't.
ImageField is just a file field, but it has properties such as width,height,url etc. so it can be checked through getattr(obj,field.name).width property.
You can iterate over the fields as:
def obj_to_dict(obj):
fields = obj._meta.get_fields()
obj_dict = {}
# Here I want to process fields depending upon their type:
for field in fields:
field_val = getattr(obj,field.name)
if field.get_internal_type()=="ForeignKey" and not field.auto_created:
## for foreign key defined in model class
elif field.get_internal_type()=="ForeignKey" and field.auto_created:
## for reverse relations not actually defined in model class.
elif field.get_internal_type()=="ManyToMany"/ "OneToMany" ... :
DO SOMETHING
# for image field
elif field.get_internal_type() == "FileField" and field_val.width:
## DO SOMETHING
else:(for other locally defined fields not requiring special treatment)
## DO SOMETHING
return obj_dict

Using '_id' in Django

I am a bit confused how Django handles '_id' property when we use ORM with some models that use foreign key.
For example:
class CartItem(models.Model):
user = models.ForeignKey('accounts.CustomUser', related_name='carts', on_delete=models.CASCADE, verbose_name='User')
product = models.ForeignKey('pizza.Product', related_name='carts', on_delete=models.CASCADE, verbose_name=_('Product'))
quantity = models.SmallIntegerField(verbose_name=_('Quantity'))
And when I use ORM with 'filter' I can easily use something like:
CartItem.objects.filter(user=1, product=1, quantity=1)
And Django kind of 'see' that I refer to 'id', but when I use exacly the same line of code, but with 'create' instead of 'filter':
CartItem.objects.create(user=1, product=1, quantity=1)
Then it throws an error saying:
Cannot assign "1": "CartItem.user" must be a "CustomUser" instance.
And to create it I need to use:
CartItem.objects.create(user_id=1, product_id=1, quantity=1)
Why is that? Is there some rule here that I don't understand?
This is the database representation of the ForeignKey [Django-doc]. A reference to model object is represented as:
Behind the scenes, Django appends "_id" to the field name to create its database column name. In the above example, the database table for the Car model will have a manufacturer_id column. (You can change this explicitly by specifying db_column) However, your code should never have to deal with the database column name, unless you write custom SQL. You’ll always deal with the field names of your model object.
So you could say that Django will construct a "twin" column, with an _id suffix. This column has the same type as the type of the primary key of the model you target, and that column will thus contain the primary key of the model object you use. Note that you can use a different field to which you target by specifying the to_field=… parameter [Django-doc].
The ForeignKey itself thus does not exists at the database, that is the logic of Django that will use the primary of that object, and store that in a column with, by default, an _id suffix.

Django setting many_to_many object while doing a bulk_create

I am using Django 1.9 and am trying the bulk_create to create many new model objects and associate them with a common related many_to_many object.
My models are as follows
#Computational Job object
class OT_job(models.Model):
is_complete = models.BooleanField()
is_submitted = models.BooleanField()
user_email = models.EmailField()
#Many sequences
class Seq(models.Model):
sequence=models.CharField(max_length=100)
ot_job = models.ManyToManyField(OT_job)
I have thousands of Seq objects that are submitted and have to be associated with their associated job. Previously I was using an iterator and saving them in a for loop. But after reading realized that Django 1.9 has bulk_create.
Currently I am doing
DNASeqs_list = []
for a_seq in some_iterable_with_my_data:
# I create new model instances and add them to the list
DNASeqs_list.append(Seq(sequence=..., ))
I now want to bulk_create these sequence and associate them with the current_job_object.
created_dnaseqs = Seq.objects.bulk_create(DNASeqs_list)
# How do I streamline this part below
for a_seq in created_dnaseqs:
# Had to call save here otherwise got an error
a_seq.save()
a_seq.ot_job.add(curr_job_obj)
I had to call "a_seq.save()" in for loop because I got an error in the part where I was doing "a_seq.ot_job.add(curr_job_obj)" which said
....needs to have a value for field "seq" before this many-to-many relationship can be used.
Despite reading the other questions on this topic , I am still confused because unlike others I do not have a custom "through" model. I am confused with how best to associate the OT_Job with many Seqs with minimal hits to database.
From the docs https://docs.djangoproject.com/en/1.9/ref/models/querysets/#bulk-create:
If the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does.
It does not work with many-to-many relationships.
bulk_create literally will just create the objects, it does not retrieve the PK into the variable as save does. You would have to re-query the db to get your newly created objects, and then create the M2M relationships, but it sounds like that would not be appropriate and that your current method is currently the best solution.

Django - bulk_create objects with order_with_respect_to attribute

Hi I have list of model objects: my_objects, which should be saved in a databse.
This model has order_with_respect_to property in its Meta class.
When I try to bulk_create this list I got:
null value in column "_order" violates not-null constraint" during bulk_create
When I just iterate over elements and invoke save() on every each of them. Everything is fine, but such sequential database access doesn't satisfy me...
I've tried to invoke signals.pre_save.send function, but this didn't change the situation.
This worked when I've invoked _save_table, on every signle element from my_objects, but _save_table is the heaviest part of save() method, so I gained nothing...
Is there a possibility to save batch of django objects with only one database connection?
I'm using postgresql.
It's just a field and you can set "_order" manually or calculate that before bulk_create.
# Model
class Product(models.Model):
name = models.CharField(max_length=255)
# Bulk create example
# Data
product_data_list = [{"name": "Apple"}, {"name": "Potato"}]
# Add "_order" field for each product
for index, product_data in enumerate(product_data_list):
product_data["_order"] = index
# Create
Product.objects.bulk_create(Product(**product_data) for product_data in product_data_list)
From the docs:
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#bulk-create
If the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does.
I am guessing that your id was on autoincrement and now it isn't being saved, which is being referenced by _order.

How do I get the manager object for a Model foreign key field?

I'm trying to retrieve the Manager (or Model) for a Django foreign key. This should be straightforward but I can't seem to find the right attribtues.
class ModelA(models.Model):
pass
class ModelB(models.Model):
thing = models.ForeignKey(ModelA)
Let's say I have the variable modelBInstance and the string "thing" and I want to get ModelA. I've followed (exhaustively, I think) the obvious looking attributes of each object using getattr. Either I'm missing something or it's not possible. And I don't think it's not possible.
All the relevant information about fields is stored in the _meta class of the model. In there, you'll see a get_field_by_name method that will return the actual foreign key field. From there, you can get the model it points at via rel.to.
thing = ModelB._meta.get_field_by_name('thing')[0]
print thing.rel.to
Couple of things:
1. Model instances don't have managers, Model's do.
2. To get the manager, of the foreign key you will have to first reference its class and then reference it manager.
type(getattr(modelBInstance,'thing')).objects would give you access to the manager.

Categories