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
Related
I have a cbv create view that displays a modelform.
I want to preselect a foreignkey field which is displayed as select choice field.
My problem is that kwargs.get('building_id') in modelform returns None
class VlanCreateForm(ModelForm):
class Meta:
model = Vlan
fields = ['number','description','network','building']
def __init__(self, *args, **kwargs):
building_id = kwargs.get('building_id')
super().__init__(*args, **kwargs)
self.fields['building'].initial = building_id
building is a foreign key to buildings. If I put a constant like self.fields['building'].initial = 1 it is working
class VlanCreateView(CreateView):
model = Vlan
form_class = VlanCreateForm
and the url is
vlan/building/<int:building_id>/create
so I call it like
vlan/building/1/create
You'll need to define the building id in get_form_kwargs
class VlanCreateView(CreateView):
...
building_id=None
def dispatch(self, request, *args, **kwargs):
# Retrieves the building id from url
self.building_id=kwargs.get("building_id")
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self, *args, **kwargs):
kwargs=super().get_form_kwargs(*args, **kwargs)
## Sends building id to the form
kwargs["building_id"]=self.building_id
return kwargs
class VlanCreateForm(ModelForm):
class Meta:
model = Vlan
fields = ['number','description','network','building']
def __init__(self, *args, **kwargs):
self.building_id = kwargs.get('building_id')
super().__init__(*args, **kwargs)
self.fields['building'].initial = self.building_id
def post_url(self):
return reverse('app_name:url_name',kwargs={'cg_id':self.building_id} )
In form post action use this post_url for submit form.
then you got the building_id in your view kwargs
I have a form for which I'd like to use user data to filter the content of a choicefield.
Following this solution, I added all references to user in the __init__ function of my Form class:
class MyChoiceField(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(MyChoiceField, self).__init__(*args, **kwargs)
user_id = self.user.id
myobjects = forms.ModelChoiceField(label='',queryset = Myobject.objects.values_list('name', flat=True).exclude(name__isnull=True).filter(Q(person__isnull=True) | Q(person=user_id)).distinct(),empty_label=None)
And in the view I call it as:
def my_view(request):
my_list = MyChoiceField(user = request.user)
context = {
'my_list': my_list,
}
return render(request, 'foo/bar.html', context)
Debugging the __init__ part indicates the queryset content is correct, but in the view, my_list contains the following:<MyChoiceField bound=False, valid=Unknown, fields=()>.
Should I include something in the form class, outside of the __init__ part for this to work?
try this
class MyChoiceField(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MyChoiceField, self).__init__(*args, **kwargs)
user_id = user.id
Whenever I have to add a value to the instance of a form obtained from the context or from the URL I do it in the following way, using form.instance.
class PreguntaForm(forms.ModelForm):
class Meta:
model = Pregunta
fields = ('etiqueta', 'grupo', 'tipo_pregunta', 'opciones', 'mostrar_tabla', 'activo')
def __init__(self, *args, **kwargs):
cuestionario = kwargs.pop('cuestionario', False)
super(PreguntaForm, self).__init__(*args, **kwargs)
self.fields['grupo'].queryset = Grupo.objects.filter(cuestionario=cuestionario)
class PreguntaNueva(InfoPregunta, CreateView):
form_class = PreguntaForm
encabezado = 'Nueva Pregunta'
model = Pregunta
def get_form_kwargs(self):
kwargs = super(PreguntaNueva, self).get_form_kwargs()
kwargs['cuestionario'] = self.dame_cuestionario()
return kwargs
def form_valid(self, form):
form.instance.cuestionario = self.dame_cuestionario()
return super(PreguntaNueva, self).form_valid(form)
The problem that arises now is that I want to perform a check CreateView and EditView. To DRY, I want to do it in the clean method of the model, but the value that I assign to form.instance.cuestionario, is not available within the clean method. How could I do it? This value must not be edited by the user in any case.
Yes it is, you pass it in via get_form_kwargs; you just need to assign it to an instance variable in the form's __init__.
def __init__(self, *args, **kwargs):
self.cuestionario = kwargs.pop('cuestionario', False)
super(PreguntaForm, self).__init__(*args, **kwargs)
self.fields['grupo'].queryset = Grupo.objects.filter(cuestionario=self.cuestionario)
def clean(self):
# do something with self.cuestionario
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
explain me please how to use it in my Admin?
You can just create a custom ModelForm for your model, with the following:
remove_the_file = forms.BooleanField(required=False)
def save(self, *args, **kwargs):
object = super(self.__class__, self).save(*args, **kwargs)
if self.cleaned_data.get('remove_the_file'):
object.the_file = ''
return object
Use that form in your ModelAdmin, and there's no need to change the database.
there is what i created in forms.py:
class MediaForm(forms.ModelForm):
remove_the_file = forms.BooleanField(required=False)
def save(self, *args, **kwargs):
object = super(self.__class__, self).save(*args, **kwargs)
if self.cleaned_data.get('remove_the_file'):
object.the_file = ''
return object
And there is my admin.py:
class MediaAdmin(admin.ModelAdmin):
raw_id_fields = ('parent',)
how should i change MediaAdmin class to apply it?
class MediaAdmin(admin.ModelAdmin):
raw_id_fields = ('parent',)
form = MediaForm