So as the title states, I'm trying to pass a parameter from views to forms. I've seen several Stack Overflow posts regarding this issue, but none of them solved my problem :(
So what I'm trying to build is basically a question-and-answer application. Each user answers to pre-provided questions. I want to make an answering template for each corresponding question. So here's my code:
forms.py
class AnswerForm(forms.ModelForm):
main_answer = formsCharField(required=True)
# Some other fields
def __init__(self, *args, **kwargs):
self.q = kwargs.pop('q')
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['main_answer'].label = q.main_question
views.py
def createAnswer(request, pk):
thisQuestion = Question.objects.get(question_number=pk)
AnswerFormSet = modelformset_factory(Answer, form=AnswerForm(q = thisQuestion), extra=1)
formset = AnswerFormSet(queryset=Answer.objects.filter(authuser=request.user.id, question_number=pk))
# And some other codes
I'm supposed to show this formset in my template using {% crispy_formset%}. However I keep getting this error: "name 'q' is not defined". What is the problem here?
You pass these when you construct the FormSet with the form_kwargs=… parameter [Django-doc]:
def createAnswer(request, pk):
thisQuestion = Question.objects.get(question_number=pk)
AnswerFormSet = modelformset_factory(Aswer, form=AnswerForm, extra=1)
formset = AnswerFormSet(
form_kwargs={'q': thisQuestion},
queryset=Answer.objects.filter(authuser=request.user.id, question_number=pk)
)
You also will get a name error, because you use q in the __init__ method, but q is not defined:
class AnswerForm(forms.ModelForm):
main_answer = formsCharField(required=True)
# Some other fields
def __init__(self, *args, **kwargs):
# ↓ assign to a local variable q
q = self.q = kwargs.pop('q')
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['main_answer'].label = q.main_question
Related
I have the following model relationship where a task is associated to an objective and an objective is associated to a User. I created a Django form that displays all the objectives that are associated to a User.
class DropDownMenuSelectedObjectivesForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedObjectivesForm, self).__init__(*args, **kwargs)
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None)
When I open my views.py I am able to see all the objectives from the User but I would like that the Django form dropdown menu could be initialized with the current objective that is associated with a task.
So far, I have tried the following to initialize the dropdown menu but I've got no success.
# views.py
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.objective.values('objective')[0]
form = TaskModelForm(request.POST or None, instance=task)
# Attempt 1 to initialize the ModelChoiceField
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
if request.method == "GET":
template_name = 'task/formTask.html'
return render(request, template_name, {'form': form, 'objective':objective})
# forms.py
class DropDownMenuSelectedGoalsForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedGoalsForm, self).__init__(*args, **kwargs)
# Attempt 2 to initialize the ModelChoiceField
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None, initial=2)
Even if I try to initialize the ModelChoiceField from the forms.py with a valid pk number, the modelchoicefield doesn't initialize.
Any idea or suggestion is really appreciated :)
I had to do the following to solve this problem:
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.goal.values_list('goal',flat=True)[0]
form = TaskModelForm(request.POST or None, instance=task)
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
The initial keyword is looking by name of the objective, not by pk.
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.
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 use inlineformset_factory with a custom form option in order to change the queryset and the widget of a m2m field, ie: ezMap. I want the form to give the user the option to add or remove the current selected_map to the m2m field with CheckBoxSelectMultiple widget. However, I dont want to give the user the ability to remove other objects that were already there. The problem is when I save the formset with formset.save_m2m(), it overides the field and erase all objects that were already saved.
How could I just add a new object without erasing others?
models: (some of unecessary fields were removed)
class Shapefile(models.Model):
filename = models.CharField(max_length=255)
class EzMap(models.Model):
map_name = models.SlugField(max_length=50)
layers = models.ManyToManyField(Shapefile, verbose_name='Layers to display', null=True, blank=True)
class LayerStyle(models.Model):
styleName = models.SlugField(max_length=50)
layer = models.ForeignKey(Shapefile)
ezMap = models.ManyToManyField(EzMap)
forms:
class polygonLayerStyleFormset(forms.ModelForm):
add_to_map = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleFormset, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to_map']
return instance
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, can_delete=True, extra=1, max_num=5,
fields = ['styleName', 'conditionStyle', 'fillColor', 'fillOpacity', 'strokeColor', 'strokeWeight', 'ezMap'], form=polygonLayerStyleFormset)
views:
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
styleFormset = ftlStylePolygonFormset
if request.POST:
formset = styleFormset(request.POST, instance=layer_selected)
if formset.is_valid():
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selecte)
else:
instance.ezMap.remove(map_selected)
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = styleFormset(instance=layer_selected)
#set initial data for add_to_map
for form in formset:
if form.instance.pk:
if map_selected in form.instance.ezMap.all():
form.fields['add_to_map'].initial = {'add_to_map': True}
I am confused as to what you're doing with the ezMap form field. You set its queryset to a single-element list, then use a CheckboxSelectMultiple widget for it. Are you setting up to let the user deselect that matching map, but not add new ones?
To do this at initialization, you need to define a custom base formset class and pass that in as the formset argument to your factory.
from django.forms.models import BaseInlineFormSet
class polygonLayerStyleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleForm, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
class polygonLayerStyleFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['map_selected'] = self.map_selected
return super(polygonLayerStyleFormset, self)._construct_form(i, **kwargs)
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, formset=polygonLayerStyleFormset, form=polygonLaterStyleForm, # and other arguments as above
)
It might be simpler to just go through the formset forms and directly change the field's queryset after creating it in your view:
formset = ftlStylePolygonFormset(instance=layer_selected)
for form in formset.forms:
form.fields['ezMap'].queryset = EzMap.objects.filter(id=map_selected.id)
Speaking of which, the usual convention is to split the POST and GET cases in the view:
from django.shortcuts import render
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
if request.method == 'POST':
formset = ftlStylePolygonFormset(request.POST, instance=layer_selected, map_selected=map_selected)
if formset.is_valid():
instances = formset.save()
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = ftlStylePolygonFormset(instance=layer_selected, map_selected=map_selected)
return render(request, "ezmapping/manage_layerStyle.html", {'layer_style': layerStyle_selected, 'layerStyleformset': formset, 'layer': layer_selected})
And it's better to use the redirect shortcut to reverse lookup a view for your redirect on success rather than hardcode the target URL. And to use get_object_or_404 or some equivalent when accessing objects based on URL arguments - right now a bogus URL will trigger an exception and give the user a 500 status error, which is undesirable.
To conditionally add to the ezMap relationship:
class polygonLayerStyleForm(forms.ModelForm):
add_to_map = forms.BooleanField()
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleForm, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to-map']
return instance
Then in the view:
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selected)
You could also do the add call in the save method, but then you'd have to set the map as member data sometime previously - and more importantly, deal with the commit=False case.
I'm needing to filter out a significant amount of objects from my query. Currently, it is grabbing all objects in the class, and I want to filter it to the relevant ones which are in a querystring. How can I do this? When I try, I get an Attribute Error stating
''QuerySet' object has no attribute '__name__'.'
The code that works, but very slowly is:
formset = modelformset_factory(Transaction, form=PaidDateForm, extra=0, can_delete=False)
Also, the formset:
formset = formset(request.POST, Transaction.objects.filter(pk__in=qs))
The QueryString that I am wanting to filter by is called 'qs.'
class PaidDateForm(forms.ModelForm):
formfield_callback = jquery_datefield
Amount",max_digits=14,decimal_places=2,required=False)
date_cleared = forms.DateField(label="Cleared Date",widget=JQueryDateWidget(), input_formats=settings.DATE_INPUT_FORMATS, required=False)
class Meta:
model = Transaction
include = ('date_time_created')
def __init__(self, *args, **kwargs):
self.queryset = Transaction.objects.filter(pk__in=qs)
super(PaidDateForm, self).__init__(*args, **kwargs)
for field in self.fields:
if field != 'date_cleared':
self.fields[field].widget = forms.HiddenInput()
self.fields['paid_amount'].widget.attrs['size'] = 12
self.initial['paid_amount'] = '%.2f' % (self.instance.usd_amount)
Look at the example in Django documentation:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#changing-the-queryset
If I understand your question correctly there is two approach to your problem:
First:
TransactionFormset = modelformset_factory(Transaction,form=PaidDateForm, extra=0, can_delete=False)
formset = TransactionFormset(queryset=Transaction.objects.filter(pk__in=qs))
Second options is to create BaseTransactionFormset
class BaseTransactionFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseTransactionFormSet, self).__init__(*args, **kwargs)
#create filtering here whatever that suits you needs
self.queryset = Transaction.objects.filter()
formset = modelformset_factory(Transaction, formset=BaseTransactionFormSet,form=PaidDateForm, extra=0, can_delete=False)
Does this code help you?
I hesitate you still need it, and yet this code works for me
FormSet = modelformset_factory(YourModel, fields=(...))
form_set = FormSet(queryset = YourModel.objects.filter(...))