The .html file is
<tbody>
{% for row in results %}
<tr class="{% cycle 'row1' 'row2' %} clickable-row" data-href="{% url "perception:detail" pk=row.object.pk %}">
{% for item in row.cols %}
{{ item }}
{% endfor %}
{% if row_actions_template %}
<td class="row-actions">{% include row_actions_template with object=row.object %}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
The .js file is
$(function(e){
$(".clickable-row").click(function() {
window.location = $(this).data("href");
});
});
The views.py file is
class PerceptionIndexView(StaffRestrictedMixin, FrontendListView):
page_title = _('Perception')
model = Perception
template_name = 'loanwolf/perception/index.html'
pjax_template_name = 'loanwolf/perception/index.pjax.html'
# row_actions_template_name = 'loanwolf/perception/list-actions.inc.html'
url_namespace = 'perception'
def active(self, obj):
if obj.is_active:
return icon(obj.get_icon(), css_class='green-text', tooltip=_('Active'))
else:
return icon(obj.get_icon(), css_class='red-text', tooltip=_('Inactive'))
def notes_count(self, obj):
return obj.notes.count()
notes_count_label = _('Notes')
def get_change_url(self, obj):
return obj.get_absolute_url()
class Meta:
ordering = ('-created', '-modified')
sortable = ('start_date', 'end_date', 'created', 'state', 'modified')
list_filter = ('state', 'is_active', customer, error)
list_display = (
'__unicode__', 'context_menu', 'state', 'start_date', 'end_date', 'current_balance',
'operation_error', 'modified', 'created', 'notes_count', 'active'
)
The part of the models.py file is
#python_2_unicode_compatible
class Perception(xwf_models.WorkflowEnabled, TimeStampedModel):
loan = models.ForeignKey('loans.Loan')
state = xwf_models.StateField(PerceptionWorkflow)
start_date = models.DateField(_('Start date'))
end_date = models.DateField(_('End date'), blank=True, null=True)
current_balance = models.DecimalField(_('Current balance'),
default=0, decimal_places=2, max_digits=11)
operation_error = models.SmallIntegerField(_('Operation error'), default=0)
notes = GenericRelation(Note)
def get_absolute_url(self):
return reverse('perception:detail', kwargs={'pk': self.pk})
def get_icon(self):
if self.is_active:
return 'check_circle'
else:
return 'remove_circle'
def save(self, *args, **kwargs):
rs = super(Perception, self).save(*args, **kwargs)
return rs
#Property
def __str__(self):
return six.text_type(_('Perception #%07d') % self.pk)
#property
def firstname(self):
first_name = self.loan.request.customer.user.first_name
return first_name
#property
def lastname(self):
last_name = self.loan.request.customer.user.last_name
return last_name
#property
def context_menu(self):
tpl = 'perception/context-menu.inc.html'
return mark_safe(render_to_string(tpl, {
'user': self.loan.request.customer.user,
'customer': self.loan.request.customer,
}))
The perception/context-menu.inc.html file looks like
{% load i18n %}
<div class="customer-context-menu closed {% if customer.gender == 0 %}male{% else %}female{% endif %}">
<b class="unselectable">
{{ customer.icon }}
{{ user.get_full_name }}
</b>
<ul>
<li class="tip"></li>
<li>{% trans "Profile" %}</li>
<li>{% trans "Alerts" %}</li>
<li>{% trans "Messaging" %}</li>
<li>{% trans "Requests" %}</li>
<li>{% trans "Documents" %}</li>
<li>{% trans "Logs" %}</li>
<li class="separator"></li>
{% if customer.phone_1 %}
<li class="phone">{{ customer.phone_1 }}</li>
{% endif %}
<li><i class="material-icons">email</i> {{ user.email }}</li>
<li><i class="material-icons">printer</i> {% trans "Print" %}</li>
</ul>
</div>
In the row image, I could click on the button associated to Randy Senger which open a little window with different options on the same page similar to enter image description here. Actually, there is a clickable-row associated to this row. The problematic is located when I clicked on the button. When I click on the button it opened the little window for approximately two seconds, and it render on another page a little as if I was clicking on the row. I think I could use a preventDefault on my .js file so that when I click on the button, it is not affected by clickable-row. Could anyone have an idea how I could modify my .js file to fix this?
P.S. Please let me know if the question is unclear.
What should be the issue here?
$(function(e){
$(".clickable-row").on('click', function() {
window.location = $(this).data("href");
});
});
$(".customer-context-menu").on('click', function(e) {
e.preventDefault();
e.stopPropagation();
});
Unfortunately, I know nothing of Python. But it seems though, that your problem is about firing events for wrapping elements on your DOM. This is related to event bubbling.
Should you bind an event to a parent and a child, clicking on the child will fire both events, unless you specify the context where your event is supposed to execute.
event.stopPropagation should stop the event handling from being notified to the parent.
$('.child').on('click', function(){
e.stopPropagation();
console.log('only the child fires the event');
});
In any case, have you tried to retrieve the event.target of your jQuery event? It should be different according to where you click inside the parent.
<script>
$('.clickable-row').on('click', function(event){
// Every event returns an event object when specified as a callback parameter
console.log(event.target);
if ($(event.target).is('.my-class')){
// Do code for specific clicked element
}
});
</script>
I really hope this helps!
Related
I am stuck on a problem I am having trying to implement a 'Like' button into my django application.
I have the functionality working for the models, and even in the html template the code works if I manually add a like from a user.
It seems like my Ajax may be the issue, but I can't seem to figure out why.
Here is my models.py:
class Post(models.Model):
direct_url = models.URLField(unique=True)
post_url = models.URLField()
post_title = models.CharField(max_length=300)
time_posted = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Like(models.Model):
liker = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
date_created = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
super(Like, self).save(*args, **kwargs)
And here is my views.py
class PostListView(LoginRequiredMixin, generic.ListView):
model = Post
template_name = 'homepage/home.html'
ordering = ['-time_posted']
context_object_name = 'posts'
paginate_by = 6
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = datetime.now()
context['likesbyuser'] = Like.objects.filter(liker=self.request.user)
return context
def likePost(request):
if request.method == 'GET':
post_id = request.GET['post_id']
likedpost = Post.objects.get(pk=post_id) # getting the liked post
if Like.objects.filter(post=likedpost, liker=request.user).exists():
Like.objects.filter(post=likedpost, liker=request.user).delete()
else:
m = Like(post=likedpost, liker=request.user) # creating like object
m.save() # saves into database
return HttpResponse(likedpost.likes.count())
else:
return HttpResponse("Request method is not a GET")
This is part of the template, where I am putting the buttons to use the Ajax which should call the like function:
{% for i in post.likes.all %}
{% if user == i.liker %}
<a class='likebutton' data-catid="{{ post.id }}"><i id='like{{ post.id }}' class="btn fas fa-heart fa-lg post-buttons "></i></a>
{% elif forloop.last %}
<a class='likebutton' data-catid="{{ post.id }}"><i id='like{{ post.id }}' class="btn far fa-heart fa-lg post-buttons "></i></a>
{% endif %}
{% empty %}
<a class='likebutton' data-catid="{{ post.id }}"><i id='like{{ post.id }}' class="btn far fa-heart fa-lg post-buttons "></i></a>
{% endfor %}
{% endfor %}
And here is the Ajax code I am adding to the base.html in script tags.
<script type="text/javascript">
$('main').on('click', '.likebutton', function(){
var catid;
catid = $(this).attr("data-catid");
$.ajax(
{
type:"GET",
url: "/likepost/",
data:{
post_id: catid
},
success: function(data){
if(data==0){
$('.like' + catid).toggle();
$('#like' + catid).toggleClass("far fas");
$('#likecount' + catid).html('');
console.log(data+'if');
}
else if(data==1){
if($('.like' + catid).is(":hidden"))
{
$('.like' + catid).toggle();
}
$('#like' + catid).toggleClass("far fas");
$('#likecount' + catid).html(data + ' like');
console.log(data+'elseif');
}
else{
$('#like' + catid).toggleClass("far fas");
$('#likecount' + catid).html(data + ' likes');
console.log(data+'else');
}
}
})
});
</script>
I have tried adding event.preventDefault(); to the ajax call, but that did not fix my issue.
I found the issue.
I was pointing the Ajax code at "main" as seen in the following snippet:
...
<script type="text/javascript">
$('main').on('click', '.likebutton', function(){
...
My issue was that I forgot to surround the block with the main tag when I ported this project over.
In my base.html I added the following code:
<main role="main" class="container h-100">
{% block content %}
{% endblock content %}
</main>
This fixed the issue.
I have followed a tutorial to utilise AJAX to validate an input field before attempting to submit. I have it working on my django built site; however, I have been using toasts to alert the user to other actions and did not want to get way from this.
$("#id_hub_name").focusout(function (e) {
e.preventDefault();
// get the hubname
var hub_name = $(this).val();
// GET AJAX request
$.ajax({
type: 'GET',
url: "{% url 'validate_hubname' %}",
data: {"hub_name": hub_name},
success: function (response) {
// if not valid user, alert the user
if(!response["valid"]){
alert("You cannot create a hub with same hub name");
var hubName = $("#id_hub_name");
hubName.val("")
hubName.focus()
}
},
error: function (response) {
console.log(response)
}
})
})
This is my current JS, I want to change the alert function to use toasts instead.
In my base.html I use the following to listen for toasts and create them.
{% if messages %}
<div class="message-container">
{% for message in messages %}
{% with message.level as level %}
{% if level == 40 %}
{% include 'includes/toasts/toast_error.html' %}
{% elif level == 30 %}
{% include 'includes/toasts/toast_warning.html' %}
{% elif level == 25 %}
{% include 'includes/toasts/toast_success.html' %}
{% else %}
{% include 'includes/toasts/toast_info.html' %}
{% endif %}
{% endwith %}
{% endfor %}
</div>
{% endif %}
Thanks in advance
You just need to append the html element for the toast.
First of all, loose the {% if messages %}, make sure <div class="message-container"> is always present in your template. If there are no messages, it will just be an empty div.You also need to add the templates for the toasts in js as well, so that you can just append the toasts from JS.
Now you can just append the template for the toast after your ajax response.
something like:
function show_alert_from_js(alert_type, text) {
msg = `<<your template here>>${text}`; // to render the text message to show.
msg_html = $.parseHTML(msg);
$('.message-container').append(msg_html);
}
$.ajax({
type: 'GET',
...
success: function (response) {
if(!response["valid"]){
show_alert_from_js("error", "You cannot create a hub with same hub name")
...
})
Hello Awesome People!
So many questions on StackOverflow are about "How to refresh dom via jquery (from the same view/url)" It's not what I'm looking for.
With a website that large of its parts are running with ajax, I wonder how to refresh a part of the HTML DOM when querying a foreign django view.
Let me be clearer with some examples:
I have that view that sends all_users to template
def usersList(request):
all_users = User.objects.all()
return render(request,'administration/users-list.html',{'all_users':all_users})
In the template I loop through all_users... The 2nd <span> reflects the activation state of the user
{% for u in all_users %}
<span>{{forloop.counter}}.- {{u.name}} <span>
<span id='user_{{u.id}}_state'>
<button data-id='{{u.id}}' type='button' class='css-btn btn-circle'>
{% if u.is_activate %} Active{% else %}Inactive{% endif %}
</button>
<span>
{% endfor %}
With jquery, I send a request to a specific view responsible only to activate or deactivate the account of the user. We can activate/deactivate user in many parts of the website, that's why I do so in a different view.
Here's the view:
def deactivateUser(request):
user = request.user
if user.has_perm('is_admin') and request.is_ajax() and request.method == 'POST':
id_user = request.POST.get('id')
targeted_user = get_object_or_deny(User,id=id_user)
# get_object_or_deny is my own function
it will get the object or raise PermissionDenied otherwise
if targeted_user.is_activate:
targeted_user.is_activate = False
state = 'deactivated'
else:
targeted_user.is_activate = True
state = 'activated'
targeted_user.date_update_activation = NOW() # own function
targeted_user.save()
return JsonResponse({'done':True,'msg':'User successfully %s' %s state})
# Here we return a JsonResponse
raise PermissionDenied
So now, how can I refresh the Dom with following jquery stuff to get the current state of each user
$(document).on('click','.btn-circle',function(){
var id = $(this).data("id");
$.ajax({
url:'/u/de-activate/?ref={{ request.path }}',
type:'post',
data:{
csrfmiddlewaretoken:"{{ csrf_token }}",
id:id,
},
success:function(response){
$("#user_"+id+"_state").replaceWith($("#user_"+id+"_state",response));
if(response.created) alert(response.msg);
},
error:function(){
alert("An error has occured, try again later");
}
});
});
Note that all_users is required to loop through. deactivateUser() return a Json response, even though it doesn't returned it, it will not matter.
You can send http response, not json.
First, just move your html that want to change. in this situation,
{% for u in all_users %}
<div id="user-part">
<span>{{forloop.counter}}.- {{u.name}} <span>
<span id='user_{{u.id}}_state'>
<button data-id='{{u.id}}' type='button' class='css-btn btn-circle'>
{% if u.is_activate %} Active{% else %}Inactive{% endif %}
</button>
<span>
</div>
{% endfor %}
Then save it i.e. user_part.html
Second, make your view return HttpResponse with that html, and context. You can use either HttpResponse or render_to_response. I recommend render_to_response.
context = {
'all_users': all_users,
}
return render_to_response(
'path_to/user_part.html',
context=context,
)
Third, you just change script for replacing your html.
success: function(response){
$('#user-part').html(response);
prevent();
}
I'm trying to make a Javascript function involving a django-tables2 table featuring model objects that will show relevant information when clicked. Here is the relevant code:
models.py
class Student(models.Model):
student_id = models.CharField(max_length=128, unique=True, null=True, blank=True)
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
ssn = USSocialSecurityNumberField(null=False)
gender = models.CharField(max_length=128, choices=GENDER_CHOICES)
country = CountryField(default='US', blank=True)
primary_phone = models.CharField(max_length=128)
email = models.EmailField(max_length=254, validators=[validate_email])
background = models.CharField(max_length=128, choices=BACKGROUND_CHOICES)
location = models.CharField(max_length=128, choices=LOCATION_CHOICES, default='south_plainfield')
created_at = models.DateTimeField(null=True, auto_now_add=True)
tables.py
class StudentListTable(tables.Table):
name = tables.TemplateColumn('''{{ record.first_name }} {{ record.last_name }}''', verbose_name=u'Name')
manage = tables.TemplateColumn('''Update / Delete''')
assign = tables.TemplateColumn('''Courses / Employment / Counselor / Show''')
class Meta:
model = Student
fields = ('student_id', 'name', 'gender', 'ssn', 'email', 'primary_phone', 'created_at', 'manage', 'assign')
row_attrs = {
'id': lambda record: record.pk
}
attrs = {'class': 'table table-hover', 'id': 'student_list'}
views.py
def all_My_Student(request):
student_list = Student.objects.order_by('-created_at')
table = StudentListTable(student_list)
RequestConfig(request).configure(table)
return render(request, 'students/all_my_student.html', {'table': table, 'student_list': student_list})
all_my_student.html
{% block main_content %}
<div class="box box-primary" id="student_list_table">
{% if table %}
{% render_table table %}
{% endif %}
</div>
{% for student in student_list %}
<div class="detailed" id="{{ student.id }}">
{{ student.first_name }} {{ student.last_name }}
</div>
{% endfor %}
{% endblock %}
{% block custom_javascript %}
<script>
$(document).ready(function()
{
$('.detailed').hide();
}
);
</script>
{% endblock %}
What I want to do is make a Javascript function that's basically like this: As you can see, the "detailed" class divs are hidden from the start. When you click on a row in the model object table, the "detailed" class div with an id that matches that of the row (or basically corresponds with the same model object in both the table loop and the second loop) will show, and the rest will still be hidden. I hope that it's clear.
As a sidenote, if the queryset used in the table is the same as student_list there is no need to pass student_list separately to the template context, you can just use table.data.
I would hide the <div class="detailed"> blocks using CSS, and not using JavaScript, as if you do use JavaScript, they might be visible for a slight moment when the page loads.
You should try to avoid adding id attributes to too much html elements. In your example, you add the student id as an id attribute to both the table <tr>'s and the <div class="detailed"> elements. The HTML spec requires ID's to be unique in the whole document, so if you want to use them, make sure they are unique.
It's better to use data attributes. In the example below, I added the attribute data-student-id to both the table row and the <div class="detailed"> element. You can now select div[data-student-id="12"] to get the details div element for student with id = 12:
# table.py
class StudentListTable(tables.Table):
name = tables.TemplateColumn('''...''', verbose_name=u'Name')
manage = tables.TemplateColumn('''....''')
class Meta:
model = Student
fields = ('student_id', 'name', 'gender', 'ssn', 'email', 'primary_phone', 'created_at', 'manage', 'assign')
row_attrs = {
'data-student-id': lambda record: record.pk
}
attrs = {'class': 'table table-hover', 'id': 'student_list'}
{# django template #}
<style>
.detailed {
display: none;
}
</style>
{% for student in table.data %}
<div class="detailed" data-student-id="{{ student.id }}">
{{ student.first_name }} {{ student.last_name }}
</div>
{% endfor %}
{% block custom_javascript %}
<script>
$(document).ready(function() {
$('#student_list tr').on('click', function () {
var student_id = $(this).data('student-id');
$('div[data-student-id="' + student_id + '"]').show();
});
});
</script>
{% endblock %}
Alternatively, you could add the required data to data- attributes of the table and create the details view in JavaScript. That maybe would make the generated HTML a bit smaller.
I have a model form in which I need to store an unknown number of helpers alongside a thing. The names can be serialised upon save, and that's not a problem. It's being able to clean and validate them upon submission.
The form looks like;
class ThingForm(forms.ModelForm):
"""
Form for the Thing
"""
owner = forms.CharField()
helpers = forms.CharField()
class Meta:
model = Thing
def save(self, *args, **kwargs):
"""
Serialize helpers to JSON.
"""
...
And the model is using a JSONField to store the serialised helpers.
class Thing(models.Model):
owner = models.CharField()
helpers = JSONField()
I have JavaScript adding as many helpers as required with the same input name:
<input name="helpers" value="Fred" />
<input name="helpers" value="Joe" />
Which is returning a tuple of the helpers. The problem is that the if the form isn't valid - those names will be lost and the cleaning isn't working.
My first thought was to add to the form's constructor:
def __init__(self, *args, **kwargs):
super(ThingForm, self).__init__(*args, **kwargs)
try:
helpers = args[0].pop('helpers')
for name in helpers:
# Add a charfield, or something?
except:
pass
But I'm not really getting anywhere...
Thanks to AdamKG for the answer to this. You can just use the list in your view again:
View:
if request.method == 'POST':
helpers = request.POST.getlist('helpers')
form = ThingForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/saved/')
else:
helpers = None
form = ThingForm()
return render_to_response('my_template.html',
{'helpers': helpers, 'form': form},
context_instance=RequestContext(request))
Template:
{% for field in form %}
{% if field.name == 'helpers' %}
{% for name in helpers %}
<input name="helpers" value="{{ name }}" />
{% endfor %}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
I think all you need to do is do something like this in your template:
{% if form.data %}{# eg, was invalid and this is a re-render w/ errors #}
{% for helper in form.data.helpers %}
<input type="hidden" name="helpers" value="{{ helper }}">
{% endfor %}
{% endif %}
Note that this will break if you start passing a prefix kwarg to your form - but so would your original code, fixing that is a separate issue :)
We had a similar problem and found no ready solution. So we did ours (https://github.com/vialink/vlk-django-jsonfield).