mass edit for compute plans

This commit is contained in:
Tobias Brunner 2025-06-04 15:50:19 +02:00
parent b32a19ffa2
commit 2da6285800
No known key found for this signature in database
2 changed files with 243 additions and 1 deletions

View file

@ -3,8 +3,12 @@ Admin classes for pricing models including compute plans, storage plans, and VSH
"""
import re
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.admin import helpers
from django.utils.html import format_html
from django import forms
from django.shortcuts import render
from django.http import HttpResponseRedirect
from adminsortable2.admin import SortableAdminMixin
from import_export.admin import ImportExportModelAdmin
from import_export import resources
@ -27,6 +31,8 @@ from ..models import (
Service,
)
from ..models.base import Term
def natural_sort_key(obj):
"""Extract numeric parts for natural sorting"""
@ -123,6 +129,41 @@ class ComputePlanResource(resources.ModelResource):
)
class MassUpdateComputePlanForm(forms.Form):
"""Form for mass updating ComputePlan fields"""
active = forms.ChoiceField(
choices=[("", "-- No change --"), ("True", "Active"), ("False", "Inactive")],
required=False,
help_text="Set active status for selected compute plans",
)
term = forms.ChoiceField(
choices=[("", "-- No change --")] + Term.choices,
required=False,
help_text="Set billing term for selected compute plans",
)
group = forms.ModelChoiceField(
queryset=ComputePlanGroup.objects.all(),
required=False,
empty_label="-- No change --",
help_text="Set group for selected compute plans",
)
valid_from = forms.DateField(
widget=forms.DateInput(attrs={"type": "date"}),
required=False,
help_text="Set valid from date for selected compute plans",
)
valid_to = forms.DateField(
widget=forms.DateInput(attrs={"type": "date"}),
required=False,
help_text="Set valid to date for selected compute plans",
)
@admin.register(ComputePlan)
class ComputePlanAdmin(ImportExportModelAdmin):
"""Admin configuration for ComputePlan model with import/export functionality"""
@ -141,6 +182,7 @@ class ComputePlanAdmin(ImportExportModelAdmin):
search_fields = ("name", "cloud_provider__name", "group__name")
list_filter = ("active", "cloud_provider", "group")
inlines = [ComputePlanPriceInline]
actions = ["mass_update_compute_plans"]
def changelist_view(self, request, extra_context=None):
"""Override changelist view to apply natural sorting"""
@ -164,6 +206,80 @@ class ComputePlanAdmin(ImportExportModelAdmin):
display_prices.short_description = "Prices (Amount Currency)"
def mass_update_compute_plans(self, request, queryset):
"""Admin action to mass update compute plan fields"""
if request.POST.get("post"):
# Process the form submission
form = MassUpdateComputePlanForm(request.POST)
if form.is_valid():
updated_count = 0
updated_fields = []
# Prepare update data
update_data = {}
# Handle active field
if form.cleaned_data["active"]:
update_data["active"] = form.cleaned_data["active"] == "True"
updated_fields.append("active")
# Handle term field
if form.cleaned_data["term"]:
update_data["term"] = form.cleaned_data["term"]
updated_fields.append("term")
# Handle group field
if form.cleaned_data["group"]:
update_data["group"] = form.cleaned_data["group"]
updated_fields.append("group")
# Handle valid_from field
if form.cleaned_data["valid_from"]:
update_data["valid_from"] = form.cleaned_data["valid_from"]
updated_fields.append("valid_from")
# Handle valid_to field
if form.cleaned_data["valid_to"]:
update_data["valid_to"] = form.cleaned_data["valid_to"]
updated_fields.append("valid_to")
# Perform the bulk update
if update_data:
updated_count = queryset.update(**update_data)
# Create success message
field_list = ", ".join(updated_fields)
self.message_user(
request,
f"Successfully updated {updated_count} compute plan(s). "
f"Updated fields: {field_list}",
messages.SUCCESS,
)
else:
self.message_user(
request, "No fields were selected for update.", messages.WARNING
)
return HttpResponseRedirect(request.get_full_path())
else:
# Show the form
form = MassUpdateComputePlanForm()
# Render the mass update template
return render(
request,
"admin/mass_update_compute_plans.html",
{
"form": form,
"queryset": queryset,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"opts": self.model._meta,
"title": f"Mass Update {queryset.count()} Compute Plans",
},
)
mass_update_compute_plans.short_description = "Mass update selected compute plans"
class VSHNAppCatBaseFeeInline(admin.TabularInline):
"""Inline admin for VSHNAppCatBaseFee model"""

View file

@ -0,0 +1,126 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {{ title }}
</div>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ title }}</h3>
</div>
<div class="card-body">
<p class="mb-3">You are about to update <strong>{{ queryset.count }}</strong> compute plan(s). Please select the fields you want to update:</p>
<!-- Selected items display -->
<div class="alert alert-info" role="alert">
<h6 class="alert-heading">Selected Compute Plans:</h6>
<div class="row">
{% for obj in queryset %}
<div class="col-md-6 mb-1">
<small><i class="fas fa-server me-1"></i>{{ obj.name }} ({{ obj.cloud_provider }})</small>
</div>
{% endfor %}
</div>
</div>
<!-- Update form -->
<form method="post">
{% csrf_token %}
<!-- Pass selected items -->
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
{% endfor %}
<input type="hidden" name="action" value="mass_update_compute_plans">
<input type="hidden" name="post" value="yes">
<!-- Form fields -->
<div class="row mb-3">
<label for="{{ form.active.id_for_label }}" class="col-sm-3 col-form-label">Active Status:</label>
<div class="col-sm-9">
{{ form.active }}
{% if form.active.help_text %}
<div class="form-text">{{ form.active.help_text }}</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="{{ form.term.id_for_label }}" class="col-sm-3 col-form-label">Term:</label>
<div class="col-sm-9">
{{ form.term }}
{% if form.term.help_text %}
<div class="form-text">{{ form.term.help_text }}</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="{{ form.group.id_for_label }}" class="col-sm-3 col-form-label">Group:</label>
<div class="col-sm-9">
{{ form.group }}
{% if form.group.help_text %}
<div class="form-text">{{ form.group.help_text }}</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="{{ form.valid_from.id_for_label }}" class="col-sm-3 col-form-label">Valid From:</label>
<div class="col-sm-9">
{{ form.valid_from }}
{% if form.valid_from.help_text %}
<div class="form-text">{{ form.valid_from.help_text }}</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="{{ form.valid_to.id_for_label }}" class="col-sm-3 col-form-label">Valid To:</label>
<div class="col-sm-9">
{{ form.valid_to }}
{% if form.valid_to.help_text %}
<div class="form-text">{{ form.valid_to.help_text }}</div>
{% endif %}
</div>
</div>
<!-- Submit buttons -->
<div class="row">
<div class="col-sm-9 offset-sm-3">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>Update Compute Plans
</button>
<a href="{% url opts|admin_urlname:'changelist' %}" class="btn btn-secondary ms-2">
<i class="fas fa-times me-1"></i>Cancel
</a>
</div>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('select');
const inputs = document.querySelectorAll('input[type="date"]');
selects.forEach(select => {
select.classList.add('form-select');
});
inputs.forEach(input => {
input.classList.add('form-control');
});
});
</script>
{% endblock %}