Django: join two table on foreign key to third table? - python

I have three models
class A(Model):
...
class B(Model):
id = IntegerField()
a = ForeignKey(A)
class C(Model):
id = IntegerField()
a = ForeignKey(A)
I want get the pairs of (B.id, C.id), for which B.a==C.a. How do I make that join using the django orm?

Django allows you to reverse the lookup in much the same way that you can use do a forward lookup using __:
It works backwards, too. To refer to a “reverse” relationship, just use the lowercase name of the model.
This example retrieves all Blog objects which have at least one Entry whose headline contains 'Lennon':
Blog.objects.filter(entry__headline__contains='Lennon')
I think you can do something like this, with #Daniel Roseman's caveat about the type of result set that you will get back.
ids = B.objects.prefetch_related('a', 'a__c').values_list('id', 'a__c__id')
The prefetch related will help with performance in older versions of django if memory serves.

Related

Retrieving a child model's annotation via a query to the parent in Django?

I have a concrete base model, from which other models inherit (all models in this question have been trimmed for brevity):
class Order(models.Model):
state = models.ForeignKey('OrderState')
Here are a few examples of the "child" models:
class BorrowOrder(Order):
parts = models.ManyToManyField('Part', through='BorrowOrderPart')
class ReturnOrder(Order):
parts = models.ManyToManyField('Part', through='ReturnOrderPart')
As you can see from these examples, each child model has a many-to-many relationship of Parts through a custom table. Those custom through-tables look something like this:
class BorrowOrderPart(models.Model):
borrow_order = models.ForeignKey('BorrowOrder', related_name='borrowed_parts')
part = models.ForeignKey('Part')
qty_borrowed = models.PositiveIntegerField()
class ReturnOrderPart(models.Model):
return_order = models.ForeignKey('ReturnOrder', related_name='returned_parts')
part = models.ForeignKey('Part')
qty_returned = models.PositiveIntegerField()
Note that the "quantity" field in each through table has a custom name (unfortunately): qty_borrowed or qty_returned. I'd like to be able to query the base table (so that I'm searching across all order types), and include an annotated field for each that sums these quantity fields:
# Not sure what I specify in the Sum() call here, given that the fields
# I'm interested in are different depending on the child's type.
qs = models.Order.objects.annotate(total_qty=Sum(???))
# For a single model, I would do something like:
qs = models.BorrowOrder.objects.annotate(
total_qty=Sum('borrowed_parts__qty_borrowed'))
So I guess I have two related questions:
Can I annotate a child-model's data through a query on the parent model?
If so, can I conditionally specify the field to be annotated, given that the actual field name changes depending on the model in question?
This feels to me like a place where using When() and Case() might be helpful, but I'm not sure how I'd build the necessary logic.
The problem is that, when you are querying the base model (in multi-table inheritance), it's hard to find out which subclass the object actually is. See How to know which is the child class of a model.
The query might be achievable in theory, with something like
SELECT
CASE
WHEN child1.base_ptr_id IS NOT NULL THEN ...
WHEN child2.base_ptr_id IS NOT NULL THEN ...
END AS ...
FROM base
LEFT JOIN child1 ON child1.base_ptr_id = base.id
LEFT JOIN child2 ON child2.base_ptr_id = base.id
...
but I don't know how to translate that in Django and I think it would be too much trouble to do it. It could be done, if not anything else using raw queries.
Another solution would be to add to the base class a field that specifies which actual subclass each object is; in that case, you'd need to make as many queries as there are subclasses and join them. I don't like this solution either. Update: After I slept on this I conclude that the most Django-like solution would be not to query the parent model in the first place; simply query the submodels and join the results. I would explore the third option below only if there were performance or other practical problems.
Another idea is to create a database view (with CREATE VIEW) based on the above SQL query and translate it into a Django model with managed = False, and query that one. Maybe this is somewhat cleaner than the other solutions, but it is a bit non-standard.

Is there a way to get *only* related object's PKs from M2M relationship?

consider the below:
class Tag(Model):
...
class Post(Model):
tags = ManyToManyField(Tag) # a join table "post_tags" is created
post = Post.objects.get(pk=1)
post.tags.all() # this will cause django to join "tag" with "post_tags"
post.tags.values('pk') # even though pk is already in post_tags, django will still join with "tag" table
My need is only the list of PKs. Does anyone know of a supported way, or a clean hack where I can just get the PKs from an M2M without an additional join to the actual related table?
You can checkout django doc about prefetch_related. Quoting the docs:
prefetch_related, on the other hand, does a separate lookup for each
relationship, and does the ‘joining’ in Python. This allows it to
prefetch many-to-many and many-to-one objects, which cannot be done
using select_related, in addition to the foreign key and one-to-one
relationships that are supported by select_related.
So it should be:
post = Post.objects.filter(pk=1).prefetch_related('tags')[0]
You can define relation using through argument:
class Tag(Model):
pass
class Post(Model):
tags = ManyToManyField(Tag, through='PostTag')
class PostTag(Model):
post = models.ForeignKey(Tag)
tag = models.ForeignKey(Post)
then
PostTag.objects.filter(post_id=1).values('tag_id')
will perform in a single query, like this:
SELECT `appname_posttag`.`tag_id` FROM `appname_posttag` WHERE `appname_posttag`.`post_id` = 1

Django: list all reverse relations of a model

I would like my django application to serve a list of any model's fields (this will help the GUI build itself).
Imagine the classes (ignore the fact that all field of Steps could be in Item, I have my reasons :-) )
class Item(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
class Steps(models.Model):
item = models.OneToOneField('Item', related_name='steps')
design = models.BooleanField(default=False)
prototype = models.BooleanField(default=False)
production = models.BooleanField(default=False)
Now, when I want to list a model's fields:
def get_fields(model):
return model._meta.fields + model._meta.many_to_many
But I would also like to get the list of "related" one-to-one foreign keys to my models. In my case Item.steps would not be in that list.
I have found that model._meta.get_all_field_names does include all the related fields.
But when I call Item._meta.get_field_by_name('steps') it returns a tuple holding a RelatedObject, which does not tell me instantly whether this is a single relation or a one-to-many (I want to list only reversed one-to-one relations).
Also, I can use this bit of code:
from django.db.models.fields.related import SingleRelatedObjectDescriptor
reversed_f_keys = [attr for attr in Item.__dict__.values() \
if isinstance(attr, SingleRelatedObjectDescriptor)]
But I'm not very satisfied with this.
Any help, idea, tips are welcome!
Cheers
This was changed (in 1.8 I think) and Olivier's answer doesn't work anymore. According to the docs, the new way is
[f for f in Item._meta.get_fields()
if f.auto_created and not f.concrete]
This includes one-to-one, many-to-one, and many-to-many.
I've found out that there are methods of Model._meta that can give me what I want.
my_model = get_model('app_name','model_name')
# Reverse foreign key relations
reverse_fks = my_model._meta.get_all_related_objects()
# Reverse M2M relations
reverse_m2ms = my_model._meta.get_all_related_many_to_many_objects()
By parsing the content of the relations, I can guess whether the "direct" field was a OneToOneField or whatever.
I was looking into this answer as a starting point to identify reversed relationships for a model instance.
So, I noticed that when you get all the fields using instance._meta.get_fields(), those that are direct relationships, which are 3 types (ForeignKey, ManyToMany, OneTone), their parent class (field.__class__.__bases__) is django.db.models.fields.related.ForeignKey.
However, those that are reverse relationships inherit from django.db.models.fields.reverse_related.ForeignObjectRel. And if you take a look at this class, it has:
auto_created = True
concrete = False
So you could identify those by the attributes mentioned in the top-rated answer or by asking isinstance(field, ForeignObjectRel.
Another thing I could notice is that those reverse relationships have a field attribute which points to the direct relationship generating that reverse relationship.
Additionally, in order to exclude the fields instantiating the through table, those have through and through_fields attributes
And what about this :
oneToOneFieldNames = [
field_name
for field_name in Item._meta.get_all_field_names()
if isinstance(
getattr(
Item._meta.get_field_by_name(field_name)[0],
'field',
None
),
models.OneToOneField
)
]
RelatedObject may have a Field attribute for relations. You just have to check if this is a OneToOne field and you can retrieve only what you want
if you are using Django Rest Framework, you could use something like that for your obj:
from rest_framework.utils import model_meta
info = model_meta.get_field_info(obj)
for field in obj.__class__.__dict__.keys():
if field in info.relations and info.relations[field].to_many and info.relations[field].reverse:
#print all reverse relations
print(field)

Django ManyToMany Field

I need to make a smart menu, for which I need a ManyToMany relation.
My model is:
from django.db import models
class Health_plan(models.Model):
a = models.IntegerField ()
b = models.IntegerField ()
class Doctors_list(models.Model):
name = models.CharField(max_length=30)
hp_id = models.ManyToManyField(Health_plan)
def __unicode__(self):
return self.name
How do I make this relation in the database ? I was thinking in puting the health_plans (a,b) as columns, and the doctors as rows, with 0s and 1s to identify their covered health_plans.
Someone told me this was a misuse of a ManyToManyField, I don't know wich step to take.
Help appreciated
The approach of puting the health_plans as columns is not necessarily wrong, but it implies that you have a fixed number of health plans and that you will never add a new one.
The traditional approach for many-to-many relationships in relational databases is to introduce a table in the middle. This table will just contain the association between a doctor and a health plan.
If you have a Doctor table that contains:
id name
1 foo
2 bar
And a HealthPlan table:
id model
1 a
2 b
You then add a table Doctor_HealthPlan that is like:
doctor_id healthplan_id
1 2
2 1
2 2
The ManyToMany field type in django will automatically create this table for you. Your code is correct, but you should probably rename hp_id to something like health_plans, since it is a proxy that allows you to access the list of health plans associated to a doctor.
Django's ORM already takes care of the intermediate table so you don't have to "make this relation(ship) in the database", but given your question you obviously need to learn about proper relational model normalisation - if you don't understand the relational model you won't get nowhere with Django's ORM, nor with any other sql stuff FWIW.
For the record, in the relational model, a many to many relationship is modeled as a relation ("table" in SQL) with foreign keys on both other tables, ie:
health_plan(#health_plan_id, name, ...)
doctor(#doctor_id, firstname, lastname, ...)
doctors_health_plans(#health_plan_id, #doctor_id)
So your django models should be:
class HealthPlan(models.Model):
# no need to define an 'id' field,
# the ORM provides one by default
name = models.CharField(....)
class Doctor(models.Model):
firstname = models.CharField(....)
lastname = models.CharField(....)
health_plans = models.ManyToManyField(HealthPlan, related_name="doctors")
Then you'll be able to get all HealthPlans for a Doctor :
doc = Doctor.objects.get(pk=xxxx)
doc.health_plans.all()
and all Doctors for an HealthPlan:
plan = HealthPlan.objects.get(pk=xxxx)
plan.doctors.all()
The FineManual(tm) is your friend as usual...
You just need to save the two models first then add the healthplan instance to the doctors list. Django will handle the rest for you .
For example :
doctor_list = Doctors_list(name="Bwire")
health_plan.save()
doctor_list.save()
#Then add the plan to the doctors list.
doctor_list.hp_id.add(health_plan)
Django creates the tabels for you. In your project folder run:
python manage.py syncdb
Health_plan and Doctors_list are both tables.
'a' and 'b' are columns in Health_plan. 'Name' and 'hp_id' are columns in Doctors_list.
Django will create a column for id in each table. Django will also create a table "Doctor_list_Health_plan" to store the relation information.
Django models are Python classes, so the Python naming conventions apply. Use HealthPlan and Doctor (CapitalizeWord singular).
Your field names are a bit abstract. I suggest you use more descriptive names. Eg:
class HealthPlan(models.Model):
name = models.CharField()
extra_care = models.BooleanField()

Django ForeignKey which does not require referential integrity?

I'd like to set up a ForeignKey field in a django model which points to another table some of the time. But I want it to be okay to insert an id into this field which refers to an entry in the other table which might not be there. So if the row exists in the other table, I'd like to get all the benefits of the ForeignKey relationship. But if not, I'd like this treated as just a number.
Is this possible? Is this what Generic relations are for?
This question was asked a long time ago, but for newcomers there is now a built in way to handle this by setting db_constraint=False on your ForeignKey:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.db_constraint
customer = models.ForeignKey('Customer', db_constraint=False)
or if you want to to be nullable as well as not enforcing referential integrity:
customer = models.ForeignKey('Customer', null=True, blank=True, db_constraint=False)
We use this in cases where we cannot guarantee that the relations will get created in the right order.
EDIT: update link
I'm new to Django, so I don't now if it provides what you want out-of-the-box. I thought of something like this:
from django.db import models
class YourModel(models.Model):
my_fk = models.PositiveIntegerField()
def set_fk_obj(self, obj):
my_fk = obj.id
def get_fk_obj(self):
if my_fk == None:
return None
try:
obj = YourFkModel.objects.get(pk = self.my_fk)
return obj
except YourFkModel.DoesNotExist:
return None
I don't know if you use the contrib admin app. Using PositiveIntegerField instead of ForeignKey the field would be rendered with a text field on the admin site.
This is probably as simple as declaring a ForeignKey and creating the column without actually declaring it as a FOREIGN KEY. That way, you'll get o.obj_id, o.obj will work if the object exists, and--I think--raise an exception if you try to load an object that doesn't actually exist (probably DoesNotExist).
However, I don't think there's any way to make syncdb do this for you. I found syncdb to be limiting to the point of being useless, so I bypass it entirely and create the schema with my own code. You can use syncdb to create the database, then alter the table directly, eg. ALTER TABLE tablename DROP CONSTRAINT fk_constraint_name.
You also inherently lose ON DELETE CASCADE and all referential integrity checking, of course.
To do the solution by #Glenn Maynard via South, generate an empty South migration:
python manage.py schemamigration myapp name_of_migration --empty
Edit the migration file then run it:
def forwards(self, orm):
db.delete_foreign_key('table_name', 'field_name')
def backwards(self, orm):
sql = db.foreign_key_sql('table_name', 'field_name', 'foreign_table_name', 'foreign_field_name')
db.execute(sql)
Source article
(Note: It might help if you explain why you want this. There might be a better way to approach the underlying problem.)
Is this possible?
Not with ForeignKey alone, because you're overloading the column values with two different meanings, without a reliable way of distinguishing them. (For example, what would happen if a new entry in the target table is created with a primary key matching old entries in the referencing table? What would happen to these old referencing entries when the new target entry is deleted?)
The usual ad hoc solution to this problem is to define a "type" or "tag" column alongside the foreign key, to distinguish the different meanings (but see below).
Is this what Generic relations are for?
Yes, partly.
GenericForeignKey is just a Django convenience helper for the pattern above; it pairs a foreign key with a type tag that identifies which table/model it refers to (using the model's associated ContentType; see contenttypes)
Example:
class Foo(models.Model):
other_type = models.ForeignKey('contenttypes.ContentType', null=True)
other_id = models.PositiveIntegerField()
# Optional accessor, not a stored column
other = generic.GenericForeignKey('other_type', 'other_id')
This will allow you use other like a ForeignKey, to refer to instances of your other model. (In the background, GenericForeignKey gets and sets other_type and other_id for you.)
To represent a number that isn't a reference, you would set other_type to None, and just use other_id directly. In this case, trying to access other will always return None, instead of raising DoesNotExist (or returning an unintended object, due to id collision).
tablename= columnname.ForeignKey('table', null=True, blank=True, db_constraint=False)
use this in your program

Categories