Allow to create group and its contest on UI
This commit is contained in:
parent
196e2a9bb0
commit
67ef6b9111
28 changed files with 1029 additions and 556 deletions
|
@ -75,6 +75,7 @@ DMOJ_BLOG_NEW_CONTEST_COUNT = 7
|
||||||
DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT = 7
|
DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT = 7
|
||||||
DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1
|
DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1
|
||||||
DMOJ_USER_MAX_ORGANIZATION_COUNT = 10
|
DMOJ_USER_MAX_ORGANIZATION_COUNT = 10
|
||||||
|
DMOJ_USER_MAX_ORGANIZATION_ADD = 5
|
||||||
DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5
|
DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5
|
||||||
DMOJ_PDF_PROBLEM_CACHE = ""
|
DMOJ_PDF_PROBLEM_CACHE = ""
|
||||||
DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir()
|
DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir()
|
||||||
|
|
15
dmoj/urls.py
15
dmoj/urls.py
|
@ -581,6 +581,11 @@ urlpatterns = [
|
||||||
organization.OrganizationList.as_view(),
|
organization.OrganizationList.as_view(),
|
||||||
name="organization_list",
|
name="organization_list",
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r"^organizations/add/$",
|
||||||
|
organization.AddOrganization.as_view(),
|
||||||
|
name="organization_add",
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r"^organization/(?P<pk>\d+)-(?P<slug>[\w-]*)",
|
r"^organization/(?P<pk>\d+)-(?P<slug>[\w-]*)",
|
||||||
include(
|
include(
|
||||||
|
@ -609,6 +614,16 @@ urlpatterns = [
|
||||||
organization.OrganizationContests, "organization_contests"
|
organization.OrganizationContests, "organization_contests"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r"^/contest/add",
|
||||||
|
organization.AddOrganizationContest.as_view(),
|
||||||
|
name="organization_contest_add",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^/contest/edit/(?P<contest>\w+)",
|
||||||
|
organization.EditOrganizationContest.as_view(),
|
||||||
|
name="organization_contest_edit",
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r"^/submissions/",
|
r"^/submissions/",
|
||||||
paged_list_view(
|
paged_list_view(
|
||||||
|
|
|
@ -47,6 +47,7 @@ class OrganizationAdmin(VersionAdmin):
|
||||||
"registrant",
|
"registrant",
|
||||||
"show_public",
|
"show_public",
|
||||||
)
|
)
|
||||||
|
search_fields = ("name", "short_name", "registrant__user__username")
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
prepopulated_fields = {"slug": ("name",)}
|
||||||
actions_on_top = True
|
actions_on_top = True
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
|
|
149
judge/forms.py
149
judge/forms.py
|
@ -7,7 +7,14 @@ from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import CharField, ChoiceField, Form, ModelForm
|
from django.forms import (
|
||||||
|
CharField,
|
||||||
|
ChoiceField,
|
||||||
|
Form,
|
||||||
|
ModelForm,
|
||||||
|
formset_factory,
|
||||||
|
BaseModelFormSet,
|
||||||
|
)
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -23,6 +30,7 @@ from judge.models import (
|
||||||
Profile,
|
Profile,
|
||||||
Submission,
|
Submission,
|
||||||
BlogPost,
|
BlogPost,
|
||||||
|
ContestProblem,
|
||||||
)
|
)
|
||||||
from judge.utils.subscription import newsletter_id
|
from judge.utils.subscription import newsletter_id
|
||||||
from judge.widgets import (
|
from judge.widgets import (
|
||||||
|
@ -31,6 +39,10 @@ from judge.widgets import (
|
||||||
PagedownWidget,
|
PagedownWidget,
|
||||||
Select2MultipleWidget,
|
Select2MultipleWidget,
|
||||||
Select2Widget,
|
Select2Widget,
|
||||||
|
HeavySelect2MultipleWidget,
|
||||||
|
HeavySelect2Widget,
|
||||||
|
Select2MultipleWidget,
|
||||||
|
DateTimePickerWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,7 +139,15 @@ class ProblemSubmitForm(ModelForm):
|
||||||
class EditOrganizationForm(ModelForm):
|
class EditOrganizationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Organization
|
model = Organization
|
||||||
fields = ["about", "logo_override_image", "admins", "is_open"]
|
fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"short_name",
|
||||||
|
"about",
|
||||||
|
"logo_override_image",
|
||||||
|
"admins",
|
||||||
|
"is_open",
|
||||||
|
]
|
||||||
widgets = {"admins": Select2MultipleWidget()}
|
widgets = {"admins": Select2MultipleWidget()}
|
||||||
if HeavyPreviewPageDownWidget is not None:
|
if HeavyPreviewPageDownWidget is not None:
|
||||||
widgets["about"] = HeavyPreviewPageDownWidget(
|
widgets["about"] = HeavyPreviewPageDownWidget(
|
||||||
|
@ -135,6 +155,105 @@ class EditOrganizationForm(ModelForm):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddOrganizationForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Organization
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"short_name",
|
||||||
|
"about",
|
||||||
|
"logo_override_image",
|
||||||
|
"is_open",
|
||||||
|
]
|
||||||
|
widgets = {}
|
||||||
|
if HeavyPreviewPageDownWidget is not None:
|
||||||
|
widgets["about"] = HeavyPreviewPageDownWidget(
|
||||||
|
preview=reverse_lazy("organization_preview")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.request = kwargs.pop("request", None)
|
||||||
|
super(AddOrganizationForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
res = super(AddOrganizationForm, self).save(commit=False)
|
||||||
|
res.registrant = self.request.profile
|
||||||
|
if commit:
|
||||||
|
res.save()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationContestForm(ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.org_id = kwargs.pop("org_id", 0)
|
||||||
|
super(OrganizationContestForm, self).__init__(*args, **kwargs)
|
||||||
|
for field in [
|
||||||
|
"authors",
|
||||||
|
"curators",
|
||||||
|
"testers",
|
||||||
|
"private_contestants",
|
||||||
|
"banned_users",
|
||||||
|
"view_contest_scoreboard",
|
||||||
|
]:
|
||||||
|
self.fields[field].widget.data_url = (
|
||||||
|
self.fields[field].widget.get_url() + "?org_id=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Contest
|
||||||
|
fields = (
|
||||||
|
"key",
|
||||||
|
"name",
|
||||||
|
"authors",
|
||||||
|
"curators",
|
||||||
|
"testers",
|
||||||
|
"is_visible",
|
||||||
|
"use_clarifications",
|
||||||
|
"hide_problem_tags",
|
||||||
|
"scoreboard_visibility",
|
||||||
|
"run_pretests_only",
|
||||||
|
"points_precision",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"time_limit",
|
||||||
|
"description",
|
||||||
|
"og_image",
|
||||||
|
"logo_override_image",
|
||||||
|
"summary",
|
||||||
|
"format_name",
|
||||||
|
"format_config",
|
||||||
|
"problem_label_script",
|
||||||
|
"access_code",
|
||||||
|
"private_contestants",
|
||||||
|
"view_contest_scoreboard",
|
||||||
|
"banned_users",
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
"authors": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||||
|
"curators": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||||
|
"testers": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||||
|
"private_contestants": HeavySelect2MultipleWidget(
|
||||||
|
data_view="profile_select2"
|
||||||
|
),
|
||||||
|
"banned_users": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||||
|
"view_contest_scoreboard": HeavySelect2MultipleWidget(
|
||||||
|
data_view="profile_select2"
|
||||||
|
),
|
||||||
|
"organizations": HeavySelect2MultipleWidget(
|
||||||
|
data_view="organization_select2"
|
||||||
|
),
|
||||||
|
"tags": Select2MultipleWidget,
|
||||||
|
"description": HeavyPreviewPageDownWidget(
|
||||||
|
preview=reverse_lazy("contest_preview")
|
||||||
|
),
|
||||||
|
"start_time": DateTimePickerWidget(),
|
||||||
|
"end_time": DateTimePickerWidget(),
|
||||||
|
"format_name": Select2Widget(),
|
||||||
|
"scoreboard_visibility": Select2Widget(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AddOrganizationMemberForm(ModelForm):
|
class AddOrganizationMemberForm(ModelForm):
|
||||||
new_users = CharField(
|
new_users = CharField(
|
||||||
max_length=65536,
|
max_length=65536,
|
||||||
|
@ -291,3 +410,29 @@ class ProblemPointsVoteForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProblemPointsVote
|
model = ProblemPointsVote
|
||||||
fields = ["points"]
|
fields = ["points"]
|
||||||
|
|
||||||
|
|
||||||
|
class ContestProblemForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = ContestProblem
|
||||||
|
fields = (
|
||||||
|
"order",
|
||||||
|
"problem",
|
||||||
|
"points",
|
||||||
|
"partial",
|
||||||
|
"output_prefix_override",
|
||||||
|
"max_submissions",
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
"problem": HeavySelect2Widget(
|
||||||
|
data_view="problem_select2", attrs={"style": "width:100%"}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ContestProblemFormSet(
|
||||||
|
formset_factory(
|
||||||
|
ContestProblemForm, formset=BaseModelFormSet, extra=6, can_delete=True
|
||||||
|
)
|
||||||
|
):
|
||||||
|
model = ContestProblem
|
||||||
|
|
|
@ -19,12 +19,15 @@ def gen_submissions():
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
headers = [i[0] for i in cursor.description]
|
headers = [i[0] for i in cursor.description]
|
||||||
with open(os.path.join(settings.ML_DATA_PATH, "submissions.csv"), "w") as csvfile:
|
with open(
|
||||||
|
os.path.join(settings.ML_DATA_PATH, "submissions.csv"), "w"
|
||||||
|
) as csvfile:
|
||||||
f = csv.writer(csvfile)
|
f = csv.writer(csvfile)
|
||||||
f.writerow(headers)
|
f.writerow(headers)
|
||||||
for row in cursor.fetchall():
|
for row in cursor.fetchall():
|
||||||
f.writerow(row)
|
f.writerow(row)
|
||||||
|
|
||||||
|
|
||||||
def gen_users():
|
def gen_users():
|
||||||
print("Generating users")
|
print("Generating users")
|
||||||
headers = ["uid", "username", "rating", "points"]
|
headers = ["uid", "username", "rating", "points"]
|
||||||
|
|
28
judge/migrations/0132_auto_20220915_1349.py
Normal file
28
judge/migrations/0132_auto_20220915_1349.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.2.25 on 2022-09-15 06:49
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0131_auto_20220905_0027"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contestproblem",
|
||||||
|
name="max_submissions",
|
||||||
|
field=models.IntegerField(
|
||||||
|
default=0,
|
||||||
|
help_text="Maximum number of submissions for this problem, or 0 for no limit.",
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(
|
||||||
|
0, "Why include a problem you can't submit to?"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
verbose_name="max submissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -759,6 +759,7 @@ class ContestProblem(models.Model):
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Maximum number of submissions for this problem, " "or 0 for no limit."
|
"Maximum number of submissions for this problem, " "or 0 for no limit."
|
||||||
),
|
),
|
||||||
|
verbose_name=_("max submissions"),
|
||||||
default=0,
|
default=0,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(0, _("Why include a problem you " "can't submit to?"))
|
MinValueValidator(0, _("Why include a problem you " "can't submit to?"))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from itertools import chain
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -39,9 +40,12 @@ from reversion import revisions
|
||||||
|
|
||||||
from judge.forms import (
|
from judge.forms import (
|
||||||
EditOrganizationForm,
|
EditOrganizationForm,
|
||||||
|
AddOrganizationForm,
|
||||||
AddOrganizationMemberForm,
|
AddOrganizationMemberForm,
|
||||||
OrganizationBlogForm,
|
OrganizationBlogForm,
|
||||||
OrganizationAdminBlogForm,
|
OrganizationAdminBlogForm,
|
||||||
|
OrganizationContestForm,
|
||||||
|
ContestProblemFormSet,
|
||||||
)
|
)
|
||||||
from judge.models import (
|
from judge.models import (
|
||||||
BlogPost,
|
BlogPost,
|
||||||
|
@ -52,6 +56,7 @@ from judge.models import (
|
||||||
Profile,
|
Profile,
|
||||||
Contest,
|
Contest,
|
||||||
Notification,
|
Notification,
|
||||||
|
ContestProblem,
|
||||||
)
|
)
|
||||||
from judge import event_poster as event
|
from judge import event_poster as event
|
||||||
from judge.utils.ranker import ranker
|
from judge.utils.ranker import ranker
|
||||||
|
@ -121,6 +126,7 @@ class OrganizationMixin(OrganizationBase):
|
||||||
context["logo_override_image"] = self.organization.logo_override_image
|
context["logo_override_image"] = self.organization.logo_override_image
|
||||||
if "organizations" in context:
|
if "organizations" in context:
|
||||||
context.pop("organizations")
|
context.pop("organizations")
|
||||||
|
print(context)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
@ -374,16 +380,59 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrganizationContests(LoginRequiredMixin, MemberOrganizationMixin, ContestList):
|
class OrganizationContestMixin(
|
||||||
|
LoginRequiredMixin,
|
||||||
|
TitleMixin,
|
||||||
|
OrganizationMixin,
|
||||||
|
OrganizationHomeViewContext,
|
||||||
|
):
|
||||||
|
model = Contest
|
||||||
|
form_class = OrganizationContestForm
|
||||||
|
|
||||||
|
def is_contest_editable(self, request, contest):
|
||||||
|
return request.profile in contest.authors.all() or self.can_edit_organization(
|
||||||
|
self.organization
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(OrganizationContestMixin, self).get_form_kwargs()
|
||||||
|
kwargs["org_id"] = self.organization.id
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationContests(
|
||||||
|
OrganizationContestMixin, MemberOrganizationMixin, ContestList
|
||||||
|
):
|
||||||
template_name = "organization/contests.html"
|
template_name = "organization/contests.html"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.org_query = [self.organization_id]
|
self.org_query = [self.organization_id]
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
def set_editable_contest(self, contest):
|
||||||
|
if not contest:
|
||||||
|
return False
|
||||||
|
contest.is_editable = self.is_contest_editable(self.request, contest)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganizationContests, self).get_context_data(**kwargs)
|
context = super(OrganizationContests, self).get_context_data(**kwargs)
|
||||||
context["page_type"] = "contests"
|
context["page_type"] = "contests"
|
||||||
|
context["hide_contest_orgs"] = True
|
||||||
|
context.pop("organizations")
|
||||||
|
context["create_url"] = reverse(
|
||||||
|
"organization_contest_add",
|
||||||
|
args=[self.organization.id, self.organization.slug],
|
||||||
|
)
|
||||||
|
|
||||||
|
for participation in context["active_participations"]:
|
||||||
|
self.set_editable_contest(participation.contest)
|
||||||
|
for contest in context["past_contests"]:
|
||||||
|
self.set_editable_contest(contest)
|
||||||
|
for contest in context["current_contests"]:
|
||||||
|
self.set_editable_contest(contest)
|
||||||
|
for contest in context["future_contests"]:
|
||||||
|
self.set_editable_contest(contest)
|
||||||
|
print(context)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -772,6 +821,153 @@ class EditOrganization(
|
||||||
return super(EditOrganization, self).form_valid(form)
|
return super(EditOrganization, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class AddOrganization(LoginRequiredMixin, TitleMixin, CreateView):
|
||||||
|
template_name = "organization/add.html"
|
||||||
|
model = Organization
|
||||||
|
form_class = AddOrganizationForm
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return _("Create group")
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(AddOrganization, self).get_form_kwargs()
|
||||||
|
kwargs["request"] = self.request
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
if (
|
||||||
|
not self.request.user.is_staff
|
||||||
|
and Organization.objects.filter(registrant=self.request.profile).count()
|
||||||
|
>= settings.DMOJ_USER_MAX_ORGANIZATION_ADD
|
||||||
|
):
|
||||||
|
return generic_message(
|
||||||
|
self.request,
|
||||||
|
_("Exceeded limit"),
|
||||||
|
_("You created too many groups. You can only create at most %d groups")
|
||||||
|
% settings.DMOJ_USER_MAX_ORGANIZATION_ADD,
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
with transaction.atomic(), revisions.create_revision():
|
||||||
|
revisions.set_comment(_("Added from site"))
|
||||||
|
revisions.set_user(self.request.user)
|
||||||
|
res = super(AddOrganization, self).form_valid(form)
|
||||||
|
self.object.admins.add(self.request.profile)
|
||||||
|
self.object.members.add(self.request.profile)
|
||||||
|
self.object.save()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class AddOrganizationContest(
|
||||||
|
AdminOrganizationMixin, OrganizationContestMixin, CreateView
|
||||||
|
):
|
||||||
|
template_name = "organization/contest/add.html"
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return _("Add contest")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
with transaction.atomic(), revisions.create_revision():
|
||||||
|
revisions.set_comment(_("Added from site"))
|
||||||
|
revisions.set_user(self.request.user)
|
||||||
|
res = super(AddOrganizationContest, self).form_valid(form)
|
||||||
|
self.object.organizations.add(self.organization)
|
||||||
|
self.object.is_organization_private = True
|
||||||
|
self.object.save()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
"organization_contest_edit",
|
||||||
|
args=[self.organization.id, self.organization.slug, self.object.key],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EditOrganizationContest(
|
||||||
|
OrganizationContestMixin, MemberOrganizationMixin, UpdateView
|
||||||
|
):
|
||||||
|
template_name = "organization/contest/add.html"
|
||||||
|
|
||||||
|
def setup_contest(self, request, *args, **kwargs):
|
||||||
|
contest_key = kwargs.get("contest", None)
|
||||||
|
if not contest_key:
|
||||||
|
raise Http404()
|
||||||
|
self.contest = get_object_or_404(Contest, key=contest_key)
|
||||||
|
if self.organization not in self.contest.organizations.all():
|
||||||
|
raise Http404()
|
||||||
|
if not self.is_contest_editable(request, self.contest):
|
||||||
|
return generic_message(
|
||||||
|
self.request,
|
||||||
|
_("Permission denied"),
|
||||||
|
_("You are not allowed to edit this contest"),
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
res = self.setup_contest(request, *args, **kwargs)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
res = self.setup_contest(request, *args, **kwargs)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
problem_formset = self.get_problem_formset(True)
|
||||||
|
if problem_formset.is_valid():
|
||||||
|
for problem_form in problem_formset.save(commit=False):
|
||||||
|
if problem_form:
|
||||||
|
problem_form.contest = self.contest
|
||||||
|
problem_form.save()
|
||||||
|
for problem_form in problem_formset.deleted_objects:
|
||||||
|
problem_form.delete()
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
self.object = self.contest
|
||||||
|
return self.render_to_response(
|
||||||
|
self.get_context_data(
|
||||||
|
problems_form=problem_formset,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return _("Edit %s") % self.contest.key
|
||||||
|
|
||||||
|
def get_content_title(self):
|
||||||
|
href = reverse("contest_view", args=[self.contest.key])
|
||||||
|
return mark_safe(f'Edit <a href="{href}">{self.contest.key}</a>')
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.contest
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
with transaction.atomic(), revisions.create_revision():
|
||||||
|
revisions.set_comment(_("Edited from site"))
|
||||||
|
revisions.set_user(self.request.user)
|
||||||
|
res = super(EditOrganizationContest, self).form_valid(form)
|
||||||
|
self.object.organizations.add(self.organization)
|
||||||
|
self.object.is_organization_private = True
|
||||||
|
self.object.save()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_problem_formset(self, post=False):
|
||||||
|
return ContestProblemFormSet(
|
||||||
|
data=self.request.POST if post else None,
|
||||||
|
prefix="problems",
|
||||||
|
queryset=ContestProblem.objects.filter(contest=self.contest).order_by(
|
||||||
|
"order"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
if "problems_form" not in context:
|
||||||
|
context["problems_form"] = self.get_problem_formset()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.request.path
|
||||||
|
|
||||||
|
|
||||||
class AddOrganizationBlog(
|
class AddOrganizationBlog(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
|
|
|
@ -10,8 +10,14 @@ from judge.jinja2.gravatar import gravatar
|
||||||
from judge.models import Comment, Contest, Organization, Problem, Profile
|
from judge.models import Comment, Contest, Organization, Problem, Profile
|
||||||
|
|
||||||
|
|
||||||
def _get_user_queryset(term):
|
def _get_user_queryset(term, org_id):
|
||||||
qs = Profile.objects
|
if org_id:
|
||||||
|
try:
|
||||||
|
qs = Organization.objects.get(id=org_id).members.all()
|
||||||
|
except Exception:
|
||||||
|
raise Http404()
|
||||||
|
else:
|
||||||
|
qs = Profile.objects
|
||||||
if term.endswith(" "):
|
if term.endswith(" "):
|
||||||
qs = qs.filter(user__username=term.strip())
|
qs = qs.filter(user__username=term.strip())
|
||||||
else:
|
else:
|
||||||
|
@ -46,9 +52,14 @@ class Select2View(BaseListView):
|
||||||
|
|
||||||
|
|
||||||
class UserSelect2View(Select2View):
|
class UserSelect2View(Select2View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.org_id = kwargs.get("org_id", request.GET.get("org_id", ""))
|
||||||
|
print(self.org_id)
|
||||||
|
return super(UserSelect2View, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
_get_user_queryset(self.term)
|
_get_user_queryset(self.term, self.org_id)
|
||||||
.annotate(username=F("user__username"))
|
.annotate(username=F("user__username"))
|
||||||
.only("id")
|
.only("id")
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,3 +2,4 @@ from judge.widgets.checkbox import CheckboxSelectMultipleWithSelectAll
|
||||||
from judge.widgets.mixins import CompressorWidgetMixin
|
from judge.widgets.mixins import CompressorWidgetMixin
|
||||||
from judge.widgets.pagedown import *
|
from judge.widgets.pagedown import *
|
||||||
from judge.widgets.select2 import *
|
from judge.widgets.select2 import *
|
||||||
|
from judge.widgets.datetime import *
|
||||||
|
|
24
judge/widgets/datetime.py
Normal file
24
judge/widgets/datetime.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimePickerWidget(forms.DateTimeInput):
|
||||||
|
template_name = "widgets/datetimepicker.html"
|
||||||
|
|
||||||
|
def get_context(self, name, value, attrs):
|
||||||
|
datetimepicker_id = "datetimepicker_{name}".format(name=name)
|
||||||
|
if attrs is None:
|
||||||
|
attrs = dict()
|
||||||
|
attrs["data-target"] = "#{id}".format(id=datetimepicker_id)
|
||||||
|
attrs["class"] = "form-control datetimepicker-input"
|
||||||
|
context = super().get_context(name, value, attrs)
|
||||||
|
context["widget"]["datetimepicker_id"] = datetimepicker_id
|
||||||
|
return context
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media(self):
|
||||||
|
css_url = "https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.min.css"
|
||||||
|
js_url = "https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.min.js"
|
||||||
|
return forms.Media(
|
||||||
|
js=[js_url],
|
||||||
|
css={"screen": [css_url]},
|
||||||
|
)
|
|
@ -45,6 +45,7 @@ from django.conf import settings
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.forms.models import ModelChoiceIterator
|
from django.forms.models import ModelChoiceIterator
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
DEFAULT_SELECT2_JS = "//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"
|
DEFAULT_SELECT2_JS = "//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"
|
||||||
DEFAULT_SELECT2_CSS = (
|
DEFAULT_SELECT2_CSS = (
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -36,7 +36,7 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.full {
|
.full {
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.sortable thead {
|
table.sortable thead {
|
||||||
|
|
|
@ -23,4 +23,7 @@
|
||||||
}
|
}
|
||||||
.org-field-wrapper {
|
.org-field-wrapper {
|
||||||
margin-top: 0.4em;
|
margin-top: 0.4em;
|
||||||
}
|
}
|
||||||
|
.org-field-wrapper:has(> input[type=checkbox]) {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends "three-column-content.html" %}
|
{% extends "two-column-content.html" %}
|
||||||
{% block meta %}
|
{% block meta %}
|
||||||
<meta name="description" content="The {{ SITE_NAME }}'s contest list - past, present, and future.">
|
<meta name="description" content="The {{ SITE_NAME }}'s contest list - past, present, and future.">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block three_col_media %}
|
{% block two_col_media %}
|
||||||
<style>
|
<style>
|
||||||
.time-left {
|
.time-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -66,17 +66,10 @@
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
.middle-content {
|
|
||||||
max-width: none;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block three_col_js %}
|
{% block two_col_js %}
|
||||||
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
|
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -127,19 +120,28 @@
|
||||||
<i class="fa fa-eye-slash"></i> {{ _('hidden') }}
|
<i class="fa fa-eye-slash"></i> {{ _('hidden') }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if contest.is_editable %}
|
||||||
|
<span style="background-color: green" class="contest-tag">
|
||||||
|
<a href="{{ url('organization_contest_edit', organization.id, organization.slug, contest.key) }}" style="color: white">
|
||||||
|
<i class="fa fa-edit"></i> {{ _('Edit') }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
{% if contest.is_private %}
|
{% if contest.is_private %}
|
||||||
<span style="background-color: #666666; color: #ffffff" class="contest-tag">
|
<span style="background-color: #666666; color: #ffffff" class="contest-tag">
|
||||||
<i class="fa fa-lock"></i> {{ _('private') }}
|
<i class="fa fa-lock"></i> {{ _('private') }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if contest.is_organization_private %}
|
{% if not hide_contest_orgs %}
|
||||||
{% for org in contest.organizations.all() %}
|
{% if contest.is_organization_private %}
|
||||||
<span style="background-color: #cccccc" class="contest-tag">
|
{% for org in contest.organizations.all() %}
|
||||||
<a href="{{ org.get_absolute_url() }}" style="color: #000000">
|
<span style="background-color: #cccccc" class="contest-tag">
|
||||||
<i class="fa fa-lock"></i> {{ org.name }}
|
<a href="{{ org.get_absolute_url() }}" style="color: #000000">
|
||||||
</a>
|
<i class="fa fa-lock"></i> {{ org.name }}
|
||||||
</span>
|
</a>
|
||||||
{% endfor %}
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if contest.is_rated %}
|
{% if contest.is_rated %}
|
||||||
<span style="background-color: #e54c14; color: #ffffff" class="contest-tag">
|
<span style="background-color: #e54c14; color: #ffffff" class="contest-tag">
|
||||||
|
@ -219,6 +221,9 @@
|
||||||
</select>
|
</select>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="search-btn" class="btn-green small"> {{ _('Search')}} </button>
|
<button id="search-btn" class="btn-green small"> {{ _('Search')}} </button>
|
||||||
|
{% if create_url %}
|
||||||
|
<a href="{{create_url}}" class="button small" style="float: right"><i class="fa fa-plus"></i> {{ _('Create')}}</a>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% if active_participations %}
|
{% if active_participations %}
|
||||||
<h4 class="toggle open">
|
<h4 class="toggle open">
|
||||||
|
@ -384,11 +389,12 @@
|
||||||
{{ user_count(contest, request.user) }}
|
{{ user_count(contest, request.user) }}
|
||||||
</td>
|
</td>
|
||||||
{% if not request.in_contest %}
|
{% if not request.in_contest %}
|
||||||
<td><form action="{{ url('contest_join', contest.key) }}" method="post">
|
<td><form action="{{ url('contest_join', contest.key) }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="submit" class="unselectable button full small"
|
<input type="submit" class="unselectable button full small"
|
||||||
value="{{ _('Virtual join') }}">
|
value="{{ _('Virtual join') }}">
|
||||||
</form></td>
|
</form>
|
||||||
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
18
templates/organization/add.html
Normal file
18
templates/organization/add.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "two-column-content.html" %}
|
||||||
|
|
||||||
|
{% block two_col_js %}
|
||||||
|
{{ form.media.js }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block two_col_media %}
|
||||||
|
{{ form.media.css }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block left_sidebar %}
|
||||||
|
{% include "user/user-left-sidebar.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block middle_content %}
|
||||||
|
<center><h2>{{title}}</h2></center>
|
||||||
|
{% include "organization/form.html" %}
|
||||||
|
{% endblock %}
|
|
@ -7,11 +7,6 @@
|
||||||
|
|
||||||
{% block three_col_media %}
|
{% block three_col_media %}
|
||||||
{{ form.media.css }}
|
{{ form.media.css }}
|
||||||
<style>
|
|
||||||
#org-field-wrapper-visible, #org-field-wrapper-sticky {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block middle_content %}
|
{% block middle_content %}
|
||||||
|
|
87
templates/organization/contest/add.html
Normal file
87
templates/organization/contest/add.html
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
{% extends "organization/home-base.html" %}
|
||||||
|
|
||||||
|
{% block three_col_js %}
|
||||||
|
{{ form.media.js }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block three_col_media %}
|
||||||
|
{{ form.media.css }}
|
||||||
|
<style>
|
||||||
|
#org-field-wrapper-scoreboard_visibility,
|
||||||
|
#org-field-wrapper-points_precision,
|
||||||
|
#org-field-wrapper-start_time,
|
||||||
|
#org-field-wrapper-end_time,
|
||||||
|
#org-field-wrapper-time_limit,
|
||||||
|
#org-field-wrapper-format_name {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.problems-problem {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
input[type=number] {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
.middle-content {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block middle_content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-dismissable">
|
||||||
|
<a href="#" class="close">x</a>
|
||||||
|
{{ form.non_field_errors() }}
|
||||||
|
{{ form.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% if not field.is_hidden %}
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
{{ field.errors }}
|
||||||
|
<label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span style="color:red"> * </span>{% endif %}:</b> </label>
|
||||||
|
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
{% if field.help_text %}
|
||||||
|
<i style="display: block">{{ field.help_text|safe }}</i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if problems_form %}
|
||||||
|
<hr><br>
|
||||||
|
{{ problems_form.management_form }}
|
||||||
|
<i>{{_('If you run out of rows, click Save')}}</i>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for field in problems_form[0] %}
|
||||||
|
{% if not field.is_hidden %}
|
||||||
|
<th>
|
||||||
|
{{field.label}}
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for form in problems_form %}
|
||||||
|
<tr>
|
||||||
|
{% for field in form %}
|
||||||
|
<td class="problems-{{field.name}}" title="
|
||||||
|
{{ field.help_text|safe if field.help_text }}"
|
||||||
|
style="{{ 'display:none' if field.is_hidden }}"
|
||||||
|
>{{field}}<div style="color:red">{{field.errors}}</div></td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit">{{ _('Save') }}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -29,11 +29,6 @@
|
||||||
|
|
||||||
{% block three_col_media %}
|
{% block three_col_media %}
|
||||||
{{ form.media.css }}
|
{{ form.media.css }}
|
||||||
<style>
|
|
||||||
#org-field-wrapper-is_open {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block middle_content %}
|
{% block middle_content %}
|
||||||
|
|
|
@ -4,18 +4,19 @@
|
||||||
<div class="alert alert-danger alert-dismissable">
|
<div class="alert alert-danger alert-dismissable">
|
||||||
<a href="#" class="close">x</a>
|
<a href="#" class="close">x</a>
|
||||||
{{ form.non_field_errors() }}
|
{{ form.non_field_errors() }}
|
||||||
|
{{ form.errors }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if not field.is_hidden %}
|
{% if not field.is_hidden %}
|
||||||
<div style="margin-bottom: 1em;">
|
<div style="margin-bottom: 1em;">
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
<label for="{{field.id_for_label }}"><b>{{ field.label }}:</b></label>
|
<label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span style="color:red"> * </span>{% endif %}:</b> </label>
|
||||||
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
|
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
|
||||||
{{ field }}
|
{{ field }}
|
||||||
</div>
|
</div>
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<i>{{ field.help_text|safe }}</i>
|
<i style="display: block">{{ field.help_text|safe }}</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block middle_title %}
|
{% block middle_title %}
|
||||||
{% if title %}
|
{% if title or content_title %}
|
||||||
<center><h2>{{ title }}</h2></center>
|
<center><h2>{{ content_title if content_title else title }}</h2></center>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
{% extends "user/base-users-three-col.html" %}
|
{% extends "two-column-content.html" %}
|
||||||
{% block users_js_media %}
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function(){
|
|
||||||
register_all_toggles();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block users_media %}
|
{% block two_col_media %}
|
||||||
<style>
|
<style>
|
||||||
.organization-row {
|
.organization-row {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -62,7 +55,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% block users_table %}
|
{% block middle_content %}
|
||||||
|
<a style="float: right" class="button small" href="{{url('organization_add')}}">{{_("Create group")}}</a>
|
||||||
{{ org_list(_('My groups'), my_organizations) }}
|
{{ org_list(_('My groups'), my_organizations) }}
|
||||||
{{ org_list(_('Open groups'), open_organizations) }}
|
{{ org_list(_('Open groups'), open_organizations) }}
|
||||||
{{ org_list(_('Private groups'), private_organizations) }}
|
{{ org_list(_('Private groups'), private_organizations) }}
|
||||||
|
|
|
@ -49,6 +49,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
{% if can_edit %}
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<a href="{{ url('organization_contest_add', organization.id, organization.slug) }}">{{ _('Add contest') }}</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if is_member and not can_edit %}
|
{% if is_member and not can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="{{ url('leave_organization', organization.id, organization.slug) }}">
|
<form method="post" action="{{ url('leave_organization', organization.id, organization.slug) }}">
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<form action="{{ kick_url }}" method="POST" class="kick-form">
|
<form action="{{ kick_url }}" method="POST" class="kick-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.id }}">
|
<input type="hidden" name="user" value="{{ user.id }}">
|
||||||
<a href="#" class="button">{{ _('Kick') }}</a>
|
<a href="#" class="button small">{{ _('Kick') }}</a>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
17
templates/two-column-content.html
Normal file
17
templates/two-column-content.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "three-column-content.html" %}
|
||||||
|
|
||||||
|
{% block three_col_js %}
|
||||||
|
{% block two_col_js %}{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block three_col_media %}
|
||||||
|
<style>
|
||||||
|
@media (min-width: 800px) {
|
||||||
|
.middle-content {
|
||||||
|
max-width: none;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% block two_col_media %}{% endblock %}
|
||||||
|
{% endblock %}
|
|
@ -1,20 +1,12 @@
|
||||||
{% extends "three-column-content.html" %}
|
{% extends "two-column-content.html" %}
|
||||||
|
|
||||||
{% block three_col_js %}
|
{% block two_col_js %}
|
||||||
{% block users_js_media %}{% endblock %}
|
{% block users_js_media %}{% endblock %}
|
||||||
{% include "user/base-users-js.html" %}
|
{% include "user/base-users-js.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block three_col_media %}
|
{% block two_col_media %}
|
||||||
<link href="http://fonts.cdnfonts.com/css/jersey-m54" rel="stylesheet">
|
<link href="http://fonts.cdnfonts.com/css/jersey-m54" rel="stylesheet">
|
||||||
<style>
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
.middle-content {
|
|
||||||
max-width: none;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% block users_media %}{% endblock %}
|
{% block users_media %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
20
templates/widgets/datetimepicker.html
Normal file
20
templates/widgets/datetimepicker.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<input
|
||||||
|
type="{{ widget.type }}"
|
||||||
|
name="{{ widget.name }}"
|
||||||
|
{% if widget.value != None %}
|
||||||
|
value="{{ widget.value }}"
|
||||||
|
{% endif %}
|
||||||
|
{% for name, value in widget.attrs.items() %}
|
||||||
|
{% if value %}
|
||||||
|
{{ name }}{% if not value %}="{{ value }}"{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$("input[name='{{ widget.name }}']").datetimepicker({
|
||||||
|
format: 'Y-m-d H:i:s',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in a new issue