Coverage for server/views.py: 92%

126 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-24 19:06 +0000

1from django.contrib.auth.mixins import LoginRequiredMixin 

2from django.contrib import messages 

3 

4from sm.views import SafeDeleteMixin, GenericCSVExportView 

5from sm.mixins import filter_queryset_by_tenant, MultiTenantMixin, BulkActionMixin 

6 

7from .models import Model 

8from .forms import Form, FormDisabled, BulkActionForm 

9from . import app_label 

10 

11from django.views import View 

12from django.shortcuts import get_object_or_404, redirect 

13from django.db import transaction 

14from django.utils import timezone 

15 

16from django.views.generic import ListView as GenericListView 

17from django.views.generic.edit import UpdateView as GenericUpdateView 

18from django.views.generic.edit import CreateView as GenericCreateView 

19from django.views.generic.edit import DeleteView as GenericDeleteView 

20 

21from django.contrib.messages.views import SuccessMessageMixin 

22 

23from django.utils.translation import gettext as _ 

24 

25from django.urls import reverse_lazy 

26 

27 

28class ListView(LoginRequiredMixin, MultiTenantMixin, BulkActionMixin, GenericListView): 

29 template_name = "%s/list.html" % app_label 

30 model = Model 

31 paginate_by = 20 

32 paginate_orphans = paginate_by / 4 

33 # queryset = model.objects.all() 

34 ordering = "hostname" 

35 

36 def get_queryset(self): 

37 queryset = ( 

38 super() 

39 .get_queryset() 

40 .select_related( 

41 "status", 

42 "location", 

43 "domain", 

44 "patchtime", 

45 "servermodel", 

46 "operatingsystem__vendor", 

47 ) 

48 ) 

49 if "srvmanager-show_disposed" in self.request.COOKIES: 

50 if self.request.COOKIES["srvmanager-show_disposed"] == "true": 

51 return queryset.order_by(self.ordering) 

52 return queryset.exclude(status__name="Disposed").order_by(self.ordering) 

53 

54 def get_context_data(self, **kwargs): 

55 context = super().get_context_data(**kwargs) 

56 context["bulk_form"] = BulkActionForm() 

57 return context 

58 

59 

60class BulkActionView(LoginRequiredMixin, View): 

61 

62 def post(self, request, *args, **kwargs): 

63 server_ids = request.POST.getlist("selected_ids") 

64 if not server_ids: 

65 messages.warning(request, _("No servers selected.")) 

66 return redirect("server:index") 

67 

68 form = BulkActionForm(request.POST) 

69 if form.is_valid(): 

70 # Filter by tenant so a user can never bulk-operate on another 

71 # group's servers. 

72 servers = filter_queryset_by_tenant(Model.objects.all(), request).filter( 

73 id__in=server_ids 

74 ) 

75 # Global (group-less) seed fixtures are read-only for tenant users 

76 if not request.user.is_superuser: 

77 servers = servers.exclude(group__isnull=True) 

78 count = servers.count() 

79 

80 if form.cleaned_data["delete"]: 

81 # Check delete permission 

82 if not request.user.has_perm("server.delete_model"): 

83 messages.error( 

84 request, 

85 _("You don't have permission to delete servers."), 

86 ) 

87 return redirect("server:index") 

88 servers.delete() 

89 messages.success(request, _("Successfully deleted %d servers.") % count) 

90 elif form.cleaned_data["decommission"]: 

91 if not request.user.has_perm("server.change_model"): 

92 messages.error( 

93 request, 

94 _("You don't have permission to change servers."), 

95 ) 

96 return redirect("server:index") 

97 decommissioned = 0 

98 for server in servers: 

99 if server.decommission_date is None: 

100 server.decommission_date = timezone.localdate() 

101 server.save() 

102 decommissioned += 1 

103 messages.success( 

104 request, 

105 _("Successfully decommissioned %d servers.") % decommissioned, 

106 ) 

107 elif form.cleaned_data["status"]: 

108 # Check change permission before re-labelling any servers. 

109 if not request.user.has_perm("server.change_model"): 

110 messages.error( 

111 request, 

112 _("You don't have permission to change servers."), 

113 ) 

114 return redirect("server:index") 

115 new_status = form.cleaned_data["status"] 

116 with transaction.atomic(): 

117 for server in servers: 

118 server.status = new_status 

119 server.save() 

120 messages.success( 

121 request, 

122 _("Successfully updated status to %s for %d servers.") 

123 % (new_status, count), 

124 ) 

125 else: 

126 messages.info(request, _("No action performed.")) 

127 

128 return redirect("server:index") 

129 

130 

131class DecommissionView(LoginRequiredMixin, MultiTenantMixin, View): 

132 """Marks a single server as decommissioned (sets the decommission date).""" 

133 

134 def post(self, request, *args, **kwargs): 

135 server = get_object_or_404(Model, pk=kwargs.get("pk")) 

136 if not request.user.has_perm("server.change_model"): 

137 messages.error(request, _("You don't have permission to change servers.")) 

138 return redirect("server:index") 

139 server.decommission_date = timezone.localdate() 

140 server.save() 

141 messages.success( 

142 request, 

143 _("%(hostname)s has been decommissioned.") % {"hostname": server.hostname}, 

144 ) 

145 return redirect("server:index") 

146 

147 

148class RestoreView(LoginRequiredMixin, MultiTenantMixin, View): 

149 """Clears the decommission date, bringing a server back into use.""" 

150 

151 def post(self, request, *args, **kwargs): 

152 server = get_object_or_404(Model, pk=kwargs.get("pk")) 

153 if not request.user.has_perm("server.change_model"): 

154 messages.error(request, _("You don't have permission to change servers.")) 

155 return redirect("server:index") 

156 server.decommission_date = None 

157 server.save() 

158 messages.success( 

159 request, 

160 _("%(hostname)s has been restored.") % {"hostname": server.hostname}, 

161 ) 

162 return redirect("server:index") 

163 

164 

165class DetailView(LoginRequiredMixin, MultiTenantMixin, GenericUpdateView): 

166 template_name = "%s/detail.html" % app_label 

167 model = Model 

168 form_class = FormDisabled 

169 

170 

171class UpdateView( 

172 SuccessMessageMixin, 

173 LoginRequiredMixin, 

174 MultiTenantMixin, 

175 GenericUpdateView, 

176): 

177 success_message = "%(hostname)s " + _("was updated successfully") 

178 model = Model 

179 

180 template_name = "%s/edit.html" % app_label 

181 form_class = Form 

182 success_url = reverse_lazy("%s:index" % app_label) 

183 

184 

185class CreateView( 

186 SuccessMessageMixin, 

187 LoginRequiredMixin, 

188 MultiTenantMixin, 

189 GenericCreateView, 

190): 

191 success_message = "%(hostname)s " + _("was created successfully") 

192 model = Model 

193 

194 template_name = "%s/edit.html" % app_label 

195 form_class = Form 

196 success_url = reverse_lazy("%s:index" % app_label) 

197 

198 

199class DeleteView( 

200 SafeDeleteMixin, 

201 LoginRequiredMixin, 

202 MultiTenantMixin, 

203 GenericDeleteView, 

204): 

205 success_message = "%(hostname)s " + _("was deleted successfully") 

206 template_name = "delete.html" 

207 model = Model 

208 success_url = reverse_lazy("%s:index" % app_label) 

209 

210 

211class SearchView(LoginRequiredMixin, MultiTenantMixin, GenericListView): 

212 template_name = "%s/list.html" % app_label 

213 model = Model 

214 paginate_by = 20 

215 paginate_orphans = paginate_by / 4 

216 ordering = "hostname" 

217 

218 

219class CSVExportView(GenericCSVExportView): 

220 model = Model 

221 filename = "server.csv" 

222 export_fields = [ 

223 ("hostname", "hostname"), 

224 ("status", "status__name"), 

225 ("domain", "domain__name"), 

226 ("location", "location__name"), 

227 ("application", "application"), 

228 ("rack", "rack"), 

229 ("primary_ip", "primary_ip"), 

230 ("management_ip", "management_ip"), 

231 ("description", "description"), 

232 ("decommission_date", "decommission_date"), 

233 ("tags", "tags.names"), 

234 ]