Combine queries from 2 inherited Django models - django

I would like to perform a prefetch. The point is: I have a baseModel and inherited models that are liked to other models. But my baseModel isn't linked with them.
Here is the speudo code:
class Author(models.Model):
name = models.CharField()
class Movie(PolymorphicModel):
title = models.CharField()
author = models.ForeignKey('Author')
class EnglishMovie(Movie):
pass
class FrenchMovie(Movie):
pass
class Subtitle(models.Model):
movie = models.ForeignKey('FrenchMovie', related_name='subtitle')
text = models.CharField()
As you can see, FrenchMovie is linked to subtitles, but EnglishMovie isn't.
I would like to prefetch all my movie, and prefetch the subtitles as well.
I've tried several methods:
def get_queryset(self):
return Movie.objects.prefetch_related('author', 'subtitle').all()
I get: ValueError: Cannot query "EnglishMovie object (1)": Must be "FrenchMovie" instance.
def get_queryset(self):
q1 = Movie.objects.prefetch_related('author').all()
q2 = FrenchMovie.objects.prefetch_related('subtitle').all()
return q1 | q2
I get: AssertionError: Cannot combine queries on two different base models.
Any tips ? Thanks a lot.

Related

Django: To combine and retrieve rows from different models

1.We have two similar models, and
I would like to be able to retrieve these at the same time and sort them by posting date and time, etc.
Is it possible?
2.Or should both redundant fields be combined into one model?
# 1.
class Model1(models.Model):
title = ...
thumbnail = ...
description = ...
...
class Model2(models.Model):
title = ...
image_url = ...
product_id = ...
reivew = ...
# 2. Combine into one model
class Model(models.Model)
title = ...
thumbnail = ...
description = ...
image_url = ...
product_id = ...
reivew = ...
You can union() two models with no shared parents, and after that order_by() some
column.
'Consider this answer from a beginner'
If you just want to fetch someones model1 and model2 objects with just one query statement maybe is good to inherit two models from a base model like this:
in your models.py:
class Base(models.Model):
title = ...
created_at = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(user)
class Meta:
ordering = ['-created_at']
# inherits fields of Base model
class Model1(Base):
field = ...
# fields
class Model2(Base):
# fields
using this method, remember fill needed fields of `Base' model like this:
>>> m1 = Model1(field="value", title=..., owner=UserObject,...).save()
# for get objects do normal:
>>> objects = Base.objects.filter(owner=user)
# the result is list of users `Model1' or 'Model2` created objects
Also you can use django-model-utils and get Base objects by child type. It would be like this:
from model_utils.managers import InheritanceManager
class Base(models.Model):
# like previous version
# just remember modify objects
objects = InheritanceManager()
# inherits fields of Base model
class Model1(Base):
class Model2(Base):
get objects:
>>> Base.objects.all().select_related('Model1', 'Model2')
please read also this answer and others.

Django) How to connect urls-views-models in ManyToMany, OneToMany relationship

I made some models which have ManyToMany, OneToMany relationships, and then I tried to make appropriate class in views.py, so that one can see sub models related to the chosen model.
But in terms of connecting models-serializers-views-urls, I just couldn't figure out how to make it work...
So, what I want to do is : (simplified)
There are 3 models.
Party
People
Food
So Party has ManyToMany relationship with People, and OneToMany relationship with Food. When I reached url like /party_id/people_id, then I want to get specific person's information from given party id.
Here goes my code.
models.py
class Party(models.Model):
par_id = models.TextField()
par_people = models.ManyToManyField(People)
class People(models.Model):
peo_id = models.TextField()
peo_name = models.TextField()
peo_type = models.TextField()
class Food(models.Model):
foo_id = models.TextField()
foo_party = models.ForeignKey(Party, on_delete=models.CASCADE)
serializers.py
class PartySerializer(serializers.ModelSerializer):
class Meta:
model = Party
fields = ('par_id', 'par_people')
# People, Food has same structure...
views.py
class PartyList(generics.ListAPIView):
queryset = Party.objects.all()
serializer_class = PartySerializer
# People, Food has same structure...
urls.py
Here's the part where I got lost
#redundancy reduced...(e.g. import)
urlpatterns = [
path('party/<int:par_id>/<int:peo_id>', views.PartyList.as_view()),
path('party/<int:par_id>/<int:foo_id>', views.PartyList.as_view()),
]
So If I reach website/party/1/3, I want to see person's information(whose peo_id is 3) of party(whose par_id is 1). For food, It goes the same.
Should I make new class in views.py to make it work? But how can url check par_id and foo_id at the same time if I use PartyList view class..? Any help would be much appreciated.
I think something like this should work. The basic principle if work out if using peo_id or foo_id and then filter the queryset on that basis.
def get (self, *args, **kwargs):
id = kwargs.get(peo_id, None)
if id:
self.queryset.filter(par_people__peo_id=id)
else:
id = kwargs.get(foo_id, None)
self.queryset.filter(foo_party=id)

Filter by custom QuerySet of a related model in Django

Let's say I have two models: Book and Author
class Author(models.Model):
name = models.CharField()
country = models.CharField()
approved = models.BooleanField()
class Book(models.Model):
title = models.CharField()
approved = models.BooleanField()
author = models.ForeignKey(Author)
Each of the two models has an approved attribute, which shows or hides the object from the website. If the Book is not approved, it is hidden. If the Author is not approved, all his books are hidden.
In order to define these criteria in a DRY manner, making a custom QuerySet looks like a perfect solution:
class AuthorQuerySet(models.query.QuerySet):
def for_site():
return self.filter(approved=True)
class BookQuerySet(models.query.QuerySet):
def for_site():
reuturn self.filter(approved=True).filter(author__approved=True)
After hooking up these QuerysSets to the corresponding models, they can be queried like this: Book.objects.for_site(), without the need to hardcode all the filtering every time.
Nevertheless, this solution is still not perfect. Later I can decide to add another filter to authors:
class AuthorQuerySet(models.query.QuerySet):
def for_site():
return self.filter(approved=True).exclude(country='Problematic Country')
but this new filter will only work in Author.objects.for_site(), but not in Book.objects.for_site(), since there it is hardcoded.
So my questions is: is it possible to apply a custom queryset of a related model when filtering on a different model, so that it looks similar to this:
class BookQuerySet(models.query.QuerySet):
def for_site():
reuturn self.filter(approved=True).filter(author__for_site=True)
where for_site is a custom QuerySet of the Author model.
I think, I've come up with a solution based on Q objects, which are described in the official documentation. This is definitely not the most elegant solution one can invent, but it works. See the code below.
from django.db import models
from django.db.models import Q
######## Custom querysets
class QuerySetRelated(models.query.QuerySet):
"""Queryset that can be applied in filters on related models"""
#classmethod
def _qq(cls, q, related_name):
"""Returns a Q object or a QuerySet filtered with the Q object, prepending fields with the related_name if specified"""
if not related_name:
# Returning Q object without changes
return q
# Recursively updating keywords in this and nested Q objects
for i_child in range(len(q.children)):
child = q.children[i_child]
if isinstance(child, Q):
q.children[i_child] = cls._qq(child, related_name)
else:
q.children[i_child] = ('__'.join([related_name, child[0]]), child[1])
return q
class AuthorQuerySet(QuerySetRelated):
#classmethod
def for_site_q(cls, q_prefix=None):
q = Q(approved=True)
q = q & ~Q(country='Problematic Country')
return cls._qq(q, q_prefix)
def for_site(self):
return self.filter(self.for_site_q())
class BookQuerySet(QuerySetRelated):
#classmethod
def for_site_q(cls, q_prefix=None):
q = Q(approved=True) & AuthorQuerySet.for_site_q('author')
return cls._qq(q, q_prefix)
def for_site(self):
return self.filter(self.for_site_q())
######## Models
class Author(models.Model):
name = models.CharField(max_length=255)
country = models.CharField(max_length=255)
approved = models.BooleanField()
objects = AuthorQuerySet.as_manager()
class Book(models.Model):
title = models.CharField(max_length=255)
approved = models.BooleanField()
author = models.ForeignKey(Author)
objects = BookQuerySet.as_manager()
This way, whenever the AuthorQuerySet.for_site_q() method is changed, it will be automatically reflected in the BookQuerySet.for_site() method.
Here the custom QuerySet classes perform selection at the class level by combining different Q objects, instead of using filter() or exclude() methods at the object level. Having a Q object allows 3 different ways of using it:
put it inside a filter() call, to filter a queryset in place;
combine it with other Q objects using & (AND) or | (OR) operators;
dynamically change names of keywords used in the Q objects by accessing its children attribute, which is defined in the superclass django.utils.tree.Node
The _qq() method defined in every custom QuerySet class takes care of prepending the specified related_name to all filter keys.
If we have a q = Q(approved=True) object, then we can have the following outputs:
self._qq(q) – is equivalent to self.filter(approved=True);
self._qq(q, 'author') – is equivalent to self.filter(author__approved=True)
This solution still has serious drawbacks:
one has to import and call custom QuerySet class of the related model explicitly;
for each filter method one has to define two methods filter_q (class method) and filter (instance method);
UPDATE: The drawback 2. can be partially reduced by creating filter methods dynamically:
# in class QuerySetRelated
#classmethod
def add_filters(cls, names):
for name in names:
method_q = getattr(cls, '{0:s}_q'.format(name))
def function(self, *args, **kwargs):
return self.filter(method_q(*args, **kwargs))
setattr(cls, name, function)
AuthorQuerySet.add_filters(['for_site'])
BookQuerySet.add_filters(['for_site'])
Therefore, if someone comes up with a more elegant solution, please suggest it. It would be very appreciated.

Filter queryset between two or more models for search form in django

Im building a search form in django, Im doing it with filter querysets, this form is like a "advance search form", I mean, form could has more than two inputs, but problem is that each input corresponding of a field of different model, I have this and works fine if the form only has one input for one model:
def post(self,request,*args,**kwargs):
buscar_predio = request.POST['nombre_predio']
query1 = InfoPredioGeneral.objects.filter(nombre_predio__iexact=buscar_predio)
if query1:
ctx = {'predio':query1}
return render(request,'resultados_busqueda.html',ctx)
else:
return render(request,'resultados_busqueda.html')
If I have this models:
class InfoPredioGeneral(models.Model):
nombre_predio = models.CharField(max_length=30)
class Propietario(models.Model):
predio = models.ForeignKey(InfoPredioGeneral,blank=True,null=True,related_name='predio_propietario+')
tipo_identificacion = models.ForeignKey(TipoIdentificacion,related_name='tipo identificacion+',blank=True,null=True)
In the post method how can I search in the same form a InfoPredioGeneral and Propietario? For example filter where nombre_predio is exact to "predio proof" and where tipo_identificacion is exacto to "123"? As you can see Propietario has a ForeignKey to InfoPredioGeneral
you are looking for a many-to-one relationship
https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/
you already set the related_name to "predio_propietario+" and with the '+' at the end this means there is no backwards relation to this model
there is you example:
queryset = Propietario.objects.filter(predio__nombre_predio__iexact=request.POST['nombre_predio'])
Extra:
class C(models.Model):
c_text = models.CharField(max_length=100)
class B(models.Model):
b_text = models.CharField(max_length=100)
class A(models.Model):
b = models.ForeignKey(B)
c = models.ForeignKey(C)
queryset will look like this:
queryset = A.objects.filter(b_btext__isexact="your b text", c_ctext__isexact="your c text")

django custom model field

If I have 2 interlinked models:
class Person(models.Model)
name = models.CharField()
class Project(models.Model):
person = models.ForeignKey(Person)
title = models.CharField()
I frequently find myself trying to find the number of Projects associated with each Person:
person = Person.objects.get(id=1)
no_projects = Project.objects.filter(person=person).count()
Is there a way of adding this as a custom field to the Person model, such that I may just call person.no_projects?
This can be done by adding a property to the Person class.
class Person(models.Model)
name = models.CharField()
#property
def no_projects(self):
return Project.objects.filter(person=self).count()
This can be called now like this
person = Person.objects.get(id=1)
person.no_projects
In fact, that functionality is (almost) built-in. Instead of querying Projects, you can just do person.project_set.count() to get that person's count of projects (and .all() to get the actual list).
You can use the property() to generate the dynamical field.
class Project(models.Model):
person = models.ForeignKey(Person)
title = models.CharField()
def _get_no_projects(self):
return Projects.objects.filter(person=self).count()
no_projects = property(_get_no_projects)
But the dynamical fields can not be used in querying, because the they don't really store data into the database.
FYI: http://www.b-list.org/weblog/2006/aug/18/django-tips-using-properties-models-and-managers/