Pass parameter to Form in Django - 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

Related

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 object is not iterable with custom instance

I am trying to leave my object itself out of the queryset of possible options. Problem is i get the error: 'Country' object is not iterable
Not sure where i am going wrong.
My view:
def edit_country(request, country_id):
country = get_object_or_404(Country, pk=country_id)
country_form = CountryForm(instance=country)
return render(request, 'create_country.html', {'country_form': country_form})
My form init:
def __init__(self, *args, **kwargs):
super(CountryForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
self.fields['likes'].queryset = Country.objects.exclude(kwargs['instance'])
self.fields['hates'].queryset = Country.objects.exclude(kwargs['instance'])
Where do i go wrong?
Change the order of the method, so you pop the kwarg first. You are sending the kwarg to super.
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance', None)
#all other stuff

Passing a kwargs parameter to a ModelForm Field constructor?

How can I pass a parameter in to a ModelForm Field constructor?
class ThingSettingsForm(ModelForm):
things = forms.ModelChoiceField(empty_label='--',queryset=self.?????)
def __init__(self, *args, **kwargs):
super(ThingSettingsForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
instance = kwargs['instance']
?????? = instance.visible_things
#self.fields['things'] =
#forms.ModelChoiceField(queryset=instance.visible_things)
class Meta:
model = Gallery
fields = (
'title',
'summary',
'things',
)
In the underlying model 'things' is a models.ForeignKey, and the default of showing every possible relation is not appropriate.
If visible_things is a queryset, you can change the queryset attribute of the form field:
self.fields['things'].queryset = instance.visible_things
It really does have to be a queryset, not an arbitrary iterable, but other than that it's easy.
Just add a kwarg if the arg is optional:
myform = ThingSettingsForm(thing=<my instance>)
You'll have to change the init method to pop your new arg first, as super isn't expecting this arg:
def __init__(self, *args, **kwargs):
instance = kwargs.pop('thing', None)
super(ThingSettingsForm, self).__init__(*args, **kwargs)
Or if its required, just add it into the init signature:
def __init__(self, thing, *args, **kwargs):
pass
and call it thus:
myform = ThingSettingsForm(<my instance>)

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)

Django - Can't remove empty_label from TypedChoiceField

I have field in my model:
TYPES_CHOICES = (
(0, _(u'Worker')),
(1, _(u'Owner')),
)
worker_type = models.PositiveSmallIntegerField(max_length=2, choices=TYPES_CHOICES)
When I use it in ModelForm it has "---------" empty value. It's TypedChoiceField so it hasn't empty_label attribute., so I can't override it in form init method.
Is there any way to remove that "---------"?
That method doesn't work too:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
if self.fields['worker_type'].choices[0][0] == '':
del self.fields['worker_type'].choices[0]
EDIT:
I managed to make it work in that way:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
if self.fields['worker_type'].choices[0][0] == '':
worker_choices = self.fields['worker_type'].choices
del worker_choices[0]
self.fields['worker_type'].choices = worker_choices
The empty option for any model field with choices determined within the .formfield() method of the model field class. If you look at the django source code for this method, the line looks like this:
include_blank = self.blank or not (self.has_default() or 'initial' in kwargs)
So, the cleanest way to avoid the empty option is to set a default on your model's field:
worker_type = models.PositiveSmallIntegerField(max_length=2, choices=TYPES_CHOICES,
default=TYPES_CHOICES[0][0])
Otherwise, you're left with manually hacking the .choices attribute of the form field in the form's __init__ method.
self.fields['xxx'].empty_value = None would not work If you field type is TypedChoiceField which do not have empty_label property.
What should we do is to remove first choice:
class JobOpinionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
for field_name in self.fields:
field = self.fields.get(field_name)
if field and isinstance(field , forms.TypedChoiceField):
field.choices = field.choices[1:]
Try:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
self.fields['worker_type'].empty_value = None
https://docs.djangoproject.com/en/1.3/ref/forms/fields/#typedchoicefield