I'm trying to refactor my schema so that a field that is currently a simple ForeignKey becomes a GenericForeignKey. So, in my model, this:
ref_track = models.ForeignKey(Track, unique=False, related_name='posts_by_ref')
...becomes this:
ref_content_type = models.ForeignKey(ContentType)
ref_id = models.PositiveIntegerField()
ref_object = generic.GenericForeignKey('ref_content_type', 'ref_id')
Previously I was using posts_by_ref in a number of database queries, for example checking the submission status of the posts attached to a track (the post object is where the above fields are). However, I understand that related_name is not supported when using a GenericForeignKey, so is there another way to replicate this behaviour?
You could have a look at Reverse generic relations
Related
I have a model that has 2 separate ManyToManyField relations back to itself
class Company(models.Model):
parent = models.ManyToManyField("self", through='CompanyParent', through_fields=('company_child', 'company_parent'), related_name='+')
child = models.ManyToManyField("self", through='CompanyParent', through_fields=('company_parent', 'company_child'), related_name='+')
The above works fine on my localhost Django v3.0.2/ SQLite 3.8.7.2
To actually publish it, I have had to use Django v2.1.15/ SQLite 3.7.17, but with the published version it is chucking out the following errors
companies.Company.child: (fields.E332) Many-to-many fields with
intermediate tables must not be symmetrical.
companies.Company.parent: (fields.E332) Many-to-many fields with
intermediate tables must not be symmetrical.
companies.Company: (models.E003) The model has two many-to-many
relations through the intermediate model 'companies.CompanyParent'.
What's going on here? Solved the first 2 issues by adding symmetrical=False to each model, but no idea how to solve the final error?
You can check this answer to set up two many-to-many relations.
An example from the mentioned answer:
class Person(models.Model):
name = models.CharField(max_length=127, blank=False)
to_users = models.ManyToManyField(
'self',
symmetrical=False,
related_name='from_users',
through='Event',
through_fields=('from_user', 'to_user'),
)
class Event(models.Model):
item = models.ForeignKey(Item, related_name='events')
from_user = models.ForeignKey(Person, related_name='events_as_giver')
to_user = models.ForeignKey(Person, related_name='events_as_receiver')
In the event anyone happens across the same issue; the above is the answer but thought I would expand slightly after making the necessary changes to my own project
If you are looking to have numerous many-to-many within the same model and if you are on a version of Django after 2.2a1, then the best method is the one detailed in my question; two different models which you can easily call on views or templates; for example -
> data.manytomany1
> data.manytomany2
However, before 2.2a1 you will have issues. For me, this is because on cPanel, I have to use Django v2.1.15 due to the older SQLite 3.7.17 utilised. This means that you can only have one ManyToManyField (see above) and use a filter to get your 2nd manytomany > you will only be able to do this in the views.py
Hope that makes sense
I am building the example django polls application. I want to exclude all polls, which have no choices. For that I have to access the related choices objects:
return Question.objects.filter(
pub_date__lte=timezone.now()
).exclude(
choice_set__count=0
).order_by('-pub_date')[:5]
But this query results in a field error:
Cannot resolve keyword 'choice_set' into field. Choices are: choice, id, pub_date, question_text
How can I query related models from the query?
To filter against a related model, you just use the lower-case model name - you can see that choice is one of the available fields.
However, this still won't work; there is no __count attribute to filter against. You can add one by using annotations, but there is a simpler way: compare against None:
.exclude(choice=None)
In the Choice model set the related_name to the Question foreign key.
Example:
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices')
# other code..
And then your query should be something like that:
return (Question.objects
.filter(pub_date__lte=timezone.now(),
choices__isnull=False)
.order_by('-pub_date')[:5])
Note: There is no `__count' lookup. If you want to rely on counts then check the docs on this.
docs: https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_name
The _set only applies when you are using that to retrieve the related model outside of the queryset, instead, as the error suggests, you can just use choice
.exclude(choice__isnull=True)
Ok so this may be really easy for someone else to solve but i'm really confused on how to go about solving this.
So to start, i have a model A that has multiple fields that have many-to-many relationships to specific tables. So for example
class A(models.Model):
field1 = models.ManyToMany('field1Collection')
field2 = models.ManyToMany(field2Collection')
class field1Collection(models.Model):
description = models.TextField()
class field2Collection(models.Model):
description = models.TextFIeld()
Anyway this is what i'm trying to accomplish. I need to write another model that can hold a ranking system. So for example, i want to create a record where i can define
I have x number of ranks (3 for example):
field1Collection Object 3
field2Collection Object 6
field1Collection Object 2
So i basically want to be able to select objects from my field1Collection and field2Collection tables and assign them ranks. I tried thinking up schemes using foreignkeys and m2m fields but they all go wrong because the model needs to know "ahead" of time which collection set i need to reference. Does this many sense? can anyone help?
You can solve this using GenericForeignKey relationship
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class RankItem(models.Model):
rank = models.IntegerField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.rank
A normal ForeignKey can only "point to" one other model, which means that if the RankItem model used a ForeignKey it would have to choose one and only one model to store tags for. The contenttypes application provides a special field type which works around this and allows the relationship to be with any model
You need that field1Collection and filed2Collection have a common ancestor class which you can refer with a foreignKey. See django documentation on inheritance.
I want to have two foreign keys to the same model:
class Test(models.model):
example1 = models.ForeignKey(Example)
example2 = models.ForeignKey(Example)
I get errors like:
Accessor for field 'example1' clashes with related
field 'Example.test_set'. Add a related_name argument
to the definition for 'example1'.
Try using related_name:
class Test(models.model):
example1 = models.ForeignKey('Example', related_name='example1')
example2 = models.ForeignKey('Example', related_name='example2')
Django uses some python magic to define relationships between models, some of which involves using the name of the models in the relationships (that's where the 'test' in 'test__set' is coming from.) What's happening, I would guess, is that it's trying to put "test__set" in the Example model twice, once for each foreign key you've got defined.
The error message suggests something to try: define a related_name argument (overriding one of those 'test_set's) that it can use instead of auto-generating two clashing names.
More info here: page has been removed
Current page relating to model relationships:
https://docs.djangoproject.com/en/2.0/ref/models/fields/#module-django.db.models.fields.related
Just do what the error message tells you to do, and if you're unsure what that means, consult the documentation for related_name.
In django 2.0 Try this:
user = models.ForeignKey(User, on_delete=models.PROTECT, null=True, related_name='user')
paper = models.ForeignKey(paperRecord, on_delete=models.PROTECT, null=True, related_name='paper')
There must be a way to do this query through the ORM, but I'm not seeing it.
The Setup
Here's what I'm modelling: one Tenant can occupy multiple rooms and one User can own multiple rooms. So Rooms have an FK to Tenant and an FK to User. Rooms are also maintained by a (possibly distinct) User.
That is, I have these (simplified) models:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User)
maintainer = models.ForeignKey(User)
tenant = models.ForeignKey(Tenant)
The Problem
Given a Tenant, I want the Users owning a room which they occupy.
The relevant SQL query would be:
SELECT auth_user.id, ...
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id;
Getting any individual value off the related User objects can be done with, for example, my_tenant.rooms.values_list('owner__email', flat=True), but getting a full queryset of Users is tripping me up.
Normally one way to solve it would be to set up a ManyToMany field on my Tenant model pointing at User with TenantRoom as the 'through' model. That won't work in this case, though, because the TenantRoom model has a second (unrelated) ForeignKey to User(see "restictions"). Plus it seems like needless clutter on the Tenant model.
Doing my_tenant.rooms.values_list('user', flat=True) gets me close, but returns a ValuesListQuerySet of user IDs rather than a queryset of the actual User objects.
The Question
So: is there a way to get a queryset of the actual model instances, through the ORM, using just one query?
Edit
If there is, in fact, no way to do this directly in one query through the ORM, what is the best (some combination of most performant, most idiomatic, most readable, etc.) way to accomplish what I'm looking for? Here are the options I see:
Subselect
users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
Subselect through Python (see Performance considerations for reasoning behind this)
user_ids = id__in=my_tenant.rooms.values_list('user')
users = User.objects.filter(id__in=list(user_ids))
Raw SQL:
User.objects.all("""SELECT auth_user.*
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id""")
Others...?
The proper way to do this is with related_name:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User, related_name='owns')
maintainer = models.ForeignKey(User, related_name='maintains')
tenant = models.ForeignKey(Tenant)
Then you can do this:
jrb = User.objects.create(username='jrb')
bill = User.objects.create(username='bill')
bob = models.Tenant.objects.create(name="Bob")
models.Room.objects.create(owner=jrb, maintainer=bill, tenant=bob)
User.objects.filter(owns__tenant=bob)