Skip to content

Commit ccb956d

Browse files
authored
Merge pull request #25 from jasonyates/calendar
Adding maintenance calendar
2 parents 70e2e8a + c7bfa32 commit ccb956d

File tree

7 files changed

+192
-14
lines changed

7 files changed

+192
-14
lines changed

netbox_circuitmaintenance/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ class CircuitMaintenanceTypeChoices(ChoiceSet):
1313
CHOICES = [
1414
('TENTATIVE', 'Tentative', 'yellow'),
1515
('CONFIRMED', 'Confirmed', 'green'),
16-
('CANCELLED', 'Cancelled', 'gray'),
17-
('IN-PROCESS', 'In-Process', 'orange'),
16+
('CANCELLED', 'Cancelled', 'blue'),
17+
('IN-PROCESS', 'In-Progress', 'orange'),
1818
('COMPLETED', 'Completed', 'indigo'),
1919
('RE-SCHEDULED', 'Rescheduled', 'green'),
20-
('UNKNOWN', 'Unknown', 'gray'),
20+
('UNKNOWN', 'Unknown', 'blue'),
2121
]
2222

2323
class CircuitMaintenanceImpactTypeChoices(ChoiceSet):

netbox_circuitmaintenance/navigation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
)
1515
]
1616
),
17-
#PluginMenuItem(
18-
# link='plugins:netbox_circuitmaintenance:maintenanceschedule',
19-
# link_text='Maintenance Schedule',
20-
#),
17+
PluginMenuItem(
18+
link='plugins:netbox_circuitmaintenance:maintenanceschedule',
19+
link_text='Maintenance Schedule',
20+
),
2121
]
2222

2323
menu = PluginMenu(

netbox_circuitmaintenance/template_content.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class CircuitMaintenanceList(PluginTemplateExtension):
88
def left_page(self):
99

1010
return self.render('netbox_circuitmaintenance/circuitmaintenance_include.html', extra_context={
11-
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(circuit__cid=self.context['object'].cid, circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RESCHEDULED', 'UNKNOWN']),
11+
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(circuit__cid=self.context['object'].cid, circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RE-SCHEDULED', 'UNKNOWN']),
1212
})
1313

1414
class ProviderMaintenanceList(PluginTemplateExtension):
@@ -17,7 +17,7 @@ class ProviderMaintenanceList(PluginTemplateExtension):
1717
def left_page(self):
1818

1919
return self.render('netbox_circuitmaintenance/providermaintenance_include.html', extra_context={
20-
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(circuitmaintenance__provider=self.context['object'], circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RESCHEDULED', 'UNKNOWN']),
20+
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(circuitmaintenance__provider=self.context['object'], circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RE-SCHEDULED', 'UNKNOWN']),
2121
})
2222

2323
class SiteMaintenanceList(PluginTemplateExtension):
@@ -26,7 +26,7 @@ class SiteMaintenanceList(PluginTemplateExtension):
2626
def left_page(self):
2727

2828
return self.render('netbox_circuitmaintenance/providermaintenance_include.html', extra_context={
29-
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(Q(circuit__termination_a__site=self.context['object']) | Q(circuit__termination_z__site=self.context['object']), circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RESCHEDULED', 'UNKNOWN']),
29+
'circuitmaintenance': CircuitMaintenanceImpact.objects.filter(Q(circuit__termination_a__site=self.context['object']) | Q(circuit__termination_z__site=self.context['object']), circuitmaintenance__status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RE-SCHEDULED', 'UNKNOWN']),
3030
})
3131

3232

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% extends 'generic/_base.html' %}
2+
{% load buttons %}
3+
{% load render_table from django_tables2 %}
4+
{% load static %}
5+
{% load perms %}
6+
{% load helpers %}
7+
8+
{% block title %}Circuit Maintenance Schedule{% endblock %}
9+
10+
{% block controls %}
11+
<div class="controls">
12+
<div class="control-group">
13+
<a id="btnPrevMonth" class="btn btn-info" href="{{basepath}}?month={{prev_month}}&year={{prev_year}}">
14+
<i class="mdi mdi-arrow-left"></i>
15+
Previous Month
16+
</a>
17+
{% if month != this_month or year != this_year%}
18+
<a id="btnPrevMonth" class="btn btn-info" href="{{basepath}}?month={{this_month}}&year={{this_year}}">
19+
Today
20+
</a>
21+
{% endif %}
22+
<a id="btnNextMonth" class="btn btn-info" href="{{basepath}}?month={{next_month}}&year={{next_year}}">
23+
<i class="mdi mdi-arrow-right"></i>
24+
Next Month
25+
</a>
26+
</div>
27+
</div>
28+
{% endblock controls %}
29+
30+
{% block content %}
31+
{{ calendar }}
32+
{% endblock content %}
33+
34+
{% block javascript %}
35+
36+
{% endblock javascript %}

netbox_circuitmaintenance/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
path('circuitmaintenance/<int:pk>/', views.CircuitMaintenanceView.as_view(), name='circuitmaintenance'),
1616
path('circuitnotification/<int:pk>/', views.CircuitMaintenanceNotificationView.as_view(), name='circuitnotification'),
17-
path('maintenanceschedule/', views.CircuitMaintenanceScheduleView.as_view(), name='maintenanceschedule'),
1817

1918
path('circuitmaintenance/<int:pk>/edit/', views.CircuitMaintenanceEditView.as_view(), name='circuitmaintenance_edit'),
2019
path('circuitimpact/<int:pk>/edit/', views.CircuitMaintenanceImpactEditView.as_view(), name='circuitimpact_edit'),
@@ -31,5 +30,7 @@
3130
'model': models.CircuitMaintenanceImpact
3231
}),
3332

33+
path('maintenanceschedule/', views.CircuitMaintenanceScheduleView.as_view(), name='maintenanceschedule'),
34+
3435

3536
)

netbox_circuitmaintenance/views.py

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
from netbox.views import generic
33
from django.db.models import Count
44
from . import forms, models, tables, filtersets
5+
from django.views.generic import View
6+
from django.contrib.auth.mixins import PermissionRequiredMixin
7+
from django.shortcuts import render
8+
import datetime
9+
import calendar
10+
from django.utils.safestring import mark_safe
11+
from django.db.models import Q
12+
from django.conf import settings
513

614
# Circuit Maintenance Views
715
class CircuitMaintenanceView(generic.ObjectView):
@@ -55,6 +63,139 @@ class CircuitMaintenanceNotificationsDeleteView(generic.ObjectDeleteView):
5563
class CircuitMaintenanceNotificationView(generic.ObjectView):
5664
queryset = models.CircuitMaintenanceNotifications.objects.all()
5765

66+
67+
class Calendar(calendar.HTMLCalendar):
68+
def __init__(self, year=None, month=None):
69+
self.year = year
70+
self.month = month
71+
super(Calendar, self).__init__()
72+
73+
def suffix(self, day):
74+
if 4 <= day <= 20 or 24 <= day <= 30:
75+
return "th"
76+
else:
77+
return ["st", "nd", "rd"][day % 10 - 1]
78+
79+
def custom_strftime(self, format, t):
80+
return t.strftime(format).replace('SU', str(t.day) + self.suffix(t.day))
81+
82+
def formatmonthname(self, theyear, themonth) :
83+
return f"<h1>{calendar.month_name[themonth]} {theyear}</h1>"
84+
85+
def prev_month(self,month):
86+
if month == 1:
87+
return 12
88+
else:
89+
return month-1
90+
91+
def next_month(self, month):
92+
if month == 12:
93+
return 1
94+
else:
95+
return month+1
96+
97+
def prev_year(self, month, year):
98+
if month == 1:
99+
return year-1
100+
elif month == 12:
101+
return year+1
102+
else:
103+
return year
104+
105+
def next_year(self, month, year):
106+
if month == 1:
107+
return year-1
108+
elif month == 12:
109+
return year+1
110+
else:
111+
return year
112+
113+
def formatday(self, day, weekday, events):
114+
"""
115+
Return a day as a table cell.
116+
"""
117+
events_from_day = events.filter(Q(start__day=day) | Q(end__day=day))
118+
events_html = "<ul>"
119+
for event in events_from_day:
120+
if events_html != '<ul>':
121+
events_html += '<br><br>'
122+
123+
# Format time of the event
124+
if self.custom_strftime('SU', event.start) == self.custom_strftime('SU', event.end):
125+
event_time = f'{self.custom_strftime('%H:%M', event.start)} - {self.custom_strftime('%H:%M', event.end)}'
126+
else:
127+
event_time = f'{self.custom_strftime('SU %H:%M', event.start)} - {self.custom_strftime('SU %H:%M', event.end)}'
128+
129+
# Add the event to the day
130+
events_html += f'<span class="badge text-bg-{event.get_status_color()}"><a href="{event.get_absolute_url()}">{event_time}<br>{event.name} <br>{event.provider} - {event.status}<br>{event.impact_count} Impacted</a></span>'
131+
events_html += "</ul>"
132+
133+
if day == 0:
134+
return '<td>&nbsp;</td>'
135+
else:
136+
return '<td class="%s"><strong>%d</strong>%s</td>' % (self.cssclasses[weekday], day, events_html)
137+
138+
def formatweek(self, theweek, events):
139+
"""
140+
Return a complete week as a table row.
141+
"""
142+
week = ''.join(self.formatday(d, wd, events) for (d, wd) in theweek)
143+
return '<tr>%s</tr>' % week
144+
145+
def formatmonth(self, theyear, themonth):
146+
events = models.CircuitMaintenance.objects.filter(Q(start__month=themonth) | Q(end__month=themonth)).annotate(impact_count=Count('impact'))
147+
v = []
148+
a = v.append
149+
a('<table class="table">')
150+
a('\n')
151+
a(self.formatmonthname(theyear, themonth))
152+
a('\n')
153+
a(self.formatweekheader())
154+
a('\n')
155+
for week in self.monthdays2calendar(theyear, themonth):
156+
a(self.formatweek(week, events))
157+
a('\n')
158+
a('</table>')
159+
a('\n')
160+
return ''.join(v)
161+
162+
58163
# CircuitMaintenanceSchedule
59-
class CircuitMaintenanceScheduleView(generic.ObjectView):
60-
queryset = models.CircuitMaintenance.objects.all()
164+
class CircuitMaintenanceScheduleView(View):
165+
template_name = 'netbox_circuitmaintenance/calendar.html'
166+
167+
168+
def get(self, request):
169+
170+
curr_month = datetime.date.today()
171+
172+
# Check if we have a month and year in the URL
173+
if request.GET and 'month' in request.GET:
174+
month = int(request.GET["month"])
175+
year = int(request.GET["year"])
176+
177+
else:
178+
month = curr_month.month
179+
year = curr_month.year
180+
181+
# Load calendar
182+
cal = Calendar()
183+
html_calendar = cal.formatmonth(year, month)
184+
html_calendar = html_calendar.replace('<td ', '<td width="300" height="150"')
185+
186+
return render(
187+
request,
188+
self.template_name,
189+
{
190+
"calendar": mark_safe(html_calendar),
191+
"this_month": curr_month.month,
192+
"this_year": curr_month.year,
193+
"month": month,
194+
"year": year,
195+
"next_month": cal.next_month(month),
196+
"next_year": cal.next_year(month, year),
197+
"prev_month": cal.prev_month(month),
198+
"prev_year": cal.prev_year(month, year),
199+
"basepath": settings.BASE_PATH,
200+
}
201+
)

netbox_circuitmaintenance/widgets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ReminderWidget(DashboardWidget):
1616
def render(self, request):
1717

1818
return render_to_string(self.template_name, {
19-
'circuitmaintenance': CircuitMaintenance.objects.filter(status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RESCHEDULED', 'UNKNOWN']).annotate(
19+
'circuitmaintenance': CircuitMaintenance.objects.filter(status__in=['TENTATIVE', 'CONFIRMED', 'IN-PROCESS', 'RE-SCHEDULED', 'UNKNOWN']).annotate(
2020
impact_count=Count('impact')
2121
),
2222
})

0 commit comments

Comments
 (0)