How to set ID for form in Django? - javascript

I've created a page, where user can make an order. So they can fill some fields like description,notes etc. including "language", which is a one field of formset.
So in my view, there is a job_creation_form which contains almost all fields, then there is a Formset consists of Language forms.
I would like to use JQuery validate plugin to validate fields without refreshing the page. The problem is that I can see one form tag in HTML, which doesn't have any id. How to set the id?
def create_order(request):
LanguageLevelFormSet = formset_factory(LanguageLevelForm, extra=5, max_num=5)
language_level_formset = LanguageLevelFormSet(request.POST or None)
job_creation_form = JobCreationForm(request.POST or None, request.FILES or None)
context = {'job_creation_form': job_creation_form,
'formset': language_level_formset}
if request.method == 'POST':
if job_creation_form.is_valid() and language_level_formset.is_valid():
cleaned_data_job_creation_form = job_creation_form.cleaned_data
cleaned_data_language_level_formset = language_level_formset.cleaned_data
for language_level_form in [d for d in cleaned_data_language_level_formset if d]:
language = language_level_form['language']
level = language_level_form['level']
job = Job(
customer=request.user,
text_to_translate=cleaned_data_job_creation_form['text_to_translate'],
file=cleaned_data_job_creation_form['file'],
short_description=cleaned_data_job_creation_form['short_description'],
notes=cleaned_data_job_creation_form['notes'],
language_from=cleaned_data_job_creation_form['language_from'],
language_to=language,
level=level,
)
job.save()
return HttpResponseRedirect('/order-success')
else:
print job_creation_form.errors
print language_level_formset.errors
return render(request, 'auth/jobs/create-job.html', context=context)
return render(request, 'auth/jobs/create-job.html', context=context)

In Django django.forms.Form only deals with the internal (i.e. fields, validation, saving) parts of a form, not any of the details relating to submission(submit buttons, actions, method).
That is why when you render a form in a template it is like:
{% extend base.html %}
{% block content %}
<form action='' method='post'>
{{ form }}
</form>
{% endblock %}
If you want you can set an id property on the form and access it in the template like <form ... id='{{ form.id }}'>, or just put an id on the form.

Related

How to dynamically generate different fields based on previous selection of select field in Flask WTForm

I would like to implement a form that generates different text area fields depending on what value was selected in the dropdown select field before hand. The context is creating projects of different categories: for example, if a user selects "Education", the next fields generated live could be a select field for "quarter/semester?", and a text area field for "Describe your subject matter". I understand that accomplishing such a feature requires javascript/jquery but I have no idea where to start. I am also unsure the best way to relate this to my models and database.
In my HTML template, I have implemented if statements but these of course don't work.
My form class looks something like this:
class ProjectForm(FlaskForm):
"""Create a new project, on /index"""
categories = ['Education','Software development', ...]
langs = ['Python', 'Java', ...]
name = TextAreaField('Give your project a name', validators=[
DataRequired(), Length(min=1, max=60)])
category = SelectField('Category', choices=categories, default=1)
#Education case
academic_pace = SelectField('Quarter or semester?', choices=['quarter', 'semester'],
default=1)
subject = TextAreaField('Describe your subject matter', validators=[
DataRequired(), Length(min=10, max=100)])
#Software development case
software_language = SelectField('What programming language??', choices=langs, default=1)
submit = SubmitField('Submit')
The route like this:
from app.models import User, Project
from app.forms import LoginForm, ProjectForm ...
#app.route('/', methods=['GET', 'POST'])
#login_required
def index():
form = ProjectForm()
if form.validate_on_submit():
project = Project(name = form.name.data, category = form.category.data,
academic_pace = form.academic_pace.data,
subject = form.subject.data,
software_language = form.software_language.data,
creator=current_user)
db.session.add(project)
db.session.commit()
flash('Your project is now live!')
return redirect(url_for('index'))
return render_template('index.html', title='Home', form = form)
index.html:
...
{{ form.category.label }}<br>
{{ form.category(cols=32, rows=4) }}<br>
{% for error in form.category.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{% if form.category == "Education" %}
{{ form.academic_pace.label }}<br>
{{ form.academic_pace(cols=32, rows=4) }}<br>
{% for error in form.academic_pace.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{{ form.subject.label }}<br>
{{ form.subject(cols=32, rows=4) }}<br>
{% for error in form.subject.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{% endif %}
...
I think I figured out how to do this using jquery. Essentially for each category's unique forms, I wrap them in a div. These divs are hidden at first, with style="display:none;". I use jquery to then identify what options are being selected from the SelectField, and show or hide the other form divs accordingly. Below is the javascript for one case.
$(document).ready(function() {
$("#category").change(function () {
var val = $(this).val();
if (val == "learning") {
$("#learning").show();
}
else {
$("#learning").hide();
}
});
});

Django/Ajax : How to filter a form field's queryset asynchronously?

In a given Django form view, I'd like to filter a field's queryset by an option a user has selected while viewing the form. And I'd like that to happen asynchronously. I've read that AJAX might be what I should use, but I know very little of JS, and I can't make much sense of the information I've read.
The way I see it, I'd like a user to be able to click on one or more checkboxes that would filter 'loot.item_name' by 'item.in_itemlist' or 'item.item_type' (automatically, using onChange or onUpdate or smth?).
Would someone kindly give me some pointers? Cheers,
Here are my models:
models.py
class Itemlist(models.Model):
name = models.CharField([...])
class Item(models.Model):
name = models.CharField([...])
item_type = models.CharField([...])
in_itemlist = models.ForeignKey(Itemlist, [...])
class Loot(models.Model):
item_name = models.ForeignKey(Item, [...])
quantity = [...]
my view,
views.py
def create_loot(request):
# LootForm is a simple ModelForm, with some crispy layout stuff.
form = LootForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
[...]
return render(request, 'loot_form.html', context={'form': form}
and the template,
loot_form.html
{% extends "base_generic.html" %}
{% block leftsidebar %}
/* for each unique itemlist referenced in the items, */
/* make a checkbox that once clicked, filters 'loot.item_name' by the selected option.*/
{% endblock leftsidebar %}
{% block content %}
<h1 class="page-title">Add Loot</h1>
<hr class="page-title-hr">
{% csrf_token %}
{% load crispy_forms_tags %}
{% crispy form form.helper %}
{% endblock content %}
You can create Api based view for your Model
in JavaScript, you can call the the API end and get the data
Ajax code can be something like below
$(document).ready(function () {
setTimeout(function () {
$(document).on('click', '#id', function () {
$.get("./api/v2/end_point?state=" + state, function (data, status) {
var inner_data = data[0].state_count;
change_text.html(inner_data);
});
});
}, 100);
})

How to keep a form from submitting when creating a Django model object with many-to-many field selection that is doesn't exist in the related model?

I'm using Django to create a web app. I have multiple Django models with relationships between each other. One of my models is called Type and another is called TestEquipment. The Type model has a many-to-many relationship with TestEquipment.
To allow the user to create a new Type, I have an html form and to select which TestEquipment will be associated with that Type I have am using searchable dropdown with "chips" (javascript) which loads all TestEquipment and allows you to search and select multiple objects to add. When the user submits the form, the selected TestEquipment is added.
Everything works great other than when the user enters text and presses enter, instead of selecting from the dropdown, a chip is added with that text. When the form submits it tries to add a TestEquipment that doesn't exists.
I would like to find a way to either not allow adding an objects that doesn't exist or throw an alert "must select from existing test equipment"; somehow I have to ensure that the form does not submit to my constructor and create the new Type if text is added to the field.
I've tried to find an answer to this and absolutely have had no luck. Any help is REALLY appreciated!
Django Models code:
class TestEquipment(models.Model):
name = models.CharField(max_length=64, unique=True)
notes = models.TextField(null=True, blank=True)
def __str__(self):
return f"{self.name}"
class Type(models.Model):
name = models.CharField(max_length=64, unique=True)
type_folder = models.URLField(null = True, blank = True)
type_test_equipment = models.ManyToManyField(TestEquipment, blank=True, related_name="type_test_equipment")
type_notes = models.TextField(null=True, blank=True)
test_sheet = models.URLField(null=True, blank=True)
type_test_guide = models.URLField(max_length=300, null=True, blank=True)
objects = TypeManager()
def __str__(self):
return f"{self.name}"
Views.py code:
def create_type_view(request):
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
test_equipments = TestEquipment.objects.all()
equipment_types = Type.objects.all()
#pass in existing types of equipment
context= {
"equipment_types": equipment_types,
"test_equipments": test_equipments
}
if request.user.is_authenticated:
return render(request, "jobs/create_type.html", context)
create_type.html code (pulls in materialize styling and javascript):
<div>
<form id="type_form" class="col s12" action="{% url 'create_type' %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col s12">
<h6 style="color:#808080">Select Type-Specific Test Equipment</h6>
<div class="row">
<div class="input-field col s12">
<div id="test_equipment-chips" class="chips chips-autocomplete">
</div>
<input id="test_equipment" name="test_equipment" style="visibility: hidden;" value="">
</div>
</div>
</div>
</div>
<div class="row">
<input id="submit-btn" type="submit" value="Confirm Add" onclick="onSubmit();" />
</div>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script>
function onSubmit() {
var chipInstance = M.Chips.getInstance($("#test_equipment-chips"));
var value = Array.from(chipInstance.chipsData.map(x=>x.tag)).join(",");
$("#test_equipment").val(value);
}
$('#test_equipment-chips').chips({
autocompleteOptions: {
data: {
{% for test_equipment in test_equipments %}
'{{test_equipment}}': null,
{% endfor %}
},
limit: Infinity,
minLength: 0,
}
});
</script>
code for constructor:
def create_type(request):
#extract type name from form
type_name = request.POST["type_name"]
#create new type object
new_type=Type.objects.create_type(type_name)
#fill in all properties that were submitted in the form
new_type.type_folder = request.POST["type_folder"]
new_type.test_sheet = request.POST["test_sheet"]
new_type.type_test_guide = request.POST["type_test_guide"]
new_type.type_notes = request.POST["type_notes"]
equipment_list=request.POST["test_equipment"].split(",")
for equipment_name in equipment_list:
equipment=TestEquipment.objects.get(name=equipment_name)
new_type.type_test_equipment.add(equipment.pk)
#save to database
new_type.save()
return HttpResponseRedirect(reverse("jobs"))
So the most important thing to do there is to perform a check in your python because you shouldn't perform a get request to the database like that unless you know it can't fail. As you're seeing, it just ends up with 500 errors when objects don't exist.
So job 1 would be to do this in your view;
for equipment_name in equipment_list:
try:
equipment=TestEquipment.objects.get(name=equipment_name)
except TestEquipment.DoesNotExist:
# Log the error somehow if you like?
print(f"Equipment not found with the name {equipment_name}")
else:
# This only executes when the exception wasn't raised
new_type.type_test_equipment.add(equipment.pk)
Using the above code, it'd just ignore invalid input, which suits a minimum viable option. If you wanted to provide an error in the event of invalid input however you should run through all values in equipment_list to ensure they exist before adding them to the relationship. That way you could render the form again with the errors attached.
I've had a look at the demo/docs for chips and it doesn't look like it can help prevent invalid inputs, but you could do some validation in javascript yourself to prevent submission.
You should also have a look at using django's forms because your view is directly accessing post data. By using forms you can handle validation logic outside of your view & there's a bunch more benefits from them. If you're yet to find out about forms, give this page a read; https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms

How to hide Django form fields using JavaScript, Jquery etc

I would like to dynamically
hide form fields. The user should be able to select the component type, which could be a VALVE in which case the user should specify the Kv value and the DI and length fields should be hidden. Or the user could select the PIPE component type in which case the user should specify the inner diameter (DI) and length of the pipe and the k_v field should be hidden.
The model is defined as follows:
class Component(models.Model):
COMPONENT_TYPE_CHOICES = (
(1, 'k_v'),
(2, 'pipe')
)
circuit = models.ForeignKey('circuit.Circuit', related_name='components', on_delete=models.CASCADE)
component_type = models.IntegerField(default=1, choices = COMPONENT_TYPE_CHOICES)
component_name = models.CharField(max_length=200)
branch_number_collectors = models.IntegerField(default=4)
# Hide if component_type==2
k_v = models.FloatField(default=1)
# Hide if component_type==1
DI = models.FloatField(default=0.025)
length = models.FloatField(default=1)
# Calculated properties
branch_volumetric_flow_rate = models.FloatField(default=0)
branch_mass_flow_rate = models.FloatField(default=0)
velocity = models.FloatField(default=0)
reynolds = models.FloatField(default=0)
friction_coefficient = models.FloatField(default=0)
pressure_loss = models.FloatField(default=0)
#classmethod
def create( cls,
circuit,
...,
The forms.py is as follows:
class ComponentForm(forms.ModelForm):
class Meta:
model = Component
fields = [
'component_type',
'component_name',
'branch_number_collectors',
'k_v',
'DI',
'length'
]
The simplified Django template is as follows:
{% block content %}
<form method='POST'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Save</button>
</form>
{% endblock %}
first go to django shell and then do the following:
python manage.py shell
from yourapp.yourform import ComponentForm
f = ComponentForm()
print(f.as_p())
this will give you all the id and class names you can use in your javascript or CSS to manipulate.
lets say you want to hide length then you will do:
$(document).ready(function(){
$('#id_length').hide();
})
Ok I solved the problem. When the user selects the PIPE option form the component_type dropdownlist the k_v field is hidden and the DI and length fields are shown. When the user selects the k_v option from the component_type dropdownlist the k_v field is shown and the length and DI fields are hidden.
My Django template is now as follows:
{% extends 'base.html' %}
<script>
{% block jquery %}
// Call hideShow when page is loaded
$(document).ready(function(){
hideShow()
})
// call hideShow when the user clicks on the component_type dropdownlist
$('#id_component_type').click(function(){
hideShow()
});
// The jquery function below hides/shows the k_v, DI and length fields depending on the selected component_type
function hideShow(){
if(document.getElementById('id_component_type').options[document.getElementById('id_component_type').selectedIndex].value == "1")
{
$('#id_length').parents('p:first').hide();
$('#id_DI').parents('p:first').hide();
$('#id_k_v').parents('p:first').show();
}else
{
$('#id_length').parents('p:first').show();
$('#id_DI').parents('p:first').show();
$('#id_k_v').parents('p:first').hide();
}
}
{% endblock %}
</script>
{% block content %}
<form method='POST'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Save</button>
</form>
{% endblock %}

Handle form submission in bootstrap modal with ajax and class based views

i'm quite new using django and i've been stuck in this problem for several days.
I have a form.Form in a bootstrap modal on my template with only 1 field (email_field) and basically i need to submit that form via ajax, check if that email address is registered in the database, then send an invitation to that email and close the modal. if the email is not registered show the form errors without closing the modal. I've tried with different examples but can find the solution either because the examples don't handle errors or the form is not inside a modal or not using class based views
.
I'm having 2 issues with my code:
Not sure what to return in my view if the form is valid or invalid and how to handle errors in my js code to show them on the modal.(return tha form to render the errors or a JSON response??).
After the first success submission is made the form cannot be used again.(The size of the submit button changes and if you click it return a error : CSRF token missing or incorrect)
Form.py
class CollaboratorForm(forms.Form):
email_address = forms.EmailField(required=True,widget=forms.TextInput(attrs={'class': 'form-control focus-text-box', 'type': 'email',
'placeholder': 'Enter email'}))
def clean_email_address(self):
email = self.cleaned_data['email_address']
if not User.objects.filter(email=email):
raise forms.ValidationError('This user is not registered')
return email
def sendEmail(self, datas):
message = "Hello, " + datas['user_name']+" "+ datas['email_from'] + " invited you to collaborate in an existing project. Follow this link if you are interested " + datas['invitation_link']
msg = EmailMessage('Invitation from ' + datas['user_name'],
message, to=[datas['email_to']])
msg.send()
Template.html (project_detail.html)
<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>
<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
<div class="modal-body">
<form action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
{% csrf_token %}
<div class="form-group">
{% if collaborator_form.errors %}
<ol>
{% for error in collaborator_form.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
<label class="control-label">Invite someone by email</label>
<div class="input-group mt10">
{{ collaborator_form }}
<span class="input-group-btn">
<input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
</span>
</div>
</div>
</form>
</div>
</div>
Url.py
urlpatterns = [
url(r'^(?P<project_id>[0-9]+)/invite_collaborator$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]
View.py
class ProjectDetail(DetailView):
model = Project
template_name = 'experiments/project_detail.html'
pk_url_kwarg = 'project_id'
def get_context_data(self, **kwargs):
context = super(ProjectDetail, self).get_context_data()
project = get_object_or_404(Project,pk=self.kwargs["project_id"])
context["project"] = project
context["collaborator_form"] = CollaboratorForm()
return context
class InviteCollaborator(FormView):
form_class = CollaboratorForm
template_name = 'experiments/project_detail.html'
def post(self, request, *args, **kwargs):
collaborator_form = CollaboratorForm(data=request.POST)
project_id = request.POST['project_id']
current_project = Project.objects.get(id=project_id)
datas={}
if collaborator_form.is_valid():
cleaned_data = collaborator_form.cleaned_data
email_address = cleaned_data.get('email_address')
user = User.objects.get(pk=request.user.id)
invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
datas['user_name'] = user.first_name + ' ' + user.last_name
datas['email_from'] = user.email
datas['email_to'] = email_address
datas['invitation_link'] = invitation_link
collaborator_form.sendEmail(datas)
data = simplejson.dumps("Success")
return HttpResponse(data, content_type='application/json')
else:
return super(InviteCollaborator, self).form_invalid(collaborator_form)
invite_collaborator.js
function invite(project_id) {
$('#collaborator-form').submit(function(e) {
e.preventDefault();
$.ajax({
data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
type: $(this).attr('method'),
url: $(this).attr('action'),
});
$('#collaboratorModal').modal('toggle');
$('#collaboratorModal').on('hidden.bs.modal', function () {
$(this).find("input,textarea,select").val('').end();
});
});
};
I've read about using success: & error: on the js file but don't know how to use it without the appropriate "return" in the view
You need to have two ajax methods, one to get the form (as raw html) and one to post the form. You will have a corresponding get and post method in your view too.
get function of your view class:
def get(self, request, *args, **kwargs):
form = CollaboratorForm()
return render(request,'template.html',{'form':form})
def post(self, request, *args, **kwargs):
form = CollaboratorForm(request.POST)
if form.is_valid():
//save form
//return whatever you want to show on successful form submission
else:
//return bound form as html with errors
return render(request,'template.html',{'form':form})
js functions
have two seperate ajax function one for get (showing form) one for post(submitting form)
If you want to use templates on server's side, with FormView and ajax, I would suggest splitting templates into two parts - wrapper and form, load only wrapper via TemplateView, then fetch form with ajax. That allows you to send form with ajax and put responses (like form with errors) in wrapper.
Change your HTML template - take modal body's to another file, ex.:
project_detail.html
<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>
<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
<div class="modal-body" id="collaboratorModalContent">
</div>
</div>
project_detail_content.html
<form action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
{% csrf_token %}
<div class="form-group">
{% if collaborator_form.errors %}
<ol>
{% for error in collaborator_form.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
<label class="control-label">Invite someone by email</label>
<div class="input-group mt10">
{{ collaborator_form }}
<span class="input-group-btn">
<input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
</span>
</div>
</div>
</form>
FormView should handle GET and POST - first one to get the form in project_detail_content.html into modal, second for sending email. Fortunately, FormView can do all that for us! (I don't know from where you get that project variable though)
View.py
class InviteCollaborator(FormView):
form_class = CollaboratorForm
template_name = 'experiments/project_detail_content.html'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
project_id = self.request.POST['project_id']
current_project = Project.objects.get(id=project_id)
datas={}
cleaned_data = form.cleaned_data
email_address = cleaned_data.get('email_address')
user = User.objects.get(pk=request.user.id)
invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
datas['user_name'] = user.first_name + ' ' + user.last_name
datas['email_from'] = user.email
datas['email_to'] = email_address
datas['invitation_link'] = invitation_link
form.sendEmail(datas)
data = simplejson.dumps("Success")
return HttpResponse(data, content_type='application/json')
Note few things - we use FormView, so for GET request it will return content of project_detail_content.html with CollaboratorForm, and on POST, same template with form and errors if form is invalid, or JSON with Success message otherwise.
What happened to project_detail.html? We will use TemplateView to create thw wrapper:
Url.py
urlpatterns = [
url(r'^invite_collaborator$', TemplateView.as_view(template_name="project_detail.html")),
url(r'^(?P<project_id>[0-9]+)/invite_collaborator/form$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]
Finally, JS
invite_collaborator.js
// In JS you need to make sure you fetch form from /project_id/invite_collaborator/form each time you show modal
$(document).ready(function(e) {
$('#collaboratorModalContent').load('invite_collaborator');
});
// Then, on submit we simply send data and handle response with success and error.
// With our current View, invalid form will generate successful response with form and error, so we need to check
function invite(project_id) {
$('#collaborator-form').submit(function(e) {
e.preventDefault();
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
success: function ( response, status, xhr, dataType ) {
if( dataType === 'json' ){
//Make sure response is 'Success' and close modal
$('#collaboratorModal').modal('toggle');
$('#collaboratorModal').on('hidden.bs.modal', function () {
$(this).find("input,textarea,select").val('').end();
});
});
};
}
else {
// It's not JSON, it must be HttpResposne with forms and errors, so it goes into modal's body
$('#collaboratorModalContent').html(response)
}
}
});
I still don't know where and how you get/set you project variable, so maybe TemplateView is bad choice...

Categories