Django Queryset only method and foreign keys real fields ('_id') - python

In order to minimize bugs in competitive access to instances of django models, I'm using "only" method of QuerySet. For example, I have models like:
#models.py
class MyModel1(models.Model):
...
class MyModel2(models.Model):
field_1 = models.ForeignKey(MyModel1)
...
I'm using "only" in this way:
instances = list(MyModel2.objects.filter(...).only('id'))
Will field "field_1_id" of instances be loaded at this moment (not 'field_1', exactly 'field_1_id')?

only() method loads only the id of model MyModel2.

Check out using the values_list() method, which can return a list of values if you have only one field you need and you pass it flat = True:
instances = MyModel2.objects.filter(...).values_list('id', flat=True)

Related

Get all values from Django QuerySet plus additional fields from a related model

I was wondering if there is a shortcut to getting all fields from a Django model and only defining additional fields that are retrieved through a join (or multiple joins).
Consider models like the following:
class A(models.Model):
text = models.CharField(max_length=10, blank=True)
class B(models.Model):
a = models.ForeignKey(A, null=True, on_delete=models.CASCADE)
y = models.PositiveIntegerField(null=True)
Now I can use the values() function like this
B.objects.values('y', 'a__text')
to get tuples containing the specified values from the B model and the actual field from the A model. If I only use
B.objects.values()
I only get tuples containing fields from the B model (i.e., y and the foreign key id a). Let's assume a scenario where B and A have many fields, and I am interested in all of those belonging to B but only in a single field from A. Manually specifying all the field names in the values() call would be possible, but tedious and error-prone.
So is there a way to specify that I want all local fields, but only a (few) specific joined field(s)?
Note: I'm currently using Django 1.11, but if a solution only works with a more recent version I am interested in that too.
You can use prefetch_related for this. See docs:
You want to use performance optimization techniques like deferred
fields:
queryset = Pizza.objects.only('name')
restaurants = Restaurant.objects.prefetch_related(Prefetch('best_pizza', queryset=queryset))
In your case you can do something like this:
from django.db.models import Prefetch
queryset = A.objects.only('text')
b_list = B.objects.prefetch_related(Prefetch('a', queryset=queryset))
Maybe something like this would work in your case?
B.objects.select_related('a').defer('a__field_to_lazy_load');
This will load all fields from both models except the ones you specify in defer(), where you can use the usual Django double underscore convention to traverse the relationship.
The fields you specify in defer() won't be loaded from the db but they will be if you try to access them later on (e.g. in a template).

Any way to fetch the through fields for an object linked via Many2Many field without knowing the column name in advance?

I am trying to write a generic method that can take any Django Model and returns it in a dictionary form.
So for example, if my models are defined thus (very generic):
class A(models.Model):
somefieldA = models.TextField()
m2mfield = models.ManyToManyField(B, through='AandB')
def __unicode__(self):
return self.somefieldA
class B(models.Model):
somefieldB = models.TextField()
def __unicode__(self):
return self.somefieldB
class AandB(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
field1 = models.DecimalField()
field2 = models.TextField()
field3 = models.DateField()
Now, assume we have an instance of the object A a_obj.
I can get all the related B objects using:
# This loop is there because I am working with other fields as well.
def instance_to_dict(instance):
for field in instance._meta.get_fields():
if field.many_to_many:
m2m_mgr = getattr(instance, field.name)
for idx, assoc_obj in enumerate(m2m_mgr.all()):
assoc_obj_str = str(assoc_obj)
# How to obtain the related through field values?
# m2m_mgr.through.objects.get() would need prior knowlegde
# of field name so get(a=instance, b=assoc_obj) is not possible
# m2m_mgr.through.objects.all() fetches all the objects
# in the Many to Many manager.
And then call instance_to_dict(a_obj). This method could be called by passing other models' instances.
Ideally, I would like to create a dict of the obj and related "through" fields for any object. Is this possible to do?
In addition to the explicitly defined ManyToMany manager, there is also an implicit reverse relationship for the ForeignKey from AandB to A. So you can do something like this:
for field in instance._meta.get_fields(include_hidden=True):
if field.one_to_many: # reverse ForeignKey
m2m_through_mgr = getattr(instance, field.get_accessor_name()) # e.g. aandb_set
m2m_through_mgr.all() # all related instances from the through table
Another approach is to go through the through table fields looking at field.related_model to see which one points back to your original table.
This all gets quite messy, but there should be enough meta information to do what you want. One obstacle is that the API isn't fully documented. Specifically, relation fields are represented by instances of the ManyToOneRel class, which as of Django 2.1 remains undocumented for reasons hinted at in the source code. Hence my use of the undocumented get_accessor_name() method.

Django .values_list() alternative that returns a QuerySet for a ForeignKey field's model?

I'm looking for a clean way to convert one type of QuerySet to another based on a models ForeignKey field, so basically something like a .values_list('my_fk', flat=True) but returning a proper QuerySet instead of a values_list() variant.
For example:
class Parent(models.Model):
child = models.ForeignKey(Child)
...
children_qs = Parent.objects.filter(...).theMagicMethod('child')
Here children_qs should now be a queryset for all Child instances used in the earlier query, instead of a queryset returning Parent instance.
You can sort of do this with a custom queryset and an __in lookup but it feels a bit smelly:
class ParentQuerySet(models.QuerySet):
...
def children(self):
return Child.objects.filter(id__in=self.values_list('child_id', flat=True))
This takes all the child_id FK's from the records in the Parent's queryset and re-query Child directly. When I inspect the SQL it does a sub query, and I'm not sure if this is optimal or has some odd side effects. It does look like the ordering from the original Parent query is gone, and so are duplicates.
Does anyone got something better then this?
note: I'm aware I could query directly via Child and filter the Parent's fields using the reverse lookup, but that doesn't support everything you can do on the main model.
Try this, it will return query_set of Child class
parent_primary_keys = Parent.objects.filter(...).values_list('pk',flat=True)
children_qs = Child.objects.filter(id__in=parent_primary_keys)
It sounds like you can leverage the Django's function prefetch_related. Check out this answer and django's documentation

How to make superclass querysets return subclass objects? Or downcast the superclass queryset to a subclass list?

I'm working in a django project which I need to list two different models in the same view ordered by date. In order to achieve that I used inheritance to be able to get them all into a generic queryset. My models are:
class Publication(models.model):
title = models.CharField(max_lengh = 200)
pub_date = models.DateTimeField(default = datetime.now)
headline = models.TextField()
class Meta:
abstract = True
#abc.abstractmethod
def say_hello(self):
return
class New(Publication):
author = models.ForeignKey(Author)
source = models.CharField(max_length = 200)
categories = models.ManyToManyField(Category)
url = '/news/'
def say_hello(self):
return "Hello New!!!"
class Opinion(Publication):
writer = models.ForeignKey(Writer)
style = .models.CharField(max_length=3, choices=(('txt', 'Text'), ('glr', 'Galery')))
url = '/opinions/'
def say_hello(self):
return "Hello Opinion!!!"
I'm trying to call the subclass method while iterating through the Publication QuerySet like this:
publications = Publications.objects.all().order_by('-pub_date')
for pub in publications:
pub.say_hello()
url = pub.url
The problem is that my QuerySet is returning Publication objects, so I can't access child attributes and methods, obviously cus I'm dealing with Publication objects. Shouldn't The fact that I've set Publication as an abstract class, avoid the possibility of dealing with Publication objects?. Shouldn't they be prevented from being instantiated? Is there any option for perform perform a QuerySet in Publication class and return a list with child objects?
If no. How would you guys go around this situation? I could really use some tips.
Sounds like it might be appropriate to use multi-table inheritance and django polymorphic:
Multi-table inheritance: https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance
Django polymorphic: http://django-polymorphic.readthedocs.org/en/latest/
Multi-table inheritance in django allows you to have a base model/table which has your base fields. Your subclasses then define the extended fields which are put in their own tables. When you fetch records with querysets from any of the subclasses, you'll get information for each record from both the base model/table and the subclass model/table.
In order to fetch records using the base model's queryset, and get an instance of the appropriate subclass for each result, one option is django polymorphic. I've used it before and it works pretty well. It definitely has its limitations but I'd give it a shot.
Each Publication instance should have either a 'new' attribute or a 'opinion' attribute pointing to one of the two subclasses respectively. Be aware that each instance has only one of this attributes so maybe it's better to try...except access to them.
Well, I will put the code for my solution here which I achieved thanks to #David answer.
As suggested for David, I used django-polymorphic, which is great and simple. But the fact that I already had a populated database, made things a bit complicated. Nothing hard to fix.
First thing I did was to migrate the database with south in order to add the new field (polymorphic_ctype) to my parent model (no field is added to the subclasses).
Then, I used the following code in django shell mode on terminal. (python manage.py shell)
from jornal.models import Publication, New, Opinion
from django.contrib.contenttypes.models import ContentType
ctype_opinion = ContentType.objects.get(model = 'opinion', app_label = 'jornal')
ctype_new = ContentType.objects.get(model = 'new', app_label = 'jornal')
opinions = Opinion.objects.non_polymorphic().all()
news = New.objects.non_polymorphic().all()
for new in news:
new.polymorphic_ctype = ctype_new
new.save()
for opinion in opinions:
opinion.polymorphic_ctype = ctype_opinion
opinion.save()

Get FK set for each instance in queryset

I have a tree-like Django model say named A, which been done by django-mptt.
class A(MPTTModel):
parent = TreeForeignKey('self')
this class automaticly has the 'children' manager, so i can easily get the subtree
There is another model, which have FK link to A:
class SomeModel(models.Model):
link_to_a = models.ForeignKey(A)
I know, that if i want to get SomeModel set of A instance i can do that:
a = A.objects.filter(blah)
a.somemodel_set.all()
and the question is:
what is the most pythonic way to fetch somemodel_set of each instance in some queryset under A model, i.e. i want 4 example this:
some_A_instance.children.all().get_all_somemodel_instances()
and get_all_somemodel_instances() should retrieve ziped queryset of sets for each children
Do you just need the related items in one list, or do you need to associate each set with their parent? If the former, you can get them all at once with this:
related_items = SomeModel.objects.filter(link_to_a=some_A_instance.children.all())
which will do one single query (with a subquery) to get everything.
Otherwise, you can use prefetch_related() to get all items' related sets in one go:
items = some_A_instance.children.all().prefetch_related('somemodel_set')
This should do:
[child.somemodel_set.all() for child in some_A_instance.children.all()]

Categories

Resources