Coverage for src / lexigram / admin / resources / users.py: 0%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 18:58 +0800

1""" 

2User resource definition for Lexigram Admin. 

3 

4This module defines the UserResource class that provides admin interface 

5for managing users with date range filtering capabilities. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import Any 

11 

12from lexigram.admin.resources.base import Resource 

13from lexigram.admin.schema import BooleanField, DateField, SelectField, TextField 

14from lexigram.admin.ui.filters import SelectFilter, ToggleFilter 

15from lexigram.ui import ( 

16 BadgeColumn, 

17 CreateAction, 

18 DateColumn, 

19 DeleteAction, 

20 EditAction, 

21 TextColumn, 

22) 

23 

24 

25class UserResource(Resource): 

26 """ 

27 Admin resource for managing users. 

28 

29 Provides comprehensive user management with filtering by registration date 

30 and last active date using DateRangeFilter. 

31 """ 

32 

33 # Model configuration 

34 model = None # Will be set by DI or registry 

35 service = None # Will be set by DI or registry 

36 

37 # UI Configuration 

38 layout_type = "sidebar" 

39 data_view = "tabular" 

40 

41 # New SchemaField declarations (canonical definition) 

42 fields: list[Any] = [ 

43 TextField(name="name", sortable=True, searchable=True), 

44 TextField(name="email", sortable=True, searchable=True), 

45 SelectField( 

46 name="role", 

47 options=[ 

48 ("admin", "Admin"), 

49 ("moderator", "Moderator"), 

50 ("user", "User"), 

51 ("guest", "Guest"), 

52 ], 

53 ), 

54 SelectField( 

55 name="status", 

56 options=[ 

57 ("active", "Active"), 

58 ("inactive", "Inactive"), 

59 ("pending", "Pending"), 

60 ("suspended", "Suspended"), 

61 ], 

62 ), 

63 DateField(name="created_at", label="Registration Date", sortable=True), 

64 DateField(name="last_active", sortable=True), 

65 BooleanField(name="is_active", label="Active"), 

66 ] 

67 

68 # Columns configuration (legacy — kept for backward compat) 

69 @property 

70 def columns(self) -> list[Any]: # type: ignore[override] 

71 """Define the columns for the user table.""" 

72 return [ 

73 TextColumn("name").sortable().searchable().weight("semibold"), 

74 TextColumn("email").sortable().searchable().copyable().color("blue"), 

75 BadgeColumn( 

76 "role", 

77 colors={ 

78 "admin": "purple", 

79 "moderator": "blue", 

80 "user": "gray", 

81 "guest": "slate", 

82 }, 

83 ), 

84 BadgeColumn( 

85 "status", 

86 colors={ 

87 "active": "green", 

88 "inactive": "red", 

89 "pending": "yellow", 

90 "suspended": "orange", 

91 }, 

92 ), 

93 DateColumn("created_at").label("Registration Date").datetime().sortable(), # type: ignore[operator] 

94 DateColumn("last_active").datetime().sortable(), 

95 ToggleFilter("is_active", label="Active"), 

96 ] 

97 

98 # Filter configuration 

99 @property 

100 def filter_options(self) -> dict[str, Any]: 

101 """Define filter options for the user resource.""" 

102 field_map: dict[str, Any] = {f.name: f for f in self.fields} 

103 return { 

104 "role": SelectFilter( 

105 "role", 

106 options=[v for v, _ in field_map["role"].options], 

107 label="Role", 

108 ), 

109 "status": SelectFilter( 

110 "status", 

111 options=[v for v, _ in field_map["status"].options], 

112 label="Status", 

113 ), 

114 "is_active": ToggleFilter("is_active", label="Active"), 

115 } 

116 

117 # Actions configuration 

118 @property 

119 def actions(self) -> list[Any]: # type: ignore[override] 

120 """Define row-level actions.""" 

121 return [ 

122 EditAction(), 

123 DeleteAction(), 

124 ] 

125 

126 # Header actions 

127 @property 

128 def header_actions(self) -> list[Any]: 

129 """Define header-level actions.""" 

130 return [ 

131 CreateAction(label="Add User"), 

132 ] 

133 

134 # Search configuration 

135 search_fields = ["name", "email"] 

136 

137 # Bulk actions 

138 bulk_actions: list[Any] = ["delete_selected", "export_csv"] 

139 

140 # Permissions 

141 def has_view_permission(self, user: Any) -> bool: 

142 """Check if user can view users.""" 

143 return user and (user.is_admin or user.role in ["admin", "moderator"]) 

144 

145 def has_add_permission(self, user: Any) -> bool: 

146 """Check if user can add users.""" 

147 return user and user.is_admin 

148 

149 def has_change_permission(self, user: Any) -> bool: 

150 """Check if user can change users.""" 

151 return user and (user.is_admin or user.role in ["admin", "moderator"]) 

152 

153 def has_delete_permission(self, user: Any) -> bool: 

154 """Check if user can delete users.""" 

155 return user and user.is_admin 

156 

157 # Custom methods 

158 async def get_queryset(self, request: Any) -> Any: 

159 """Get the base queryset for users.""" 

160 if self.service: 

161 return await self.service.list() 

162 return [] 

163 

164 async def get_object(self, pk: Any) -> Any: 

165 """Get a single user object.""" 

166 if self.service: 

167 return await self.service.get_by_id(pk) 

168 return None 

169 

170 async def create_object(self, data: dict[str, Any]) -> Any: 

171 """Create a new user.""" 

172 if self.service: 

173 return await self.service.create(data) 

174 return None 

175 

176 async def update_object(self, pk: Any, data: dict[str, Any]) -> Any: 

177 """Update an existing user.""" 

178 if self.service: 

179 return await self.service.update(pk, data) 

180 return None 

181 

182 async def delete_object(self, pk: Any) -> bool: 

183 """Delete a user.""" 

184 if self.service: 

185 return await self.service.delete(pk) 

186 return False