Take `self` without 'def __init__' an instance in Django - django

'Generic view class' in Django puzzles me very much.
for instance:
class ProfileDetailView(DetailView):
def get_object(self):
username = self.kwargs.get('username')
if username is None:
raise Http404
return get_object_or_404(User, username__iexact=username, is_active=True)
Without the procedure of getting instantiated, it works as well.
What I can understand is:
class ProfileDetailView(DetailView):
def __init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
def get_object(self):
username = self.kwargs.get('username')
if username is None:
raise Http404
return get_object_or_404(User, username__iexact=username, is_active=True)
What's the mechanism behind it?

That's the fundamental principle of inheritance:
for your reference:
9. Classes — Python 3.6.3 documentation
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)

In Python3 super method behaves a bit more magically. It automatically figures out the parent of the class and implicitly passes the self as well. It has nothing to do specifically with Django itself. It's been done so to reduce the code redundancy. It is pretty irritating to be forced to change all super calls when changing the name of the class.
# Python2
class B(A):
def __init__(self, *args, **kwargs):
super(A, self).__init__(*args, **kwargs)
# Python 3
class B(A):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Now as far as Django generic views are concerned, there is an as_view class method that creates a callable instance of the View, which delegates the call to dispatch method.

The __init__ method named constructor call automatically when you instantiated Class. Here As you inherit DetailView so super() call takes all __init__ functionality from DetailView.
class ProfileDetailView(DetailView): # here DetailView is inherited
def __init__(self, *args, **kwargs) # Own class(ProfileDetailView)'s __init__
super().__init__(*args, **kwargs) # it takes DetailView's __init__ behabiour
and it is not ProfileDetailView own so 2nd __init__ avoid self.

Related

call parent dispatch before child dispatch django

I have a mixin which beside other things simplifies call of request.user object.
class MyMixin(LoginRequiredMixin, View):
...
leader = False
employee = False
def dispatch(self, request, *args, **kwargs):
self.leader = request.user.is_leader()
self.employee = request.user.is_employee()
return super().dispatch(request, *args, **kwargs)
...
And I have a heir of DetailView which has it's own dispatch method.
class MyDetailView(MyMixin, DetailView):
def dispatch(self, request, *args, **kwargs):
if not self.leader:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
But as you could've told it ain't working. Is there a way to elegantly call parent dispatch method from it's heir?
You could make your attributes into properties so that the order of execution does not matter. Using cached_property will mean that the property is only evaluated once for each request
from django.utils.functional import cached_property
class MyMixin(LoginRequiredMixin, View):
#cached_property
def leader(self):
return self.request.user.is_leader()
#cached_property
def employee(self):
return self.request.user.is_employee()

Django how to extend generic View class

I noticed that I am setting site-wide context variables and request variables for many views on my site. Naturally, this situation calls for inheritance. If all of my view class-based views are inheriting from SiteView instead of the generic View, I can factor out all the commonalities into the SiteView child class. I can then inherit from SiteView on all my views. But, I cannot get this to work. Here is my code:
from django.contrib.auth.decorators import login_required
from django.views.generic import View
from django.utils.decorators import method_decorator
class SiteView(View):
''' Extends the generic django-supplied View class '''
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(SiteView, self).dispatch(*args, **kwargs)
def get(self, *args, **kwargs):
''' Adds the variables required in the get request '''
context = super(SiteView, self).get(*args, **kwargs)
context['common_var'] = 'some common value'
context['user'] = request.user
return self.render_to_response(context)
This throws the following TypeError:
dispatch() missing 1 required positional argument: 'request'
Any help would be appreciated
Edit: Even though the correct answer is marked, there were other issues with the code. In particular, the get method of the SiteView should not have the following line:
context = super(SiteView, self).get(*args, **kwargs)
This is because the View class does NOT have any get method.
You forgot to pass the request to the super().dispatch(..) call:
class SiteView(View):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(SiteView, self).dispatch(request, *args, **kwargs)
or you can just omit the request in the dispatch parameters, and thus pass it through *args and **kwargs:
class SiteView(View):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(SiteView, self).dispatch(*args, **kwargs)
It is however probably more elegant, to pass the name of the function, like:
#method_decorator(login_required, name='dispatch')
class SiteView(View):
# ...
EDIT: Note that a View has no get(..), post(..), etc. method. The dispatch(..) method will look if such method exists, and if so redirect to it. If such method does not exists, it will return a "405 Method Not Allowed" response.
Your get(..) function thus be implemented like:
#method_decorator(login_required, name='dispatch')
class SiteView(View):
''' Extends the generic django-supplied View class '''
def render_to_response(self, context):
# ...
def get(self, request, *args, **kwargs):
context = {
'common_var': 'some common value',
'user': request.user
}
return self.render_to_response(context)
It perhaps makes more sense to implement a "mixin" (perhaps with a subclass of the LoginRequiredMixin mixin [Django-doc].
For example like:
class SiteViewMixin(LoginRequiredMixin):
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context.update(common_var='some common value', user=self.request.user)
return context
and then use the mixin in another view, like:
class SomeView(SiteViewMixin, TemplateView):
# ...

Django get instance in inline form admin

Have a inline form class:
class ItemColorSelectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ItemColorSelectForm, self).__init__(*args, **kwargs)
#here i need current object
Inline class:
class ItemColorSelectInline(generic.GenericTabularInline):
model = ColorSelect
extra = 1
form = ItemColorSelectForm
Admin class
class ItemAdmin(admin.ModelAdmin):
inlines = [ItemColorInline,]
Question: how can a get current object in ItemColorSelectForm.
print kwargs return:
{'auto_id': u'id_%s', 'prefix': u'catalog-colorselect-content_type-object_id-__prefix__', 'empty_permitted': True}
Currently accepted solution is not thread safe. If you care about thread safety, never, ever assign an instance to a static class property.
Thread safe solutions are:
For Django 1.7 < 1.9 (possibly earlier versions, unclear):
from django.utils.functional import cached_property
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
self.instance = kwargs['instance']
super(ProxyFormSet, self).__init__(*args, **kwargs)
#cached_property
def forms(self):
kwargs = {'instance': self.instance}
forms = [self._construct_form(i, **kwargs)
for i in xrange(self.total_form_count())]
return forms
return ProxyFormSet
As of Django >= 1.9 it's also possible to pass form_kwargs:
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
form_kwargs = kwargs.pop('form_kwargs', {})
form_kwargs['instance'] = kwargs['instance']
super(ProxyFormSet, self).__init__(
*args, form_kwargs=form_kwargs, **kwargs)
return ProxyFormSet
Above solutions will make an instance kwarg available in the model form:
class InlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InlineForm, self).__init__(*args, **kwargs)
print('instance', kwargs['instance'])
Solution:
Override the formset method in Inline class
def get_formset(self, request, obj=None, **kwargs):
InlineForm.obj = obj
return super(InlineAdmin, self).get_formset(request, obj, **kwargs)
To fix: currently accepted solution not safe in multi-thread mode
Arti's solution works, another better option could be:
Instead of passing the current object id into the inline form,
use the object id to create a inline form field within the get_formset().
# admin.py
class TransactionInline(admin.TabularInline):
model = Transaction
form = TransactionInlineForm
def get_formset(self, request, obj=None, **kwargs):
# comment Arti's solution
# TransactionInlineForm.project_id = obj.id
formset = super().get_formset(request, obj, **kwargs)
field = formset.form.declared_fields['purchase']
field.queryset = get_object_or_404(Project, pk=obj.id).products.all()
return formset
# forms.py
class TransactionInlineForm(ModelForm):
purchase = ModelChoiceField(queryset=None, label='Purchase', required=False)
So, there is no need to override the __init__() in form anymore, neither the current object.
works in Django 2.1.7

Django: Passing arguments to ModelField at runtime

I am fairly new to Python + Django and I am stuck with the following problem. I have created a custom ModelField like:
class MyField(models.TextField):
def __init__(self, *args, **kwargs):
super(MyField, self).__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
# custom operations here
# need access to variable xyz
The model using this field looks something like:
class MyModel(models.Model):
my_field = MyField()
def __init__(self, model, xyz, *args, **kwargs):
self.instance = model
# how to pass xyz to ModelField before pre_save gets called?
self.xyz = xyz
def save(self, *args, **kwargs):
if self.instance:
self.my_field = self.instance
Q: Like it says in the comment, is there a way to pass a variable to the ModelField instance at runtime, ideally before my_field.pre_save() gets called?
You don't need to do anything to pass the xyz variable on -- it is an instance variable on the model, so it is already present in the model_instance variable that gets passed to pre_save()
class MyField(models.TextField):
def pre_save(self, model_instance, add):
...
# Access model_instance.xyz here
...
# Call the superclass in case it has work to do
return super(MyField, self).pre_save(model_instance, add)

Pass parameter to Form in Django

I have a custom form to which I would like to pass a parameter.
Following this example I came up with the following code :
class EpisodeCreateForm(forms.Form):
def __init__(self, *args, **kwargs):
my_arg = kwargs.pop('my_arg')
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
my_field = forms.CharField(initial=my_arg)
But I get the following error:
Exception Value: name 'my_arg' is not defined
How can I get it to recognize the argument in the code of the form ?
You need to set the initial value by referring to the form field instance in __init__. To get access to the form field instance in __init__, put this before the call to super:
self.fields['my_field'].initial=my_arg
And remove initial=my_arg from where you declare my_field because at that point (when class is declared) my_arg is not in scope.
The thing is that my_field is initialized when the class is created, but my_arg is initialized when a new instance is created, far too late for my_field to know its value. What you can do is initialize my_field in __init__ too:
def __init__(self, *args, **kwargs):
my_arg = kwargs.pop('my_arg')
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
if not self.my_field:
self.my_field = my_arg
This code is executed once at import time:
my_field = forms.CharField(initial=my_arg)
and this code is executed on form instance creation:
my_arg = kwargs.pop('my_arg')
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
So this won't work this way. You should set initial value for the field in your __init__ method.
By the way, all this seems unnecessary, why don't use 'initial' keyword in a view?
Considering your comment, I would do this:
class EpisodeCreateForm(forms.Form):
def __init__(self, *args, **kwargs):
self.my_arg = kwargs.pop('my_arg')
kwargs.setdefault('initial', {})['my_field'] = self.my_arg
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
def save(self):
do_something(self.my_arg)
...
super(EpisodeCreateForm, self).save()
my_field = forms.CharField()
Passing initial to the superclass and letting it do the work seems cleaner to me than directly setting it on the field instance.
You simply need to pop your arg before super() and put it in the fields dictionnary after super() :
class EpisodeCreateForm(forms.Form):
my_field = forms.CharField(label='My field:')
def __init__(self, *args, **kwargs):
my_arg = kwargs.pop('my_arg')
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
self.fields['my_arg'].initial = my_arg
Then, simply call
form = EpisodeCreateForm (my_arg=foo)
As an example, say you have a table of Episodes, and you want to show the availables ones in a choices menu, and select the current episode. For that, use a ModelChoiceField:
class EpisodeCreateForm(forms.Form):
available_episode_list = Episode.objects.filter(available=True)
my_field = forms.ModelChoiceField(label='My field:',
queryset=available_episode_list)
def __init__(self, *args, **kwargs):
cur_ep = kwargs.pop('current_episode')
super(EpisodeCreateForm, self).__init__(*args, **kwargs)
self.fields['current_episode'].initial = cur_ep