django form field displayed out of order - django

I have this form and model for a group:
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ('leader', 'description', 'size', 'max_size', 'motto')
widgets = {
'size': forms.CheckboxInput(attrs={'id': 'size'}),
'max_size': forms.TextInput(attrs={'type': 'hidden', 'id': 'maxSize'}),
}
The creator of the group has an option to check yes for size and on doing so, I used javascript to change the type of max_size to show.
In my create_group.html template:
<script>
let size = document.getElementById('size')
let maxSize = document.getElementById('maxSize')
let checked = false
size.onclick = () => {
checked = !checked
if (checked === true) {
maxSize.type = 'show'
} else {
maxSize.type = 'hidden'
}
}
</script>
Now, this works fine, the only problem is that the fields are displayed out of order.
When the page loads, max_size is false and its field is not displayed. Which is good. However, when the user checks that group has a size, and, subsequently, the max_size has a display of show, the field shows up after the motto field and not in its correct order according to fields = ('leader', 'description', 'size', 'max_size', 'motto').
Furthermore, the max_size field is included inside the motto element itself and not as its own field:
vs. the other fields which are all in their own <p></p>.

I'm guessing that {{form.as_p}} etc. render all the visible fields first, then the hidden ones.
You can explicitly render the fields in the order you want in your template. Rather than hardcoding the order in your template, maybe this (I've never tried this):
FIELD_ORDER = ( ('leader', 'description', 'size', 'max_size', 'motto')
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = FIELD_ORDER
Pass to your template a list of fields explicitly in the order you want:
fields_in_order = [ form[x] for x in FIELD_ORDER ]
In the template
{% for field in fields_in_order %}
{{field}}
{% endfor %}
Or, you can make the hiding of this field something done by JS

If anyone else comes across this issue, it's also possible to just use js and css. Right now, I'm using javascript to see if size is checked and if it is then maxSize.style.display = 'block' vs maxSize.style.display = 'none' if size isn't checked.
Then I had the issue of django's form label still being visible. To fix that I saw an answer on dev.to which you can see for yourself.
My issue now is that I don't know how to add a label that is only visible when the form field is visible.

Related

Add ID field to ModelForm

I need to add ID field to my form, and I'm getting so mad
Currently I have :
class ProductVideoForm(forms.ModelForm):
class Meta:
model = ProductVideo
translatable_fields = get_translatable_fields(ProductVideoTranslation)
fields = [
"product",
"id", #added!!!!
"type",
"placeholder",
] + translatable_fields
widgets = {
"placeholder": ImageInput(),
}
trans_fields_per_lang = get_trans_fields_per_lang(translatable_fields)
I added ID to fields, and the template is:
{{ video_formset.management_form }}
Why Is ID not displayed ??
actually, I just need display it, not updated.
Yea ok, but by default django will not display editable=False fields in forms.
What you are looking for is disabled param.
https://docs.djangoproject.com/en/4.1/ref/forms/fields/#disabled

if statement for a specific field in django template

I'm rendering a form in a django template using this model:
class Group(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
members = models.IntegerField(default=0)
has_max = models.BooleanField(default=False)
max_size = models.IntegerField(default=10)
DAYS = [
('Sundays', 'Sundays'),
('Mondays', 'Mondays'),
('Tuesdays', 'Tuesdays'),
('Wednesdays', 'Wednesdays'),
('Thursdays', 'Thursdays'),
('Fridays', 'Fridays'),
('Saturdays', 'Saturdays'),
]
meeting_day = MultiSelectField(
verbose_name = 'Meeting day(s)',
choices=DAYS,
max_choices=6,
max_length=100
)
start_time = TimeField(widget=TimePickerInput)
end_time = TimeField(widget=TimePickerInput)
def __str__(self):
return self.name
And onto this template:
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button>POST</button>
</form>
I have a couple of issues going forward:
My first issue is that I only want to have max_size be displayed in my template form if the user clicks has_max. Like a conditional, where once the user checks the box, changing has_max to True then they can enter in the max size of the group.
My second issue is that the start_time and end_time to render in my template or on the admin side. I'm not sure how TimePickerInput works, but right now, there are no fields in my template form or admin form that have those time fields.
Also, last thing, if I have both names the exact same (i.e., ('Sundays', 'Sundays'), is it necessary to have both? Or can django figure it out.
The first Problem as I understand it You want to have this check box and when it will true the Field of max_size will appear this problem needs to solve with javascript
Why ?
Because if You use the Django template you will need to refresh the page when the user changes something and this will overload the server So you need to create an event with js when the use click on the checkbox it will disappear
Also, last thing, if I have both names the exact same (i.e., ('Sundays', 'Sundays'), is it necessary to have both? Or can Django figure it out?
This part ... You need to know that the first value is the value will be stored in the database and the second value is the human-readable value this what will be displayed for the user in the form Read This
You must know you can't put widgets into your model if you want to add it add it to your from in Your admin or form files ... You will override the AdminModel in your admin file and change the formvalue Read This

Multiple choice form within an advanced search form

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.

How to change form field attribute in the view?

I have a model form containing four fields, by default only one field is active waiting for the user input.
class SaleOrderInvoiceForm(forms.ModelForm):
class Meta:
model = SaleOrderInvoice
fields = (
'supplier',
'sale_order',
'invoice_number',
'invoice_date',
)
widgets={
'supplier':forms.Select(attrs={'class':'form-control'}),
'sale_order':forms.Select(attrs={'class':'form-control', 'disabled':'disabled'}),
'invoice_number':forms.TextInput(attrs={'class':'form-control', 'disabled':'disabled'}),
'invoice_date':forms.DateInput(attrs={'class':'form-control','type':'date','disabled':'disabled'}),
}
Based on the user selection of the first input, the rest of the form is loaded via AJAX. Now in the view, after necessary filtering, I want to swtich the disabled form fields to enabled and the enabled to disabled. I have tried the following but with no luck.
form = SaleOrderInvoiceForm()
form.fields['supplier'].disabled = True
form.fields['sale_order'].disabled = False
form.fields['invoice_number'].disabled = False
form.fields['invoice_date'].disabled = False
It neither raises any exceptions nor it works as desired.
can anyone suggest a way forward with this?
I found the solution, it appears the disabled attribute cannot be reset back to False, it can however pop out.
form.fields['supplier'].disabled = 'disabled'
form.fields['sale_order'].widget.attrs.pop('disabled')
form.fields['invoice_number'].widget.attrs.pop('disabled')
form.fields['invoice_date'].widget.attrs.pop('disabled')

Recent values in CharField

I have some model
class Mod(Model):
f = CharField(max_length=10)
And some form:
class ModForm(ModelForm):
class Meta:
model = Mod
fields = ('f',)
When I display form to user I want to suggest some dropdown with Mod.objects.all() values of 'f'. How to do this in simplest way? I tried specify widgets = {'f': Select}, but it is not editable. I also can use selectize.js to make it selectable with preloading ajax values, but seems like this is too long way.
models.py
class Mod(models.Model):
CHOICE_1 = 'CHOICE 1'
CHOICE_2 = 'CHOICE 2'
FIELD_CHOICES = (
(CHOICE_1, 'The real charfield value'),
(CHOICE_2, 'another charfield value')
)
f = models.CharField(
max_length=10,
choices=FIELD_CHOICES,
default=CHOICE_1
)
forms.py
class ModForm(ModelForm):
class Meta:
model = Mod
fields = ('f',)
end of views.py
return render(request, 'template.html', context={'mod_form': mod_form})
template.html
{{mod_form.as_p}}
This is extracted from working code. Hopefully the obfuscating my specific app out did not make it unclear.
Autocomplete is a simple and nice solution. Thanks to Bilou06.
So, first of all we need to pass list of values, we can do it in view:
args['f_vals'] = Mod.objects.all().values_list("f", flat=True).distinct()[:10]
here we get all flat list of values and perform distinct to filter only uniq values. Then we limit values to 10. Additional benefit here is that we can perform more complex querysets with additional filtering and so on. For example if you have some field you can order it to be real "recent". (You can edit this answer to provide some example).
Next we pass our args into template, and there create autocompleate:
<script>
$(document).ready(function(){
$("#id_f").autocomplete({
source: [{% for v in f_vals %}
"{{v}}",
{% endfor %}
],
minLength: 0,
}).on("focus", function () {
$(this).autocomplete("search", '');
});
}
</script>
On focus event here displays list of recent values immidiately on focus. Also values will be filtered when you typing.
Note that you should include jquery-ui:
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/jquery-ui.min.js' %}"></script>