Django: How to cascade custom managers defined in different abstract classes? - django

I want to be able to make complex queries combining different custom manager functions from different Abstract classes.
My models are like these:
class GenderManager(models.Manager):
def male(self):
return self.filter(gender="M")
def female(self):
return self.filter(gender="F")
class SpeciesManager(models.Manager):
def lion(self):
return self.filter(species="L")
def tiger(self):
return self.filter(species="T")
class GenderModel(models.Model):
gender = models.CharField(max_length=1)
objects = GenderManager()
class Meta:
abstract = True
class SpeciesModel(models.Model):
species = models.CharField(max_length=1)
objects = SpeciesManager()
class Meta:
abstract = True
class Animal(GenderModel,SpeciesModel):
name = models.CharField(max_length=30)
age = models.DecimalField(max_digits=4, decimal_places=2)
The reason I want to split Gender and Species is that in my models sometimes I will need to inherit only from GenderModel and sometimes only from SpeciesModel.
In the cases where I want to inherit form both (like in Animal class), I would like to be able to make queries like this:
Animal.objects.male().tiger().filter(age__gte = 10.00)
But it doesn't work.
However, if I don't use custom manager functions it works:
Animal.objects.filter(gender="M").filter(species="T").filter(age__gte = 10.00)
How can I make it work with custom manager functions do make it DRY?
Thank you!

This can't be done due to how Managers actually work but there can be different ways to refactor your model and the logic, depending on the complexity and how independent the models actually need to be.
Since your manager is actually needed for the concrete class and not the abstract class, concanate both managers into a single manager, afterall you are filtering on the concrete class and not the Abstract.
class GenderModel(models.Model):
gender = models.CharField(max_length=1)
class Meta:
abstract = True
class SpeciesModel(models.Model):
species = models.CharField(max_length=1)
class Meta:
abstract = True
class AnimalManager(models.Manager):
def gender(self):
return self.filter(gender="M")
def female(self):
return self.filter(gender="F")
def lion(self):
return self.filter(species="L")
def tiger(self):
return self.filter(species="T")
def get_male_tigers(self):
return self.filter(species="T").filter(gender="M").all()
class Animal(GenderModel,SpeciesModel):
name = models.CharField(max_length=30)
age = models.DecimalField(max_digits=4, decimal_places=2)
objects = AnimalManager()
Then:
animals = Animal.objects.get_male_tigers()
Of course you can further refactor to your needs

objects, of course, can't be a reference to both GenderManager and SpeciesManager. Given how MRO works, in your example it is a SpeciesManager instance.
You can create a third manager:
class AnimalManager(GenderManager, SpeciesManager):
def male_and_tiger(self):
return self.male & self.tiger()
[all other combinations here]
class Animal(GenderModel,SpeciesModel):
[...]
objects = AnimalManager()
But really I think you are being possessed by the OOP inheritance demon :) A plain call to the orm is much better and more future proof (unless you want to add a new method to your SpeciesManager each time you add a new species)

Related

How to chain model managers?

I have two abstract models:
class SoftDeleteModel(models.Model):
objects = SoftDeletableManager()
class Meta:
abstract = True
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
class Meta:
abstract = True
I use often use these models together for DRY purposes, e.g.:
class MyNewModel(SoftDeleteModel, BookAwareModel):
The SoftDeleteModel has a custom manager SoftDeletableManager():
class SoftDeletableManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_removed=False)
If I want to extend the BookAware abstract model to add a queryset filter on Books whilst still preserving the SoftDeletableManager() how would I go about this?
E.g. I can't add objects = BookManager() to BookAwareModel because it will overwrite the SoftDeletableManager.
Having played with your code a bit I came up with three possible solutions which seem to work (according to my tests):
Option 1:
Create a combined manager which is used when defining your concrete MyNewModel and use it for that model:
class CombiManager(SoftDeletableManager, BookAwareManager):
def get_queryset(self):
qs1 = SoftDeletableManager.get_queryset(self)
qs2 = BookAwareManager.get_queryset(self)
return qs1.intersection(qs2)
and then
class MyNewModel(SoftDeleteModel, BookAwareModel):
objects = CombiManager()
Option 2:
Create a Manager for the BookAware model as a subclass of the SoftDeleteableManager
class BookAwareManager(SoftDeletableManager):
def get_queryset(self):
return super().get_queryset().filter(your_filter)
and then add it to your BookAware model with a different name than 'objects':
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
book_objects = BookAwareManager()
class Meta:
abstract = True
allowing you to get the filtered queryset like
MyNewModel.book_objects.all()
Option 3
Put the BookAwareManager as in Option two as manager into your concrete MyNewModel. Then you can leave the managers name as the default 'objects'

How add custom fields to django-serializer dynamically?

I am trying to write a simple CRUD application. I have several pretty similar model classes and one serializer:
class TestModelSerializer(serializers.ModelSerializer):
queryset = None
#classmethod
def initialize(cls, queryset, context_class, **kwargs):
_srlz = cls()
_srlz.Meta.model = context_class
_srlz.Meta.fields = '__all__'
_srlz.queryset = queryset
_srlz = cls(_srlz.queryset, **kwargs)
return _srlz
class Meta:
model = None
fields = ''
so I can serialize my classes, calling initialize function like this:
_q = Foo.objects.all()
_srlz = TestModelSerializer.initialize(_q, Foo, many = True)
_q2 = Bar.objects.all()
_srlz2 = TestModelSerializer.initialize(_q2, Bar, many = True)
and so on.
But I have faced to one problem. Sometimes my classes are in hard One-to-Many relation (composition):
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo, on_delete = models.CASCADE)
When I serialize Foo class I want to serialize a list of related Bar classes as well and nest result data to Foo's serializer data.
I am not intended to write custom serializer for each of these classes but instead try to implement some generic approach.
I decided to make some experiments and finally create an interface that provides me several methods for my collection items:
class Foo(models.Model):
bars = iCollection
pass
Now when instantiate a serializer I can get all fields of my model class that have iCollection type.
The question is how can I add them to my serializer?
I tried to write an abstract fabric and create a serializer class using type(), but this approach is too ugly, I suppose there should be much more easier solution.

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.

How to architect and implement models with similar state and behavior in django?

I have the following models:
class Member(models.Model):
name = models.CharField(max_length=255)
class Location(models.Model):
name = models.CharField(max_length=255)
member = models.ForeignKey(Member)
class Department(models.Model):
name = models.CharField(max_length=255)
member = models.ForeignKey(Member)
class LocationInvite(models.Model):
sent = models.BooleanField(default=False)
location = models.ForeignKey(Location)
def send(self):
location = self.location.member
email = self.location.member.email
send_populated_email(location, email)
self.sent = True
I need to allow departments to have invites as well.
I am thinking of changing LocationInvite to Invite and making it an abstract base class. I will then create 2 concrete implementations LocationInvite and DepartmentInvite. I will move the location foreign key to the LocationInvite class.
How then will I refactor the send method of Invite to accommodate extracting the email address of either the Location or the Department, depending on the concrete implementation?
My question is, is using an abstract base class a good architecture move and how would I implement it given the constraints of the send method?
These records will be in the millions, so that is why I did not mention using generic foreign keys. Unless this is not a problem?
Refactor the attribute access into a separate property, and override in each model.
class Invite(...):
def send(...):
self._group....
...
...
class LocationInvite(Invite):
...
#property _group(self):
return self.location
class DepartmentInvite(Invite):
...
#property _group(self):
return self.department
I created an abstract base class and let both invite types extend from it.

Django models inheritance and Meta class

I have a problem with django models inheritance. This is what I have :
class Room(models.Model):
name = models.CharField(max_length=32)
class Container(models.Model):
size = models.IntegerField(default=10)
...
class BigBox(Container):
room = models.ForeignKey(Room)
...
class SmallBox(Container):
big_box = models.ForeignKey(BigBox)
...
class Stuff(models.Model):
container = models.ForeignKey(Container)
...
class Meta:
ordering = ('container__???__name',)
So, with this, I'm able to put some stuff in the big box or in a small box, which is in the big box.
How can I know the type of my stuff field ´container´ in order to acces to the name of the room ? I know I can write
container__big_box__room__name
and
container__room__name
but I would like something like
container__get_room__name.
Is it possible ?
Thank you,
Alex.
To your actual quuestion about the ordering meta, my answer is: I don't think that is possible.
Now, some workarounds:
I would rethink your model hierarchy.
To me a box/container which can be cointained in another box/container is still a box.
Have a look at this alternative:
class Container(models.Model):
size = models.IntegerField(default=10)
room = models.ForeignKey(Room)
...
class ContainableContainer(Container):
parent_container = models.ForeignKey('self', null=True)
...
class Stuff(models.Model):
container = models.ForeignKey(Container)
...
class Meta:
ordering = ('container__room__name',)
With this solution you don't really need a different model, they're both containers, where a cointainer of the cointainer is optional. And so, you can do the ordering as you have already thought.
You would have to be careful with the room field management. You need to make each contained container room be equal with its container's room.
For example, override the save method or use a pre_save signal:
class ContainableContainer(Container):
parent_container = models.ForeignKey('self', null=True)
...
def save(self, *args, **kwargs):
self.room = self.parent_container.room
super(ContainableContainer, self).save(*args, **kwargs)
EDIT: This is actually a tree-like hierarchy. To make it more efficient query-wise django-mptt would be a nice choice.
It allows you to obtain the root container or to iterate over the box hierarchy with more efficient queries.
I don't have any experience with it, but it really seems the best possible solution.