Geo Django Subclassing Queryset - django

I'm using GeoDjango to search for a bunch of locations of different types. For example, both House and Appartment models are subclasses of Location.
Using the Subclassing Queryset below, I'm able to do something like Location.objects.all() and have it return to me [<House: myhouse>, <House: yourhouse>, <Appartment: myappartment>], which is my desire.
However, I also want to determine the distance to each location. Normally, without the Subclassing Queryset, the code from Exhibit 2 returns for me the distances from the given point to each location.... [ (<Location: Location object>, Distance(m=866.092847284))]
However, if I try to find the distances using the Subclassing Querysets, I get an error such as:
AttributeError: 'House' object has no attribute 'distance'
Do you know how I can preserve the ability return a queryset of subclassed objects yet have the distance property available on the subclass objects? Any advice is much appreciated.
Exhibit 1:
class SubclassingQuerySet(models.query.GeoQuerySet):
def __getitem__(self, k):
result = super(SubclassingQuerySet, self).__getitem__(k)
if isinstance(result, models.Model) :
return result.as_leaf_class()
else :
return result
def __iter__(self):
for item in super(SubclassingQuerySet, self).__iter__():
yield item.as_leaf_class()
class LocationManager(models.GeoManager):
def get_query_set(self):
return SubclassingQuerySet(self.model)
class Location(models.Model):
content_type = models.ForeignKey(ContentType,editable=False,null=True)
objects = LocationManager()
class House(Location):
address = models.CharField(max_length=255, blank=True, null=True)
objects = LocationManager()
class Appartment(Location):
address = models.CharField(max_length=255, blank=True, null=True)
unit = models.CharField(max_length=255, blank=True, null=True)
objects = LocationManager()
Exhibit 2:
from django.contrib.gis.measure import D
from django.contrib.gis.geos import fromstr
ref_pnt = fromstr('POINT(-87.627778 41.881944)')
location_objs = Location.objects.filter(
point__distance_lte=(ref_pnt, D(m=1000)
)).distance(ref_pnt).order_by('distance')
[ (l, l.distance) for l in location_objs.distance(ref_pnt) ] # <--- errors out here

I'm busy trying to solve this one to. How about this:
class QuerySetManager(models.GeoManager):
'''
Generates a new QuerySet method and extends the original query object manager in the Model
'''
def get_query_set(self):
return super(QuerySetManager, self).get_query_set()
And the rest can follow from this DjangoSnippet.

You have to reassign the manager in all subclasses.
From Django documentation:
Managers defined on non-abstract base classes are not inherited by child classes. If you want to reuse a manager from a non-abstract base, redeclare it explicitly on the child class. These sorts of managers are likely to be fairly specific to the class they are defined on, so inheriting them can often lead to unexpected results (particularly as far as the default manager goes). Therefore, they aren't passed onto child classes.
https://docs.djangoproject.com/en/dev/topics/db/managers/#custom-managers-and-model-inheritance

Related

override __str__(self) of a model from an imported app

I'm facing the following situation: I have a django project, which uses an outside app [App1]. Within App1, it has the following structure:
abstract class 'Base':
class Base(models.Model):
"""
Base model with boilerplate for all models.
"""
name = models.CharField(max_length=200, db_index=True)
alternate_names = models.TextField(null=True, blank=True,
default='')
..............
..............
class Meta:
abstract = True
def __str__(self):
display_name = getattr(self, 'display_name', None)
if display_name:
return display_name
return self.name
abstract class based on 'Base', called 'AbstractClassA':
class AbstractClassA(Base):
display_name = models.CharField(max_length=200)
....
....
class Meta(Base.Meta):
abstract = True
def get_display_name(self):
....
....
return ....
The non abstract class class ClassA(AbstractClassA)
Now, when I do a query in my view for this ClassA, for example:
qs = ClassA.objects.filter(Q(name__icontains=query_term)....)
return qs
I feed this qs into another outside app (autocomplete), so that when I type in 'xxxx' on my web form, the form would give me suggestions on available matches in the DB, based on this qs.
This all works great, the only thing is, the list of potential matches shown to me is the default representation of the ClassA objects, which I traced back to
def __str__(self):
display_name = getattr(self, 'display_name', None)
if display_name:
return display_name
return self.name
defined in the base abstract model I've mentioned earlier. What I want is, to have something else displayed as the list of potential matches (e.g. instead of 'display_name' or 'name', show me 'fieldA' + ';'+ 'fieldB' of each filtered item in qs).
My thought was to override this __str__ method somewhere. But because both the upstream and downstream aspect of my process are done in outside apps that I don't want to modify directly (i.e. copy directly into my Django project and rewrite certain parts), I'm not sure how I could achieve my goal.
Is there any elegant way to do so?
Please let me know if anything is unclear, or if I could provide you with any further information. Thanks!
Another approach besides Monkey Patching is to use Proxy models.
class MyClassA(ClassA):
class Meta:
proxy = True
def __str__(self):
return self.attribute
Then use MyClassA instead of ClassA.
From your question it is not clear if the non-abstract classes are written by you, but what you can do is to create a mixin and add that to the class signature of your concrete classes, such as:
class NiceStrMixin():
def __str__(self):
return self.display_name
then
class ClassA(AbstractClassA, NiceStrMixin):
...
If you don't have access to ClassA either, you can monkey patch AbstractClassA.

Django Model Inheritance - get child

Is there a way to access the actual child of the base model, means: Staying with the example from the django Docs, let's assume I am modeling different delivery restaurants, that just have in common
name
all have a deliver method
as of this:
class Place(models.Model):
name = models.CharField(max_length=10)
class Pizzeria(Place):
topping = models.CharField(max_length=10)
tip = models.IntegerField()
def deliver(self):
deliver_with_topping(self.topping)
ask_for_tip(self.tip)
class Shoarma(Place):
sauce = models.CharField(max_length=10)
meat = models.CharField(max_lenght=10)
def deliver(self):
prepare_sauce_with_meat(self.sauce, self.meat)
I would now like to execute:
Place.objects.get(name="my_place").<GENERIC_CHILD>.deliver()
i.e. I don't need to know what the place is actually, just the common deliver method. The model then 'knows' what to call.
Is there something like <GENERIC_CHILD>?
I always use Inheritance Manager from django-model-utils for this kind of operations. On your models:
class Place(models.Model):
objects = InheritanceManager() #<- add inheritance manager
name = models.CharField(max_length=10)
def deliver(self):
pass #not needed
Your query:
Place.objects.get_subclass(name="my_place").deliver()
For me it is a clean and elegant solution. Don't forget to star-up django-model-util repo if you like it.
I did it in a messy way.
I do have parent class Activity, with childs - Action, Deal, Order classes.
I want to list them all in 1 place, 1) with a field specifieing it's class, 2) link them to same page, where i will render page based on Activity class
So in my model Activity i add:
def get_type(self):
children = ['action', 'deal', 'order']
for c in children:
try:
_ = self.__getattribute__(c) # returns child model
except ObjectDoesNotExist:
pass
else:
return c
else:
return 'Not specified'

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.

Can a model manager access its models' Meta attribute (`Meta.unique_together`)?

Here's my attempt at a generalized natural key model manager. It's like the docs except it tries (unsuccessfully) to determine the natural key field names from the Meta.unique_together attribute.
class NaturalKeyModelManager(Manager):
def get_by_natural_key(self, *args):
field_dict = {}
for i, k in enumerate(self.model.Meta.unique_together[0]):
field_dict[k] = args[i]
return self.get(**field_dict)
If I insert a debug print just before the for loop like this:
print dir(self.model.Meta)
it doesn't list the unqiue_together attribute at all:
['__doc__', '__module__', 'abstract']
The 'abstract' bit worried me, but another debug print shows that the model I'm trying manage with natural keys is not abstract:
>>> print self.model.Meta.abstract
False
I am mixing in a lot of abstract base classes. Could that be the problem?
class MixedModel(NamedModel, TimeStampedModel, VersionedModel, Model):
objects = NaturalKeyModelManager()
class Meta:
unique_together = (('name', 'version',),)
For completeness here's one of the mixins:
class TimeStampedModel(Model):
created = DateTimeField(_("Created"), auto_now_add=True, null=True, editable=False)
updated = DateTimeField(_("Updated"), auto_now=True, null=True, editable=True)
class Meta:
abstract = True
The hard-coded model manager works just fine:
class MixedModelManager(Manager):
def get_by_natural_key(self, name, version):
return self.get(name=name, version=version)
In order to get the actual options passed to meta, you should use self.model._meta rather than self.model.Meta

Polymorphism in Django models

I'm developing django application, and I have such a model structure
class Animal(models.Model):
aul = models.ForeignKey(Aul)
age = models.IntegerField()
def __unicode__(self):
return u'Animal'
class Sheep(Animal):
wool = models.IntegerField()
def __unicode__(self):
return u'Sheep'
And I pass animal_set to template and output every object like this {{ animal }}. It outputs Animal, but I created objects of sheep type and want to use __unicode__ method of sheep not of animal.
Do polymorphism work in Django models? I have found several answers, but there are snippets of code to write inside models, but I'm interested in native solutions.
At the time of writing, Django latest version was 1.2
But it needs some additional elements to work.
You need to assign a custom models.Manager object for each animal model which will call its own custom QuerySet object.
Basically, instead of returning Animal instances (this is what you get), SubclassingQuerySet calls as_leaf_class() method to check if item's model is Animal or not - if it is, then just return it, otherwise perform search in its model context. Thats it.
#models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet
class SubclassingQuerySet(QuerySet):
def __getitem__(self, k):
result = super(SubclassingQuerySet, self).__getitem__(k)
if isinstance(result, models.Model):
return result.as_leaf_class()
return result
def __iter__(self):
for item in super(SubclassingQuerySet, self).__iter__():
yield item.as_leaf_class()
class AnimalManager(models.Manager):
def get_query_set(self): # Use get_queryset for Django >= 1.6
return SubclassingQuerySet(self.model)
class Animal(models.Model):
name = models.CharField(max_length=100)
content_type = models.ForeignKey(ContentType, editable=False, null=True)
objects = AnimalManager()
def __unicode__(self):
return "Animal: %s" % (self.name)
def save(self, *args, **kwargs):
if not self.content_type:
self.content_type = ContentType.objects.get_for_model(self.__class__)
super(Animal, self).save(*args, **kwargs)
def as_leaf_class(self):
content_type = self.content_type
model = content_type.model_class()
if model == Animal:
return self
return model.objects.get(id=self.id)
class Sheep(Animal):
wool = models.IntegerField()
objects = AnimalManager()
def __unicode__(self):
return 'Sheep: %s' % (self.name)
Testing:
>>> from animals.models import *
>>> Animal.objects.all()
[<Sheep: Sheep: White sheep>, <Animal: Animal: Dog>]
>>> s, d = Animal.objects.all()
>>> str(s)
'Sheep: White sheep'
>>> str(d)
'Animal: Dog'
>>>
You might be successful by accessing {{ animal.sheep }} - the model inheritance is not what you would think, there is a heavy metaclass machinery under the cover that "converts" such inheritance into an implicit OneToOneField relationship.
There's a very simple django app called django-polymorphic-models that helps you with that. It will provide you with a downcast() method on the model itself that will return your "child" object, as well as a special queryset class to deal with these problems!
It can also be very useful to know that using select_related() on the base model's queryset will also get the child objects, that are referenced through a OneToOneField, which can be a nice performance boost sometimes!
I would recommend using Django proxy models, e.g. if you have the base model Animal which is subclassed by Sheep and Horse you would use:
class Animal(models.Model):
pass
class Horse(Animal):
class Meta(Animal.Meta):
proxy = True
class Sheep(Animal):
class Meta(Animal.Meta):
proxy = True
This is not what Proxy models are intended for but I wouldn't recommend using Django polymorphism unless you need the benefits of storing model specific data in separate tables. If you have a hundred horse specific attributes that all have default values stored in the database, and then only have 2 horse objects, but have a million sheep, you have a million rows, each with a hundred horse specific values you don't care about, but again this is only really relevant if you don't have enough disk space, which is unlikely. When polymorphism works well it's fine, but when it doesn't it's a pain.
You should check this answer: https://stackoverflow.com/a/929982/684253
The solution it proposes is similar to using django-polymorphic-models, that was already mentioned by #lazerscience. But I'd say django-model-utils is a little bit better documented than django-polymorphic, and the library is easier to use. Check the readme under "Inheritance Manager": https://github.com/carljm/django-model-utils/#readme