I built posts "like" functionality in posts list page (where posts of a single user are displayed). Using examples form the book "Django by examples", I did ajax like button bellow every post. But it works incorrectly. In that example like button was made for single post page, and I tried to fit it for posts list page (many post in one page). When push the like button in the database everything is fine - I got plus one like for particular post. But in the front end stange things happen - likes number for all the posts is changing, as if all the posts are connected. And when I do Like and unlike, numbers of likes for all the post are changing to some big values. I think that happens becouse in this case Ajax uses the same class selector (instead of id) for all posts. I'm still not so good at Django and Ajax and can't find a way to make this work correctly. A spent long time of triying to fix this and in googling with no result. Any help appriciated.
Bellow is the code.
The post's model with the likes fields:
class Posts(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True, blank=True)
content = models.TextField()
image = models.ImageField(upload_to="posts/%Y/%m/%d", null=True,blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
users_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='images_liked',
blank=True)
total_likes = models.PositiveIntegerField(db_index=True,
default=0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("post_detail", kwargs={"slug": self.slug})
def slug_generator(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(slug_generator, sender=Posts)
The post_like function in user's view.py:
#ajax_required
#login_required
#require_POST
def post_like(request):
post_id = request.POST.get('id')
action = request.POST.get('action')
if post_id and action:
try:
post = Posts.objects.get(id=post_id)
if action == 'like':
post.users_like.add(request.user)
create_action(request.user, 'likes', post)
else:
post.users_like.remove(request.user)
return JsonResponse({'status':'ok'})
except:
pass
return JsonResponse({'status':'ok'})
The code in urls.py for this:
urlpatterns = [
path('like/', views.post_like, name='like'),
...another urls...
]
The html for like button and counting likes:
{% with total_likes=post.users_like.count users_like=post.users_like.all %}
<div class="image-info">
<div>
<span class="count">
<span class="total">{{ total_likes }}</span>
like{{ total_likes|pluralize }}
</span>
<a href="#" data-id="{{ post.id }}" data-action="{% if request.user in users_like %}un{% endif %}like" class="like button">
{% if request.user not in users_like %}
Like
{% else %}
Unlike
{% endif %}
</a>
</div>
</div>
<div class="image-likes">
</div>
{% endwith %}
And in the same html file, where button is, in the bottom of it is the ajax code for likes functionality:
{% block domready %}
$('a.like').click(function(e){
e.preventDefault();
$.post('/like/',
{
id: $(this).data('id'),
action: $(this).data('action')
},
function(data){
if (data['status'] == 'ok')
{
var previous_action = $('a.like').data('action');
// toggle data-action
$('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like');
// toggle link text
$('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like');
// update total likes
var previous_likes = parseInt($('span.count .total').text());
$('span.count .total').text(previous_action == 'like' ? previous_likes + 2 : previous_likes - 2);
}
}
);
});
{% endblock %}
You'll only want to modify the like button that has been clicked by getting it inside your click function. You could tidy up your code a bit, but to keep it similar you could do something like:
$('a.like').click(function(e){
e.preventDefault();
// Get the clicked button.
const $clickedButton = $( this );
$.post('/like/',
{
id: $clickedButton.data('id'),
action: $clickedButton.data('action')
},
function(data) {
if (data['status'] == 'ok') {
// Update the clicked button only.
var previous_action = $clickedButton.data('action');
// toggle data-action
$clickedButton.data('action', previous_action == 'like' ? 'unlike' : 'like');
// toggle link text
$clickedButton.text(previous_action == 'like' ? 'Unlike' : 'Like');
// update total likes
const $total = $clickedButton.prev('span.count').children('.total');
var previous_likes = parseInt($total.text());
$total.text(previous_action == 'like' ? previous_likes + 2 : previous_likes - 2);
}
}
);
});
I'm not 100% sure why you're adding/subtracting 2 from the total rather than 1, but I don't have enough context.
Related
After searching for several days and trying different options, I decided to finally post the issue and question.
I have a template that has a form and 2 different formsets.
One of the formsets uses an intermediate model with a GenericForeignKey that will reference two other models.
For the formset, I am using an inlineformset and adding a CharField which is used with Select2 to make an ajax call to check the two other models. The value returned by the ajax call will be a json/dict with 3 key/value pairs.
The issue I am having is that when the template is submitted and there are errors, how can I redisplay the value that was entered in the Select2 CharField when the template is presented again?
The value is in self.data and is sent back to the template.
However, everything I've tried so far will not redisplay the select2 field with the value selected previously or the values that were submitted.
The submitted values are returned to the template in a json/dict, key/value, format under form.fieldname.value but I am not sure how I can use that to repopulate the select2 field.
I appreciate any suggestions or links. If there is an alternate way to set this up, I am interested to hear.
Thank you.
UPDATE: 2021-03-18
Here is, hopefully all, the relevant bits from the various files.
models.py
class SiteDomain(models.Model):
website = models.ForeignKey(
WebSite,
on_delete=models.CASCADE,
)
domain_model = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
help_text=(
"The model that the website entry is related to. eg: Domain or SubDomain"
),
)
object_id = models.PositiveIntegerField(
help_text="The ID of the model object the entry is related to."
)
content_object = GenericForeignKey("domain_model", "object_id")
content_object.short_description = "Domain Name:"
views.py
class AddWebsite(View):
def get(self, request, *args, **kwargs):
domain_formset = inlineformset_factory(
WebSite,
SiteDomain,
formset=SiteDomainInlineFormSet,
fields=(),
extra=3,
)
forms.py
class SiteDomainInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.account = kwargs.pop('account', None)
super(SiteDomainInlineFormSet, self).__init__(*args, **kwargs)
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["domain_name"] = forms.CharField(
max_length=255,
widget=forms.Select(),
required=False,
)
template
<script type="text/javascript">
function s2search() {
$('.domain-lookup-ajax').select2({
width: 'style',
ajax: {
url: "{% url 'accounts_ajax:website_domain_lookup' %}",
dataType: 'json',
delay: 250,
data: function (params) {
var query = {
term: params.term,
acct_id: '{{ account.id }}',
}
return query;
},
processResults: function (data, params) {
return {
results: data,
};
},
cache: true
},
placeholder: 'Enter at least 2 characters for search.',
minimumInputLength: 2,
});
}
</script>
<form action="" method="post">
{% csrf_token %}
{{ domain_formset.management_form }}
{{ app_formset.management_form }}
{% for form in domain_formset %}
<div class="domainfieldWrapper" id="row_{{ forloop.counter0 }}">
<select id="id_dform-{{ forloop.counter0 }}-domain_name" class="domain_name domain-lookup-ajax" name="dform-{{ forloop.counter0 }}-domain_name"></select>
<button id="id_dform-{{ forloop.counter0 }}-button" class="button" type="button" onclick="clearSelect('id_dform-{{ forloop.counter0 }}-domain_name')">Clear</button>
</div>
{% endfor %}
</form>
The ajax call will return something like:
{"model_id":"74", "domain_id":"177", "name":"alfa.first-example.com"}
A side note:
I also tested the select2 field in the second formset and it does not get repopulated either when the template is reloaded if there are any form errors. Which I kind of expected since it basically uses the same setup except for the value returned by the ajax call which is for a normal ModelChoiceField.
Using a combination of https://select2.org/programmatic-control/add-select-clear-items#preselecting-options-in-an-remotely-sourced-ajax-select2, js and Django template tags I was able to get something that works for me.
i have a problem and i dont know if im using the good way to solve it , but their is the problem , i have a client list that i show on the client page , by getting all client with a simple
clients = client.objects.all()
so all client can have a lot of sessions, and session have a lot of clients, its like a ManytoMany relation, so what im trynna do its to show the assigned session of client one by one by clicking on a button (the button open a boostrap modal ) , so i tried to send the id of the the chosen client on click and send it into a django view with ajax GET method, and take this ID directly to find all sessions related with this client and return the query to this page , so the ajax is working correctly and i can send the id , but its like the view its not sending anything . so their is my code hope you can help me :
Html (the div im inserting on the forloop client) :
<div class="list-group list-group-me">
<ul class="list-group list-group-horizontal">
<li class="list-group-item">{{ client.name }}</li>
<li class="list-group-item">{{ client.email }}</li>
<li class="list-group-item">{{ client.created_at | date:" M Y" }}</li>
<li class="list-group-item list-group-item-success">Active</li>
<li class="list-group-item">025f55azg5</li>
<li><div class="btn-group">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" id="AddSession"
data-id="{{ client.id }}">
Add
</button>
<button type="button" class="btn btn-primary" data-id="{{ client.id }}" >View</button>
</div>
</li>
</ul>
</div>
the view (tryin the .all() just to see if its working) :
class show_session(View):
def get(self, request , id):
Sclient = session.objects.all()
#Sclient = session.objects.filter(client__id=id)
context = { 'Sclient': Sclient }
return render(request, 'coach/addclient.html', { 'Sclient': Sclient })
the model.py :
class session(models.Model):
name = models.CharField(max_length=80)
client = models.ManyToManyField(client, blank=True)
coach = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
created_at = models.DateTimeField(auto_now_add=True)
detail = models.CharField(max_length=256)
def __str__(self):
return self.name
the main view of the page :
def addclient(request):
form = ClientForm
clients = client.objects.filter(affiliation=request.user.id)
Csession = session.objects.filter(coach=request.user.id)
context = {'form': form ,'clients': clients , 'Csession' : Csession}
form = ClientForm()
if request.method == 'POST':
form = ClientForm(request.POST)
print(form)
if form.is_valid():
print('adding client 1133', form)
name = request.POST.get('name')
email = request.POST.get('email')
new_client = client.objects.create(name= name, email=email , affiliation=request.user)
new_client.save()
return JsonResponse({'client': model_to_dict(new_client)}, status=200)
else:
print('not adding client to the form 1333')
return redirect('addclient')
return render(request, 'coach/addclient.html', context= context)
and the ajax function :
$("#AddSession").click(function(){
var dataID = $(this).data('id');
console.log(dataID)
$.ajax({
url:'addclient/'+ dataID +'/added',
data: {
csrfmiddlewaretoken: csrfToken,
id: dataID
},
type : "get",
success: function(response) {
console.log("hey hey session over here");
}
})
});
i dont know if can use just the main view to get the data and display it , and when i try to acess the url of show_session.view , the query set is working but im still tryin to display it on the addclient url
thanks for your time and help !
so i have fixed the problem , i have just used a POST method to send the id of current user to the view then i used the action data attribute to handle multiple post on my view and use the id given by ajax to have the correct queryset and serialized the queryset into a JSON object so ajax can add the session assiciated with the choosen client on click their is the code :
View :
if request.method == 'POST' and request.POST['action'] == 'session':
print("session response")
id = request.POST.get('id')
print(id)
#Sclient = list(session.objects.filter(coach=request.user.id))
Sclient = session.objects.filter(client__id=id)
SerializedQuery = serializers.serialize('json', Sclient)
return JsonResponse(SerializedQuery, safe=False)
Ajax :
$("#AddSession").click(function(){
var dataID = $(this).data('id');
$.ajax({
url: $(this).data('url'),
data: {
csrfmiddlewaretoken: csrfToken,
id: dataID,
action: 'session',
//dataType:"json"
},
type : "post",
success: function(response) {
console.log("hey hey session over here");
console.log(response)
var obj = jQuery.parseJSON(response)
//console.log(obj[1].fields.name)
var Sarray = [];
var Selector = $("#assigned");
var count = $("#assigned span")
console.log(count.length)
//used to limit the number of element on div on first click
if(count.length < obj.length)
{
for (i = 0 ; i < obj.length ; i++) {
// console.log(obj[i].fields.name)
Sarray.push(obj[i].fields.name)
Selector.append('<span class="badge badge-success">'+ obj[i].fields.name +' x </span>')
}
console.log("yoow",Sarray.length)
}
}
})
});
models.py
class Doctor(models.Model):
name = models.CharField(max_length=255)
class Album(models.Model):
doc = models.ForeignKey(Doctor)
name = models.CharField(max_length=255)
class Video(models.Model):
doc = models.ForeignKey(Doctor)
album = models.ForeignKey(Album)
src = models.CharField(max_length=500)
As show above, in the Video Admin add/change page, there are two dropdown menu for selecting: Album and Doctor.
However, when I choose an Album (e.g. album.id=1 and belongs to doctor.id=5), the Doctor dropdown menu display all Doctors, but I only wanna the only one who related to the Album that I choose.
I tried django-smart-selects, it didn't work.
And, I add autocomplete_fields = ['doc'] under class VideoAdmin(admin.ModelAdmin, ExportCsvMixin), it didn't work too.
Anyone can give me a hand? Thanks VEry veRY much!
I found a way which is overriding the change_form.html of the Admin to add some jQuery code to accomplish it.
/PROJECT_DIR/templates/admin/APP_NAME/MODEL_NAME/change_form.html
{% extends "admin/change_form.html" %}
{% block extrahead %}
{{ block.super }}
<script>
jQuery(document).ready(function ($) {
$( "select#id_aid" ).change(function() {
$.getJSON("/api/getAlbumRelatedDoc/",{id: $(this).val()}, function(j){
for (var i = 0; i < j.length; i++) {
options = '<option value="' + j[i].doc + '">' + j[i].doc__name + '</option>';
}
$("select#id_doc").html(options);
$("span#select2-id_doc-container").html(j[0].doc__name)
});
});
});
</script>
{% endblock %}
/PROJECT_DIR/APP_NAME/views.py
#api_view(['GET', 'POST'])
def get_album_relate_doc(request):
id = request.GET.get('id','')
result = list(Album.objects.filter(id=int(id)).values('doc', 'doc__name'))
return HttpResponse(json.dumps(result), content_type="application/json")
/PROJECT_DIR/APP_NAME/urls.py
path('getAlbumRelatedDoc/', views.get_album_relate_doc),
BTW, I'm using django-jet as the admin replacement.
I have a class Explore, which is used to get all the photos of other classes (like Photo, ProfilePicture, etc). And in the Explore class I have a method (get_rendered_html) which is used to render the objects in the template (explore_photo.html), so that I can pass it on to the main page (index.html).
Update:
Please let me elaborate my question. Suppose there are three Users A, B and C. If A is the current user, I want all the photos of A to be displayed, including other PUBLIC photos of B and C.
I tried this:
{% if object.display == 'P' or user == object.user %}
{% if object.display == 'P' or request.user == object.user %}
If I do it this way, only the public images are displayed, even though the current user is the object's user. And if I do this way:
{% if object.display == 'P' or user != object.user %}
All the image are displayed.
What may be the cause here? Please help me solve this problem. I would really appreciate your suggestion or advice. Thank you.
class Photo(models.Model):
ONLYME = 'O'
FRIENDS = 'F'
PUBLIC = 'P'
CHOICES = (
(ONLYME, "Me"),
(FRIENDS, "Friends"),
(PUBLIC, "Public"),
)
display = models.CharField(default='F', max_length=1, choices=CHOICES, blank=True, null=True)
user = models.ForeignKey(User)
description = models.TextField()
pub_date = models.DateTimeField(auto_now=True, auto_now_add=False)
update = models.DateTimeField(auto_now=False, auto_now_add=True)
image = models.ImageField(upload_to=get_upload_file_name, blank=True)
class Meta:
ordering = ['-pub_date']
verbose_name = 'photo'
verbose_name_plural = 'photos'
def __unicode__(self):
return self.description
class Explore(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
pub_date = models.DateTimeField()
content_object = generic.GenericForeignKey('content_type','object_id')
def get_rendered_html(self):
template_name = 'explore_photo.html'
return mark_safe(render_to_string(template_name, {'object': self.content_object}))
explore_photo.html:
{% if object.display == 'P' or user == object.user %}
<div class="explore_photo">
<img src="media/{{ object.image }}">
<p class="photo_date">{{ object.pub_date|date:"F jS Y, P" }}</p>
<p class="photo_description">{{object.description}}</p>
<p class="photo_user">{{ object.user }}</p>
</div>
{% endif %}
index.html:
<body>
<h1>Explore</h1>
<div id="explore">
{% for photo in photos %}
<p>1</p>
{{ photo.get_rendered_html }}
<hr>
{% endfor %}
</div>
</body>
Update:
def explore(request):
"""
Return all the photos of the Explore Class in the Explore url
"""
photos = Explore.objects.all()
return render(request, 'index.html', {'photos':photos})
Disclaimer: I'm not familiar with GenericForeignKey and I don't understand why you have a user FK in Explore. I'm sure of one thing though: the filtering should be done in the view, not in the template! Use a something like that:
photos = Explore.objects.filter(content_object__display='P',
content_object__user=request.user)
# But as I said, I don't understand this Explore model, so I would probably
# delete it and use Photo directly
photos = Photo.objects.filter(display='P', user=request.user)
Now you can remove the condition in your template and it should work.
EDIT
My bad, you need a OR query, not a AND. Use Q objects in the filter instead.
from django.db.models import Q
photos = Photo.objects.filter(Q(display='P') | Q(user=request.user))
# Idem for the Explore model or any other model where you need a OR or a
# complex query.
You might want to compare User.pk rather than User objects.
Edit :
In this line : {% if object.display == 'P' or user == object.user %} and the others involving user, object.user or request.user, django will not guarantee that the objects will be equal, even if they should be. To compare an object against another, you want to compare some values they hold, comparing primary keys (user.pk) is a convention.
{% if object.display == 'P' or user.pk == object.user.pk %} should work fine. (well, i honestly suppose it will)
I have 2 models in one app and 1 view. I'm currently pulling information from 1 model perfectly fine. However i wish to pull in another model from the same app and output them both to the same page.
The idea of the page is it being a a news hub so it's pulling through different types of news posts (from one model) and a different type of post which is from the other model.
I'm fairly new to Django so go easy! :) Anyway here is the code:
//VIEWS
def news_home(request):
page_context = details(request, path="news-hub", only_context=True)
recent_posts = NewsPost.objects.filter(live=True, case_study=False).order_by("-posted")[:5]
recent_posts_pages = Paginator(recent_posts, 100)
current_page = request.GET.get("page", 1)
this_page = recent_posts_pages.page(current_page)
notes = BriefingNote.objects.filter(live=True).order_by("-posted")
news_categories = NewsCategory.objects.all()
news_context = {
"recent_posts": this_page.object_list,
"news_categories": news_categories,
"pages": recent_posts_pages,
"note": notes,
}
context = dict(page_context)
context.update(news_context)
return render_to_response('news_hub_REDESIGN.html', context, context_instance=RequestContext(request))
//model 1
class BriefingNote(models.Model):
title = models.CharField(max_length=300)
thumbnail = models.ImageField(upload_to='images/briefing_notes', blank=True)
file = models.FileField(upload_to='files/briefing_notes')
live = models.BooleanField(help_text="The post will only show on the frontend if the 'live' box is checked")
categories = models.ManyToManyField("NewsCategory")
# Dates
posted = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u"%s" % self.title
// model 2
class NewsPost(models.Model):
title = models.CharField(max_length=400)
slug = models.SlugField(help_text="This will form the URL of the post")
summary = models.TextField(help_text="To be used on the listings pages. Any formatting here will be ignored on the listings page.")
post = models.TextField(blank=True)
#TO BE REMOVED????
thumbnail = models.ImageField(help_text="To be displayed on listings pages", upload_to="images/news", blank=True)
remove_thumbnail = models.BooleanField()
I'm outputting the content on the front end like so:
{% for post in recent_posts %}
<div class='news_first'>
<img class="news_thumb" src="/media/{% if post.article_type %}{{post.article_type.image}}{% endif %}{% if post.news_type %}{{post.news_type.image}}{% endif%}" alt="">
<h3><a href='{{post.get_absolute_url}}'>{% if post.article_type.title %}{{post.title}}{% endif %} <span>{{post.posted|date:"d/m/y"}}</span></a></h3>
<p class='news_summary'>
{% if post.thumbnail %}<a href='{{post.get_absolute_url}}'><img src='{% thumbnail post.thumbnail 120x100 crop upscale %}' alt='{{post.title}}' class='news_thumbnail'/></a>{% endif %}{{post.summary|striptags}} <a href='{{post.get_absolute_url}}'>Read full story ยป</a>
</p>
<div class='clearboth'></div>
</div>
{% endfor %}
I was thinking perhaps i could output them both within the same forloop however they need to ordered by -posted. So i though this could mess things up.
If you need anymore info please let me know.
Thanks in advance.
I've now solved the problem.
news_hub = list(chain(notes, recent_posts))
news_hub = sorted(
chain(notes, recent_posts),
key = attrgetter('posted'), reverse=True)[:10]