diff --git a/src/locale/en_US/LC_MESSAGES/django.po b/src/locale/en_US/LC_MESSAGES/django.po index a402e1d86..6f617f9f9 100644 --- a/src/locale/en_US/LC_MESSAGES/django.po +++ b/src/locale/en_US/LC_MESSAGES/django.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: PyCon TW\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-21 07:48+0800\n" +"POT-Creation-Date: 2024-07-20 15:33+0800\n" "PO-Revision-Date: 2022-07-11 02:23+0800\n" "Last-Translator: Tom Chen \n" "Language-Team: English (United States) (http://www.transifex.com/pycon-" @@ -1563,6 +1563,14 @@ msgstr "Change Password" msgid "Reviews" msgstr "Reviews" +#: templates/default/_includes/dashboard_tablist.html:28 +#: templates/default/reviews/review_stages.html:16 +#: templates/default/reviews/review_stages.html:44 +#, fuzzy +#| msgid "Reviews" +msgid "Review Stages" +msgstr "Reviews" + #: templates/default/_includes/nav/dashboard_nav.html:10 msgid "Log out" msgstr "Log out" @@ -1791,6 +1799,7 @@ msgstr "Change Log In Password" #: templates/default/registration/password_change_form.html:22 #: templates/default/reviews/review_form.html:101 +#: templates/default/reviews/review_stages.html:114 msgid "Submit" msgstr "Submit" @@ -2010,6 +2019,18 @@ msgstr "My Previous Reviews" msgid "Review Proposal" msgstr "Review Proposal" +#: templates/default/reviews/review_stages.html:23 +#, fuzzy +#| msgid "Current Review Stage: %(review_stage)s" +msgid "Current Review Stage Setting" +msgstr "Current Review Stage: %(review_stage)s" + +#: templates/default/reviews/review_stages.html:35 +#, fuzzy +#| msgid "Personal Review Stats" +msgid "Set Review Stage" +msgstr "Personal Review Stats" + #: templates/default/reviews/talk_proposal_list.html:14 #, python-format msgid "Current Review Stage: %(review_stage)s" @@ -5108,28 +5129,28 @@ msgstr "latest agreed CoC version" msgid "agreed at" msgstr "agreed at" -#: users/views.py:44 +#: users/views.py:48 msgid "Sign up successful. You are now logged in." msgstr "Sign up successful. You are now logged in." -#: users/views.py:61 +#: users/views.py:65 msgid "Email verification successful." msgstr "Email verification successful." -#: users/views.py:73 +#: users/views.py:77 #, python-brace-format msgid "A verification email has been sent to {email}" msgstr "A verification email has been sent to {email}" -#: users/views.py:103 +#: users/views.py:107 msgid "Your profile has been updated successfully." msgstr "Your profile has been updated successfully." -#: users/views.py:116 +#: users/views.py:120 msgid "Your new password has been applied successfully." msgstr "Your new password has been applied successfully." -#: users/views.py:123 +#: users/views.py:127 msgid "" "An email is sent to your email account. Please check your inbox for furthur " "instructions to reset your password." @@ -5137,7 +5158,7 @@ msgstr "" "An email is sent to your email account. Please check your inbox for furthur " "instructions to reset your password." -#: users/views.py:131 +#: users/views.py:135 msgid "Password reset successful. You can now login." msgstr "Password reset successful. You can now login." diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.po b/src/locale/zh_Hant/LC_MESSAGES/django.po index a1e05ee09..90a41f541 100644 --- a/src/locale/zh_Hant/LC_MESSAGES/django.po +++ b/src/locale/zh_Hant/LC_MESSAGES/django.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: PyCon TW\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-21 07:48+0800\n" +"POT-Creation-Date: 2024-07-20 15:33+0800\n" "PO-Revision-Date: 2022-07-11 02:25+0800\n" "Last-Translator: Tom Chen \n" "Language-Team: Chinese Traditional (http://www.transifex.com/pycon-taiwan/" @@ -1516,6 +1516,12 @@ msgstr "更改密碼" msgid "Reviews" msgstr "審查" +#: templates/default/_includes/dashboard_tablist.html:28 +#: templates/default/reviews/review_stages.html:16 +#: templates/default/reviews/review_stages.html:44 +msgid "Review Stages" +msgstr "審查階段" + #: templates/default/_includes/nav/dashboard_nav.html:10 msgid "Log out" msgstr "登出" @@ -1739,6 +1745,7 @@ msgstr "更改密碼" #: templates/default/registration/password_change_form.html:22 #: templates/default/reviews/review_form.html:101 +#: templates/default/reviews/review_stages.html:114 msgid "Submit" msgstr "送出" @@ -1952,6 +1959,14 @@ msgstr "我之前的審查意見" msgid "Review Proposal" msgstr "審查提案" +#: templates/default/reviews/review_stages.html:23 +msgid "Current Review Stage Setting" +msgstr "目前審查階段設定" + +#: templates/default/reviews/review_stages.html:35 +msgid "Set Review Stage" +msgstr "設定審查階段" + #: templates/default/reviews/talk_proposal_list.html:14 #, python-format msgid "Current Review Stage: %(review_stage)s" @@ -4983,35 +4998,35 @@ msgstr "最後同意的 CoC 版本" msgid "agreed at" msgstr "同意於" -#: users/views.py:44 +#: users/views.py:48 msgid "Sign up successful. You are now logged in." msgstr "註冊成功。您現在已被登入。" -#: users/views.py:61 +#: users/views.py:65 msgid "Email verification successful." msgstr "Email 認證成功。" -#: users/views.py:73 +#: users/views.py:77 #, python-brace-format msgid "A verification email has been sent to {email}" msgstr "一封認證用的 email 已經寄送至 {email}" -#: users/views.py:103 +#: users/views.py:107 msgid "Your profile has been updated successfully." msgstr "您的講者資訊已成功地更新。" -#: users/views.py:116 +#: users/views.py:120 msgid "Your new password has been applied successfully." msgstr "已成功地使用您的新密碼。" -#: users/views.py:123 +#: users/views.py:127 msgid "" "An email is sent to your email account. Please check your inbox for furthur " "instructions to reset your password." msgstr "" "一封電子郵件已經發送至您的信箱。請檢查您的收件匣並參照信中指示重設密碼。" -#: users/views.py:131 +#: users/views.py:135 msgid "Password reset successful. You can now login." msgstr "密碼重設成功。您現在即可登入。" diff --git a/src/proposals/templatetags/proposals.py b/src/proposals/templatetags/proposals.py index b8a245e88..d24ee2aa3 100644 --- a/src/proposals/templatetags/proposals.py +++ b/src/proposals/templatetags/proposals.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.template import Library from proposals.utils import SEP_DEFAULT, SEP_LAST, format_names @@ -7,6 +8,11 @@ @register.filter def speaker_names_display( - proposal, sep_default=SEP_DEFAULT, sep_last=SEP_LAST): + proposal, sep_default=SEP_DEFAULT, sep_last=SEP_LAST): names = [info.user.speaker_name for info in proposal.speakers] return format_names(names, sep_default=sep_default, sep_last=sep_last) + + +@register.filter +def configuration_switch(value): + return settings.CONFERENCE_DEFAULT_SLUG + value diff --git a/src/proposals/tests/test_templatetags.py b/src/proposals/tests/test_templatetags.py index 97ba79818..0dc2de2f5 100644 --- a/src/proposals/tests/test_templatetags.py +++ b/src/proposals/tests/test_templatetags.py @@ -53,3 +53,34 @@ def test_speaker_names_display(talk_proposals, parser): """) assert actual == expected + +@pytest.fixture +def review_stage_keys(): + review_stage_keys = [ + '.proposals.creatable', '.proposals.editable', '.proposals.withdrawable', + '.reviews.visible.to.submitters', '.reviews.stage', + '.proposals.disable.after' + ] + return review_stage_keys + +def test_configuration_switch(review_stage_keys, parser): + result = render_template( + '{% load proposals %}' + '', {'proposals': review_stage_keys}, + ) + actual = parser.arrange(parser.parse(text=result, create_parent=False)) + expected = parser.arrange(""" + + """) + assert actual == expected diff --git a/src/reviews/urls.py b/src/reviews/urls.py index f81b1df27..2c18103ee 100644 --- a/src/reviews/urls.py +++ b/src/reviews/urls.py @@ -1,9 +1,11 @@ from django.conf.urls import url +from . import views from .views import ReviewEditView, TalkProposalListView urlpatterns = [ url(r'^$', TalkProposalListView.as_view(), name='review_proposal_list'), url(r'^talk/(?P\d+)/$', ReviewEditView.as_view(), name='review_edit'), + url(r'^review-stages/$', views.review_stages, name='review_stages'), ] diff --git a/src/reviews/views.py b/src/reviews/views.py index 6e0ea7534..784cf617f 100644 --- a/src/reviews/views.py +++ b/src/reviews/views.py @@ -1,12 +1,19 @@ import collections +import datetime import json import random +import pytz +from django.conf import settings +from django.conf.global_settings import DATETIME_INPUT_FORMATS +from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count from django.http import Http404 +from django.shortcuts import render from django.urls import reverse from django.views.generic import ListView, UpdateView +from registry.helper import reg from core.utils import SequenceQuerySet from proposals.models import TalkProposal @@ -26,7 +33,6 @@ def dispatch(self, request, *args, **kwargs): class TalkProposalListView(ReviewableMixin, PermissionRequiredMixin, ListView): - model = TalkProposal permission_required = REVIEW_REQUIRED_PERMISSIONS template_name = 'reviews/talk_proposal_list.html' @@ -170,9 +176,9 @@ def get_stage_1_reviews(self): review__stage=1, review__reviewer=self.request.user ) & TalkProposal.objects.filter( - review__stage=2, - review__reviewer=self.request.user - ) + review__stage=2, + review__reviewer=self.request.user + ) ) stage_1_reviews = ( Review.objects @@ -200,7 +206,6 @@ def get_stage_2_reviews(self): class ReviewEditView(ReviewableMixin, PermissionRequiredMixin, UpdateView): - form_class = ReviewForm permission_required = REVIEW_REQUIRED_PERMISSIONS template_name = 'reviews/review_form.html' @@ -327,3 +332,77 @@ def get_success_url(self): if query_string: return url + '?' + query_string return url + + +def review_stages(request): + current_review_stages_setting = {} + review_stages_list = [ + 'Call for Proposals', + 'Locked (proposal editing and reviewing disabled)', + 'First Round Review', 'Modification Stage', 'Second Round Review', + 'Internal Decision', 'Announcement of Acceptance' + ] + review_stages_var = [ + 'proposals.creatable', 'proposals.editable', 'proposals.withdrawable', + 'reviews.visible.to.submitters', 'reviews.stage', + 'proposals.disable.after' + ] + + # Initialize current setting with existing value + for tag in review_stages_var: + key = settings.CONFERENCE_DEFAULT_SLUG + '.' + tag + value = reg.get(key, '') + update_current_review_stages_setting(tag, value, current_review_stages_setting) + + if request.method == 'POST': + for tag in review_stages_var: + key = settings.CONFERENCE_DEFAULT_SLUG + '.' + tag + if tag == 'proposals.disable.after': + if request.POST[tag] == "": + continue + else: + date_time_obj = date_preprocess(DATETIME_INPUT_FORMATS, request.POST[tag]) + if date_time_obj is None: + messages.error(request, 'Please input valid date format : " + "%Y-%m-%dT%H:%M') + value = None + else: + tz_selectd = pytz.timezone(request.POST['review_timezone']) + loc_dt = tz_selectd.localize(date_time_obj).strftime( + '%Y-%m-%d %H:%M:%S%z') + value = loc_dt + elif tag == 'reviews.stage': + value = int(request.POST[tag]) + else: + value = request.POST[tag] + reg[key] = value + update_current_review_stages_setting(tag, value, current_review_stages_setting) + + messages.info(request, 'This setting has been changed successfully.') + + return render( + request, 'reviews/review_stages.html', { + 'timezones': pytz.common_timezones, + 'review_stages_list': review_stages_list, + 'current_review_stages_setting': current_review_stages_setting, + **reviews_state()._asdict() + }) + + +def update_current_review_stages_setting(tag, value, current_review_stages_setting): + # Django template language does not support dictionary keys containing "." + if "." in tag: + tag = tag.replace(".", "_") + current_review_stages_setting[tag] = value + + +def date_preprocess(datetime_input_format, value): + # Add defined datetime formatx + datetime_input_format += ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M'] + value = value.strip() + # Try to strptime against each input format. + for format in datetime_input_format: + try: + return datetime.datetime.strptime(value, format) + except (ValueError, TypeError): + continue + return None diff --git a/src/static/css/components/_buttons.scss b/src/static/css/components/_buttons.scss index c7240dbd7..0ee29b276 100644 --- a/src/static/css/components/_buttons.scss +++ b/src/static/css/components/_buttons.scss @@ -10,6 +10,10 @@ @include button-variant($btn-natural-color, $btn-natural-bg, $btn-natural-border); } +.btn-natural-noborder { + @include button-variant($btn-natural-color, $btn-natural-bg, $btn-natural-border); +} + .btn-action{ @include button-variant($btn-action-color, $btn-action-bg, $btn-action-border); } @@ -40,6 +44,19 @@ } } +.btn-natural-noborder { + &:hover { + background-color: $btn-natural-hover-bg; + } + &, + &:focus, + &:active { + outline: none; + border-color: transparent; + } + margin-bottom: 20px; +} + .btn-natural.btn-withdraw { &, &:hover, diff --git a/src/static/css/components/_texts.scss b/src/static/css/components/_texts.scss index 659429f2c..cf494cfb1 100644 --- a/src/static/css/components/_texts.scss +++ b/src/static/css/components/_texts.scss @@ -8,3 +8,8 @@ .text-emphasize { @include roboto-medium(); } + +.input-customized-size input{ + width: 16.2em; +} + diff --git a/src/static/css/components/_toggle.scss b/src/static/css/components/_toggle.scss new file mode 100644 index 000000000..f5d121e78 --- /dev/null +++ b/src/static/css/components/_toggle.scss @@ -0,0 +1,47 @@ +.material-switch > input[type="checkbox"] { + display: none; +} + +.material-switch > label { + cursor: pointer; + height: 0px; + position: relative; + width: 40px; +} + +.material-switch > label::before { + background: rgb(0, 0, 0); + box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5); + border-radius: 8px; + content: ''; + height: 16px; + margin-top: -8px; + position:absolute; + opacity: 0.3; + transition: all 0.4s ease-in-out; + width: 40px; +} + +.material-switch > label::after { + background: rgb(255, 255, 255); + border-radius: 16px; + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); + content: ''; + height: 24px; + left: -4px; + margin-top: -8px; + position: absolute; + top: -4px; + transition: all 0.3s ease-in-out; + width: 24px; +} + +.material-switch > input[type="checkbox"]:checked + label::before { + background: inherit; + opacity: 0.5; +} + +.material-switch > input[type="checkbox"]:checked + label::after { + background: inherit; + left: 20px; +} \ No newline at end of file diff --git a/src/static/css/main.scss b/src/static/css/main.scss index 962ffb4d4..ee36a5e16 100644 --- a/src/static/css/main.scss +++ b/src/static/css/main.scss @@ -20,6 +20,7 @@ @import "components/tables"; @import "components/texts"; @import "components/lists"; +@import "components/toggle"; // Pages @import "pages/proposals"; diff --git a/src/static/css/vendors/bootstrap/_buttons.scss b/src/static/css/vendors/bootstrap/_buttons.scss index 6452b709f..da8828aba 100755 --- a/src/static/css/vendors/bootstrap/_buttons.scss +++ b/src/static/css/vendors/bootstrap/_buttons.scss @@ -110,6 +110,7 @@ a.btn { &:hover, &:focus, &:active { + outline: none; border-color: transparent; } &:hover, @@ -128,7 +129,6 @@ a.btn { } } - // Button Sizes // -------------------------------------------------- @@ -166,3 +166,4 @@ input[type="button"] { width: 100%; } } + diff --git a/src/static/css/vendors/bootstrap/_panels.scss b/src/static/css/vendors/bootstrap/_panels.scss index be9410f5b..d2c65b749 100755 --- a/src/static/css/vendors/bootstrap/_panels.scss +++ b/src/static/css/vendors/bootstrap/_panels.scss @@ -15,6 +15,7 @@ // Panel contents .panel-body { padding: $panel-body-padding; + color: black; @include clearfix; } @@ -33,8 +34,7 @@ .panel-title { margin-top: 0; margin-bottom: 0; - font-size: ceil(($font-size-base * 1.125)); - color: inherit; + color: black; > a, > small, diff --git a/src/static/js/reviews/review_stages.js b/src/static/js/reviews/review_stages.js new file mode 100644 index 000000000..8fa376d34 --- /dev/null +++ b/src/static/js/reviews/review_stages.js @@ -0,0 +1,85 @@ +var proposals_creatable = document.getElementById("proposals.creatable"); +var proposals_editable = document.getElementById("proposals.editable"); +var proposals_withdrawable = document.getElementById("proposals.withdrawable"); +var proposals_disable_after = document.getElementById("proposals.disable.after"); +var reviews_stage = document.getElementById("reviews.stage"); +var reviews_visible_to_submitters = document.getElementById("reviews.visible.to.submitters"); + +$('.hotkey').click(function () { + if ($(this).val() == "Call for Proposals") { + Call_for_Proposals(); + } else if ($(this).val() == "Locked (proposal editing and reviewing disabled)") { + Locked() + } else if ($(this).val() == "First Round Review") { + First_Round_Review() + } else if ($(this).val() == "Modification Stage") { + Modification_Stage() + } else if ($(this).val() == "Second Round Review") { + Second_Round_Review() + } else if ($(this).val() == "Internal Decision") { + Internal_Decision() + } else { + Announcement_of_Acceptance() + } + + /* + Proposal Review Stage Setting + Reference : https://github.com/pycontw/pycon.tw/blob/master/src/reviews/README.md + */ + function Call_for_Proposals() { + proposals_creatable.checked = true; + proposals_editable.checked = true; + proposals_withdrawable.checked = true; + reviews_stage.value = "0"; + reviews_visible_to_submitters.checked = false; + } + + function Locked() { + proposals_creatable.checked = false; + proposals_editable.checked = false; + proposals_withdrawable.checked = false; + reviews_stage.value = "0"; + reviews_visible_to_submitters.checked = false; + } + + function First_Round_Review() { + proposals_creatable.checked = false; + proposals_editable.checked = false; + proposals_withdrawable.checked = false; + reviews_stage.value = "1"; + reviews_visible_to_submitters.checked = false; + } + + function Modification_Stage() { + proposals_creatable.checked = false; + proposals_editable.checked = true; + proposals_withdrawable.checked = false; + reviews_stage.value = "0"; + reviews_visible_to_submitters.checked = true; + } + + function Second_Round_Review() { + proposals_creatable.checked = false; + proposals_editable.checked = false; + proposals_withdrawable.checked = false; + reviews_stage.value = "2"; + reviews_visible_to_submitters.checked = false; + } + + function Internal_Decision() { + proposals_creatable.checked = false; + proposals_editable.checked = false; + proposals_withdrawable.checked = false; + reviews_stage.value = "0"; + reviews_visible_to_submitters.checked = false; + } + + function Announcement_of_Acceptance() { + proposals_creatable.checked = false; + proposals_editable.checked = true; + proposals_withdrawable.checked = false; + reviews_stage.value = "0"; + reviews_visible_to_submitters.checked = true; + } + +}); diff --git a/src/templates/default/_includes/dashboard_tablist.html b/src/templates/default/_includes/dashboard_tablist.html index 05154e393..b3c72d496 100644 --- a/src/templates/default/_includes/dashboard_tablist.html +++ b/src/templates/default/_includes/dashboard_tablist.html @@ -22,4 +22,10 @@ {% endif %} {% endif %} + + {% if user.is_superuser %} +
  • + {% trans 'Review Stages' %} +
  • + {% endif %} diff --git a/src/templates/default/reviews/review_stages.html b/src/templates/default/reviews/review_stages.html new file mode 100644 index 000000000..d47929ff3 --- /dev/null +++ b/src/templates/default/reviews/review_stages.html @@ -0,0 +1,141 @@ +{% extends 'dashboard_base.html' %} + +{% load i18n static %} +{% load proposals %} +{% load compress crispy_forms_tags %} +{% load tz %} +{% get_current_timezone as TIME_ZONE %} + +{% block dashboard_tablist %} +{% include '_includes/dashboard_tablist.html' with active='admin' %} +{% endblock dashboard_tablist %} + +{% block main-content %} + +

    + {% trans 'Review Stages' %} +

    + +
    +
    + +
    +
    {% trans 'Current Review Stage Setting' %}
    +
      +
    • + {{ ".proposals.disable.after"|configuration_switch }} +
      +

      {{ current_review_stages_setting.proposals_disable_after }}

      +
      +
    • +
    +
    + +
    +

    {% trans 'Set Review Stage' %}

    +
    + {% for rs in review_stages_list %} + + {% endfor %} + +
    + {% csrf_token %} +
    +
    {% trans 'Review Stages' %} + +
    +
      +
    • + {{ ".proposals.creatable"|configuration_switch }} +
      + + + +
      +
    • +
    • + {{ ".proposals.editable"|configuration_switch }} +
      + + + +
      +
    • +
    • + {{ ".proposals.withdrawable"|configuration_switch }} +
      + + + +
      +
    • +
    • + {{ ".reviews.visible.to.submitters"|configuration_switch }} +
      + + + +
      +
    • +
    • + {{ ".reviews.stage"|configuration_switch }} +
      + +
      +
    • +
    • + {{ ".proposals.disable.after"|configuration_switch }} +
      + +
      +
    • +
    • + timezone +
      + +
      +
    • +
    +
    + +
    + {{ form|crispy }} + +
    +
    +
    +
    + +{% endblock main-content %} + +{% block extra_js %} +{% compress js %} + + +{% endcompress %} +{% endblock extra_js %} + diff --git a/src/users/urls.py b/src/users/urls.py index f71dd74ec..63b9d6636 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -4,11 +4,9 @@ from . import views urlpatterns = [ - url(r'^login/$', views.login, name='login'), url(r'^logout/$', views.logout, name='logout'), url(r'^profile/$', views.user_profile_update, name='user_profile_update'), - url(r'^password-change/$', views.password_change, name='password_change'), url(r'^password-change/done/$', views.password_change_done, name='password_change_done'), @@ -30,5 +28,4 @@ url(r'^agreement/$', views.coc_agree, name='coc_agreement'), - ] diff --git a/src/users/views.py b/src/users/views.py index 24415ac66..6d53b914b 100644 --- a/src/users/views.py +++ b/src/users/views.py @@ -193,7 +193,6 @@ def get_context_data(self, **kwargs): context.update(**reviews_state()._asdict()) return context - login = auth_views.LoginView.as_view(authentication_form=AuthenticationForm) logout = auth_views.LogoutView.as_view() password_change = PasswordChangeView.as_view()