parent
21311e3725
commit
40325d0281
@ -0,0 +1,3 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="wxapplet" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,12 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredIdentifiers">
|
||||
<list>
|
||||
<option value="seg.data_prepare.data_prepare_elec.helper_ply" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="wxapplet" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Rollcall_applet.iml" filepath="$PROJECT_DIR$/.idea/Rollcall_applet.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,45 @@
|
||||
"""
|
||||
ASGI config for Rollcall_applet 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/3.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
# import os
|
||||
#
|
||||
# from django.core.asgi import get_asgi_application
|
||||
# from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
# from channels.auth import AuthMiddlewareStack
|
||||
# from . import routings
|
||||
# from api.consumers import RollCallConsumer
|
||||
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rollcall_applet.settings')
|
||||
#
|
||||
#
|
||||
# # application = get_asgi_application()
|
||||
#
|
||||
# application = ProtocolTypeRouter(
|
||||
# {
|
||||
# "http": get_asgi_application(), # http走Django默认的asgi
|
||||
# "websocket": URLRouter(routings.websocket_urlpatterns), # websocket走channels
|
||||
# }
|
||||
# )
|
||||
|
||||
|
||||
import os
|
||||
from django.core.asgi import get_asgi_application
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from api.routing import websocket_urlpatterns
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
"http": get_asgi_application(),
|
||||
"websocket": AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
})
|
@ -0,0 +1,22 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include # 导入 path 和 re_path
|
||||
from api import views
|
||||
|
||||
urlpatterns = [
|
||||
# # 管理员页面的路由(如果需要)
|
||||
# path('admin/', admin.site.urls),
|
||||
|
||||
# 上传学生名单
|
||||
path('api/upload-students/', views.upload_students, name='upload_students'),
|
||||
# # 随机选择学生
|
||||
# path('api/select-student/', views.select_student, name='select-student'),
|
||||
# path('api/classroom_view/', views.classroom_view, name='classroom'),
|
||||
path('api/get_student_info/<str:student_id>/', views.get_student_info, name='get_student_info'),
|
||||
path('api/update_student_score/', views.update_student_score, name='update_student_score'),
|
||||
path('api/leaderboard/', views.leaderboard, name='leaderboard'),
|
||||
path('api/create_pledge/', views.create_pledge, name='create_pledge'),
|
||||
path('api/update_question_score', views.update_question_score, name='update_question_score'),
|
||||
path('api/export_students_scores', views.export_students_scores, name='export_students_scores'),
|
||||
path('__debug__/', include('debug_toolbar.urls')),
|
||||
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for Rollcall_applet 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/3.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rollcall_applet.settings')
|
||||
|
||||
application = get_wsgi_application()
|
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api'
|
@ -0,0 +1,34 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-09 06:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Pledge',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('pledge_score', models.IntegerField()),
|
||||
('status', models.IntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('student_id', models.CharField(max_length=10)),
|
||||
('student_name', models.CharField(max_length=10)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Students',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sid', models.CharField(max_length=10, unique=True, verbose_name='学号')),
|
||||
('name', models.CharField(max_length=10, verbose_name='姓名')),
|
||||
('score', models.DecimalField(decimal_places=1, default=0, max_digits=5, verbose_name='积分')),
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from .consumers import ClassroomConsumer
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/classroom/', ClassroomConsumer.as_asgi()),
|
||||
]
|
@ -0,0 +1,105 @@
|
||||
import os
|
||||
import pytest
|
||||
import django
|
||||
import json
|
||||
from asgiref.sync import sync_to_async
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from channels.layers import get_channel_layer
|
||||
from channels.db import database_sync_to_async # 确保导入
|
||||
from api.consumers import ClassroomConsumer
|
||||
from api.models import Students
|
||||
|
||||
# 设置 Django 设置模块
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rollcall_applet.settings')
|
||||
django.setup()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@database_sync_to_async
|
||||
def create_student(sid, name, score):
|
||||
return Students.objects.create(sid=sid, name=name, score=score)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@database_sync_to_async
|
||||
def get_student(sid):
|
||||
return Students.objects.get(sid=sid)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@database_sync_to_async
|
||||
def update_student_score(sid, new_score):
|
||||
student = Students.objects.get(sid=sid)
|
||||
student.score = new_score
|
||||
student.save()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.asyncio
|
||||
async def test_pick_student():
|
||||
# 创建一些学生
|
||||
await create_student("1", "Student 1", 10)
|
||||
await create_student("2", "Student 2", 0)
|
||||
await create_student("3", "Student 3", -5)
|
||||
|
||||
consumer = ClassroomConsumer()
|
||||
await consumer.connect()
|
||||
|
||||
chosen_student = await consumer.pick_student()
|
||||
|
||||
assert chosen_student is not None
|
||||
assert chosen_student.sid in ["1", "2", "3"]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.asyncio
|
||||
async def test_exemption_card_used():
|
||||
# 创建一个学生
|
||||
student = await create_student("1", "Student 1", 10)
|
||||
|
||||
consumer = ClassroomConsumer()
|
||||
await consumer.connect()
|
||||
|
||||
# 模拟接收豁免卡使用信息
|
||||
await consumer.receive(text_data=json.dumps({
|
||||
'action': 'use_exemption_card',
|
||||
'studentId': student.sid,
|
||||
'newScore': 20,
|
||||
'studentName': student.name,
|
||||
}))
|
||||
|
||||
updated_student = await get_student(student.sid)
|
||||
assert updated_student.score == 20
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_student_message():
|
||||
student = await create_student("2", "Student 2", 0)
|
||||
|
||||
consumer = ClassroomConsumer()
|
||||
await consumer.connect()
|
||||
|
||||
# 发送学生消息
|
||||
await consumer.send_student_message(student)
|
||||
|
||||
# 验证消息发送到学生组
|
||||
channel_layer = get_channel_layer()
|
||||
message = await channel_layer.receive(consumer.student_group)
|
||||
assert message['type'] == 'student_message'
|
||||
assert message['message']['name'] == student.name
|
||||
assert message['message']['student_id'] == student.sid
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_student_score():
|
||||
student = await create_student("3", "Student 3", -5)
|
||||
|
||||
consumer = ClassroomConsumer()
|
||||
await consumer.connect()
|
||||
|
||||
await update_student_score(student.sid, 30)
|
||||
|
||||
updated_student = await get_student(student.sid)
|
||||
assert updated_student.score == 30
|
@ -0,0 +1,90 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
from api.models import Students
|
||||
from api.models import Pledge
|
||||
from decimal import Decimal
|
||||
|
||||
class StudentsAPITests(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
# 创建一些测试数据
|
||||
self.student = Students.objects.create(sid='123', name='Student1', score=Decimal('0.0'))
|
||||
self.student = Students.objects.create(sid='WW1', name='小明', score=Decimal('0.0'))
|
||||
self.student = Students.objects.create(sid='@1wW23rbrsrf3', name='123HI', score=Decimal('0.0'))
|
||||
|
||||
def test_update_student_score(self):
|
||||
# 测试更新学生积分的功能
|
||||
response = self.client.post(reverse('update_student_score'), {'student_id': self.student.sid, 'score': 5.5})
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.student.refresh_from_db() # 刷新数据库数据
|
||||
self.assertEqual(self.student.score, Decimal('5.5'))
|
||||
|
||||
|
||||
|
||||
def test_create_pledge_success(self):
|
||||
# 测试成功创建典当信息
|
||||
data = {
|
||||
'studentId': self.student.sid,
|
||||
'studentName': self.student.name,
|
||||
'pledgeScore': 10,
|
||||
}
|
||||
response = self.client.post(reverse('create_pledge'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('典当信息已保存', response.data.values())
|
||||
self.assertTrue(Pledge.objects.filter(student_id=self.student.sid).exists())
|
||||
|
||||
|
||||
def test_create_pledge_missing_fields(self):
|
||||
# 测试创建典当时缺少字段
|
||||
data = {
|
||||
'studentId': self.student.sid,
|
||||
'studentName': '',
|
||||
'pledgeScore': 10,
|
||||
}
|
||||
response = self.client.post(reverse('create_pledge'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('所有字段都是必需的', response.data['error'])
|
||||
|
||||
|
||||
|
||||
def test_update_question_score_success_without_pledge(self):
|
||||
# 测试更新积分时没有典当信息
|
||||
data = {
|
||||
'studentId': self.student.sid,
|
||||
'questionScore': 2,
|
||||
}
|
||||
response = self.client.post(reverse('update_question_score'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.student.refresh_from_db()
|
||||
self.assertEqual(self.student.score, Decimal('2.0')) # 0.0 + 2
|
||||
|
||||
|
||||
def test_update_question_score_student_not_found(self):
|
||||
# 测试更新积分时学生不存在
|
||||
data = {
|
||||
'studentId': '999', # 一个不存在的学生ID
|
||||
'questionScore': 5,
|
||||
}
|
||||
response = self.client.post(reverse('update_question_score'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
self.assertEqual(response.data['error'], '学生不存在')
|
||||
|
||||
|
||||
def test_update_question_score_exception_handling(self):
|
||||
# 测试更新积分时的异常处理
|
||||
pledge = Pledge.objects.create(student_id=self.student.sid, student_name=self.student.name, pledge_score=5,
|
||||
status=0)
|
||||
|
||||
# 模拟异常(例如分数为字符串)
|
||||
data = {
|
||||
'studentId': self.student.sid,
|
||||
'questionScore': 'erewc3ergag',
|
||||
}
|
||||
response = self.client.post(reverse('update_question_score'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ChatConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'chat'
|
@ -0,0 +1,13 @@
|
||||
from channels.generic.websocket import WebsocketConsumer
|
||||
from channels.exceptions import StopConsumer
|
||||
|
||||
class ChatConsumer(WebsocketConsumer):
|
||||
|
||||
def websocket_connect(self, message):
|
||||
self.accept()
|
||||
|
||||
def websocket_receive(self, message):
|
||||
self.send(text_data='OK') # 返回给客户端的消息
|
||||
|
||||
def websocket_disconnect(self, message):
|
||||
raise StopConsumer()
|
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
@ -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', 'Rollcall_applet.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()
|
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = Rollcall_applet.settings
|
Binary file not shown.
Loading…
Reference in new issue