How to use a string to call a queryset in Django? - django

I have 2 models which have foreign keys referencing a third model. Its something like this:
class Foo(models.Model):
...
class Model1(models.Model):
foo = models.ForeignKey(Foo, related_name='m1_related')
class Model2(models.Model):
foo = models.ForeignKey(Foo, related_name='m2_related')
Now, lets say I have an instance of Foo and a string which is either 'm1' or 'm2'. How can I use this string to call the appropriate queryset using the related names?
Something like this:
my_str = 'm1'
foo.my_str+'_related'.objects.all()
Obviously, the above code is just wrong in so many ways, but hopefully that makes it clear. I don't want to use if else conditions for this, as the number of models will be a lot, and all their related names will follow the same pattern.
Thanks.

You can use getattr(..) to obtain the name of an attribute, like:
my_str = 'm1'
getattr(foo, my_str+'_related').objects.all()
A call to getattr(x, 'y') is equivalent to x.y (note that in getattr(..) the 'y' is a string. So we can use getattr(..) obtain the attribute.

Related

django - Adding to M2M relationship when realtionship already exist

I have an M2M relationship like this:
class Foo:
# foo fields...
class Bar:
Foos = ManytoManyField(Foo)
I am trying to add a foo to the list of foos attributed to a Bar, so here's what I have:
if Foo not in Bar.Foos:
Bar.Foos.add(Foo)
Question: is the if-judgment really necessary?
Thanks
As stated in the Django docs : https://docs.djangoproject.com/en/2.2/topics/db/examples/many_to_many/
Adding a second time is OK, it will not duplicate the relation
You may call bar.foo.add(baz) many times, it won't create a duplicate relationship or line in database.
p.s.: in Python, everything (variables, instances, etc..) must be lowercase with words separated by underscores. The exceptions are ClassNames, ExceptionNames and GLOBAL_CONSTANT_NAMES. C.f.: What is the naming convention in Python for variable and function names?
Exemple with your above code :
class Bar:
foos = ManytoManyField(Foo)
def your_view(id):
foo = Foo.objects.get(id=id)
bar = Bar.objects.get(foo=foo)
if foo not in bar.foos:
bar.foos.add(foo)

Django, avoid N+1 query

I have three models in play, and want to avoid N+1 query.
class Rule(models.Model):
pass
class RuleConstraint(models.Model):
rules = models.ManyToManyField(Rule)
class Foo(models.Model):
rule = models.ForeignKey(Rule, related_name='foos')
for a given foo, I can get related RuleConstraints like the following
RuleContraint.objects.filter(rules__foos=foo)
Question is, how do I avoid N+1 query symptom, when I have foos instead of a single foo.
ie, is there a better way of doing than
for foo in foos:
rule_constraints = RuleConstraint.objects.filter(rules__foos=foo)
You want prefetch_related
foos = Foo.objects.prefetch_related('rule__rule_constraint')
You can then iterate through the queryset with:
for foo in foos:
rule_constraints = foo.rule.ruleconstraint_set.all()
You can improve this further by using select_related to fetch the rule.
foos = Foo.objects.select_related('rule').prefetch_related('rule__rule_constraint')
For more information see the prefetch related docs - your models are very similar to those in the examples.

Django queryset exclude empty foreign key set

I have the following models where B has a many-to-one relationship with A:
class A(model.Model):
name = models.IntegerField()
class B(models.Model
a = models.ForeignKey(A, db_column='a_id')
When I use a queryset on A, is there a way to exclude the rows in A that have no rows in B?
Use isnull :
A.objects.filter(b__isnull=False).distinct()
Using distinct() prevents duplicate entries, otherwise each a appears once for every b which is linked to it.
no_rows_in_b = B.objects.all().select_related('a')
will get you all the B's with A's
Then you can cycle through them and output the A's
If you want non-repeats:
no_rows_in_b = B.objects.all().distinct('a').select_related('a')
Then:
for rec in no_rows_in_b:
print(rec.a)
Notice that if you want to be more explicit, you could do something like this:
A.objects.exclude(b__isnull=True).distinct()
using exclude instead of filter and using the True boolean arg.

Django model needs one each of two related fields: should I use oneToOneField?

I'm writing a scientific web app in Django dealing with the amino acid sequences of antibodies Fab fragments, each of which is comprised of exactly one Heavy Chain and one Light Chain. Each of these chains consists of a sequence of amino acid Residues.
Fab 1
Light Chain
Residue 1
Residue 2
...
Heavy Chain
Residue 1
Residue 2
...
Fab 2
etc...
My models.py is essentially this:
from django.db.models import *
class Fab(Model):
name = CharField(max_length=30)
...
def __unicode__(self):
return self.name
class Chain(Model):
fab = ForeignKey(Fab)
TYPE_CHOICES = (
('L', 'light'),
('H', 'heavy'),
)
type = CharField(max_length=5)
...
class Residue(Model):
ch = ForeignKey(Chain)
...
So in the process of entering an Fab into the database, I create 2 chains, assign each a type and an fab foreign key. Then, to use these in a template, I use the following view, getting each chain as an object and passing it to the template independent of its Fab parent object, which isn't exactly ideal.
def fab_detail(request, fab_id):
f = get_object_or_404(Fab, pk=fab_id)
h = get_object_or_404(Chain, fab=f, type='H')
l = get_object_or_404(Chain, fab=f, type='L')
return render_to_response('antibodies/fab_detail.html', {
'fab': f,
'light': l,
'heavy': h,
}, context_instance=RequestContext(request))
However, I want to:
have a better way to refer to the Light or Heavy Chain in a template, e.g. to loop over the residues of the chain with {% for r in fab.light_chain.residue_set.all %}.
ensure that each Fab has only 1 light chain and 1 heavy chain
I've considered subclassing Chain but wasn't sure exactly how to achieve a similar result. I came up with something along the lines of:
class Chain(Model):
# same as before, but without the fab ForeignKey field
...
class LightChain(Chain):
pass
class HeavyChain(Chain):
pass
class Fab(Model):
name = CharField(max_length=30)
light_chain = OneToOneField(LightChain)
heavy_chain = OneToOneField(HeavyChain)
...
class Residue(Model):
???
The main problem I'm having is how to get the LightChain and HeavyChain fields to contain Residue data. Specifically, with what do I replace ch = ForeignKey(Chain) in the Residue class?
Any suggestions or references will be greatly appreciated.
keni's solution is the one I was about to write.
However, I don't think that the "choices=TYPE_CHOICES" constraint is enforced at any level, it just tells Django to use a "select" menu in forms and admin. So theoretically you could have type = 'R', 'W' or anything. Btw, I think you (jared) meant max_length=1.
Another solution would be to simply use a multi-table inheritance, as you seem to do, and not an abstract base class, which are two different forms of model inheritance. In which case you can simply have ch = ForeignKey(Chain). But that may be too much overhead: three tables would be created, one for Chain, one for Light and one for Heavy, the latter two ones referencing the first, one and containing basically nothing else. It may be interesting if you need to store specific information for Light or Heavy chains.
A third solution would be to do this:
class Fab(Model):
name = CharField(max_length=30)
light = OneToOneField(Chain, related_name="fab_as_light")
heavy = OneToOneField(Chain, related_name="fab_as_heavy")
This way you can do fab.light and fab.heavy very easily, and uniqueness is enforced. I'm pretty sure it's legal to have two OneToOneField towards the same model. If it's not you can still have a Foreign Key and set it "unique".
I think the third one is your solution.
For completeness, you'd have:
class Residue(Model):
ch = ForeignKey(Chain)
And Chain would be almost empty (just the id).
For one, you can have a meta class to make the fields unique on a combination of type and chain type.
class Chain(Model):
fab = ForeignKey(Fab)
TYPE_CHOICES = (
('L', 'light'),
('H', 'heavy'),
)
type = CharField(max_length=5, choices=TYPE_CHOICES)
class Meta:
unique_together = (
('type', 'fab'),
)
This way, you can't add more that 2 since you have only two choices anyway.
class Residue(Model):
ch = ForeignKey(Chain)
looks good as used above already.
After trying a couple different things and not being able to use the 'my_chain.fab_as_light/heavy' syntax, my current solution is to use a variation on #Arthur's solution, where I generate a couple properties called 'type' and 'fab' in the Chain model, which are calculated based on the related_name value of the Fab object. (These will be useful, for example, in a function that performs operations on a Chain object but doesn't care which type of chain it is: my_chain.fab returns the Fab object for either a light or heavy chain.)
class Chain(Model):
# determine the type based on Fab related_name
def _get_type(self):
try:
if self.fab_as_light:
return 'L'
except:
try:
if self.fab_as_heavy:
return 'H'
except:
return None
type = property(_get_type)
# consolidate fab_as_light and fab_as_heavy into one property
def _get_fab(self):
try:
return self.fab_as_light
except:
try:
return self.fab_as_heavy
except:
return None
fab = property(_get_fab)
def __unicode__(self):
return "%s_%s" % (self.fab.name, self.type)
class Fab(Model):
name = CharField(max_length=30)
light = OneToOneField(Chain, related_name='fab_as_light')
heavy = OneToOneField(Chain, related_name='fab_as_heavy')
This probably isn't the best route (it's not exactly graceful!), but it's working for me so I'll go with it for now.
Thanks all for your input.

Limit django queryset by another related table

Lets say I have 2 django models like this:
class Spam(models.Model):
somefield = models.CharField()
class Eggs(models.Model):
parent_spam = models.ForeignKey(Spam)
child_spam = models.ForeignKey(Spam)
Given the input of a "Spam" object, how would the django query looks like that:
Limits this query based on the parent_spam field in the "Eggs" table
Gives me the corresponding child_spam field
And returns a set of "Spam" objects
In SQL:
SELECT * FROM Spam WHERE id IN (SELECT child_spam FROM Eggs WHERE parent_spam = 'input_id')
I know this is only an example, but this model setup doesn't actually validate as it is - you can't have two separate ForeignKeys pointing at the same model without specifying a related_name. So, assuming the related names are egg_parent and egg_child respectively, and your existing Spam object is called my_spam, this would do it:
my_spam.egg_parent.child_spam.all()
or
Spam.objects.filter(egg_child__parent_spam=my_spam)
Even better, define a ManyToManyField('self') on the Spam model, which handles all this for you, then you would do:
my_spam.other_spams.all()
According to your sql code you need something like this
Spam.objects.filter(id__in= \
Eggs.objects.values_list('child_spam').filter(parent_spam='input_id'))