Coverage for baibu/chat/admin.py: 100%

125 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-10-06 13:07 +0000

1from django.contrib import admin 

2from django.db import transaction 

3from django.utils.translation import gettext_lazy as _ 

4 

5from .models import AudioClip 

6from .models import ChatFlag 

7from .models import Conversation 

8from .models import ConversationEvent 

9from .models import ConversationTag 

10from .models import Message 

11from .models import ModelVariant 

12from .models import Prompt 

13from .models import Run 

14from .models import ToolInvocation 

15from .models import Topic 

16 

17 

18class ReadOnlyInline(admin.TabularInline): 

19 extra = 0 

20 can_delete = False 

21 show_change_link = False 

22 

23 def has_add_permission(self, request, obj=None): 

24 return False 

25 

26 def has_change_permission(self, request, obj=None): 

27 return False 

28 

29 

30class MessageInline(ReadOnlyInline): 

31 model = Message 

32 fields = ("created_at", "role", "content") 

33 readonly_fields = fields 

34 

35 

36class RunInline(ReadOnlyInline): 

37 model = Run 

38 fields = ("created_at", "status", "attempt", "model_name", "prompt_version", "latency_ms", "error") 

39 readonly_fields = fields 

40 

41 

42class ToolInvocationInline(ReadOnlyInline): 

43 model = ToolInvocation 

44 fields = ("created_at", "tool_name", "provider", "status", "arguments", "result_count", "error", "latency_ms") 

45 readonly_fields = fields 

46 

47 

48class AudioClipInline(ReadOnlyInline): 

49 """Metadata only: recordings are not played in the admin.""" 

50 

51 model = AudioClip 

52 fields = ("created_at", "status", "attempt", "content_type", "size_bytes", "duration_seconds", "provider", "error") 

53 readonly_fields = fields 

54 

55 

56class TagInline(ReadOnlyInline): 

57 model = ConversationTag 

58 fields = ("topic_list", "language", "intent", "tagger", "tagged_at", "error") 

59 readonly_fields = fields 

60 

61 @admin.display(description=_("Topics")) 

62 def topic_list(self, obj): 

63 return ", ".join(topic.label for topic in obj.topics.all()) or "—" 

64 

65 

66class EventInline(ReadOnlyInline): 

67 model = ConversationEvent 

68 fields = ("created_at", "type", "payload") 

69 readonly_fields = fields 

70 

71 

72@admin.register(Conversation) 

73class ConversationAdmin(admin.ModelAdmin): 

74 list_display = ("__str__", "user", "language_code", "consent_tier", "last_activity_at") 

75 list_filter = ("consent_tier", "language_code", "tag__topics", "tag__intent") 

76 search_fields = ("id", "user__email", "title") 

77 date_hierarchy = "last_activity_at" 

78 readonly_fields = ("id", "user", "title", "language_code", "consent_tier", "created_at", "last_activity_at") 

79 inlines = (TagInline, MessageInline, AudioClipInline, RunInline, EventInline) 

80 

81 def has_add_permission(self, request): 

82 return False 

83 

84 def has_change_permission(self, request, obj=None): 

85 return request.method in {"GET", "HEAD"} and super().has_change_permission(request, obj) 

86 

87 

88@admin.register(Run) 

89class RunAdmin(admin.ModelAdmin): 

90 list_display = ("id", "conversation", "status", "attempt", "model_name", "latency_ms", "created_at") 

91 list_filter = ("status", "model_name") 

92 date_hierarchy = "created_at" 

93 readonly_fields = [field.name for field in Run._meta.fields] 

94 inlines = (ToolInvocationInline,) 

95 

96 def has_add_permission(self, request): 

97 return False 

98 

99 def has_change_permission(self, request, obj=None): 

100 return request.method in {"GET", "HEAD"} and super().has_change_permission(request, obj) 

101 

102 

103@admin.register(ModelVariant) 

104class ModelVariantAdmin(admin.ModelAdmin): 

105 list_display = ("name", "model", "api_base", "is_default") 

106 fields = ("name", "model", "api_base", "api_key_env", "parameters", "is_default") 

107 

108 def save_model(self, request, obj, form, change): 

109 with transaction.atomic(): 

110 if obj.is_default: 

111 ModelVariant.objects.exclude(pk=obj.pk).update(is_default=False) 

112 super().save_model(request, obj, form, change) 

113 

114 

115@admin.register(Prompt) 

116class PromptAdmin(admin.ModelAdmin): 

117 """Adding a prompt creates the next version; existing versions cannot be edited.""" 

118 

119 list_display = ("name", "version", "active", "notes", "created_by", "created_at") 

120 list_filter = ("name", "active") 

121 actions = ("activate",) 

122 

123 def get_fields(self, request, obj=None): 

124 if obj is None: 

125 return ("name", "body", "notes", "active") 

126 return ("name", "version", "body", "notes", "active", "created_by", "created_at") 

127 

128 def get_readonly_fields(self, request, obj=None): 

129 if obj is None: 

130 return () 

131 return ("name", "version", "body", "notes", "created_by", "created_at") 

132 

133 def save_model(self, request, obj, form, change): 

134 with transaction.atomic(): 

135 if not change: 

136 obj.created_by = request.user 

137 if obj.active: 

138 Prompt.objects.filter(name=obj.name, active=True).exclude(pk=obj.pk).update(active=False) 

139 super().save_model(request, obj, form, change) 

140 

141 @admin.action(description=_("Make the selected version active")) 

142 def activate(self, request, queryset): 

143 if queryset.count() != 1: 

144 self.message_user(request, _("Select exactly one version."), level="warning") 

145 return 

146 prompt = queryset.get() 

147 with transaction.atomic(): 

148 Prompt.objects.filter(name=prompt.name, active=True).update(active=False) 

149 Prompt.objects.filter(pk=prompt.pk).update(active=True) 

150 self.message_user(request, _("%(prompt)s is now active.") % {"prompt": prompt}) 

151 

152 

153@admin.register(ChatFlag) 

154class ChatFlagAdmin(admin.ModelAdmin): 

155 list_display = ("__str__", "conversation", "created_at", "decided_by") 

156 list_filter = ("status", "reason") 

157 readonly_fields = [field.name for field in ChatFlag._meta.fields] 

158 

159 def has_add_permission(self, request): 

160 return False 

161 

162 def has_change_permission(self, request, obj=None): 

163 return request.method in {"GET", "HEAD"} and super().has_change_permission(request, obj) 

164 

165 

166@admin.register(Topic) 

167class TopicAdmin(admin.ModelAdmin): 

168 list_display = ("label", "slug", "active") 

169 list_filter = ("active",) 

170 prepopulated_fields = {"slug": ("label",)}