diff --git a/.gitignore b/.gitignore index ce11548..8f37340 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,5 @@ tmp/restart.txt # Django migrations/ -media/img/ \ No newline at end of file +media/img/ +db.sqlite3.sqbpro diff --git a/khaikang/models.py b/khaikang/models.py index f6aa195..b86b394 100644 --- a/khaikang/models.py +++ b/khaikang/models.py @@ -63,6 +63,8 @@ class Following(models.Model): ) + + class Post(models.Model): text = models.TextField() poster = models.ForeignKey(User, on_delete=models.CASCADE) @@ -81,3 +83,24 @@ class Post(models.Model): ) + +class Repost(models.Model): + reposter = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reposter") + reposted_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="reposted_post") + repost_time = models.DateTimeField(default=timezone.now()) + is_read = models.BooleanField(default=False) + +class Reply(models.Model): + replyer_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="replyer_post") + replyee_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="replyee_post") + is_read = models.BooleanField(default=False) + +class Fav(models.Model): + favouriter = models.ForeignKey(User, on_delete=models.CASCADE, related_name="favouriter") + favourited_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="favourited_post") + favourited_time = models.DateTimeField(default=timezone.now()) + is_read = models.BooleanField(default=False) + + + + diff --git a/khaikang/urls.py b/khaikang/urls.py index 634dc82..f85a9f3 100644 --- a/khaikang/urls.py +++ b/khaikang/urls.py @@ -32,7 +32,10 @@ urlpatterns = [ path('api/get_latest_posts', views.api_get_latest_posts), path('api/get_previous_posts', views.api_get_previous_posts), path('user/', views.user_timeline), - path('api/follow_request//', views.follow_request) + path('api/follow_request//', views.api_follow_request), + path('api/repost_request/', views.api_repost_request), + path('api/fav_request/', views.api_fav_request), + ] if settings.DEBUG: diff --git a/khaikang/views.py b/khaikang/views.py index 7f3ecda..4193b35 100644 --- a/khaikang/views.py +++ b/khaikang/views.py @@ -1,7 +1,7 @@ import json from django.http import HttpResponse, HttpResponseNotAllowed from django.template import loader -from .models import User, Post, Following +from .models import User, Post, Following, Fav, Repost from django.utils import timezone from django.contrib.auth.forms import UserCreationForm from django.shortcuts import redirect @@ -37,12 +37,21 @@ def api_get_previous_posts(request): older_posts = Post.objects.filter(post_time__lt=oldest_datetime_object).filter(poster=poster).order_by('-id')[:20] else: pass - older_posts_list = [{"id" : o.pk, - "post_time" : datetime.strftime(o.post_time, "%Y-%m-%d %H:%M:%S.%f%z"), - "poster_username" : o.poster.username, - "poster_shown_name":o.poster.shown_name , - "text": o.text} - for o in older_posts] + + is_faved = [Fav.objects.filter(favourited_post=p).filter(favouriter=request.user).__len__() for p in older_posts] + is_reposted = [Repost.objects.filter(reposted_post=p).filter(reposter=request.user).__len__() for p in older_posts] + older_posts_info = zip(older_posts, is_faved, is_reposted) + + older_posts_list = [{"id" : o[0].pk, + "post_time" : datetime.strftime(o[0].post_time, "%Y-%m-%d %H:%M:%S.%f%z"), + "poster_avatar" : o[0].poster.avatar.url, + "poster_username" : o[0].poster.username, + "poster_shown_name":o[0].poster.shown_name , + "text": o[0].text, + "is_faved" : o[1], + "is_reposted" : o[2], + } + for o in older_posts_info] if len(list(older_posts)) > 0: oldest_time = datetime.strftime(list(older_posts)[-1].post_time, "%Y-%m-%d %H:%M:%S.%f%z") else: @@ -72,12 +81,20 @@ def api_get_latest_posts(request): poster = User.objects.get(username=username) newer_posts = Post.objects.filter(post_time__gt=latest_datetime_object).filter(poster=poster).order_by('-id')[:20] - newer_posts_list = [{"id" : o.pk, - "post_time" : datetime.strftime(o.post_time, "%Y-%m-%d %H:%M:%S.%f%z"), - "poster_username" : o.poster.username, - "poster_shown_name":o.poster.shown_name , - "text": o.text} - for o in newer_posts] + is_faved = [Fav.objects.filter(favourited_post=p).filter(favouriter=request.user).__len__() for p in newer_posts] + is_reposted = [Repost.objects.filter(reposted_post=p).filter(reposter=request.user).__len__() for p in newer_posts] + newer_posts_infp = zip(newer_posts, is_faved, is_reposted) + + newer_posts_list = [{"id" : o[0].pk, + "post_time" : datetime.strftime(o[0].post_time, "%Y-%m-%d %H:%M:%S.%f%z"), + "poster_avatar" : o[0].poster.avatar.url, + "poster_username" : o[0].poster.username, + "poster_shown_name":o[0].poster.shown_name , + "text": o[0].text, + "is_faved" : o[1], + "is_reposted" : o[2], + } + for o in newer_posts_infp] return JsonResponse({'newer_posts':newer_posts_list}) else: return HttpResponseNotAllowed('POST') @@ -273,7 +290,39 @@ def user_config(request): def is_custom_avatar_path(old_image_path): return os.path.exists(old_image_path) and (old_image_path != os.path.abspath("./media/static/default_avatar.png")) -def follow_request(request, request_value, dest_username): +def api_fav_request(request, post_id): + if request.method != "POST": + return HttpResponseNotAllowed('POST') + current_user = User.objects.get(id=request.user.id) + post = Post.objects.get(id=post_id) + + if Fav.objects.filter(favouriter = current_user, favourited_post=post).__len__() > 0: + existed_fav = Fav.objects.filter(favouriter = current_user, favourited_post=post)[0] + existed_fav.delete() + return JsonResponse({'status': "deleted"}) + else: + added_fav = Fav(favouriter = current_user, favourited_post=post) + added_fav.save() + return JsonResponse({'status': "faved"}) + +def api_repost_request(request, post_id): + if request.method != "POST": + return HttpResponseNotAllowed('POST') + current_user = User.objects.get(id=request.user.id) + post = Post.objects.get(id=post_id) + + if Repost.objects.filter(reposter = current_user, reposted_post=post).__len__() > 0: + existed_repost = Repost.objects.filter(reposter = current_user, reposted_post=post)[0] + existed_repost.delete() + return JsonResponse({'status': "deleted"}) + else: + added_repost = Repost(reposter = current_user, reposted_post=post) + added_repost.save() + return JsonResponse({'status': "reposted"}) + + + +def api_follow_request(request, request_value, dest_username): if request.method != "POST": return HttpResponseNotAllowed('POST') else: @@ -317,6 +366,10 @@ def user_timeline(request, username): viewed_timeline_list = Post.objects.filter(poster = viewed_user.id).order_by('-id')[:20] else: viewed_timeline_list = Post.objects.filter(poster = viewed_user.id).filter(privilage = 'public').order_by('-id')[:20] + + is_faved = [Fav.objects.filter(favourited_post=p).filter(favouriter=request.user).__len__() for p in viewed_timeline_list] + is_reposted = [Repost.objects.filter(reposted_post=p).filter(reposter=request.user).__len__() for p in viewed_timeline_list] + viewed_timeline_info = zip(viewed_timeline_list, is_faved, is_reposted) latest_received_time = timezone.now() if len(viewed_timeline_list) > 0: @@ -334,7 +387,7 @@ def user_timeline(request, username): 'user_follower_number': user_follower_number, 'latest_received_time' : latest_received_time, 'oldest_received_time' : oldest_received_time, - 'viewed_timeline_list': viewed_timeline_list, + 'viewed_timeline_info': viewed_timeline_info, } return HttpResponse(template.render(context, request)) @@ -346,6 +399,10 @@ def home(request): all_i_follow = Following.objects.filter(follower=request.user.id).filter(isapproved="yes") all_i_follow_and_me = User.objects.filter(Q(id__in = all_i_follow) | Q(id=request.user.id)) public_timeline_list = Post.objects.filter(poster__in = all_i_follow_and_me).order_by('-id')[:20] + is_faved = [Fav.objects.filter(favourited_post=p).filter(favouriter=request.user).__len__() for p in public_timeline_list] + is_reposted = [Repost.objects.filter(reposted_post=p).filter(reposter=request.user).__len__() for p in public_timeline_list] + + public_timeline_info = zip(public_timeline_list, is_faved, is_reposted) latest_received_time = timezone.now() @@ -359,6 +416,6 @@ def home(request): context = { 'latest_received_time' : latest_received_time, 'oldest_received_time' : oldest_received_time, - 'public_timeline_list': public_timeline_list, + 'public_timeline_info': public_timeline_info, } return HttpResponse(template.render(context, request)) \ No newline at end of file diff --git a/static/generic.css b/static/generic.css index 4ea3221..16ba0fc 100644 --- a/static/generic.css +++ b/static/generic.css @@ -1,9 +1,40 @@ -div.avatar-preview{ +.post{ + display:flex; +} +.post-content{ + flex-direction: row; +} + +.timeline-avatar-img{ + flex-direction: row; + width:70px; + height:70px; } #avatar-img{ - width: 200px; - height: 200px; + width: 150px; + height: 150px; object-fit: cover; +} + +.reply{ + filter: grayscale(1); +} + +.reply:hover{ + filter: grayscale(0); +} + + +.unchecked{ + filter: grayscale(1); +} + +.repost:hover{ + filter: grayscale(0); +} + +.fav:hover{ + filter: grayscale(0); } \ No newline at end of file diff --git a/static/timeline.js b/static/timeline.js index da9036e..9e1a610 100644 --- a/static/timeline.js +++ b/static/timeline.js @@ -137,8 +137,10 @@ body: JSON.stringify({ return item['older_posts'];}) .then(posts => {if (posts.length == 0) {$('previous_post_loader').style.display = 'none';} - else{posts.forEach(post => appendPostToTheEnd(post)); - $('oldest_time').innerHTML = new_oldest_time;}}) + else{posts.forEach(post => + appendPostToTheEnd(post)); + updateClickedIconEvent(); + $('oldest_time').innerHTML = new_oldest_time;}}) } @@ -185,6 +187,7 @@ body: JSON.stringify({ $('new_post_notifier').style.display = "none"; var nowTime = new Date(); var nowTimeString = nowTime.toISOString(); +updateClickedIconEvent(); $("latest_time").innerHTML = nowTimeString.substring(0,10) + " " + nowTimeString.substring(11,23) + "000+0000"; }); } @@ -194,12 +197,16 @@ var new_div = document.createElement("div"); new_div.id = `post-${post.id}`; new_div.className = "post"; post_text_replaced = post.text.replace(/(\r\n|\n\r|\r|\n)/g, "
" ); -new_div.innerHTML = `${post.poster_shown_name} +new_div.innerHTML = ` + +
+${post.poster_shown_name} at ${post.post_time}
${post_text_replaced}
-↩ī¸ -- 🔁 -- ⭐`; +↩ī¸ +- 🔁 +- ⭐ +
`; new_div_timestamp = new_div.getElementsByClassName('post-time')[0]; @@ -220,11 +227,66 @@ public_tl.insertBefore(new_div, $('latest_time').nextSibling); $("new_post_notifier").addEventListener('click', getLatestPosts); $("previous_post_loader").addEventListener('click', getPreviousPosts); +async function makeRepostRequest(event){ + var postId = event.target.getAttribute('data-value'); + await fetch(`/api/repost_request/${postId}`, { + method: 'POST', + headers: { + "X-CSRFToken": csrf_token, + 'Content-Type': 'application/json', + }, + body : "" + }).then(response => response.json()) + .then(item => item['status']) + .then(status =>{ + if (status == "reposted"){ + event.target.classList.remove("unchecked"); + }else if (status == "deleted"){ + event.target.classList.add("unchecked"); + } + }); +} + + +async function makeFavRequest(event){ + var postId = event.target.getAttribute('data-value'); + await fetch(`/api/fav_request/${postId}`, { + method: 'POST', + headers: { + "X-CSRFToken": csrf_token, + 'Content-Type': 'application/json', + }, + body : "" + }).then(response => response.json()) + .then(item => item['status']) + .then(status =>{ + if (status == "faved"){ + event.target.classList.remove("unchecked"); + }else if (status == "deleted"){ + event.target.classList.add("unchecked"); + } + }); +} + +function updateClickedIconEvent(){ + var reposted_items = document.getElementsByClassName('repost'); + for (var i = 0; i < reposted_items.length; i++) { + reposted_items[i].addEventListener('click', makeRepostRequest); + } + var reposted_items = document.getElementsByClassName('fav'); + for (var i = 0; i < reposted_items.length; i++) { + reposted_items[i].addEventListener('click', makeFavRequest); + } +} + +updateClickedIconEvent() if (requestNote["type"] == "home"){ $("submit_post").addEventListener('click', make_req); adjust_post_text(); } if (requestNote["type"] == "user"){ - $("follow-user-button").addEventListener('click', make_follow_req); + if ($("follow-user-button")){ + $("follow-user-button").addEventListener('click', make_follow_req); + } } \ No newline at end of file diff --git a/templates/base_generic.html b/templates/base_generic.html index 699e11b..ba4b977 100644 --- a/templates/base_generic.html +++ b/templates/base_generic.html @@ -6,7 +6,7 @@ {% endblock %} {% block title %}Khaikang - + {% endblock %} diff --git a/templates/index.html b/templates/index.html index fc8dca0..b554d21 100644 --- a/templates/index.html +++ b/templates/index.html @@ -36,14 +36,18 @@
{{latest_received_time|date:"Y-m-d H:i:s.u"}}+0000
- {% for public_post in public_timeline_list %} + {% for public_post in public_timeline_info %} -
{{public_post.poster.shown_name}} - at {{public_post.post_time|date:"Y-m-d H:i:s.u"}}+0000
- {{public_post.text|linebreaksbr}}
- ↩ī¸ - - 🔁 - - ⭐ +
+ +
+ {{public_post.0.poster.shown_name}} + at {{public_post.0.post_time|date:"Y-m-d H:i:s.u"}}+0000
+ {{public_post.0.text|linebreaksbr}}
+ ↩ī¸ + - 🔁 + - ⭐ +
{% endfor %} @@ -52,7 +56,7 @@
- {% endblock %} \ No newline at end of file diff --git a/templates/user_timeline.html b/templates/user_timeline.html index 34b2a16..1653a6c 100644 --- a/templates/user_timeline.html +++ b/templates/user_timeline.html @@ -27,14 +27,16 @@ Following: {{user_following_number}} Follower:
{{latest_received_time|date:"Y-m-d H:i:s.u"}}+0000
- {% for post in viewed_timeline_list %} + {% for post in viewed_timeline_info %} -
{{post.poster.shown_name}} - at {{post.post_time|date:"Y-m-d H:i:s.u"}}+0000
- {{post.text|linebreaksbr}}
- ↩ī¸ - - 🔁 - - ⭐ +
+ +
{{post.0.poster.shown_name}} + at {{post.0.post_time|date:"Y-m-d H:i:s.u"}}+0000
+ {{post.0.text|linebreaksbr}}
+ ↩ī¸ + - 🔁 + - ⭐
{% endfor %} @@ -43,7 +45,7 @@ Following: {{user_following_number}} Follower: -