I am trying to use cached_property and classmethod decorators together in a viewset but it doesnt work regardless of their mutual position. Is it any chance to make it work together or cached_property doesnt work with classmethod?
Tnanks.
#cached_property
#classmethod
def get_child_extra_actions(cls):
"""
Returns only extra actions defined in this exact viewset exclude actions defined in superclasses.
"""
all_extra_actions = cls.get_extra_actions()
parent_extra_actions = cls.__base__.get_extra_actions()
child_extra_actions = set(all_extra_actions).difference(parent_extra_actions)
return (act.__name__ for act in child_extra_actions)
For cached property with classmethod usage i wrote that code a few days ago:
from django.utils.decorators import classproperty
class cached_classproperty(classproperty):
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
It will be caching result in class-level.
Usage is similar as classproperty:
#cached_classproperty
def some_func(cls, *args, **kwargs):
...
If you do not have django in dependencies, then you may want prevent classproperty parent usage (sources). In that case you may use that decorator:
class cached_classproperty(classproperty):
def __init__(self, method=None):
self.fget = method
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
def getter(self, method):
self.fget = method
return self
Related
I stumbled upon a code that is used to provide some args to the request method. Problem is that I'm not that sure if it is the cleanest way to handle this case.
def check_permissions(check_mixins):
"""
:param check_mixins: is given to the inner decorator
Decorator that will automatically populate some parameters when
using dispatch() toward the right method (get(), post())
"""
def _decorator(_dispatch):
def wrapper(request, *args, **kwargs):
Is it a problem if "self" isn't passed in the method definition in here...
for mixin in check_mixins:
kwargs = mixin.check(request, *args, **kwargs)
if isinstance(kwargs, HttpResponseRedirect):
return kwargs
return _dispatch(request, *args, **kwargs)
return wrapper
return _decorator
class UserLoginMixin(object):
def check(request, *args, **kwargs):
... and here ? It seems so ugly in my IDE
user = request.user
if user.is_authenticated() and not user.is_anonymous():
kwargs['user'] = user
return kwargs
return redirect('user_login')
class AppoExistMixin(object):
def check(request, *args, **kwargs):
Here too...
appo_id = kwargs['appo_id']
try:
appoff = IdAppoff.objects.get(id=appo_id)
kwargs['appoff'] = appoff
del kwargs['appo_id']
return kwargs
except IdAppoff.DoesNotExist:
pass
messages.add_message(request, messages.ERROR,
"Item doesn't exist!")
return redirect('home')
class SecurityMixin(View):
"""
Mixin that dispatch() to the right method with augmented kwargs.
kwargs are added if they match to specific treatment.
"""
data = []
def __init__(self, authenticators):
super(SecurityMixin, self).__init__()
# Clearing data in order to not add useless param to kwargs
self.data.clear()
# Build the list that contain each authenticator providing
# context increase
for auth in authenticators:
self.data.append(auth)
#method_decorator(check_permissions(data))
Why data and not self.data ? How is it possible ?
def dispatch(self, request, *args, **kwargs):
return super(SecurityMixin, self).dispatch(request, *args, **kwargs)
Each view then inherits from SecurityMixin and got authenticators = [UserLoginMixin, ...] as class attribute.
The problem I have sometimes (I can't reproduce the bug...) is that I got KeyError on augmented kwargs while URL definition is properly set. eg:
appo_id = kwargs['appo_id']
KeyError: 'appo_id'Exception
I've been looking for hours and it seems that I will never have the solution... It's a bit frustrating.
If someone could help It'll be greatly appreciated.
I have a hunch that improper handling of class attributes is at fault.
CLASS VS INSTANCE
The class attribute data is overwritten every time SecurityMixin.__init__ is called:
class A:
data = []
def __init__(self, *args):
self.data.clear() # self.data references the class attribute
for x in args:
self.data.append(x)
x = A('foo')
# A.data = ['foo']
# x.data = ['foo']
y = A('bar')
# A.data = ['bar']
# y.data = ['bar']
# x.data = ['bar'] !!
HOWEVER:
class A:
data = ['I am empty']
def __init__(self, *args):
self.data = [] # redeclaring data as an instance attribute
for x in args:
self.data.append(x)
x = A('foo')
# A.data = ['I am empty']
# x.data = ['foo']
y = A('bar')
# A.data = ['I am empty']
# y.data = ['bar']
# x.data = ['foo']
This class attribute data is passed to the decorator (you cannot pass an instance attribute to a method decorator, i.e. self.data, because the instance does not yet exist during decorator declaration).
The wrapped function, however, does have access to the instance if it is passed in ('self' argument).
Django's method_decorator removes this self argument; that decorator is used to transform a function decorator (which does not get a self argument implicitly) into a method decorator (which gets a self parameter implicitly). That's why you do not have to include self in the list of parameters for the various mixin check methods as it was removed by method_decorator. To put it simply: use method_decorator to decorate a method with a function decorator. Read up on it here decorating CBVs.
Knowing that, I am not really sure why check_permissions should be a function decorator as it is now when you only use it to decorate methods.
You could just decorate dispatch with check_permissions itself:
def check_permissions(_dispatch):
def _decorator(self, request, *args, **kwargs): # adding self
for mixin in self.data: # referencing the INSTANCE data
kwargs = mixin.check(request, *args, **kwargs)
if isinstance(kwargs, HttpResponseRedirect):
return kwargs
return _dispatch(self, request, *args, **kwargs) # don't forget self here
return _decorator
#check_permissions
def dispatch(self, request, *args, **kwargs):
...
Maybe some view is trying to check AppoExistMixin because it is in that view's data list, although it should not be - and the view's kwargs do not include 'appo_id'. You could also try being explicit by passing the wanted check mixins directly to the decorator: #method_decorator(check_permissions([UserLoginMixin, ...])). This way you you don't have to mess with class vs instance attributes.
Also... you should rename data to something that you are unlikely to overwrite with your own variable.
If you want to be super-lazy you could just do:
appo_id = kwargs.get('appo_id',False)
if not appo_id: return kwargs
But this would only fix that particular error in that one view. It's ignoring a symptom instead of curing the disease.
Some more explanation:
function vs method. check_permissions is a function, while dispatch() is a method. You cannot simply use a function decorator on a method: for one, because the implicit argument self (the instance the method belongs to) is passed to the decorator as well, although it may not expect it.
That is where django's method_decorator comes in by removing and storing self within the decorator. Compare the two signatures: wrapper(request, *args, **kwargs) vs _decorator(self, request, *args, **kwargs). In the former, method_decorator 'absorbed' self before the function decorator is called.
Think of it as an adapter, a decorator for the decorator, that 'bridges the gap' between function and method. Use it if you don't want to/cannot alter the decorator.
In your case, however, you can change the decorator to make it work with a method - thus you don't need django's method_decorator.
Working with ReviewBoard 1.6.11, which uses Django 1.3.3.
There is a RepositoryManager class that has a method called 'accessible', defined as this:
class RepositoryManager(Manager):
def accessible(self, user, visible_only=True, local_site=None):
"""Returns repositories that are accessible by the given user."""
if user.is_superuser:
qs = self.all()
else:
q = Q(public=True)
if visible_only:
q = q & Q(visible=True)
if user.is_authenticated():
q = q | (Q(users__pk=user.pk) |
Q(review_groups__users=user.pk))
qs = self.filter(q).distinct()
return qs.filter(local_site=local_site)
The problem is that I want to filter the results of this query by something else that does not interact with the database (filesystem permissions of the user). accessible() needs to return a QuerySet, though. Otherwise, I would just create a list and populate it with the appropriate items from the result.
I'm fairly new to Django. Is this a reasonable thing to do, or am I going about this all wrong?
I was able to solve this by creating a QuerySet subclass like so:
class RepositoryQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(RepositoryQuerySet, self).__init__(*args, **kwargs)
self._user_filter = None
def _clone(self, *args, **kwargs):
retval = super(RepositoryQuerySet, self)._clone(*args, **kwargs)
retval._user_filter = self._user_filter
return retval
def filter_by_user_access(self, user):
self._user_filter = user.username
def iterator(self):
for repo in super(RepositoryQuerySet, self).iterator():
if self._user_filter is None:
yield repo
continue
# ...logic for restricting access by user...
# If user has access, just yield repo. If not, just 'continue'
Then in RepositoryManager.accessible, right after the call to distinct(), call:
qs.filter_by_user_access(user)
Finally, for the manager to use the new query set class, create:
def get_query_set(self):
return RepositoryQuerySet(self.model, using=self.db)
in the RepositoryManager class.
I've created a model, and I'm rendering the default/unmodified model form for it. This alone generates 64 SQL queries because it has quite a few foreign keys, and those in turn have more foreign keys.
Is it possible to force it to always (by default) perform a select_related every time one of these models are returned?
You can create a custom manager, and simply override get_queryset for it to apply everywhere. For example:
class MyManager(models.Manager):
def get_queryset(self):
return super(MyManager, self).get_queryset().select_related('foo', 'bar')
(Prior to Django 1.6, it was get_query_set).
Here's also a fun trick:
class DefaultSelectOrPrefetchManager(models.Manager):
def __init__(self, *args, **kwargs):
self._select_related = kwargs.pop('select_related', None)
self._prefetch_related = kwargs.pop('prefetch_related', None)
super(DefaultSelectOrPrefetchManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultSelectOrPrefetchManager, self).get_queryset(*args, **kwargs)
if self._select_related:
qs = qs.select_related(*self._select_related)
if self._prefetch_related:
qs = qs.prefetch_related(*self._prefetch_related)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# ...
objects = DefaultSelectOrPrefetchManager(select_related=('bread',), prefetch_related=('extras',))
Then you can re-use the manager easily between model classes. As an example use case, this would be appropriate if you had a __unicode__ method on the model which rendered a string that included some information from a related model (or anything else that meant a related model was almost always required).
...and if you really want to get wacky, here's a more generalized version. It allows you to call any sequence of methods on the default queryset with any combination of args or kwargs. There might be some errors in the code, but you get the idea.
from django.db import models
class MethodCalls(object):
"""
A mock object which logs chained method calls.
"""
def __init__(self):
self._calls = []
def __getattr__(self, name):
c = Call(self, name)
self._calls.append(c)
return c
def __iter__(self):
for c in self._calls:
yield tuple(c)
class Call(object):
"""
Used by `MethodCalls` objects internally to represent chained method calls.
"""
def __init__(self, calls_obj, method_name):
self._calls = calls_obj
self.method_name = method_name
def __call__(self, *method_args, **method_kwargs):
self.method_args = method_args
self.method_kwargs = method_kwargs
return self._calls
def __iter__(self):
yield self.method_name
yield self.method_args
yield self.method_kwargs
class DefaultQuerysetMethodCallsManager(models.Manager):
"""
A model manager class which allows specification of a sequence of
method calls to be applied by default to base querysets.
`DefaultQuerysetMethodCallsManager` instances expose a property
`default_queryset_method_calls` to which chained method calls can be
applied to indicate which methods should be called on base querysets.
"""
def __init__(self, *args, **kwargs):
self.default_queryset_method_calls = MethodCalls()
super(DefaultQuerysetMethodCallsManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultQuerysetMethodCallsManager, self).get_queryset(*args, **kwargs)
for method_name, method_args, method_kwargs in self.default_queryset_method_calls:
qs = getattr(qs, method_name)(*method_args, **method_kwargs)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# Other field definitions...
objects = DefaultQuerysetMethodCallsManager()
objects.default_queryset_method_calls.filter(
bread__type='wheat',
).select_related(
'bread',
).prefetch_related(
'extras',
)
The python-mock-inspired MethodCalls object is an attempt at making the API more natural. Some might find that a bit confusing. If so, you could sub out that code for an __init__ arg or kwarg that just accepts a tuple of method call information.
Create a custom models.Manager and override all the methods (filter, get etc.) and append select_related onto every query. Then set this manager as the objects attribute on the model.
I would recommend just going through your code and adding the select_related where needed, because doing select_related on everything is going to cause some serious performance issues down the line (and it wouldn't be entirely clear where it's coming from).
I'm referring to the code snippet in the first answer taken from
this post: Custom QuerySet and Manager without breaking DRY?
from django.db import models
from django.db.models.query import QuerySet
class CustomQuerySetManager(models.Manager):
"""A re-usable Manager to access a custom QuerySet"""
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model)
Here is the model:
from custom_queryset.models import CustomQuerySetManager
from django.db.models.query import QuerySet
class Inquiry(models.Model):
objects = CustomQuerySetManager()
class QuerySet(QuerySet):
def active_for_account(self, account):
self.filter(account = account, deleted=False, *args, **kwargs)
self.model.QuerySet(self.model) always receives a same model, but the result QuerySet depends on the input QuerySet. For example:
Inquiry.objects.all()[:5].active_for_account(xyz), then active_for_account will receive a query set of 5 items, while in Inquiry.objects.all()[:7].active_for_account(xyz), active_for_account will receive a query set of 7 items. Here are stack traces:
Inquiry.objects.all()[:5].active_for_account(xyz)
return getattr(self.get_query_set(), attr, *args),
return self.model.QuerySet(self.model) (1)
Inquiry.objects.all()[:7].active_for_account(xyz)
return getattr(self.get_query_set(), attr, *args),
return self.model.QuerySet(self.model) (2)
Why are results at (1) and (2) different?
I'm not entirely sure what you're asking here.
Inquiry.objects.all()[:5] doesn't give you give objects, it gives you a single QuerySet object which contains five elements.
Is it possible to dynamically set the 'extra' option in the Django Admin Inline?
For example, If Student class have Address class as Inline.
If there is no Address inline's associated with Student, then extra =1.
If there is any Address inline's associated with Student, then extra =0.
Just simply override the get_extra method. The following example set extra to 0 for the add view and 10 for the edit view.
class MyInline(admin.TabularInline):
model = MyModel
def get_extra(self, request, obj=None, **kwargs):
return 0 if obj else 10
You can just leverage inheritance..
// based on some condition
kwargs['extra'] = something
.........
return super(*******Inline, self).get_formset(request, obj, **kwargs) // 'defaults.update(kwargs)' takes care of the dynamic overriding
The get_formset method from my project :
def get_formset(self, request, obj=None, **kwargs):
## Put in your condition here and assign extra accordingly
if obj is None:
return super(ImageInline, self).get_formset(request, obj, **kwargs)
current_topic = TopicPage.objects.get(pk = obj.id)
topic_images = ThruImage.objects.filter(topic = current_topic)
kwargs['extra'] = 0
if len(topic_images) <= 3:
kwargs['extra'] = 3 - len(topic_images)
return super(ImageInline, self).get_formset(request, obj, **kwargs)
This is of course, useful only for simple conditionals based off the parent model object ..
Not sure if it would work and I am not too familiar with inlines and this extra attribute, but you could subclass django.contrib.admin.InlineModelAdmin and replace the InlineModelAdmin.extra attribute with a python property:
from django.contrib import admin
from myproject.myapp.models import MyInlineModel
class DynamicExtraInlineModelAdmin(admin.InlineModelAdmin):
#property
def extra():
return 1 if some_logic else 0
admin.site.register(MyInlineModel, DynamicExtraInlineModelAdmin)
You just monkey patch django's (1.3.1) source code as follows:
First add the following code to your app:
from django.forms.models import inlineformset_factory
from django.contrib.admin.util import flatten_fieldsets
from django.utils.functional import curry
from django.contrib.admin.options import InlineModelAdmin
class MyInlineModelAdmin(InlineModelAdmin):
#extra = 1
def get_formset(self, request, obj=None, **kwargs):
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(kwargs.get("exclude", []))
exclude.extend(self.get_readonly_fields(request, obj))
# if exclude is an empty list we use None, since that's the actual
# default
exclude = exclude or None
if obj and hasattr(obj, 'id'): # <<=======================================
_extra = 0
else:
_extra = self.extra
defaults = {
"form": self.form,
"formset": self.formset,
"fk_name": self.fk_name,
"fields": fields,
"exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
"extra": _extra,
"max_num": self.max_num,
"can_delete": self.can_delete,
}
defaults.update(kwargs)
return inlineformset_factory(self.parent_model, self.model, **defaults)
class MyTabularInline(MyInlineModelAdmin):
template = 'admin/edit_inline/tabular.html'
and assuming your models are something like:
class ContainerModel(models.Model):
pass #etc...
class ListModel(models.Model):
pass #etc...
then change your admins to:
class ListModelInline(MyTabularInline): # <<=================================
model = MyModel
class ContainerModelAdmin(admin.ModelAdmin):
inlines = (ListModelInline,)
admin.site.register(ContainerModel, ContainerModelAdmin)
#etc...