I'm trying to modify the admin ModelMultipleChoiceField so it does load data dynamically.
Because I want to load data dynamically the queryset for ModelMultipleChoiceField is empty when creating an instance of the form, for that reason when doing form validation django complains that the choices aren't valid because they can't be found in the queryset.
Is there any way around this ?
FORM:
class FormName(forms.ModelForm):
dinamic_field = forms.ModelMultipleChoiceField(Entry.objects.none(),widget=
widgets.FilteredSelectMultiple("", False))
class Meta:
model = ModelName
fields = ('dinamic_field',)
class Media:
js = ('jquery.js', 'dinamic_field.js')
VIEW:
def add(request):
if request.method == 'POST':
form = FormName(request.POST)
if request.is_ajax():
obj = Packages.objects.get(id = form.data['package'])
form.fields['dinamic_field'].queryset = Entry.objects.filter(test__in =obj.all())
return HttpResponse(form['dinamic_field'])
if form.is_valid():
job = form.save()
return HttpResponseRedirect('../../')
else:
form = FormName()
return return render_to_response('/template_name', {'form': 'form'})
Have you tried overriding your form's __init__() method and setting the queryset for the field? Something like:
class JobForm(forms.ModelForm):
dynamic_field = forms.ModelMultipleChoiceField(Entry.objects.none(),widget=
widgets.FilteredSelectMultiple("", False))
def __init__(self, *args, **kwargs):
super(JobForm, self).__init__(*args, **kwargs)
self.dynamic_field.queryset = Entry.objects.<etc>
Related
I want to add some choices to an exiting fieldchoice from the database,
I have did that in my views.py:
def operation(request):
if request.method == 'GET':
form = FormOperation(instance=request.user, )
var = Metry.objects.filter(user=request.user).last().profile.name
varr = Metry.objects.filter(user=request.user).last().profile.category
form.fields['dite'].choices.append((varr, var))
print(form.fields['dite'].choices)
else:
if request.user.is_authenticated:
form = FormOperation(request.POST, )
if form.is_valid():
form.save()
return render(request, 'pages/operation.html', {'form': form})
models.py:
dite = models.CharField(null = True, max_length=60,choices = CHOICES)
forms.py:
class FormOperation(forms.ModelForm):
class Meta:
model = Operation
exclude = ("user",)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
after "append" the choice , As a test I have did a "print" to see the choice and it's normal i can see it in my terminal, but not in the page browser of my django application indeed ,i can see just the first choices without considering what i have append in my views.py,...
Any help will be appreciated.
You can use list extend method. (Make sure your CHOICES is a LIST)
new_choices[('abc', 'def'),]
CHOICES.extend(new_choices)
(Note: It will not override the existing value in select field, and will create another choice with same value)
I have a Django application and I wanna create an update form with ModelForm but I have to get the fields parameter from user_profile model. So I have created my form and view such as below.
forms.py;
class UpdateForm(forms.ModelForm):
class Meta:
model = Data
fields = []
def __init__(self, *args, **kwargs):
input_choices = kwargs.pop('input_choices')
super(UpdateForm, self).__init__(*args,**kwargs)
self.fields = input_choices
views.py;
#login_required(login_url = "user:login")
def updateData(request,id):
features = request.user.profile.selected_features
instance = get_object_or_404(Data,id = id)
form = UpdateForm(request.POST or None, input_choices=features, instance=instance)
if form.is_valid():
data = form.save(commit=False)
data.author = request.user
data.save()
return redirect("data:dashboard")
return render(request,"update.html",{"form":form})
However, I'm getting a list indices must be integers or slices, not str error in the return render(request,"update.html",{"form":form}) line. There is an issue with the fields parameter but I couldn't find anything.
How can I pass the fields parameter from view to ModelForm?
You can use modelform_factory to create a form with dynamic fields
from django.forms import modelform_factory
#login_required(login_url = "user:login")
def updateData(request,id):
features = request.user.profile.selected_features
instance = get_object_or_404(Data, id=id)
UpdateForm = modelform_factory(Data, fields=features)
form = UpdateForm(request.POST or None, instance=instance)
If features is a queryset, you'll probably have to use something like features.values_list('name', flat=True) to get just the names of the fields
How can i pass extra kwargs to django modelform in modelformset factory
I want for example, disable all form fields in detail view.
forms.py:
class ConceptForm(forms.ModelForm):
class Meta:
model = app_models.Concept
fields = '__all__'
def __init__(self, *args, **kwargs):
self.action = kwargs.pop('action', None)
super(ConceptForm, self).__init__(*args, **kwargs)
if self.action is not None:
if self.action == 'detail':
for field in self.fields:
self.fields[field].widget.attrs['readonly'] = True
views.py
modelformset = modelformset_factory(
model = model,
fields = show_fields,
form = ConceptForm(kwargs={'action': 'detail'), <-- I would like something like this
)
The error is:
'ConceptForm' object is not callable
Without call init not errors showed but the form fields not are disabled
modelformset = modelformset_factory(
model = model,
fields = show_fields,
form = ConceptForm
)
Thanks in advance
As is specified in the Passing custom parameters to formset forms section of the documentation, you can make use of the form_kwargs=… parameter of the FormSet:
ConceptFormSet = modelformset_factory(
model = Concept,
form = ConceptForm,
fields = show_fields
)
formset = ConceptFormSet(form_kwargs={'action': 'detail'})
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.
models:
class UserDataUpdate(models.Model):
code = models.CharField(max_length=8)
address = models.CharField(max_length=50)
class UserSurvey(models.Model):
about_treatment = models.CharField(max_length=2)
user_data_update = OneToOneField(UserDataUpdate)
views:
#login_required
def generate_survey(request):
user_data_update = UserDataUpdate.objects.get(code=request.user.username)
if request.method == 'POST':
form = SurveyForm(request.POST)
if form.is_valid():
form.save()
return redirect('/success')
else:
form = SurveyForm(request.GET)
return render_to_response(
'survey.html',
{'form': form },
context_instance = RequestContext(request))
form:
class SurveyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SurveyForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget = RadioSelect(choices=SURVEY_CHOICES)
class Meta:
model = Survey
exclude = ['user_data_update']
I just need a way to set the UserDataUpdate id (that already has been created) on a UserSurvey.
I'm getting this message on generate_survey request.POST:
user_data_update_app_usersurvey.user_data_update_id may not be NULL
It should be clear to you that you get the user_data_update value but then don't do anything with it. I guess you want to set it on the object that's created by the form:
if form.is_valid():
instance = form.save(commit=False)
instance.user_data_update = user_data_update
instance.save()
(I don't understand what all that stuff in the form's __init__ method is supposed to do. You only have one field in your form, anyway.)