diff --git a/dmoj/urls.py b/dmoj/urls.py index fedaace..b18457c 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -587,6 +587,16 @@ urlpatterns = [ organization.OrganizationUsers.as_view(), name="organization_users", ), + url( + r"^/problems$", + organization.OrganizationProblems.as_view(), + name="organization_problems", + ), + url( + r"^/contests$", + organization.OrganizationContests.as_view(), + name="organization_contests", + ), url( r"^/join$", organization.JoinOrganization.as_view(), diff --git a/judge/models/profile.py b/judge/models/profile.py index e155a20..fe8ce17 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -106,6 +106,12 @@ class Organization(models.Model): def get_users_url(self): return reverse("organization_users", args=(self.id, self.slug)) + def get_problems_url(self): + return reverse("organization_problems", args=(self.id, self.slug)) + + def get_contests_url(self): + return reverse("organization_contests", args=(self.id, self.slug)) + class Meta: ordering = ["name"] permissions = ( diff --git a/judge/views/blog.py b/judge/views/blog.py index 9ce0fdc..400ab73 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -69,31 +69,15 @@ class FeedView(ListView): now = timezone.now() - # Dashboard stuff - # if self.request.user.is_authenticated: - # user = self.request.profile - # context['recently_attempted_problems'] = (Submission.objects.filter(user=user) - # .exclude(problem__in=user_completed_ids(user)) - # .values_list('problem__code', 'problem__name', 'problem__points') - # .annotate(points=Max('points'), latest=Max('date')) - # .order_by('-latest') - # [:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT]) - visible_contests = ( Contest.get_visible_contests(self.request.user) .filter(is_visible=True) .order_by("start_time") ) - context["current_contests"] = visible_contests.filter( start_time__lte=now, end_time__gt=now ) context["future_contests"] = visible_contests.filter(start_time__gt=now) - - visible_contests = Contest.get_visible_contests(self.request.user).filter( - is_visible=True - ) - context["top_rated"] = Profile.objects.filter(is_unlisted=False).order_by( "-rating" )[:10] diff --git a/judge/views/contests.py b/judge/views/contests.py index f16fdae..f179c5c 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -146,9 +146,15 @@ class ContestList( self.contest_query = None self.org_query = [] - if "orgs" in self.request.GET: + if "orgs" in self.request.GET and self.request.profile: try: self.org_query = list(map(int, request.GET.getlist("orgs"))) + self.org_query = [ + i + for i in self.org_query + if i + in self.request.profile.organizations.values_list("id", flat=True) + ] except ValueError: pass @@ -215,7 +221,9 @@ class ContestList( context["first_page_href"] = "." context["contest_query"] = self.contest_query context["org_query"] = self.org_query - context["organizations"] = Organization.objects.all() + if self.request.profile: + context["organizations"] = self.request.profile.organizations.all() + context["page_type"] = "list" context.update(self.get_sort_context()) context.update(self.get_sort_paginate_context()) return context diff --git a/judge/views/organization.py b/judge/views/organization.py index 2883fc0..dc96bdc 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -7,8 +7,14 @@ from django.core.cache.utils import make_template_fragment_key from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import Count, Q, Value, BooleanField +from django.db.utils import ProgrammingError from django.forms import Form, modelformset_factory -from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect +from django.http import ( + Http404, + HttpResponsePermanentRedirect, + HttpResponseRedirect, + HttpResponseBadRequest, +) from django.urls import reverse from django.utils import timezone from django.utils.safestring import mark_safe @@ -18,6 +24,7 @@ from django.views.generic.detail import ( SingleObjectMixin, SingleObjectTemplateResponseMixin, ) +from django.core.paginator import Paginator from reversion import revisions from judge.forms import EditOrganizationForm @@ -37,11 +44,15 @@ from judge.utils.views import ( QueryStringSortMixin, DiggPaginatorMixin, ) +from judge.views.problem import ProblemList +from judge.views.contests import ContestList __all__ = [ "OrganizationList", "OrganizationHome", "OrganizationUsers", + "OrganizationProblems", + "OrganizationContests", "OrganizationMembershipChange", "JoinOrganization", "LeaveOrganization", @@ -111,6 +122,12 @@ class OrganizationDetailView(OrganizationMixin, DetailView): 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 @@ -139,19 +156,8 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase): class OrganizationHome(OrganizationDetailView): template_name = "organization/home.html" - def get_context_data(self, **kwargs): - context = super(OrganizationHome, self).get_context_data(**kwargs) - context["title"] = self.object.name - context["can_edit"] = self.can_edit_organization() - context["is_member"] = self.is_member() - context["new_problems"] = Problem.objects.filter( - is_public=True, is_organization_private=True, organizations=self.object - ).order_by("-date", "-id")[: settings.DMOJ_BLOG_NEW_PROBLEM_COUNT] - context["new_contests"] = Contest.objects.filter( - is_visible=True, is_organization_private=True, organizations=self.object - ).order_by("-end_time", "-id")[: settings.DMOJ_BLOG_NEW_CONTEST_COUNT] - - context["posts"] = ( + def get_posts(self): + posts = ( BlogPost.objects.filter( visible=True, publish_on__lte=timezone.now(), @@ -161,6 +167,21 @@ class OrganizationHome(OrganizationDetailView): .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 + + def get_context_data(self, **kwargs): + context = super(OrganizationHome, self).get_context_data(**kwargs) + context["title"] = self.object.name + context["new_problems"] = Problem.objects.filter( + is_public=True, is_organization_private=True, organizations=self.object + ).order_by("-date", "-id")[: settings.DMOJ_BLOG_NEW_PROBLEM_COUNT] + context["new_contests"] = Contest.objects.filter( + is_visible=True, is_organization_private=True, organizations=self.object + ).order_by("-end_time", "-id")[: settings.DMOJ_BLOG_NEW_CONTEST_COUNT] + context["posts"] = self.get_posts() context["post_comment_counts"] = { int(page[2:]): count for page, count in Comment.objects.filter( @@ -173,6 +194,26 @@ class OrganizationHome(OrganizationDetailView): context["pending_count"] = OrganizationRequest.objects.filter( state="P", organization=self.object ).count() + + now = timezone.now() + visible_contests = ( + Contest.get_visible_contests(self.request.user) + .filter( + is_visible=True, is_organization_private=True, organizations=self.object + ) + .order_by("start_time") + ) + context["current_contests"] = visible_contests.filter( + start_time__lte=now, end_time__gt=now + ) + context["future_contests"] = visible_contests.filter(start_time__gt=now) + context["top_rated"] = self.object.members.filter(is_unlisted=False).order_by( + "-rating" + )[:10] + context["top_scorer"] = self.object.members.filter(is_unlisted=False).order_by( + "-performance_points" + )[:10] + context["page_type"] = "home" return context @@ -186,7 +227,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): context = super(OrganizationUsers, self).get_context_data(**kwargs) context["title"] = _("%s Members") % self.object.name context["partial"] = True - context["is_admin"] = self.can_edit_organization() + context["can_edit"] = self.can_edit_organization() context["kick_url"] = reverse( "organization_user_kick", args=[self.object.id, self.object.slug] ) @@ -206,10 +247,76 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): ) ) context["first_page_href"] = "." + context["page_type"] = "users" context.update(self.get_sort_context()) return context +class OrganizationExternalMixin(OrganizationBase): + organization_id = None + organization = None + + def get_organization_from_url(self, request, *args, **kwargs): + try: + self.organization_id = int(kwargs["pk"]) + self.organization = Organization.objects.get(id=self.organization_id) + except: + return HttpResponseBadRequest() + if self.organization.slug != kwargs["slug"]: + return HttpResponsePermanentRedirect( + request.get_full_path().replace(kwargs["slug"], self.object.slug) + ) + return None + + def edit_context_data(self, context): + context["is_member"] = self.is_member(self.organization) + context["can_edit"] = self.can_edit_organization(self.organization) + context["organization"] = self.organization + context["logo_override_image"] = self.organization.logo_override_image + context.pop("organizations") + + +class OrganizationProblems(ProblemList, OrganizationExternalMixin): + template_name = "organization/problems.html" + + def get_queryset(self): + self.org_query = [self.organization_id] + return super().get_normal_queryset() + + def get(self, request, *args, **kwargs): + ret = super().get_organization_from_url(request, *args, **kwargs) + if ret: + return ret + self.setup_problem_list(request) + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(OrganizationProblems, self).get_context_data(**kwargs) + self.edit_context_data(context) + context["page_type"] = "problems" + return context + + +class OrganizationContests(ContestList, OrganizationExternalMixin): + template_name = "organization/contests.html" + + def get_queryset(self): + self.org_query = [self.organization_id] + return super().get_queryset() + + def get(self, request, *args, **kwargs): + ret = super().get_organization_from_url(request, *args, **kwargs) + if ret: + return ret + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(OrganizationContests, self).get_context_data(**kwargs) + self.edit_context_data(context) + context["page_type"] = "contests" + return context + + class OrganizationMembershipChange( LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View ): diff --git a/judge/views/problem.py b/judge/views/problem.py index 1efca66..30c94b4 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -567,6 +567,13 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ) ] + def get_org_query(self, query): + return [ + i + for i in query + if i in self.profile.organizations.values_list("id", flat=True) + ] + def get_normal_queryset(self): filter = Q(is_public=True) if self.profile is not None: @@ -588,6 +595,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ).values_list("problem__id", flat=True) ) if self.org_query: + self.org_query = self.get_org_query(self.org_query) queryset = queryset.filter( Q(organizations__in=self.org_query) | Q(contests__contest__organizations__in=self.org_query) @@ -650,7 +658,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView context["show_editorial"] = 0 if self.in_contest else int(self.show_editorial) context["have_editorial"] = 0 if self.in_contest else int(self.have_editorial) - context["organizations"] = Organization.objects.all() + if self.request.profile: + context["organizations"] = self.request.profile.organizations.all() all_authors_ids = set(Problem.objects.values_list("authors", flat=True)) context["all_authors"] = Profile.objects.filter(id__in=all_authors_ids) context["category"] = self.category @@ -702,7 +711,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ("?" + self.request.GET.urlencode()) if self.request.GET else "" ) context["first_page_href"] = (self.first_page_href or ".") + suffix - + context["has_show_editorial_option"] = True return context def get_noui_slider_points(self): @@ -811,6 +820,7 @@ cf_logger = logging.getLogger("judge.ml.collab_filter") class ProblemFeed(ProblemList): model = Problem context_object_name = "problems" + template_name = "problem/feed.html" paginate_by = 20 title = _("Problem feed") feed_type = None @@ -926,7 +936,8 @@ class ProblemFeed(ProblemList): context["page_type"] = "feed" context["title"] = self.title context["feed_type"] = self.feed_type - + context["has_show_editorial_option"] = False + context["has_have_editorial_option"] = False return context def get(self, request, *args, **kwargs): diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index d2efe12..63e7ffc 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -4199,11 +4199,11 @@ msgstr "Quản lý" #: templates/organization/home.html:135 msgid "Edit organization" -msgstr "Chỉnh sửa tổ chức" +msgstr "Chỉnh sửa" #: templates/organization/home.html:141 msgid "View requests" -msgstr "Xem các đơn đăng ký" +msgstr "Đơn đăng ký" #: templates/organization/home.html:154 msgid "Admin organization" diff --git a/templates/blog/list.html b/templates/blog/list.html index 98cb698..e330845 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -122,72 +122,7 @@ {% endif %} - {% if current_contests %} - - {% endif %} - - {% if future_contests %} - - {% endif %} - - + {% include 'contests-countdown.html' %} + {% include 'top-users.html' %} {% endblock %} \ No newline at end of file diff --git a/templates/contest/list.html b/templates/contest/list.html index e0b94a7..ed8b95e 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -1,9 +1,9 @@ -{% extends "common-content.html" %} +{% extends "three-column-content.html" %} {% block meta %} {% endblock %} -{% block media %} +{% block three_col_media %} {% endblock %} -{% block js_media %} +{% block three_col_js %} {% endblock %} -{% block title_ruler %}{% endblock %} - -{% block title_row %} - {% set tab = 'list' %} - {% set title = 'Contests' %} - {% include "contest/contest-list-tabs.html" %} +{% block left_sidebar %} + {% endblock %} {% macro contest_head(contest) %} @@ -212,19 +219,20 @@ {% endif %} {% endmacro %} -{% block body %} +{% block middle_content %}
- - + {% if organizations %} + + {% endif %}
{% if active_participations %} diff --git a/templates/contests-countdown.html b/templates/contests-countdown.html new file mode 100644 index 0000000..9cd2310 --- /dev/null +++ b/templates/contests-countdown.html @@ -0,0 +1,35 @@ +{% if current_contests %} + +{% endif %} + +{% if future_contests %} + +{% endif %} diff --git a/templates/organization/contests.html b/templates/organization/contests.html new file mode 100644 index 0000000..14aed86 --- /dev/null +++ b/templates/organization/contests.html @@ -0,0 +1,4 @@ +{% extends "contest/list.html" %} +{% block left_sidebar %} + {% include "organization/org-left-sidebar.html" %} +{% endblock %} \ No newline at end of file diff --git a/templates/organization/home.html b/templates/organization/home.html index 6007abe..2be494a 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -1,12 +1,6 @@ -{% extends "base.html" %} - -{% block media %} +{% extends "three-column-content.html" %} +{% block three_col_media %} {% endblock %} -{% block js_media %} +{% block three_col_js %} {% endblock %} -{% block title_row %} +{% block left_sidebar %} + {% include "organization/org-left-sidebar.html" %} +{% endblock %} + +{% block title_ruler %}{% endblock %} +{% block middle_content %} + {% block before_posts %}{% endblock %}

{{title}}

@@ -85,81 +72,68 @@ class="unselectable button">{{ _('Request membership') }} {% endif %} {% endif %} -
-{% endblock %} -{% block title_ruler %} {% endblock %} -{% block body %} - {% block before_posts %}{% endblock %} -
- -
-
- - {% if page_type == 'list' %} + {% if has_show_editorial_option %}
- {% elif page_type == 'feed' %} + {% endif %} + {% if has_have_editorial_option %}
{% endif %} -
- - -
+ {% if organizations %} +
+ + +
+ {% endif %}
-
{% if show_types %}
diff --git a/templates/top-users.html b/templates/top-users.html new file mode 100644 index 0000000..fac55ec --- /dev/null +++ b/templates/top-users.html @@ -0,0 +1,37 @@ +{% if top_rated %} + +{% endif %} + +{% if top_scorer %} + +{% endif %} \ No newline at end of file diff --git a/templates/user/base-users-three-col.html b/templates/user/base-users-three-col.html new file mode 100644 index 0000000..c7fbb46 --- /dev/null +++ b/templates/user/base-users-three-col.html @@ -0,0 +1,102 @@ +{% extends "three-column-content.html" %} + +{% block three_col_js %} + {% block users_js_media %}{% endblock %} + +{% endblock %} + +{% block three_col_media %} + + + {% block users_media %}{% endblock %} +{% endblock %} + +{% block middle_content %} +
+
+ {% if page_obj and page_obj.num_pages > 1 %} +
+ {% include "list-pages.html" %} + + + +
+ {% endif %} + + + {% block users_table %}{% endblock %} +
+ + {% if page_obj and page_obj.num_pages > 1 %} +
{% include "list-pages.html" %}
+ {% endif %} +
+
+{% endblock %}