mass edit for compute plans
This commit is contained in:
parent
b32a19ffa2
commit
2da6285800
2 changed files with 243 additions and 1 deletions
|
@ -3,8 +3,12 @@ Admin classes for pricing models including compute plans, storage plans, and VSH
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
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.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 adminsortable2.admin import SortableAdminMixin
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export import resources
|
from import_export import resources
|
||||||
|
@ -27,6 +31,8 @@ from ..models import (
|
||||||
Service,
|
Service,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ..models.base import Term
|
||||||
|
|
||||||
|
|
||||||
def natural_sort_key(obj):
|
def natural_sort_key(obj):
|
||||||
"""Extract numeric parts for natural sorting"""
|
"""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)
|
@admin.register(ComputePlan)
|
||||||
class ComputePlanAdmin(ImportExportModelAdmin):
|
class ComputePlanAdmin(ImportExportModelAdmin):
|
||||||
"""Admin configuration for ComputePlan model with import/export functionality"""
|
"""Admin configuration for ComputePlan model with import/export functionality"""
|
||||||
|
@ -141,6 +182,7 @@ class ComputePlanAdmin(ImportExportModelAdmin):
|
||||||
search_fields = ("name", "cloud_provider__name", "group__name")
|
search_fields = ("name", "cloud_provider__name", "group__name")
|
||||||
list_filter = ("active", "cloud_provider", "group")
|
list_filter = ("active", "cloud_provider", "group")
|
||||||
inlines = [ComputePlanPriceInline]
|
inlines = [ComputePlanPriceInline]
|
||||||
|
actions = ["mass_update_compute_plans"]
|
||||||
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
"""Override changelist view to apply natural sorting"""
|
"""Override changelist view to apply natural sorting"""
|
||||||
|
@ -164,6 +206,80 @@ class ComputePlanAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
display_prices.short_description = "Prices (Amount Currency)"
|
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):
|
class VSHNAppCatBaseFeeInline(admin.TabularInline):
|
||||||
"""Inline admin for VSHNAppCatBaseFee model"""
|
"""Inline admin for VSHNAppCatBaseFee model"""
|
||||||
|
|
126
hub/services/templates/admin/mass_update_compute_plans.html
Normal file
126
hub/services/templates/admin/mass_update_compute_plans.html
Normal 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>
|
||||||
|
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
||||||
|
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||||
|
› {{ 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 %}
|
Loading…
Add table
Add a link
Reference in a new issue