Conditional Form Field in Django wizard form - python

I am using wizard forms in django. I want to create a form field only if answer to some other form field is marked "yes" otherwise I don't want this new form field. How can I do this ?
I have tried some other answers related to this but most of them tells about how to mark field required or not but I want to display that field only if answer to other field is "Yes"
Django Form Wizard with Conditional Questions
In below code I want to display field "Pool2" only if answer to field "Pool" is marked "yes" otherwise I don't want that field. Basically I want to get some details of pool in field "Pool2" if there is pool in user's house.
forms.py
class ListingForm2(forms.Form):
Pool = (
("Yes","Yes"),
("No","No"),
)
Pool = forms.ChoiceField(choices = Pool,label = "Does your property have a pool ?")
Pool2 = forms.CharField(required=False)
Views.py
class ListingWizard(SessionWizardView):
template_name = 'listing_form.html'
form_list = [ListingForm1,ListingForm2,ListingForm3,ListingForm4]
def done(self, form_list, **kwargs):
save_data(form.cleaned_data for form in form_list)
return render(self.request,'done.html',{
'form_data' : [form.cleaned_data for form in form_list],
})

What you are trying to do have to be done with JavaScript, you could do it with only Django posts but it is not a right way.
Check this out:
class BookForm(forms.ModelForm):
has_sequel = forms.BooleanField(initial=True)
class Meta:
model = Book
fields = ['author', 'length', 'has_sequel', 'sequel']
class Media:
js = ('book_form.js', )
def clean(self):
if self.cleaned_data['has_sequel'] and self.cleaned_data['sequel'] is None:
raise ValidationError('You should indicate the sequel if the book has one.')
class BookView(FormView):
template_name = 'book_form.html'
form_class = BookForm
success_url = '/done/'
This code is including a Javascript with the form, this way you could reuse the form with its own Javascript, the Javascript code should be something like this (you maybe have to change javascript depending on how you print your form in the template):
$(document).ready(function() {
$('#id_has_sequel')[0].addEventListener('change', (event) => {
let sequelField = $('#id_sequel').parents('p');
if (event.target.checked) {
sequelField.show();
} else {
sequelField.hide();
}
})
});
And the template should be something like this:
{% load static %}
<head>
<title>Book form</title>
<script src="{% static 'jquery-3.4.1.min.js' %}"></script>
{{ form.media }}
</head>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send message">
</form>
If you have any question feel free to ask but trying to do this without Javascript it is not a good approach. As much you will find some kind of Django widget that will use Javascript too.

Related

How can i use more different forms in the same Django template?

In my project, i have a template where i'm trying to put two forms for different use cases. I've never come across this problem before, so i don't really know where to go from here to use two forms in the same page.
At first i thought of creating another view to handle each form, but i think that this solution would create problems with the rendering of my templates, other than not being sustainable if i should have this problem again with another template.
After making some research, i found a solution but it works for class based views, but i'd like to avoid that since my view is already a function based view, and i would have to make a lot of changes in my code.
Would it be possible to solve this problem with a function based view? Every advice is appreciated
First field
class FirstForm(forms.ModelForm):
firstfield = forms.CharField()
secondfield = forms.CharField()
class Meta:
model = MyModel
fields = ("firstfield", "secondfield")
def save(self, commit=True):
send = super(FirstForm, self).save(commit=False)
if commit:
send.save()
return send**
Second Form
class SecondForm(forms.ModelForm):
firstfield = forms.FloatField()
secondfield = forms.Floatfield()
thirdfield = forms.CharField()
class Meta:
model = MyModelTwo
fields = ("firstfield", "secondfield", "thirdfield")
def save(self, commit=True):
send = super(SecondForm, self).save(commit=False)
if commit:
send.save()
return send
Template
<h3> First Form </h3>
<form method="post" novalidate>
{% csrf_token %}
{% include 'main/includes/bs4_form.html' with form=form %}
<button type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
</form>
<h3> Second Form </h3>
<form method="post" novalidate>
{% csrf_token %}
{% include 'main/includes/bs4_form.html' with form=form %}
<button type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
</form>
EDIT: my view:
def myview(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = FirstForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
send = form.save()
send.save()
messages.success(request, f"Success")
# if a GET (or any other method) we'll create a blank form
else:
form = FirstForm()
return render(request,
"main/mytemplate.html",
context={"form":form})
This answer is a bit general because you haven't included your view function. You can add each of these forms to your view's context. Something like this:
views.py
...
from .forms import FirstForm, SecondForm
...
def some_view(request):
context = {
'first_form': FirstForm(request.POST or None),
'second_form': SecondForm(request.POST or None)
}
return render(request, "app/some_template.html", context)

Django/django-tables2 html table on row click to edit form

sorry this post may be messy not sure how do explain what I am looking for very well but here goes nothing.
I have a Django App and using django-table2 to print a data model to a table, the next thing I am looking to do it when the user clicks on the table row to redirect the page to a equivalent edit form
urls.py
path('', CustomerView.as_view(), name='customer'),
path('customer_edit/', views.customer_edit, name='customer_edit'),
tables.py
import django_tables2 as tables
from customer.models import Customer
class CustomerTable(tables.Table):
account = tables.Column(attrs={'td': {'class': 'account'}})
class Meta:
model = Customer
attrs = {'id': 'table'}
exclude = ('is_deleted',)
template_name = 'django_tables2/bootstrap-responsive.html'
views.py
from django.shortcuts import render
from django_tables2 import RequestConfig
from customer.models import Customer
from customer.tables import CustomerTable
from django.views.generic import TemplateView
class CustomerView(TemplateView):
template_name = 'customer/customer.html'
def get(self, request, *args, **kwargs):
table = CustomerTable(Customer.objects.all().filter(is_deleted=False))
RequestConfig(request).configure(table)
return render(request, 'customer/customer.html', {'table': table})
def customer_edit(request):
return render(request, 'customer/customer_edit.html')
template
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% block head %}
<title>Dev Genie - Customers</title>
{% endblock %}
{% block body %}
<div class="input-group col-md-6">
<input type="button" class="btn btn-success" value="Add">
<input type="button" class="btn btn-danger" value="Delete">
<input class="form-control py-2" type="search" value="search" id="example-search-input">
<span class="input-group-append">
<button class="btn btn-outline-secondary" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
{% render_table table %}
<script>
$(document).ready(function () {
$('table:first').children('tbody:first').children('tr:first').css('background-color', '#0099ff');
$('table tbody tr').bind("mouseover", function () {
var colour = $(this).css("background-color");
$(this).css("background", '#0099ff');
$(this).bind("mouseout", function () {
$(this).css("background", colour);
});
});
$('table tbody tr').click(function () {
let account = $(this).closest('tr').find('td.account').text();
alert(account);
//on table row click event, pass back to django
});
});
</script>
{% endblock %}
I am struggling to get the account code from the onclick even to pass the account code back to Django to move to the next page to begin editing the record
I really think I am barking up the wrong tree with this
any help would be very much appreciated
I couldn't find any solution that suits my needs.
All the solutions I found requires some weird processing in Javascript and parsing slugs and PK's from the table to redirect to the correct URL.
My solution?
Define an absolute URL in your models.py
def get_absolute_url(self):
return reverse('product:detail', kwargs={'slug': self.slug})
Then in your tables.py, we add a data-href attribute to each column that we want to be clickable. This allows us to restrict which columns become clickable.
class ProductTable(tables.Table):
clickable = {'td': {'data-href': lambda record: record.get_absolute_url}}
name = tables.Column(attrs=clickable)
in_stock = tables.Column(attrs=clickable)
class Meta:
model = Product
fields = (name, in_stock)
And in your template just add this simple event listener,
$(document).ready(function($) {
$("td").click(function() {
window.location = $(this).data("href");
});
});
Alternatively, if you just want the whole row to be clickable, just use Row attributes as defined in the docs,
class ProductTable(tables.Table):
class Meta:
model = Product
row_attrs = {'data-href': lambda record: record.get_absolute_url}
fields = (name, in_stock)
and then change your template script too
$(document).ready(function($) {
$("tr").click(function() {
window.location = $(this).data("href");
});
});
I think i may have found a implementation for the above.
Putting a click event for a dialogue box with Django Tables2
it is for deleting a row but the concept is the same
I will test and check
Simple code to do that on row click or col
row_attrs = {
"onClick": lambda record: "document.location.href='/app/view/{0}';".format(record.id)
}
if you want to use it on col use tables.Column
Docs
Ok after spending this evening on this, I have found a way to perform this action without adding the href tag into the python code,
by using Ajax I can get the account code from the table and then pass this through to the url
$('table tbody tr').click(function () {
let account = $(this).closest('tr').find('td.account').text();
window.location = account;
});
adding the primary key to the url.py
path('<slug:account>/', views.customer_edit, name='customer_edit'),
and adding the customer_edit def to the views.py
def customer_edit(request, account):
customer = get_object_or_404(Customer, pk=account)
if request.method == 'POST':
form = CustomerEdit(request.POST, instance=customer)
if form.is_valid():
customer.save()
return redirect(reverse('customer:customer'))
else:
form = CustomerEdit(instance=customer)
args = {'customer': customer, 'form': form}
return render(request, 'customer/customer_edit.html', args)
this is the most optimum way I could find to redirect to another view from Django without having the url specified inside of the python file, I am 100% that there is better ways to do this but for now this will be the accepted answer
I may be a little confused about what you are trying to do. It seems like you are for some reason trying to have the view render a new response back from the click events on the table. That is why you are getting tripped up with all this JavaScript rendering. You should simply have those cells render as links that go to where you need them to.
Take a look at the django-tables2 documentation for TemplateColumn. You will want to just have it point to a template that creates the url given the record pk.
https://django-tables2.readthedocs.io/en/latest/pages/api-reference.html?highlight=templatecolumn#templatecolumn
tables.py
class CustomerTable(tables.Table):
account = tables.TemplateColumn(template_name="_account.html")
def render_title(self, record, value, column, bound_column, bound_row):
value = self.value_title(record, value)
return mark_safe( # noqa: S308, S703
column.render(record, self, value, bound_column, bound_row=bound_row)
)
_account.html
<a href={% url('customer_edit', args=[record.pk]) %}>your text here</a>

How make 'add members' functionality Django

I am doing a management tool web application. I would like user can click 'add members' button, and the member will be added under 'Members'. I am using ajax to retrieve data from database, but I do not know how to display 'username'. It only displays 'id'. Moreover, I would like added members to stay in page whenever user refreshes the page.
project_index.html
<form action="" method="GET" id="selection-form">
{% csrf_token %}
<select id="member_list">
{% for user in user %}
<option value="{{user.pk }} }}">
{{ user.username }}
</option>
{% endfor %}
</select>
<input type="button" value="Add member" id="selection-button">
</form>
<div id="res"> </div>
views.py
def member_select(request):
selection = request.GET.get('id',None)
if selection:
data = serializers.serialize('json',User.objects.filter(pk=selection))
else:
data = {}
return HttpResponse(data, content_type='application/json')
base.html
<script>
var url = $( '#selection-button' ).attr( 'action' );
$("#selection-button").on('click', function(e) {
e.preventDefault();
var value =$('#member_list').val();
console.info('test',value)
$.ajax({
type:'GET',
url:'.',
data:{
id:value,
},
success:function (result) {
$("#res").append(value);
console.info(result)
},
error:function (result) {
alert('error');
}
});
});
</script>
You Can write a serializer, and specify fields you want to display:
class UserSerializer:
class Meta:
model = User
fields = ('pk', 'username')
class UserSerializer:
class Meta:
model = User
fields = ('pk', 'username')
def create(self,validated_data):
return User(**validated_data)
For view
class UserCreateAPIView(generic.CreateAPIView):
serializer_class=UserSerializer
And I hope you was familiar with django rest
I have 2 concerns about how you are doing this:
First is that in your javascript you're reading the data wrong.
I inspected the result return from django.core.serializers.serialize the result only have those keys ['model', 'pk', 'fields'].
this is why you can access pk attribute in your javascript but not username that is because username attribute is part of fields object.
so you should read username as user.fields.username not user.username
Second concern is that your version is not retrieving all fields by default:
Try to user fields keyword argument:
https://docs.djangoproject.com/en/2.0/topics/serialization/#subset-of-fields
If you only want a subset of fields to be serialized, you can specify a fields argument to the serializer:
from django.core import serializers
...
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
In this example, only the name and size attributes of each model will be serialized. The primary key is always serialized as the pk element in the resulting output; it never appears in the fields part.
So in your case this should be something like
data = serializers.serialize('json', User.objects.filter(pk=selection), fields=('username','id'))
I recommend that you use django-rest-framework just as #mohammad-ali described.
djanog-rest-framework allows you to do more.

Updating a variable value of a user object in Django

I am trying to implement some functionality that allows a user to edit their personal information in a Django project using Django forms. When a user enters the new value in the form and hits enter, they are brought back to the main profile page which is correct however, the values remain the same as before. Below is how I have tried to implement the functionality:
Forms
class UpdateProfile(forms.ModelForm):
email = forms.EmailField(required=False)
first_name = forms.CharField(required=False)
last_name = forms.CharField(required=False)
age = forms.IntegerField(required=False)
height = forms.IntegerField(required=False)
weight = forms.IntegerField(required=False)
class Meta:
#Here are the fields that i want editable
model = User
fields = ('email', 'first_name', 'last_name', 'age', 'height', 'weight')
#Here im trying to commit the changes to the user and return the user
def save(self, commit=True):
super(UpdateProfile, self).__init__(commit)
if commit:
user.save()
return user
Views
def update_profile(request):
args = {}
if request.method == 'POST':
form = UpdateProfile(request.POST, instance=request.user)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('account/profile.html'))
else:
form = UpdateProfile()
args['form'] = form
return render(request, 'account/edit_profile.html', args)
HTML
% block head %}
<title>Profile</title>
{% endblock %}
{% block body %}
<div class="container">
<form method="POST" action="{% url 'account:profile' %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
<br>
</div>
{% endblock %}
Your form is submitting directly to the view profile page. But that page is presumably not expecting to validate a form. You need to submit it back to the update_profile page, which you normally do by using an action of just "." in the form HTML element.
<form method="POST" action=".">
Once you've done that, you'll see some issues with your form save() method. That method does not do anything useful anyway; you should remove it and let the superclass one be called automatically.
This line seems wrong:
super(UpdateProfile, self).__init__(commit)
You're calling __init__ from the parent class, but the method being called is save()... Also you're refering to a user variable which is (hopefully) not defined in this scope.

Clean Django form fields with same name

I have a django template in which I'm dynamically rendering multiple fields (using ajax)
Below is a Django form (which has been rendered in a template) whose fields have same names. I want to use the cleaned_data method to clean form data in views.py before storing them in the database.
index.html
<div class="form-container">
<!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
<input type='text' name='this_field'>
<input type='text' name='this_field'>
<button type="submit">Submit</button>
</form>
</div>
<form id="addItemForm">
{% csrf_token %}
<!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
<button id = "addItemButton">Add item</button>
</form>
<script>
var addItemButton = document.querySelector('#addItemButton');
addItemButton.onclick = function(){
$.ajax({
type: 'POST',
url: 'addItem/',
data: addItemForm.serialize(),
success: function (response) {
$("#orderForm").append(response);
console.log('Success');
},
error: function (response) {
console.log('Error = '+response);
}
});
};
</script>
forms.py
class ItemForm(forms.Form):
this_field = forms.CharField()
urls.py
urlpatterns = [
url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]
views.py
def add_item(request):
if request.method == 'POST':
itemForm = ItemForm()
return HttpResponse(itemForm.as_p())
def pickup_order_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
same_name_fields = request.POST.getlist('this_field')
# WANT TO CLEAN DATA IN same_name_fields
if form.is_valid():
print(form.cleaned_data)
# ONLY PRINTS THE LAST FIELD's DATA
return HttpResponseRedirect('/viewPickupRequests')
The problem I'm facing is that if I use form.cleaned_data['this_field'], only the last field's data is fetched i.e. in this example, the field with value anotherTestValue is fetched and cleaned. If I fetch the data using request.POST.getlist('this_field'), all the fields' data is fetched and stored as a list, but, I don't know how to clean it using cleaned_data method. Is there a way to apply the cleaned_data method to the list of field data?
I'm sorry, I can't test if this works so this is not really an answer - but the comment system is not suitable for larger code chunks so I'm posting here.
Django forms lack a field type that renders to multiple text inputs with the same name. The proper thing to do would be to write a new form field class and a new widget. Since you are not rendering the form in the template (you are using it only for validation) I will omit the widget part.
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
Then use this field class instead of forms.CharField() (you may need to pass an empty choices parameter).
[update]
So essentially what you're saying is that I need to create new form field class and then render it to the template each time the user wants to add a new field? What if user has to add 15 fields, I'll need to create 15 classes then! I think this method won't be suitable in scenarios where number of fields required to be generated is large. I feel there should be some elegant way to do this which i'm not aware of – The OP
No, it is not what I'm saying. You probably want to subclass something like MultipleHiddenInput and set AcceptAnythingMultipleChoiceField.widget to it. You will have to create a new template based on the template for MultipleHiddenInput and replace input type="hidden" for type="text" (the original template is django/forms/widgets/multiple_hidden.html).
class AcceptAnythingWidget(MultipleHiddenInput):
template_name = 'django/forms/widgets/multiple_visible.html'
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
widget = AcceptAnythingWidget
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
This should render as many <input name='this_field'> as needed for instantiated forms at the frontend if you use:
{{ form.this_field }}
in the template, but will not add/remove them dynamically.
In order to do that you must plug in the JavaScript required to add/remove inputs dynamically in the widget but I will left this as an exercise for you. Look at Form Assets (the Media class) in the docs in order to figure out how to do that.
I think that what you are looking for is formsets. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
from django.forms import formset_factory
ItemFormSet = formset_factory(ItemForm, extra=2)
You can the essentialy use ItemFormSet in the way you would use a normal form except that this objects is iterable.
You will also have to change your jquery if you want to dynamically add items. There are many examples online on how to do this. In short what you do is
clone one of the forms in the formset
clear all the values from the copied form
update the input's (prefixes of) id's
Using Formsets doesn't solve the problem of fetching and validating
fields with same name. The issue still remains
It does however generate the end result you wanted (see below). My question would be why you need to have inputs with the same name? If there is some jquery stuff that uses these names I dont see any reason why you wouldn't be able to use name like... or assign a class to the inputs instead.
def pickup_order_view(request):
if request.method == 'GET':
ItemFormSet = formset_factory(ItemForm, extra=5)
item_formset = ItemFormSet()
template = "some_template.html"
template_context = {'item_formset': item_formset}
return render(request, template, template_context)
if request.method == 'POST':
ItemFormSet = formset_factory(ItemForm)
item_formset = ItemFormSet(request.POST)
same_name_fields=[]
if item_formset.is_valid():
for item_form in item_formset:
same_name_fields.append(item_form.cleaned_data['this_field'])
print(same_name_fields)
Template
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
{% csrf_token %}
{{ item_formset.management_form }}
{{ for item_form in item_formset }}
{{ item_form.as_p }}
{{ endfor }}
<input type='submit' value='submit'>
</form>
Go to newPickupOrder/ , fill in the 5 fields, hit submit, and watch it print your list.

Categories