Django CheckBoxSelectMultiple not working - django

My website helps musicians connect and borrow/lend their instruments from/to one another.
I have a form on my webpage called InstrumentSearchForm which let's you search for an instrument by category, date and location.
class InstrumentSearchForm(forms.Form):
categories = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple())
date = forms.DateField(required=False)
location = forms.CharField(required=False)
This form is initialized in the view and passed to the template in the context (I caught out things that were unrelated clutter)
def main_page(request):
if request.method == 'POST':
....
else:
form_search = InstrumentSearchForm(prefix="search") # An unbound form
args = {}
args.update(csrf(request))
args['form_search'] = form_search
...
categories_to_show = Categories.objects.filter(users = request.user) #show users categories
form_search.fields['categories'].queryset = categories_to_show
return render(request, 'main_page.html', args)
The trouble is, that in the template page, when I say
{{ form_search }}
the form is missing the "Categories" widget. It only has the date and location boxes. In the source, it doesn't show any choices for Categories even though I know they exist.
I've been trying to figure out what the problem, with no results. Does anyone have any ideas?

I can't see anything necessarily wrong with the code you've posted, but I would put the logic into the form:
class InstrumentSearchForm(forms.Form):
categories = forms.MultipleChoiceField(queryset=Categories.objects.none(), required=False, widget=forms.CheckboxSelectMultiple())
...
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(InstrumentSearchForm, self).__init__(*args, **kwargs)
self.fields['categories'].queryset = Categories.objects.filter(users=user)
and instantiate it with:
form = InstrumentSearchForm(user=request.user)
Does that help at all?

Related

Passing default values into an unbound django form

I have two select classes that I am trying to create in an unbound form. The data selections are only relevant to the presentation that is created in the view, so are throwaways and do not need to be saved in a model.
The challenge I have is that I can pass in the field listings ok, but how do I set "default" checked / selected values so that the form becomes 'bound'?
views.py
def cards(request):
sort_name = []
sort_name.append("Alphabetic Order")
sort_name.append("Most Popular")
sort_name.append("Least Popular")
sort_name.append("Highest Win Rate")
sort_name.append("Lowest Win Rate")
sort_id = range(len(sort_name))
sort_list = list(zip(sort_id, sort_name))
<more code to make filt_list and zip it>
if request.method == 'POST':
form = cardStatsForm(request.POST, sortList=sort_list, filtList=filt_list)
if form.is_valid():
do something
else:
do something else
else:
form = cardStatsForm(filter_list, sort_list)
forms.py
class cardStatsForm(forms.Form):
def __init__(self, filterList, sortList, *args, **kwargs):
super(cardStatsForm, self).__init__(*args, **kwargs)
self.fields['filts'].choices = filterList
self.fields['filts'].label = "Select player rankings for inclusion in statistics:"
self.fields['sorts'].choices = sortList
self.fields['sorts'].label = "Choose a sort order:"
filts = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=(), required=True)
sorts = forms.ChoiceField(choices=(), required=True)
The difficulty I am having is the the form fails the "is_valid" test since it is not bound, and I have the "required=true" setting (so that the user must select a checkbox / select a value), but I cannot enforce the logic since it seems the form is never 'bound'.
You can use django forms validation or pass defult value in your views.py. It will return unbound forms if value doesn't match with your default value.
let show you how to do it in your views.py:
error_message = None
default_value = "jhone"
if form.is_valid():
name = request.POST['name']
defult_name = jhone
if defult_name != name:
error_message = 'Name must be jhone'
if not error_message:
form.save() #it will only save forms if default value match
else:
do something else
context = {'error_message':error_message,
'default_value': default_value,
'form':form,
} #pass the context in your html template for showing default value and error message
in your .html
{{error_message}}
<input type=text name='name' {%if form.is_bound %} value="{{default_value}} {%endif%}">
I was able to correct my issue by adding "inital=0" and modifying my form call as outlined below:
forms.py
filts = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=(), initial=0, required=True)
sorts = forms.ChoiceField(choices=(), initial=0, required=True)
views.py
if request.method == 'POST':
form = cardStatsForm(data=request.POST, sortList=sort_list, filterList=filter_list)
else:
form = cardStatsForm(filter_list, sort_list)

access to pk in a template form

I want to display all elements of a form including the pk and I have not found a satisfying method to do so:
class SomeForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["number"] = forms.IntegerField(required = True)
self.fields["id"] = forms.IntegerField(disabled = True)
self.fields["data"] = forms.CharField(required = False)
class Meta:
model = SomeModel
fields = ["id", "number", "data"]
So now I just call the form in the view with:
class SomeView(TemplateView):
template_name = "app/sometemplate.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
obj = MODEL.objects.get(pk = context["pk"])
MODEL_data = {}
MODEL_data["number"] = obj.number
MODEL_data["id"] = obj.pk
MODEL_data["data"] = obj.data
context["form"] = SomeForm(initial = MODEL_data)
return context
and now in the templatetags I have a filter get_pk:
#register.filter(name='get_pk')
def get_pk(obj):
return obj.initial["id"]
and I get the pk in the template with {{ form|get_pk }} and I feel like this is not smart. I tried stuff like {{ form.instance.id }} like suggested here. I still feel there must be an easy way to achieve this?
I don't know why you need form in template view? You just need to get the context no more..
There are many comments in shared code like:
1-Disabled id field in initial form
2-Using different model name in form and get context data
3-Filtering context by using context["pk"] before assessing obj
Find the revised code,
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["obj"] = SomeModel.objects.get(pk = the desired value)
return context
Note: if you want user to insert pk then you can use input field inside form with get method and get its value inside get context data in this way:
context["pk"] = self.request.GET.get("name of input field ")
In template html you can display fields directly:
{{obj.id}},{{obj.name}}, {{obj.data}}

Printing kwargs.pop displays the right value, using it in a method takes None

I ant to pass a PK in kwargs to a form :
views.py
def create_mapping_form(request, pk):
context = {
'form': MappingForm(pk=pk)
}
return render(request, 'flows/partials/mapping_form.html', context)
In the form i retrieve the PK using :
forms.py
class MappingForm(forms.ModelForm):
class Meta:
model = MappingField
fields = (
'fl_col_number',
'fl_col_header',
'fl_cross_field_name',
'fl_cross_position',
'fl_replace_list'
)
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', 'Rien')
super(MappingForm, self).__init__(*args, **kwargs)
#print(pk)
self.helper = FormHelper(self)
self.fields['fl_replace_list'].widget.attrs[
'placeholder'] = "Liste de tuples eg. : [('reman','ES'), ('Gasoline','Diesel')] "
headers = GetCsvHeadersAndSamples(pk)['headers']
[...]
For populating some fields' CHOICES, I use a method that returns a dic (last line above)
headers = GetCsvHeadersAndSamples(pk)['headers']
But something I can't explain sends Rien to GetCsvHeadersAndSamples while when I print(pk) the right value is shown. (GetCsvHeadersAndSamples is not useful, I don't show it).
Note: I display the form in template using HTMX. The issue seems not coming from HTMX because when I hard-code the PK, everything is ok.
For the moment, I have found nothing else but storing the PK value in a "temp" file but this slows down my script.
Thanks
I moved GetCsvHeadersAndSamples from forms.py to views.py and passed the return of GetCsvHeadersAndSamples in form kwargs.
[...]
headers_samples = GetCsvHeadersAndSamples(pk)
fiche_headers = fetch_fiche_headers()
form = MappingForm(request.POST or None,
headers_samples=headers_samples,
fiche_headers=fiche_headers)
[...]
Then I retrieve them in the form's init
def __init__(self, *args, **kwargs):
self.headers_samples = kwargs.pop('headers_samples', None)
self.fiche_headers = kwargs.pop('fiche_headers', None)
Issue solved with a workaround ... but still not explained

Getting the value of a forms.ChoiceField instead of the label, from the view

I have a FormView from which I'd like to do a bit of form processing.
Within the form_valid() I'm trying to obtain the submitted form's values in order to instantiate a different object.
When I get the form.cleaned_data however, I am returned a dictionary of {'form field name' : 'choice label'}.
I'd like to get the value corresponding to the choice label.
Here is the FormView--the get_form_kwargs bit is simple passing custom choices to the view:
class RequestView(FormView):
form_class = RequestForm
template_name = 'profile/request.html'
success_url = '/'
def get_form_kwargs(self, *args, **kwargs):
requester_obj = Profile.objects.get(
user__username=self.request.user)
accepter_obj = Profile.objects.get(
user__username=self.kwargs.get('username'))
r_favorite_set = requester_obj.get_favorite()
a_favorite_set = accepter_obj.get_favorite()
kwargs = super().get_form_kwargs()
kwargs['your_favorite'] = r_favorite_set
kwargs['their_favorite'] = a_favorite_set
return kwargs
def form_valid(self, form):
super(RequestView, self).form_valid(form)
# I've tried get_FOO_display() extra instance method
print(form.get_your_favorite_display())
# The following will return the choice label
print(form.cleaned_data.get('your_favorite'))
Favorites.objects.create(#need form choice selections)
return redirect(self.get_success_url())
Code explanation: Within the form_valid I hope to create a different object using the selection from the submitted form.
So how do I get the submitted choice instead of the label? Is this even the proper place to do this?
edit: The Form:
class RequestForm(forms.Form):
your_favorite = forms.ChoiceField(
choices=[],
widget=RadioSelect,
required=True,
label="What would you like to exchange?"
)
their_favorite = forms.ChoiceField(
widget=RadioSelect,
required=False,
)
def __init__(self, *args, **kwargs):
your_choices = kwargs.pop('your_favorite')
their_choices = kwargs.pop('their_favorite')
super().__init__(*args, **kwargs)
self.fields['your_favorite'].choices = your_choices
self.fields['their_favorite'].choices = their_choices
edit2:
def get_favorite(self):
return (('a', self.fave1), ('b', self.fave2), ('c', self.fave3))
edit3:
...So I could've just done this all along.
def get_favorite(self):
return ((self.fave1, self.fave1), (self.fave2, self.fave2), (self.fave3, self.fave3))
BUT this causes some really funky behavior in the form. For some reason this causes a radio to be selected by default with every GET request to the form view. It will select the third choice to be selected if all the options are "None", or the first option to be selected if only the first is "None".
Anyway, this may be a question for a separate post.

giving initial value raises 'value for field is required' error in django

Django form trips me many times...
I gave initial value to a ChoiceField (init of Form class)
self.fields['thread_type'] = forms.ChoiceField(choices=choices,
widget=forms.Select,
initial=thread_type)
The form which is created with thread_type with the above code doesn't pass is_valid() because 'this field(thread_type) is required'.
-EDIT-
found the fix but it still perplexes me quite a bit.
I had a code in my template
{% if request.user.is_administrator() %}
<div class="select-post-type-div">
{{form.thread_type}}
</div>
{% endif %}
and when this form gets submitted, request.POST doesn't have 'thread_type' when user is not admin.
the view function creates the form with the following code:
form = forms.MyForm(request.POST, otherVar=otherVar)
I don't understand why giving initial value via the the following(the same as above) is not enough.
self.fields['thread_type'] = forms.ChoiceField(choices=choices,
widget=forms.Select,
initial=thread_type)
And, including the thread_type variable in request.POST allows the form to pass the is_valid() check.
The form class code looks like the following
class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm):
title = TitleField()
tags = TagNamesField()
#some more fields.. but removed for brevity, thread_type isn't defined here
def __init__(self, *args, **kwargs):
"""populate EditQuestionForm with initial data"""
self.question = kwargs.pop('question')
self.user = kwargs.pop('user')#preserve for superclass
thread_type = kwargs.pop('thread_type', self.question.thread.thread_type)
revision = kwargs.pop('revision')
super(EditQuestionForm, self).__init__(*args, **kwargs)
#it is important to add this field dynamically
self.fields['thread_type'] = forms.ChoiceField(choices=choices, widget=forms.Select, initial=thread_type)
Instead of adding this field dynamically, define it in the class appropriately:
class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm):
title = TitleField()
tags = TagNamesField()
thread_type = forms.ChoiceField(choices=choices, widget=forms.Select)
When creating the form instance set an intitial value if needed:
form = EditQuestionForm(initial={'tread_type': thread_type})
And if you dont need this field, just delete it:
class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm):
def __init__(self, *args, **kwargs):
super(EditQuestionForm, self).__init__(*args, **kwargs)
if some_condition:
del self.fields['thread_type']
When saving form, check:
thread_type = self.cleaned_data['thread_type'] if 'thread_type' in self.cleaned_data else None
This approach always works well for me.