From 456a97e17a9a1c6c910da1232f6068ab6c78d642 Mon Sep 17 00:00:00 2001 From: pkugenuine Date: Sat, 28 Dec 2024 06:51:34 +0000 Subject: [PATCH 1/5] Add `RoomClass` model --- Appointment/admin.py | 7 + Appointment/hardware_api.py | 7 +- ...m_image_room_quick_reservable_roomclass.py | 62 ++++ Appointment/models.py | 44 ++- Appointment/utils/utils.py | 4 - Appointment/views.py | 186 ++++------- templates/Appointment/booking-talk.html | 5 - templates/Appointment/booking.html | 85 +----- templates/Appointment/checkout.html | 2 +- templates/Appointment/index.html | 288 ++---------------- 10 files changed, 179 insertions(+), 511 deletions(-) create mode 100644 Appointment/migrations/0004_room_image_room_quick_reservable_roomclass.py diff --git a/Appointment/admin.py b/Appointment/admin.py index 708fdbfe5..f259652cf 100644 --- a/Appointment/admin.py +++ b/Appointment/admin.py @@ -109,6 +109,13 @@ def remove_hidden(self, request, queryset: QuerySet[Participant]): return self.message_user(request, '操作成功!') +@admin.register(RoomClass) +class RoomClassAdmin(admin.ModelAdmin): + list_display = ('id', 'name', 'sort_idx',) + ordering = ('sort_idx',) + # For many-to-many fields + filter_horizontal = ('rooms',) + @admin.register(Room) class RoomAdmin(admin.ModelAdmin): list_display = ('Rid', 'Rtitle', 'Rmin', 'Rmax', 'Rstart', 'Rfinish', diff --git a/Appointment/hardware_api.py b/Appointment/hardware_api.py index 514e2756e..1193b8fb9 100644 --- a/Appointment/hardware_api.py +++ b/Appointment/hardware_api.py @@ -8,10 +8,7 @@ from Appointment.models import Room, Appoint, Participant, CardCheckInfo from Appointment.extern.wechat import notify_user -from Appointment.utils.utils import ( - door2room, ip2room, - check_temp_appoint, -) +from Appointment.utils.utils import door2room, ip2room from Appointment.utils.log import logger import Appointment.utils.web_func as web_func from Appointment.utils.identity import get_participant @@ -266,7 +263,7 @@ def _temp_failed(message: str, record_temp=True): # 当前无预约 # 房间不可以临时预约 - if not check_temp_appoint(room): + if not room.quick_reservable: return _temp_failed(f"该房间不可临时预约", False) # 该房间可以用于临时预约 diff --git a/Appointment/migrations/0004_room_image_room_quick_reservable_roomclass.py b/Appointment/migrations/0004_room_image_room_quick_reservable_roomclass.py new file mode 100644 index 000000000..d63c711dd --- /dev/null +++ b/Appointment/migrations/0004_room_image_room_quick_reservable_roomclass.py @@ -0,0 +1,62 @@ +# Generated by Django 5.0.9 on 2024-12-27 16:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("Appointment", "0003_alter_appoint_options_alter_appoint_areason"), + ] + + operations = [ + migrations.AddField( + model_name="room", + name="image", + field=models.ImageField( + null=True, upload_to="room_images", verbose_name="房间图片" + ), + ), + migrations.AddField( + model_name="room", + name="quick_reservable", + field=models.BooleanField(default=False, verbose_name="支持临时预约"), + ), + migrations.CreateModel( + name="RoomClass", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("sort_idx", models.SmallIntegerField(verbose_name="主页排序")), + ( + "name", + models.CharField(max_length=32, unique=True, verbose_name="类别名称"), + ), + ( + "description", + models.CharField( + blank=True, default="", max_length=256, verbose_name="类别描述" + ), + ), + ( + "rooms", + models.ManyToManyField( + db_index=True, + related_name="classes", + to="Appointment.room", + verbose_name="房间列表", + ), + ), + ], + options={ + "verbose_name": "房间类别", + "verbose_name_plural": "房间类别", + }, + ), + ] diff --git a/Appointment/models.py b/Appointment/models.py index 5f04c30fe..4b0d19966 100644 --- a/Appointment/models.py +++ b/Appointment/models.py @@ -18,6 +18,7 @@ 'User', 'College_Announcement', 'Participant', + 'RoomClass', 'Room', 'Appoint', 'LongTermAppoint', @@ -105,15 +106,6 @@ def activated(self): '''只保留所有可用的房间''' return self.filter(Rstatus__in=[Room.Status.UNLIMITED, Room.Status.PERMITTED]) - def basement_only(self): - '''只保留所有地下室的房间''' - return self.exclude(Rid__icontains="R") - - def russian_only(self): - '''只保留所有俄文楼的房间''' - return self.filter(Rid__icontains="R") - - class RoomManager(models.Manager['Room']): def get_queryset(self) -> RoomQuerySet: return RoomQuerySet(self.model, using=self._db, hints=self._hints) @@ -127,27 +119,25 @@ def permitted(self): def unlimited(self): return self.get_queryset().unlimited() - def function_rooms(self): - '''获取所有可预约功能房''' - titles = ['航模', '绘画', '书法', '活动'] - title_query = ~Q(Rtitle__icontains="研讨") - title_query |= Q(Rtitle__icontains="/") - for room_title in titles: - title_query |= Q(Rtitle__icontains=room_title) - return self.get_queryset().permitted().basement_only().filter(title_query) + def interview_room_ids(self): + return set() - def talk_rooms(self): - '''获取所有研讨室''' - return self.get_queryset().permitted().filter(Rtitle__icontains="研讨") - def russian_rooms(self): - '''获取所有可预约俄文楼教室''' - return self.get_queryset().permitted().russian_only() +class RoomClass(models.Model): + class Meta: + verbose_name = '房间类别' + verbose_name_plural = verbose_name - def interview_room_ids(self): - '''获取所有可面试俄文楼教室''' - return set() + sort_idx = models.SmallIntegerField('主页排序') + name = models.CharField('类别名称', max_length=32, unique=True) + description = models.CharField( + '类别描述', max_length=256, blank=True, default='') + rooms = models.ManyToManyField( + 'Room', verbose_name='房间列表', db_index=True, related_name='classes') + # Used for room's many-to-many field + def __str__(self): + return self.name class Room(models.Model): class Meta: @@ -165,12 +155,14 @@ class Meta: Rfinish = models.TimeField('最迟预约时间') Rlatest_time = models.DateTimeField("摄像头心跳", auto_now_add=True) Rpresent = models.IntegerField('目前人数', default=0) + image = models.ImageField('房间图片', upload_to='room_images', null=True) # Rstatus 标记当前房间是否允许预约,可由管理员修改 class Status(models.IntegerChoices): PERMITTED = 0, '允许预约' # 允许预约 UNLIMITED = 1, '无需预约' # 允许使用 FORBIDDEN = 2, '禁止使用' # 禁止使用 + quick_reservable = models.BooleanField('支持临时预约', default=False) Rstatus = models.SmallIntegerField('房间状态', choices=Status.choices, default=0) diff --git a/Appointment/utils/utils.py b/Appointment/utils/utils.py index 705f80847..8ce29c2cd 100644 --- a/Appointment/utils/utils.py +++ b/Appointment/utils/utils.py @@ -100,10 +100,6 @@ def door2room(door): return door_room_dict[door] -def check_temp_appoint(room: Room) -> bool: - return '研讨' in room.Rtitle - - def get_conflict_appoints(appoint: Appoint, times: int = 1, interval: int = 1, week_offset: int = 0, exclude_this: bool = False, diff --git a/Appointment/views.py b/Appointment/views.py index 9f319cc4f..7f79324f5 100644 --- a/Appointment/views.py +++ b/Appointment/views.py @@ -13,14 +13,7 @@ from utils.global_messages import wrong, succeed, message_url import utils.global_messages as my_messages from generic.utils import to_search_indices -from Appointment.models import ( - User, - Participant, - Room, - Appoint, - College_Announcement, - LongTermAppoint, -) +from Appointment.models import * from Appointment.extern.wechat import MessageType, notify_appoint from Appointment.utils.utils import ( get_conflict_appoints, to_feedback_url, @@ -310,68 +303,51 @@ def format_time(delta): # 格式化timedelta,隐去0h hour, rem = divmod(delta.seconds, 3600) return f"{rem // 60}min" if hour == 0 else f"{hour}h{rem // 60}min" - # --------- 1,2 地下室状态部分 ---------# - function_room_list = Room.objects.function_rooms().order_by('Rid') - # --------- 地下室状态:left tab ---------# unlimited_rooms = room_list.unlimited().order_by( '-Rtitle') # 开放房间 statistics_info = [(room, (room.Rpresent * 10) // (room.Rmax or 1)) - for room in unlimited_rooms] # 开放房间人数统计 - - # --------- 地下室状态:right tab ---------# - talk_room_list = Room.objects.talk_rooms().order_by('Rid') - room_info = [(room, - room.Rid in occupied_rooms, + for room in unlimited_rooms] # 开放房间人数统计 + # TODO: optimization + reservable_room_classes = [ + rclass for rclass in RoomClass.objects.order_by('sort_idx') + if rclass.rooms.permitted().exists() + ] + quick_reservable_rooms = Room.objects.permitted().filter(quick_reservable=True) + room_info = [(room, room.Rid in occupied_rooms, format_time(room_appointments[room.Rid])) - for room in talk_room_list] # 研讨室占用情况 - - # --------- 3 俄文楼部分 ---------# - - russian_room_list = Room.objects.russian_rooms().order_by('Rid') # 俄文楼 - russ_len = len(russian_room_list) + for room in quick_reservable_rooms] render_context.update( - function_room_list=function_room_list, + room_info=room_info, statistics_info=statistics_info, - talk_room_list=talk_room_list, room_info=room_info, - russian_room_list=russian_room_list, russ_len=russ_len, + reservable_room_classes=reservable_room_classes, ) if request.method == "POST": - # YHT: added for Russian search request_time = request.POST.get("request_time", None) - russ_request_time = request.POST.get("russ_request_time", None) - check_type = "" - if request_time is None and russ_request_time is not None: - check_type = "russ" - request_time = russ_request_time - elif request_time is not None and russ_request_time is None: - check_type = "talk" - else: + room_class = request.POST.get("room_class", None) + if not request_time or not room_class: return render(request, 'Appointment/index.html', render_context) - - if request_time != None and request_time != "": # 初始加载或者不选时间直接搜索则直接返回index页面,否则进行如下反查时间 - day, month, year = int(request_time[:2]), int( - request_time[3:5]), int(request_time[6:10]) - re_time = datetime(year, month, day) # 获取目前request时间的datetime结构 - if re_time.date() < datetime.now().date(): # 如果搜过去时间 - render_context.update(search_code=1, - search_message="请不要搜索已经过去的时间!") - return render(request, 'Appointment/index.html', render_context) - elif re_time.date() - datetime.now().date() > timedelta(days=6): - # 查看了7天之后的 - render_context.update(search_code=1, - search_message="只能查看最近7天的情况!") - return render(request, 'Appointment/index.html', render_context) - # 到这里 搜索没问题 进行跳转 - urls = my_messages.append_query( - reverse("Appointment:arrange_talk"), - year=year, month=month, day=day, type=check_type) - # YHT: added for Russian search - return redirect(urls) - + # TODO: Fix this + day, month, year = int(request_time[:2]), int( + request_time[3:5]), int(request_time[6:10]) + re_time = datetime(year, month, day) # 获取目前request时间的datetime结构 + if re_time.date() < datetime.now().date(): # 如果搜过去时间 + render_context.update( + search_code=1, search_message="请不要搜索已经过去的时间!") + return render(request, 'Appointment/index.html', render_context) + elif re_time.date() - datetime.now().date() > timedelta(days=6): + # 查看了7天之后的 + render_context.update(search_code=1, + search_message="只能查看最近7天的情况!") + return render(request, 'Appointment/index.html', render_context) + # 到这里 搜索没问题 进行跳转 + urls = my_messages.append_query( + reverse("Appointment:arrange_talk"), + year=year, month=month, day=day, type=room_class) + return redirect(urls) return render(request, 'Appointment/index.html', render_context) @@ -409,44 +385,14 @@ def arrange_time(request: HttpRequest): 选择预约时间 """ - # 只接受GET方法,不接受POST方法 - if request.method == 'POST': - return redirect(reverse('Appointment:index')) - + room = Room.objects.permitted().get(Rid=request.GET.get('Rid')) # 判断当前用户是否可以进行长期预约 has_longterm_permission = get_participant(request.user).longterm - - # 获取房间编号 - Rid = request.GET.get('Rid') - try: - room: Room = Room.objects.get(Rid=Rid) - room_object = room # 用于前端使用 - except: - return redirect( - message_url(wrong(f"房间号{Rid}不存在!"), - reverse("Appointment:account"))) - - if room.Rstatus == Room.Status.FORBIDDEN: - return render(request, 'Appointment/booking.html', locals()) - - # start_week=0代表查看本周,start_week=1代表查看下周 - start_week = request.GET.get('start_week') - if start_week is None: - is_longterm = False - start_week = 0 - else: - is_longterm = True - try: - start_week = int(start_week) - # 参数检查 - assert start_week == 0 or start_week == 1 - assert has_longterm_permission or not is_longterm - except: - return redirect(reverse('Appointment:index')) - + is_longterm = (request.GET.get( + 'longterm') == 'on') and has_longterm_permission + next_week = (request.GET.get('start_week') == '1') and is_longterm dayrange_list, start_day, end_next_day = web_func.get_dayrange( - day_offset=start_week * 7) - + day_offset=7 if next_week else 0) # 获取预约时间的最大时间块id max_stamp_id = web_func.get_time_id(room, room.Rfinish, mode="leftopen") @@ -476,14 +422,14 @@ class TimeStatus: # 筛选已经存在的预约 appoints: QuerySet[Appoint] = Appoint.objects.not_canceled().filter( - Room_id=Rid, Afinish__gte=start_day, Astart__date__lt=end_next_day) + Room_id=room.Rid, Afinish__gte=start_day, Astart__date__lt=end_next_day) start_day = dayrange_list[0] start_day = date(start_day['year'], start_day['month'], start_day['day']) # 给出已有预约的信息 # TODO: 后续可优化 for appoint in appoints: - change_id_list = web_func.timerange2idlist(Rid, appoint.Astart, + change_id_list = web_func.timerange2idlist(room.Rid, appoint.Astart, appoint.Afinish, max_stamp_id) appoint_usage = html.escape(appoint.Ausage).replace('\n', '
') @@ -528,51 +474,39 @@ class TimeStatus: day['timesection'][i]['display_info'] = display_info # 删去今天已经过去的时间 - if start_week == 0: + if not next_week: curr_stamp_id = web_func.get_time_id(room, datetime.now().time()) for i in range(min(max_stamp_id, curr_stamp_id) + 1): dayrange_list[0]['timesection'][i]['status'] = TimeStatus.PASSED - # 转换成方便前端使用的形式 - js_dayrange_list = json.dumps(dayrange_list) - - # 获取房间信息,以支持房间切换的功能 - function_room_list = Room.objects.function_rooms().order_by('Rid') - talk_room_list = Room.objects.talk_rooms().order_by('Rid') - - return render(request, 'Appointment/booking.html', locals()) + render_context = dict( + room_object=room, + is_longterm=is_longterm, + has_longterm_permission=has_longterm_permission, + start_week=1 if next_week else 0, + dayrange_list=dayrange_list, + # 转换成方便前端使用的形式 + js_dayrange_list=json.dumps(dayrange_list), + TimeStatus=TimeStatus, + ) + return render(request, 'Appointment/booking.html', render_context) @identity_check(redirect_field_name='origin') def arrange_talk_room(request): - try: - assert request.method == "GET" - year = int(request.GET.get("year")) - month = int(request.GET.get("month")) - day = int(request.GET.get("day")) - # YHT: added for russian search - check_type = str(request.GET.get("type")) - assert check_type in {"russ", "talk"} - re_time = datetime(year, month, day) # 如果有bug 直接跳转 - if (re_time.date() < datetime.now().date() - or re_time.date() - datetime.now().date() > timedelta(days=6)): - # 这种就是乱改url - return redirect(reverse("Appointment:index")) - # 接下来判断时间 - except: + year = int(request.GET.get("year")) + month = int(request.GET.get("month")) + day = int(request.GET.get("day")) + room_class = str(request.GET.get("type")) + re_time = datetime(year, month, day) # 如果有bug 直接跳转 + if (re_time.date() < datetime.now().date() + or re_time.date() - datetime.now().date() > timedelta(days=6)): return redirect(reverse("Appointment:index")) - is_today = False - if check_type == "talk": - if re_time.date() == datetime.now().date(): - is_today = True - show_min = CONFIG.today_min - room_list = Room.objects.talk_rooms().basement_only().order_by('Rmin', 'Rid') - else: # type == "russ" - room_list = Room.objects.russian_rooms().order_by('Rid') - # YHT: added for russian search + room_list = RoomClass.objects.get(name=room_class).rooms.permitted() Rids = [room.Rid for room in room_list] + # TODO: Fix `get_talkroom_timerange` t_start, t_finish = web_func.get_talkroom_timerange( room_list) # 对所有讨论室都有一个统一的时间id标准 t_start = web_func.time2datetime(year, month, day, t_start) # 转换成datetime类 diff --git a/templates/Appointment/booking-talk.html b/templates/Appointment/booking-talk.html index 800535b12..8e564846b 100644 --- a/templates/Appointment/booking-talk.html +++ b/templates/Appointment/booking-talk.html @@ -64,11 +64,6 @@
-
- - User Image -

{{month}}月{{day}}日

diff --git a/templates/Appointment/booking.html b/templates/Appointment/booking.html index b0f64ad68..31cf57eb2 100644 --- a/templates/Appointment/booking.html +++ b/templates/Appointment/booking.html @@ -65,9 +65,9 @@