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.
Related
I have a model and I have used it at multiple places now and changing it all places is cumbersome. Could you please let me know how to do that? I already know the I can create a custom model manager but it's not feasible to change the model manager at all places.
class Slot(models.Model):
state = models.CharField(max_length=20, choices=SLOT_STATE, default=SLOT_STATE[0][0])
According to the docs-
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author='Roald Dahl')
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
I want to retain the same default manager but I want to exclude all the NULL slot. P.S. slot can have null state.
Following the docs, you could add a custom manager which returns only objects where state is not NULL:
class SlotNoNullStateManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(state__isnull=False)
class Slot(models.Model):
...
objects = models.Manager() # the default manager
no_null_state_objects = SlotNoNullStateManager()
Now your model has 2 managers; and the default behaviour has not changed because it still uses the same default manager for .objects.
It is importante to NOT use .filter() in the .get_queryset() method of the default manger, see https://docs.djangoproject.com/en/3.2/topics/db/managers/#don-t-filter-away-any-results-in-this-type-of-manager-subclass
If you really want to change the default behaviour, despite the warnings, the simply interchange the 2 managers:
class Slot(models.Model):
...
# this has the be placed before the other manager, so that
# it becomes the default manager
objects = SlotNoNullStateManager()
all_objects = models.Manager()
If you still want the rows that have NULL, then you use the manager all_objects.
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?
Anybody knows how to create a foreignkey field and make it always point to same model, so far I got these.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
to = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(to, **kwargs)
But I got errors when on start.
I aim to use it like,
machine_unit = PanMachineTimeUnitField()
No further declarations needed.
Edit:
I want this because, I will have this foreignkey in quiet a few places. If I want to change the verbose_name of field, I want all of my fields to be affected by this change. Verbose name was an example, it may be an another attribute.
I dont want to use settings py to declare the defaults, either.
I recommend that you use only a simple function to create a similarly pre-configured instance of ForeignKey: (not an instance of subclass of ForeignKey)
def pan_machine_time_unit_field(**kwargs):
othermodel = 'panbas.PanBasTimeUnit'
on_delete = models.DO_NOTHING # or what you need
kwargs['verbose_name'] = 'Machine Unit'
kwargs.setdefault('related_name', '+')
# or: kwargs.setdefault('related_name', "%(app_label)s_%(class)s_related",
return models.ForeignKey(othermodel, on_delete, **kwargs)
class C(models.Model):
machine_unit = pan_machine_time_unit_field()
# or:
# machine_unit = pan_machine_time_unit_field(related_name='klass_c_children')
The related_name attribute is a name used for backward relation from the target object of othermodel to all objects that reference it. That name must be unique on othermodel ('panbas.PanBasTimeUnit', usually something with app and class name that is unique enough) or that name can be '+' if you don't want to create a backward relationship query set. Both variants are implied in the example. Also remember on_delete.
If you would really need to create a subclass (which makes sense if more methods need be customized), you must also define a deconstruct method for migrations. It would be complicated if you need to modify such subclass later. It can be never removed, renamed etc. due to migrations on a custom field. On the other hand, if you create a simple instance of ForeignKey directly by a function, all about migrations can be ignored.
EDIT
Alternatively you can create an abstract base model with that field and create new models by inheritance or multiple inheritance:
class WithPanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey(
'panbas.PanBasTimeUnit',
models.DO_NOTHING,
verbose_name=_('Machine Unit'),
related_name='%(app_label)s_%(class)s_related'
)
class Meta:
abstract = True
class ExampleModel(WithPanBasTimeUnit, ...or more possible base models...):
... other fields
This solution (inspired by an invalid soution Ykh) useful if you want to add a method to models with that field or to add more fields together, otherwise the original solution is easier.
class PanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey('self', blank=True, null=True,
verbose_name=u'parent')
use 'self' or 'panbas.PanBasTimeUnit' will fine.
You can not have several Foreign Keys to a model with same related_name.
Indeed, on a PanBasTimeUnit instance, which manager should Django return when calling <instance>.machine_unit? This is why you have to be carefull on related models and abstract classes.
It should work fine if you remove kwargs['related_name'] = 'machine_unit' in your code, and replace it with kwargs['related_name'] = "%(app_label)s_%(class)s_related" or something similar.
A slight modification in your attempt should do your work.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
kwargs["to"] = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(**kwargs)
why not use directly machine_unit = models.ForeignKey(panbas.PanBasTimeUnit, verbose_name=_('Machine Unit'), related_name='machine_unit')) ?
I am having two models and a custom Manager:
class ActiveModelAManager(models.Manager):
def get_query_set(self):
return super(ActiveModelAManager,self).get_query_set().filter( active = True)
class ModelA(models.Model):
name = CharField(....)
active = BooleanField()
active_models = ActiveModelAManager()
objects = models.Manager()
class ModelB(models.Model):
modelA = ForeignKey(ModelA)
in my view I am passing ModelA to the template and I would like to access ModelB_set but using my active_models manager instead of objects ?
so if I am doing this :
{{ ModelB.modelb_set.all }}
I am accessing the objects manager but I want to access the active_models.
Same problem goes when I am trying to access the custom manager via my view.
Any idea how can I accomplish this ?
Not sure whether I got your question right, but in order to query for the modelB objects which reference to an active ModelA object, in your view you can just write.
active_modelA_objects = ModelA.active_models.all().values_list('pk', flat=True)
modelB_objects_with_active_modelA = ModelB.objects.filter(modelA__in=active_modelA_objects)
Related docs:
values-list
__in
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.