Coverage for /Users/davegaeddert/Development/dropseed/plain/plain-staff/plain/staff/views/models.py: 38%

140 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-16 22:04 -0500

1from typing import TYPE_CHECKING 

2 

3from plain.models import Q 

4from plain.urls import reverse_lazy 

5 

6from .base import URL_NAMESPACE, StaffDetailView, StaffListView, StaffUpdateView 

7 

8if TYPE_CHECKING: 

9 from plain import models 

10 from plain.views import View 

11 

12 

13def get_model_field(instance, field): 

14 if "__" in field: 

15 # Allow __ syntax like querysets use, 

16 # also automatically calling callables (like __date) 

17 result = instance 

18 for part in field.split("__"): 

19 result = getattr(result, part) 

20 

21 # If we hit a None, just return it 

22 if not result: 

23 return result 

24 

25 if callable(result): 

26 result = result() 

27 

28 return result 

29 

30 return getattr(instance, field) 

31 

32 

33class StaffModelListView(StaffListView): 

34 show_search = True 

35 allow_global_search = True 

36 

37 model: "models.Model" 

38 

39 fields: list = ["pk"] 

40 queryset_order = [] 

41 search_fields: list = ["pk"] 

42 

43 @classmethod 

44 def get_title(cls) -> str: 

45 return getattr(cls, "title", cls.model._meta.model_name.capitalize() + "s") 

46 

47 @classmethod 

48 def get_slug(cls) -> str: 

49 return cls.model._meta.model_name 

50 

51 def get_template_context(self): 

52 context = super().get_template_context() 

53 

54 order_by = self.request.GET.get("order_by", "") 

55 if order_by.startswith("-"): 

56 order_by_field = order_by[1:] 

57 order_by_direction = "-" 

58 else: 

59 order_by_field = order_by 

60 order_by_direction = "" 

61 

62 context["order_by_field"] = order_by_field 

63 context["order_by_direction"] = order_by_direction 

64 

65 return context 

66 

67 def get_objects(self): 

68 queryset = self.get_initial_queryset() 

69 queryset = self.order_queryset(queryset) 

70 queryset = self.search_queryset(queryset) 

71 return queryset 

72 

73 def get_initial_queryset(self): 

74 # Separate override for the initial queryset 

75 # so that annotations can be added BEFORE order_by, etc. 

76 return self.model.objects.all() 

77 

78 def order_queryset(self, queryset): 

79 if order_by := self.request.GET.get("order_by"): 

80 queryset = queryset.order_by(order_by) 

81 elif self.queryset_order: 

82 queryset = queryset.order_by(*self.queryset_order) 

83 

84 return queryset 

85 

86 def search_queryset(self, queryset): 

87 if search := self.request.GET.get("search"): 

88 filters = Q() 

89 for field in self.search_fields: 

90 filters |= Q(**{f"{field}__icontains": search}) 

91 

92 queryset = queryset.filter(filters) 

93 

94 return queryset 

95 

96 def get_field_value(self, obj, field: str): 

97 return get_model_field(obj, field) 

98 

99 def get_field_value_template(self, obj, field: str, value): 

100 templates = super().get_field_value_template(obj, field, value) 

101 if hasattr(obj, f"get_{field}_display"): 

102 # Insert before the last default template, 

103 # so it can still be overriden by the user 

104 templates.insert(-1, "staff/values/get_display.html") 

105 return templates 

106 

107 

108class StaffModelDetailView(StaffDetailView): 

109 model: "models.Model" 

110 fields: list = [] 

111 

112 @classmethod 

113 def get_title(cls) -> str: 

114 return getattr(cls, "title", cls.model._meta.model_name.capitalize()) 

115 

116 @classmethod 

117 def get_slug(cls) -> str: 

118 return f"{cls.model._meta.model_name}_detail" 

119 

120 @classmethod 

121 def get_path(cls) -> str: 

122 return f"{cls.model._meta.model_name}/<int:pk>/" 

123 

124 def get_template_context(self): 

125 context = super().get_template_context() 

126 context["fields"] = self.fields or ["pk"] + [ 

127 f.name for f in self.object._meta.get_fields() if not f.remote_field 

128 ] 

129 return context 

130 

131 def get_field_value(self, obj, field: str): 

132 return get_model_field(obj, field) 

133 

134 def get_object(self): 

135 return self.model.objects.get(pk=self.url_kwargs["pk"]) 

136 

137 def get_template_names(self) -> list[str]: 

138 return super().get_template_names() + [ 

139 "staff/detail.html", 

140 ] 

141 

142 def get_links(self): 

143 links = super().get_links() 

144 if hasattr(self.object, "get_absolute_url"): 

145 links["View in app"] = self.object.get_absolute_url() 

146 if update_url := self.get_update_url(self.object): 

147 links["Update"] = update_url 

148 return links 

149 

150 

151class StaffModelUpdateView(StaffUpdateView): 

152 model: "models.Model" 

153 form_class = None # TODO type annotation 

154 success_url = "." # Redirect back to the same update page by default 

155 

156 @classmethod 

157 def get_title(cls) -> str: 

158 return getattr(cls, "title", f"Update {cls.model._meta.model_name}") 

159 

160 @classmethod 

161 def get_slug(cls) -> str: 

162 return f"{cls.model._meta.model_name}_update" 

163 

164 @classmethod 

165 def get_path(cls) -> str: 

166 return f"{cls.model._meta.model_name}/<int:pk>/update/" 

167 

168 def get_object(self): 

169 return self.model.objects.get(pk=self.url_kwargs["pk"]) 

170 

171 def get_links(self): 

172 links = super().get_links() 

173 if hasattr(self.object, "get_absolute_url"): 

174 links["View in app"] = self.object.get_absolute_url() 

175 if detail_url := self.get_detail_url(self.object): 

176 links["Detail"] = detail_url 

177 return links 

178 

179 

180class StaffModelViewset: 

181 @classmethod 

182 def get_views(cls) -> list["View"]: 

183 views = [] 

184 

185 if hasattr(cls, "ListView") and hasattr(cls, "DetailView"): 

186 cls.ListView.get_detail_url = lambda self, obj: reverse_lazy( 

187 f"{URL_NAMESPACE}:{cls.DetailView.view_name()}", 

188 kwargs={"pk": obj.pk}, 

189 ) 

190 

191 cls.DetailView.parent_view_class = cls.ListView 

192 

193 if hasattr(cls, "ListView") and hasattr(cls, "UpdateView"): 

194 cls.ListView.get_update_url = lambda self, obj: reverse_lazy( 

195 f"{URL_NAMESPACE}:{cls.UpdateView.view_name()}", 

196 kwargs={"pk": obj.pk}, 

197 ) 

198 

199 cls.UpdateView.parent_view_class = cls.ListView 

200 

201 if hasattr(cls, "DetailView") and hasattr(cls, "UpdateView"): 

202 cls.DetailView.get_update_url = lambda self, obj: reverse_lazy( 

203 f"{URL_NAMESPACE}:{cls.UpdateView.view_name()}", 

204 kwargs={"pk": obj.pk}, 

205 ) 

206 

207 cls.UpdateView.get_detail_url = lambda self, obj: reverse_lazy( 

208 f"{URL_NAMESPACE}:{cls.DetailView.view_name()}", 

209 kwargs={"pk": obj.pk}, 

210 ) 

211 

212 if hasattr(cls, "ListView"): 

213 views.append(cls.ListView) 

214 

215 if hasattr(cls, "DetailView"): 

216 views.append(cls.DetailView) 

217 

218 if hasattr(cls, "UpdateView"): 

219 views.append(cls.UpdateView) 

220 

221 return views