Django custom widget: how to access field error? - django

I try to have more control over form rendrering and template.
Initially, I used django-crispy but event if Layout give huge range of customization I thought it became confused...
So I truned out to widget customization following django documentation and this site https://www.webforefront.com/.
I first face issue with renderding field label. I fix that by passing label argument in attrs of CustomWidget but not sure it is a good practice.
But my main issue is rendering erros...
How can I manage this?
template for input CustumoInput.html
<div class="row mb-0">
<div class="input-group input-group-sm col-6 rounded">
<label>{{ widget.label }}</label>
</div>
<div class="input-group input-group-sm mb-1 col-2">
<input
type="{{ widget.type }}"
name="{{ widget.name }}"
id="id_{{ widget.name }}"
class = "textinput textInput form-control"
{% if widget.value != None %}
value="{{ widget.value }}"
{% endif %}
{% include "django/forms/widgets/attrs.html" %}
/>
{% for error in widget.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
</div>
widgets.py
from django import forms
class custom_input(forms.widgets.Input):
template_name = 'ecrf/CustomInput.html'
input_type = 'text'
def __init__(self, attrs={}):
super(custom_input, self).__init__(attrs)
def get_context(self, name, value, attrs):
context = super(custom_input, self).get_context(name, value, attrs)
context['widget']['label'] = self.attrs['label']
context['widget']['attrs']['placeholder'] = self.attrs['placeholder']
context['widget']['attrs']['autocomplete'] = self.attrs['autocomplete']
context['widget']['attrs']['data-mask'] = self.attrs['data-mask']
return context
forms.py
self.fields['inc_tai'] = forms.IntegerField(label = 'Taille (mesurée ou déclarée, en cm)',widget=custom_input(attrs={'label':'Taille (mesurée ou déclarée, en cm)','autocomplete': 'off','placeholder': '000','data-mask':'000'}),required=False,disabled=True)
template.htlm (usage)
{{ form.inc_tai }}

Related

How can two HTML template elements be updated using (HTMX)?

I have a form in which there are three fields, basically these fields contain drop-down lists - selections. These select lists are based on a data model that has fields associated with ForeynKey .
After completing this form.
I update information, code and do calculations in code.
Further after the calculations.
I am updating two elements on the template - a table and a graph.
I have these two elements in separate parts of the template.
Like two different pieces of HTML .
With (HTMX) I can only update one element on the table - this element is a chunk of the HTML template - which is updated by rendering that chunk of that template. How can I update another piece of the template?
How can two HTML template elements be updated using (HTMX) ?
I would be very grateful for any help.
--
<div class="row">
<div class="col-6">
<form method="POST" class="post-form">
{% csrf_token %} {{form_1.as_p}}
<button type="submit" class="save btn btn-light">Form</button>
</form>
</div>
</div>
<div class="row">
<div class="col">
{{ div_1|safe }}
{{ script_1|safe }}
</div>
</div>
<div class="row">
<div class="col">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
</div>
--
class Form_1(forms.ModelForm):
class Meta:
model = Model_1
fields = "__all__"
--
class Model_1(models.Model):
name_1 = models.CharField(max_length=150, verbose_name="Name_1")
name_2 = models.CharField(max_length=150, verbose_name="Name_2")
def __str__(self):
return self.name_1, self.name_2
--
def form_1(request):
context = {}
form = Form_1(request.POST or None)
if form.is_valid():
form.save()
script_1, div_1 = components(data_table)
context['script_1'] = script_1
context['div_1'] = div_1
script_2, div_2 = components(fig)
context['script_2'] = script_2
context['div_2'] = div_2
return render(request, "data_table", "fig", context)
context['form_1'] = form
return render(request, "form_1.html", context)
added
def index_htmx(request):
context = {}
///code///
if request.htmx:
print("HTMX")
return render(request, 'index_htmx_added.html', context)
return render(request, "index_htmx.html", context)
index_htmx_added.html
<div id="table"
hx-swap="beforeend"
hx-swap-oob="true"
class="col-6">
{{ div|safe }}
{{ script|safe }}
</div>
<div id="figure"
hx-swap="beforeend"
hx-swap-oob="true"
class="col-6">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
index_htmx.html
<div class="row">
<div class="col-4">
<select
id="select-name"
class="custom-select"
name="select"
autocomplete="off"
hx-get="{% url 'index_htmx' %}"
hx-target="#figure, #table"">
{% for select in selector %}
<option value="{{ select }}">{{ select }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div id="table" class="col-6">
{{ div|safe }}
{{ script|safe }}
</div>
<div id="figure" class="col-6">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
</div>
HTMX call this feature Out of Band Swap. In the response HTML you can have multiple HTML fragments with different targets and HTMX will swap the corresponding element on the page. Given your page looks like this:
<div id="tabular_data">...Tabular data...<div>
<div id="graph">...Graph...</div>
The response HTML should contain this (note the added hx-swap-oob="true"):
<div id="tabular_data" hx-swap-oob="true">...New tabular data...<div>
<div id="graph" hx-swap-oob="true">...New graph...</div>
HTMX will find id="tabular_data" element and swaps its content with the new content then do the same with id="graph" element.
These oob elements must be in the top level of the response, and not a children of an element.
At the backend you need to create a new template that contains all the element you want to swap together or just join the rendered HTML fragments when you return the response.

Django poll - change from only one seleted option (radio) in multiple selection (checkbox) and register all togheter

I made a poll app in django. It work ok but what i want to change is the selection an register vote option. Now i can select and register one option ('participanti') (radio btn select) and i want to be able to let the user to select more than one "participanti" (1 or 1 to maximum 3 ) in the same instance and after submit to register all the option/participanti selected (1 or 2 or all maximum 3 that user selected) . I don't know how to approach this kind of mechanism. Please help me with a solution. Thank you very much.
Below mai settings
models
class Campanie(models.Model):
.....
class Participanti(models.Model):
campanievot = models.ForeignKey(Campanie, on_delete=models.CASCADE)
nume_participant = models.CharField(max_length=200, verbose_name='nume')
dep_participant = models.CharField(max_length=200, verbose_name='departament')
votes = models.IntegerField(default=0)
def __str__(self):
return self.nume_participant
class Meta:
verbose_name = 'Participanti'
verbose_name_plural = 'Participanti'
views.py
def votare_campanie(request):
campanie = get_object_or_404(Campanie)
try:
participant_votat = campanie.participanti_set.get(pk=request.POST['participanti'])
except (KeyError, Participanti.DoesNotExist):
return render(request, 'campanievot/campanie.html', {
'campanie' : campanie,
'error_message' : "Trebuie sa votezi cel putin un coleg",
})
else:
participant_votat.votes += 1
participant_votat.save()
messages.success(request, "Multumim pentru feedback!")
return redirect('campanievot:index')
template.html
<form action=" {% url 'campanievot:votare_campanie'%} " method="post" >
{% csrf_token %}
{% for p in campanie.participanti_set.all %}
{% if p.dep_participant == request.user.departament %}
{% else %}
<label for="p{{ forloop.counter }}" class="form-check custom-icon mt-2 mb-4" role="button">
<input type="radio" name="participanti" class="form-check-input" id="p{{ forloop.counter }}"
value="{{ p.id }}">
<span class="form-check-label">
<span class="content">
<span for="p{{ forloop.counter }}" class="heading mb-1 d-block lh-1-25">{{p.nume_participant }} {{ p.dep_participant }}</span>
<!-- <span class="d-block text-small text-muted">04.05.2021 - 12:00 </span> -->
</span>
</span>
</label>
{% endif %}
{% endfor %}
{{form.media}}
<button class="m-auto btn btn-icon btn-icon-start btn-primary mb-1" type="submit">
<i data-acorn-icon="send"></i>
<span>Trimite</span>
</button>
</form>
Thank you!

How to format Django form with UIKit styling

I am struggling to see a way to style a django form in the style of a uikit horizontal form. UIKit has the styling I want and Django has the validation and templating I want.A way to implement a datepicker too would be useful.
I have tried the plain django form template with .as_p and .as_table. I have also tried to use Meta and widgets but couldn't get that to work. I can't see how I can add the needed uikit tags to each element and add the uk-form-controls div.
template.html
<form class="uk-form-horizontal uk-margin-large uk-align-center">
<div class="uk-margin">
<label class="uk-form-label" for="form-horizontal-text">Job Title</label>
<div class="uk-form-controls">
<input class="uk-input uk-form-width-large" id="form-horizontal-text" type="text" placeholder="Some text...">
</div>
</div>
forms.py
class job_form(forms.Form):
job_title = forms.CharField(label='Job Title', max_length=50)
hiring_manager = forms.CharField(label='Hiring Manager', max_length=50)
job_description = forms.CharField(label='Job Description', max_length=50)
salary = forms.IntegerField()
closing_date = forms.DateField()
I am expecting to be able to have the uikit form styling with the templating and validation of django forms but am yet to get it to work.
Django has a variety of ways to override the form behavior and layout. I see you are using forms.Form instance. Simply add your classes to the form class like:
class NiceForm(forms.Form):
solid_field=forms.CharField(widget=forms.TextInput(attrs={'class': 'uk-form-input'}))
Although it is simple, but sluggish, when you want introduce breaking changes to the layout, I would override the template (or render method if you like) to bundle a reusable widget. Simple example to extract the form to external reusable template as you can render them manually as HTML like documentation.
Leave the form clean and use templatetag to override classes:
# _form_icludes.html
{% for field in form.visible_fields %}
<fieldset class="uk-fieldset">
<div class="uk-margin">
<label class="uk-form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
<div class="uk-form-controls">
{% if field.field.widget.input_type == 'select' %}
{{ field | add_css_cls:'uk-select'}}
{% elif field.field.widget.input_type == 'option'%}
{{ field | add_css_cls:'uk-option'}}
{% elif field.field.widget.input_type == 'checkbox'%}
{{ field | add_css_cls:'uk-checkbox'}}
{% elif field.field.widget.input_type == 'select'%}
{{ field | add_css_cls:'uk-select'}}
{% elif field.field.widget.input_type == 'file'%}
{{ field }}
{% else %}
{{ field | add_css_cls:'uk-input'}}
{% endif %}
{% if field.help_text %}
<span class="uk-text-small uk-text-left uk-text-italic">
{{ field.help_text }}
</span>
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<p class="uk-flex uk-flex-middle uk-text-danger ">
<span data-uk-icon="icon:warning" class="uk-text-danger uk-margin-small-right"></span>
{{ error | escape }}
<p/>
</div>
{% endfor %}
{% endif %}
</div>
</fieldset>
{% endfor %}
# add_css_cls
from django import template
register = template.Library()
#register.filter
def add_css_cls(value, arg):
css_classes = value.field.widget.attrs.get('class', '').split(' ')
if css_classes and arg not in css_classes:
css_classes = '%s %s' % (css_classes, arg)
return value.as_widget(attrs={'class': css_classes})
There are many different ways.

Django Crispy Forms - Checkbox not being displayed

I'm trying to add a boolean field on my form and render it with crispy forms tags. And everything is showed except for the checkbox.
My project works with django 2.1, python 3.6 and Bootstrap-4. My version of Django Crispy Forms is: 1.7.2
The field is investment.
Model field not working:
investment = models.BooleanField(default=False, help_text='Want to accept Investments?')
My form:
class CreateProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ('name', 'short_description', 'category', 'investment')
widgets = {
'name': forms.TextInput(attrs={'placeholder': 'enter the project name here...'}),
'short_description': forms.Textarea(attrs={'rows': '2', 'maxlength': '135', 'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'}),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateProjectForm, self).__init__(*args, **kwargs)
# there's a `fields` property now
self.fields['investment'].required = True
self.fields['investment'].widget = forms.CheckboxInput()
self.fields['name'].widget = forms.TextInput(
attrs={'placeholder': 'enter the project name here...'})
self.fields['short_description'].widget = forms.Textarea(
attrs={'rows': '2',
'maxlength': '135',
'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'})
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
My View:
class ProjectCreateView(SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_create_form.html'
model = Project
form_class = CreateProjectForm
success_message = 'Project created! Now try to add all the details and then publish it!'
def get_success_url(self):
return reverse_lazy('project-edit-general', args=(self.object.id,))
# Set field as current user
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.start_date = timezone.now()
form.instance.history_change_reason = 'Project Created'
return super(ProjectCreateView, self).form_valid(form)
And for the template I tried two methods, but none of them worked:
My template 01:
This is the method I want to use at the end. Displaying each fiend individually so I can make the layout directly on the template.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="row">
<div class="col-md-5 col-sm-5">
<h6>Name
<span class="icon-danger">*</span>
</h6>
{{ form.name|as_crispy_field }}
<h6>Categories
<span class="icon-danger">*</span>
</h6>
{{ form.category|as_crispy_field }}
</div>
<div class="col-md-7 col-sm-7">
<h6>Short Description
<span class="icon-danger">*</span>
</h6>
{{ form.short_description|as_crispy_field }}
<h5>
<small>
<span id="textarea-limited-message" class="pull-right">135 characters left</span>
</small>
</h5>
<h6>Investment
<span class="icon-danger">*</span>
</h6>
{{ form.investment|as_crispy_field }}
</div>
</div>
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
My template 02:
This one is using directly the {{ form|crispy }} to show all the elements automatically.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
{{ form|crispy }}
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
I checked the rendered HTML and I found where the problem seems to be, but I don't know how to solve it:
Crispy forms create an element adding the corresponding divs and other necessary elements, like for example on the name input:
<div id="div_id_name" class="form-group">
<label for="id_name" class="col-form-label requiredField">Name
<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="name" placeholder="enter the project name here..." class="textinput textInput form-control" required="" id="id_name">
<small id="hint_id_name" class="form-text text-muted">Add a title to your project.</small>
</div>
</div>
But for the checkbox creates the following structure:
<div class="form-group">
<div id="div_id_investment" class="form-check">
<label for="id_investment" class="form-check-label requiredField">
<input type="checkbox" name="investment" class="checkboxinput form-check-input" required="" id="id_investment">
Investment
<span class="asteriskField">*</span>
</label>
<small id="hint_id_investment" class="form-text text-muted">Want to accept Investments?</small>
</div>
</div>
As you can see, the div with the id is inside another div. Well, so if I delete the class of this extra div (form-group) and change the class of the div with id: form-check for form-group, the checkbox appears and is fully functional.
So my idea is try to change the template that crispy forms created for the checkbox, But I don't know how to do that. Also, if there is another better option, I'm open to them.
Try to change self.fields['investment'].required = False, 'cause it seems that because the field is not specified as optional Django ignores it.
Their is an open issue in django-crispy-forms github see : https://github.com/django-crispy-forms/django-crispy-forms/issues/787
According to this the issue is the way bootsrap handle this form element in the last version.
Here is a js hack to make it work :
$(document).ready(function() {
$( ":checkbox" ).each(function( index ) {
$(this).prependTo($("#div_" + $(this).attr('id')))
});
});
I could make a half hack for this by the element I needed by using the following JS:
$(window).on('load',function(){
$('#div_id_investment').addClass('form-group').removeClass('form-check');
});
But this just let the user see and interact with the checkbox, it does not solve that if the field is required and you don't click it, the error message it doesn't appear.
If anyone have an improved JS to make this to all checkboxes and solve the problem with the error message, I'll appreciate it.
note: I'm not marking this as a valid answer for the question because the bug still there and this 'hack' doesn't solve all the problems.

Use two forms at the same time

I want to submit two forms in my template. The reason for that is, that I have two separate models, each with a model form, which I have to submit at the same time to create the desired result:
I have these two forms:
class BootstrapModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BootstrapModelForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
class DeckForm(ModelForm):
class Meta:
model = Deck
exclude = ('dust',)
class GuideForm(BootstrapModelForm):
class Meta:
model = Guide
exclude = ('author', 'upvotes', 'downvotes', 'release_date', 'modified', 'deck')
def __init__(self, *args, **kwargs):
super(GuideForm, self).__init__(*args, **kwargs)
self.fields['title'].label = 'Title of your AWESOME Guide!'
self.fields['public'].label = 'Check, when you want to make your guide public'
self.fields['public'].widget.attrs.update({
'class': 'checkbox'
})
self.fields['introduction'].label = 'Introduction (not required, but recommended)'
A deck is a part of the Guide that gets created. Since the user should be able to create his own deck while writing his guide, I need two forms.
In my views I handled it like this:
def guide_create(request):
if request.method == "POST":
deck_form = DeckForm(request.POST)
guide_form = GuideForm(request.POST)
if guide_form.is_valid() and deck_form.is_valid():
new_deck = deck_form.save(commit=False)
new_deck.dust = 0
new_deck.save()
new_guide = deck_form.save(commit=False)
new_guide.author = Account.objects.get(id=request.user.id)
new_guide.deck = Deck.objects.get(id=new_deck.id)
new_guide.save()
else:
print(guide_form.errors)
print(deck_form.errors)
else:
deck_form = DeckForm(prefix = 'DeckForm')
guide_form = GuideForm(prefix = 'GuideForm')
return render(request, 'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
I use commit=False so I still can insert the deck in my guide. Now my problem comes with creating the template. When I submit the forms with my current set up, I receive no errors any more but the guide and the deck aren't saved either!
<div style="width: 60%; margin: 0 auto;">
<form class="form-horizontal" method="POST" action="{% url 'GuideCreate' %}"> {% csrf_token %}
<fieldset>
{% for field in DeckForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<fieldset>
{% for field in GuideForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<div class="form-actions" style="margin-top: 4px;">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
</div>
Edit I don't receive an error message any more, the deck gets created as well but the guide that belongs to it not.
Just Change
return render_to_response('hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
to
return render(request,'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
You're saying the guide doesn't get created. I see this line new_guide = deck_form.save(commit=False)..... shouldn't it be new_guide = guide_form.save(commit=False) ?
Not 100% but this might be a mistake. Hopefully, it's not just because you refactored the code for putting it here.
I didnot check the whole thing, but this place is wrong, it should be:
if request.method == "POST":
deck = DeckForm(request.POST)
if deck.is_valid():
# ....
you are not packing the POST data into your forms..
the same goes for the second form as well
Try this, you might forgot to pass the RequestContext:
from django.template import RequestContext
return render_to_response('hsguides/guide_create.html',
{'DeckForm': DeckForm, 'GuideForm': GuideForm},
RequestContext(request))
It passes the csrf token to the template.