How does Django foreign key access work - python

Say I have a model like this.
class Job(models.Model):
client = models.ForeignKey(Contacts, null=True)
and lets say I have job j. I know I can access the client belonging to j like this
j.client
but there is also
j.client_id
So my question is how does accessing j.client work?
Does django store client__id then when j.client is called it does a query to find the correct object?
Or is the object reference stored to j and accessing client__id is getting the id from the Contact object?
I've looked around the source code a bit but couldn't find the answer to my question

What you are probably talking about is client and client_id (single underscore).
The client_id attribute is a regular (integer) attribute. This is the foreign key that is saved to the database. You will only ever see a client_id column in the database, even though you specify the ForeignKey as client.
The client attribute is an object descriptor instance. It is a special class that overrides the __get__ and __set__ methods, so settings and accessing that attributes invokes that class's methods. This is the magic that gives you access to the actual related model instance. __get__ will retrieve the correct model instance from the database if it isn't loaded already, based on the client_id attribute. __set__ will also set the client_id attribute to the primary key of the related object, so that client_id is always up-to-date.
Note that this attribute is also available in query lookups, and is quite handy. E.g., if you have just the primary key of a foreign object, and not the model instance itself, the following queries look very similar:
job = Job.objects.filter(client__id=pk)
job = Job.objects.filter(client_id=pk)
However, underneath the first query accesses an attribute on the related object (double underscore) and performs an OUTER JOIN. The second query only ever accesses a local attribute, thus not having to perform the OUTER JOIN statement and saving performance.

This is explained in the docs:
https://docs.djangoproject.com/en/dev/ref/models/fields/#database-representation
In the database there is only client_id field (single underscore)
On the model instance you will have client attribute, when you access it this will cause Django to load the related object from the db and instantiate as another model instance.
You will also have client_id attribute (one underscore) which has the primary key value of the related object, as stored in the db field.
When doing ORM queries you are able to use client__id (double underscore) syntax to lookup against fields on the related model, eg you could also do client__name if Client model had a name field. This will become a SQL JOIN query across both models.
eg
Job.objects.get(client__id=1)
Job.objects.filter(client__name='John')
client = Client.objects.get(pk=1)
Job.objects.get(client=client)

j.client gives you the models.Model object. You can access it's properties like ...
client = j.client
id = client.id
name = client.name
But there should not be a j.client__id field. You should use j.client.id to get the id field. Although you can use j.client__id field to do filters and such.
So,
id = j.client.id # good
id = j.client__id # bad
and
job = Job.objects.get(client__id=1) # good
job = Job.objects.get(client.id=1) # bad

Related

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 / Factoryboy - unable to delete instances in test

I am writings tests for a django application and I've run into a problem were deleted objects still exist in the test database after I've supposedly deleted them.
I'm using the following factory
class CMSPageFactory(factory.DjangoModelFactory):
class Meta:
model = CMSPage
title = factory.Faker('company')
tenant = factory.SubFactory(TenantFactory)
key = factory.Faker('slug')
protected = False
in_navigation = False
active = True
This is the test I am running
def test_example_for_so(self):
page = CMSPageFactory()
page.delete()
self.assertFalse(page)
And it raises the following error:
AssertionError: <CMSPage: Fletcher LLC> is not false
I must be missing something very obvious but for the life of me I cannot figure out what. Does anyone know what I am doing wrong?
Are you sure, page still exists in the db?
Calling delete() on a django model instance (which your factory supposedly creates) will delete the database row, but nor your local python representation:
https://docs.djangoproject.com/en/2.1/ref/models/instances/#django.db.models.Model.delete
Issues an SQL DELETE for the object. This only deletes the object in the database; the Python instance will still exist and will still have data in its fields.
The object was deleted from the database but still exists in memory. From the Model delete docs:
Issues an SQL DELETE for the object. This only deletes the object in
the database; the Python instance will still exist and will still have
data in its fields. This method returns the number of objects deleted
and a dictionary with the number of deletions per object type
What you can do in the test is get the id and then try to get the object from the database, or count the objects in the database and expect 0.

Construct new django model object without loading all related models into memory

I want to be able to construct a new django object by specifying the primary keys of the related objects, rather than loading those related objects into memory to pass to the model's constructor. Is this possible? Is there a model creation factory or something similar that constructs the underlying SQL without actually loading the objects into memory?
Example:
class ObjectChildEntity(models.Model):
myobject = models.ForeignKey(MyObject)
some_data = models.TextField(null=False,blank=False)
related_stuff = models.ForeignKey(StuffModel)
I want to be able to do something like this:
new_child_entity= django.db.models.new(ObjectChildEntity, myobject__id = 123, some_data='foo', related_stuff__id = 456)
This of course isn't a real method, but what I want to accomplish is avoiding the loading of MyObject instance with id 123 into memory and the loading of StuffModel instance with id 456 into memory just for the purpose of passing these two objects into the ObjectChildEntity constructor.
Is there anything similar that exists for Django object creation without having to roll my own custom SQL?
This should work fine using the usual model class constructor and the _id name rather than an __id chained relationship as in your example code for the foreign key id:
new_child_entity = ObjectChildEntity(myobject_id=123, some_data='foo', related_stuff_id=456)
new_child_entity.save()
Or, if you've overridden the db_column field in the model field declaration, that name instead of myobject_id.

SQLAlchemy - Update ForeignKey when setting the relationship

I have a class:
class ExampleClass(Base):
__tablename__ = 'chart'
id = Column(Integer, primary_key=True)
element_id = Column(Integer, ForeignKey('anotherTable.id'))
element = relationship(AnotherClass)
element2_id = Column(Integer, ForeignKey('anotherTable2.id'))
element2 = relationship(AnotherClass2)
I want to do a lookup based on the element_id and element2_id :
class ExampleClass(Base):
...
def get_with_element2(self, element2):
return session.query(ExampleClass).\
filter_by(element_id = self.element_id,
element2_id = element2.id).first()
The problem I find is that if I instantiate a new ExampleClass object and assign it an element, the element_id field is not being set:
a = ExampleClass(element=element_obj)
a.element_id => None
How can I solve this? What's the best way to deal with this kind of situation?
First off, all the examples below assume that your ExampleClass instance is at least in the pending state if not the "persistent" state (that is, session.add(a)). In other words, if you aren't yet interacting with a Session and have not added the ExampleClass object to one, then you won't get any of the database-level behavior of relationship(), of which maintaining foreign key column values is the primary feature. You are of course free to make this assignment directly:
a = ExampleClass(element_id=element_obj.id)
but this is obviously not making use of the automation provided by the relationship() construct.
The assignment of foreign key attributes by relationship() occurs during a flush, which is a process that only occurs when interaction with the database is necessary, such as before you emit a SQL statement using session.query() or before you complete your transaction using session.commit().
Generally, the philosophy of relationship() is that you'd deal only with the "element" and "element2" attributes here, and let the foreign key attributes be handled behind the scenes. You can write your query like this:
session.query(ExampleClass).\
filter_by(element=self.element).\
filter_by(element2=element2)
The ORM will take a comparison such as SomeClass.somerelationship=someobject and convert that into the foreign-key expression SomeClass.some_fk=some_id, but the difference is, the evaluation of the ultimate value of "some_id" is deferred until the right before the query is executed. Before the query is executed, the Query() object tells the Session to "autoflush", which will have the effect of your ExampleClass row being inserted along with the primary key identifier of element_obj being assigned to the element_id attribute on the ExampleClass object.
you could get a similar effect while still using the FK attributes like this, this is mostly just to understand how it works though:
session.query(ExampleClass).\
filter_by(element_id=bindparam(callable_=lambda: self.element_id)).\
filter_by(element2_id=element2.id)
or even more explicit, do the flush first:
session.flush()
session.query(ExampleClass).\
filter_by(element_id=self.element_id).\
filter_by(element2_id=element2.id)
So to the degree you'd want to refer to foreign-key attributes like element_id explicitly, you'd also need to do the things relationship() does for you explicitly, as well. If you deal strictly with object instances and the relationship()-bound attribute, and leave typical defaults like autoflush enabled, it will generally do the "right thing" and make sure attributes are ready when needed.

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