Add a field value outside form in Django - django

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

Related

Passing and accessing value in Django form

I'm trying to pass info to my form and I have a bit of a struggle with that. My code looks as follows:
views.py
class ObjectUpdateView(UpdateView):
template_name = 'manage/object_form.html'
form_class = ObjectEditForm
def get_success_url(self):
#...
def form_valid(self, form):
return super(ObjectUpdateView, self).form_valid(form)
def get_object(self):
return get_object_or_404(Room, pk=self.kwargs['object_id'])
def get_form_kwargs(self, **kwargs):
objectid = self.kwargs['object_id']
object = Object.objects.get(id = objectid)
container = object.container
kwargs['container_id'] = container.id
return kwargs
forms.py
class ObjectEditForm(forms.ModelForm):
class Meta:
model = Object
fields = ['TestField']
def __init__(self, *args, **kwargs):
super(ObjectEditForm, self).__init__(*args, **kwargs)
self.Container_id = kwargs.pop('container_id')
form_page.html
{{fomr.kwarg.Container_id}}
As you can see I'd like to access Container_id value in my form_page.html. Unfortunately, nothing is there. What I also noticed, that with __init__ I had to add, now values are empty in my form. Before I added __init__ all values were properly passed (well, except Container_id).
Could you recommend how I can pass such value to be accessed in the form template?
You can render this with:
{{ form.Container_id }}
In your form you should first pop the container_id from the kwargs, like:
class ObjectEditForm(forms.ModelForm):
class Meta:
model = Object
fields = ['TestField']
def __init__(self, *args, **kwargs):
# first pop from the kwargs
self.Container_id = kwargs.pop('container_id', None)
super(ObjectEditForm, self).__init__(*args, **kwargs)
Use the context over the form
That being said, it is a bit strange that you pass this to the form, and not add this to the context data. You can simplify your view a lot to:
class ObjectUpdateView(UpdateView):
template_name = 'manage/object_form.html'
pk_url_kwarg = 'object_id'
form_class = ObjectEditForm
def get_success_url(self):
#...
def get_context_data(self, **kwargs):
objectid = self.kwargs['object_id']
object = Object.objects.get(id = objectid)
context = super().get_context_data()
context.update(container_id=object.container_id)
return context
Django automatically fetches a single element based on the pk_url_kwarg [Django-doc]. You only need to set it correctly, so here that is the object_id.
In that case, we can simply render this with:
{{ container_id }}
and you do not need to store this in the form.

Django 1.11 pass kwarg to modelform field

I would like to pass a kwarg to set a modelform field but im struggling to figure out how to do it.
My URL is as follows:
url(r'^tent/create/(?P<munc>\d+)',views.TentCreate.as_view(),name='tent_create'),
My view is simply:
class TentCreate(CreateView):
model = Tent
form_class = TentForm
And my form:
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=self.kwargs['munc'])
from the model:
class Tent(models.Model):
primary = models.ForeignKey(Mark,on_delete=models.CASCADE)
I can render the form fine without overriding def __init, with no filtering applied to the 'primary' field.
However attempting to use the def __init code I've described above to pass the munc kwarg to the form field is resulting in the following error:
"'TentForm' object has no attribute 'kwargs'"
I've been going around in circles trying to work through this so I would be really appreciative if anyone is able to provide me some guidance to solve this. This is my first Django project so I'm learning how I go so I assume I have made some fundamental error somewhere here!
Try overriding get_form_kwargs method:
views.py
class TentCreate(CreateView):
model = Tent
form_class = TentForm
def get_form_kwargs(self):
kwargs = super(TentCreate, self).get_form_kwargs()
kwargs.update({'munc': self.kwargs['munc']})
return kwargs
forms.py
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
munc = kwargs.pop('munc')
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=munc)
class TentCreate(CreateView):
form_class = TentForm
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
kwargs = self.get_form_kwargs()
print(kwargs, self.kwargs)
kwargs.update(self.kwargs)
return form_class(**kwargs)
forms.py
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
munc=self.kwargs['munc']
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=munc)
you must pop munc before call super(TentForm, self).__init__(*args, **kwargs)

set variable into queryset of forms.py from my generic view or url

I want to set a dynamic variable into queryset of forms.py , I used __init__ to pass the dynamic variable , I think the code in forms.py is correct, the problem is how to pass the variable in views?
forms.py :
class ContainerForm(forms.ModelForm):
vehicle=forms.ModelChoiceField(required=False,queryset=Vehicle.objects.all(),widget=forms.Select(attrs={'class':'form-control'}))
def __init__(self, *args, **kwargs):
vehicle_id = kwargs.pop('vehicle_id',None)
super(ContainerForm, self).__init__(*args, **kwargs)
if vehicle_id:
self.fields['vehicle'].queryset = Vehicle.objects.filter(id=vehicle_id)
views.py
class ContainerCreate(CreateView):
form_class = ContainerForm(id= vehicle_id)
template_name = 'vehicule_app/container_form.html'
the error said :
Exception Value:'ContainerForm' object is not callable
If you want to use the vehicle_id from the URL, then you can exclude the field from the model form:
class ContainerForm(forms.ModelForm):
class Meta:
model = Container
exclude = ['vehicle']
You can then fetch the parameter from self.kwargs, and set the value on the form's instance in get_form_kwargs:
class ContainerCreate(CreateView):
form_class = ContainerForm
template_name = 'vehicule_app/container_form.html'
def get_form_kwargs(self):
kwargs = super(ContainerCreate, self).get_form_kwargs()
if kwargs['instance'] is None:
kwargs['instance'] = Container()
kwargs['instance'].vehicle_id = self.kwargs['pk'] # Fetch the vehicle_id from the URL
return kwargs
Note that the above code will not validate the id from the URL. The user could change it to any value they like.
If you want to keep the vehicle field in the form but with a single choice, then override the __init__ method and set the queryset.
class ContainerForm(forms.ModelForm):
class Meta:
model = Container
def __init__(self, *args, **kwargs):
vehicle_id = kwargs.pop('vehicle_id')
self.fields['vehicle'].queryset = Vehicle.objects.filter(id=vehicle_id)
Then in the get_form_kwargs method, add vehicle_id to kwargs instead:
class ContainerCreate(CreateView):
form_class = ContainerForm
template_name = 'vehicule_app/container_form.html'
def get_form_kwargs(self):
kwargs = super(ContainerCreate, self).get_form_kwargs()
kwargs['vehicle_id'] = self.kwargs['pk']
return kwargs

ModelForm User Mixin

I've got some models with user field.
For this purpose I'd like to create a form mixin that would add self.user instance (which is provided to the form in views). Is it possible ?
Here's the example
class UserFormMixin(object):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, **kwargs):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if kwargs['commit']:
return obj.save()
else:
return obj
What I'd like to achieve:
class SomeFormWithUserField(UserFormMixin, ModelForm):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
if kwargs['commit']:
return data.save()
else
return data
class SomeOtherFormWithUser(UserFormMixin, ModelForm):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# no need to save here.. standard model form with user prepended on save()
The problem is that UserFormMixin doesn't know about model instance? Or am I wrong here?
I am getting some problems.. like 'commit' kwargs key error.. or object is not saved..
You're close, you just have some logic errors. First, in order to override ModelForm methods, your mixin needs to inherit from ModelForm.
class UserFormMixin(forms.ModelForm):
...
Then, any forms that inherit from it just inherit UserFormMixin, not ModelForm.
class SomeOtherFormWithUser(UserFormMixin):
...
Second, your __init__ method override is incorrect. You need to accept any and all args and kwargs that get passed into it.
def __init__(self, *args, **kwargs):
...
Finally, don't override the save method again, in the subclass. I guess it won't technically hurt anything, but what's the point of inheritance if you're going to repeat code, anyways? If user is not nullable, you can always add an if block to check if self.user is not None before adding it to the model. Of course, if user is not nullable, your model won't likely save without self.user anyways.
This one seems to work fine. Thanks Chris!
If this can be coded better please let me know.
class UserFormMixin(forms.ModelForm):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, commit=True):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if commit:
return obj.save()
else:
return obj
class SomeFormWithUserField(UserFormMixin):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
# self.send_mail() f.e.
return data.save()
class SomeOtherFormWithUser(UserFormMixin):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# this will work too

Django ModelForm with field overwrite does not post data

So, I have the following form:
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
My view:
d = Design.object.get(slug=fromInput)
....
DesignItemInlineFormSet = inlineformset_factory(Design, DesignItem, fk_name="design", form=DesignItemForm,)
if request.method == "POST":
formset = DesignItemInlineFormSet(request.POST, request.FILES, instance=d)
if formset.is_valid():
formset.save()
DesignItemInlineFormSet(instance=d)
As you can tell, in my form, I overwrote the quantity field to be a drop down instead of an integer field.
For some reason, when I submit the form, the data is not updated in the database. However, if I change the form to the following, it works (of course, it doesn't have the dropdowns I want, but it posts to the db). Why is this, and how do I fix it?
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
# CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
# self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
EDIT: Here is the DesignItem model:
class DesignItem(models.Model):
"""Specifies how many of an item are in a design."""
design = models.ForeignKey(Design, related_name="items")
quantity = models.PositiveIntegerField(default=1)
trackable = models.ForeignKey(Trackable, related_name="used")
have you tried just overriding the widget instead of the whole field?
i guess you want a select widget
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'].widget = forms.Select(choices=CHOICES)