commit b8f2bbbadf127540116cbcc78330653337e02554 Author: Xiao Furen Date: Tue Aug 5 06:15:47 2025 +0800 test commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6569b55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,206 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +######### +db.sqlite3.* \ No newline at end of file diff --git a/point_system/manage.py b/point_system/manage.py new file mode 100644 index 0000000..940e742 --- /dev/null +++ b/point_system/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'point_system.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/point_system/point_system/__init__.py b/point_system/point_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/point_system/point_system/asgi.py b/point_system/point_system/asgi.py new file mode 100644 index 0000000..74072d7 --- /dev/null +++ b/point_system/point_system/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for point_system project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'point_system.settings') + +application = get_asgi_application() diff --git a/point_system/point_system/models.py b/point_system/point_system/models.py new file mode 100644 index 0000000..beeb308 --- /dev/null +++ b/point_system/point_system/models.py @@ -0,0 +1,2 @@ +from django.db import models + diff --git a/point_system/point_system/settings.py b/point_system/point_system/settings.py new file mode 100644 index 0000000..c7d9068 --- /dev/null +++ b/point_system/point_system/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for point_system project. + +Generated by 'django-admin startproject' using Django 5.2. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-h-e*7!1gztzg6*m5k-oo-vp_y26jukv+%z-6ikw*f_n0dvm(mr' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'students.apps.StudentsConfig', + # 'point_system.apps.PointSystemConfig', + +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'point_system.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'point_system.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'zh-hant' + +TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Taipei' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/point_system/point_system/urls.py b/point_system/point_system/urls.py new file mode 100644 index 0000000..40eda9e --- /dev/null +++ b/point_system/point_system/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for point_system project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + + path('', include('students.urls')), + +] diff --git a/point_system/point_system/wsgi.py b/point_system/point_system/wsgi.py new file mode 100644 index 0000000..9a87e74 --- /dev/null +++ b/point_system/point_system/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for point_system project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'point_system.settings') + +application = get_wsgi_application() diff --git a/point_system/students.csv b/point_system/students.csv new file mode 100644 index 0000000..a1a502d --- /dev/null +++ b/point_system/students.csv @@ -0,0 +1,25 @@ +"學號 +Student +ID","姓名 +Name","年級 +year","希望生 +Hope admission +scheme’ students","減免 +Tuition +waiver","弱勢_生輔組 +Financial assistances +for disadvantaged +students","就學貸款 +Loan",Email +D13K48007,李艾臻,1,'-,'-,'-,0,D13K48007@ntu.edu.tw;zaq4747@yahoo.com.tw +R13458003,林慕峰,1,'-,'-,'-,0,R13458003@ntu.edu.tw;brenton.lin.tw@gmail.com +R13458019,"林 樸",1,'-,'-,'-,0,R13458019@ntu.edu.tw;pu@johnsonandannie.com +R13K47020,劉書瑜,1,'-,'-,'-,0,R13K47020@ntu.edu.tw;freestyle770614@gmail.com +R13K47029,陳品辰,1,'-,'-,'-,0,R13K47029@ntu.edu.tw;sw1sw2sw3@gmail.com +R13K47031,"NANDHITHA SURULIANDI",1,'-,'-,'-,0,R13K47031@ntu.edu.tw;nandhithasuruliandi@gmail.com +D12K48020,傅冠豪,2,'-,'-,'-,0,D12K48020@ntu.edu.tw;haobbc@hotmail.com +R12458006,周宜葇,2,'-,通過,'-,0,R12458006@ntu.edu.tw;r12458006@g.ntu.edu.tw +R12458007,林育暄,2,'-,'-,'-,0,R12458007@ntu.edu.tw;sunny891228@gmail.com +R12458014,劉羽軒,2,'-,'-,'-,0,R12458014@ntu.edu.tw;liumilk613@gmail.com +R12458015,"ZOLNAMAR DORJSEMBE",2,'-,'-,'-,0,R12458015@ntu.edu.tw;zolnamar@gmail.com +R11458015,郭士榮,3,'-,'-,'-,0,R11458015@ntu.edu.tw;tonypaul57@gmail.com diff --git a/point_system/students/__init__.py b/point_system/students/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/point_system/students/admin.py b/point_system/students/admin.py new file mode 100644 index 0000000..e633f24 --- /dev/null +++ b/point_system/students/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin +from .models import Student, PointRecord + +@admin.register(Student) +class StudentAdmin(admin.ModelAdmin): + list_display = ('student_id', 'name', 'grade', 'hope_admission_scheme', 'tuition_waiver', 'financial_assistance_disadvantaged_students', 'loan', 'email', 'is_active') + search_fields = ('student_id', 'name') + ordering = ['student_id'] # Changed to order by student_id as an example + list_filter = ['grade', 'hope_admission_scheme', 'tuition_waiver', 'financial_assistance_disadvantaged_students', 'loan', 'is_active'] + +@admin.register(PointRecord) +class PointRecordAdmin(admin.ModelAdmin): + list_display = ('student', 'datetime') # Updated to use datetime + search_fields = ['student__name', 'student__student_id'] + list_filter = ['datetime'] # Updated to use datetime + ordering = ['-datetime'] # Updated to use datetime + + fieldsets = [ + (None, { + 'fields': ['student'] + }), + ('點名時間', { + 'fields': ['datetime'], # Updated to use datetime + 'classes': ['collapse'] # 顯示為可展開的區塊 + }), + ] \ No newline at end of file diff --git a/point_system/students/apps.py b/point_system/students/apps.py new file mode 100644 index 0000000..c242c4d --- /dev/null +++ b/point_system/students/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StudentsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'students' diff --git a/point_system/students/forms.py b/point_system/students/forms.py new file mode 100644 index 0000000..de4b8e6 --- /dev/null +++ b/point_system/students/forms.py @@ -0,0 +1,8 @@ +from django import forms + +class StudentLoginForm(forms.Form): + student_id_or_name = forms.CharField( + label='學號或姓名', + max_length=100, + required=True + ) \ No newline at end of file diff --git a/point_system/students/management/commands/import_students.py b/point_system/students/management/commands/import_students.py new file mode 100644 index 0000000..27cedb5 --- /dev/null +++ b/point_system/students/management/commands/import_students.py @@ -0,0 +1,51 @@ +import csv +from django.core.management.base import BaseCommand, CommandError +from ...models import Student + +class Command(BaseCommand): + help = 'Import students from a CSV file' + + def add_arguments(self, parser): + parser.add_argument('csv_file', type=str, help='The path to the CSV file') + + def handle(self, *args, **kwargs): + csv_file_path = kwargs['csv_file'] + + with open(csv_file_path, newline='', encoding='utf-8-sig') as csvfile: + reader = csv.DictReader(csvfile) + + for row in reader: + student_id = row.get('學號\nStudent\nID') + name = row.get('姓名\nName') + + if not student_id or not name: + self.stdout.write(self.style.WARNING(f"Skipping row due to missing student_id or name: {row}")) + continue + + grade = int(row.get('年級\nyear', 0)) if row.get('年級\nyear') else None + hope_admission_scheme = row.get('希望生\nHope admission\nscheme’ students').strip("'") == '通過' + tuition_waiver = row.get('減免\nTuition\nwaiver').strip("'") == '通過' + financial_assistance_disadvantaged_students = row.get('弱勢_生輔組\nFinancial assistances\nfor disadvantaged\nstudents').strip("'") == '通過' + loan = int(row.get('就學貸款\nLoan', 0)) if row.get('就學貸款\nLoan') else 0 + emails = row.get('Email').split(';') + + # Use the first email for simplicity; you can handle multiple emails as needed + email = emails[0] if emails else None + + student, created = Student.objects.update_or_create( + student_id=student_id, + defaults={ + 'name': name, + 'grade': grade, + 'hope_admission_scheme': hope_admission_scheme, + 'tuition_waiver': tuition_waiver, + 'financial_assistance_disadvantaged_students': financial_assistance_disadvantaged_students, + 'loan': loan, + 'email': email + } + ) + + if created: + self.stdout.write(self.style.SUCCESS(f"Successfully added student: {student}")) + else: + self.stdout.write(self.style.WARNING(f"Updated existing student: {student}")) diff --git a/point_system/students/migrations/0001_initial.py b/point_system/students/migrations/0001_initial.py new file mode 100644 index 0000000..bd03cfd --- /dev/null +++ b/point_system/students/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2 on 2025-07-25 02:53 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Student', + fields=[ + ('student_id', models.CharField(max_length=10, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('grade', models.IntegerField(blank=True, null=True)), + ('hope_admission_scheme', models.BooleanField(blank=True, default=False, null=True)), + ('tuition_waiver', models.BooleanField(blank=True, default=False, null=True)), + ('financial_assistance_disadvantaged_students', models.BooleanField(blank=True, default=False, null=True)), + ('loan', models.IntegerField(blank=True, default=0, null=True)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='PointRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datetime', models.DateTimeField(auto_now_add=True)), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='students.student')), + ], + options={ + 'verbose_name': 'point record', + 'verbose_name_plural': 'point records', + }, + ), + ] diff --git a/point_system/students/migrations/__init__.py b/point_system/students/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/point_system/students/models.py b/point_system/students/models.py new file mode 100644 index 0000000..125e978 --- /dev/null +++ b/point_system/students/models.py @@ -0,0 +1,26 @@ +from django.db import models + +class Student(models.Model): + student_id = models.CharField(max_length=10, primary_key=True) + name = models.CharField(max_length=255) + grade = models.IntegerField(null=True, blank=True) + hope_admission_scheme = models.BooleanField(default=False, null=True, blank=True) + tuition_waiver = models.BooleanField(default=False, null=True, blank=True) + financial_assistance_disadvantaged_students = models.BooleanField(default=False, null=True, blank=True) + loan = models.IntegerField(null=True, blank=True, default=0) + email = models.EmailField(null=True, blank=True) + is_active = models.BooleanField(default=True) + + def __str__(self): + return self.name + +class PointRecord(models.Model): + student = models.ForeignKey(Student, on_delete=models.CASCADE) + datetime = models.DateTimeField(auto_now_add=True) # Combined date and time field + + class Meta: + verbose_name = 'point record' + verbose_name_plural = 'point records' + + def __str__(self): + return f"{self.datetime} - {self.student}" diff --git a/point_system/students/templates/base.html b/point_system/students/templates/base.html new file mode 100644 index 0000000..972be29 --- /dev/null +++ b/point_system/students/templates/base.html @@ -0,0 +1,71 @@ + + + + + + + {% block title %}學生考勤系統{% endblock %} + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ + + + + + + {% block scripts %} + {% endblock %} + + \ No newline at end of file diff --git a/point_system/students/templates/students/attendance_report.html b/point_system/students/templates/students/attendance_report.html new file mode 100644 index 0000000..871e5a6 --- /dev/null +++ b/point_system/students/templates/students/attendance_report.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} +{% block content %} +

年度出席統計報表

+ + + + + + + + + {% for entry in attendance_data %} + + + + + + + {% endfor %} +
學號姓名{{ last_year }} 出席次數{{ current_year }} 出席次數
{{ entry.student.student_id }}{{ entry.student.name }}{{ entry.last_year_count }}{{ entry.current_year_count }}
+ +返回首頁 +{% endblock %} \ No newline at end of file diff --git a/point_system/students/templates/students/recent_attendance.html b/point_system/students/templates/students/recent_attendance.html new file mode 100644 index 0000000..02f8f17 --- /dev/null +++ b/point_system/students/templates/students/recent_attendance.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block title %}最近考勤紀錄{% endblock %} + +{% block content %} +
+

最近20筆考勤紀錄

+ + {% if recent_records %} +
+ + + + + + + + + + {% for record in recent_records %} + + + + + + {% endfor %} + +
學生姓名學號考勤時間
{{ record.student.name }}{{ record.student.student_id }}{{ record.datetime|date:"Y-m-d H:i:s" }}
+
+ {% else %} +

目前沒有考勤紀錄。

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/point_system/students/templates/students/record_attendance.html b/point_system/students/templates/students/record_attendance.html new file mode 100644 index 0000000..2513679 --- /dev/null +++ b/point_system/students/templates/students/record_attendance.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% load tz %} + +{% block content %} +

紀錄點名

+ +
+ {% csrf_token %} + +
+ {% for student in students %} +
+ {% endfor %} +
+ +
+ +{% if point_records %} +

Recent Attendance Records

+ +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/point_system/students/templates/students/student_list.html b/point_system/students/templates/students/student_list.html new file mode 100644 index 0000000..c50d661 --- /dev/null +++ b/point_system/students/templates/students/student_list.html @@ -0,0 +1,36 @@ + +{% extends 'base.html' %} + +{% block title %}學生列表{% endblock %} + +{% block content %} +

學生列表

+ + + + + + + + + + + + + {% for item in student_data %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
學號姓名班級考勤次數詳細資料
{{ item.student.student_id }}{{ item.student.name }}{{ item.student.grade|default:"未填" }}{{ item.total_attendance }}查看詳情
沒有學生資料
+ +{% endblock %} \ No newline at end of file diff --git a/point_system/students/tests.py b/point_system/students/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/point_system/students/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/point_system/students/urls.py b/point_system/students/urls.py new file mode 100644 index 0000000..35051cb --- /dev/null +++ b/point_system/students/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('record/', views.record_attendance, name='record_attendance'), + path('attendance//', views.show_attendance, name='show_attendance'), + path('report/', views.attendance_report, name='attendance_report'), # 新增路由 + path('recent/', views.recent_attendance, name='recent_attendance'), # 新增最近考勤紀錄路由 + path('student_list/', views.student_list, name='student_list'), +] \ No newline at end of file diff --git a/point_system/students/views.py b/point_system/students/views.py new file mode 100644 index 0000000..2cceb11 --- /dev/null +++ b/point_system/students/views.py @@ -0,0 +1,84 @@ +from django.shortcuts import render, redirect +from .models import Student, PointRecord # 從 students.models 引入 PointRecord +from .forms import StudentLoginForm +from datetime import datetime, timedelta + +def index(request): + return render(request, 'base.html') + + +def record_attendance(request): + point_records = [] + if request.method == 'POST': + student_ids = request.POST.getlist('student_id') + current_datetime = datetime.now() + one_hour_ago = current_datetime - timedelta(hours=1) + + for student_id in student_ids: + try: + student = Student.objects.get(student_id=student_id) + + # 檢查該學生在一小時內是否有點名紀錄 + if not PointRecord.objects.filter(student=student, datetime__gte=one_hour_ago).exists(): + point_record = PointRecord.objects.create(student=student, datetime=current_datetime) + point_records.append(point_record) + except Student.DoesNotExist: + pass + + + students = Student.objects.filter(is_active=True).order_by('name') # Filter active students + return render(request, 'students/record_attendance.html', {'students': students, 'point_records': point_records}) + +def show_attendance(request, student_id): + student = Student.objects.get(student_id=student_id) + point_records = PointRecord.objects.filter(student=student).order_by('-datetime') + return render(request, 'students/show_attendance.html', {'point_records': point_records, 'student': student}) + +def attendance_report(request): + current_year = datetime.now().year + last_year = current_year - 1 + + # 計算每個學生在去年和今年的出席次數 + students = Student.objects.all() + attendance_data = [] + + for student in students: + last_year_records = PointRecord.objects.filter(student=student, datetime__year=last_year).count() + current_year_records = PointRecord.objects.filter(student=student, datetime__year=current_year).count() + + # 只添加那些至少有一年的出席記錄的學生 + if last_year_records > 0 or current_year_records > 0: + attendance_data.append({ + 'student': student, + 'last_year_count': last_year_records, + 'current_year_count': current_year_records + }) + + return render(request, 'students/attendance_report.html', { + 'attendance_data': attendance_data, + 'last_year': last_year, + 'current_year': current_year + }) + +def recent_attendance(request): + """顯示最後20筆考勤紀錄""" + recent_records = PointRecord.objects.select_related('student').order_by('-datetime')[:20] + return render(request, 'students/recent_attendance.html', {'recent_records': recent_records}) + +def student_list(request): + """顯示所有學生資訊及考勤統計""" + students = Student.objects.all().order_by('student_id') # 按學號排序 + + # 為每個學生計算考勤統計 + student_data = [] + for student in students: + total_attendance = PointRecord.objects.filter(student=student).count() + # 可以添加更多統計資訊 + student_data.append({ + 'student': student, + 'total_attendance': total_attendance + }) + + return render(request, 'students/student_list.html', { + 'student_data': student_data + }) \ No newline at end of file