Coverage for server/views.py: 92%
126 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-24 19:06 +0000
« 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
4from sm.views import SafeDeleteMixin, GenericCSVExportView
5from sm.mixins import filter_queryset_by_tenant, MultiTenantMixin, BulkActionMixin
7from .models import Model
8from .forms import Form, FormDisabled, BulkActionForm
9from . import app_label
11from django.views import View
12from django.shortcuts import get_object_or_404, redirect
13from django.db import transaction
14from django.utils import timezone
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
21from django.contrib.messages.views import SuccessMessageMixin
23from django.utils.translation import gettext as _
25from django.urls import reverse_lazy
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"
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)
54 def get_context_data(self, **kwargs):
55 context = super().get_context_data(**kwargs)
56 context["bulk_form"] = BulkActionForm()
57 return context
60class BulkActionView(LoginRequiredMixin, View):
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")
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()
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."))
128 return redirect("server:index")
131class DecommissionView(LoginRequiredMixin, MultiTenantMixin, View):
132 """Marks a single server as decommissioned (sets the decommission date)."""
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")
148class RestoreView(LoginRequiredMixin, MultiTenantMixin, View):
149 """Clears the decommission date, bringing a server back into use."""
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")
165class DetailView(LoginRequiredMixin, MultiTenantMixin, GenericUpdateView):
166 template_name = "%s/detail.html" % app_label
167 model = Model
168 form_class = FormDisabled
171class UpdateView(
172 SuccessMessageMixin,
173 LoginRequiredMixin,
174 MultiTenantMixin,
175 GenericUpdateView,
176):
177 success_message = "%(hostname)s " + _("was updated successfully")
178 model = Model
180 template_name = "%s/edit.html" % app_label
181 form_class = Form
182 success_url = reverse_lazy("%s:index" % app_label)
185class CreateView(
186 SuccessMessageMixin,
187 LoginRequiredMixin,
188 MultiTenantMixin,
189 GenericCreateView,
190):
191 success_message = "%(hostname)s " + _("was created successfully")
192 model = Model
194 template_name = "%s/edit.html" % app_label
195 form_class = Form
196 success_url = reverse_lazy("%s:index" % app_label)
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)
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"
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 ]