Django displaying CPU average for many VMs using ListView - django-views

I have a model that stores a list of VMs and their management IPs. I have another model that stores cpu consumption for all of the VMs tied to the foreign key of the VM. There is a cron job that will run every five minutes to populate the Cpu model with the cpu consumption of each VM.
What I want to do is create a table in a template that lists all VMs, with an average of the CPU consumption, and max CPU consumption.
'''
#models.py
from django.db import models
class VMs (models.Model):
mgmtIP = models.CharField(max_length=16)
hostName = models.CharField(max_length=128)
def __str__(self):
return self.hostName
class Cpu (models.Model):
hostName = models.ForeignKey(F5Model, on_delete=models.CASCADE)
cpuAverage = models.DecimalField(max_digits=3, decimal_places=2)
whenReported = models.DateTimeField(auto_now=False, auto_now_add=True)
#views.py
from .models import Cpu, VMs
class Report(ListView):
model = VMs
template_name = 'report.html'
def get_queryset(self):
queryset = Cpu.objects.filter(hostName= self.hostName).aggregate(Avg(cpuAverage))
return queryset
'''
I can get the basic ListView to show the VMs in my template but when I try to add the queryset it stops displaying anything.
!!!Update!!!
I am able to get it to sort of work with this
'''
class Report(ListView):
model = VMs
context_object_name = 'cpu_list'
template_name = 'report.html'
def get_context_data(self, **kwargs):
context = super(Report, self).get_context_data(**kwargs)
context.update({ 'cpuAverageReport':Cpu.objects.filter(hostName_id='1').aggregate(Avg('cpuAverage')),
})
return context
'''
The problem is I need the hostName id to be dynamic. It should be which ever host it is currently looping through. I put in '1' just to test the rest of my syntax... I've tried 'self.kwargs['id'], self.args['id'], just id, VMs.id, and probably a thousand other combinations...

The problem is aggregate only returns one line. I needed to change it for annotate.
'''
cpuAverageReport':Cpu.objects.values('hostName_id').annotate(Avg('cpuAverage'))
'''
Then I needed to add a for loop into my for loop and an if to match the records up with each other in my template. like so...
'''
<table style="width:100%">
<tr>
<td>Name</td><td>CPU Average</td>
</tr>
{% for x in report %}
<tr>
<td>{{ x.hostName }}</td>
{% for y in cpuAverageReport %}
{% if x.id == y.hostName_id %}
<td>{{ y.cpuAverage__avg }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</table>
'''

Related

How to perform addition (+) of attributes from multiple model instances connected via one ManytoManyField in Django?

Problem Statement: I want to add up the Activity.models attribute net_cost from within Trip.models i.e. connected via ManytoManyField. I have 3 or more choices per instance, so add: from the template language is inadequate (https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#add)
More Context: Activity is connected to Trip via ManytoManyField. Accessing and adding up via save method in models.py is causing id needs to be assigned first error, I believe since ManytoMany can only be assigned once an instance of the model is saved.
Even if I access them all in the views.py before rendering, the context passed even after iterating over each object in list, can only repeat a common context["grand_total"] for all entries rendered in the template, whereas I need grandtotal for each Trip instance in frontend List.view.
Models:
Activity ->
class Activity(models.Model):
activity_location = [
...
]
acitivity_duration = [
...
]
activity_title = models.CharField(max_length=20, unique=True)
...
net_cost = models.PositiveIntegerField(validators=[MaxValueValidator(100000), MinValueValidator(0)], default=0)
class Meta:
ordering = ['-margin']
def __repr__(self):
return f"{self.activity_title} - {self.activity_location} - {self.net_cost}"
def __str__(self):
return f"Activity {self.activity_title}, Location {self.activity_location}, Duration {self.acitivity_duration}, Cost {self.net_cost}"
def get_absolute_url(self):
return reverse('activitys-list')
Trip ->
class Trip(models.Model):
transfer_choices = [
...
]
transfers = models.CharField(max_length=11, choices=transfer_choices, blank=True, null=True)
activity = models.ManyToManyField(Activity, related_name="activities", blank=True, verbose_name='Activities', help_text='select multiple, note location tags')
....
class Meta:
ordering = ['-entry_created']
def __repr__(self):
return f'{self.customer.name} for {self.duration} day/s'
def __str__(self):
return f"{self.customer.name} - {self.duration} - {self.start_date} - {self.end_date}"
def save(self, *args, **kwargs):
...
#successfully processing all inhenrent model attributes for grand total, but
unable to process activity.net_cost for all entries.
super(Trip, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('trip-lists')
View:
class TripLists(LoginRequiredMixin, ListView):
login_url = '/login/'
redirect_field_name = 'index'
model = Trip
template_name = 'gobasic/trip_list.html'
paginate_by = 10
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in extra QuerySets here
Trips = Trip.objects.all()
activity_total = 0
for i in Trips:
for a in i.activity:
activity_total += a.net_cost
# Is pointless as same gets repeated.
context['activity_total'] = activity_total
context['total_trips'] = Trip.objects.all().count()
return context
Template:
<h2>Trip List - {{total_trips}}</h2>
<p><button type="button" class="btn btn-info">Add Trip</button>
<a href="{% url 'index' %}"> <button type="button" class="btn btn-info">Home</button>
</a></p>
<ol>
{% for t in object_list %}
<li><strong>🆔:</strong> {{ t.customer.name}}<strong> PB:</strong> {{ t.hotel_pb.hotel_name }} <strong>HV:</strong> {{ t.hotel_hv.hotel_name }} <strong>NL:</strong> {{ t.hotel_nl.hotel_name }} <!--<strong>Activities:</strong> {% for activity in t.activity.all %} {{ activity.activity_title}}, {% endfor %}--> <strong>Start:</strong>{{t.start_date.date}} <strong>End:</strong>{{t.end_date.date}} <strong>🏨:</strong>{{t.hotel_cost}} 🚕 {{t.transfer_cost}} | 🤿 {% for act in t.activity.all %} {{ act.net_cost}} {% endfor %}<strong> Grand Total= {{t.hotel_cost |add:t.transfer_cost}}</strong> <button class="btn">🖋️</button> </li>
<br>
{% empty %}
<p>No Entries Detected Yet !</p>
{% endfor %}
</ol>
How do I get a grand total i.e. t.hotel_cost + t.transfer_cost + t.activity.net_cost (for all objects within activity i.e. ManytoMany inside object_list )

Django using template input data to create new objects

Due to my lack of skills I think I overcomplicate and would like to get input for a simple solution. I'm creating kind of a web shop template. I have a service work order that I would like to add parts to. I have a decent looking template where I can list all parts that fits a machine model and then filter through them.
One Workorder many Parts (So I have one separate class for all parts objects for workorder). SO to avoid to have to update part number by part number, using create view. I have tried to use a listview where I try to select all parts at the same time.
In my CBV I define the filtering and get a context "Partslist" with all parts to show in table. In the template I have created an input fields qty. I.e. all part in the "Partslist" gets a "qty" inputfields.
In my post or save definition can I simply catch all input fields data with a self.request.GET statement somehow, or could I use the context "Partslist" to iterate through the fields?
models.py
class WorkOrderParts(models.Model):
wo_num = models.CharField(max_length=30, verbose_name='Order Number')
wo_pa_number = models.CharField(max_length=30, verbose_name='Part Number')
wo_pa_qty_def = models.DecimalField(max_digits=3,decimal_places=0,verbose_name='Qty Planned for')
class Parts(models.Model):
pa_number = models.CharField(max_length=30,verbose_name='Part Number')
pa_group1 = models.CharField(max_length=30,verbose_name='Group of Parts')
pa_group2 = models.CharField(max_length=30,verbose_name='2:nd Group of Parts')
pa_fits = ArrayField(models.CharField(max_length=20,verbose_name='models that part fit to (BRAND-MODEL)'),default=list, null=True, blank=True)
views.py
class WO_PartCreate(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = WorkOrderParts
def get_context_data(self, **kwargs):
model = 'my machine model'
qs=Parts.objects.filter(pa_fits__icontains=model)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post(self):
??? This is where I want to take all parts with input qty>0 and update new objects into WorkOrderParts.
AddPart.html
<table>
<thead>
<tr>
<th class="text-left">Part Number</th>
<th class="text-left">Quantity</th>
</tr>
</thead>
{% for item in Partslist %}
<tbody>
<tr>
<td class="text-left">
{% autoescape off %}{{item.pa_number}}{% endautoescape %}<br>
</td>
<td class="text-left">
<input type="number" method="GET" name="{{ item.pa_number }}-Qty"/>
</td>
</tr>
</tbody>
I hope I have been able to explain enough for someone to understand me.The reason why I use a table is simply that I have much more data in there that I want to display.
OK I know there is most probably better ways to solve this. But since I at least was able to find out something that worked, I thought I would share it with others.
It at least solved my immediate need to grab input fields from a dynamic list and do something with it.
views.py
class AddPartTest(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = Parts
form_class = Add_Parts2 ##including selected fields
def get_context_data(self, **kwargs):
order = self.kwargs.get('str')
wo = WorkOrder_main.objects.get(order=order)
model = wo.wo_machine_model
qs=Parts.objects.filter(pa_fits__icontains=model)
first_contains_query = self.request.GET.get('pa_group1')
if is_valid_queryparam(first_contains_query):
qs=qs.filter(pa_group1=first_contains_query)
second_contains_query = self.request.GET.get('pa_group2')
if is_valid_queryparam(second_contains_query):
qs=qs.filter(pa_group2= second_contains_query)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post (self, request, *args, **kwargs):
order = self.kwargs.get('str')
context = self.get_context_data()
Partslist_x = context['Partslist']
Parts_num_list = Partslist_x.values_list('pa_number', flat=True)
for part in Parts_num_list:
qty = part + 'Qty'
Qty = self.request.POST.get(qty)
if int(Qty)==0:
pass
else:
WorkOrderParts.objects.create(
wo_num=order,
wo_pa_number=part,
wo_pa_qty_def=Qty,
)
path='where I want to end up'
return redirect (path)

Counting all items linked to foreign key

I'm looking for a solution to combine in one view information from two models.
One model is for "node" definition. Second model is "drive" definition, where I have foreign key for "node". One "node" can contain many drives.
In a template I need to show all assigned drives for a given node in a list view. I'm interested in count, a number of drives assigned to given node.
I don't have any new idea how calculate this information and pass to template. Should I count this as a "pure python"? I believe there is a way in Django as this doesn't look very complex.
View:
class NodeListView(ListView):
template_name = 'nodes/nodes_list.html'
queryset = Node.objects.all()
context_object_name = 'nodes'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
drives = Drive.objects.all()
context.update({
'drives': drives
})
return context
Models:
class Node(models.Model):
name = models.CharField(max_length=32)
.... more ....
class Drive(models.Model):
node = models.ForeignKey(Node, related_name='nodes', on_delete=models.CASCADE)
.... more ....
You can annotate the Nodes with the number of related Drives:
from django.db.models import Count
class NodeListView(ListView):
template_name = 'nodes/nodes_list.html'
queryset = Node.objects.annotate(ndrives=Count('nodes'))
context_object_name = 'nodes'
then you can render this with:
{% for node in nodes %}
{{ node.ndrives }}
{% endfor %}
You can also enumerate over the related Drives with:
{% for node in nodes %}
{{ node.ndrives }}
{% for drive in node.nodes.all %}
{{ drive }}
{% endfor %}
{% endfor %}
where you can boost efficiency with:
from django.db.models import Count
class NodeListView(ListView):
template_name = 'nodes/nodes_list.html'
queryset = Node.objects.annotate(
ndrives=Count('nodes')
).prefetch_related('nodes')
context_object_name = 'nodes'
The reason that this is named nodes is because of the related_name='nodes'. This however does not make much sense: related_name='drives' would make more sense, since this deals with a collection of Drives, you thus might want to rename this to:
class Node(models.Model):
name = models.CharField(max_length=32)
# …
class Drive(models.Model):
node = models.ForeignKey(
Node,
related_name='drives',
on_delete=models.CASCADE
)
# …
Then the view thus looks like:
from django.db.models import Count
class NodeListView(ListView):
template_name = 'nodes/nodes_list.html'
queryset = Node.objects.annotate(
ndrives=Count('drives')
).prefetch_related('drives')
context_object_name = 'nodes'
and we render this with:
{% for node in nodes %}
{{ node.ndrives }}
{% for drive in node.drives.all %}
{{ drive }}
{% endfor %}
{% endfor %}

How to bind more than one model to a template

I'm trying to feed a select tag with options recovered from a database.
The problem is, I'm totally beginner with Django and don't even know how to search for this.
I'm using generic view and as far as I know, the template is fed by a model bound to a context_object, default named as object_list, but you can change it in the context_object_name variable.
But my companies_object is not feeding the template.
<tbody>
{% for project in projects %}
<tr>
<td>
{{ project.title }}
</td>
[...]
<select>
{% for company in companies %}
<option value="{{company.id}}">{{company.name}}</option>
{% endfor %}
</select>
class ProjectsView(LoginRequiredMixin, ListView):
model = Project
context_object_name = 'projects'
template_name = 'projects/projects.html'
def select_company(self):
companies = Company.objects.all()
return 1 #return selected company
def get_projects(self):
seek_in_database()
return projects
I expect to know how to show two different objects in the same template, the projects, which is already working, and the companies object.
I didn't figure it out yet how the template is getting the project's data, I suspect of model = Projects and context_object_name.
I know that it is begginer level, and I don't expect someone to write a complete guide, I'll be very happy with some instruction of what subject to look.
Here an example how I do it:
class CompanyListView(ListView):
model = Company
context_object_name = 'companies'
template_name = 'core/company/listCompanies.html'
queryset = Company.objects.get_main_companies()
def get_context_data(self, **kwargs):
context = super(CompanyListView, self).get_context_data(**kwargs)
context.update({
'company_abbr': self.request.session.get('company_abbr'),
'page_title': 'Manage Companies',
})
return context
So in get_context_data, you may add as many data as you need.

Django's formset to edit partly predefined data

Here's my domain model:
class Seat(models.Model):
name = models.CharField(max_length=8)
class Event(models.Model):
...
class Occupation(models.Model):
event = models.ForeignKey(Event, related_name='occupations')
seat = models.ForeignKey(Seat)
number = models.CharField(max_length=20)
(Seat is a small table, like 5-10 records.)
We have an UI requirements for a event edit page to look like this:
[1-01] [enter number]
[1-02] [enter number]
[1-03] [enter number]
[2-01] [enter number]
[2-02] [enter number]
[2-03] [enter number]
User navigates to event's occupation page where they see a list of all seats from system and prompted to fill numbers from the external source into the system.
Since the seats table is pretty small and to prevent errors like choosing same seat twice, we're required to display all seats pre-fill into the form and locked, so the user can't change seat selection and only limited to enter corresponding numbers.
Also, seats can be added or removed, so we can't make a "static" form with 6 predefined rows.
I suppose it should be a Django's inline model formset with a form like
class OccupationForm(forms.ModelForm):
class Meta:
model = Occupation
fields = ('seat', 'number')
...
But I'm not sure how should I display a prefilled form, prevent an user from changing seats (and not just a client-side locking via disabled or javascript)
First set seat widget to HiddenInput and add the seat_name property to the form. This property will be used later in the HTML template:
class OccupationForm(forms.ModelForm):
class Meta:
model = Occupation
fields = ('seat', 'number')
widgets = {'seat': forms.HiddenInput}
def __init__(self, *args, **kwargs):
super(OccupationForm, self).__init__(*args, **kwargs)
self.fields['number'].required = False
self.seat_name = self.initial['seat'].name
Then populate initial data with the seats and numbers for the event. Pass this initial to the formset. Validate and save formset as usual:
from django.forms.formsets import formset_factory
def update_seats(request, event_id):
event = Event.objects.get(pk=event_id)
numbers = dict((o.seat, o.number) for o in event.occupations.all())
initial = [{'seat': seat, 'number': numbers.get(seat, '')}
for seat in Seat.objects.all()]
OccupationFormSet = formset_factory(OccupationForm,
min_num=len(initial), validate_min=True,
max_num=len(initial), validate_max=True,
extra=0)
if request.method == 'POST':
formset = OccupationFormSet(request.POST, initial=initial)
if formset.is_valid():
for form in formset:
seat = form.initial['seat']
number = form.cleaned_data.get('number', '').strip()
if number:
Occupation.objects.update_or_create(
event=event, seat=seat,
defaults={'number': number})
else:
Occupation.objects.filter(event=event, seat=seat).delete()
return redirect('.')
else:
formset = OccupationFormSet(initial=initial)
return render(request, 'update_seats.html', {'formset': formset})
And update_seats.html template in which we show seat name as {{ form.seat_name }}:
<form action="" method="post">
{% csrf_token %}
<table>
{{ formset.management_form }}
<tr>
<th>Seat</th>
<th>Number</th>
</tr>
{% for form in formset %}
<tr>
<td>[{{ form.seat_name }}]{{ form.seat }}</td>
<td>{{ form.number }}</td>
</tr>
{% endfor %}
</table>
<button>Update</button>
</form>