Infinite scrolling and comment migration

This commit is contained in:
cuom1999 2023-02-20 17:15:13 -06:00
parent 4b558bd656
commit 799ff5f8f8
33 changed files with 639 additions and 556 deletions

View file

@ -7,8 +7,8 @@ from django.utils.translation import ugettext as _
from django.views.generic import ListView
from judge.comments import CommentedDetailView
from judge.views.pagevote import PageVoteDetailView, PageVoteListView
from judge.views.bookmark import BookMarkDetailView, BookMarkListView
from judge.views.pagevote import PageVoteDetailView
from judge.views.bookmark import BookMarkDetailView
from judge.models import (
BlogPost,
Comment,
@ -26,28 +26,16 @@ from judge.utils.diggpaginator import DiggPaginator
from judge.utils.problems import user_completed_ids
from judge.utils.tickets import filter_visible_tickets
from judge.utils.views import TitleMixin
from judge.views.feed import FeedView
# General view for all content list on home feed
class FeedView(ListView):
class HomeFeedView(FeedView):
template_name = "blog/list.html"
title = None
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
return DiggPaginator(
queryset,
per_page,
body=6,
padding=2,
orphans=orphans,
allow_empty_first_page=allow_empty_first_page,
**kwargs
)
def get_context_data(self, **kwargs):
context = super(FeedView, self).get_context_data(**kwargs)
context = super(HomeFeedView, self).get_context_data(**kwargs)
context["has_clarifications"] = False
if self.request.user.is_authenticated:
participation = self.request.profile.current_contest
@ -60,17 +48,7 @@ class FeedView(ListView):
if participation.contest.is_editable_by(self.request.user):
context["can_edit_contest"] = True
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
context["user_count"] = lazy(Profile.objects.count, int, int)
context["problem_count"] = lazy(
Problem.objects.filter(is_public=True).count, int, int
)
context["submission_count"] = lazy(Submission.objects.count, int, int)
context["language_count"] = lazy(Language.objects.count, int, int)
now = timezone.now()
visible_contests = (
Contest.get_visible_contests(self.request.user, show_own_contests_only=True)
.filter(is_visible=True)
@ -102,10 +80,12 @@ class FeedView(ListView):
return context
class PostList(FeedView, PageVoteListView, BookMarkListView):
class PostList(HomeFeedView):
model = BlogPost
paginate_by = 10
paginate_by = 4
context_object_name = "posts"
feed_content_template_name = "blog/content.html"
url_name = "blog_post_list"
def get_queryset(self):
queryset = (
@ -121,13 +101,23 @@ class PostList(FeedView, PageVoteListView, BookMarkListView):
queryset = queryset.filter(filter)
return queryset
def get_feed_context(self, object_list):
post_comment_counts = {
int(page[2:]): count
for page, count in Comment.objects.filter(
page__in=["b:%d" % post.id for post in object_list], hidden=False
)
.values_list("page")
.annotate(count=Count("page"))
.order_by()
}
return {"post_comment_counts": post_comment_counts}
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data(**kwargs)
context["title"] = (
self.title or _("Page %d of Posts") % context["page_obj"].number
)
context["first_page_href"] = reverse("home")
context["page_prefix"] = reverse("blog_post_list")
context["page_type"] = "blog"
context["post_comment_counts"] = {
int(page[2:]): count
@ -138,18 +128,17 @@ class PostList(FeedView, PageVoteListView, BookMarkListView):
.annotate(count=Count("page"))
.order_by()
}
context = self.add_pagevote_context_data(context)
context = self.add_bookmark_context_data(context)
return context
def get_comment_page(self, post):
return "b:%s" % post.id
class TicketFeed(FeedView):
class TicketFeed(HomeFeedView):
model = Ticket
context_object_name = "tickets"
paginate_by = 30
paginate_by = 8
feed_content_template_name = "ticket/feed.html"
def get_queryset(self, is_own=True):
profile = self.request.profile
@ -181,30 +170,25 @@ class TicketFeed(FeedView):
def get_context_data(self, **kwargs):
context = super(TicketFeed, self).get_context_data(**kwargs)
context["page_type"] = "ticket"
context["first_page_href"] = self.request.path
context["page_prefix"] = "?page="
context["title"] = _("Ticket feed")
return context
class CommentFeed(FeedView):
class CommentFeed(HomeFeedView):
model = Comment
context_object_name = "comments"
paginate_by = 50
paginate_by = 8
feed_content_template_name = "comments/feed.html"
def get_queryset(self):
return Comment.most_recent(
self.request.user, 1000, organization=self.request.organization
self.request.user, 100, organization=self.request.organization
)
def get_context_data(self, **kwargs):
context = super(CommentFeed, self).get_context_data(**kwargs)
context["page_type"] = "comment"
context["first_page_href"] = self.request.path
context["page_prefix"] = "?page="
context["title"] = _("Comment feed")
context["page_type"] = "comment"
return context

View file

@ -74,13 +74,3 @@ class BookMarkDetailView(TemplateResponseMixin, SingleObjectMixin, View):
queryset = BookMark.objects.get_or_create(page=self.get_comment_page())
context["bookmark"] = queryset[0]
return context
class BookMarkListView:
def add_bookmark_context_data(self, context, obj_list="object_list"):
for item in context[obj_list]:
bookmark, _ = BookMark.objects.get_or_create(
page=self.get_comment_page(item)
)
setattr(item, "bookmark", bookmark)
return context

34
judge/views/feed.py Normal file
View file

@ -0,0 +1,34 @@
from django.views.generic import ListView
from django.shortcuts import render
from django.urls import reverse
from judge.utils.infinite_paginator import InfinitePaginationMixin
class FeedView(InfinitePaginationMixin, ListView):
def get_feed_context(selfl, object_list):
return {}
def get(self, request, *args, **kwargs):
only_content = request.GET.get("only_content", None)
if only_content and self.feed_content_template_name:
queryset = self.get_queryset()
paginator, page, object_list, _ = self.paginate_queryset(
queryset, self.paginate_by
)
context = {
self.context_object_name: object_list,
"has_next_page": page.has_next(),
}
context.update(self.get_feed_context(object_list))
return render(request, self.feed_content_template_name, context)
return super(FeedView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["feed_content_url"] = reverse(self.url_name)
except Exception as e:
context["feed_content_url"] = self.request.path
return context

View file

@ -42,7 +42,6 @@ class NotificationList(ListView):
context["unseen_count"] = self.unseen_cnt
context["title"] = _("Notifications (%d unseen)" % context["unseen_count"])
context["has_notifications"] = self.queryset.exists()
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
return context
def get(self, request, *args, **kwargs):

View file

@ -72,9 +72,7 @@ from judge.utils.problems import user_attempted_ids, user_completed_ids
from judge.views.problem import ProblemList
from judge.views.contests import ContestList
from judge.views.submission import AllSubmissions, SubmissionsListBase
from judge.views.pagevote import PageVoteListView
from judge.views.bookmark import BookMarkListView
from judge.views.feed import FeedView
__all__ = [
"OrganizationList",
@ -194,7 +192,7 @@ class MemberOrganizationMixin(OrganizationMixin):
)
class OrganizationHomeViewContext:
class OrganizationHomeView(OrganizationMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if not hasattr(self, "organization"):
@ -221,28 +219,6 @@ class OrganizationHomeViewContext:
return context
class OrganizationDetailView(
OrganizationMixin, OrganizationHomeViewContext, DetailView
):
context_object_name = "organization"
model = Organization
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.slug != kwargs["slug"]:
return HttpResponsePermanentRedirect(
request.get_full_path().replace(kwargs["slug"], self.object.slug)
)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["can_edit"] = self.can_edit_organization()
context["is_member"] = self.is_member()
return context
class OrganizationList(TitleMixin, ListView, OrganizationBase):
model = Organization
context_object_name = "organizations"
@ -272,51 +248,50 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase):
return context
class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListView):
class OrganizationHome(OrganizationHomeView, FeedView):
template_name = "organization/home.html"
pagevote_object_name = "posts"
paginate_by = 4
context_object_name = "posts"
feed_content_template_name = "blog/content.html"
def get_posts_and_page_obj(self):
posts = (
def get_queryset(self):
return (
BlogPost.objects.filter(
visible=True,
publish_on__lte=timezone.now(),
is_organization_private=True,
organizations=self.object,
organizations=self.organization,
)
.order_by("-sticky", "-publish_on")
.prefetch_related("authors__user", "organizations")
)
paginator = Paginator(posts, 10)
page_number = self.request.GET.get("page", 1)
posts = paginator.get_page(page_number)
return posts, paginator.page(page_number)
def get_comment_page(self, post):
return "b:%s" % post.id
def get_feed_context(self, object_list):
post_comment_counts = {
int(page[2:]): count
for page, count in Comment.objects.filter(
page__in=["b:%d" % post.id for post in object_list], hidden=False
)
.values_list("page")
.annotate(count=Count("page"))
.order_by()
}
return {"post_comment_counts": post_comment_counts}
def get_context_data(self, **kwargs):
context = super(OrganizationHome, self).get_context_data(**kwargs)
context["title"] = self.object.name
context["title"] = self.organization.name
http = "http" if settings.DMOJ_SSL == 0 else "https"
context["organization_subdomain"] = (
http
+ "://"
+ self.object.slug
+ self.organization.slug
+ "."
+ get_current_site(self.request).domain
)
context["posts"], context["page_obj"] = self.get_posts_and_page_obj()
context = self.add_pagevote_context_data(context, "posts")
context = self.add_bookmark_context_data(context, "posts")
# Hack: This allows page_obj to have page_range for non-ListView class
setattr(
context["page_obj"], "page_range", context["posts"].paginator.page_range
)
context["first_page_href"] = self.request.path
context["page_prefix"] = "?page="
context["post_comment_counts"] = {
int(page[2:]): count
for page, count in Comment.objects.filter(
@ -331,7 +306,9 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie
visible_contests = (
Contest.get_visible_contests(self.request.user)
.filter(
is_visible=True, is_organization_private=True, organizations=self.object
is_visible=True,
is_organization_private=True,
organizations=self.organization,
)
.order_by("start_time")
)
@ -344,11 +321,27 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie
return context
class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
class OrganizationUsers(QueryStringSortMixin, OrganizationMixin, FeedView):
template_name = "organization/users.html"
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
default_desc = all_sorts
default_sort = "-performance_points"
context_object_name = "users"
def get_queryset(self):
return ranker(
self.organization.members.filter(is_unlisted=False)
.order_by(self.order, "id")
.select_related("user")
.only(
"display_rank",
"user__username",
"points",
"rating",
"performance_points",
"problem_count",
)
)
def dispatch(self, request, *args, **kwargs):
res = super(OrganizationUsers, self).dispatch(request, *args, **kwargs)
@ -363,26 +356,13 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
def get_context_data(self, **kwargs):
context = super(OrganizationUsers, self).get_context_data(**kwargs)
context["title"] = _("%s Members") % self.object.name
context["title"] = _("%s Members") % self.organization.name
context["partial"] = True
context["kick_url"] = reverse(
"organization_user_kick", args=[self.object.id, self.object.slug]
"organization_user_kick",
args=[self.organization.id, self.organization.slug],
)
context["users"] = ranker(
self.get_object()
.members.filter(is_unlisted=False)
.order_by(self.order, "id")
.select_related("user")
.only(
"display_rank",
"user__username",
"points",
"rating",
"performance_points",
"problem_count",
)
)
context["first_page_href"] = "."
context["page_type"] = "users"
context.update(self.get_sort_context())
@ -421,8 +401,7 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL
class OrganizationContestMixin(
LoginRequiredMixin,
TitleMixin,
OrganizationMixin,
OrganizationHomeViewContext,
OrganizationHomeView,
):
model = Contest
@ -613,8 +592,7 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
class OrganizationRequestDetail(
LoginRequiredMixin,
TitleMixin,
OrganizationMixin,
OrganizationHomeViewContext,
OrganizationHomeView,
DetailView,
):
model = OrganizationRequest
@ -639,7 +617,8 @@ OrganizationRequestFormSet = modelformset_factory(
class OrganizationRequestBaseView(
OrganizationDetailView,
DetailView,
OrganizationHomeView,
TitleMixin,
LoginRequiredMixin,
SingleObjectTemplateResponseMixin,
@ -760,7 +739,7 @@ class AddOrganizationMember(
LoginRequiredMixin,
TitleMixin,
AdminOrganizationMixin,
OrganizationDetailView,
OrganizationHomeView,
UpdateView,
):
template_name = "organization/add-member.html"
@ -822,7 +801,7 @@ class EditOrganization(
LoginRequiredMixin,
TitleMixin,
AdminOrganizationMixin,
OrganizationDetailView,
OrganizationHomeView,
UpdateView,
):
template_name = "organization/edit.html"
@ -1023,7 +1002,7 @@ class EditOrganizationContest(
class AddOrganizationBlog(
LoginRequiredMixin,
TitleMixin,
OrganizationHomeViewContext,
OrganizationHomeView,
MemberOrganizationMixin,
CreateView,
):
@ -1074,7 +1053,7 @@ class AddOrganizationBlog(
class EditOrganizationBlog(
LoginRequiredMixin,
TitleMixin,
OrganizationHomeViewContext,
OrganizationHomeView,
MemberOrganizationMixin,
UpdateView,
):
@ -1168,7 +1147,7 @@ class PendingBlogs(
LoginRequiredMixin,
TitleMixin,
MemberOrganizationMixin,
OrganizationHomeViewContext,
OrganizationHomeView,
ListView,
):
model = BlogPost

View file

@ -104,13 +104,3 @@ class PageVoteDetailView(TemplateResponseMixin, SingleObjectMixin, View):
queryset = PageVote.objects.get_or_create(page=self.get_comment_page())
context["pagevote"] = queryset[0]
return context
class PageVoteListView:
def add_pagevote_context_data(self, context, obj_list="object_list"):
for item in context[obj_list]:
pagevote, _ = PageVote.objects.get_or_create(
page=self.get_comment_page(item)
)
setattr(item, "pagevote", pagevote)
return context

View file

@ -87,8 +87,9 @@ from judge.utils.views import (
generic_message,
)
from judge.ml.collab_filter import CollabFilter
from judge.views.pagevote import PageVoteDetailView, PageVoteListView
from judge.views.bookmark import BookMarkDetailView, BookMarkListView
from judge.views.pagevote import PageVoteDetailView
from judge.views.bookmark import BookMarkDetailView
from judge.views.feed import FeedView
def get_contest_problem(problem, profile):
@ -197,31 +198,34 @@ class ProblemSolution(
template_name = "problem/editorial.html"
def get_title(self):
return _("Editorial for {0}").format(self.object.name)
return _("Editorial for {0}").format(self.problem.name)
def get_content_title(self):
return format_html(
_('Editorial for <a href="{1}">{0}</a>'),
self.object.name,
reverse("problem_detail", args=[self.object.code]),
self.problem.name,
reverse("problem_detail", args=[self.problem.code]),
)
def get_object(self):
self.problem = super().get_object()
solution = get_object_or_404(Solution, problem=self.problem)
return solution
def get_context_data(self, **kwargs):
context = super(ProblemSolution, self).get_context_data(**kwargs)
solution = get_object_or_404(Solution, problem=self.object)
solution = self.get_object()
if (
not solution.is_public or solution.publish_on > timezone.now()
) and not self.request.user.has_perm("judge.see_private_solution"):
raise Http404()
context["solution"] = solution
context["has_solved_problem"] = self.object.id in self.get_completed_problems()
context["has_solved_problem"] = self.problem.id in self.get_completed_problems()
return context
def get_comment_page(self):
return "s:" + self.object.code
return "s:" + self.problem.code
class ProblemRaw(
@ -830,11 +834,12 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
return HttpResponseRedirect(request.get_full_path())
class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
class ProblemFeed(ProblemList, FeedView):
model = Problem
context_object_name = "problems"
template_name = "problem/feed.html"
paginate_by = 20
feed_content_template_name = "problem/feed/problems.html"
paginate_by = 4
title = _("Problem feed")
feed_type = None
@ -843,19 +848,6 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
return request.session.get(key, key == "hide_solved")
return request.GET.get(key, None) == "1"
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
return DiggPaginator(
queryset,
per_page,
body=6,
padding=2,
orphans=orphans,
allow_empty_first_page=allow_empty_first_page,
**kwargs
)
def get_comment_page(self, problem):
return "p:%s" % problem.code
@ -962,8 +954,6 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
context["feed_type"] = self.feed_type
context["has_show_editorial_option"] = False
context["has_have_editorial_option"] = False
context = self.add_pagevote_context_data(context)
context = self.add_bookmark_context_data(context)
return context