Django Form Field Custom attribute assignment and use in template - python

I am trying to generate a form dynamically and want to assign indentation of form fields. I am trying to assign an custom attribute offset to forms.CharField in subclass. I plan to use this logic to create a form dynamically from an xml file, where the fields would be indented based on the depth of the node.
I am unable to retrieve the value of offset while rendering the template and hence unable to assign the margin-left style parameter. The final html output is also shown.
Can someone please help. I have searched some other answers on this site where it appears that arbitrary attributes can be assigned and retrieved in template. e.g.as in thread here where an arbitrary label_class attribute is assigned
My forms.py file :
class MyCharField(forms.CharField):
def __init__(self, *args, **kwargs):
self.offset = kwargs.pop('offset', 0)
super(MyCharField, self).__init__(*args, **kwargs)
class MyDynamicForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyDynamicForm, self).__init__(*args, **kwargs)
self.fields["Field_A"] = MyCharField(label="Input A", offset="5")
self.fields["Offset_Field_B"] = MyCharField(label="Input B", offset="50")
My Views.py looks like this:
class MyDynamicView(View):
template_name = 'demo/myform.html'
form_class = MyDynamicForm
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
My template file using bootstrap looks like this:
{% extends 'demo/base.html' %}
{% load bootstrap3 %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group bootstrap3-required">
<label class="col-md-3 control-label " style = "margin-left: {{field.offset}}px" for="{{ field.name }}">{{ field.label}}</label>
<div class="col-md-9">
<input class="form-control" id="id_{{field.name}}" name="{{ field.name }}" placeholder="{{field.label}}" style="margin-left:{{field.offset}}px" title="" required="" type="text"/>
</div>
</div>
{% endfor %}
{% buttons submit='OK' reset='Cancel' layout='horizontal' %}{% endbuttons %}
</form>
{% endblock %}
The html output is:
<form role="form" method="post">
<input type='hidden' name='csrfmiddlewaretoken' value='lTy0rc2r9KNiNNPosUoriUlNzYBpgoVpael1MYLOczFECO7H7LXdES6EGBhUoXx0' />
<div class="form-group bootstrap3-required">
<label class="col-md-3 control-label " style = "margin-left: px" for="Field_A">Input A</label>
<div class="col-md-9">
<input class="form-control" id="id_Field_A" name="Field_A" placeholder="Input A" style="margin-left:px" title="" required="" type="text"/>
</div>
</div>
<div class="form-group bootstrap3-required">
<label class="col-md-3 control-label " style = "margin-left: px" for="Offset_Field_B">Input B</label>
<div class="col-md-9">
<input class="form-control" id="id_Offset_Field_B" name="Offset_Field_B" placeholder="Input B" style="margin-left:px" title="" required="" type="text"/>
</div>
</div>
<div class="form-group"><label class="col-md-3 control-label"> </label><div class="col-md-9"><button class="btn btn-default" type="submit">OK</button> <button class="btn btn-default" type="reset">Cancel</button></div></div>
</form>

It not necessary to instantiate from CharField for that. Probably such initialization of the field in form will be enough for you:
field_a = forms.CharField('Input_A',
widget=forms.TextInput(attrs={'placeholder': 'Input_A', 'style': 'margin-left: 50px'}))

Related

Why aren't changes saved when editing a Django product?

Created a website with products. I need to make a window for editing them on the site in order to change the manufacturer and other characteristics. This must be done in a pop-up window. I have data displayed, I change it, but nothing changes when I save it. How can this problem be solved.
My vievs:
def parts(request):
added = ''
error = ''
PartAllView = Part.objects.order_by('-id')
if request.method == 'POST' and 'parts_add' in request.POST:
form = PartForm(request.POST, request.FILES)
if form.is_valid():
form.save()
added = 'Добавлено'
else:
error = 'Данная запчасть уже добавлена'
if request.method == 'POST' and 'parts_edit' in request.POST:
PartPost = int(request.POST['parts_edit'])
PartID = Part.objects.get(id=PartPost)
if PartID:
PartID.save()
added = 'Запчасть успешно отредактирована'
else:
error = 'Ошибка редактирования'
form = PartForm()
data = {
'added': added,
'error': error,
'form': form,
'PartAllView': PartAllView,
}
return render(request, 'kross/parts.html', data)
My HTML:
{% if PartAllView %}
{% for el in PartAllView %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal fade" id="partEdit{{ el.id }}">
<div class="modal-dialog modal-dialog-centered text-center" role="document">
<div class="modal-content modal-content-demo">
<div class="modal-header">
<h6 class="modal-title">Добавление запчасти</h6><button aria-label="Close" class="btn-close"
data-bs-dismiss="modal"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<div class="row row-sm">
<div class="col-lg-6">
<div class="form-group">
<input type="text" class="form-control" name="brand" value="{{ el.brand }}">
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<input type="text" class="form-control" value="{{ el.number }}">
</div>
</div>
<div class="col-lg-12">
<div class="form-group">
<input type="text" class="form-control" value="{{ el.name }}"><br>
<input type="textarea" class="form-control" rows="2" value="{{ el.description }}">
</div>
</div>
</div>
{{ el.analog }}
...
You can use updateView to edit an existing data in your website by simply:
from django.views.generic.edit import UpdateView
From MyApp models import #Model
class editview(UpdateView):
model = #Your Model You want to edit
fields = [#Add the fields you want to edit]
template_name = 'edit.html'
success_url = ('Home')
In your edit Template add:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update">
I hope it help.

How to create dropdown box with forms.ModelForm in Django?

I'm trying to add a dropdown box of choices in my form. What I tried:
from django import forms
from .models import Car
class CarForm(forms.ModelForm):
owner_name = forms.CharField(max_length=100, label='Owner Name', required=True)
car_type = forms.ChoiceField(choices=Car.CarTypes.choices, label='Car Type', required=True)
class Meta:
model = Car
fields = ('owner_name', 'car_type')
It adds the car_type field to the form but it's not a dropdown for some reason (it's a regular empty box you could fill). The CarTypes looks like:
class CarTypes(models.TextChoices):
X = 'some_x'
Y = 'somy_y'
# ...
What could be the reason?
In my Car class I have: car_type = models.CharField(max_length=24, choices=CarTypes.choices, default=CarTypes.X). Could be it? I think CharField makes it empty box. But changing it to ChoiceField ends with: module 'django.db.models' has no attribute 'ChoiceField'. How to solve it?
I tried this approach but it uses a tuple instead of a class. From the example there I see they use tuple of two-value-tuples like (('a','A'),('b','B')) and I'm using class. Could it be the reason?
In my html I have:
<form method="post" class="form-group">
{% csrf_token %}
{% for field in form %}
<div class="form-group col-md-12 mb-3">
<label for="{{ field.label }}">{{ field.label }}</label>
<input type="text" class="form-control" id="{{ field.label }}" name="{{ field.name }}">
</div>
{% endfor %}
<hr class="mb-4">
<button type="submit" class="btn btn-secondary btn-lg btn-block">Submit New Car</button>
</form>
And the Car looks like:
class Car(models.Model):
class CarTypes(models.TextChoices):
X = 'some_x'
Y = 'somy_y'
# ...
owner_name = models.CharField(max_length=100,unique=True)
car_type = models.CharField(max_length=24, choices=CarTypes.choices, default=CarTypes.X)
def __str__(self):
return self.owner_name
You don't need to override your model fields. See Field types - Django Docs
If the model field has choices set, then the form field’s widget will
be set to Select, with choices coming from the model field’s choices.
The following code should do what you want:
forms.py
from django import forms
from .models import Car
class CarForm(forms.ModelForm):
class Meta:
model = Car
fields = ['owner_name', 'car_type']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['owner_name'].widget.attrs.update({"class": "form-control"})
# or iterate over field to add class for each field
for field in self.fields:
self.fields[field].widget.attrs.update({'class':"form-control"})
You can add any html attribute in __init__ method.
html:
<form method="post" class="form-group">
{% csrf_token %}
<div class="form-group col-md-12 mb-3">
<label for="{{ form.owner_name.label }}">{{ form.owner_name.label }}</label>
{{ form.owner_name }}
</div>
<div class="form-group col-md-12 mb-3">
<label for="{{ form.car_type.label }}">{{ form.car_type.label }}</label>
{{ form.car_type }}
</div>
<hr class="mb-4">
<button type="submit" class="btn btn-secondary btn-lg btn-block">Submit New Car</button>
</form>
Or You can use {{ form.as_p }} - Django Docs will render them wrapped in <p> tags
UPDATE
Render form manually:
<form method="post" class="form-group">
{% csrf_token %}
<div class="form-group col-md-12 mb-3">
<label for="{{ form.owner_name.label }}">{{ form.owner_name.label }}</label>
<input type="text" class="form-control" id="{{ form.owner_name.auto_id }}" name="{{ form.owner_name.name }}">
</div>
<div class="form-group col-md-12 mb-3">
<label for="{{ form.car_type.label }}">{{ form.car_type.label }}</label>
<select id="{{ form.car_type.auto_id }}" name="{{ form.car_type.name }}">
{% for value, label in form.fields.car_type.choices %}
<option value="{{ value }}"{% if form.car_type.value == value %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<hr class="mb-4">
<button type="submit" class="btn btn-secondary btn-lg btn-block">Submit New Car</button>
</form>
Try to use this method of Choices :-
models.py
choices = [
('choice1', 'choice1'),
('choice2',"choice2"),
('choice3',"choice3")
]
class Car(models.Model):
owner_name = models.CharField(max_length=100,unique=True)
car_type = models.CharField(max_length=24, choices=choices, default='choice1')
def __str__(self):
return self.owner_name
And then don't forget to make migrations
Refer this ---> https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.Field.choices
For Django 3.1, you can achieve this with widgets. Here's how you can get it done:
from django.forms import ModelForm, Select, TextInput
from .models import Car
class CarForm(ModelForm):
class Meta:
model = Car
fields = ('owner_name', 'car_type')
widgets = {
'owner_name': TextInput(),
'car_type': Select(),
}
And Model should look like this:
class Car(models.Model):
choices = [('X':'some_x'), ('Y': 'some_y']
owner_name = models.CharField(max_length=100,unique=True)
car_type = models.CharField(max_length=24, choices=choices, default='some_y')
def __str__(self):
return self.owner_name```

Rendering a FormSet in Django

I want to create a FormSet which allows users to add forms as needed. However, when I render the page, I am receiving an error:
ValueError: The view website.views.presales didn't return an HttpResponse object.
It returned None instead.
I would like to always render the form blank. Please let me know if any other information is needed, thanks!
Note: cwObj.get_opportunities() is an API call to create an object from a JSON response to populate the select_opportunity dropdown. Lastly, I am using AJAX to dynamically calculate the value of the span Total using data-total-url="{% url 'presales_total' %}".
forms.py
class PresalesForm(forms.Form):
class Meta:
model = Presales
fields = ('selected_opportunity', 'task_description', 'hours', 'selected_engineer_level', 'total_cost')
views.py
def presales(request):
my_opportunities = cwObj.get_opportunities()
PresalesFormSet = formset_factory(PresalesForm, extra=1)
if request.method == 'POST':
presales_formset = PresalesFormSet(request.POST)
if presales_formset.is_valid():
for presales_form in presales_formset:
selected_opportunity = request.POST.get('selected_opportunity')
task_description = request.POST.get('task_description')
hours = request.POST.get('hours')
select_engineer_level = request.POST.get('select_engineer_level')
else:
presales_formset = PresalesFormSet(initial="None")
context = {'presales_formset': presales_formset, 'my_opportunities': my_opportunities}
return render(request, 'website/presales.html', context)
presales.html
<form action="{% url 'presales' %}" method="post" name="presalesForm" id="presalesForm" data-total-url="{% url 'presales_total' %}">
{% csrf_token %}
{{ presales_formset.management_form }}
{% for presales_form in presales_formset %}
<div class="field">
<label class="label is-large">Create Task</label>
</div>
<div class="section">
<div class="field">
<label class="label">Opportunity</label>
<div class="select">
<select name="select_opportunity" id="select_opportunity">
<option value="">Opportunity</option>
{% for opportunity in my_opportunities %}
<option name="selected_opportunity" id="selected_opportunity" value="{{ opportunity.name }}">{{ opportunity.name }}</option>
{% endfor %}
</select>
</div>
</div>
<label class="label">Task Description:</label>
<div class="field">
<div class="control">
<input class="input" name="task_description" id="task_description" placeholder="Task Description">
</div>
</div>
<label class="label">Hours</label>
<div class="field">
<div class="control">
<input class="input" name="hours" id="hours" placeholder="Hours">
</div>
</div>
<label class="label">Engineer Level:</label>
<div class="field">
<div class="select">
<select name="select_engineer_level" id="select_engineer_level">
<option value="">Engineer Level</option>
<option value="PM">PM</option>
<option value="Solutions Technician">Solutions Technician</option>
<option value="Solutions Engineer">Solutions Engineer</option>
<option value="Senior Solutions Engineer">Senior Solutions Engineer</option>
<option value="Solutions Architect">Solutions Architect</option>
</select>
</div>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-info" type="button">Add Task</button>
</div>
</div>
{% endfor %}
<span class="label is-medium is-pulled-right" id="total_cost">Total: {{ total }}</span>
</form>
Your view does NOT return an HttpResponse in all cases, which is an error. This happend when request.method is different than POST and when the formset is not valid.
This is pretty easy to spot when removing most of the code and just leaving the conditional statements:
def presales(request):
if request.method == 'POST':
if presales_formset.is_valid():
...
return render(request, 'website/presales.html', context)
You have to return other HttpResponse's in the other cases as well to solve this error.
For example, you could un-indent the line return render(... 2 levels, so that the view always returns the template.

Django ModelForm initial not working for textarea

I have a django ModelForm, I'm trying to populate it with initial values from my views, all fields are populated except for the textarea field. I have no idea why. Any help is appreciated. Thanks!
views.py
name = 'Testing123'
private = True
notes = 'JUST CHECKING'
ctx['header_form'] = NewTemplateForm(initial={'name': name, 'private': private,'notes:': notes})
private is a BooleanField, initial works even for that
name is populated
notes is the only field that isn't being populated.
template.html
I'm rendering the form like this:
{% load widget_tweaks %}
<div class="form-group">
<div class="row" style="margin-bottom: 15px">
<div class="col-lg-6">
<label>{{form.name.label}}</label>
{% render_field form.name class="form-control" required="true" %}
</div>
<div class="col-lg-6" style="margin-top: 23px">
<div class="i-checks"><label> {% render_field form.private type="checkbox" %} <i></i> Make this Template Private </label></div>
</div>
</div>
</div>
<div class="row" style="margin-bottom: 15px">
<div class="col-lg-12">
<label data-error="wrong" data-success="right" for="id_notes">{{form.notes.label}}</label>
{% render_field form.notes class="form-control" %}
</div>
</div>
forms.py
class NewTemplateForm(ModelForm):
class Meta:
model = SalesTemplate
fields = {'name', 'notes', 'private'}
def __init__(self, *args, **kwargs):
super(NewTemplateForm, self).__init__(*args, **kwargs)
self.fields['notes'].widget = forms.Textarea(attrs={'class': 'md-textarea', 'style': 'height: 75px'})
Because there is a typo in your code and you are passing 'notes:' as key instead of 'notes' in this line
ctx['header_form'] = NewTemplateForm(initial={'name': name, 'private': private,
'notes:': notes}) # HERE!

How to make Bootstrap file browse button using django-crispy-forms

Here is my Django forms.py script, using django-crispy-forms
#!/usr/bin/env python
from django import forms
from .models import Method1
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout
class Method1Form(forms.ModelForm):
def __init__(self, *args, **kwargs):
""" Use for wrapping bootstrap
This is crispy stuff.
"""
super(Method1Form, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-method1Form'
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-8'
self.helper.form_method = 'post'
self.fields['inputfile_param'].label = "Input File"
self.fields['species_param'].label = "Species"
self.fields['norm_mode_param'].label = "Normalization"
self.fields['logscale_param'].label = "Log Scale"
self.helper.layout = Layout(
'inputfile_param',
'species_param',
'norm_mode_param',
'logscale_param',
)
self.helper.add_input(Submit('submit', 'Submit'))
I can create the following form:
As shown there, I'd like to make the browse button with Bootstrap style.
How can achieve that?
I'm thinking of something like this:
Complete HTML rendered by Django looks like this:
/* Stuff for django-crispy */
.asteriskField {
display: none;
}
.form-control {
font-size:18px;
font-family: "Helvetica Neue",HelveticaNeue;
}
.form-horizontal {
padding-left: 120px;
padding-right: 130px;
font-size:20px;
font-family: "Helvetica Neue",HelveticaNeue;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<meta charset="utf-8">
</head>
<body>
<!--- DISPLAY THE FORM -->
<form id="id-method1Form" class="form-horizontal" method="post" enctype="multipart/form-data"> <input type='hidden' name='csrfmiddlewaretoken' value='JdUjVaRwOkOxbQmoeSaSHTaDNTlwjs5U' /> <div id="div_id_inputfile_param" class="form-group"> <label for="id_inputfile_param" class="control-label col-lg-2 requiredField">
Input File<span class="asteriskField">*</span> </label> <div class="controls col-lg-8"> <input class="clearablefileinput" id="id_inputfile_param" name="inputfile_param" type="file" /> </div> </div> <div id="div_id_species_param" class="form-group"> <label for="id_species_param" class="control-label col-lg-2 requiredField">
Species<span class="asteriskField">*</span> </label> <div class="controls col-lg-8"> <select class="select form-control" id="id_species_param" name="species_param">
<option value="mouse" selected="selected">Mouse</option>
<option value="human">Human</option>
</select> </div> </div> <div id="div_id_norm_mode_param" class="form-group"> <label for="id_norm_mode_param" class="control-label col-lg-2 requiredField">
Normalization<span class="asteriskField">*</span> </label> <div class="controls col-lg-8"> <select class="select form-control" id="id_norm_mode_param" name="norm_mode_param">
<option value="genecount_norm" selected="selected">Gene Count</option>
<option value="totalscore_norm">Total Score</option>
</select> </div> </div> <div class="form-group"> <div class="controls col-lg-offset-2 col-lg-8"> <div id="div_id_logscale_param" class="checkbox"> <label for="id_logscale_param" class=""> <input class="checkboxinput" id="id_logscale_param" name="logscale_param" type="checkbox" />
Log Scale
</label> </div> </div> </div> <div class="form-group"> <div class="aab controls col-lg-2"></div> <div class="controls col-lg-8"> <input type="submit"
name="submit"
value="Submit"
class="btn btn-primary"
id="submit-id-submit"
/> </div> </div> </form>
<!--- END FORM DISPLAY-->
</body>
</html>
For those that find this, the previous answer does NOT work. The OP is asking for a file input field, not any type of field with a button on it. It seems like Crispy doesn't plan on ever fixing this issue (see issue). Using FieldWithButtons will create an input field with the same default button in addition to a crispy button that says "Go", like this: Go Browse button
The only crispy-centric way to do this is by creating a Browse button template and simply calling template=<your_template>.html when you make the field. I pulled bits from the BaseInput template to make this and it works.
file_input.html
<label for="{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}" class="btn btn-secondary">
{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
Browse
<input type="file"
name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}"
class="{{ input.field_classes }}"
id="{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}"
hidden
>
</label>
forms.py
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['image']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
# Fieldset('', *self.fields),
Fieldset('', Button('image', "Browse", css_class="clearablefileinput form-control-file", template="file_input.html", css_id="id_image")),
FormActions(
Submit('save', 'Update Profile'),
)
)
views.py
def profile(request):
p_form = ProfileUpdateForm(request.POST, instance=request.user.profile, files=request.FILES)
#...etc...
Result:
Bootstrappy browse button
I know this is an old post but for any one interested, Crispy Forms has this in the documentation:
FieldWithButtons: You can create an input connected with buttons:
FieldWithButtons('field_name', StrictButton("Go!"))
this is in the docs for crispy forms Bootstrap Layout objects

Categories

Resources