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

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>

Related

Deleting a value from database based on data coming sent from a form - Django

I am trying to implement newsletter/email subscription for my project.
I created a model which only stores the email and the timestamp and uses SendGrid to send emails to the users who subscribed.
I want to include an unsubscribe button inside the emails I send them. When the user clicks unsubscribe link in the mail it appends the id of the value in db to the url and redirects to cancelsub.html where I am accessing it.
In cancelsub.html I have a form with a submit button which when a user clicks should delete the value from db. It is not working for some reason.
Models.py--
class NewsletterUser(models.Model):
email = models.EmailField(null=True)
date_added = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.email
Views.py--
def NewsLetter(request):
if request.method == 'POST':
email_input = request.POST.get('email_value')
new = NewsletterUser(email=email_input)
new.save()
sendEmail(email_input)
return render(request,"pages/index.html")
def DeleteNewsLetter(request):
if request.method == 'POST':
del_id = request.POST.get('id_value')
NewsletterUser.objects.filter(id= del_id).delete()
return render(request, "newsletter/CancelSubscription.html")
cancelsub.html--
<form id="cancel-subscription-form" method="POST">
{% csrf_token %}
<div class="email-and-btn">
<button class="btn btn-danger mb-2 art-digest-btn" id="cancel-btn" type="submit" value="">Yes, Cancel It</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-1.9.1.js"></script>
<script>
var current_url = window.location.href
var id = current_url.split('?')[1]
id_int = parseInt(id)
$("#cancel-btn").val(id_int);
$(document).on('submit','#cancel-subscription-form',function(e){
e.preventDefault();
$.ajax({
type:'POST',
url:'{% url "DeleteNewsLetter" %}',
data:
{
id_value: parseInt($("#cancel-btn").val()),
csrfmiddlewaretoken:$('input[name=csrfmiddlewaretoken]').val(),
},
success:function(){
}
})
});
</script>
</div>
urls.py--
urlpatterns = [
path('', views.NewsLetter, name='NewsLetter'),
path('CancelSubscription', views.CancelSubscription, name='CancelSubscription'),
path('', views.DeleteNewsLetter, name='DeleteNewsLetter'),
]
When I execute this code out, instead of deleting the value from database, it adds a blank value into the db. I'm confused as to why this is happening.
It'd be really helpful if anyone guide me where I went wrong!.
Thanks in advance!!
I understand that the URL that you send on the email is something like this: http://mywebsite.com/unsubscribe/?9
So you get the "9" with the javascript code. You don't need the javascript if you give a name to your value like this: http://mywebsite.com/unsubscribe/?user_id=9
Now, you can just doing this:
<form id="cancel-subscription-form" method="POST" action="{% url "DeleteNewsLetter" %}">
{% csrf_token %}
<div class="email-and-btn">
<button name="id_value" class="btn btn-danger mb-2 art-digest-btn" id="cancel-btn" type="submit" value="{{request.GET.user_id}}">Yes, Cancel It</button>
</div>
</form>
I think that your problem is in the javascript code, so simplify and deleting it probably your system works.

Django CreateView not updating context data

I have a form in a bootstrap modal, and I want to keep that modal open after submission. I am using CreateView and trying to pass an additional variable to the template in front-end where I could check if the flag is set or not, but the flag is always False even after submission. Here is what I have:
url.py
from django.urls import path
from .views import MescData
urlpatterns = [
path('mesc', MescData.as_view(), name='mesc')
]
views.py
from django.urls import reverse
from django.views.generic.edit import CreateView
from .forms import MescForm
from .models import Mesc
class MescData(CreateView):
model = Mesc
form_class = MescForm
template_name = 'Materials/mesc_data.html'
successful_submit = False # Flag to keep the add entry modal open
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['successful_submit'] = self.successful_submit
return context
def get_success_url(self):
return reverse('mesc')
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
self.successful_submit = True
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form, **kwargs):
# self.successful_submit = True
return super(MescData, self).form_valid(form, **kwargs)
And in Template, I'm checking it like this:
{% if successful_submit %}
<h1>Flag is set</h1>
{% endif %}
Is there a way to pass non-form-related data in CreateView to the template without having to change the url.py (i.e. adding variable data to url path)?
EDIT:
I tried printing the self.successful_submit in form_valid() and post() methods and it is indeed being updated to True, but in the template it is still being passed on as False.
This is the core problem: "I have a form in a bootstrap modal, and I want to keep that modal open after submission."
Simple answer: Use Ajax.
We do now have HTML over the wire as a paradigm gaining popularity, but I'm not familiar enough with it to discuss it. So I will use Ajax to show a solution. This particular solution uses a general ajax template, and the result of a post is a rendered Django template that you can use to just replace the HTML in your already rendered page.
Also, few people like JavaScript, but that is not a good enough reason to avoid using it. It is basically mandatory in any web application you're running. Even HTML over the wire uses a minimal amount of JavaScript to accomplish its goals.
First, write your Ajax view. I'm using django-rest-framework classes for this, and I'm providing an example for filling out a Calibration record. You can use whatever form you want; this is just my recipe for handling modals that you want keep open. In this view, I return a JSON response if the POST was successful. Otherwise, I return a rendered Django template.
from rest_framework.generics import (CreateAPIView, RetrieveAPIView,
UpdateAPIView, get_object_or_404)
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response
class CalibrationCreateAjaxView(CreateAPIView, UpdateAPIView, RetrieveAPIView):
renderer_classes = (TemplateHTMLRenderer,)
template_name = "documents/form/cal.html"
def post(self, request, *args, **kwargs):
context = self.get_context(request)
calibration_form = context['calibration_form']
if calibration_form.is_valid():
calibration_form.save()
request.accepted_renderer = JSONRenderer()
return Response(status=201)
return Response(context, status=400)
def get(self, request, *args, **kwargs):
return Response(self.get_context(request))
#staticmethod
def get_context(request):
pk = request.GET.get("pk")
calibration_entry = get_object_or_404(CalibrationEntry, pk=pk) if pk else None
return {
'calibration_form': CalibrationFormAjax(request.POST or None, instance=calibration_entry)
}
I have my view template as well. It takes advantage of request.is_ajax, which is being deprecated. You'll need to add some middleware to keep using it. Here's my middleware. Add it to your settings file as well.
class IsAjaxMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
"""
request.is_ajax is being removed in Django 4
Since we depend on this in our templates, we are adding this attribute to request
Please review:
https://docs.djangoproject.com/en/4.0/releases/3.1/#id2
"""
def __call__(self, request):
request.is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest'
return self.get_response(request)
general/ajax_modal.html
<!-- {% block modal_id %}{% endblock %}{% block modal_title %}{% endblock %} -->
{% block modal_body %}
{% endblock modal_body %}
general/modal.html
<div class="modal fade" id="{% block modal_id %}{{ modal_id }}{% endblock modal_id %}" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title">
{% block modal_title %}{{ modal_title }}{% endblock modal_title %}
</h4>
</div>
<div class="modal-body">
{% block modal_body %}
{% endblock modal_body %}
</div>
</div>
</div>
</div>
Even though, we're using Crispy Forms, you can get away without using it. I also have a general templatetag library that renders any errors on a form. You can write your own.
documents/form/cal.html
{% extends request.is_ajax|yesno:'general\ajax_modal.html,general\modal.html' %}
{% load crispy_forms_tags general %}
{% block modal_id %}new-cal-modal{% endblock modal_id %}
{% block modal_title %}Enter Calibration Information{% endblock modal_title %}
{% block modal_body %}
<div id="new-cal-form-container">
<form action="{% url 'calibration-create' %}" method="post" id="new-cal-modal-form" autocomplete="off" novalidate>
{% if request.is_ajax %}
{% crispy calibration_form %}
{% form_errors calibration_form %}
{% endif %}
<button type="submit" name="Submit" value="Save" class="btn btn-success button" id="submit">save</button>
</form>
</div>
{% endblock modal_body %}
So now that the Ajax view is all set up, I go back to the main page that will render the modal dialog when the user clicks a button. I have a block called "extraContent" in which I include the template of the modal form.
{% block extraContent %}
{% include 'documents/form/cal.html' %}
{% endblock extraContent %}
And now, the JavaScript, which requires jQuery, that I've added to the template. I guess I made my own jQuery plugin on top of that...
$.fn.modalFormContainer = function(optionsObject) {
//call it on the modal div (the div that contains a modal-dialog, which contains modal-header, modal-body, etc
// we expect there to be a form within that div, and that form should have an action url
// when buttons that trigger the modal are clicked, the form is fetched from that action url and replaced.
// optionsObject has formAfterLoadFunction and ajaxDoneFunction
var options = $.extend({}, $.fn.modalFormContainer.defaults, optionsObject);
var $modalFormContainer = $(this);
// get the buttons that trigger this modal to open
// add a click event so that the form is fetched whenever the buttons are clicked
// if data-pk is an attribute on the button, apply that to the querystring of the
// ajaxURL when fetching the form
var modalID = $modalFormContainer.prop("id");
var modalFormButtonSelector = "[data-target=#" + modalID + "][data-toggle=modal]";
function handleModalButtonClick(event) {
//does the button have an associated pk? if so add the pk to the querystring of the ajax url
// this is wrapped in a form so that it gets replaced by the ajax response.
var $button = $(this);
if (!$button.hasClass("disabled") && !$button.prop("disabled")) { //only do it if the button is "enabled"
var $placeholder = $("<form><h1>loading...</h1></form>");
var $modalForm = $modalFormContainer.find("form");
var ajaxURL = $modalForm.prop("action");
$modalForm.replaceWith($placeholder);
var pk = $button.data().pk;
if (pk) {
if (ajaxURL.indexOf("?") > 0) {
ajaxURL += "&pk=" + pk;
} else {
ajaxURL += "?pk=" + pk;
}
}
//fetch the form and replace $modalFormContainer's contents with it
$.ajax({
type: "GET",
url: ajaxURL
}).done(function(response) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(response);
$modalForm = $modalFormContainer.find("form"); //we would still need to find the form
options.formAfterLoadFunction($modalForm);
});
} else {
return false; //don't trigger the modal.
}
}
//using delegation here so that dynamically added buttons will still have the behavior.
// maybe use something more specific than '.main-panel' to help with performance?
$(".main-panel").on("click", modalFormButtonSelector, handleModalButtonClick);
$modalFormContainer.on("submit", "form", function(event) {
// Stop the browser from submitting the form
event.preventDefault();
var $modalForm = $(event.target);
var ajaxURL = $modalForm.prop("action");
$modalForm.find("[type=submit]").addClass("disabled").prop("disabled", true);
var formData = $modalForm.serialize();
var internal_options = {
url: ajaxURL,
type: "POST",
data: formData
};
// file upload forms have and enctype attribute
// we should not process files to be converted into strings
if ($modalForm.attr("enctype") === "multipart/form-data") {
internal_options.processData = false;
internal_options.contentType = false;
internal_options.cache = false;
formData = new FormData($modalForm.get(0));
internal_options.data = formData;
}
$.ajax(internal_options).done(function(response) {
// blank out the form
$modalForm.find("input:visible, select:visible, textarea:visible").val("");
// remove errors on the form
$modalForm.find(".has-error").removeClass("has-error");
$modalForm.find("[id^=error]").remove();
$modalForm.find(".alert.alert-block.alert-danger").remove();
// hide the modal
$(".modal-header .close").click();
options.ajaxDoneFunction(response);
}).fail(function(data) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(data.responseText);
options.formAfterLoadFunction($modalForm);
});
});
return this;
};
$.fn.modalFormContainer.defaults = {
formAfterLoadFunction: function($form) { return; },
ajaxDoneFunction: function(response) { return; }
};
$("#new-cal-modal").modalFormContainer({
formAfterLoadFunction: function($modalForm) {
$(".datetimeinput").datepicker('destroy');
$(".datetimeinput").datepicker();
},
ajaxDoneFunction: function(event) {
location.reload();
}
});
So upon reviewing this, I've realized that this recipe is far more complicated than I have tricked myself into believing. I sincerely apologize for that. I hope that you can review the code and get an idea of what is happening. There are some edge cases, such as dealing with dates and file uploads, that this recipe handles right now, but you may not actually need them. I should mention that the application that this came from is using Bootstrap 3, so its styling is not updated to the current Bootstrap 5 as of this writing. I should add that the main content of the app has a class of "main-panel" as used in this not-so-generic jQuery plugin.
I'm worried that I've gone and overwhelmed you into maintaining your position of trying to keep using a standard POST request. I guess you could just re-render the template with your POST since it'll be standard practice in your project. You could still get away without using a query string that way.

How to correctly display data stored in an external model without refreshing the page?

I am trying to display a User's name on top of a box where they enter their Employee # in a form, without having to refresh the page.
For example, they enter their # and then after they click/tab onto the next field, it renders their name on top, which comes from the database, so the user knows they've entered the correct info. This name is stored in a separate model, so I try to retrieve it using the "id/number".
I am not too familiar with AJAX but after reading a few similar questions it seems like an AJAX request would be the most appropriate way to achieve this. I tried to make a function get_employee_name that returns the name of the person based on the way I saw another ajax request worked, but I'm not sure how to implement this so it displays after the # is entered.
My page currently loads, but when I check the network using F12, there is never a call to the function/url that searches for the name to display it on the page. I'm not sure where I might be missing the part that connects these two areas of the code, but I have a feeling it has to do with the html tag where the call is supposed to happen, as I am not too familiar with html and Django.
models.py
class EmployeeWorkAreaLog(TimeStampedModel, SoftDeleteModel, models.Model):
employee_number = models.ForeignKey(Salesman, on_delete=models.SET_NULL, help_text="Employee #", null=True, blank=False)
work_area = models.ForeignKey(WorkArea, on_delete=models.SET_NULL, null=True, blank=False)
station_number = models.ForeignKey(StationNumber, on_delete=models.SET_NULL, null=True, blank=True)
This is the model where the name is stored
alldata/models.py
class Salesman(models.Model):
slsmn_name = models.CharField(max_length=25)
id = models.IntegerField(db_column='number', primary_key=True)
I was reading I can add to the "attrs" in the widget an 'onchange' part, but I am not too familiar with how to approach this and tying it to the ajax request from forms and not the html.
forms.py
class WarehouseForm(AppsModelForm):
class Meta:
model = EmployeeWorkAreaLog
widgets = {
'employee_number': ForeignKeyRawIdWidget(EmployeeWorkAreaLog._meta.get_field('employee_number').remote_field, site, attrs={'id':'employee_number_field'}),
}
fields = ('employee_number', 'work_area', 'station_number')
views.py
class EnterExitArea(CreateView):
model = EmployeeWorkAreaLog
template_name = "operations/enter_exit_area.html"
form_class = WarehouseForm
def form_valid(self, form):
# do submission stuff..
def get_employee_name(request):
employee_number = request.GET.get('employee_number')
try:
employee = Salesman.objects.get(id=employee_number)
except Salesman.DoesNotExist:
return JsonResponse({'error': 'Employee not found'}, status=404)
employee_name = employee.slsmn_name
return JsonResponse({'employee_name': employee_name})
urls.py
urlpatterns = [
url(r'enter-exit-area/$', EnterExitArea.as_view(), name='enter_exit_area'),
path('ajax/load-stations/', views.load_stations, name='ajax_load_stations'),
path('get-employee-name/', views.get_employee_name, name='ajax_get_employee_name'),
]
The ajax request I tried to create is at the end of this html. I modified a similar request I found, but it does not actually display anything on the screen, not sure if I'm missing an area where the request is actually never being called, as I am not too familiar with how these types of requests work.
enter_exit_area.html
{% extends "base.html" %}
{% block main %}
<form id="warehouseForm" action="" method="POST" data-stations-url="{% url 'operations:ajax_load_stations' %}" novalidate >
{% csrf_token %}
<div>
<h1 get-employee-name-url="{% url 'operations:ajax_get_employee_name' %}"></h1>
<div>
{{ form.employee_number.help_text }}
{{ form.employee_number }}
</div>
<div>
{{ form.work_area.help_text }}
{{ form.work_area }}
</div>
</div>
<div>
<div>
<button type="submit" name="enter_area" value="Enter">Enter Area</button>
</div>
</div>
</form>
<!-- This is the dependent dropdown script -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$("#id_work_area").change(function () {
var url = $("#warehouseForm").attr("data-stations-url");
var workAreaId = $(this).val();
$.ajax({
url: url,
data: {
'work_area': workAreaId
},
success: function (data) {
$("#my-hidden-div").show(); // show it
$("#id_station_number").html(data);
// Check the length of the options child elements of the select
if ($("#id_station_number option").length == 1) {
$("#id_station_number").parent().hide(); // Hide parent of the select node
} else {
// If any option, ensure the select is shown
$("#id_station_number").parent().show();
}
}
});
});
</script>
<!-- -->
<script>
$("#id_employee_number").change(function () {
var employee_number = $(this).val();
var url = $("#warehouseForm").attr("get-employee-name-url");
$.ajax({
url: url,
type:'GET',
data: {
'id': employee_number
},
success: function (data) {
var employee_name = data['employee_name'];
$('#employee_name')[0].innerHTML = employee_name;
},
error : function (data) {
var error_message = data['error'];
$('#employee_name')[0].innerHTML = error_message;
}
});
});
</script>
{% endblock main %}
What could be causing nothing to render on the page? Is there a call missing in the html portion?
I assume there needs to be a place where the onchange() goes, but I'm not sure where this would be in since the form fields are it's own thing, without tags.
With regards to
var url = $("#warehouseForm").attr("get-employee-name-url");
The id is referring to the form which does not have an attribute name "get-employee-name-url". Your url is actually in here
<h1 get-employee-name-url="{% url 'operations:ajax_get_employee_name' %}"></h1>
So maybe add an id to it like this
<h1 id="url" get-employee-name-url="{% url 'operations:ajax_get_employee_name' %}"></h1>
and then you can access it like you were
var url = $("#url").attr("get-employee-name-url");

Conditional Form Field in Django wizard form

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.

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.

Categories