Appointment.admin 源代码

import string
import pypinyin
from datetime import datetime, timedelta

from django.contrib import admin, messages
from django.utils.safestring import mark_safe
from django.utils.html import format_html, format_html_join
from django.db.models import QuerySet

from utils.admin_utils import *
from Appointment import jobs
from Appointment.appoint.jobs import set_scheduler, cancel_scheduler
from Appointment.extern.wechat import MessageType, notify_appoint
from Appointment.extern.jobs import set_appoint_reminder
from Appointment.utils.log import logger
from Appointment.models import *


def _appointor(appoint: Appoint) -> str:
    '''可追溯引用的str调用'''
    return appoint.major_student.__str__()


[文档] @admin.register(College_Announcement) class College_AnnouncementAdmin(admin.ModelAdmin): list_display = ['id', 'announcement', 'show'] list_editable = ['announcement', 'show']
[文档] @admin.register(Participant) class ParticipantAdmin(admin.ModelAdmin): actions_on_top = True actions_on_bottom = True search_fields = ('Sid__username', 'Sid__name', 'Sid__pinyin', 'Sid__acronym') list_display = ('Sid_id', 'name', 'credit', 'longterm', 'hidden') list_display_links = ('Sid_id', 'name')
[文档] class AgreeFilter(admin.SimpleListFilter): title = '签署状态' parameter_name = 'Agree'
[文档] def lookups(self, request, model_admin): '''针对字段值设置过滤器的显示效果''' return ( ('true', "已签署"), ('false', "未签署"), )
[文档] def queryset(self, request, queryset): '''定义过滤器的过滤动作''' if self.value() == 'true': return queryset.exclude(agree_time__isnull=True) elif self.value() == 'false': return queryset.filter(agree_time__isnull=True) return queryset
list_filter = ('Sid__credit', 'longterm', 'hidden', AgreeFilter)
[文档] @readonly_inline class AppointInline(admin.TabularInline): # 对外呈现部分 model = Appoint verbose_name = '近两周预约信息' verbose_name_plural = verbose_name classes = ['collapse'] # 对内呈现部分(max_num和get_queryset均无法限制呈现个数) ordering = ['-Aid'] fields = [ 'Room', 'Astart', 'Afinish', 'Astatus', 'Acamera_check_num', 'Acamera_ok_num', ] show_change_link = True # 可申诉的范围只有一周,筛选两周内范围的即可
[文档] def get_queryset(self, request): return super().get_queryset(request).filter( Astart__gte=datetime.now().date() - timedelta(days=14))
inlines = [AppointInline] actions = []
[文档] @as_action('全院学生信用分恢复一分', actions, atomic=True) def recover(self, request, queryset): stu_all = Participant.objects.all() stu_all = stu_all.filter(hidden=False) stu_all = User.objects.filter(id__in=stu_all.values_list('Sid__id')) User.objects.bulk_recover_credit(stu_all, 1, '地下室:全体学生恢复') return self.message_user(request, '操作成功!')
[文档] @as_action('赋予长期预约权限', actions, 'change', update=True) def add_longterm_perm(self, request, queryset: QuerySet[Participant]): queryset.update(longterm=True) return self.message_user(request, '操作成功!')
[文档] @as_action('收回长期预约权限', actions, 'change', update=True) def remove_longterm_perm(self, request, queryset: QuerySet[Participant]): queryset.update(longterm=False) return self.message_user(request, '操作成功!')
[文档] @as_action('设为不可见', actions, 'change', update=True) def set_hidden(self, request, queryset: QuerySet[Participant]): queryset.update(hidden=True) return self.message_user(request, '操作成功!')
[文档] @as_action('设为可见', actions, 'change', update=True) def remove_hidden(self, request, queryset: QuerySet[Participant]): queryset.update(hidden=False) return self.message_user(request, '操作成功!')
[文档] @admin.register(Room) class RoomAdmin(admin.ModelAdmin): list_display = ('Rid', 'Rtitle', 'Rmin', 'Rmax', 'Rstart', 'Rfinish', 'Rstatus_display', 'RIsAllNight', 'Rpresent', 'Rlatest_time', 'RneedAgree', ) list_display_links = ('Rid', ) list_editable = ('Rtitle', 'Rmin', 'Rmax', 'Rstart', 'Rfinish', 'RneedAgree') search_fields = ('Rid', 'Rtitle') list_filter = ('Rstatus', 'RIsAllNight', 'RneedAgree')
[文档] @as_display('预约状态') def Rstatus_display(self, obj): if obj.Rstatus == Room.Status.PERMITTED: color_code = 'green' else: color_code = 'red' return format_html( '<span style="color: {};">{}</span>', color_code, obj.get_Rstatus_display(), )
[文档] @admin.register(Appoint) class AppointAdmin(admin.ModelAdmin): actions_on_top = True actions_on_bottom = True LETTERS = set(string.digits + string.ascii_letters + string.punctuation) search_fields = ('Room__Rtitle', 'Room__Rid', 'major_student__name', "students__name", 'major_student__pinyin', # 仅发起者缩写,方便搜索者区分发起者和参与者 ) list_display = ( 'Aid', 'Room', 'Astart', # 'Afinish', # 'Atime', # 'Ausage', 'major_student_display', 'Participants', # 'total_display', 'usage_display', 'check_display', 'Astatus_display', 'Atype', ) list_select_related = ('Room', 'major_student__Sid') list_display_links = ('Aid', 'Room') list_per_page = 25 list_editable = ( 'Astart', # 'Afinish', ) # 'Ausage' date_hierarchy = 'Astart' autocomplete_fields = ['major_student'] filter_horizontal = ['students'] readonly_fields = ('Atime', )
[文档] class ActivateFilter(admin.SimpleListFilter): title = '有效状态' # 过滤标题显示为"以 有效状态" parameter_name = 'Activate' # 过滤器使用的过滤字段
[文档] def lookups(self, request, model_admin): '''针对字段值设置过滤器的显示效果''' return ( ('true', "有效"), ('false', "无效"), )
[文档] def queryset(self, request, queryset): '''定义过滤器的过滤动作''' if self.value() == 'true': return queryset.exclude(Astatus=Appoint.Status.CANCELED) elif self.value() == 'false': return queryset.filter(Astatus=Appoint.Status.CANCELED) return queryset
list_filter = ('Astart', 'Atime', 'Astatus', ActivateFilter, 'Atype')
[文档] def get_search_results(self, request, queryset, search_term: str): if not search_term: return queryset, False try: search_term = int(search_term) return queryset.filter(pk=search_term), False except: pass if ' ' not in search_term: # 判断时需要增加exists,否则会报错,似乎是QuerySet的缓存问题? if str.isascii(search_term) and str.isalpha(search_term): pinyin_result = queryset.filter(major_student__pinyin__icontains=search_term) if pinyin_result.exists(): return pinyin_result, False elif str.isascii(search_term) and str.isalnum(search_term): room_result = queryset.filter(Room__Rid__iexact=search_term) if room_result.exists(): return room_result, False else: room_result = queryset.filter(Room__Rtitle__icontains=search_term) if room_result.exists(): return room_result, False return super().get_search_results(request, queryset, search_term)
[文档] @as_display('参与人') def Participants(self, obj: Appoint): names = [(obj.major_student.name, )] participants = obj.students.exclude(pk=obj.get_major_id()) names += list(participants.values_list('Sid__name', flat=False)) return format_html_join('\n', '<li>{}</li>', names)
[文档] @as_display('用途') def usage_display(self, obj): batch = 6 half_len = 18 usage = obj.Ausage if len([c for c in usage if c in AppointAdmin.LETTERS]) > .6 * len(usage): batch *= 2 half_len *= 2 if len(obj.Ausage) < half_len * 2: usage = obj.Ausage else: usage = obj.Ausage[:half_len] + '...' + obj.Ausage[3-half_len:] usage = '<br/>'.join([usage[i:i+batch] for i in range(0, len(usage), batch)]) return mark_safe(usage)
[文档] @as_display('通过率') def check_display(self, obj): return f'{obj.Acamera_ok_num}/{obj.Acamera_check_num}'
[文档] @as_display('总人数') def total_display(self, obj): return obj.Anon_yp_num + obj.Ayp_num
[文档] @as_display('发起人') def major_student_display(self, obj): return obj.major_student.name
[文档] @as_display('预约状态') def Astatus_display(self, obj): status2color = { Appoint.Status.CANCELED: 'grey', Appoint.Status.APPOINTED: 'black', Appoint.Status.PROCESSING: 'purple', Appoint.Status.WAITING: 'blue', Appoint.Status.CONFIRMED: 'green', Appoint.Status.VIOLATED: 'red', Appoint.Status.JUDGED: 'yellowgreen', } color_code = status2color[obj.Astatus] status = obj.get_status() return format_html( '<span style="color: {};">{}</span>', color_code, status, )
actions = [] def _waiting2confirm(self, appoint: Appoint): appoint.Astatus = Appoint.Status.CONFIRMED appoint.save() notify_appoint(appoint, MessageType.PRE_CONFIRMED, appoint.get_status(), students_id=[appoint.get_major_id()], admin=True) logger.info(f"{appoint.Aid}号预约被管理员通过,发起人:{_appointor(appoint)}") def _violated2judged(self, appoint: Appoint): appoint.Astatus = Appoint.Status.JUDGED appoint.save() User.objects.modify_credit(appoint.get_major_id(), 1, '地下室:申诉') notify_appoint(appoint, MessageType.APPEAL_APPROVED, appoint.get_status(), students_id=[appoint.get_major_id()], admin=True) logger.info(f"{appoint.Aid}号预约被管理员通过,发起人:{_appointor(appoint)}")
[文档] @as_action('所选条目 通过', actions, 'change', update=True) def confirm(self, request, queryset: QuerySet[Appoint]): # 确认通过 invalid = [] for appoint in queryset: match appoint.Astatus: case Appoint.Status.WAITING: self._waiting2confirm(appoint) case Appoint.Status.VIOLATED: self._violated2judged(appoint) case _: invalid.append(appoint) if not invalid: return self.message_user(request, '更改状态成功!') if len(invalid) == len(queryset): return self.message_user(request, '只可通过等待、违约中的预约!', messages.WARNING) message = f'部分成功!但{invalid}状态不为等待、违约,不允许更改!' return self.message_user(request, message, messages.WARNING)
[文档] @as_action('所选条目 违约', actions, 'change', update=True) def violate(self, request, queryset: QuerySet[Appoint]): # 确认违约 for appoint in queryset: if (appoint.Astatus == Appoint.Status.VIOLATED and appoint.Areason == Appoint.Reason.R_ELSE): return self.message_user( request, '操作失败!只允许对未审核的条目操作!', messages.WARNING) ori_status = appoint.get_status() if appoint.Astatus != Appoint.Status.VIOLATED: appoint.Astatus = Appoint.Status.VIOLATED User.objects.modify_credit(appoint.get_major_id(), -1, '地下室:后台') appoint.Areason = Appoint.Reason.R_ELSE appoint.save() # send wechat message notify_appoint( appoint, MessageType.REVIEWD_VIOLATE, f'原状态:{ori_status}', students_id=[appoint.get_major_id()], admin=True) logger.info(f"{appoint.Aid}号预约被管理员设为违约,发起人:{_appointor(appoint)}") return self.message_user(request, "设为违约成功!")
[文档] @as_action('更新定时任务', actions, ['add', 'change']) def refresh_scheduler(self, request, queryset): ''' 假设的情况是后台修改了开始和结束时间后,需要重置定时任务 因此,旧的定时任务可能处于任何完成状态 ''' for appoint in queryset: try: aid = appoint.Aid start = appoint.Astart finish = appoint.Afinish if start > finish: return self.message_user(request, f'操作失败,预约{aid}开始和结束时间冲突!请勿篡改数据!', messages.WARNING) cancel_scheduler(aid) # 注销原有定时任务 无异常 set_scheduler(appoint) # 开始时进入进行中 结束后判定 set_appoint_reminder(appoint) except Exception as e: logger.error(f"定时任务失败更新: {e}") return self.message_user(request, str(e), messages.WARNING) return self.message_user(request, '定时任务更新成功!')
[文档] def longterm_wk(self, request, queryset, times, interval_week=1): new_appoints = {} for appoint in queryset: try: conflict_week, appoints = ( jobs.add_longterm_appoint( appoint.pk, times, interval_week, admin=True)) if conflict_week is not None: return self.message_user( request, f'第{conflict_week}周存在冲突的预约: {appoints[0].Aid}!', level=messages.WARNING) longterm_info = jobs.get_longterm_display(times, interval_week) notify_appoint(appoint, MessageType.LONGTERM_CREATED, f'新增了{longterm_info}同时段预约', admin=True) new_appoints[appoint.pk] = list(appoints.values_list('pk', flat=True)) except Exception as e: return self.message_user(request, f'长线化失败!', messages.WARNING) new_infos = [] if len(new_appoints) == 1: for appoint, new_appoint_ids in new_appoints.items(): new_infos.append(f'{new_appoint_ids}'[1:-1]) else: for appoint, new_appoint_ids in new_appoints.items(): new_infos.append(f'{appoint}->{new_appoint_ids}') return self.message_user(request, f'长线化成功!生成预约{";".join(new_appoints)}')
# @as_action('增加一周本预约', actions, 'add', single=True)
[文档] def longterm1(self, request, queryset): return self.longterm_wk(request, queryset, 1)
# @as_action('增加两周本预约', actions, 'add', single=True)
[文档] def longterm2(self, request, queryset): return self.longterm_wk(request, queryset, 2)
# @as_action('增加四周本预约', actions, 'add', single=True)
[文档] def longterm4(self, request, queryset): return self.longterm_wk(request, queryset, 4)
# @as_action('增加八周本预约', actions, 'add', single=True)
[文档] def longterm8(self, request, queryset): return self.longterm_wk(request, queryset, 8)
# @as_action('按单双周 增加一次本预约', actions, 'add', single=True)
[文档] def longterm1_2(self, request, queryset): return self.longterm_wk(request, queryset, 1, 2)
# @as_action('按单双周 增加两次本预约', actions, 'add', single=True)
[文档] def longterm2_2(self, request, queryset): return self.longterm_wk(request, queryset, 2, 2)
# @as_action('按单双周 增加四次本预约', actions, 'add', single=True)
[文档] def longterm4_2(self, request, queryset): return self.longterm_wk(request, queryset, 4, 2)
[文档] @admin.register(CardCheckInfo) class CardCheckInfoAdmin(admin.ModelAdmin): list_display = ('id', 'Cardroom', 'student_display', 'Cardtime', 'CardStatus', 'Message') list_select_related = ('Cardroom', 'Cardstudent__Sid') search_fields = ('Cardroom__Rtitle', 'Cardstudent__name', 'Cardroom__Rid', "id") list_filter = [ 'Cardtime', 'CardStatus', ('Cardroom', admin.EmptyFieldListFilter), ]
[文档] @as_display('刷卡者', except_value='-') def student_display(self, obj): return obj.Cardstudent.name
[文档] @admin.register(LongTermAppoint) class LongTermAppointAdmin(admin.ModelAdmin): list_display = ['id', 'applicant', 'times', 'interval', 'status'] list_select_related = ['applicant__Sid'] list_filter = ['status', 'times', 'interval'] raw_id_fields = ['appoint']
[文档] def view_on_site(self, obj: LongTermAppoint): return f'/underground/review?Lid={obj.pk}'