Django extend custom manager at runtime. Mixin style? - python

The following code is part of a TranslatedContent class which adds a custom manager to the model.
if hasattr(cls, 'objects'):
if cls.objects.__class__ == models.Manager:
# default manager, override
cls.add_to_class('objects', TranslationManager()
else:
# there is a custom manager, don't override
cls.add_to_class('translated', TranslationManager())
When a custom manager is already present it uses a different name for the manager. ('translated' in this case) That's not what I want.
How can I use the same name ('objects') and still overwrite it with TranslationManager()?
In stead of overwrite that would be an override/extend.

You could do something like:
if hasattr(cls, 'objects'):
if cls.objects.__class__ == models.Manager:
# default manager, override
cls.add_to_class('objects', TranslationManager()
else:
# there is a custom manager, don't override
class CombinedManager(cls.objects.__class__, TranslationManager):
pass
cls.add_to_class('objects', CombinedManager())
But, you need to be extremely cautious about the namespace, in particular conflicts that might occur between the original manager and your TranslationManager. In general, it's best to leave it to the end-user to determine whether or not they want their custom manager to include the TranslationManager as well.
I'd recommend simply putting it in the documentation that TranslationManager will only be added as the default manager if a custom manager isn't present. Otherwise, the user should have their custom manager inherit from TranslationManager to gain that functionality.

Related

Why Is This Method Not Visible from Django Custom Manager?

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()

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()

How can I subclass my ModelAdmins in a Django FeinCMS project?

I have a number of models that extend from feincms.models.Base, and use the FeinCMS item editor in the admin site (i.e. they all use feincms.admin.item_editor.ItemEditor as their ModelAdmin).
The models have some shared functionality that I want to be able to define in a shared ModelAdmin class that I can then extend for each model.
The problem is, this doesn't play well with FeinCMS extensions, causing
unexpected results, such as duplicate tabs, where the extensions add things to the ModelAdmins more than once.
Is there a way to do this without messing up the extensions?
This is possible, but you have to adopt slightly different syntax. First, an explanation.
The reason that straightforward inheritance of ModelAdmins is broken is because of the two issues with the way FeinCMS extensions manipulate the ModelAdmin classes:
First, any lists or dictionaries attached to the ModelAdmin (e.g. SharedModelAdmin.list_display) are passed by reference, and so shared between multiple ModelAdmins. This means that the extensions can end up performing an operation twice on the same list (even though it's attached to a different ModelAdmin).
While in our admin.py we define the ModelAdmin's settings at the class level, FeinCMS manipulates the ModelAdmin's instance.
So, in order to get it working, we can use the following mixin:
class Faked(object):
"A fake class to use to stand in for True in ExtendableModelAdminMixin."
pass
class ExtendableModelAdminMixin(object):
"""ModelAdmin mixin to allow ModelAdmins to be extended (i.e.
subclassed) without messing
up the Feincms extension registering mechanism.
Technical note: the reason we do this is because otherwise references
get preserved across different ModelAdmins, which means the ModelAdmins
fail Django's checks.
The straightforward declarative syntax of ModelAdmins sets
attributes at the class level, but FeinCMS's
initialize_extensions() method overrides them on the
instance level. So in our mixin we do a deepcopy of any
instance level attributes before initializing the extensions.
"""
def __init__(self, *args, **kwargs):
# Set the _extensions_initialized attribute to prevent
# extensions being initialized just yet
self._extensions_initialized = Faked
super(ExtendableModelAdminMixin, self).__init__(*args, **kwargs)
# Before running extensions, copy any lists so we don't
# preserve references across different ModelAdmin subclasses
# TODO - include any other ModelAdmin properties that
# are causing issues.
for attr_name in ('list_display',
'fieldsets',
'search_fields', 'list_filter'):
original = getattr(self, attr_name, [])
copied_attr = deepcopy(original)
setattr(self, attr_name, copied_attr)
# Now we're ready to initialize extensions
del(self._extensions_initialized)
self.initialize_extensions()
Usage:
class SharedModelAdmin(ExtendableModelAdmin, ItemEditor):
# Declare some defaults here, as usual
list_display = ['field_one', 'field_two']
class MyModelAdmin(SharedModelAdmin):
def __init__(self, *args, **kwargs):
super(MyModelAdmin, self).__init__(*args, **kwargs)
# Override things at the instance level
self.list_display += ['field_three']

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.

Django: Easily add extra manager for child class, still use default manager from AbstractBase

This question is about the last example on Custom managers and model inheritance.
I want to be able to do something similar to the following:
class ExtraManagerModel(models.Model):
# OtherManager class supplied by argument shall be set as manager here
class Meta:
abstract = True
class ChildC(AbstractBase, ExtraManagerModel(OtherManager)): # That doesn't work, something like that
...
# Default manager is CustomManager, but OtherManager is
# also available via the "extra_manager" attribute.
The whole purpose of this is that I don't want to write an ExtraManagerModel class for every overwritten manager in order to keep the default manager of the parent class (AbstractBase).
Any ideas how this can be acheived?
I am absolutely not sure that I understand your question. Your code snippet seems to be contradicting the comment underneath it.
Your code snippet looks like you want to be able to have different ExtraManagerModel classes. If that is the case, you can use an abstract class that is implemented by those ExtraManagerModels, and you inherit the abstract parent of those classes for the childC class.
Hope this helps.

Categories