Why Is This Method Not Visible from Django Custom Manager? - python

I have two Django models, one that stores promotion codes and another that tracks who redeemed a particular promotion code. I'm trying to create an instance method that determines whether or not a specific user has redeemed a specific code. The problem is that I'm not seeing one of my PromotionManager methods, 'redeemed_by_user'. Here are my classes:
from django.contrib.auth.models import User
from django.db import models
class PromotionManager(models.Manager):
def redeemed_by_user(self, promotion, user):
redemption_count = PromotionRedeemed.objects.filter(promotion=promotion, redeemer=user).count()
if redemption_count == 1:
return True
elif redemption_count == 0;
return False
else:
raise ValueError('Invalid redemption count')
class Promotion(models.Model):
code = models.CharField(max_length=16)
objects = PromotionManager()
class PromotionRedeemed(models.Model):
promotion = models.ForeignKey('Promotion')
user = models.ManyToManyField(User)
If I start the the Django extension shell_plus and do the following:
In [1]: user = User.objects.get(username='smith')
In [2]: promotion = Promotion.objects.get(code='bigsale')
and then I do this:
In [3]: dir(promotion)
I don't see the redeemed by user method. I was under the impression that I could move methods like this from my class to a custom manager class. Is that not the case? If so, can anyone explain why? As I understand it, class manager methods are supposed to act on table-level queries and class intance methods on row-level objects. Isn't objects.filter acting on the table level? I tried moving the method back to the Promotion class and I can see it there but I'd just like to understand why I can't see it in the manager class.

Whatever you are seeing is absolutely correct but there is small correction that you should make. When you do a dir(some_instance) then you see a property named objects .
objects = PromotionManager()
This line sets all the manager methods to the objects property so if you try to access the method via some_instance.objects.method_name then you will be able to access it although you can't use it because Django doesn't allow this. You will see an error like manager methods are not accessible from instances. dir is supposed to show only those methods which are accessible from your model instance.

From the docs,
A Manager is the interface through which database query operations are provided to Django models. By default, Django adds a Manager with the name "objects" to every Django model class.
A model’s manager is an object through which Django models perform database queries. Each Django model has at least one manager, and you can create custom managers in order to customize database access.
Adding extra manager methods(custom managers) is the preferred way to add “table-level” functionality to your models whereas for “row-level” functionality use model methods.
Objects is a special attribute through which you query your database. It’s an instance of the class django.db.models.Manager; it’s where all the default methods for performing queries against the entire model class — all(), get(), filter(), etc.
The dir() function, with an argument, attempt to return a list of valid attributes for that object.
If you dir(promotion), promotion is an instance of Promotion Model object. It returns the attributes of a Promotion instance, which includes the objects attribute. But, you defined objects as PromotionManager(), and the redeemed_by_user() is a method of the Manager instance.
If you dir(promotion.objects) , django would raise an error, AttributeError: Manager isn't accessible via Poke instances. Because, its true. objects is a Manager available at the class level, not to the instances.
From the docs,
Managers are accessible only via model classes, rather than from model instances, to enforce a separation between “table-level” operations and “record-level” operations.
So, if you dir(Promotion.objects), you could see all custom methods defined in the Manager instance of the model.

You use dir on the wrong object.
Moreover, you replaced default manager with yours.
The first manager applied to a model class has special meaning for Django, and is a default one, so add own manager this way, please:
objects = models.Manager()
<your_custom_name> = PromotionManager()

Related

Best place to put a "saveAll" method/function

I have a Model class that is structured as follows:
class Item(models.Model):
place = models.CharField(max_length=40)
item_id = models.IntegerField()
# etc.
#classmethod
def from_obj(cls, **kwargs):
i = Item()
# populate this from json data
# which needs a lot of translations applied
# before saving the data
So, from the above I have a way to create an Item instance via the from_obj classmethod. Now I want to process a json array that contains about 100 objects to create those 100 Item instances. Where should this be put? The function/method would look something like this:
def save_all():
objects = requests.get(...).json()
for obj in objects:
item = Item.from_obj(obj)
item.save()
Should this be a staticmethod within Item. A plain function outside it? Or should another class be created to 'manage' the Item. What is usually considered the best practice for something like this pattern?
If it was pure Python, I'd say either use a plain function or make it a classmethod.
Now this is django's ORM, and the convention for "table-level" operations here is to put them in a custom ModelManager class.
EDIT Except that in your example, there's an outgoing HTTP request, which suggests it might be better as a plain function - it's closer to a view or management command than to a ModelManager method
Why does python encourage one way and django encourages another way?
Python itself doesn't "encourage" anything special, those (plain function or classmethod) are just the obvious choices - it doesn't make sense to make this an instancemethod (you don't need any instance here) nor a staticmethod (since you do use the class).
Django's ORM is, well, an ORM - a framework that tries to provide a Python representation of the (rather formal and specialized) domain of relational databases. Separating row-level operations (model classes methods) from table-level operations (modelmanager classes) makes sense in this context.
The bulk_create method is ideal for this, you can add an additional classmethod that calls the API and bulk creates all the objects
#classmethod
def bulk_create_from_api(cls):
objects = requests.get(...).json()
cls.objects.bulk_create([cls.from_obj(obj) for obj in objects])
Used like this
Item.bulk_create_from_api()
Please note: bulk_create does not send any signals like pre_save or post_save

How to programatically get manager instance for model in Django

I have a custom manager added to model like that:
class StandardManagerModel(models.Model):
pass
class PublishableManager(models.Manager):
pass
class Publishable(models.Model):
published_objects = PublishableManager()
This removes the default .objects manager from the model. How can i retrieve a default manager declared in model class? I would like a function like get_default_manager():
manager = get_default_manager(Publishable)
assert manager is Publishable.published_objects
manager = get_default_manager(StandardManagerModel)
assert manager is StandardManagerModel.objects
Looks like you don't need a function at all. It's stored as an attribute on the model:
Model._default_manager
There's also Model._base_manager, which I can only assume is what the default manager would be if you don't provide one.
From the docs
If you use custom Manager objects, take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the “default” Manager, and several parts of Django (including dumpdata) will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you’d like to work with.
You can supply the default manager as well
objects = models.Manager()
published_objects = PublishableManager()

Django, override many-to-many field ModelManager

How can i override the model manager of a many-to-many field that i have considering the following:
class TermsManager(models.Manager):
def all(self):
return super(TermsManager, self).all().filter(condition_here)
class Term(models.Model):
objects = TermsManager()
name = models.CharField(max_length=255)
class Object(models.Model):
title = models.CharField(max_length=255)
terms = models.ManyToManyField(Term, blank=True)
class Channel(Object):
class Meta:
proxy = True
I also have a class which inherits from TermManager called ChannelTermManager.
How can i override the "terms" field of the Channel model so that
mychannel.terms calls the ChannelTermManager instead of TermManager?
First of all, you shouldn't be overriding all(). If you want to change the default queryset, override get_query_set like so:
class TermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
This is because all() is often omitted when other queryset functions are chained on, and you want your queryset to behave the same whether all() is explicitly called or not.
But even so, what you're doing is still problematic. As explained in the documentation for managers, filtering the default related queryset will affect all sorts of automatic things behind the scenes (such as when dumping data to create backups/fixtures, etc.). You almost definitely do not want this. And you really don't want your related object managers doing this either (by setting use_for_related_fields = True), because you'll be masking what's actually stored in the database, rather than simply detecting out of date data and creating alerts or whatever to clean it up. use_for_related_fields is intended for creating managers that augment the normal capabilities of the vanilla manager, not to filter.
I had a similar situation to yours however, and I handled it like so:
class FilteredTermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
class Term(models.Model):
allTerms = models.Manger() # Establish this as the default/automatic manager
objects = FilteredTermsManager()
name = models.CharField(max_length=255)
This way, I could do all my initial querying on the model through my filtered queryset and it looks like "regular Django", but all relational and behind the scenes queries would work on the unfiltered database. And I could always access the true full set of objects by manually doing Term.allTerms.all().
As for using different managers for different related objects, there's nothing you can really do there. But why not just add Channel specific objects to your custom manager, and simply not call them from methods that operate on get Term querysets from Object?

Overriding Django's RelatedManager methods

Django's ForeignRelatedObjectsDescriptor.create_manager(...) function dynamically creates the RelatedManager classes and subsequently initializes an instance of the dynamically created class.
If I wanted to override the RelatedManager.add(...) method, how would I do it?
The RelatedManager classes are created in file: django/db/models/fields/related.py.
An example of how I'd like to use a custom RelatedManager is...
class Record(Model):
string = CharField()
class Managed(Model):
record = ForeignKey('Record')
boolean = BooleanField()
def view_function(...):
record = Record(string='Example')
record.save()
record.managed_set.add(Managed(boolean=True)) # How to override add()?
Any suggestions would be appreciated.
I'm not sure what you need the override for - the default queryset already does what you want.
But to answer the question, you can define a custom Manager on the model and set use_for_related_fields=True to ensure it gets used as the automatic manager. See the documentation on controlling automatic Manager types.
I think I am having the same problem.
I have a custom manager that overrides self._db and get_query_set() to route it to different databases.
I dynamically created a model class, and has its _default_manager set with my custom manager.
This works for the class itself, but not for related field (foreign or many2many), even though I did set sets use_for_related_fields = True.
For related field, appending db_manager(dbname) (for example, record.managed_set.db_manager(dbname)) can fix all() method, but not for add() method.
To understand what I mean, see this django ticket: http://code.djangoproject.com/ticket/13358
I think it works for all(), but not add().
RelatedManager.add() calls RelatedManager._add_items() which calls Manager.bulk_create().
So if you extend Manager.bulk_create(), you might be able to achieve what you are after.

How to get a subclassed object of a django model

When I have a given django model class like this:
class BaseClass(models.Model):
some_field = models.CharField(max_length = 80)
...
and some subclasses of it, for example
class SomeClass(BaseClass):
other_field = models.CharField(max_length = 80)
Then I know I can get the derived object by calling
base = BaseClass.objects.get(pk=3)
my_obj= base.someclass
Now the problem arises that I have multiple subclasses, and all I have is an instance of the base class. How can I get to the subclassed object without knowing it's class in advance?
The idea is to load a corresponding view and let that do the stuff. My project features only have a limited set of default actions for these models, like view, edit, delete etc. What I don't want is to expose the type of the given object via URL, so the "normal way" is not available
There isn't a built-in way.
Perhaps the best thing to do is to define a derived_type field on your base class, which is set automatically on save of the derived class. You could then have a get_derived method on the base which checks the value of derived_type and returns the actual derived object.
How can I get to the subclassed object without knowing it's class in advance?
Why would this be useful? If you don't know what class you want, you also won't know which methods to call or which attributes can be inspected.
The idea is to load a corresponding view and let that do the stuff. My project features only have a limited set of default actions for these models, like view, edit, delete etc. What I don't want is to expose the type of the given object via URL, so the "normal way" is not available
If you know the set of model subclasses ahead of time, or are willing to register them with a central view list, you can do something like this:
VIEWS = [('subclass_a', a_views), ('subclass_b', b_views)]
def edit(request):
base = (get base somehow)
for attrname, views in VIEWS:
if getattr(base, attrname) is not None:
return views['edit']
Depending on how many different kinds of views you have, you might abstract the search out into a separate function, so the end view is something like:
def edit(request):
return generic_base_view(request, 'edit')
For people looking into this answer a bit later than the question was posted and the accepted answer was given, in the newer versions of django, this is possible more directly, see:
django documentation about multi table inheritance
look at the example of places and restaurants. The base object is a place, and the subclass is a restaurant. You can get the subclass by place.restaurant which gives an exception you can catch if the place is not a restaurant.
I add this because the answer accepted might be a bit outdated and led me to the wrong track.
If you use the InheritanceManager from django-model-utils then you can select the subclasses when you query without knowing what they are ahead of time.
https://django-model-utils.readthedocs.org/en/latest/managers.html#inheritancemanager

Categories