Move problem clarification to contest clarification
This commit is contained in:
parent
f001ae4e0a
commit
0ee7de1b46
11 changed files with 115 additions and 79 deletions
|
@ -20,7 +20,6 @@ from judge.models import (
|
||||||
LanguageLimit,
|
LanguageLimit,
|
||||||
LanguageTemplate,
|
LanguageTemplate,
|
||||||
Problem,
|
Problem,
|
||||||
ProblemClarification,
|
|
||||||
ProblemTranslation,
|
ProblemTranslation,
|
||||||
Profile,
|
Profile,
|
||||||
Solution,
|
Solution,
|
||||||
|
@ -147,23 +146,6 @@ class LanguageTemplateInline(admin.TabularInline):
|
||||||
form = LanguageTemplateInlineForm
|
form = LanguageTemplateInlineForm
|
||||||
|
|
||||||
|
|
||||||
class ProblemClarificationForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
if HeavyPreviewPageDownWidget is not None:
|
|
||||||
widgets = {
|
|
||||||
"description": HeavyPreviewPageDownWidget(
|
|
||||||
preview=reverse_lazy("comment_preview")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ProblemClarificationInline(admin.StackedInline):
|
|
||||||
model = ProblemClarification
|
|
||||||
fields = ("description",)
|
|
||||||
form = ProblemClarificationForm
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ProblemSolutionForm(ModelForm):
|
class ProblemSolutionForm(ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ProblemSolutionForm, self).__init__(*args, **kwargs)
|
super(ProblemSolutionForm, self).__init__(*args, **kwargs)
|
||||||
|
@ -256,7 +238,6 @@ class ProblemAdmin(CompareVersionAdmin):
|
||||||
inlines = [
|
inlines = [
|
||||||
LanguageLimitInline,
|
LanguageLimitInline,
|
||||||
LanguageTemplateInline,
|
LanguageTemplateInline,
|
||||||
ProblemClarificationInline,
|
|
||||||
ProblemSolutionInline,
|
ProblemSolutionInline,
|
||||||
ProblemTranslationInline,
|
ProblemTranslationInline,
|
||||||
]
|
]
|
||||||
|
|
46
judge/migrations/0133_auto_20221013_0850.py
Normal file
46
judge/migrations/0133_auto_20221013_0850.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Generated by Django 2.2.25 on 2022-10-13 01:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0132_auto_20220915_1349"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ContestProblemClarification",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("description", models.TextField(verbose_name="clarification body")),
|
||||||
|
(
|
||||||
|
"date",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="clarification timestamp"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"problem",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="judge.ContestProblem",
|
||||||
|
verbose_name="clarified problem",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="ProblemClarification",
|
||||||
|
),
|
||||||
|
]
|
|
@ -15,6 +15,7 @@ from judge.models.contest import (
|
||||||
ContestSubmission,
|
ContestSubmission,
|
||||||
ContestTag,
|
ContestTag,
|
||||||
Rating,
|
Rating,
|
||||||
|
ContestProblemClarification,
|
||||||
)
|
)
|
||||||
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
|
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
|
||||||
from judge.models.message import PrivateMessage, PrivateMessageThread
|
from judge.models.message import PrivateMessage, PrivateMessageThread
|
||||||
|
@ -23,7 +24,6 @@ from judge.models.problem import (
|
||||||
LanguageTemplate,
|
LanguageTemplate,
|
||||||
License,
|
License,
|
||||||
Problem,
|
Problem,
|
||||||
ProblemClarification,
|
|
||||||
ProblemGroup,
|
ProblemGroup,
|
||||||
ProblemTranslation,
|
ProblemTranslation,
|
||||||
ProblemType,
|
ProblemType,
|
||||||
|
|
|
@ -29,6 +29,7 @@ __all__ = [
|
||||||
"ContestProblem",
|
"ContestProblem",
|
||||||
"ContestSubmission",
|
"ContestSubmission",
|
||||||
"Rating",
|
"Rating",
|
||||||
|
"ContestProblemClarification",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -766,6 +767,10 @@ class ContestProblem(models.Model):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def clarifications(self):
|
||||||
|
return ContestProblemClarification.objects.filter(problem=self)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("problem", "contest")
|
unique_together = ("problem", "contest")
|
||||||
verbose_name = _("contest problem")
|
verbose_name = _("contest problem")
|
||||||
|
@ -853,3 +858,13 @@ class ContestMoss(models.Model):
|
||||||
unique_together = ("contest", "problem", "language")
|
unique_together = ("contest", "problem", "language")
|
||||||
verbose_name = _("contest moss result")
|
verbose_name = _("contest moss result")
|
||||||
verbose_name_plural = _("contest moss results")
|
verbose_name_plural = _("contest moss results")
|
||||||
|
|
||||||
|
|
||||||
|
class ContestProblemClarification(models.Model):
|
||||||
|
problem = models.ForeignKey(
|
||||||
|
ContestProblem, verbose_name=_("clarified problem"), on_delete=CASCADE
|
||||||
|
)
|
||||||
|
description = models.TextField(verbose_name=_("clarification body"))
|
||||||
|
date = models.DateTimeField(
|
||||||
|
verbose_name=_("clarification timestamp"), auto_now_add=True
|
||||||
|
)
|
||||||
|
|
|
@ -28,7 +28,6 @@ __all__ = [
|
||||||
"ProblemType",
|
"ProblemType",
|
||||||
"Problem",
|
"Problem",
|
||||||
"ProblemTranslation",
|
"ProblemTranslation",
|
||||||
"ProblemClarification",
|
|
||||||
"License",
|
"License",
|
||||||
"Solution",
|
"Solution",
|
||||||
"TranslatedProblemQuerySet",
|
"TranslatedProblemQuerySet",
|
||||||
|
@ -490,10 +489,6 @@ class Problem(models.Model):
|
||||||
def i18n_name(self, value):
|
def i18n_name(self, value):
|
||||||
self._i18n_name = value
|
self._i18n_name = value
|
||||||
|
|
||||||
@property
|
|
||||||
def clarifications(self):
|
|
||||||
return ProblemClarification.objects.filter(problem=self)
|
|
||||||
|
|
||||||
def update_stats(self):
|
def update_stats(self):
|
||||||
self.user_count = (
|
self.user_count = (
|
||||||
self.submission_set.filter(
|
self.submission_set.filter(
|
||||||
|
@ -613,16 +608,6 @@ class ProblemTranslation(models.Model):
|
||||||
verbose_name_plural = _("problem translations")
|
verbose_name_plural = _("problem translations")
|
||||||
|
|
||||||
|
|
||||||
class ProblemClarification(models.Model):
|
|
||||||
problem = models.ForeignKey(
|
|
||||||
Problem, verbose_name=_("clarified problem"), on_delete=CASCADE
|
|
||||||
)
|
|
||||||
description = models.TextField(verbose_name=_("clarification body"))
|
|
||||||
date = models.DateTimeField(
|
|
||||||
verbose_name=_("clarification timestamp"), auto_now_add=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LanguageLimit(models.Model):
|
class LanguageLimit(models.Model):
|
||||||
problem = models.ForeignKey(
|
problem = models.ForeignKey(
|
||||||
Problem,
|
Problem,
|
||||||
|
|
|
@ -14,7 +14,7 @@ from judge.models import (
|
||||||
Contest,
|
Contest,
|
||||||
Language,
|
Language,
|
||||||
Problem,
|
Problem,
|
||||||
ProblemClarification,
|
ContestProblemClarification,
|
||||||
Profile,
|
Profile,
|
||||||
Submission,
|
Submission,
|
||||||
Ticket,
|
Ticket,
|
||||||
|
@ -50,8 +50,8 @@ class FeedView(ListView):
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
participation = self.request.profile.current_contest
|
participation = self.request.profile.current_contest
|
||||||
if participation:
|
if participation:
|
||||||
clarifications = ProblemClarification.objects.filter(
|
clarifications = ContestProblemClarification.objects.filter(
|
||||||
problem__in=participation.contest.problems.all()
|
problem__in=participation.contest.contest_problems.all()
|
||||||
)
|
)
|
||||||
context["has_clarifications"] = clarifications.count() > 0
|
context["has_clarifications"] = clarifications.count() > 0
|
||||||
context["clarifications"] = clarifications.order_by("-date")
|
context["clarifications"] = clarifications.order_by("-date")
|
||||||
|
|
|
@ -65,7 +65,7 @@ from judge.models import (
|
||||||
Problem,
|
Problem,
|
||||||
Profile,
|
Profile,
|
||||||
Submission,
|
Submission,
|
||||||
ProblemClarification,
|
ContestProblemClarification,
|
||||||
)
|
)
|
||||||
from judge.tasks import run_moss
|
from judge.tasks import run_moss
|
||||||
from judge.utils.celery import redirect_to_task_status
|
from judge.utils.celery import redirect_to_task_status
|
||||||
|
@ -1179,7 +1179,7 @@ class ContestTagDetail(TitleMixin, ContestTagDetailAjax):
|
||||||
return _("Contest tag: %s") % self.object.name
|
return _("Contest tag: %s") % self.object.name
|
||||||
|
|
||||||
|
|
||||||
class ProblemClarificationForm(forms.Form):
|
class ContestProblemClarificationForm(forms.Form):
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
widget=HeavyPreviewPageDownWidget(
|
widget=HeavyPreviewPageDownWidget(
|
||||||
preview=reverse_lazy("comment_preview"),
|
preview=reverse_lazy("comment_preview"),
|
||||||
|
@ -1190,12 +1190,12 @@ class ProblemClarificationForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
super(ProblemClarificationForm, self).__init__(*args, **kwargs)
|
super(ContestProblemClarificationForm, self).__init__(*args, **kwargs)
|
||||||
self.fields["body"].widget.attrs.update({"placeholder": _("Issue description")})
|
self.fields["body"].widget.attrs.update({"placeholder": _("Issue description")})
|
||||||
|
|
||||||
|
|
||||||
class NewContestClarificationView(ContestMixin, TitleMixin, SingleObjectFormView):
|
class NewContestClarificationView(ContestMixin, TitleMixin, SingleObjectFormView):
|
||||||
form_class = ProblemClarificationForm
|
form_class = ContestProblemClarificationForm
|
||||||
template_name = "contest/clarification.html"
|
template_name = "contest/clarification.html"
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
|
@ -1225,12 +1225,13 @@ class NewContestClarificationView(ContestMixin, TitleMixin, SingleObjectFormView
|
||||||
problem_code = self.request.POST["problem"]
|
problem_code = self.request.POST["problem"]
|
||||||
description = form.cleaned_data["body"]
|
description = form.cleaned_data["body"]
|
||||||
|
|
||||||
clarification = ProblemClarification(description=description)
|
clarification = ContestProblemClarification(description=description)
|
||||||
clarification.problem = Problem.objects.get(code=problem_code)
|
clarification.problem = get_object_or_404(
|
||||||
|
ContestProblem, contest=self.get_object(), problem__code=problem_code
|
||||||
|
)
|
||||||
clarification.save()
|
clarification.save()
|
||||||
|
|
||||||
link = reverse("home")
|
return HttpResponseRedirect(reverse("problem_list"))
|
||||||
return HttpResponseRedirect(link)
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return "New clarification for %s" % self.object.name
|
return "New clarification for %s" % self.object.name
|
||||||
|
@ -1264,26 +1265,27 @@ class ContestClarificationAjax(ContestMixin, DetailView):
|
||||||
minutes=polling_time
|
minutes=polling_time
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = list(
|
queryset = ContestProblemClarification.objects.filter(
|
||||||
ProblemClarification.objects.filter(
|
problem__in=self.object.contest_problems.all(), date__gte=last_one_minute
|
||||||
problem__in=self.object.problems.all(), date__gte=last_one_minute
|
|
||||||
).values("problem", "problem__name", "description")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
problems = list(
|
problems = list(
|
||||||
ContestProblem.objects.filter(contest=self.object)
|
ContestProblem.objects.filter(contest=self.object)
|
||||||
.order_by("order")
|
.order_by("order")
|
||||||
.values("problem")
|
.values_list("problem__code", flat=True)
|
||||||
)
|
)
|
||||||
problems = [i["problem"] for i in problems]
|
res = []
|
||||||
for cla in queryset:
|
for clarification in queryset:
|
||||||
cla["order"] = self.object.get_label_for_problem(
|
value = {
|
||||||
problems.index(cla["problem"])
|
"order": self.object.get_label_for_problem(
|
||||||
)
|
problems.index(clarification.problem.problem.code)
|
||||||
|
),
|
||||||
|
"problem__name": clarification.problem.problem.name,
|
||||||
|
"description": clarification.description,
|
||||||
|
}
|
||||||
|
res.append(value)
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(res, safe=False, json_dumps_params={"ensure_ascii": False})
|
||||||
queryset, safe=False, json_dumps_params={"ensure_ascii": False}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_contest_mode(request):
|
def update_contest_mode(request):
|
||||||
|
|
|
@ -930,7 +930,12 @@ class EditOrganizationContest(
|
||||||
for problem_form in problem_formset.deleted_objects:
|
for problem_form in problem_formset.deleted_objects:
|
||||||
problem_form.delete()
|
problem_form.delete()
|
||||||
super().post(request, *args, **kwargs)
|
super().post(request, *args, **kwargs)
|
||||||
return HttpResponseRedirect(reverse("organization_contests", args=(self.organization_id,self.organization.slug)))
|
return HttpResponseRedirect(
|
||||||
|
reverse(
|
||||||
|
"organization_contests",
|
||||||
|
args=(self.organization_id, self.organization.slug),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.object = self.contest
|
self.object = self.contest
|
||||||
return self.render_to_response(
|
return self.render_to_response(
|
||||||
|
@ -1062,9 +1067,9 @@ class EditOrganizationBlog(
|
||||||
_("Not allowed to edit this blog"),
|
_("Not allowed to edit this blog"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_blog(self , request , *args , **kwargs):
|
def delete_blog(self, request, *args, **kwargs):
|
||||||
self.blog_id = kwargs["blog_pk"]
|
self.blog_id = kwargs["blog_pk"]
|
||||||
BlogPost.objects.get(pk = self.blog_id).delete()
|
BlogPost.objects.get(pk=self.blog_id).delete()
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
res = self.setup_blog(request, *args, **kwargs)
|
res = self.setup_blog(request, *args, **kwargs)
|
||||||
|
@ -1076,10 +1081,13 @@ class EditOrganizationBlog(
|
||||||
res = self.setup_blog(request, *args, **kwargs)
|
res = self.setup_blog(request, *args, **kwargs)
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
if request.POST['action'] == 'Delete':
|
if request.POST["action"] == "Delete":
|
||||||
self.create_notification("Delete blog")
|
self.create_notification("Delete blog")
|
||||||
self.delete_blog(request , *args , **kwargs)
|
self.delete_blog(request, *args, **kwargs)
|
||||||
cur_url = reverse("organization_pending_blogs", args=(self.organization_id,self.organization.slug) )
|
cur_url = reverse(
|
||||||
|
"organization_pending_blogs",
|
||||||
|
args=(self.organization_id, self.organization.slug),
|
||||||
|
)
|
||||||
return HttpResponseRedirect(cur_url)
|
return HttpResponseRedirect(cur_url)
|
||||||
else:
|
else:
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
|
@ -1090,15 +1098,13 @@ class EditOrganizationBlog(
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return _("Edit blog %s") % self.object.title
|
return _("Edit blog %s") % self.object.title
|
||||||
|
|
||||||
def create_notification(self,action):
|
def create_notification(self, action):
|
||||||
blog = BlogPost.objects.get(pk=self.blog_id)
|
blog = BlogPost.objects.get(pk=self.blog_id)
|
||||||
link = reverse(
|
link = reverse(
|
||||||
"edit_organization_blog",
|
"edit_organization_blog",
|
||||||
args=[self.organization.id, self.organization.slug, self.blog_id],
|
args=[self.organization.id, self.organization.slug, self.blog_id],
|
||||||
)
|
)
|
||||||
html = (
|
html = f'<a href="{link}">{blog.title} - {self.organization.name}</a>'
|
||||||
f'<a href="{link}">{blog.title} - {self.organization.name}</a>'
|
|
||||||
)
|
|
||||||
post_authors = blog.authors.all()
|
post_authors = blog.authors.all()
|
||||||
posible_user = self.organization.admins.all() | post_authors
|
posible_user = self.organization.admins.all() | post_authors
|
||||||
for user in posible_user:
|
for user in posible_user:
|
||||||
|
@ -1107,11 +1113,11 @@ class EditOrganizationBlog(
|
||||||
notification = Notification(
|
notification = Notification(
|
||||||
owner=user,
|
owner=user,
|
||||||
author=self.request.profile,
|
author=self.request.profile,
|
||||||
category= action,
|
category=action,
|
||||||
html_link=html,
|
html_link=html,
|
||||||
)
|
)
|
||||||
notification.save()
|
notification.save()
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
with transaction.atomic(), revisions.create_revision():
|
with transaction.atomic(), revisions.create_revision():
|
||||||
res = super(EditOrganizationBlog, self).form_valid(form)
|
res = super(EditOrganizationBlog, self).form_valid(form)
|
||||||
|
@ -1120,6 +1126,7 @@ class EditOrganizationBlog(
|
||||||
self.create_notification("Edit blog")
|
self.create_notification("Edit blog")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class PendingBlogs(
|
class PendingBlogs(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
|
|
|
@ -40,7 +40,7 @@ from judge.models import (
|
||||||
Judge,
|
Judge,
|
||||||
Language,
|
Language,
|
||||||
Problem,
|
Problem,
|
||||||
ProblemClarification,
|
ContestProblemClarification,
|
||||||
ProblemGroup,
|
ProblemGroup,
|
||||||
ProblemTranslation,
|
ProblemTranslation,
|
||||||
ProblemType,
|
ProblemType,
|
||||||
|
@ -251,7 +251,7 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
|
||||||
context["contest_problem"] = contest_problem
|
context["contest_problem"] = contest_problem
|
||||||
|
|
||||||
if contest_problem:
|
if contest_problem:
|
||||||
clarifications = self.object.clarifications
|
clarifications = contest_problem.clarifications
|
||||||
context["has_clarifications"] = clarifications.count() > 0
|
context["has_clarifications"] = clarifications.count() > 0
|
||||||
context["clarifications"] = clarifications.order_by("-date")
|
context["clarifications"] = clarifications.order_by("-date")
|
||||||
context["submission_limit"] = contest_problem.max_submissions
|
context["submission_limit"] = contest_problem.max_submissions
|
||||||
|
@ -665,8 +665,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
participation = self.request.profile.current_contest
|
participation = self.request.profile.current_contest
|
||||||
if participation:
|
if participation:
|
||||||
clarifications = ProblemClarification.objects.filter(
|
clarifications = ContestProblemClarification.objects.filter(
|
||||||
problem__in=participation.contest.problems.all()
|
problem__in=participation.contest.contest_problems.all()
|
||||||
)
|
)
|
||||||
context["has_clarifications"] = clarifications.count() > 0
|
context["has_clarifications"] = clarifications.count() > 0
|
||||||
context["clarifications"] = clarifications.order_by("-date")
|
context["clarifications"] = clarifications.order_by("-date")
|
||||||
|
|
|
@ -106,9 +106,9 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% for clarification in clarifications %}
|
{% for clarification in clarifications %}
|
||||||
<li class="clarification">
|
<li class="clarification">
|
||||||
<a href="{{ url('problem_detail', clarification.problem.code) }}"
|
<a href="{{ url('problem_detail', clarification.problem.problem.code) }}"
|
||||||
class="problem">
|
class="problem">
|
||||||
{{ clarification.problem.name }}
|
{{ clarification.problem.problem.name }}
|
||||||
</a>
|
</a>
|
||||||
<span class="time">{{ relative_time(clarification.date) }}</span>
|
<span class="time">{{ relative_time(clarification.date) }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -150,9 +150,9 @@
|
||||||
{% for clarification in clarifications %}
|
{% for clarification in clarifications %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url('problem_detail', clarification.problem.code) }}"
|
<a href="{{ url('problem_detail', clarification.problem.problem.code) }}"
|
||||||
class="problem">
|
class="problem">
|
||||||
{{ clarification.problem.name }}
|
{{ clarification.problem.problem.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="time">{{ relative_time(clarification.date) }}</td>
|
<td class="time">{{ relative_time(clarification.date) }}</td>
|
||||||
|
|
Loading…
Reference in a new issue