diff --git a/dmoj/urls.py b/dmoj/urls.py index b18457c..a791537 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -617,6 +617,26 @@ urlpatterns = [ organization.KickUserWidgetView.as_view(), name="organization_user_kick", ), + url( + r"^/add_member$", + organization.AddOrganizationMember.as_view(), + name="add_organization_member", + ), + url( + r"^/blog/add$", + organization.AddOrganizationBlog.as_view(), + name="add_organization_blog", + ), + url( + r"^/blog/edit/(?P\d+)$", + organization.EditOrganizationBlog.as_view(), + name="edit_organization_blog", + ), + url( + r"^/blog/pending$", + organization.PendingBlogs.as_view(), + name="organization_pending_blogs", + ), url( r"^/request$", organization.RequestJoinOrganization.as_view(), diff --git a/judge/forms.py b/judge/forms.py index a6166bc..164fc6e 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -4,12 +4,13 @@ import pyotp from django import forms from django.conf import settings from django.contrib.auth.forms import AuthenticationForm -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.core.validators import RegexValidator from django.db.models import Q from django.forms import CharField, ChoiceField, Form, ModelForm from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ +from django.utils import timezone from django_ace import AceWidget from judge.models import ( @@ -21,6 +22,7 @@ from judge.models import ( ProblemPointsVote, Profile, Submission, + BlogPost, ) from judge.utils.subscription import newsletter_id from judge.widgets import ( @@ -81,9 +83,9 @@ class ProfileForm(ModelForm): if sum(org.is_open for org in organizations) > max_orgs: raise ValidationError( - _( - "You may not be part of more than {count} public organizations." - ).format(count=max_orgs) + _("You may not be part of more than {count} public groups.").format( + count=max_orgs + ) ) return self.cleaned_data @@ -125,7 +127,7 @@ class ProblemSubmitForm(ModelForm): class EditOrganizationForm(ModelForm): class Meta: model = Organization - fields = ["about", "logo_override_image", "admins"] + fields = ["about", "logo_override_image", "admins", "is_open"] widgets = {"admins": Select2MultipleWidget()} if HeavyPreviewPageDownWidget is not None: widgets["about"] = HeavyPreviewPageDownWidget( @@ -133,6 +135,74 @@ class EditOrganizationForm(ModelForm): ) +class AddOrganizationMemberForm(ModelForm): + new_users = CharField( + max_length=65536, + widget=forms.Textarea, + help_text=_("Enter usernames separating by space"), + ) + + def clean(self): + new_users = self.cleaned_data.get("new_users") or "" + usernames = new_users.split() + invalid_usernames = [] + valid_usernames = [] + + for username in usernames: + try: + valid_usernames.append(Profile.objects.get(user__username=username)) + except ObjectDoesNotExist: + invalid_usernames.append(username) + + if invalid_usernames: + raise ValidationError( + _("These usernames don't exist: {usernames}").format( + usernames=str(invalid_usernames) + ) + ) + self.cleaned_data["new_users"] = valid_usernames + return self.cleaned_data + + class Meta: + model = Organization + fields = () + + +class OrganizationBlogForm(ModelForm): + class Meta: + model = BlogPost + fields = ("title", "content", "publish_on") + widgets = { + "publish_on": forms.HiddenInput, + } + if HeavyPreviewPageDownWidget is not None: + widgets["content"] = HeavyPreviewPageDownWidget( + preview=reverse_lazy("organization_preview") + ) + + def __init__(self, *args, **kwargs): + super(OrganizationBlogForm, self).__init__(*args, **kwargs) + self.fields["publish_on"].required = False + self.fields["publish_on"].is_hidden = True + + def clean(self): + self.cleaned_data["publish_on"] = timezone.now() + return self.cleaned_data + + +class OrganizationAdminBlogForm(OrganizationBlogForm): + class Meta: + model = BlogPost + fields = ("visible", "sticky", "title", "content", "publish_on") + widgets = { + "publish_on": forms.HiddenInput, + } + if HeavyPreviewPageDownWidget is not None: + widgets["content"] = HeavyPreviewPageDownWidget( + preview=reverse_lazy("organization_preview") + ) + + class NewMessageForm(ModelForm): class Meta: model = PrivateMessage diff --git a/judge/views/organization.py b/judge/views/organization.py index 7b7d7db..2f305a7 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -19,7 +19,14 @@ from django.urls import reverse from django.utils import timezone from django.utils.safestring import mark_safe from django.utils.translation import gettext as _, gettext_lazy, ungettext -from django.views.generic import DetailView, FormView, ListView, UpdateView, View +from django.views.generic import ( + DetailView, + FormView, + ListView, + UpdateView, + View, + CreateView, +) from django.views.generic.detail import ( SingleObjectMixin, SingleObjectTemplateResponseMixin, @@ -27,7 +34,12 @@ from django.views.generic.detail import ( from django.core.paginator import Paginator from reversion import revisions -from judge.forms import EditOrganizationForm +from judge.forms import ( + EditOrganizationForm, + AddOrganizationMemberForm, + OrganizationBlogForm, + OrganizationAdminBlogForm, +) from judge.models import ( BlogPost, Comment, @@ -44,6 +56,7 @@ from judge.utils.views import ( QueryStringSortMixin, DiggPaginatorMixin, ) +from judge.utils.problems import user_attempted_ids from judge.views.problem import ProblemList from judge.views.contests import ContestList @@ -73,7 +86,9 @@ class OrganizationBase(object): return False profile_id = self.request.profile.id return ( - org.admins.filter(id=profile_id).exists() or org.registrant_id == profile_id + org.admins.filter(id=profile_id).exists() + or org.registrant_id == profile_id + or self.request.user.is_superuser ) def is_member(self, org=None): @@ -92,17 +107,20 @@ class OrganizationBase(object): class OrganizationMixin(OrganizationBase): - context_object_name = "organization" - model = Organization - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["logo_override_image"] = self.object.logo_override_image + 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 + if "organizations" in context: + context.pop("organizations") return context def dispatch(self, request, *args, **kwargs): try: - return super(OrganizationMixin, self).dispatch(request, *args, **kwargs) + self.organization_id = int(kwargs["pk"]) + self.organization = Organization.objects.get(id=self.organization_id) except Http404: key = kwargs.get(self.slug_url_kwarg, None) if key: @@ -117,9 +135,72 @@ class OrganizationMixin(OrganizationBase): _("No such organization"), _("Could not find such organization."), ) + if self.organization.slug != kwargs["slug"]: + return HttpResponsePermanentRedirect( + request.get_full_path().replace(kwargs["slug"], self.object.slug) + ) + return super(OrganizationMixin, self).dispatch(request, *args, **kwargs) -class OrganizationDetailView(OrganizationMixin, DetailView): +class AdminOrganizationMixin(OrganizationMixin): + def dispatch(self, request, *args, **kwargs): + res = super(AdminOrganizationMixin, self).dispatch(request, *args, **kwargs) + if self.can_edit_organization(self.organization): + return res + return generic_message( + request, + _("Can't edit organization"), + _("You are not allowed to edit this organization."), + status=403, + ) + + +class MemberOrganizationMixin(OrganizationMixin): + def dispatch(self, request, *args, **kwargs): + res = super(MemberOrganizationMixin, self).dispatch(request, *args, **kwargs) + if self.can_access(self.organization): + return res + return generic_message( + request, + _("Can't access organization"), + _("You are not allowed to access this organization."), + status=403, + ) + + +class OrganizationHomeViewContext: + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if not hasattr(self, "organization"): + self.organization = self.object + if self.can_edit_organization(self.organization): + context["pending_count"] = OrganizationRequest.objects.filter( + state="P", organization=self.organization + ).count() + context["pending_blog_count"] = BlogPost.objects.filter( + visible=False, organizations=self.organization + ).count() + else: + context["pending_blog_count"] = BlogPost.objects.filter( + visible=False, + organizations=self.organization, + authors=self.request.profile, + ).count() + context["top_rated"] = self.organization.members.filter( + is_unlisted=False + ).order_by("-rating")[:10] + context["top_scorer"] = self.organization.members.filter( + is_unlisted=False + ).order_by("-performance_points")[:10] + 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"]: @@ -140,7 +221,7 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase): model = Organization context_object_name = "organizations" template_name = "organization/list.html" - title = gettext_lazy("Organizations") + title = gettext_lazy("Groups") def get_queryset(self): return ( @@ -180,12 +261,6 @@ class OrganizationHome(OrganizationDetailView): 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 @@ -196,9 +271,6 @@ class OrganizationHome(OrganizationDetailView): .annotate(count=Count("page")) .order_by() } - context["pending_count"] = OrganizationRequest.objects.filter( - state="P", organization=self.object - ).count() now = timezone.now() visible_contests = ( @@ -212,12 +284,6 @@ class OrganizationHome(OrganizationDetailView): 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 @@ -232,7 +298,6 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): context = super(OrganizationUsers, self).get_context_data(**kwargs) context["title"] = _("%s Members") % self.object.name context["partial"] = True - context["can_edit"] = self.can_edit_organization() context["kick_url"] = reverse( "organization_user_kick", args=[self.object.id, self.object.slug] ) @@ -257,31 +322,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): 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): +class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList): template_name = "organization/problems.html" def get_queryset(self): @@ -289,39 +330,35 @@ class OrganizationProblems(ProblemList, OrganizationExternalMixin): return super().get_normal_queryset() def get(self, request, *args, **kwargs): - ret = super().get_organization_from_url(request, *args, **kwargs) - if ret: - return ret - if not self.can_access(self.organization): - return HttpResponseBadRequest() self.setup_problem_list(request) return super().get(request, *args, **kwargs) + def get_latest_attempted_problems(self, limit=None): + if self.in_contest or not self.profile: + return () + problems = set(self.get_queryset().values_list("code", flat=True)) + result = list(user_attempted_ids(self.profile).values()) + result = [i for i in result if i["code"] in problems] + result = sorted(result, key=lambda d: -d["last_submission"]) + if limit: + result = result[:limit] + return result + 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): +class OrganizationContests(LoginRequiredMixin, MemberOrganizationMixin, ContestList): 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 - if not self.can_access(self.organization): - return HttpResponseBadRequest() - 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 @@ -329,6 +366,9 @@ class OrganizationContests(ContestList, OrganizationExternalMixin): class OrganizationMembershipChange( LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View ): + model = Organization + context_object_name = "organization" + def post(self, request, *args, **kwargs): org = self.get_object() response = self.handle(request, org, request.profile) @@ -345,23 +385,23 @@ class JoinOrganization(OrganizationMembershipChange): if profile.organizations.filter(id=org.id).exists(): return generic_message( request, - _("Joining organization"), - _("You are already in the organization."), + _("Joining group"), + _("You are already in the group."), ) if not org.is_open: return generic_message( - request, _("Joining organization"), _("This organization is not open.") + request, _("Joining group"), _("This group is not open.") ) max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT if profile.organizations.filter(is_open=True).count() >= max_orgs: return generic_message( request, - _("Joining organization"), - _( - "You may not be part of more than {count} public organizations." - ).format(count=max_orgs), + _("Joining group"), + _("You may not be part of more than {count} public groups.").format( + count=max_orgs + ), ) profile.organizations.add(org) @@ -374,7 +414,7 @@ class LeaveOrganization(OrganizationMembershipChange): if not profile.organizations.filter(id=org.id).exists(): return generic_message( request, - _("Leaving organization"), + _("Leaving group"), _('You are not in "%s".') % org.short_name, ) profile.organizations.remove(org) @@ -422,7 +462,13 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView): ) -class OrganizationRequestDetail(LoginRequiredMixin, TitleMixin, DetailView): +class OrganizationRequestDetail( + LoginRequiredMixin, + TitleMixin, + OrganizationMixin, + OrganizationHomeViewContext, + DetailView, +): model = OrganizationRequest template_name = "organization/requests/detail.html" title = gettext_lazy("Join request detail") @@ -445,11 +491,11 @@ OrganizationRequestFormSet = modelformset_factory( class OrganizationRequestBaseView( + OrganizationDetailView, TitleMixin, LoginRequiredMixin, SingleObjectTemplateResponseMixin, SingleObjectMixin, - View, ): model = Organization slug_field = "key" @@ -562,58 +608,43 @@ class OrganizationRequestLog(OrganizationRequestBaseView): return context -class EditOrganization(LoginRequiredMixin, TitleMixin, OrganizationMixin, UpdateView): - template_name = "organization/edit.html" +class AddOrganizationMember( + LoginRequiredMixin, + TitleMixin, + AdminOrganizationMixin, + OrganizationDetailView, + UpdateView, +): + template_name = "organization/add-member.html" model = Organization - form_class = EditOrganizationForm + form_class = AddOrganizationMemberForm def get_title(self): - return _("Editing %s") % self.object.name + return _("Add member for %s") % self.object.name def get_object(self, queryset=None): - object = super(EditOrganization, self).get_object() + object = super(AddOrganizationMember, self).get_object() if not self.can_edit_organization(object): raise PermissionDenied() return object - def get_form(self, form_class=None): - form = super(EditOrganization, self).get_form(form_class) - form.fields["admins"].queryset = Profile.objects.filter( - Q(organizations=self.object) | Q(admin_of=self.object) - ).distinct() - return form - def form_valid(self, form): + new_users = form.cleaned_data["new_users"] + self.object.members.add(*new_users) with transaction.atomic(), revisions.create_revision(): - revisions.set_comment(_("Edited from site")) + revisions.set_comment(_("Added members from site")) revisions.set_user(self.request.user) - return super(EditOrganization, self).form_valid(form) + return super(AddOrganizationMember, self).form_valid(form) - def dispatch(self, request, *args, **kwargs): - try: - return super(EditOrganization, self).dispatch(request, *args, **kwargs) - except PermissionDenied: - return generic_message( - request, - _("Can't edit organization"), - _("You are not allowed to edit this organization."), - status=403, - ) + def get_success_url(self): + return reverse("organization_users", args=[self.object.id, self.object.slug]) class KickUserWidgetView( - LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View + LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View ): def post(self, request, *args, **kwargs): organization = self.get_object() - if not self.can_edit_organization(organization): - return generic_message( - request, - _("Can't edit organization"), - _("You are not allowed to kick people from this organization."), - status=403, - ) - try: user = Profile.objects.get(id=request.POST.get("user", None)) except Profile.DoesNotExist: @@ -635,3 +666,147 @@ class KickUserWidgetView( organization.members.remove(user) return HttpResponseRedirect(organization.get_users_url()) + + +class EditOrganization( + LoginRequiredMixin, + TitleMixin, + AdminOrganizationMixin, + OrganizationDetailView, + UpdateView, +): + template_name = "organization/edit.html" + model = Organization + form_class = EditOrganizationForm + + def get_title(self): + return _("Edit %s") % self.object.name + + def get_object(self, queryset=None): + object = super(EditOrganization, self).get_object() + if not self.can_edit_organization(object): + raise PermissionDenied() + return object + + def get_form(self, form_class=None): + form = super(EditOrganization, self).get_form(form_class) + form.fields["admins"].queryset = Profile.objects.filter( + Q(organizations=self.object) | Q(admin_of=self.object) + ).distinct() + return form + + def form_valid(self, form): + with transaction.atomic(), revisions.create_revision(): + revisions.set_comment(_("Edited from site")) + revisions.set_user(self.request.user) + return super(EditOrganization, self).form_valid(form) + + +class AddOrganizationBlog( + LoginRequiredMixin, + TitleMixin, + OrganizationHomeViewContext, + MemberOrganizationMixin, + CreateView, +): + template_name = "organization/blog/add.html" + model = BlogPost + form_class = OrganizationBlogForm + + def get_title(self): + return _("Add blog for %s") % self.organization.name + + def form_valid(self, form): + with transaction.atomic(), revisions.create_revision(): + res = super(AddOrganizationBlog, self).form_valid(form) + self.object.is_organization_private = True + self.object.authors.add(self.request.profile) + self.object.slug = self.organization.slug + "-" + self.request.user.username + self.object.organizations.add(self.organization) + self.object.save() + + revisions.set_comment(_("Added from site")) + revisions.set_user(self.request.user) + return res + + +class EditOrganizationBlog( + LoginRequiredMixin, + TitleMixin, + OrganizationHomeViewContext, + MemberOrganizationMixin, + UpdateView, +): + template_name = "organization/blog/add.html" + model = BlogPost + + def get_form_class(self): + if self.can_edit_organization(self.organization): + return OrganizationAdminBlogForm + return OrganizationBlogForm + + def setup_blog(self, request, *args, **kwargs): + try: + self.blog_id = kwargs["blog_pk"] + self.blog = BlogPost.objects.get(id=self.blog_id) + if self.organization not in self.blog.organizations.all(): + raise Exception("This blog does not belong to this organization") + if ( + self.request.profile not in self.blog.authors.all() + and not self.can_edit_organization(self.organization) + ): + raise Exception("Not allowed to edit this blog") + except: + return generic_message( + request, + _("Permission denied"), + _("Not allowed to edit this blog"), + ) + + def get(self, request, *args, **kwargs): + res = self.setup_blog(request, *args, **kwargs) + if res: + return res + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + res = self.setup_blog(request, *args, **kwargs) + if res: + return res + return super().post(request, *args, **kwargs) + + def get_object(self): + return self.blog + + def get_title(self): + return _("Edit blog %s") % self.object.title + + def form_valid(self, form): + with transaction.atomic(), revisions.create_revision(): + res = super(EditOrganizationBlog, self).form_valid(form) + revisions.set_comment(_("Edited from site")) + revisions.set_user(self.request.user) + return res + + +class PendingBlogs( + LoginRequiredMixin, + TitleMixin, + MemberOrganizationMixin, + OrganizationHomeViewContext, + ListView, +): + model = BlogPost + template_name = "organization/blog/pending.html" + context_object_name = "blogs" + + def get_queryset(self): + queryset = BlogPost.objects.filter( + organizations=self.organization, visible=False + ) + if not self.can_edit_organization(self.organization): + queryset = queryset.filter(authors=self.request.profile) + return queryset.order_by("publish_on") + + def get_title(self): + return _("Pending blogs in %s") % self.organization.name diff --git a/judge/views/problem.py b/judge/views/problem.py index 30c94b4..4c76f92 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -568,6 +568,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ] def get_org_query(self, query): + if not self.profile: + return None return [ i for i in query diff --git a/judge/views/register.py b/judge/views/register.py index 0856f00..b10b057 100644 --- a/judge/views/register.py +++ b/judge/views/register.py @@ -46,7 +46,7 @@ class CustomRegistrationForm(RegistrationForm): ) organizations = SortedMultipleChoiceField( queryset=Organization.objects.filter(is_open=True), - label=_("Organizations"), + label=_("Groups"), required=False, widget=Select2MultipleWidget(attrs={"style": "width:100%"}), ) @@ -65,9 +65,9 @@ class CustomRegistrationForm(RegistrationForm): if sum(org.is_open for org in organizations) > max_orgs: raise forms.ValidationError( - _( - "You may not be part of more than {count} public organizations." - ).format(count=max_orgs) + _("You may not be part of more than {count} public groups.").format( + count=max_orgs + ) ) return self.cleaned_data["organizations"] diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index 63e7ffc..ea82b88 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: lqdoj2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-22 08:28+0700\n" +"POT-Creation-Date: 2022-05-30 13:51+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -20,7 +20,7 @@ msgstr "" #: chat_box/models.py:31 chat_box/models.py:52 chat_box/models.py:63 #: judge/admin/interface.py:150 judge/models/contest.py:621 -#: judge/models/contest.py:809 judge/models/profile.py:338 +#: judge/models/contest.py:809 judge/models/profile.py:344 msgid "user" msgstr "người dùng" @@ -109,6 +109,7 @@ msgid "Login" msgstr "Đăng nhập" #: dmoj/urls.py:206 templates/base.html:213 +#: templates/organization/org-left-sidebar.html:2 msgid "Home" msgstr "Trang chủ" @@ -143,9 +144,11 @@ msgstr "" #: judge/admin/contest.py:74 judge/admin/volunteer.py:33 #: templates/contest/clarification.html:42 templates/contest/contest.html:83 #: templates/contest/moss.html:43 templates/internal/base.html:29 -#: templates/internal/base.html:37 templates/problem/list.html:264 -#: templates/problem/list.html:279 templates/problem/list.html:395 -#: templates/user/user-problems.html:56 templates/user/user-problems.html:98 +#: templates/internal/base.html:37 templates/problem/list-base.html:264 +#: templates/problem/list-base.html:279 templates/problem/list-base.html:395 +#: templates/problem/list.html:15 templates/problem/list.html:30 +#: templates/problem/list.html:146 templates/user/user-problems.html:56 +#: templates/user/user-problems.html:98 msgid "Problem" msgstr "Bài tập" @@ -157,7 +160,7 @@ msgstr "Cài đặt" msgid "Scheduling" msgstr "" -#: judge/admin/contest.py:163 templates/organization/home.html:100 +#: judge/admin/contest.py:163 msgid "Details" msgstr "Chi tiết" @@ -178,47 +181,47 @@ msgstr "Truy cập" msgid "Justice" msgstr "Xử phạt" -#: judge/admin/contest.py:313 +#: judge/admin/contest.py:317 #, python-format msgid "%d contest successfully marked as visible." msgid_plural "%d contests successfully marked as visible." msgstr[0] "%d kỳ thi đã được đánh dấu hiển thị." -#: judge/admin/contest.py:320 +#: judge/admin/contest.py:324 msgid "Mark contests as visible" msgstr "Đánh dấu hiển thị các kỳ thi" -#: judge/admin/contest.py:331 +#: judge/admin/contest.py:335 #, python-format msgid "%d contest successfully marked as hidden." msgid_plural "%d contests successfully marked as hidden." msgstr[0] "%d kỳ thi đã được đánh dấu ẩn." -#: judge/admin/contest.py:338 +#: judge/admin/contest.py:342 msgid "Mark contests as hidden" msgstr "Ẩn các kỳ thi" -#: judge/admin/contest.py:359 judge/admin/submission.py:243 +#: judge/admin/contest.py:363 judge/admin/submission.py:243 #, python-format msgid "%d submission was successfully scheduled for rejudging." msgid_plural "%d submissions were successfully scheduled for rejudging." msgstr[0] "%d bài nộp đã được lên lịch thành công để chấm lại." -#: judge/admin/contest.py:467 +#: judge/admin/contest.py:471 #, python-format msgid "%d participation recalculated." msgid_plural "%d participations recalculated." msgstr[0] "%d thí sinh đã được tính điểm lại." -#: judge/admin/contest.py:474 +#: judge/admin/contest.py:478 msgid "Recalculate results" msgstr "Tính toán lại kết quả" -#: judge/admin/contest.py:479 judge/admin/organization.py:92 +#: judge/admin/contest.py:483 judge/admin/organization.py:97 msgid "username" msgstr "tên đăng nhập" -#: judge/admin/contest.py:485 templates/base.html:304 +#: judge/admin/contest.py:489 templates/base.html:304 msgid "virtual" msgstr "ảo" @@ -265,7 +268,8 @@ msgstr "" #: judge/admin/problem.py:189 judge/admin/problem.py:407 #: templates/contest/contest.html:84 templates/problem/data.html:476 -#: templates/problem/list.html:269 templates/problem/list.html:293 +#: templates/problem/list-base.html:269 templates/problem/list-base.html:293 +#: templates/problem/list.html:20 templates/problem/list.html:44 #: templates/user/base-users-table.html:10 templates/user/user-about.html:36 #: templates/user/user-about.html:52 templates/user/user-problems.html:58 msgid "Points" @@ -284,7 +288,7 @@ msgstr "Ngôn ngữ" msgid "History" msgstr "Lịch sử" -#: judge/admin/problem.py:250 templates/problem/list.html:97 +#: judge/admin/problem.py:250 templates/problem/list-base.html:97 msgid "Authors" msgstr "Các tác giả" @@ -309,7 +313,8 @@ msgid "Mark problems as private" msgstr "Đánh dấu các bài tập là riêng tư" #: judge/admin/problem.py:401 judge/admin/submission.py:316 -#: templates/problem/list.html:265 templates/problem/list.html:282 +#: templates/problem/list-base.html:265 templates/problem/list-base.html:282 +#: templates/problem/list.html:16 templates/problem/list.html:33 msgid "Problem code" msgstr "Mã bài" @@ -378,7 +383,8 @@ msgstr "Các bài tập không được cho phép" msgid "These problems are NOT allowed to be submitted in this language" msgstr "Các bài này không cho phép sử dụng ngôn ngữ này" -#: judge/admin/runtime.py:117 templates/problem/list.html:397 +#: judge/admin/runtime.py:117 templates/problem/list-base.html:397 +#: templates/problem/list.html:148 msgid "Description" msgstr "Mô tả" @@ -438,7 +444,7 @@ msgstr "Tính điểm lại cái bài nộp" #: judge/admin/submission.py:334 templates/notification/list.html:15 #: templates/organization/requests/log.html:10 #: templates/organization/requests/pending.html:13 -#: templates/problem/list.html:396 +#: templates/problem/list-base.html:396 templates/problem/list.html:147 #: templates/submission/status-testcases.html:125 #: templates/submission/status-testcases.html:127 msgid "Time" @@ -511,57 +517,66 @@ msgstr "" msgid "IOI" msgstr "" -#: judge/forms.py:44 +#: judge/forms.py:46 msgid "Subscribe to contest updates" msgstr "Đăng ký để nhận thông báo về các kỳ thi" -#: judge/forms.py:47 +#: judge/forms.py:49 msgid "Enable experimental features" msgstr "Sử dụng các tính năng thử nghiệm" -#: judge/forms.py:85 judge/views/organization.py:247 judge/views/register.py:69 +#: judge/forms.py:87 judge/views/organization.py:387 judge/views/register.py:69 #, python-brace-format -msgid "You may not be part of more than {count} public organizations." -msgstr "Bạn không thể tham gia nhiều hơn {count} tổ chức công khai." +msgid "You may not be part of more than {count} public groups." +msgstr "Bạn không thể tham gia nhiều hơn {count} nhóm công khai." -#: judge/forms.py:116 +#: judge/forms.py:118 msgid "Any judge" msgstr "" -#: judge/forms.py:148 judge/views/register.py:31 +#: judge/forms.py:140 +msgid "Enter usernames separating by space" +msgstr "" + +#: judge/forms.py:158 +#, python-brace-format +msgid "These usernames don't exist: {usernames}" +msgstr "" + +#: judge/forms.py:216 judge/views/register.py:31 #: templates/registration/registration_form.html:139 #: templates/user/base-users-table.html:5 #: templates/user/import/table_csv.html:4 msgid "Username" msgstr "Tên đăng nhập" -#: judge/forms.py:149 templates/registration/registration_form.html:151 +#: judge/forms.py:217 templates/registration/registration_form.html:151 #: templates/registration/registration_form.html:165 #: templates/user/import/table_csv.html:5 msgid "Password" msgstr "Mật khẩu" -#: judge/forms.py:175 +#: judge/forms.py:243 msgid "Two Factor Authentication tokens must be 6 decimal digits." msgstr "Two Factor Authentication phải chứa 6 chữ số." -#: judge/forms.py:188 templates/registration/totp_auth.html:32 +#: judge/forms.py:256 templates/registration/totp_auth.html:32 msgid "Invalid Two Factor Authentication token." msgstr "Token Two Factor Authentication không hợp lệ." -#: judge/forms.py:195 judge/models/problem.py:151 +#: judge/forms.py:263 judge/models/problem.py:151 msgid "Problem code must be ^[a-z0-9]+$" msgstr "Mã bài phải có dạng ^[a-z0-9]+$" -#: judge/forms.py:202 +#: judge/forms.py:270 msgid "Problem with code already exists." msgstr "Mã bài đã tồn tại." -#: judge/forms.py:209 judge/models/contest.py:89 +#: judge/forms.py:277 judge/models/contest.py:89 msgid "Contest id must be ^[a-z0-9]+$" msgstr "Mã kỳ thi phải có dạng ^[a-z0-9]+$" -#: judge/forms.py:215 +#: judge/forms.py:283 msgid "Contest with key already exists." msgstr "Mã kỳ thi đã tồn tại." @@ -784,7 +799,7 @@ msgid "" "Should be set even for organization-private contests, where it determines " "whether the contest is visible to members of the specified organizations." msgstr "" -"Đánh dấu ngay cả với các kỳ thi riêng tư của tổ chức, quyết định việc kỳ thi " +"Đánh dấu ngay cả với các kỳ thi riêng tư của nhóm, quyết định việc kỳ thi " "có được hiển thị với các thành viên hay không." #: judge/models/contest.py:140 @@ -876,10 +891,10 @@ msgstr "" #: judge/models/contest.py:211 judge/models/interface.py:92 #: judge/models/problem.py:300 msgid "private to organizations" -msgstr "riêng tư với các tổ chức" +msgstr "riêng tư với các nhóm" #: judge/models/contest.py:216 judge/models/interface.py:88 -#: judge/models/problem.py:296 judge/models/profile.py:116 +#: judge/models/problem.py:296 judge/models/profile.py:122 msgid "organizations" msgstr "tổ chức" @@ -1743,7 +1758,7 @@ msgstr "tổ chức mở?" #: judge/models/profile.py:60 msgid "Allow joining organization" -msgstr "Cho phép tham gia tổ chức" +msgstr "Cho phép mọi người tham gia tổ chức" #: judge/models/profile.py:64 msgid "maximum size" @@ -1765,138 +1780,138 @@ msgid "" "organization." msgstr "Ảnh này sẽ thay thế logo mặc định khi ở trong tổ chức." -#: judge/models/profile.py:115 judge/models/profile.py:144 -#: judge/models/profile.py:344 +#: judge/models/profile.py:121 judge/models/profile.py:150 +#: judge/models/profile.py:350 msgid "organization" msgstr "" -#: judge/models/profile.py:121 +#: judge/models/profile.py:127 msgid "user associated" msgstr "" -#: judge/models/profile.py:123 +#: judge/models/profile.py:129 msgid "self-description" msgstr "" -#: judge/models/profile.py:126 +#: judge/models/profile.py:132 msgid "location" msgstr "" -#: judge/models/profile.py:132 +#: judge/models/profile.py:138 msgid "preferred language" msgstr "" -#: judge/models/profile.py:140 +#: judge/models/profile.py:146 msgid "last access time" msgstr "" -#: judge/models/profile.py:141 +#: judge/models/profile.py:147 msgid "last IP" msgstr "" -#: judge/models/profile.py:152 +#: judge/models/profile.py:158 msgid "display rank" msgstr "" -#: judge/models/profile.py:160 +#: judge/models/profile.py:166 msgid "comment mute" msgstr "" -#: judge/models/profile.py:161 +#: judge/models/profile.py:167 msgid "Some users are at their best when silent." msgstr "" -#: judge/models/profile.py:165 +#: judge/models/profile.py:171 msgid "unlisted user" msgstr "" -#: judge/models/profile.py:166 +#: judge/models/profile.py:172 msgid "User will not be ranked." msgstr "" -#: judge/models/profile.py:170 +#: judge/models/profile.py:176 #, fuzzy #| msgid "Banned from joining" msgid "banned from voting" msgstr "Bị cấm tham gia" -#: judge/models/profile.py:171 +#: judge/models/profile.py:177 msgid "User will not be able to vote on problems' point values." msgstr "" -#: judge/models/profile.py:176 +#: judge/models/profile.py:182 msgid "user script" msgstr "" -#: judge/models/profile.py:180 +#: judge/models/profile.py:186 msgid "User-defined JavaScript for site customization." msgstr "" -#: judge/models/profile.py:184 +#: judge/models/profile.py:190 msgid "current contest" msgstr "kỳ thi hiện tại" -#: judge/models/profile.py:191 +#: judge/models/profile.py:197 msgid "math engine" msgstr "" -#: judge/models/profile.py:195 +#: judge/models/profile.py:201 msgid "the rendering engine used to render math" msgstr "" -#: judge/models/profile.py:198 +#: judge/models/profile.py:204 msgid "2FA enabled" msgstr "" -#: judge/models/profile.py:200 +#: judge/models/profile.py:206 msgid "check to enable TOTP-based two factor authentication" msgstr "đánh dấu để sử dụng TOTP-based two factor authentication" -#: judge/models/profile.py:206 +#: judge/models/profile.py:212 msgid "TOTP key" msgstr "mã TOTP" -#: judge/models/profile.py:207 +#: judge/models/profile.py:213 msgid "32 character base32-encoded key for TOTP" msgstr "" -#: judge/models/profile.py:209 +#: judge/models/profile.py:215 msgid "TOTP key must be empty or base32" msgstr "" -#: judge/models/profile.py:213 +#: judge/models/profile.py:219 msgid "internal notes" msgstr "ghi chú nội bộ" -#: judge/models/profile.py:216 +#: judge/models/profile.py:222 msgid "Notes for administrators regarding this user." msgstr "Ghi chú riêng cho quản trị viên." -#: judge/models/profile.py:331 +#: judge/models/profile.py:337 msgid "user profile" msgstr "thông tin người dùng" -#: judge/models/profile.py:332 +#: judge/models/profile.py:338 msgid "user profiles" msgstr "thông tin người dùng" -#: judge/models/profile.py:348 +#: judge/models/profile.py:354 msgid "request time" msgstr "thời gian đăng ký" -#: judge/models/profile.py:351 +#: judge/models/profile.py:357 msgid "state" msgstr "trạng thái" -#: judge/models/profile.py:358 +#: judge/models/profile.py:364 msgid "reason" msgstr "lý do" -#: judge/models/profile.py:361 +#: judge/models/profile.py:367 msgid "organization join request" msgstr "đơn đăng ký tham gia" -#: judge/models/profile.py:362 +#: judge/models/profile.py:368 msgid "organization join requests" msgstr "đơn đăng ký tham gia" @@ -2435,7 +2450,8 @@ msgctxt "hours and minutes" msgid "%h:%m" msgstr "%h:%m" -#: judge/views/about.py:10 templates/organization/home.html:105 +#: judge/views/about.py:10 templates/organization/home.html:38 +#: templates/organization/org-right-sidebar.html:7 #: templates/user/user-about.html:83 templates/user/user-tabs.html:4 #: templates/user/users-table.html:32 msgid "About" @@ -2445,16 +2461,16 @@ msgstr "Giới thiệu" msgid "Custom Checker Sample" msgstr "Hướng dẫn viết trình chấm" -#: judge/views/blog.py:128 +#: judge/views/blog.py:112 #, python-format msgid "Page %d of Posts" msgstr "Trang %d" -#: judge/views/blog.py:183 +#: judge/views/blog.py:167 msgid "Ticket feed" msgstr "Báo cáo" -#: judge/views/blog.py:201 +#: judge/views/blog.py:185 msgid "Comment feed" msgstr "Bình luận" @@ -2470,7 +2486,8 @@ msgstr "Bạn phải giải ít nhất 1 bài trước khi được vote." msgid "You already voted." msgstr "Bạn đã vote." -#: judge/views/comment.py:153 judge/views/organization.py:472 +#: judge/views/comment.py:153 judge/views/organization.py:666 +#: judge/views/organization.py:738 msgid "Edited from site" msgstr "Chỉnh sửa từ web" @@ -2478,136 +2495,136 @@ msgstr "Chỉnh sửa từ web" msgid "Editing comment" msgstr "Chỉnh sửa bình luận" -#: judge/views/contests.py:115 judge/views/contests.py:356 -#: judge/views/contests.py:361 judge/views/contests.py:602 +#: judge/views/contests.py:115 judge/views/contests.py:364 +#: judge/views/contests.py:369 judge/views/contests.py:610 msgid "No such contest" msgstr "Không có contest nào như vậy" -#: judge/views/contests.py:116 judge/views/contests.py:357 +#: judge/views/contests.py:116 judge/views/contests.py:365 #, python-format msgid "Could not find a contest with the key \"%s\"." msgstr "Không tìm thấy kỳ thi với mã \"%s\"." -#: judge/views/contests.py:135 +#: judge/views/contests.py:135 templates/organization/org-left-sidebar.html:5 msgid "Contests" msgstr "Kỳ thi" -#: judge/views/contests.py:361 +#: judge/views/contests.py:369 msgid "Could not find such contest." msgstr "Không tìm thấy kỳ thi nào như vậy." -#: judge/views/contests.py:369 +#: judge/views/contests.py:377 #, python-format msgid "Access to contest \"%s\" denied" msgstr "Truy cập tới kỳ thi \"%s\" bị từ chối" -#: judge/views/contests.py:407 +#: judge/views/contests.py:415 msgid "Clone Contest" msgstr "Nhân bản kỳ thi" -#: judge/views/contests.py:476 +#: judge/views/contests.py:484 msgid "Contest not ongoing" msgstr "Kỳ thi đang không diễn ra" -#: judge/views/contests.py:477 +#: judge/views/contests.py:485 #, python-format msgid "\"%s\" is not currently ongoing." msgstr "\"%s\" kỳ thi đang không diễn ra." -#: judge/views/contests.py:484 +#: judge/views/contests.py:492 msgid "Already in contest" msgstr "Đã ở trong kỳ thi" -#: judge/views/contests.py:485 +#: judge/views/contests.py:493 #, python-format msgid "You are already in a contest: \"%s\"." msgstr "Bạn đã ở trong kỳ thi: \"%s\"." -#: judge/views/contests.py:495 +#: judge/views/contests.py:503 msgid "Banned from joining" msgstr "Bị cấm tham gia" -#: judge/views/contests.py:497 +#: judge/views/contests.py:505 msgid "" "You have been declared persona non grata for this contest. You are " "permanently barred from joining this contest." msgstr "Bạn không được phép tham gia kỳ thi này." -#: judge/views/contests.py:586 +#: judge/views/contests.py:594 #, python-format msgid "Enter access code for \"%s\"" msgstr "Nhập mật khẩu truy cập cho \"%s\"" -#: judge/views/contests.py:603 +#: judge/views/contests.py:611 #, python-format msgid "You are not in contest \"%s\"." msgstr "Bạn không ở trong kỳ thi \"%s\"." -#: judge/views/contests.py:626 +#: judge/views/contests.py:634 msgid "ContestCalendar requires integer year and month" msgstr "Lịch thi yêu cầu giá trị cho năm và tháng là số nguyên" -#: judge/views/contests.py:684 +#: judge/views/contests.py:692 #, python-format msgid "Contests in %(month)s" msgstr "Các kỳ thi trong %(month)s" -#: judge/views/contests.py:685 +#: judge/views/contests.py:693 msgid "F Y" msgstr "F Y" -#: judge/views/contests.py:745 +#: judge/views/contests.py:753 #, python-format msgid "%s Statistics" msgstr "%s Thống kê" -#: judge/views/contests.py:1002 +#: judge/views/contests.py:1010 #, python-format msgid "%s Rankings" msgstr "%s Bảng điểm" -#: judge/views/contests.py:1013 +#: judge/views/contests.py:1021 msgid "???" msgstr "???" -#: judge/views/contests.py:1029 +#: judge/views/contests.py:1037 #, python-format msgid "Your participation in %s" msgstr "Lần tham gia trong %s" -#: judge/views/contests.py:1030 +#: judge/views/contests.py:1038 #, python-format msgid "%s's participation in %s" msgstr "Lần tham gia của %s trong %s" -#: judge/views/contests.py:1044 +#: judge/views/contests.py:1052 msgid "Live" msgstr "Trực tiếp" -#: judge/views/contests.py:1063 templates/contest/contest-tabs.html:13 +#: judge/views/contests.py:1071 templates/contest/contest-tabs.html:13 msgid "Participation" msgstr "Lần tham gia" -#: judge/views/contests.py:1112 +#: judge/views/contests.py:1120 #, python-format msgid "%s MOSS Results" msgstr "%s Kết quả MOSS" -#: judge/views/contests.py:1148 +#: judge/views/contests.py:1156 #, python-format msgid "Running MOSS for %s..." msgstr "Đang chạy MOSS cho %s..." -#: judge/views/contests.py:1171 +#: judge/views/contests.py:1179 #, python-format msgid "Contest tag: %s" msgstr "Nhãn kỳ thi: %s" -#: judge/views/contests.py:1186 judge/views/ticket.py:72 +#: judge/views/contests.py:1194 judge/views/ticket.py:72 msgid "Issue description" msgstr "Mô tả vấn đề" -#: judge/views/contests.py:1232 +#: judge/views/contests.py:1240 #, python-format msgid "New clarification for %s" msgstr "Thông báo mới cho %s" @@ -2647,67 +2664,84 @@ msgstr "Runtimes" msgid "Notifications (%d unseen)" msgstr "Thông báo (%d chưa xem)" -#: judge/views/organization.py:93 judge/views/organization.py:99 +#: judge/views/organization.py:117 judge/views/organization.py:123 msgid "No such organization" msgstr "Không có tổ chức như vậy" -#: judge/views/organization.py:94 +#: judge/views/organization.py:118 #, python-format msgid "Could not find an organization with the key \"%s\"." msgstr "Không tìm thấy tổ chức với mã \"%s\"." -#: judge/views/organization.py:100 +#: judge/views/organization.py:124 msgid "Could not find such organization." msgstr "" -#: judge/views/organization.py:119 judge/views/register.py:49 -#: templates/organization/list.html:32 templates/user/import/table_csv.html:9 -#: templates/user/user-list-tabs.html:6 -msgid "Organizations" -msgstr "Tổ chức" +#: judge/views/organization.py:140 +msgid "Can't edit organization" +msgstr "Không thể chỉnh sửa tổ chức" -#: judge/views/organization.py:187 +#: judge/views/organization.py:141 +msgid "You are not allowed to edit this organization." +msgstr "Bạn không được phép chỉnh sửa tổ chức này." + +#: judge/views/organization.py:153 +#, fuzzy +#| msgid "Can't edit organization" +msgid "Can't access organization" +msgstr "Không thể chỉnh sửa tổ chức" + +#: judge/views/organization.py:154 +msgid "You are not allowed to access this organization." +msgstr "Bạn không được phép chỉnh sửa tổ chức này." + +#: judge/views/organization.py:208 judge/views/register.py:49 +#: templates/contest/list.html:109 templates/problem/list-base.html:95 +msgid "Groups" +msgstr "Nhóm" + +#: judge/views/organization.py:283 #, python-format msgid "%s Members" msgstr "%s Thành viên" -#: judge/views/organization.py:232 judge/views/organization.py:238 -#: judge/views/organization.py:245 -msgid "Joining organization" -msgstr "Tham gia tổ chức" +#: judge/views/organization.py:372 judge/views/organization.py:378 +#: judge/views/organization.py:385 +msgid "Joining group" +msgstr "Tham gia nhóm" -#: judge/views/organization.py:233 -msgid "You are already in the organization." -msgstr "Bạn đã ở trong tổ chức." +#: judge/views/organization.py:373 +msgid "You are already in the group." +msgstr "Bạn đã ở trong nhóm." -#: judge/views/organization.py:238 -msgid "This organization is not open." -msgstr "Tổ chức này không phải tổ chức mở." +#: judge/views/organization.py:378 +msgid "This group is not open." +msgstr "Nhóm này là nhóm kín." -#: judge/views/organization.py:261 -msgid "Leaving organization" -msgstr "Rời tổ chức" +#: judge/views/organization.py:401 +msgid "Leaving group" +msgstr "Rời nhóm" -#: judge/views/organization.py:262 +#: judge/views/organization.py:402 #, python-format msgid "You are not in \"%s\"." msgstr "Bạn không ở trong \"%s\"." -#: judge/views/organization.py:287 +#: judge/views/organization.py:427 #, python-format msgid "Request to join %s" msgstr "Đăng ký tham gia %s" -#: judge/views/organization.py:312 +#: judge/views/organization.py:452 msgid "Join request detail" msgstr "Chi tiết đơn đăng ký" -#: judge/views/organization.py:360 +#: judge/views/organization.py:500 #, python-format msgid "Managing join requests for %s" msgstr "Quản lý đơn đăng ký cho %s" -#: judge/views/organization.py:400 +#: judge/views/organization.py:540 #, python-format msgid "" "Your organization can only receive %d more members. You cannot approve %d " @@ -2716,48 +2750,74 @@ msgstr "" "Tổ chức chỉ có thể chứa %d thành viên. Bạn không thể chấp thuận nhiều hơn %d " "người." -#: judge/views/organization.py:418 +#: judge/views/organization.py:558 #, python-format msgid "Approved %d user." msgid_plural "Approved %d users." msgstr[0] "Đã chấp thuận %d người." -#: judge/views/organization.py:421 +#: judge/views/organization.py:561 #, python-format msgid "Rejected %d user." msgid_plural "Rejected %d users." msgstr[0] "Đã từ chối %d người." -#: judge/views/organization.py:455 +#: judge/views/organization.py:595 #, python-format -msgid "Editing %s" -msgstr "Đang chỉnh sửa %s" +msgid "Add member for %s" +msgstr "" -#: judge/views/organization.py:482 judge/views/organization.py:496 -msgid "Can't edit organization" -msgstr "Không thể chỉnh sửa tổ chức" +#: judge/views/organization.py:607 +#, fuzzy +#| msgid "Edited from site" +msgid "Added members from site" +msgstr "Chỉnh sửa từ web" -#: judge/views/organization.py:483 -msgid "You are not allowed to edit this organization." -msgstr "Bạn không được phép chỉnh sửa tổ chức này." - -#: judge/views/organization.py:497 -msgid "You are not allowed to kick people from this organization." -msgstr "Bạn không được phép đuổi người." - -#: judge/views/organization.py:506 judge/views/organization.py:514 +#: judge/views/organization.py:625 judge/views/organization.py:633 msgid "Can't kick user" msgstr "Không thể đuổi" -#: judge/views/organization.py:507 +#: judge/views/organization.py:626 msgid "The user you are trying to kick does not exist!" msgstr "" -#: judge/views/organization.py:515 +#: judge/views/organization.py:634 #, python-format msgid "The user you are trying to kick is not in organization: %s." msgstr "" +#: judge/views/organization.py:649 +#, fuzzy, python-format +#| msgid "Editing %s" +msgid "Edit %s" +msgstr "Đang chỉnh sửa %s" + +#: judge/views/organization.py:677 +#, python-format +msgid "Add blog for %s" +msgstr "Thêm bài đăng cho %s" + +#: judge/views/organization.py:688 +msgid "Added from site" +msgstr "Thêm từ web" + +#: judge/views/organization.py:713 +msgid "Permission denied" +msgstr "Truy cập bị từ chối" + +#: judge/views/organization.py:714 +msgid "Not allowed to edit this blog" +msgstr "Bạn không được phép chỉnh sửa bài đăng này." + +#: judge/views/organization.py:733 +msgid "Edit blog %s" +msgstr "Chỉnh sửa %s" + +#: judge/views/organization.py:758 +#, python-format +msgid "Pending blogs in %s" +msgstr "Bài đang đợi duyệt trong %s" + #: judge/views/problem.py:108 msgid "No such problem" msgstr "Không có bài nào như vậy" @@ -2778,39 +2838,40 @@ msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" #: judge/views/problem.py:463 templates/contest/contest.html:79 +#: templates/organization/org-left-sidebar.html:4 #: templates/user/user-about.html:28 templates/user/user-tabs.html:5 #: templates/user/users-table.html:29 msgid "Problems" msgstr "Bài tập" -#: judge/views/problem.py:815 +#: judge/views/problem.py:827 msgid "Problem feed" msgstr "Bài tập" -#: judge/views/problem.py:1027 +#: judge/views/problem.py:1040 msgid "Banned from submitting" msgstr "Bị cấm nộp bài" -#: judge/views/problem.py:1029 +#: judge/views/problem.py:1042 msgid "" "You have been declared persona non grata for this problem. You are " "permanently barred from submitting this problem." msgstr "Bạn đã bị cấm nộp bài này." -#: judge/views/problem.py:1052 +#: judge/views/problem.py:1065 msgid "Too many submissions" msgstr "Quá nhiều lần nộp" -#: judge/views/problem.py:1054 +#: judge/views/problem.py:1067 msgid "You have exceeded the submission limit for this problem." msgstr "Bạn đã vượt quá số lần nộp cho bài này." -#: judge/views/problem.py:1133 judge/views/problem.py:1138 +#: judge/views/problem.py:1146 judge/views/problem.py:1151 #, python-format msgid "Submit to %(problem)s" msgstr "Nộp bài cho %(problem)s" -#: judge/views/problem.py:1160 +#: judge/views/problem.py:1173 msgid "Clone Problem" msgstr "Nhân bản bài tập" @@ -3322,8 +3383,11 @@ msgstr "Xin chào, %(username)s." #: templates/base.html:259 templates/chat/chat.html:20 #: templates/comments/list.html:89 templates/contest/contest-list-tabs.html:24 -#: templates/contest/ranking-table.html:53 templates/internal/base.html:59 -#: templates/problem/list.html:249 templates/problem/problem-list-tabs.html:6 +#: templates/contest/list.html:130 templates/contest/ranking-table.html:53 +#: templates/internal/base.html:59 +#: templates/organization/org-left-sidebar.html:9 +#: templates/problem/left-sidebar.html:5 templates/problem/list-base.html:249 +#: templates/problem/problem-list-tabs.html:6 #: templates/submission/info-base.html:12 #: templates/submission/submission-list-tabs.html:15 msgid "Admin" @@ -3372,7 +3436,7 @@ msgstr "" #: templates/comments/list.html:83 templates/contest/contest-tabs.html:23 #: templates/contest/tag-title.html:9 templates/flatpages/admin_link.html:3 #: templates/license.html:10 templates/problem/editorial.html:14 -#: templates/problem/feed.html:38 +#: templates/problem/feed.html:58 msgid "Edit" msgstr "Chỉnh sửa" @@ -3412,8 +3476,8 @@ msgstr "Sự kiện" msgid "You have no ticket" msgstr "Bạn không có báo cáo" -#: templates/blog/list.html:94 templates/problem/list.html:392 -#: templates/problem/problem.html:407 +#: templates/blog/list.html:94 templates/problem/list-base.html:392 +#: templates/problem/list.html:143 templates/problem/problem.html:407 msgid "Clarifications" msgstr "Thông báo" @@ -3421,31 +3485,11 @@ msgstr "Thông báo" msgid "Add" msgstr "Thêm mới" -#: templates/blog/list.html:119 templates/problem/list.html:414 -#: templates/problem/problem.html:418 +#: templates/blog/list.html:119 templates/problem/list-base.html:414 +#: templates/problem/list.html:165 templates/problem/problem.html:418 msgid "No clarifications have been made at this time." msgstr "Không có thông báo nào." -#: templates/blog/list.html:127 -msgid "Ongoing contests" -msgstr "Kỳ thi đang diễn ra" - -#: templates/blog/list.html:135 -msgid "Ends in" -msgstr "Còn" - -#: templates/blog/list.html:145 -msgid "Upcoming contests" -msgstr "Kỳ thi sắp diễn ra" - -#: templates/blog/list.html:161 -msgid "Top Rating" -msgstr "Top Rating" - -#: templates/blog/list.html:177 -msgid "Top Score" -msgstr "Top Score" - #: templates/chat/chat.html:18 msgid "Recent" msgstr "Gần đây" @@ -3463,6 +3507,8 @@ msgid "New message(s)" msgstr "Tin nhắn mới" #: templates/chat/chat.html:519 templates/chat/chat.html:600 +#: templates/user/base-users-three-col.html:14 +#: templates/user/base-users-three-col.html:88 #: templates/user/base-users.html:14 templates/user/base-users.html:80 msgid "Search by handle..." msgstr "Tìm kiếm theo tên..." @@ -3539,8 +3585,8 @@ msgstr "Link" msgid "Reply" msgstr "Trả lời" -#: templates/comments/list.html:86 templates/contest/list.html:91 -#: templates/contest/list.html:95 templates/contest/list.html:281 +#: templates/comments/list.html:86 templates/contest/list.html:98 +#: templates/contest/list.html:102 templates/contest/list.html:289 msgid "Hide" msgstr "Ẩn" @@ -3659,16 +3705,17 @@ msgstr "Hôm nay" msgid "Next" msgstr "Tiếp" -#: templates/contest/contest-list-tabs.html:21 templates/problem/list.html:248 +#: templates/contest/contest-list-tabs.html:21 templates/contest/list.html:128 +#: templates/problem/left-sidebar.html:4 templates/problem/list-base.html:248 #: templates/problem/problem-list-tabs.html:5 msgid "List" msgstr "Danh sách" -#: templates/contest/contest-list-tabs.html:22 +#: templates/contest/contest-list-tabs.html:22 templates/contest/list.html:129 msgid "Calendar" msgstr "Lịch" -#: templates/contest/contest-tabs.html:4 templates/organization/home.html:98 +#: templates/contest/contest-tabs.html:4 msgid "Info" msgstr "Thông tin" @@ -3698,7 +3745,7 @@ msgstr "Nhân bản" msgid "Leave contest" msgstr "Rời kỳ thi" -#: templates/contest/contest-tabs.html:45 templates/contest/list.html:393 +#: templates/contest/contest-tabs.html:45 templates/contest/list.html:401 msgid "Virtual join" msgstr "Tham gia ảo" @@ -3778,22 +3825,24 @@ msgstr "Kéo dài %(length)s bắt đầu từ %(start_time)s" msgid "AC Rate" msgstr "Tỷ lệ AC" -#: templates/contest/contest.html:86 templates/contest/list.html:236 -#: templates/contest/list.html:290 templates/contest/list.html:370 -#: templates/problem/list.html:270 templates/problem/list.html:299 +#: templates/contest/contest.html:86 templates/contest/list.html:244 +#: templates/contest/list.html:298 templates/contest/list.html:378 +#: templates/problem/list-base.html:270 templates/problem/list-base.html:299 +#: templates/problem/list.html:21 templates/problem/list.html:50 msgid "Users" msgstr "Số lượng" -#: templates/contest/contest.html:111 templates/problem/list.html:303 -#: templates/problem/list.html:375 +#: templates/contest/contest.html:111 templates/problem/list-base.html:303 +#: templates/problem/list-base.html:375 templates/problem/list.html:54 +#: templates/problem/list.html:126 msgid "Editorial" msgstr "Hướng dẫn" -#: templates/contest/list.html:83 templates/contest/media-js.html:9 +#: templates/contest/list.html:90 templates/contest/media-js.html:9 msgid "Are you sure you want to join?" msgstr "Bạn có chắc tham gia?" -#: templates/contest/list.html:84 +#: templates/contest/list.html:91 msgid "" "Joining a contest for the first time starts your timer, after which it " "becomes unstoppable." @@ -3801,84 +3850,80 @@ msgstr "" "Tham gia kỳ thi lần đầu sẽ kích hoạt thời gian đếm ngược, không thể dừng lại " "sau đó." -#: templates/contest/list.html:92 templates/contest/list.html:279 +#: templates/contest/list.html:99 templates/contest/list.html:287 msgid "Show" msgstr "Hiển thị" -#: templates/contest/list.html:102 templates/problem/list.html:95 -msgid "Organizations..." -msgstr "Tổ chức..." - -#: templates/contest/list.html:135 +#: templates/contest/list.html:142 msgid "hidden" msgstr "ẩn" -#: templates/contest/list.html:140 +#: templates/contest/list.html:147 msgid "private" msgstr "riêng tư" -#: templates/contest/list.html:154 +#: templates/contest/list.html:161 msgid "rated" msgstr "rated" -#: templates/contest/list.html:180 +#: templates/contest/list.html:187 #, python-format msgid "%(time_limit)s window" msgstr "Cửa sổ thi dài %(time_limit)s" -#: templates/contest/list.html:182 +#: templates/contest/list.html:189 #, python-format msgid "%(duration)s long" msgstr "Kéo dài %(duration)s" -#: templates/contest/list.html:202 +#: templates/contest/list.html:209 msgid "Spectate" msgstr "Theo dõi" -#: templates/contest/list.html:208 +#: templates/contest/list.html:215 templates/organization/home.html:16 msgid "Join" msgstr "Tham gia" -#: templates/contest/list.html:219 +#: templates/contest/list.html:226 msgid "Search contests..." msgstr "Tìm kiếm kỳ thi..." -#: templates/contest/list.html:228 +#: templates/contest/list.html:236 msgid "Search" msgstr "Tìm kiếm" -#: templates/contest/list.html:231 +#: templates/contest/list.html:239 msgid "Active Contests" msgstr "Kỳ thi bạn đang tham gia" -#: templates/contest/list.html:235 templates/contest/list.html:289 -#: templates/contest/list.html:328 templates/contest/list.html:367 +#: templates/contest/list.html:243 templates/contest/list.html:297 +#: templates/contest/list.html:336 templates/contest/list.html:375 msgid "Contest" msgstr "Kỳ thi" -#: templates/contest/list.html:253 +#: templates/contest/list.html:261 #, python-format msgid "Window ends in %(countdown)s" msgstr "Cửa số thi còn %(countdown)s" -#: templates/contest/list.html:256 templates/contest/list.html:305 +#: templates/contest/list.html:264 templates/contest/list.html:313 #, python-format msgid "Ends in %(countdown)s" msgstr "Kết thúc trong %(countdown)s" -#: templates/contest/list.html:276 +#: templates/contest/list.html:284 msgid "Ongoing Contests" msgstr "Kỳ thi đang diễn ra" -#: templates/contest/list.html:323 +#: templates/contest/list.html:331 msgid "Upcoming Contests" msgstr "Kỳ thi sắp tới" -#: templates/contest/list.html:351 +#: templates/contest/list.html:359 msgid "There are no scheduled contests at this time." msgstr "Không có kỳ thi nào được lên lịch hiện tại." -#: templates/contest/list.html:357 +#: templates/contest/list.html:365 msgid "Past Contests" msgstr "Kỳ thi trong quá khứ" @@ -3931,7 +3976,7 @@ msgstr "Thêm vào đó, chỉ những tổ chức này mới được tham gia msgid "Only the following organizations may access this contest:" msgstr "Chỉ những tổ chức sau được tham gia kỳ thi:" -#: templates/contest/ranking-table.html:9 templates/problem/search-form.html:36 +#: templates/contest/ranking-table.html:9 msgid "Organization" msgstr "Tổ chức" @@ -3999,6 +4044,18 @@ msgstr "Số bài nộp theo ngôn ngữ" msgid "Language AC Rate" msgstr "Tỷ lệ AC theo ngôn ngữ" +#: templates/contests-countdown.html:3 +msgid "Ongoing contests" +msgstr "Kỳ thi đang diễn ra" + +#: templates/contests-countdown.html:11 +msgid "Ends in" +msgstr "Còn" + +#: templates/contests-countdown.html:21 +msgid "Upcoming contests" +msgstr "Kỳ thi sắp diễn ra" + #: templates/fine_uploader/script.html:4 msgid "Drop files here to upload" msgstr "" @@ -4049,12 +4106,13 @@ msgstr "" msgid "Thinking" msgstr "Bảng xếp hạng" -#: templates/internal/base.html:78 templates/problem/list.html:267 -#: templates/problem/list.html:289 +#: templates/internal/base.html:78 templates/problem/list-base.html:267 +#: templates/problem/list-base.html:289 templates/problem/list.html:18 +#: templates/problem/list.html:40 msgid "Types" msgstr "Dạng" -#: templates/internal/base.html:82 templates/problem/feed.html:84 +#: templates/internal/base.html:82 templates/problem/feed.html:104 #, fuzzy #| msgid "Feed" msgid "Feedback" @@ -4159,102 +4217,105 @@ msgstr "Bạn không có thông báo" msgid "Activity" msgstr "Hoạt động" -#: templates/organization/edit.html:46 -#: templates/organization/requests/pending.html:34 -#: templates/ticket/edit-notes.html:4 -msgid "Update" -msgstr "Cập nhật" +#: templates/organization/blog/pending.html:8 +msgid "Blog" +msgstr "" -#: templates/organization/home.html:32 +#: templates/organization/blog/pending.html:11 +#: templates/problem/search-form.html:49 +msgid "Author" +msgstr "Tác giả" + +#: templates/organization/blog/pending.html:14 +#, fuzzy +#| msgid "posted time" +msgid "Post time" +msgstr "thời gian đăng" + +#: templates/organization/form.html:23 +msgid "Save" +msgstr "" + +#: templates/organization/home-js.html:7 msgid "Are you sure you want to leave this organization?" msgstr "Bạn có chắc muốn rời tổ chức?" -#: templates/organization/home.html:34 +#: templates/organization/home-js.html:9 msgid "You will have to rejoin to show up on the organization leaderboard." msgstr "Bạn phải tham gia lại để được hiển thị trong bảng xếp hạng tổ chức." -#: templates/organization/home.html:36 +#: templates/organization/home-js.html:11 msgid "You will have to request membership in order to join again." msgstr "Bạn phải đăng ký thành viên để được tham gia lại." -#: templates/organization/home.html:81 -msgid "Join organization" -msgstr "Tham gia tổ chức" - -#: templates/organization/home.html:85 +#: templates/organization/home.html:20 msgid "Request membership" msgstr "Đăng ký thành viên" -#: templates/organization/home.html:115 -msgid "Organization news" -msgstr "Tin tức tổ chức" +#: templates/organization/list.html:31 templates/problem/search-form.html:38 +#: templates/user/user-list-tabs.html:6 +msgid "Group" +msgstr "" -#: templates/organization/home.html:121 -msgid "There is no news at this time." -msgstr "Không có tin tức." +#: templates/organization/list.html:39 +#, fuzzy +#| msgid "Show my tickets only" +msgid "Show my groups only" +msgstr "Chỉ hiển thị các báo cáo dành cho tôi" -#: templates/organization/home.html:130 -msgid "Controls" -msgstr "Quản lý" - -#: templates/organization/home.html:135 -msgid "Edit organization" -msgstr "Chỉnh sửa" - -#: templates/organization/home.html:141 -msgid "View requests" -msgstr "Đơn đăng ký" - -#: templates/organization/home.html:154 -msgid "Admin organization" -msgstr "Trang admin tổ chức" - -#: templates/organization/home.html:160 -msgid "View members" -msgstr "Xem thành viên" - -#: templates/organization/home.html:167 -msgid "Leave organization" -msgstr "Rời tổ chức" - -#: templates/organization/home.html:176 -msgid "New private contests" -msgstr "Kỳ thi riêng tư mới" - -#: templates/organization/home.html:186 templates/organization/home.html:201 -msgid "View all" -msgstr "Tất cả" - -#: templates/organization/home.html:192 -msgid "New private problems" -msgstr "Bài tập riêng tư mới" - -#: templates/organization/list.html:40 -msgid "Show my organizations only" -msgstr "Chỉ hiển thị tổ chức của tôi" - -#: templates/organization/list.html:47 templates/status/language-list.html:34 +#: templates/organization/list.html:46 templates/status/language-list.html:34 #: templates/user/import/table_csv.html:6 msgid "Name" msgstr "Tên" -#: templates/organization/list.html:48 +#: templates/organization/list.html:47 +#: templates/organization/org-left-sidebar.html:7 msgid "Members" msgstr "Thành viên" -#: templates/organization/requests/detail.html:13 +#: templates/organization/org-right-sidebar.html:19 +msgid "Controls" +msgstr "Quản lý" + +#: templates/organization/org-right-sidebar.html:24 +msgid "Edit organization" +msgstr "Chỉnh sửa" + +#: templates/organization/org-right-sidebar.html:31 +msgid "View requests" +msgstr "Đơn đăng ký" + +#: templates/organization/org-right-sidebar.html:43 +#, fuzzy +#| msgid "View members" +msgid "Add members" +msgstr "Xem thành viên" + +#: templates/organization/org-right-sidebar.html:50 +msgid "Add blog" +msgstr "Thêm bài đăng" + +#: templates/organization/org-right-sidebar.html:56 +msgid "Pending blogs" +msgstr "Bài đăng đang chờ" + +#: templates/organization/org-right-sidebar.html:68 +msgid "Leave group" +msgstr "Rời nhóm" + +#: templates/organization/requests/detail.html:6 msgid "User:" msgstr "Thành viên:" -#: templates/organization/requests/detail.html:17 +#: templates/organization/requests/detail.html:10 msgid "Organization:" msgstr "Tổ chức:" -#: templates/organization/requests/detail.html:25 +#: templates/organization/requests/detail.html:18 msgid "Time:" msgstr "Thời gian:" -#: templates/organization/requests/detail.html:29 +#: templates/organization/requests/detail.html:22 msgid "Reason:" msgstr "Lý do:" @@ -4278,6 +4339,11 @@ msgstr "Không có đơn đăng ký." msgid "Delete?" msgstr "Xóa?" +#: templates/organization/requests/pending.html:34 +#: templates/ticket/edit-notes.html:4 +msgid "Update" +msgstr "Cập nhật" + #: templates/organization/requests/request.html:18 msgid "Your reason for joining:" msgstr "Lý do tham gia:" @@ -4353,80 +4419,80 @@ msgstr "" "viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là " "hành vi có thể dẫn đến khóa tài khoản." -#: templates/problem/feed.html:36 +#: templates/problem/feed.html:9 templates/problem/list-base.html:423 +msgid "FOR YOU" +msgstr "DÀNH CHO BẠN" + +#: templates/problem/feed.html:12 templates/problem/list-base.html:426 +msgid "NEW" +msgstr "MỚI NHẤT" + +#: templates/problem/feed.html:16 templates/problem/list-base.html:430 +msgid "VOLUNTEER" +msgstr "TÌNH NGUYỆN" + +#: templates/problem/feed.html:56 msgid "Volunteer form" msgstr "Phiếu tình nguyện" -#: templates/problem/feed.html:40 +#: templates/problem/feed.html:60 msgid "Submit" msgstr "Gửi" -#: templates/problem/feed.html:47 +#: templates/problem/feed.html:67 msgid "Value" msgstr "Giá trị" -#: templates/problem/feed.html:54 +#: templates/problem/feed.html:74 msgid "Knowledge point" msgstr "Độ khó kiến thức" -#: templates/problem/feed.html:62 +#: templates/problem/feed.html:82 msgid "Thinking point" msgstr "Độ khó nghĩ" -#: templates/problem/feed.html:70 templates/problem/search-form.html:73 +#: templates/problem/feed.html:90 templates/problem/search-form.html:75 msgid "Problem types" msgstr "Dạng bài" -#: templates/problem/feed.html:87 +#: templates/problem/feed.html:107 msgid "Any additional note here" msgstr "Lưu ý thêm cho admin" -#: templates/problem/list.html:93 -msgid "Filter by type..." -msgstr "Lọc theo dạng..." - -#: templates/problem/list.html:152 templates/problem/list.html:178 -msgid "Add types..." -msgstr "Thêm dạng" - -#: templates/problem/list.html:194 -msgid "Fail to vote!" -msgstr "Hệ thống lỗi!" - -#: templates/problem/list.html:197 -msgid "Successful vote! Thank you!" -msgstr "Đã gửi thành công! Cảm ơn bạn!" - -#: templates/problem/list.html:247 +#: templates/problem/left-sidebar.html:3 templates/problem/list-base.html:247 msgid "Feed" msgstr "Gợi ý" -#: templates/problem/list.html:285 templates/problem/search-form.html:56 -#: templates/user/user-problems.html:57 -msgid "Category" -msgstr "Nhóm" +#: templates/problem/list-base.html:93 +msgid "Filter by type..." +msgstr "Lọc theo dạng..." -#: templates/problem/list.html:296 +#: templates/problem/list-base.html:152 templates/problem/list-base.html:178 +msgid "Add types..." +msgstr "Thêm dạng" + +#: templates/problem/list-base.html:194 +msgid "Fail to vote!" +msgstr "Hệ thống lỗi!" + +#: templates/problem/list-base.html:197 +msgid "Successful vote! Thank you!" +msgstr "Đã gửi thành công! Cảm ơn bạn!" + +#: templates/problem/list-base.html:285 templates/problem/list.html:36 +#: templates/problem/search-form.html:59 templates/user/user-problems.html:57 +msgid "Category" +msgstr "Nhóm bài" + +#: templates/problem/list-base.html:296 templates/problem/list.html:47 #, python-format msgid "AC %%" msgstr "AC %%" -#: templates/problem/list.html:387 +#: templates/problem/list-base.html:387 templates/problem/list.html:138 msgid "Add clarifications" msgstr "Thêm thông báo" -#: templates/problem/list.html:423 -msgid "FOR YOU" -msgstr "DÀNH CHO BẠN" - -#: templates/problem/list.html:426 -msgid "NEW" -msgstr "MỚI NHẤT" - -#: templates/problem/list.html:430 -msgid "VOLUNTEER" -msgstr "TÌNH NGUYỆN" - #: templates/problem/manage_submission.html:55 msgid "Leave empty to not filter by language" msgstr "Để trống nếu không lọc theo ngôn ngữ" @@ -4659,29 +4725,25 @@ msgstr "Hiển thị dạng bài" msgid "Show editorial" msgstr "Hiển thị hướng dẫn" -#: templates/problem/search-form.html:32 +#: templates/problem/search-form.html:33 msgid "Have editorial" msgstr "Có hướng dẫn" -#: templates/problem/search-form.html:46 -msgid "Author" -msgstr "Tác giả" - -#: templates/problem/search-form.html:59 templates/problem/search-form.html:61 +#: templates/problem/search-form.html:62 templates/problem/search-form.html:64 #: templates/submission/submission-list-tabs.html:4 msgid "All" msgstr "Tất cả" -#: templates/problem/search-form.html:84 +#: templates/problem/search-form.html:86 msgid "Point range" msgstr "Mốc điểm" -#: templates/problem/search-form.html:90 templates/submission/list.html:331 +#: templates/problem/search-form.html:92 templates/submission/list.html:331 #: templates/ticket/list.html:248 msgid "Go" msgstr "Lọc" -#: templates/problem/search-form.html:91 +#: templates/problem/search-form.html:93 msgid "Random" msgstr "Ngẫu nhiên" @@ -5229,6 +5291,14 @@ msgstr "Lưu ý cho người ủy thác" msgid "Nothing here." msgstr "Không có gì." +#: templates/top-users.html:3 +msgid "Top Rating" +msgstr "Top Rating" + +#: templates/top-users.html:22 +msgid "Top Score" +msgstr "Top Score" + #: templates/user/base-users-table.html:3 msgid "Rank" msgstr "Rank" @@ -5305,6 +5375,10 @@ msgstr "" msgid "School" msgstr "" +#: templates/user/import/table_csv.html:9 +msgid "Organizations" +msgstr "Tổ chức" + #: templates/user/pp-row.html:22 #, python-format msgid "" @@ -5496,6 +5570,42 @@ msgstr "Thông tin" msgid "Check all" msgstr "Chọn tất cả" +#~ msgid "You may not be part of more than {count} public organizations." +#~ msgstr "Bạn không thể tham gia nhiều hơn {count} tổ chức công khai." + +#~ msgid "Join organization" +#~ msgstr "Tham gia tổ chức" + +#~ msgid "Organizations..." +#~ msgstr "Tổ chức..." + +#~ msgid "Show my organizations only" +#~ msgstr "Chỉ hiển thị tổ chức của tôi" + +#~ msgid "Leave organization" +#~ msgstr "Rời tổ chức" + +#~ msgid "You are not allowed to kick people from this organization." +#~ msgstr "Bạn không được phép đuổi người." + +#~ msgid "Organization news" +#~ msgstr "Tin tức tổ chức" + +#~ msgid "There is no news at this time." +#~ msgstr "Không có tin tức." + +#~ msgid "Admin organization" +#~ msgstr "Trang admin tổ chức" + +#~ msgid "New private contests" +#~ msgstr "Kỳ thi riêng tư mới" + +#~ msgid "View all" +#~ msgstr "Tất cả" + +#~ msgid "New private problems" +#~ msgstr "Bài tập riêng tư mới" + #~ msgid "Hot problems" #~ msgstr "Bài tập mới" diff --git a/resources/organization.scss b/resources/organization.scss new file mode 100644 index 0000000..b0356ab --- /dev/null +++ b/resources/organization.scss @@ -0,0 +1,26 @@ +.leave-organization, .leave-organization:hover { + color: red; +} +#control-list li { + border-bottom: 1px solid black; +} +#pending-count-box { + float: right; + text-align: center; + background: red; + color: white; + border-radius: 3px; + padding-left: 0.3em; + padding-right: 0.3em; +} +.org-field-wrapper { + input[type=text], textarea { + width: 100%; + } + .select2 { + width: 100% !important; + } +} +.org-field-wrapper { + margin-top: 0.4em; +} \ No newline at end of file diff --git a/resources/style.scss b/resources/style.scss index 98d9ede..5fd4a80 100644 --- a/resources/style.scss +++ b/resources/style.scss @@ -13,3 +13,4 @@ @import "contest"; @import "misc"; @import "chatbox"; +@import "organization"; diff --git a/templates/contest/list.html b/templates/contest/list.html index ed8b95e..36f81bb 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -106,7 +106,7 @@ makeToggleBtn($("#ongoing-btn"), $("#ongoing-table")); - $('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'}) + $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'}) .css({'visibility': 'visible'}); // var tooltip_classes = 'tooltipped tooltipped-e'; diff --git a/templates/organization/add-member.html b/templates/organization/add-member.html new file mode 100644 index 0000000..3a562e4 --- /dev/null +++ b/templates/organization/add-member.html @@ -0,0 +1,9 @@ +{% extends "organization/home-base.html" %} + +{% block org_js %} + {{ form.media.js }} +{% endblock %} + +{% block middle_content %} + {% include "organization/form.html" %} +{% endblock %} diff --git a/templates/organization/blog/add.html b/templates/organization/blog/add.html new file mode 100644 index 0000000..1a29587 --- /dev/null +++ b/templates/organization/blog/add.html @@ -0,0 +1,19 @@ +{% extends "organization/home-base.html" %} + +{% block three_col_js %} + {{ form.media.js }} + {% include "organization/home-js.html" %} +{% endblock %} + +{% block three_col_media %} + {{ form.media.css }} + +{% endblock %} + +{% block middle_content %} + {% include "organization/form.html" %} +{% endblock %} \ No newline at end of file diff --git a/templates/organization/blog/pending.html b/templates/organization/blog/pending.html new file mode 100644 index 0000000..4df3608 --- /dev/null +++ b/templates/organization/blog/pending.html @@ -0,0 +1,28 @@ +{% extends "organization/home-base.html" %} + +{% block middle_content %} + + + + + + + + + + {% for blog in blogs %} + + + + + + {% endfor %} + +
+ {{_('Blog')}} + + {{_('Author')}} + + {{_('Post time')}} +
{{blog.title}}{{link_users(blog.authors.all())}}{{- blog.publish_on|date(_("N j, Y, g:i a")) -}}
+{% endblock %} \ No newline at end of file diff --git a/templates/organization/edit.html b/templates/organization/edit.html index 996c2b9..9df98b0 100644 --- a/templates/organization/edit.html +++ b/templates/organization/edit.html @@ -1,6 +1,6 @@ -{% extends "base.html" %} +{% extends "organization/home-base.html" %} -{% block js_media %} +{% block org_js %} {{ form.media.js }} {% endblock %} -{% block media %} +{% block three_col_media %} {{ form.media.css }} - - - {% endblock %} -{% block body %} -
- {% csrf_token %} - {{ form.as_table() }}
- -
-{% endblock %} \ No newline at end of file +{% block middle_content %} + {% include "organization/form.html" %} +{% endblock %} diff --git a/templates/organization/form.html b/templates/organization/form.html new file mode 100644 index 0000000..332e4ad --- /dev/null +++ b/templates/organization/form.html @@ -0,0 +1,24 @@ +
+ {% csrf_token %} + {% if form.errors %} +
+ x + {{ form.non_field_errors() }} +
+ {% endif %} + {% for field in form %} + {% if not field.is_hidden %} +
+ {{ field.errors }} + +
+ {{ field }} +
+ {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} +
+ {% endif %} + {% endfor %} + +
\ No newline at end of file diff --git a/templates/organization/home-base.html b/templates/organization/home-base.html new file mode 100644 index 0000000..0291acd --- /dev/null +++ b/templates/organization/home-base.html @@ -0,0 +1,21 @@ +{% extends "three-column-content.html" %} + +{% block three_col_js %} + {% include "organization/home-js.html" %} + {% block org_js %}{% endblock %} +{% endblock %} + +{% block left_sidebar %} + {% include "organization/org-left-sidebar.html" %} +{% endblock %} + +{% block right_sidebar %} + {% include "organization/org-right-sidebar.html" %} +{% endblock %} + +{% block middle_title %} + {% if title %} +

{{ title }}

+
+ {% endif %} +{% endblock %} diff --git a/templates/organization/home-js.html b/templates/organization/home-js.html new file mode 100644 index 0000000..9725bdb --- /dev/null +++ b/templates/organization/home-js.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/templates/organization/home.html b/templates/organization/home.html index 2be494a..6e1cc30 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -1,79 +1,31 @@ -{% extends "three-column-content.html" %} -{% block three_col_media %} - -{% endblock %} - -{% block three_col_js %} - -{% endblock %} - -{% block left_sidebar %} - {% include "organization/org-left-sidebar.html" %} -{% endblock %} +{% extends "organization/home-base.html" %} {% block title_ruler %}{% endblock %} -{% block middle_content %} - {% block before_posts %}{% endblock %} + +{% block middle_title %}
-

{{title}}

- - - {% if request.user.is_authenticated %} - {% if is_member or can_edit %} - - {% elif organization.is_open or can_edit %} -
- {% csrf_token %} - -
- {% else %} - {{ _('Request membership') }} - {% endif %} +

{{title}}

+ + + {% if request.user.is_authenticated %} + {% if is_member or can_edit %} + {% elif organization.is_open or can_edit %} +
+ {% csrf_token %} + +
+ {% else %} + {{ _('Request membership') }} {% endif %} -
+ {% endif %}
+ +{% endblock %} + +{% block middle_content %} + {% block before_posts %}{% endblock %} {% if is_member or can_edit %} {% for post in posts %} {% include "blog/content.html" %} @@ -95,56 +47,3 @@ {% endif %} {% block after_posts %}{% endblock %} {% endblock %} - -{% block right_sidebar %} - -{% endblock %} \ No newline at end of file diff --git a/templates/organization/list.html b/templates/organization/list.html index 364f821..8d6feb7 100644 --- a/templates/organization/list.html +++ b/templates/organization/list.html @@ -22,14 +22,13 @@ {% endif %} }); - {% endblock %} {% block title_ruler %}{% endblock %} {% block title_row %} {% set tab = 'organizations' %} - {% set title = _('Organizations') %} + {% set title = _('Group') %} {% include "user/user-list-tabs.html" %} {% endblock %} @@ -37,7 +36,7 @@ {% if request.user.is_authenticated %}
- +
{% endif %} diff --git a/templates/organization/org-right-sidebar.html b/templates/organization/org-right-sidebar.html new file mode 100644 index 0000000..55cecae --- /dev/null +++ b/templates/organization/org-right-sidebar.html @@ -0,0 +1,76 @@ + \ No newline at end of file diff --git a/templates/organization/requests/detail.html b/templates/organization/requests/detail.html index 562be13..8e18e20 100644 --- a/templates/organization/requests/detail.html +++ b/templates/organization/requests/detail.html @@ -1,14 +1,7 @@ -{% extends "base.html" %} -{% block media %} - -{% endblock %} +{% extends "organization/home-base.html" %} -{% block body %} - +{% block middle_content %} +
@@ -26,10 +19,8 @@ - - - - + +
{{ _('User:') }} {{ link_user(object.user) }}{{ object.time|date(_("N j, Y, g:i a")) }}
{{ _('Reason:') }}
{{ object.reason }}{{ _('Reason:') }}{{ object.reason }}
{% endblock %} \ No newline at end of file diff --git a/templates/organization/requests/log.html b/templates/organization/requests/log.html index 73a7b8c..a70f2ab 100644 --- a/templates/organization/requests/log.html +++ b/templates/organization/requests/log.html @@ -1,6 +1,6 @@ -{% extends "base.html" %} +{% extends "organization/home-base.html" %} -{% block body %} +{% block middle_content %} {% include "organization/requests/tabs.html" %} {% if requests %} @@ -16,7 +16,7 @@ {{ link_user(r.user) }} - {{- r.time|date(_("N j, Y, H:i")) -}} + {{- r.time|date(_("N j, Y, g:i a")) -}} {{ r.state }} diff --git a/templates/organization/requests/pending.html b/templates/organization/requests/pending.html index 3d300a4..c542fad 100644 --- a/templates/organization/requests/pending.html +++ b/templates/organization/requests/pending.html @@ -1,5 +1,5 @@ -{% extends "base.html" %} -{% block body %} +{% extends "organization/home-base.html" %} +{% block middle_content %} {% include "messages.html" %} {% include "organization/requests/tabs.html" %} @@ -21,7 +21,7 @@ {{ form.id }}{{ link_user(form.instance.user) }} - {{ form.instance.time|date(_("N j, Y, H:i")) }} + {{ form.instance.time|date(_("N j, Y, g:i a")) }} {{ form.state }} {{ form.instance.reason|truncatechars(50) }} diff --git a/templates/organization/requests/request.html b/templates/organization/requests/request.html index dff6e8d..b3666e8 100644 --- a/templates/organization/requests/request.html +++ b/templates/organization/requests/request.html @@ -1,6 +1,6 @@ -{% extends "base.html" %} +{% extends "organization/home-base.html" %} -{% block js_media %} +{% block org_js %} {% endblock %} -{% block body %} +{% block middle_content %}
{% csrf_token %}

diff --git a/templates/problem/list-base.html b/templates/problem/list-base.html index 69ea5ec..2eabfe6 100644 --- a/templates/problem/list-base.html +++ b/templates/problem/list-base.html @@ -92,7 +92,7 @@ $category.select2().css({'visibility': 'visible'}).change(clean_submit); $('#types').select2({multiple: 1, placeholder: '{{ _('Filter by type...') }}'}) .css({'visibility': 'visible'}); - $('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'}) + $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'}) .css({'visibility': 'visible'}); $('#search-author').select2({multiple: 1, placeholder: '{{ _('Authors') }}...'}) .css({'visibility': 'visible'}); diff --git a/templates/problem/search-form.html b/templates/problem/search-form.html index 827735d..9f0c910 100644 --- a/templates/problem/search-form.html +++ b/templates/problem/search-form.html @@ -35,7 +35,7 @@ {% endif %} {% if organizations %}
- +