Filter model on reverse_set length - django

Consider these pseudo classes:
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo)
I would like to filter via Foo's manager effectively to get a QuerySet that only holds Foo objects with atleast 2 Bar objects pointing towards it.

Use aggregation with Count:
from django.db.models import Count
Foo.objects.annotate(bar_count=Count('bar')).filter(bar_count__gte=2)

Related

How to get objects from two different tables that have a relationship to a third table using the related names in Django?

Let me explain. I have 2 tables which are child classes of another abstract table. The abstract table has a relationship to a model called Foo. The related_name is set dynamically. The code looks like this:
class Foo(models.Model):
...
class Parent(models.Model):
foo = models.ForeignKey(
Foo,
on_delete=models.CASCADE,
related_name='%(app_label)s_%(class)s_related'
)
...
def bar(self):
print('bar')
class Meta:
abstract = True
class ChildOne(Parent):
...
class ChildTwo(Parent):
...
Therefore, the related names become 'myapp_childone_related', and 'myapp_childtwo_related'.
Now, lets say I want to call the bar() method of all the objects from the ChildOne and ChildTwo model that is related to a Foo object. There is a catch though, I want to it from with a class method of the Foo model. Currently, I'm doing it like this:
class Foo(models.Model):
...
def call_bar(self):
references = ('childone', 'childtwo')
for ref in references:
children = getattr(self, f'myapp_{ref}_related').all()
for child in children:
child.bar()
This works fine, but honestly feels a bit hack-y, especially when dealing with more than two children classes. Is there a nicer, more Pythonic solution to this problem?
Edit: I decided not to mention previously that I wanted to call the bar() method from within a class method of the Foo model because I thought that it was unnecessary for this question. However, Daneil Roseman's answer suggested making a list of classes, which is a good solution, but it would not work within the class method, as the classes have not yet been defined at that point in the module. So mentioning that in this edit.
A related_name is only syntactic sugar for performing a query from the related class itself. So you should just do this explicitly:
child_classes = [ChildOne, ChildTwo]
for child_class in child_classes:
children = child_class.objects.filter(foo=foo)

django models to model 'has-a' and 'contains' relationships

I have the following entities:
from django.db import models
class Foo(models.Model):
pass
class Bar(models.Model):
pass
class FooBar(models.Model):
pwned_foo = # Foobar contains one Foo object
bars = # Collection of Bar objects
How do I express the relation between Foo, Bar and FooBar in the FooBar class?
I think you want a OneToOneField and a ForeignKey like this:
from django.db import models
class Foo(models.Model):
pass
class Bar(models.Model):
foobar = models.ForeignKey(FooBar, related_name='bars')
class FooBar(models.Model):
pwned_foo = models.OneToOneField(Foo)
bars = # Collection of Bar objects
# you can access bars via the reverse relationship like
# myfoobar.bars
This assumes you want a 1-N relation with bars. If you want an N-N relation, then use a ManyToManyField.
If you really need to keep Bar clean of any relationship (can you explain why?) you could try something like ArrayField with the caveats that this only works on postgresql, and you would need to reimplement relationships in querysets, etc... And I suspect performance would suffer too...
from django.db import models
class Foo(models.Model):
pass
class Bar(models.Model):
pass
class FooBar(models.Model):
pwned_foo = models.OneToOneField(Foo, on_delete=models.CASCADE)
bars = models.ForeignKey(Bar, on_delete=models.CASCADE)

Django filter across many to many field

This is a simplified version of my models:
class User(models.Model):
pass
class Foo(models.Model):
owners = models.ManyToManyField(User)
bar = models.ForeignKey(Bar)
class Bar(models.Mode)
pass
I have a user instance and I would like to compute a queryset for all Bar instances associated with that user. Going from user to Bar consists of getting all Foo objects that have user as owner and then getting all bar instances associated with each Foo.
How can I express this most efficiently using django queries?
Add related names to your model Foo. This will facilitate the writing of queries.
class Foo(models.Model):
owners = models.ManyToManyField('User', related_name='foo')
bar = models.ForeignKey('Bar', related_name='foo')
Now assuming that you have a single user instance as you mentioned, you could make a query like this:
user = User.objects.get(pk=1)
qs = Bar.objects.filter(foo__owners=user)
If you by most efficient mean the performance and not the expression, then you should take a look at prefetch_related and select_related methods of QuerySet.

Find all instances of child models matching query

I have base class Publication, which Book and Magazine inherit from.
class Author(models.Model):
pass
class Publication(models.Model):
author = models.ForeignKey(Author)
class Book(Publication):
pass
class Magazine(Publication):
pass
I want to find all Book and Magazine objects with a certain author. Since the foreign key to Author is on the parent class Publication, my attempts at a query all return Publication objects rather than the child classes:
# Both of these return Publication objects, not Book/Magazine objects
Author.objects.get(pk=1).publication_set
Publication.objects.get(author_pk=1)
Is there a way to get all instances of child classes with a certain author, without manually running the query for all child classes like Book.objects.get(author_pk=1) (in reality I have more than two child classes)?
You might need to use an external package to get this kind of functionality. This looks like what you need: https://django-model-utils.readthedocs.io/en/latest/managers.html#inheritancemanager
Specifically:
from model_utils.managers import InheritanceManager
class Place(models.Model):
# ...
objects = InheritanceManager()
class Restaurant(Place):
# ...
class Bar(Place):
# ...
nearby_places = Place.objects.filter(location='here').select_subclasses()
for place in nearby_places:
# "place" will automatically be an instance of Place, Restaurant, or Bar

Define a default child (reverse ForeignKey)

I have 2 models Foo and Bar. Each Foo has multiple Bars, but one needs to be the "default". At this point I have a foreignkey in Bar pointing to Foo, but now I need a way to specify which of the Bar's belonging to Foo is the default. I tried setting another foreignkey in Foo that points to a Bar (with a unique related_name), but I get all sorts of errors (including in the django-admin templates).
Code so far:
class Foo(models.Model):
default_bar = models.ForeignKey('Bar')
class Bar(models.Model):
foo = models.ForeignKey(Foo)
I have absolutely NO problem with a completely new solution as I'm probably doing it wrong anyways. The only other way I can think of is to have a separate table that connects Foos and Bars and having the Foo part unique, but that makes the admin interface a MESS.
If a Foo can have multiple Bars, then can a Bar belong to multiple Foos? If so then you can solve this really easily with a ManyToManyField and the through parameter, like this:
class Foo(models.Model):
name = models.CharField(max_length=128)
bars = models.ManyToManyField(Bar, through='FooBar')
class Bar(models.Model):
name = models.CharField(max_length=128)
class FooBar(models.Model):
foo = models.ForeignKey(Foo)
bar = models.ForeignKey(Bar)
is_default = models.BooleanField()
Check the Django documentation on "Extra fields on many-to-many relationships".
And just as a note, posting a simple version of your actual problem (ie. saying Person and Group vs. Foo and Bar) helps, as we get a better understanding of what you're actually trying to model! That way it's easier to see where the relationships should actually go.