Skip to content
1 change: 1 addition & 0 deletions app/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def get_normal_fields(self, request, obj: NaturalPerson = None):
f(_m.identity), f(_m.status),
f(_m.wechat_receive_level),
f(_m.accept_promote), f(_m.active_score),
f(_m.course_priority)
])
return fields

Expand Down
27 changes: 12 additions & 15 deletions app/course_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

import openpyxl
import openpyxl.worksheet.worksheet
from random import sample
from numpy.random import choice
from urllib.parse import quote
from collections import Counter
from datetime import datetime, timedelta
Expand Down Expand Up @@ -608,16 +608,13 @@ def registration_status_change(course_id: int, user: NaturalPerson,
and course_status != Course.Status.STAGE2):
return wrong("在非选课阶段不能选课!")

need_to_create = False

if action == "select":
if CourseParticipant.objects.filter(course_id=course_id,
person=user).exists():
participant_info = CourseParticipant.objects.get(
course_id=course_id, person=user)
cur_status = participant_info.status
else:
need_to_create = True
cur_status = CourseParticipant.Status.UNSELECT

if course_status == Course.Status.STAGE1:
Expand Down Expand Up @@ -681,15 +678,11 @@ def registration_status_change(course_id: int, user: NaturalPerson,
course.current_participants += 1
course.save()

# 由于不同用户之间的状态不共享,这个更新应该可以不加锁
if need_to_create:
CourseParticipant.objects.create(course_id=course_id,
person=user,
status=to_status)
else:
CourseParticipant.objects.filter(
course_id=course_id,
person=user).update(status=to_status)
CourseParticipant.objects.update_or_create(
course_id = course_id,
person = user,
defaults = {"status": to_status}
)
succeed("选课成功!", context)
except:
return context
Expand Down Expand Up @@ -816,7 +809,11 @@ def draw_lots():
if participants_num <= 0:
continue

participants_id = list(participants.values_list("id", flat=True))
participants_info = participants.values_list("id", "person__course_priority")
participants_id, priority = map(list, zip(*participants_info))
# Turn priority into a probability distribution
sum_priority = sum(priority)
priority = [i / sum_priority for i in priority]
capacity = course.capacity

if participants_num <= capacity:
Expand All @@ -828,7 +825,7 @@ def draw_lots():
current_participants=participants_num)
else:
# 抽签;可能实现得有一些麻烦
lucky_ones = sample(participants_id, capacity)
lucky_ones = choice(participants_id, capacity, replace=False, p=priority)
unlucky_ones = list(
set(participants_id).difference(set(lucky_ones)))
# 不确定是否要加悲观锁
Expand Down
53 changes: 53 additions & 0 deletions app/management/commands/update_course_priority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.core.management.base import BaseCommand
from django.db import transaction

from app.models import NaturalPerson, CourseRecord

from typing import Dict, List
from tqdm import trange

class Command(BaseCommand):
# This command should be run before new semester starts
help = "Updates course_priority for all NaturalPerson"

def add_arguments(self, parser):
parser.add_argument('year', type=int, help='year of the course records to check (required)')
parser.add_argument('semester', type=str, help='semester of the course records to check. Valid values are: Fall, Spring (required)')

@transaction.atomic
def handle(self, *args, **options):
# Currently, just adjust all of them to 1.
NaturalPerson.objects.update(course_priority=1.0)
# Comment out this return to enable course priority
# return
if options['semester'] not in ('Fall', 'Spring'):
print('Error: <semester> must have value of "Fall" or "Spring"!')
return
# A list of invalid record's student id.
invalid_id: List[int] = list(CourseRecord.objects.filter(
year = options['year'], invalid = True, semester = options['semester']
).order_by(
'person__id'
).values_list(
'person__id', flat = True
))
# Number of invalid records
n = len(invalid_id)
print(f'update_course_priority: There are {n} invalid records.')
# a[k] is the list of id of people who have k invalid records
a: Dict[int, List[int]] = dict()
# Length of the current continuous segment
k = 0
for i in trange(n, desc = 'Processing invalid records'):
k += 1
if i == n - 1 or invalid_id[i] != invalid_id[i + 1]:
if k not in a:
a[k] = []
a[k].append(invalid_id[i])
k = 0
# Print out a summary of invalid records
for i in a:
print(f'There are {len(a[i])} people with {i} invalid records...')
p = max(0.5, 1 - 0.05 * i)
NaturalPerson.objects.filter(id__in = a[i]).update(course_priority = p)
print('Done!')
23 changes: 23 additions & 0 deletions app/migrations/0009_naturalperson_course_priority_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0.9 on 2025-01-19 15:23

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("app", "0008_pool_activity_alter_poolitem_exchange_attributes_and_more"),
]

operations = [
migrations.AddField(
model_name="naturalperson",
name="course_priority",
field=models.FloatField(default=1.0, verbose_name="书院课选课权重"),
),
migrations.AddConstraint(
model_name="courseparticipant",
constraint=models.UniqueConstraint(
fields=("course", "person"), name="Unique course selection record"
),
),
]
4 changes: 4 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ class ReceiveLevel(models.IntegerChoices):

accept_promote = models.BooleanField(default=True) # 是否接受推广消息
active_score = models.FloatField("活跃度", default=0) # 用户活跃度
course_priority = models.FloatField("书院课选课权重", default=1.0) # 书院课预选抽签权重

def __str__(self):
return str(self.name)
Expand Down Expand Up @@ -1580,6 +1581,9 @@ class CourseParticipant(models.Model):
class Meta:
verbose_name = "4.课程报名情况"
verbose_name_plural = verbose_name
constraints = [
models.UniqueConstraint(fields = ['course', 'person'], name='Unique course selection record')
]

course = models.ForeignKey(Course, on_delete=models.CASCADE,
related_name="participant_set")
Expand Down
Loading