diff --git a/point_system/locale/en/LC_MESSAGES/django.po b/point_system/locale/en/LC_MESSAGES/django.po index ffad73c..d354d5c 100644 --- a/point_system/locale/en/LC_MESSAGES/django.po +++ b/point_system/locale/en/LC_MESSAGES/django.po @@ -157,7 +157,7 @@ msgid "免報告" msgstr "Exempt" msgid "次" -msgstr "time(s)" +msgstr "times" msgid "拖曳排程微調提示:" msgstr "Drag & Drop Tip: " @@ -170,3 +170,60 @@ msgstr "View-Only Mode: " msgid "登入系統後,可拖曳學生調整報告日期。" msgstr "Log in to drag and drop student cards to adjust presentation dates." + +msgid "不可將報告日期移轉至早於今天" +msgstr "Cannot move schedule date to earlier than today" + +msgid "當日無排定報告者 (可拖曳學生至此)" +msgstr "No presenter scheduled for this day (drag & drop student here)" + +msgid "學士生" +msgstr "Undergraduate Student" + +msgid "學士生 (1次/月)" +msgstr "Undergraduate Student (1 time/month)" + +msgid "%(y1)s年%(m1)s月 & %(y2)s年%(m2)s月" +msgstr "%(y1)s-%(m1)s & %(y2)s-%(m2)s" + +msgid "%(year)s 年 %(month)s 月週會排程 (當月)" +msgstr "%(year)s-%(month)s Schedule (Current Month)" + +msgid "%(year)s 年 %(month)s 月週會排程 (下月)" +msgstr "%(year)s-%(month)s Schedule (Next Month)" + +msgid "%(date_str)s (星期一)" +msgstr "%(date_str)s (Monday)" + +msgid "%(count)s 位報告者" +msgstr "%(count)s presenters" + +msgid "%(count)s 位報告者(單數)" +msgstr "%(count)s presenter" + +msgid "經調整後但仍未能報告者,須準備一小時之進度簡報,當週向指導教授當面報告,次週週會補報。若連續兩次應報告而未報告,將視同解除指導關係。" +msgstr "Students who fail to present after schedule adjustment must prepare a 1-hour progress report, present in person to their advisor that week, and make up the presentation at next week's seminar. Failing to present twice consecutively will be treated as termination of the advising relationship." + +msgid "確定要校正並驗證排程規則嗎?(如為交換生將自動刪除其卡片,並清理重複或補齊缺漏排程)" +msgstr "Are you sure you want to verify and clean schedule rules? Exchange students' cards will be deleted, and missing/duplicate schedules will be cleaned up." + +msgid "博士在職生 (1次/2月)" +msgstr "Ph.D. In-Service Student (1 time/2 months)" + +msgid "一般生 (1次/月)" +msgstr "Regular Student (1 time/month)" + +msgid "0 次" +msgstr "0 time" + +msgid "%(count)s 次" +msgstr "%(count)s times" + +msgid "%(count)s 次(單數)" +msgstr "%(count)s time" + +msgid "無在學學生資料" +msgstr "No active student data" + + + diff --git a/point_system/locale/zh_Hant/LC_MESSAGES/django.po b/point_system/locale/zh_Hant/LC_MESSAGES/django.po index 595c2f6..a9cd6da 100644 --- a/point_system/locale/zh_Hant/LC_MESSAGES/django.po +++ b/point_system/locale/zh_Hant/LC_MESSAGES/django.po @@ -35,3 +35,9 @@ msgstr "登出" msgid "AD 登入" msgstr "AD 登入" + +msgid "%(count)s 位報告者(單數)" +msgstr "%(count)s 位報告者" + +msgid "%(count)s 次(單數)" +msgstr "%(count)s 次" diff --git a/point_system/point_system/settings.py b/point_system/point_system/settings.py index 50e3993..c205e55 100644 --- a/point_system/point_system/settings.py +++ b/point_system/point_system/settings.py @@ -27,6 +27,17 @@ DEBUG = True ALLOWED_HOSTS = ['*'] +CSRF_TRUSTED_ORIGINS = [ + 'https://point.mdi.bar', + 'http://point.mdi.bar', + 'https://*.mdi.bar', + 'http://*.mdi.bar', + 'http://localhost', + 'http://127.0.0.1', +] + +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + # Application definition @@ -141,6 +152,9 @@ LOCALE_PATHS = [ # https://docs.djangoproject.com/en/5.2/howto/static-files/ STATIC_URL = 'static/' +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field diff --git a/point_system/static/weekly_schedule_en.html b/point_system/static/weekly_schedule_en.html new file mode 100644 index 0000000..3a61d0d --- /dev/null +++ b/point_system/static/weekly_schedule_en.html @@ -0,0 +1,1040 @@ + + + + + + + Weekly Seminar Schedule + + + + + + + + +
+ + + + + +
+

+ 2026-08 Schedule + (Current Month) +

+
+ +
+
+
+
+ 2026-08-03 (Monday) +
+ 0 presenter +
+
+ + +

Cannot move schedule date to earlier than + today

+ + +
+
+
+ +
+
+
+
+ 2026-08-10 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
BUSHRA UROOJ + + Ph.D. Student + + + + Regular + +
林 樸 + + Master's Student + + + + Regular + +
李亮慶 + + Master's Student + + + + Regular + +
Emran Khan + + Master's Student + + + + Regular + +
林宇晨 + + Undergraduate + Student + + + + Regular + +
林冠儒 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-17 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林孟忻 + + Ph.D. Student + + + + Regular + +
劉冠岑 + + Master's Student + + + + Regular + +
江霈 + + Master's Student + + + + Regular + +
李艾臻 + + Ph.D. Student + + + + In-Service + +
劉書瑜 + + Master's Student + + + + In-Service + +
孫艾琳 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-24 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
ZOLNAMAR DORJSEMBE + + Ph.D. Student + + + + Regular + +
林慕峰 + + Master's Student + + + + Regular + +
劉睿恩 + + Master's Student + + + + Regular + +
白承右 + + Undergraduate + Student + + + + Regular + +
陳品辰 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-31 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
NANDHITHA SURULIANDI + + Master's Student + + + + Regular + +
林靖哲 + + Master's Student + + + + Regular + +
林綺音 + + Master's Student + + + + Regular + +
郭彥翔 + + Undergraduate + Student + + + + Regular + +
傅冠豪 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+ + +
+

+ 2026-09 Schedule (Next + Month) +

+
+ +
+
+
+
+ 2026-09-07 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林孟忻 + + Ph.D. Student + + + + Regular + +
林慕峰 + + Master's Student + + + + Regular + +
劉冠岑 + + Master's Student + + + + Regular + +
林宇晨 + + Undergraduate + Student + + + + Regular + +
陳其蒙 + + Ph.D. Student + + + + In-Service + +
陳品辰 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-14 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
BUSHRA UROOJ + + Ph.D. Student + + + + Regular + +
劉睿恩 + + Master's Student + + + + Regular + +
NANDHITHA SURULIANDI + + Master's Student + + + + Regular + +
林綺音 + + Master's Student + + + + Regular + +
Emran Khan + + Master's Student + + + + Regular + +
孫艾琳 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-21 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林 樸 + + Master's Student + + + + Regular + +
林靖哲 + + Master's Student + + + + Regular + +
江霈 + + Master's Student + + + + Regular + +
白承右 + + Undergraduate + Student + + + + Regular + +
吳瓊芳 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-28 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
ZOLNAMAR DORJSEMBE + + Ph.D. Student + + + + Regular + +
李亮慶 + + Master's Student + + + + Regular + +
郭彥翔 + + Undergraduate + Student + + + + Regular + +
黃子芬 + + Ph.D. Student + + + + In-Service + +
劉書瑜 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/point_system/students/migrations/0007_alter_student_degree_type.py b/point_system/students/migrations/0007_alter_student_degree_type.py new file mode 100644 index 0000000..dd251de --- /dev/null +++ b/point_system/students/migrations/0007_alter_student_degree_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2026-08-04 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('students', '0006_alter_weeklyschedule_options_student_email2_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='student', + name='degree_type', + field=models.CharField(choices=[('學士生', '學士生'), ('碩士生', '碩士生'), ('博士生', '博士生')], default='碩士生', max_length=10, verbose_name='學位類別'), + ), + ] diff --git a/point_system/students/models.py b/point_system/students/models.py index b8a5743..bb6d25f 100644 --- a/point_system/students/models.py +++ b/point_system/students/models.py @@ -1,6 +1,7 @@ from django.db import models DEGREE_CHOICES = [ + ('學士生', '學士生'), ('碩士生', '碩士生'), ('博士生', '博士生'), ] diff --git a/point_system/students/static/students/weekly_schedule_en.html b/point_system/students/static/students/weekly_schedule_en.html new file mode 100644 index 0000000..3a61d0d --- /dev/null +++ b/point_system/students/static/students/weekly_schedule_en.html @@ -0,0 +1,1040 @@ + + + + + + + Weekly Seminar Schedule + + + + + + + + +
+ + + + + +
+

+ 2026-08 Schedule + (Current Month) +

+
+ +
+
+
+
+ 2026-08-03 (Monday) +
+ 0 presenter +
+
+ + +

Cannot move schedule date to earlier than + today

+ + +
+
+
+ +
+
+
+
+ 2026-08-10 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
BUSHRA UROOJ + + Ph.D. Student + + + + Regular + +
林 樸 + + Master's Student + + + + Regular + +
李亮慶 + + Master's Student + + + + Regular + +
Emran Khan + + Master's Student + + + + Regular + +
林宇晨 + + Undergraduate + Student + + + + Regular + +
林冠儒 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-17 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林孟忻 + + Ph.D. Student + + + + Regular + +
劉冠岑 + + Master's Student + + + + Regular + +
江霈 + + Master's Student + + + + Regular + +
李艾臻 + + Ph.D. Student + + + + In-Service + +
劉書瑜 + + Master's Student + + + + In-Service + +
孫艾琳 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-24 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
ZOLNAMAR DORJSEMBE + + Ph.D. Student + + + + Regular + +
林慕峰 + + Master's Student + + + + Regular + +
劉睿恩 + + Master's Student + + + + Regular + +
白承右 + + Undergraduate + Student + + + + Regular + +
陳品辰 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-08-31 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
NANDHITHA SURULIANDI + + Master's Student + + + + Regular + +
林靖哲 + + Master's Student + + + + Regular + +
林綺音 + + Master's Student + + + + Regular + +
郭彥翔 + + Undergraduate + Student + + + + Regular + +
傅冠豪 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+ + +
+

+ 2026-09 Schedule (Next + Month) +

+
+ +
+
+
+
+ 2026-09-07 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林孟忻 + + Ph.D. Student + + + + Regular + +
林慕峰 + + Master's Student + + + + Regular + +
劉冠岑 + + Master's Student + + + + Regular + +
林宇晨 + + Undergraduate + Student + + + + Regular + +
陳其蒙 + + Ph.D. Student + + + + In-Service + +
陳品辰 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-14 (Monday) +
+ 6 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
BUSHRA UROOJ + + Ph.D. Student + + + + Regular + +
劉睿恩 + + Master's Student + + + + Regular + +
NANDHITHA SURULIANDI + + Master's Student + + + + Regular + +
林綺音 + + Master's Student + + + + Regular + +
Emran Khan + + Master's Student + + + + Regular + +
孫艾琳 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-21 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
林 樸 + + Master's Student + + + + Regular + +
林靖哲 + + Master's Student + + + + Regular + +
江霈 + + Master's Student + + + + Regular + +
白承右 + + Undergraduate + Student + + + + Regular + +
吳瓊芳 + + Ph.D. Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+
+
+ 2026-09-28 (Monday) +
+ 5 presenters +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDegree LevelCategory
ZOLNAMAR DORJSEMBE + + Ph.D. Student + + + + Regular + +
李亮慶 + + Master's Student + + + + Regular + +
郭彥翔 + + Undergraduate + Student + + + + Regular + +
黃子芬 + + Ph.D. Student + + + + In-Service + +
劉書瑜 + + Master's Student + + + + In-Service + +
+
+ +
+
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/point_system/students/templates/students/show_attendance.html b/point_system/students/templates/students/show_attendance.html index a64a7c0..7e2a9ce 100644 --- a/point_system/students/templates/students/show_attendance.html +++ b/point_system/students/templates/students/show_attendance.html @@ -1,4 +1,5 @@ {% extends 'base.html' %} +{% load i18n %} {% block title %}{{ student.name }} ({{ student.student_id }}) - 考勤紀錄 - 1218 Lab{% endblock %} @@ -34,9 +35,11 @@ 學位 / 身分
{% if student.degree_type == '博士生' %} - 博士生 + {% trans "博士生" %} + {% elif student.degree_type == '學士生' %} + {% trans "學士生" %} {% else %} - 碩士生 + {% trans "碩士生" %} {% endif %} {% if student.student_category == '在職生' %} diff --git a/point_system/students/templates/students/student_list.html b/point_system/students/templates/students/student_list.html index bbbd05e..e491a77 100644 --- a/point_system/students/templates/students/student_list.html +++ b/point_system/students/templates/students/student_list.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} +{% load i18n %} {% block title %}學生列表{% endblock %} @@ -41,20 +42,18 @@ - - + {% if user.is_authenticated %}{% endif %} {% for item in student_data %} - - + - + {% if user.is_authenticated %}{% endif %} {% empty %} - + {% endfor %} diff --git a/point_system/students/templates/students/weekly_schedule.html b/point_system/students/templates/students/weekly_schedule.html index bec2fba..fd79f00 100644 --- a/point_system/students/templates/students/weekly_schedule.html +++ b/point_system/students/templates/students/weekly_schedule.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}週會排程 - 1218 Lab{% endblock %} +{% block title %}{% trans "週會排程" %} - 1218 Lab{% endblock %} {% block content %}
@@ -13,13 +13,13 @@ {% trans "資料庫狀態: 已同步儲存" %} - {{ m1_year }}年{{ m1_month }}月 & {{ m2_year }}年{{ m2_month }}月 + {% blocktrans with y1=m1_year m1=m1_month y2=m2_year m2=m2_month %}{{ y1 }}年{{ m1 }}月 & {{ y2 }}年{{ m2 }}月{% endblocktrans %} {% if user.is_authenticated %}
{% csrf_token %} @@ -31,26 +31,32 @@
{% trans "週會報告排程規則" %}
-
-
-
-
{% trans "碩士生 (一般 & 在職)" %}
- {% trans "排程頻率: 每月 1 次" %} -
-
-
-
-
{% trans "博士一般生" %}
- {% trans "排程頻率: 每月 1 次" %} -
-
-
+
+
{% trans "博士在職生" %}
{% trans "排程頻率: 每 2 個月 1 次" %}
-
+
+
+
{% trans "博士一般生" %}
+ {% trans "排程頻率: 每月 1 次" %} +
+
+
+
+
{% trans "碩士生 (一般 & 在職)" %}
+ {% trans "排程頻率: 每月 1 次" %} +
+
+
+
+
{% trans "學士生" %}
+ {% trans "排程頻率: 每月 1 次" %} +
+
+
{% trans "交換生 (交換中)" %}
{% trans "排程狀態: 免報告" %} @@ -80,7 +86,7 @@

- {{ m1_year }} 年 {{ m1_month }} 月週會排程 (當月) + {% blocktrans with year=m1_year month=m1_month %}{{ year }} 年 {{ month }} 月週會排程 (當月){% endblocktrans %}

{% for item in schedule_m1 %} @@ -89,9 +95,15 @@ style="transition: all 0.2s ease-in-out;">
- {{ item.formatted }} (星期一) + {% blocktrans with date_str=item.formatted %}{{ date_str }} (星期一){% endblocktrans %}
- {{ item.students|length }} 位報告者 + + {% if item.students|length > 1 %} + {% blocktrans with count=item.students|length %}{{ count }} 位報告者{% endblocktrans %} + {% else %} + {% blocktrans with count=item.students|length %}{{ count }} 位報告者(單數){% endblocktrans %} + {% endif %} +
{% if item.students %} @@ -99,10 +111,9 @@
學號 姓名 在學狀態 學位類別 身分類別 班級/年級詳細資料詳細資料
{{ item.student.student_id }}{{ item.student.name }}{{ item.student.name }} {% if item.student.is_active %} 在學 @@ -64,9 +63,11 @@ {% if item.student.degree_type == '博士生' %} - 博士生 + {% trans "博士生" %} + {% elif item.student.degree_type == '學士生' %} + {% trans "學士生" %} {% else %} - 碩士生 + {% trans "碩士生" %} {% endif %} @@ -79,11 +80,11 @@ {% endif %} {{ item.student.grade|default:"未填" }}查看詳情查看詳情
沒有符合條件的學生資料沒有符合條件的學生資料
- - - - + + + @@ -116,29 +127,30 @@ {% endif %} - @@ -147,7 +159,11 @@
學號姓名學位類別身分類別{% trans "姓名" %}{% trans "學位類別" %}{% trans "身分類別" %}
{% if user.is_authenticated %}{% endif %} - {{ student.student_id }} + class="fas fa-grip-vertical text-muted me-2">{% endif %}{{ student.name }} {{ student.name }} {% if student.degree_type == '博士生' %} 博士生 + class="fas fa-user-graduate me-1">{% trans "博士生" %} + {% elif student.degree_type == '學士生' %} + {% trans "學士生" %} {% else %} 碩士生 + class="fas fa-graduation-cap me-1">{% trans "碩士生" %} {% endif %} {% if student.student_category == '在職生' %} 在職生 + class="fas fa-user-tie me-1">{% trans "在職生" %} {% elif student.student_category == '交換中' %} 交換中 + class="fas fa-exchange-alt me-1">{% trans "交換中" %} {% else %} 一般生 + class="fas fa-user me-1">{% trans "一般生" %} {% endif %}
{% else %} -

當日無排定報告者 (可拖曳學生至此)

+ {% if item.is_past %} +

{% trans "不可將報告日期移轉至早於今天" %}

+ {% else %} +

{% trans "當日無排定報告者 (可拖曳學生至此)" %}

+ {% endif %} {% endif %}
@@ -159,7 +175,7 @@

- {{ m2_year }} 年 {{ m2_month }} 月週會排程 (下月) + {% blocktrans with year=m2_year month=m2_month %}{{ year }} 年 {{ month }} 月週會排程 (下月){% endblocktrans %}

{% for item in schedule_m2 %} @@ -168,9 +184,15 @@ style="transition: all 0.2s ease-in-out;">
- {{ item.formatted }} (星期一) + {% blocktrans with date_str=item.formatted %}{{ date_str }} (星期一){% endblocktrans %}
- {{ item.students|length }} 位報告者 + + {% if item.students|length > 1 %} + {% blocktrans with count=item.students|length %}{{ count }} 位報告者{% endblocktrans %} + {% else %} + {% blocktrans with count=item.students|length %}{{ count }} 位報告者(單數){% endblocktrans %} + {% endif %} +
{% if item.students %} @@ -178,10 +200,9 @@ - - - - + + + @@ -195,29 +216,30 @@ {% endif %} - @@ -226,7 +248,11 @@
學號姓名學位類別身分類別{% trans "姓名" %}{% trans "學位類別" %}{% trans "身分類別" %}
{% if user.is_authenticated %}{% endif %} - {{ student.student_id }} + class="fas fa-grip-vertical text-muted me-2">{% endif %}{{ student.name }} {{ student.name }} {% if student.degree_type == '博士生' %} 博士生 + class="fas fa-user-graduate me-1">{% trans "博士生" %} + {% elif student.degree_type == '學士生' %} + {% trans "學士生" %} {% else %} 碩士生 + class="fas fa-graduation-cap me-1">{% trans "碩士生" %} {% endif %} {% if student.student_category == '在職生' %} 在職生 + class="fas fa-user-tie me-1">{% trans "在職生" %} {% elif student.student_category == '交換中' %} 交換中 + class="fas fa-exchange-alt me-1">{% trans "交換中" %} {% else %} 一般生 + class="fas fa-user me-1">{% trans "一般生" %} {% endif %}
{% else %} -

當日無排定報告者 (可拖曳學生至此)

+ {% if item.is_past %} +

{% trans "不可將報告日期移轉至早於今天" %}

+ {% else %} +

{% trans "當日無排定報告者 (可拖曳學生至此)" %}

+ {% endif %} {% endif %}
@@ -238,47 +264,46 @@
-
學生個人報告統計明細 (兩月排程核對)
+
{% trans "學生個人報告統計明細 (兩月排程核對)" %}
- - - - - + + + + {% for row in summary_data %} - - + {% empty %} - + {% endfor %} @@ -381,4 +406,4 @@ }); {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/point_system/students/templates/students/weekly_schedule_en_iframe.html b/point_system/students/templates/students/weekly_schedule_en_iframe.html new file mode 100644 index 0000000..5aeee27 --- /dev/null +++ b/point_system/students/templates/students/weekly_schedule_en_iframe.html @@ -0,0 +1,203 @@ + + + + + + + Weekly Seminar Schedule + + + + + + + + +
+ + + + + +
+

+ {{ m1_year }}-{{ m1_month|stringformat:"02d" }} Schedule + (Current Month) +

+
+ {% for item in schedule_m1 %} +
+
+
+
+ {{ item.formatted }} (Monday) +
+ {% if item.students|length > 1 %}{{ item.students|length }} presenters{% else %}{{ item.students|length }} presenter{% endif %} +
+
+ {% if item.students %} +
+
學號姓名身份 / 規則排定報告日期 (Mondays)應報告次數{% trans "姓名" %}{% trans "身份 / 規則" %}{% trans "排定報告日期 (Mondays)" %}{% trans "應報告次數" %}
{{ row.student.student_id }}{{ row.student.name }}{{ row.student.name }} {{ row.rule_desc }} {% if row.assigned_dates %} {% for d in row.assigned_dates %} - {{ d - }} + {{ d }} {% endfor %} {% else %} - 免報告 + {% trans "免報告" %} {% endif %} - {% if row.total_count > 0 %} - {{ row.total_count }} 次 + {% if row.total_count > 1 %} + {% blocktrans with count=row.total_count %}{{ count }} 次{% endblocktrans %} + {% elif row.total_count == 1 %} + {% blocktrans with count=row.total_count %}{{ count }} 次(單數){% endblocktrans %} {% else %} - 0 次 + {% trans "0 次" %} {% endif %}
無在學學生資料{% trans "無在學學生資料" %}
+ + + + + + + + + {% for student in item.students %} + + + + + + {% endfor %} + +
NameDegree LevelCategory
{{ student.name }} + {% if student.degree_type == '博士生' %} + Ph.D. Student + {% elif student.degree_type == '學士生' %} + Undergraduate + Student + {% else %} + Master's Student + {% endif %} + + {% if student.student_category == '在職生' %} + In-Service + {% elif student.student_category == '交換中' %} + On Exchange + {% else %} + Regular + {% endif %} +
+
+ {% else %} + {% if item.is_past %} +

Cannot move schedule date to earlier than + today

+ {% else %} +

No presenter scheduled for this day

+ {% endif %} + {% endif %} +
+
+ + {% endfor %} + + + + +
+

+ {{ m2_year }}-{{ m2_month|stringformat:"02d" }} Schedule (Next + Month) +

+
+ {% for item in schedule_m2 %} +
+
+
+
+ {{ item.formatted }} (Monday) +
+ {% if item.students|length > 1 %}{{ item.students|length }} presenters{% else %}{{ item.students|length }} presenter{% endif %} +
+
+ {% if item.students %} +
+ + + + + + + + + + {% for student in item.students %} + + + + + + {% endfor %} + +
NameDegree LevelCategory
{{ student.name }} + {% if student.degree_type == '博士生' %} + Ph.D. Student + {% elif student.degree_type == '學士生' %} + Undergraduate + Student + {% else %} + Master's Student + {% endif %} + + {% if student.student_category == '在職生' %} + In-Service + {% elif student.student_category == '交換中' %} + On Exchange + {% else %} + Regular + {% endif %} +
+
+ {% else %} + {% if item.is_past %} +

Cannot move schedule date to earlier than + today

+ {% else %} +

No presenter scheduled for this day

+ {% endif %} + {% endif %} +
+
+
+ {% endfor %} +
+
+ + + + + \ No newline at end of file diff --git a/point_system/students/tests.py b/point_system/students/tests.py index 112ba54..c8ad166 100644 --- a/point_system/students/tests.py +++ b/point_system/students/tests.py @@ -1,6 +1,7 @@ from django.test import TestCase, Client from django.contrib.auth import authenticate, get_user_model from django.urls import reverse +from django.conf import settings from unittest.mock import patch, MagicMock from students.models import Student from students.ad_backend import ActiveDirectoryBackend, parse_group_dn @@ -182,7 +183,12 @@ class ActiveDirectoryBackendTestCase(TestCase): s = Student.objects.create(student_id='D12458003', name='測試生', degree_type='博士生', is_active=True) PointRecord.objects.create(student=s) - # Unauthenticated / non-admin user -> 簽到歷史紀錄 hidden with permission notice + # Unauthenticated user -> redirected to login + response = self.client.get(reverse('show_attendance', kwargs={'student_id': 'D12458003'})) + self.assertEqual(response.status_code, 302) + + # Authenticated non-admin user -> 簽到歷史紀錄 hidden with permission notice + self.client.force_login(self.test_user) response = self.client.get(reverse('show_attendance', kwargs={'student_id': 'D12458003'})) self.assertEqual(response.status_code, 200) self.assertContains(response, 'D12458003') @@ -553,3 +559,55 @@ class ActiveDirectoryBackendTestCase(TestCase): with open(log_file, 'r', encoding='utf-8') as f: content = f.read() self.assertIn("AUDIT LOG: User 'aduser' executed Verify & Clean Schedule Rules", content) + +class EnglishStaticIframeAndPastEmptyCardTestCase(TestCase): + def setUp(self): + self.client = Client() + self.student = Student.objects.create( + student_id="M999", + name="Test Student", + degree_type="碩士生", + student_category="一般生", + is_active=True + ) + + def test_past_date_empty_card_message(self): + response = self.client.get(reverse('weekly_schedule')) + self.assertEqual(response.status_code, 200) + + def test_generate_english_schedule_static_html_and_iframe_endpoint(self): + response = self.client.get(reverse('weekly_schedule_en_iframe')) + self.assertEqual(response.status_code, 200) + self.assertIn("Weekly Seminar Schedule", response.content.decode('utf-8')) + + static_file = settings.BASE_DIR / 'static' / 'weekly_schedule_en.html' + self.assertTrue(static_file.exists()) + + def test_bachelor_student_degree_choice_and_scheduling(self): + bachelor = Student.objects.create( + student_id="B101", + name="Bachelor Student", + degree_type="學士生", + student_category="一般生", + is_active=True + ) + self.assertEqual(bachelor.degree_type, "學士生") + + response = self.client.get(reverse('weekly_schedule')) + self.assertEqual(response.status_code, 200) + self.assertIn("學士生", response.content.decode('utf-8')) + + def test_verify_schedule_rules_never_adds_past_dates(self): + from datetime import date + today = date.today() + # Delete schedules for a student + s = Student.objects.create(student_id="M888", name="New M Student", degree_type="碩士生", student_category="一般生", is_active=True) + # Verify rules + self.client.post(reverse('verify_schedule_rules')) + # Check all schedules for this student + from students.models import WeeklySchedule + past_schedules = WeeklySchedule.objects.filter(student=s, date__lt=today) + self.assertEqual(past_schedules.count(), 0) + + + diff --git a/point_system/students/urls.py b/point_system/students/urls.py index c8b33e3..56c1053 100644 --- a/point_system/students/urls.py +++ b/point_system/students/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ path('schedule/', views.weekly_schedule, name='weekly_schedule'), path('schedule/move/', views.move_schedule_item, name='move_schedule_item'), path('schedule/verify-rules/', views.verify_schedule_rules, name='verify_schedule_rules'), + path('schedule/embed/en/', views.weekly_schedule_en_iframe, name='weekly_schedule_en_iframe'), path('update-from-excel/', views.update_students_from_excel, name='update_students_from_excel'), path('upload-excel/', views.upload_students_excel, name='upload_students_excel'), path('set-language//', views.set_language, name='set_language'), diff --git a/point_system/students/views.py b/point_system/students/views.py index 1c7d22d..4c50eea 100644 --- a/point_system/students/views.py +++ b/point_system/students/views.py @@ -8,7 +8,8 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.contrib import messages from django.conf import settings -from django.http import HttpResponseForbidden, JsonResponse +from django.http import HttpResponseForbidden, JsonResponse, HttpResponse +from django.views.decorators.clickjacking import xframe_options_exempt from django.db.models import Q, Case, When, Value, IntegerField from .models import Student, PointRecord, WeeklySchedule from .forms import StudentLoginForm, ADLoginForm @@ -22,6 +23,7 @@ def get_month_mondays(year, month): return [d for d in c.itermonthdates(year, month) if d.month == month and d.weekday() == 0] from django.utils import translation +from django.utils.translation import gettext_lazy as _ def set_language(request, lang_code): """ @@ -171,6 +173,8 @@ def process_excel_rows(rows): else: if student_id.upper().startswith('D'): degree_type = '博士生' + elif student_id.upper().startswith('B'): + degree_type = '學士生' student_category = '一般生' for k, v in row_dict.items(): @@ -301,7 +305,7 @@ def sync_weekly_schedule_to_db(m1_year, m1_month, m2_year, m2_month, force=False for s in active_students: if s.student_category == '交換中': continue - elif s.degree_type == '碩士生': + elif s.degree_type in ('碩士生', '學士生'): monthly_students.append(s) elif s.degree_type == '博士生' and s.student_category == '一般生': monthly_students.append(s) @@ -316,14 +320,18 @@ def sync_weekly_schedule_to_db(m1_year, m1_month, m2_year, m2_month, force=False m1_pool = monthly_students + bimonthly_m1 m2_pool = monthly_students + bimonthly_m2 + today = date.today() + valid_m1 = [d for d in m1_mondays if d >= today] or m1_mondays + valid_m2 = [d for d in m2_mondays if d >= today] or m2_mondays + if not m1_exists or force: for idx, student in enumerate(m1_pool): - monday_date = m1_mondays[idx % len(m1_mondays)] + monday_date = valid_m1[idx % len(valid_m1)] WeeklySchedule.objects.get_or_create(student=student, date=monday_date) if not m2_exists or force: for idx, student in enumerate(m2_pool): - monday_date = m2_mondays[idx % len(m2_mondays)] + monday_date = valid_m2[idx % len(valid_m2)] WeeklySchedule.objects.get_or_create(student=student, date=monday_date) def index(request): @@ -399,6 +407,7 @@ def record_attendance(request): students = Student.objects.filter(is_active=True).order_by('name') return render(request, 'students/record_attendance.html', {'students': students, 'point_records': point_records}) +@login_required(login_url='login') def show_attendance(request, student_id): student = Student.objects.get(student_id=student_id) point_records = PointRecord.objects.filter(student=student).order_by('-datetime') @@ -506,39 +515,41 @@ def weekly_schedule(request): default=Value(4), output_field=IntegerField() ) + degree_order = Case( + When(student__degree_type='博士生', then=Value(1)), + When(student__degree_type='碩士生', then=Value(2)), + When(student__degree_type='學士生', then=Value(3)), + default=Value(4), + output_field=IntegerField() + ) schedule_m1 = [] for m in m1_mondays: records = WeeklySchedule.objects.filter(date=m).select_related('student').annotate( - cat_order=category_order - ).order_by('cat_order', 'student__student_id') + cat_order=category_order, + deg_order=degree_order, + ).order_by('cat_order', 'deg_order', 'student__student_id') schedule_m1.append({ 'date': m, 'formatted': m.strftime('%Y-%m-%d'), - 'students': [r.student for r in records] + 'students': [r.student for r in records], + 'is_past': m < today, }) schedule_m2 = [] for m in m2_mondays: records = WeeklySchedule.objects.filter(date=m).select_related('student').annotate( - cat_order=category_order - ).order_by('cat_order', 'student__student_id') + cat_order=category_order, + deg_order=degree_order, + ).order_by('cat_order', 'deg_order', 'student__student_id') schedule_m2.append({ 'date': m, 'formatted': m.strftime('%Y-%m-%d'), - 'students': [r.student for r in records] + 'students': [r.student for r in records], + 'is_past': m < today, }) - student_cat_order = Case( - When(student_category='一般生', then=Value(1)), - When(student_category='在職生', then=Value(2)), - When(student_category='交換中', then=Value(3)), - default=Value(4), - output_field=IntegerField() - ) - active_students = Student.objects.filter(is_active=True).annotate( - cat_order=student_cat_order - ).order_by('cat_order', 'student_id') + active_students = Student.objects.filter(is_active=True).order_by('student_id') summary_data = [] for s in active_students: assigned_m1 = [m['formatted'] for m in schedule_m1 if s in m['students']] @@ -546,13 +557,15 @@ def weekly_schedule(request): assigned_dates = assigned_m1 + assigned_m2 if s.student_category == '交換中': - rule_desc = '交換生 (免報告)' + rule_desc = _('交換生 (免報告)') + elif s.degree_type == '學士生': + rule_desc = _('學士生 (1次/月)') elif s.degree_type == '碩士生': - rule_desc = '碩士生 (1次/月)' + rule_desc = _('碩士生 (1次/月)') elif s.degree_type == '博士生' and s.student_category == '一般生': - rule_desc = '博士一般生 (1次/月)' + rule_desc = _('博士一般生 (1次/月)') else: - rule_desc = '博士在職生 (1次/2個月)' + rule_desc = _('博士在職生 (1次/2個月)') summary_data.append({ 'student': s, @@ -565,6 +578,9 @@ def weekly_schedule(request): request.user.is_superuser or check_user_has_all_ad_groups(request.user, request, ['Administrators']) ) + # Generate clean English static HTML for iframe embedding + generate_english_schedule_static_html() + return render(request, 'students/weekly_schedule.html', { 'm1_year': m1_year, 'm1_month': m1_month, @@ -616,7 +632,7 @@ def move_schedule_item(request): if student.student_category == '交換中': return JsonResponse({'success': False, 'error': '交換生無需報告,無法排程'}, status=400) - is_monthly = (student.degree_type == '碩士生') or (student.degree_type == '博士生' and student.student_category == '一般生') + is_monthly = (student.degree_type in ('碩士生', '學士生')) or (student.degree_type == '博士生' and student.student_category == '一般生') if is_monthly: if from_date.month != to_date.month or from_date.year != to_date.year: @@ -633,6 +649,9 @@ def move_schedule_item(request): schedule_entry.month = to_date.month schedule_entry.save() + # Update static English HTML for iframe embed + generate_english_schedule_static_html() + log_msg = f"AUDIT LOG: User '{request.user.username}' (authenticated) moved weekly schedule for student '{student.student_id}' ({student.name}) from {from_date} to {to_date}." logger.info(log_msg) schedule_logger.info(log_msg) @@ -684,8 +703,12 @@ def verify_schedule_rules(request): active_students = Student.objects.filter(is_active=True).exclude(student_category='交換中') + valid_m1_mondays = [d for d in m1_mondays if d >= today] + valid_m2_mondays = [d for d in m2_mondays if d >= today] + valid_all_mondays = [d for d in (m1_mondays + m2_mondays) if d >= today] + for s in active_students: - is_monthly = (s.degree_type == '碩士生') or (s.degree_type == '博士生' and s.student_category == '一般生') + is_monthly = (s.degree_type in ('碩士生', '學士生')) or (s.degree_type == '博士生' and s.student_category == '一般生') if is_monthly: # Check Month 1 @@ -695,9 +718,10 @@ def verify_schedule_rules(request): r.delete() deleted_count += 1 elif len(m1_records) == 0: - monday = min(m1_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) - WeeklySchedule.objects.create(student=s, date=monday) - created_count += 1 + if valid_m1_mondays: + monday = min(valid_m1_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) + WeeklySchedule.objects.create(student=s, date=monday) + created_count += 1 # Check Month 2 m2_records = list(WeeklySchedule.objects.filter(student=s, year=m2_year, month=m2_month).order_by('date')) @@ -706,9 +730,10 @@ def verify_schedule_rules(request): r.delete() deleted_count += 1 elif len(m2_records) == 0: - monday = min(m2_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) - WeeklySchedule.objects.create(student=s, date=monday) - created_count += 1 + if valid_m2_mondays: + monday = min(valid_m2_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) + WeeklySchedule.objects.create(student=s, date=monday) + created_count += 1 else: # 博士在職生: 1 report per 2 months bimonthly_records = list(WeeklySchedule.objects.filter(student=s, year__in=[m1_year, m2_year], month__in=[m1_month, m2_month]).order_by('date')) @@ -717,16 +742,19 @@ def verify_schedule_rules(request): r.delete() deleted_count += 1 elif len(bimonthly_records) == 0: - all_mondays = m1_mondays + m2_mondays - monday = min(all_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) - WeeklySchedule.objects.create(student=s, date=monday) - created_count += 1 + if valid_all_mondays: + monday = min(valid_all_mondays, key=lambda d: WeeklySchedule.objects.filter(date=d).count()) + WeeklySchedule.objects.create(student=s, date=monday) + created_count += 1 log_msg = f"AUDIT LOG: User '{request.user.username}' executed Verify & Clean Schedule Rules. Deleted {deleted_count} non-compliant/exchange cards, added {created_count} missing cards." logger.info(log_msg) schedule_logger.info(log_msg) print(log_msg) + # Update static English HTML for iframe embed + generate_english_schedule_static_html() + messages.success( request, f"校正與驗證排程規則完成!已清理 {deleted_count} 筆不符規則之卡片(包含刪除交換生卡片與重複排程),補齊 {created_count} 筆缺漏排程。" @@ -820,4 +848,135 @@ def upload_students_excel(request): except Exception as e: messages.error(request, f"上傳匯入學生資料失敗: {e}") - return redirect('student_list') \ No newline at end of file + return redirect('student_list') + + +def generate_english_schedule_static_html(): + """ + Generates a standalone, clean English static HTML file for the weekly schedule, + suitable for embedding directly in an