parent
716d008b6a
commit
0f252937b5
@ -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>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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,25 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-02 12:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
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='积分')),
|
||||
('signin', models.IntegerField(default=0, verbose_name='签到次数')),
|
||||
('absences', models.IntegerField(default=0, verbose_name='缺席次数')),
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-04 03:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Pledge',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('student_id', models.CharField(max_length=20)),
|
||||
('student_name', models.CharField(max_length=50)),
|
||||
('pledge_score', models.IntegerField()),
|
||||
('status', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-04 03:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0002_pledge'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='pledge',
|
||||
name='student_id',
|
||||
field=models.CharField(max_length=10),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='pledge',
|
||||
name='student_name',
|
||||
field=models.CharField(max_length=10),
|
||||
),
|
||||
]
|
@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-04 06:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0003_auto_20241004_1103'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='pledge',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -0,0 +1,21 @@
|
||||
# Generated by Django 3.2.25 on 2024-10-04 07:31
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0004_pledge_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='students',
|
||||
name='absences',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='students',
|
||||
name='signin',
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from .consumers import ClassroomConsumer
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/classroom/', ClassroomConsumer.as_asgi()),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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,92 @@
|
||||
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'))
|
||||
|
||||
|
||||
|
||||
|
||||
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': 'invalid_score',
|
||||
}
|
||||
response = self.client.post(reverse('update_question_score'), data)
|
||||
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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()
|
Binary file not shown.
@ -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.
|
Binary file not shown.
@ -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
|
@ -0,0 +1,91 @@
|
||||
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 .consumers import ClassroomConsumer
|
||||
from .models import Students
|
||||
|
||||
# 设置 Django 设置模块
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rollcall_applet.settings')
|
||||
django.setup()
|
||||
|
||||
@database_sync_to_async
|
||||
def create_student(sid, name, score):
|
||||
return Students.objects.create(sid=sid, name=name, score=score)
|
||||
|
||||
@database_sync_to_async
|
||||
def get_student(sid):
|
||||
return Students.objects.get(sid=sid)
|
||||
|
||||
@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.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.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.asyncio
|
||||
async def test_send_student_message():
|
||||
student = await create_student("1", "Student 1", 10)
|
||||
|
||||
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.asyncio
|
||||
async def test_update_student_score():
|
||||
student = await create_student("1", "Student 1", 10)
|
||||
|
||||
consumer = ClassroomConsumer()
|
||||
await consumer.connect()
|
||||
|
||||
await update_student_score(student.sid, 30)
|
||||
|
||||
updated_student = await get_student(student.sid)
|
||||
assert updated_student.score == 30
|
Binary file not shown.
Loading…
Reference in new issue