I'm trying to create a dynamic form which pre-populates the initial attribute of a certain field (title) when another field (checkin_type) is selected from a drop-down menu.
I'm using Django's generic CreateView, and the way I'd like to go about it is by overriding the view's post() method such that if it receives an AJAX request (which is triggered by a jQuery .change() in the aforementioned drop-down menu and contains the id of the selection option), it updates the initial property of the form's title.
Here is the view I've tried so far (in views.py):
from django.views import generic
from .models import CheckIn, CheckInType
class CheckInCreate(generic.CreateView):
model = CheckIn
fields = '__all__'
def post(self, request, *args, **kwargs):
if request.is_ajax():
checkin_type = CheckInType.objects.get(pk=request.POST['id'])
form = self.get_form()
form['title'].initial = checkin_type.title
self.object = CheckIn.objects.create(checkin_type=checkin_type)
return self.render_to_response(self.get_context_data(form=form))
else:
return super().post(request, *args, **kwargs)
Here is how the AJAX request is made in the form's template, called templates/dashboard/checkin_form.html in accordance with Django's naming convention (dashboard is the name of the app):
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie#2/src/js.cookie.min.js"></script>
<script>
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$(".auto-submit").change(function() {
$.post({
url: "{% url 'checkin-create' %}",
data: {id: $(".auto-submit option:selected").val()}
})
});
});
</script>
<form action="" method="post">{% csrf_token %}
{% for field in form %}
<div class="{% if field.name == 'checkin_type' %}auto-submit{% endif %}">
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
<input type="submit" value="Send message" />
</form>
Here are the corresponding models (in models.py):
from django.db import models
class CheckInType(models.Model):
title = models.CharField(blank=True, max_length=255)
description = models.TextField(blank=True)
def __str__(self):
return self.title
class CheckIn(models.Model):
checkin_type = models.ForeignKey(CheckInType, null=True, on_delete=models.CASCADE)
title = models.CharField(blank=True, max_length=255)
description = models.TextField(blank=True)
notes = models.TextField(blank=True)
# Scheduling
requested_date = models.DateField(blank=True, null=True)
completed_date = models.DateField(blank=True, null=True)
The problem is that when I select an option from the drop-down menu for checkin_type, I see no change in the form:
I would have expected the Title field to become pre-populated with '1-week check-in' in this example. Any ideas why this is not working?
I think it will work if you change
form['title'].initial = checkin_type.title
to
form.cleaned_data['title'] = checkin_type.title
Related
I am trying to create an upvote system in my Django site using ajax so it would not refresh when users click the upvote button. In my site's main page with the listview of all the posts, I am able to get the ajax and the upvote request to work, but only for the top post. No matter if I click on an upvote button from another post, it would only register the effect on the top post. Where did I go wrong?
Below is my code.
models.py
User = settings.AUTH_USER_MODEL #this is the user model
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) #many to one relationship where many posts can be tied to one user
content = models.TextField(blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
image = models.ImageField(upload_to='trade_images', blank=True, null=True)
upvotes = models.ManyToManyField(User, blank=True, related_name='upvotes')
total_upvotes = models.IntegerField(default='0')
def get_absolute_url(self):
return reverse('main:post-detail', kwargs={'pk': self.pk}) #returns the url for individual posts
def __str__(self):
return self.content
class Upvote(models.Model):
user = models.ForeignKey(User, related_name='upvoted_user', on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name='upvoted_post', on_delete=models.CASCADE)
def __str__(self):
return str(self.user) + ':' + str(self.post)
views.py
# list of all posts
class post_list_view(ListView):
model = Post
template_name = 'main/home.html'
context_object_name = 'posts' # this is called from the html as 'for post in posts'
ordering = ['-date_posted'] # minus to reverse the date posted, so newer posts show up on top
paginate_by = 5 #sets pagination per page
return context
def upvote(request):
if request.POST.get('action') == 'post':
result = ''
id = int(request.POST.get('postid'))
post = get_object_or_404(Post, id=id) #grabbing the selected post using postid
new_relation = Upvote(user=request.user, post=post) #storing the upvote relation between user and post using Upvote model arguments - 'user' and 'post'
if post.upvotes.filter(id=request.user.id).exists(): #checking if user already has upvote relations with post by filtering user and post
post.upvotes.remove(request.user) #remove upvote relation from post
post.total_upvotes -= 1 #minus 1 total_upvotes from post
result = post.total_upvotes #storing the new total_upvotes into result
Upvote.objects.filter(user=request.user, post=post).delete() #filtering user and post and deleting the upvote table
post.save()
else:
post.upvotes.add(request.user)
post.total_upvotes += 1
result = post.total_upvotes
print('create upvote')
new_relation.save()
post.save()
return JsonResponse({'result': result }) # return the new total_vote count back to html as json
template.html
<div id="upvote-section" class="card-footer">
{% if user.is_authenticated %}
<form action="{% url 'main:upvote-post' %}" method=POST>
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm" id="upvote-btn" name="post_id" value="{{post.id}}">Upvote</button>
<button type="submit" class="btn btn-outline-success btn-sm" id="upvote-btn" name="post_id" value="{{post.id}}">Upvote</button>
<p id="total_upvotes">
{{ post.total_upvotes }}
</p>
</i> Comment
{% if post.user == user %}
Edit
Delete
{% endif %}
</form>
{% else %}
Upvotes ({{ post.total_upvotes }}) Please login to upvote or comment.
{% endif %}
</div>
script/ajax
<script>
$(document).on('click', '#upvote-btn', function (e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '{% url "main:upvote-post" %}',
data: {
postid: $('#upvote-btn').val(),
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'post'
},
success: function (json) {
document.getElementById("total_upvotes").innerHTML = json['result']
},
error: function (xhr, errmsg, err) {
console.log(err)
}
});
})
</script>
The problem lies in your AJAX script. Specifically in your data object,
data: {
postid: $('#upvote-btn').val(),
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'post'
}
Since you're selecting the postid using $('#upvote-btn').val(), you are always sending back the value for the first upvote button.
Selecting anything in your DOM by using it's id, will return the FIRST element that matches the specified id. This is why it's recommended you don't use same ids for more than one element in your DOM.
Now that we know what the problem is, here's ONE possible way to fix it:
You can have the upvote-btn as a class instead of id in your template, so that every upvote button has a class called upvote-btn.
So your script becomes something like:
$(document).on('click', '.upvote-btn', function (e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '{% url "main:upvote-post" %}',
data: {
postid: e.target.value,
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'post'
},
success: function (json) {
document.getElementById("total_upvotes").innerHTML = json['result']
},
error: function (xhr, errmsg, err) {
console.log(err)
}
});
})
Note, the changes are in the first line of the script, we have changed the selector from #upvote-btn to .upvote-btn because we have previously discussed converting upvote-btn to a class instead of an id.
And we have also changed the postid in your AJAX call from $('#upvote-btn').val() to e.target.value
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");
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.
I get this error when trying to see the page now, I'm not sure where I'm passing the "id/employee_number" incorrectly which is causing this to show:
django.urls.exceptions.NoReverseMatch: Reverse for 'ajax_get_employee_name' with no arguments not found. 1 pattern(s) tried: ['operations/ajax\\/get\\-employee\\-name\\/(?P<id>[0-9]+)\\/$']
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)
def __str__(self):
return self.employee_number
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')
employee = Salesman.objects.get(id=employee_number)
employee_name = employee.slsmn_name
return employee_name
urls.py
urlpatterns = [
url(r'enter-exit-area/$', EnterExitArea.as_view(), name='enter_exit_area'),
path('ajax/get-employee-name/<int:id>/', 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" novalidate >
{% csrf_token %}
<div>
<h1 get-employee-name-url="{% url 'operations:ajax_get_employee_name' %}" id='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>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></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')[0].innerHTML = employee_name;
}
});
});
</script>
The url definition for ajax_get_employee_name requires an id parameter:
path('ajax/get-employee-name/<int:id>/', views.get_employee_name, name='ajax_get_employee_name')
But the url call in the html file does not provide an id:
<h1 get-employee-name-url="{% url 'operations:ajax_get_employee_name' %}" id='employee_name'></h1>
Either update the url definition to not require an id, or update the url call to include one.
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.
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)
def __str__(self):
return self.employee_number
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')
employee = Salesman.objects.get(employee_number=employee_number)
employee_name = employee.slsmn_name
return employee_name
urls.py
urlpatterns = [
url(r'enter-exit-area/$', EnterExitArea.as_view(), name='enter_exit_area'),
path('get-employee-name/<int:id>/', views.get_employee_name, name='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" novalidate >
{% csrf_token %}
<div>
<h1 id='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>
<script>
$("#employee_number_field").change(function () {
var employee_number = $(this).val();
var url = 'get-employee-name/' + employee_number;
$.ajax({
url: url,
type:'post',
data: {
'id': employee_number
},
success: function (data) {
employee_name = data;
$('#employee_name')[0].innerHTML = employee_name;
}
});
});
</script>
{% endblock main %}
You should change the type:'get' from the post. You can always check the network tab in your browsers developer tool (press F12) to check what happens exactly when you change the id. (check weather AJAX calls or not.)
I am working on a portfolio project. where I have to categorize the projects based on their Categories. I have given buttons for each categories and I have to display each contents of individual categories on Button Clicks below them.
Eg. I have 2 projects in Design category. When I click Design I should get these two projects below the button.
I tried filtering of contents and successfully displayed them in other page but unable to display them in the same page.
Here is my Code:
Models.py
class PortfolioCategory(models.Model):
projectCategory = models.CharField(max_length=200)
projectSummary = models.CharField(max_length=300)
projectLink = models.CharField(max_length=200)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.projectCategory
class Portfolio(models.Model):
projectName = models.CharField(max_length=250)
projectLink = models.CharField(max_length=50, default="")
projectImage = models.ImageField(upload_to="img/Portfolio/")
projectContent = models.TextField(default="")
projectCategory = models.ForeignKey(
PortfolioCategory, verbose_name="Category", on_delete=models.CASCADE)
def __str__(self):
return self.projectName
Views.Py
def projectLink(request, projectLink):
category = [c.projectLink for c in PortfolioCategory.objects.all()]
if projectLink in category:
categoryItems = Portfolio.objects.filter(
projectCategory__projectLink=projectLink)
myProjects = categoryItems.all()
return render(
request,
"main/home.html#projectLink",
{
"projects": myProjects,
"navbar": navbar,
}
)
projectContent = Portfolio.objects.get(projectLink=projectLink)
return render(
request,
"main/projectItem.html",
{
"project": projectContent,
"navbar": navbar,
}
)
def homepage(request):
return render(
request=request,
template_name="main/home.html",
context={
"portfolios": Portfolio.objects.all,
"categories": PortfolioCategory.objects.all,
"navbar": navbar,
"socialLinks": socialIcons,
"skillset": skills,
})
home.html
<ul class="nav nav-tabs justify-content-center">
{% for cat in categories %}
<li><a data-toggle="tab" href="#{{cat.projectLink}}">{{cat.projectCategory}}</a></li>
{% endfor %}
</ul>
<div class="tab-content">
{% for cat in categories %}
<div id="{{cat.projectLink}}" class="tab-pane fadeIn">
{{cat.projectCategory}}
//Projects Detail Here
</div>
{% endfor %}
</div>
I have to show the cards of each Projects on button clicks.
If you want to retrieve data without refreshing the page you should use AJAX to call your view asynchronously.
You can use XMLHttpRequest or the preferably the fairly new fetch() which is based on Promises.
You can break the process into to these steps:
1)Create a destination in urls.py that will call a view responsible for retrieving categories from your database.
path('some-destination/', views.get_category, name="get_category")
2)Make an asynchronous call using javascript to your new URL
function callFunc() {
fetch('some-destination/').then(
function(response) {
if (response.status == 200) {
//manipulate and format your data here
//you may need to use `then()` again
}
}).catch(function(err) {
console.log('Fetch Error', err);
});
}
3)create a function in views.py that retrieves your object or data that you need. If required serialize it as JSON and return a response to your AJAX call
Now your button just needs to initiate your AJAX call
<input type="button" onclick="callFunc()" value="get categories">
If you are not familiar with Promises just use XMLHttpRequest or JQuery's AJAX