Access related manager method from another manager - django

I am trying to access a manager method from a manager of a related model but I'm having no success.
Let's say these are my models:
class ModelA(models.Model):
description = models.TextField(blank=True)
objects = ModelAQuerySet.as_manager()
class ModelB (models.Model):
a = models.ForeignKey(ModelA, related_name='a_objs')
objects = ModelBQuerySet.as_manager()
And I am defining the managers as QuerySets, that is:
class ModelAQuerySet(models.QuerySet):
def description_starts_with(self, desc):
return self.filter(description__startswith=desc)
class ModelBQuerySet(models.QuerySet):
pass
What I would to do is to create a ModelBQuerySet method that prefetch modelA objects starting with a certain description.
Here's a WRONG example of what I am trying to do:
class ModelBQuerySet(models.QuerySet):
def prefetch_a_objs_starts_with(self, desc):
return self.prefetch_related(
Prefetch('a_objs',
queryset=ModelAQuerySet.description_starts_with(desc),
to_attr='a_objs_with_desc'))
This of course doesn't work because description_starts_with is an instance method, and here I am invoking it from the class.
Even this is completely wrong I hope it gets the idea of what I'm trying to do.
Question is: how to create a models.QuerySet method with a prefetch that can access its related models.QuerySet instance methods?

Related

How can I generate a rest viewset for all child models based on a parent model

I have the following models based on a shared base model:
class Pet(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey('auth.User', related_name='pet', on_delete=models.CASCADE)
name=models.CharField(max=100)
class Meta:
abstract = True
ordering = ['-created']
def __str__(self):
return str(self.id)
class Dog(Pet):
legs = models.BigIntegerField()
class Bird(Pet):
wings = models.BigIntegerField()
cage = models.BigIntegerField()
Now I would like to get a rest ViewSet that looks like this when I ask for a User:
{
username: "PeterSchmidt"
email:"ps#gmail.com"
pets{
{
name="shaggy"
type="dog"
legs=4
}
{
name="Jaques"
type="bird"
wings=2
cage=1
}
}
}
Basicly I want a way of having a baseclass model that has its own viewset (multiple serilizers are fine) that I can just use to get all the pets no matter what type they are.
Can this be done within standard django rest?
You can use django-polymorphic to create polymorphic models. General recommendation is that even if the database polymorphism looks clean in the code, it is not recommended thing to do and even if it is not obvious, the code get's more complex.
The way I would solve this would be having one model with diferrent nullable fields for diferrent types, the type choice field and then one wrapper serializer with keys type, data where key would be the type of the animal and in data would be serialized animal. This could be done using serialized of the Pet model with one SerializerMethodField which will serialize the instance of Pet using diferrent serializer for each type.
Django polymorphic
Django rest polymorphic
Realated question - There is nicely explained the way ti serialize more models with one serializer

How to Raise DoesNotExist - Django SoftDelete?

I have a Abstract Model SoftDelete like follow.
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=0)
deleted_at = models.DateTimeField(null=True)
objects = SoftDeleteManager()
def delete(self):
self.is_deleted = True
self.deleted_at = timezone.now()
self.save()
class Meta:
abstract = True
class Employee(SafeDeleteModel):
pass
Whenever model gets deleted i set is_deleted to True and updating the timestamp deleted_at, and created the custom manager to override initial queryset which return only non deleted fields(is_deleted=False).
employee = Employee.objects.get(pk=1)
employee.delete()
employee.refresh_from_db() // not raising DoesNotExist
But lets say i have a Employee model which uses the SafeDeleteModel for soft delete, after deleting the model like Employee.objects.get(pk=1).delete() when i call employee.refresh_from_db(),its not raising DoesNotExist, but updates the value of is_deleted, deleted_at as expected, what mistake i made here, why its not raising DoesNotExist?
There's been a change in Django 2.1: refresh_from_db() now uses a model's _base_manager, not the _default_manager anymore, like for related queries. This to ensure an object can be refreshed even if it cannot be found by the default manager.
So you should set your SoftDeleteManager as the base manager using base_manager_name. But note this comment:
Base managers aren’t used when querying on related models. For example, if the Question model from the tutorial had a deleted field and a base manager that filters out instances with deleted=True, a queryset like Choice.objects.filter(question__name__startswith='What') would include choices related to deleted questions.
Also I don't know how you would retrieve any object that has been deleted after you make this change, except if you make a special manager to not filter the deleted objects (e.g. deleted_objects).
Note also that I would expect the safedelete package you referred to in your comments to have the same issue, as it's not changing the _base_manager either.

Django filter across many to many field

This is a simplified version of my models:
class User(models.Model):
pass
class Foo(models.Model):
owners = models.ManyToManyField(User)
bar = models.ForeignKey(Bar)
class Bar(models.Mode)
pass
I have a user instance and I would like to compute a queryset for all Bar instances associated with that user. Going from user to Bar consists of getting all Foo objects that have user as owner and then getting all bar instances associated with each Foo.
How can I express this most efficiently using django queries?
Add related names to your model Foo. This will facilitate the writing of queries.
class Foo(models.Model):
owners = models.ManyToManyField('User', related_name='foo')
bar = models.ForeignKey('Bar', related_name='foo')
Now assuming that you have a single user instance as you mentioned, you could make a query like this:
user = User.objects.get(pk=1)
qs = Bar.objects.filter(foo__owners=user)
If you by most efficient mean the performance and not the expression, then you should take a look at prefetch_related and select_related methods of QuerySet.

Django what is "objects" inside a Manager

Normally I'd access the queryset via SomeModel.objects().
I notice that inside the model, the objects is defined to be some Manager, like, objects=SomeManager().
So, if I'm defining a method inside a Manager, how would I access objects?
As in...
class SomeManager(models.Manager):
def some_method(self):
( HOW WOULD I ACCESS OBJECTS HERE? )
class SomeModel(models.Model):
... blah blah
objects=SomeManager()
If I wanted to filter something, I suppose I could do SomeModel.objects.filter inside the manager, but somehow that feels weird. Would it be something like self.filter or something?
Yes, you would just use self.filter, where 'self' refers to the Manager itself. The default Manager for the model is objects, and it is automatically created if you don't specify a custom manager. Because you a doing a custom manager, you don't use objects, because obviously that would use the default one, and not your custom one.
So, from the Docs, an example would be:
class BookManager(models.Manager):
def title_count(self, keyword):
return self.filter(title__icontains=keyword).count()
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
num_pages = models.IntegerField(blank=True, null=True)
objects = BookManager()
Short answer: You don't access the manager from within itself. There is no need since you have access to all of the methods via self
objects is the manager in this case. Since you normally use the manager to access things like filter and get you can access them locally on the class using self.filter or self.get.
If you need to have something like select_related applied to every queryset returned by the manager, you can override the get_queryset method.

Django: making relationships in memory without saving to DB

I have some models with relationships like this:
class Item(model.Model):
name = models.CharField()
class Group(models.Model):
item = models.ManyToManyField(Item)
class Serie(models.Model):
name = models.CharField()
chart = models.ForeignKey(Chart)
group = models.ForeignKey(Group)
class Chart(models.Model):
name = models.CharField()
I need to create a Chart object on the fly, without saving to the DB. But I can't do it because Django tries to use the objects primary keys when assigning the relationships.
I just want Group.add(Item()) to work without having to save the objects to the DB.
Is there any simple way around this?
Reviving here for the sake of future readers:
I've gotten around this use case by defining a private attribute that represents the relationship inside the classes and a property to inspect wether the object can be retrieved from the DB or resides in memory.
Here is a simple example:
class Parent(models.Model):
_children = []
name = models.CharField(max_length=100)
#property
def children(self):
if _children:
return self._children
else:
return self.children_set.all()
def set_virtual_children(self, value): # could use a setter for children
self._children = value # Expose _children to modification
def some_on_the_fly_operation(self):
print(','.join([c.name for c in self.children]))
class Children(models.Model):
parent = models.ForeignKey(Parent)
name = models.CharField(max_length=100)
This way, I can set the "virtual children" and use all the defined methods "on the fly"
EDIT: It seems that approach described here isn't enough for django to allow adding to the ManyToMany relationship.
Have you tried to add primary_key=True and unique=True to the name attribute of the Item model. Then doing Group.add(Item("item_name_here")) should work if you have the possibility to create the name on the fly.
I didn't test it, but I think your way failed because add() wants to use the primary-key which by default is the autoincrementing id that is assigned when it is saved to the database.