Having the following models:
#models.py
Column(models.Model):
name = models.CharField()
Input(models.Model):
column = ForeignKey(Column)
text = models.CharField()
And the following form:
class InputForm(Modelform):
class Meta():
model=Input
The following works:
c = Column.objects.get(...)
i=InputForm({'column':c.id})
i.is_valid() #true
In my application I am generating many forms, to avoid clashes I prefix it:
i=InputForm({'column':c.id}, prefix=prfx()) #prfx() is dynamically generated
i.errors # ({'column':['This field is required']})
i.data['column'] is still the right value
I also tried:
i.column = c
i.errors # ({'column':['This field is required']})
How do I populate the column field?
I cannot save the form as long as it does not validate
EDIT What I am trying to achieve:
I am building dynamic forms:
form_list = [InputForm(column, prefix=column.id) for column in col_list]
In the template I iterate over this form list (and I want to set the column field to be invisible.
{% for form in form_list %}
{{form.as_ul}}
{%endfor%}
This form then shall be processed by an AjaxView. The relation between text and column is achieved by the invisible field.
The first parameter to the form is the input data, which is usually request.POST. If you render a Form with a prefix, you will see that all form html elements will have a prefixed name, for example <input name='yourprefix_text' /> etc. If you POST such a form to your Django app, the POSTed data will have the prefix, too.
So, if you are using a prefix, the input data needs to be prefixed, too:
f = InputForm({'yourprefix_column': c.id}, prefix='yourprefix')
Usually, it is a better idea to use the initial parameter to the form for default values, because otherwise the form is always bound, and this has some consequences, for example default/initial values for other fields are will not work.
f = InputForm(prefix='yourprefix', initial={'column': c})
# or ...
form_list = [InputForm(prefix=column.id, initial={'column': column})
for column in col_list]
If you want to always set the column to a programatically determined value, and not allow the user to change it, it is better to not include the field in your form, and set the field manually after saving the object:
f = InputForm(request.POST)
if f.is_valid():
instance = f.save(commit=False)
instance.column = c
instance.save()
To make your field hidden, you can change the widget, as described in the documentation:
class InputForm(ModelForm):
class Meta:
widgets = {
'column': forms.HiddenInput,
}
# ...
Related
I am trying to create a multiple choice form where any combination of languages can be chosen. It's within a search form field:
class AdvancedSearchForm(SearchForm):
terms_show_partial_matches = forms.BooleanField(required=False,
label=_("Show partial matches in terms")
)
definitions_show_partial_matches = forms.BooleanField(required=False,
label=_("Show partial matches in definitions")
)
case_sensitive = forms.BooleanField(required=False,
label=_("Case sensitive")
)
...
I would like to implement something like this:
filter_by_part_of_speech = forms.ModelChoiceField(
queryset=PartOfSpeech.objects.all(), required=False,
label=_("Filter by part of speech")
)
However, it needs to be a multiple choice field so that any of the values can be chosen. Ideally though, I'm looking for a form where checkboxes are already checked. So something like this:
LANG_CHOICES = (
("1", "lang1"),
("2", "lang2"),
("3", "lang3"),
("4", "lang4"),
)
filter_by_language = forms.MultipleChoiceField(choices=Language.objects.all().filter(name__in=LANG_CHOICES).values(), required=False, label=_("Filter by language"))
The filter is called from the view with something like this:
tqs = tqs.filter(language=language_filter)
Now although the search works fine, the values are not displayed. On the other hand, they are displayed if I fill up a list and simply write:
choices=list(lang_list)
But then, obviously, the search is not actually performed.
Therefore, my questions are:
Can the constructor be adapted to display the values correctly?
Should I rather implement the filter in the view? If so, how?
Am I using the correct type of form or are there better options, such as providing a list of checkboxes that are checked by default?
I am using Django 2.2 (planning to upgrade soon) for now.
The template file simply refers to the search def in the view, which calls the advanced search form and the others:
{% block breadcrumbs_item %}{% trans "Advanced Search" %}{% endblock %}
Not that relevant I think, but here is the Language model:
class Language(models.Model):
iso_code = models.CharField(primary_key=True, max_length=10, verbose_name=_("ISO code"))
name = models.CharField(max_length=50, verbose_name=_("name"))
description = models.TextField(verbose_name=_("description"), null=True, blank=True)
class Meta:
verbose_name = _("language")
verbose_name_plural = _("languages")
def __str__(self):
return _("%(language_name)s (%(iso_code)s)") % {'language_name': self.name, 'iso_code': self.iso_code}
EDIT: Clarification based on Milo Persic's reply.
The request method in the view for the search functionality (that includes calling AdvancedSearchForm) is GET.
if request.method == 'GET' and 'search_string' in request.GET:
if "advanced" in request.path:
search_form = AdvancedSearchForm(request.GET)
else:
search_form = SearchForm(request.GET)
if search_form.is_valid():
tqs = Translation.objects.all()
dqs = Definition.objects.all()
data = search_form.cleaned_data
...
language_filter = data['filter_by_language']
case_filter = data['case_sensitive']
qs = apply_filters(tqs, dqs, language_filter, case_filter, orig_search_string)
etc.
The search form and results are appended and returned as context to the template:
return render(request, template_name, context)
Within AdvancedSearchForm, variables using ModelChoiceField as well as BooleanField are defined. The ModelChoiceField is the one I'm trying to replace with something more user friendly:
filter_by_language = forms.ModelChoiceField(
queryset=Language.objects.all(), required=False,
label=_("Filter by language")
)
The idea is that the default search result will include all four languages (it won't be more than that) but that any combination of them can be unchecked if so desired. With ModelChoiceField, it seems that only one can be chosen.
I'm still trying to learn if ModelMultipleChoiceField is the correct choice, but this is what I have so far:
filter_by_language = forms.ModelMultipleChoiceField(required=True, queryset=Language.objects.all().filter(name__in=LANG_CHOICES).values(),
widget=forms.CheckboxSelectMultiple)
Nothing next to "filter by language".
Only using the names in LANG_CHOICES instead of the numbers actually result in something but it shows the query set. Narrowing it down using "value_list('name')" shows only the language name but still with the syntax, e.g. "('English',)".
mmcf_qs = Language.objects.all().filter(name__in=LANG_CHOICES).values_list('name')
filter_by_language = forms.ModelMultipleChoiceField(required=True, queryset=mmcf_qs)
I'm trying to figure out how to extract the text but maybe this isn't the right approach.
I would use something like CheckBoxSelectMultiple in the form:
(disclaimer, I do not know if this is supported as written in django 2.2)
filter_by_language = forms.ModelMultipleChoiceField(required=True,
queryset=Language.objects.all().filter(name__in=LANG_CHOICES).values(),
widget=forms.CheckboxSelectMultiple)
And there are plenty of stack posts on how to make the initial values checked, like this one.
Also, if you end up with a long list of check boxes (a likely scenario using this widget), you can style them into columns for better UX, like so:
<style>
.my-language-checkboxes {
column-width: 25%;
column-count: 4;
column-fill: balance;
}
</style>
<div id="my-language-checkboxes">
{{ form.filter_by_language }}
</div>
As for the view, I'll make an assumption that you know how to write a basic view and it's the save method you need. In that case, you'll need something like:
if request.method == 'POST:
if form.is_valid():
form.save_m2m()
This also assumes that you are relating languages back to a parent object via a ManyToManyField, which is a common scenario.
I am trying to create a filter search bar that I can customize. For example, if I type a value into a search bar, then it will query a model and retrieve a list of instances that match the value. For example, here is a view:
class StudentListView(FilterView):
template_name = "leads/student_list.html"
context_object_name = "leads"
filterset_class = StudentFilter
def get_queryset(self):
return Lead.objects.all()
and here is my filters.py:
class
StudentFilter(django_filters.FilterSet):
class Meta:
model = Lead
fields = {
'first_name': ['icontains'],
'email': ['exact'],
}
Until now, I can only create a filter search bar that can provide a list of instances that match first_name or email(which are fields in the Lead model). However, this does now allow me to do more complicated tasks. Lets say I added time to the filter fields, and I would like to not only filter the Lead model with the time value I submitted, but also other Lead instances that have a time value that is near the one I submitted. Basically, I want something like the def form_valid() used in the views where I can query, calculate, and even alter the values submitted.
Moreover, if possible, I would like to create a filter field that is not necessarily an actual field in a model. Then, I would like to use the submitted value to do some calculations as I filter for the list of instances. If you have any questions, please ask me in the comments. Thank you.
You can do just about anything by defining a method on the filterset to map the user's input onto a queryset. Here's one I did earlier. Code much cut down ...
The filter coat_info_contains is defined as a CharFilter, but it is further parsed by the method which splits it into a set of substrings separated by commas. These substrings are then used to generate Q elements (OR logic) to match a model if the substring is contained in any of three model fields coating_1, coating_2 and coating_3
This filter is not implicitly connected to any particular model field. The connection is through the method= specification of the filter to the filterset's method, which can return absolutely any queryset on the model that can be programmed.
Hope I haven't cut out anything vital.
import django_filters as FD
class MemFilter( FD.FilterSet):
class Meta:
model = MyModel
# fields = [fieldname, ... ] # default filters created for these. Not required if all declarative.
# fields = { fieldname: [lookup_expr_1, ...], ...} # for specifying possibly multiple lookup expressions
fields = {
'ft':['gte','lte','exact'], 'mt':['gte','lte','exact'],
...
}
# declarative filters. Lots and lots of
...
coat_info_contains = FD.CharFilter( field_name='coating_1',
label='Coatings contain',
method='filter_coatings_contains'
)
...
def filter_coatings_contains( self, qs, name, value):
values = value.split(',')
qlist = []
for v in values:
qlist.append(
Q(coating_1__icontains = v) |
Q(coating_2__icontains = v) |
Q(coating_3__icontains = v) )
return qs.filter( *qlist )
I have a ModelForm like this:
class MyForm(forms.ModelForm):
many_keys = forms.ModelMultipleChoiceField(OtherModel.objects.all(),
required=False, widget=forms.HiddenInput)
# i set this input as hidden
class Meta:
model = MyModel
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
print(self.data.getlist('many_keys')) # ['[1411, 1412, 1413..']
When I use this form to update the model, the many_keys is already populated with previous value, but unlike the non hidden field, getlist returns the value as a list of 1 string, instead of returning a list of primary keys.
In fact, in the HTML, the hidden field is represented like this, which may be the source of the problem
<input type="hidden" name="many_keys" value="[1411, 1412, 1413, 1414, 1415, 1416, 1417, 1418, 1419, 1420]" id="id_many_keys" />
If I remove widget=forms.HiddenInput, all is fine and I get a proper list of primary keys. I found this behavior to be quite inconsistent, and I am searching a clean way to retrieve this value, if the field is hidden or not.
Hidden only can have one value, if you want multiple values you have to use MultipleHiddenInput
Ref
Ok im new to django
So ive got a situation where i want a formset to have dynamic initial data
So basically here is what im looking for.
each form in the formset to have a different UserID
and a set of groups permission which they can choose from based from the initial data
here is my form
class assignGroupPermissionToUser(forms.ModelForm):
UserID = forms.ModelChoiceField(queryset=None)
Groups = forms.ModelMultipleCHoiceField(queryset=None, widget=FilteredSelectMultiple("Groups")
class Meta:
model=User
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
Userid = kwargs.pop("UserID")
self.fields['UserID'].queryset =User.objects.get(UserID=Userid)
Permissions = kwargs.pop("Groups")
listofPermission = None
for each perm in permission:
listofPermission |= Permissions.objects.filter(GroupID=perm)
self.fields['Groups'].queryset = listofPermission
the data i wanna pass is built into a list like so
it is called
completeList
> completeList =[['13452',{'group1':'Admin','group2':'FrontDesk'}],['3532','group1':'Supervisors','group2':'ReadOnly;}]]
where the first value in each nested loop is the UserID, and the dictionary is the groups they can choose from.
override method in View.py
....
form = assignGroupPermissionToUser()
assignment = formset_factory(form,extra=0)
formset = [ assignment.__init__(completeList[x][0],completeList[x][1]) for x in range(len(completeList))]
then i get an error that str object has no 'is_bound' field line 58 of formset.py
im trytin to get this data to show up on each form and based on the user
it will be all different but everything i try to override it fails for initial form so here i am stuck
note that the Group attribute in the modelform has a widget which is used in the admin section to filter from multiple choices.
settings
Django= 1.8
python 3.5
i erased all this code and just did two loops like so
formset = assignments(initial=[{'UserID': listofUserID[x] } for x in range(len(completeList))])
#then
for form in formset:
form.fields['permissions'].queryset = querysetiwant
In my Django application application I have a formset that is created from a simple (not-model) form, with the extra=1 (to allow javasript to add more forms later on).
class SomeForm(forms.Form):
#some fields with required=False
length = forms.IntegerField(required=False)
# An example of one of the fields with choices i have
A = 0
B = 1
C = 2
D = 3
choices = ((A, 'Aah'), (B, 'Baa'), (C, 'Caa'), (D, 'Daa'))
# This is a required choice field
pickme = forms.ChoiceField(choices=choices)
SomeFormset = formset_factory(SomeForm, can_delete=True, extra=1)
Now, when I create and try to validate it in my view on the POST request:
my_formset = SomeFormset(request.POST, request.FILES)
if(my_formset.is_valid()):
# FAIL
it always fails the above check, if the extra rendered form is submitted empty.
If I check for form.changed_data on the last empty extra form, I get the fields that have choices on them (like the pickme above).
In other words, the formset is not smart enough to figure out that the empty submitted form should be ignored, when some choice fields are required.
Thanks Carl,
you led me to discover the root of my problem.
When creating a form with a choice field, which is required, we must set an initial value, otherwise the form will consider that field changed.
So for a form like this:
class SomeForm(forms.Form):
A = 0
B = 1
C = 2
D = 3
choices = ((A, 'Aah'), (B, 'Baa'), (C, 'Caa'), (D, 'Daa'))
# This is a required choice field
pickme = forms.ChoiceField(choices=choices)
we do this:
pickme = forms.ChoiceField(choices=choices, initial=A)
Then when a formset checks the extra form it will see that pickme had an initial value of A, and it is A now as well, and will consider it unchanged.
This is not the usual behavior of formsets. Formsets pass empty_permitted=True to all "extra" forms, and a form with empty_permitted that hasn't been modified should always pass validation. Note that this works just fine in the Django admin (if you use inlines).
You must be doing something else in your code that is breaking this behavior somewhere. Post the full code of the relevant form?