mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 05:22:16 +00:00
Initial work at reimplementing the calendar in python
Buhby fullcalendar
This commit is contained in:
@@ -401,6 +401,12 @@ class BaseEvent(models.Model, RevisionMixin):
|
||||
errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
|
||||
return errdict
|
||||
|
||||
@property
|
||||
def get_html_url(self):
|
||||
return f'<a href="{self.get_edit_url()}"><span class="badge badge-{self.color}">{self}</span></a>'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.display_id}: {self.name}"
|
||||
|
||||
@reversion.register(follow=['items'])
|
||||
class Event(BaseEvent):
|
||||
@@ -502,13 +508,29 @@ class Event(BaseEvent):
|
||||
else:
|
||||
return bool(self.purchase_order)
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
if self.cancelled:
|
||||
return "secondary"
|
||||
elif not self.is_rig:
|
||||
return "info"
|
||||
elif not self.mic:
|
||||
return "danger"
|
||||
elif self.confirmed and self.authorised:
|
||||
if self.dry_hire or self.riskassessment:
|
||||
return "success"
|
||||
else:
|
||||
return "warning"
|
||||
else:
|
||||
return "warning"
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('event_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.display_id}: {self.name}"
|
||||
def get_edit_url(self):
|
||||
return reverse('event_update', kwargs={'pk': self.pk})
|
||||
|
||||
def clean(self):
|
||||
errdict = super.clean()
|
||||
@@ -581,6 +603,13 @@ class Subhire(BaseEvent):
|
||||
def display_id(self):
|
||||
return f"S{self.pk:05d}"
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return "purple"
|
||||
|
||||
def get_edit_url(self):
|
||||
return reverse('subhire_update', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
class InvoiceManager(models.Manager):
|
||||
def outstanding_invoices(self):
|
||||
|
||||
@@ -4,194 +4,56 @@
|
||||
{% block title %}Calendar{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'css/main.css' %}" rel='stylesheet' />
|
||||
{% endblock %}
|
||||
<style>
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/moment.js' %}"></script>
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
<script>
|
||||
viewToUrl = {
|
||||
'timeGridWeek':'week',
|
||||
'timeGridDay':'day',
|
||||
'dayGridMonth':'month'
|
||||
}
|
||||
viewFromUrl = {
|
||||
'week':'timeGridWeek',
|
||||
'day':'timeGridDay',
|
||||
'month':'dayGridMonth'
|
||||
}
|
||||
var calendar; //Need to access it from jquery ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
.calendar {
|
||||
width: 98%;
|
||||
margin: auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
themeSystem: 'bootstrap',
|
||||
aspectRatio: 1.5,
|
||||
eventTimeFormat: {
|
||||
'hour': '2-digit',
|
||||
'minute': '2-digit',
|
||||
'hour12': false
|
||||
},
|
||||
headerToolbar: false,
|
||||
editable: false,
|
||||
dayMaxEventRows: true, // allow "more" link when too many events
|
||||
events: function(fetchInfo, successCallback, failureCallback) {
|
||||
$.ajax({
|
||||
url: '/api/event',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
start: moment(fetchInfo.startStr).format("YYYY-MM-DD[T]HH:mm:ss"),
|
||||
end: moment(fetchInfo.endStr).format("YYYY-MM-DD[T]HH:mm:ss")
|
||||
},
|
||||
success: function(doc) {
|
||||
var events = [];
|
||||
colours = {
|
||||
'Provisional': '#FFE89B',
|
||||
'Confirmed': '#3AB54A' ,
|
||||
'Booked': '#3AB54A' ,
|
||||
'Cancelled': 'grey' ,
|
||||
'non-rig': '#25AAE2'
|
||||
};
|
||||
$(doc).each(function() {
|
||||
end = $(this).attr('latest')
|
||||
allDay = false
|
||||
if(end.indexOf("T") < 0){ //If latest does not contain a time
|
||||
end = moment(end + " 23:59").format("YYYY-MM-DD[T]HH:mm:ss")
|
||||
allDay = true
|
||||
}
|
||||
.calendar tr, .calendar td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
thisEvent = {
|
||||
'start': $(this).attr('earliest'),
|
||||
'end': end,
|
||||
'className': 'modal-href',
|
||||
'title': $(this).attr('title'),
|
||||
'url': $(this).attr('url'),
|
||||
'allDay': allDay
|
||||
}
|
||||
.calendar th {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
if($(this).attr('is_rig')===true || $(this).attr('status') === "Cancelled"){
|
||||
thisEvent['color'] = colours[$(this).attr('status')];
|
||||
}else{
|
||||
thisEvent['color'] = colours['non-rig'];
|
||||
}
|
||||
events.push(thisEvent);
|
||||
});
|
||||
successCallback(events);
|
||||
}
|
||||
});
|
||||
},
|
||||
datesSet: function(info) {
|
||||
var view = info.view;
|
||||
// Set the title of the view
|
||||
$('#calendar-header').text(view.title);
|
||||
.calendar td {
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
padding: 20px 0px 0px 5px;
|
||||
}
|
||||
|
||||
// Enable/Disable "Today" button as required
|
||||
let $today = $('#today-button');
|
||||
if(moment().isBetween(view.currentStart, view.currentEnd)){
|
||||
//Today is within the current view
|
||||
$today.prop('disabled', true);
|
||||
}else{
|
||||
$today.prop('disabled', false);
|
||||
}
|
||||
.month {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
// Set active view select button
|
||||
let $month = $('#month-button');
|
||||
let $week = $('#week-button');
|
||||
let $day = $('#day-button');
|
||||
switch(view.type){
|
||||
case 'dayGridMonth':
|
||||
$month.addClass('active');
|
||||
$week.removeClass('active');
|
||||
$day.removeClass('active');
|
||||
break;
|
||||
.date {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
case 'timeGridWeek':
|
||||
$month.removeClass('active');
|
||||
$week.addClass('active');
|
||||
$day.removeClass('active');
|
||||
break;
|
||||
ul {
|
||||
height: 100%;
|
||||
padding: 0px 5px 0px 20px;
|
||||
}
|
||||
|
||||
case 'timeGridDay':
|
||||
$month.removeClass('active');
|
||||
$week.removeClass('active');
|
||||
$day.addClass('active');
|
||||
break;
|
||||
}
|
||||
history.replaceState(null,null,"{% url 'web_calendar' %}"+viewToUrl[view.type]+'/'+moment(view.currentStart).format('YYYY-MM-DD')+'/');
|
||||
}
|
||||
});
|
||||
calendar.render();
|
||||
});
|
||||
$(document).ready(function() {
|
||||
// set some button listeners
|
||||
$('#next-button').click(function(){ calendar.next(); });
|
||||
$('#prev-button').click(function(){ calendar.prev(); });
|
||||
$('#today-button').click(function(){ calendar.today(); });
|
||||
$('#month-button').click(function(){ calendar.changeView('dayGridMonth'); });
|
||||
$('#week-button').click(function(){ calendar.changeView('timeGridWeek'); });
|
||||
$('#day-button').click(function(){ calendar.changeView('timeGridDay'); });
|
||||
$('#go-to-date-input').change(function(){
|
||||
if(moment($('#go-to-date-input').val()).isValid()){
|
||||
$('#go-to-date-button').prop('disabled', false);
|
||||
} else{
|
||||
$('#go-to-date-button').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
$('#go-to-date-button').click(function(){
|
||||
day = moment($('#go-to-date-input').val());
|
||||
if(day.isValid()){
|
||||
calendar.gotoDate(day.format("YYYY-MM-DD"));
|
||||
} else{
|
||||
alert('Invalid Date');
|
||||
}
|
||||
});
|
||||
{% if view and date %}
|
||||
// Go to the initial settings, if they're valid
|
||||
view = viewFromUrl['{{view}}'];
|
||||
calendar.changeView(view);
|
||||
day = moment('{{date}}');
|
||||
if(day.isValid()){
|
||||
calendar.gotoDate(day.format("YYYY-MM-DD"));
|
||||
} else{
|
||||
console.log('Supplied date is invalid - using default')
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
a {
|
||||
color: #17a2b8;
|
||||
}
|
||||
.badge-purple, .bg-purple {
|
||||
background-color: #800080 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="pull-left">
|
||||
<span id="calendar-header" class="h2"></span>
|
||||
</div>
|
||||
<div class="form-inline float-right btn-page my-3">
|
||||
<div class="input-group mx-2">
|
||||
<input type="date" class="form-control" id="go-to-date-input" placeholder="Go to date...">
|
||||
<span class="input-group-append">
|
||||
<button class="btn btn-success" id="go-to-date-button" type="button" disabled>Go!</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group mx-2">
|
||||
<button type="button" class="btn btn-primary" id="today-button">Today</button>
|
||||
</div>
|
||||
<div class="btn-group mx-2">
|
||||
<button type="button" class="btn btn-secondary" id="prev-button"><span class="fas fa-chevron-left"></span></button>
|
||||
<button type="button" class="btn btn-secondary" id="next-button"><span class="fas fa-chevron-right"></span></button>
|
||||
</div>
|
||||
<div class="btn-group ml-2">
|
||||
<button type="button" class="btn btn-light" id="month-button">Month</button>
|
||||
<button type="button" class="btn btn-light" id="week-button">Week</button>
|
||||
<button type="button" class="btn btn-light" id="day-button">Day</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div id='calendar'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<a class="btn btn-info left" href="{% url 'web_calendar' %}?{{ prev_month }}"> Previous Month </a>
|
||||
<a class="btn btn-info right" href="{% url 'web_calendar' %}?{{ next_month }}"> Next Month </a>
|
||||
</div>
|
||||
{{ calendar }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,21 +12,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in events %}
|
||||
<tr class="{% if event.cancelled %}
|
||||
table-secondary
|
||||
{% elif not event.is_rig %}
|
||||
table-info
|
||||
{% elif not event.mic %}
|
||||
table-danger
|
||||
{% elif event.confirmed and event.authorised %}
|
||||
{% if event.dry_hire or event.riskassessment %}
|
||||
table-success
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
||||
<tr class="table-{{event.color}}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
||||
<!---Number-->
|
||||
<th scope="row" id="event_number">{{ event.display_id }}</th>
|
||||
<!--Dates & Times-->
|
||||
|
||||
@@ -43,12 +43,8 @@ urlpatterns = [
|
||||
|
||||
# Rigboard
|
||||
path('rigboard/', login_required(views.RigboardIndex.as_view()), name='rigboard'),
|
||||
path('rigboard/calendar/', login_required()(views.WebCalendar.as_view()),
|
||||
re_path(r'^rigboard/calendar/$', login_required()(views.WebCalendar.as_view()),
|
||||
name='web_calendar'),
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||
login_required()(views.WebCalendar.as_view()), name='web_calendar'),
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||
login_required()(views.WebCalendar.as_view()), name='web_calendar'),
|
||||
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||
|
||||
|
||||
@@ -77,7 +73,7 @@ urlpatterns = [
|
||||
path('subhire/create/', permission_required_with_403('RIGS.add_event')(views.SubhireCreate.as_view()),
|
||||
name='subhire_create'),
|
||||
path('subhire/<int:pk>/edit', views.SubhireEdit.as_view(),
|
||||
name='subhire_edit'),
|
||||
name='subhire_update'),
|
||||
|
||||
|
||||
# Event H&S
|
||||
|
||||
65
RIGS/utils.py
Normal file
65
RIGS/utils.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from datetime import datetime, timedelta, date
|
||||
import calendar
|
||||
from calendar import HTMLCalendar
|
||||
from RIGS.models import BaseEvent, Event, Subhire
|
||||
|
||||
class Calendar(HTMLCalendar):
|
||||
def __init__(self, year=None, month=None):
|
||||
self.year = year
|
||||
self.month = month
|
||||
super(Calendar, self).__init__()
|
||||
|
||||
# formats a day as a td
|
||||
# filter events by day
|
||||
def formatday(self, day, events, subhires):
|
||||
events_per_day = events.filter(start_date__day=day)
|
||||
subhires_per_day = subhires.filter(start_date__day=day)
|
||||
d = ''
|
||||
for event in events_per_day:
|
||||
d += f'{event.get_html_url}<br>'
|
||||
for subhire in subhires_per_day:
|
||||
d += f'{subhire.get_html_url}<br>'
|
||||
|
||||
if day != 0:
|
||||
return f"<td><span class='date'>{day}</span><ul> {d} </ul></td>"
|
||||
return '<td></td>'
|
||||
|
||||
# formats a week as a tr
|
||||
def formatweek(self, theweek, events, subhires):
|
||||
week = ''
|
||||
for d, weekday in theweek:
|
||||
week += self.formatday(d, events, subhires)
|
||||
return f'<tr> {week} </tr>'
|
||||
|
||||
# formats a month as a table
|
||||
# filter events by year and month
|
||||
def formatmonth(self, withyear=True):
|
||||
events = Event.objects.filter(start_date__year=self.year, start_date__month=self.month)
|
||||
subhires = Subhire.objects.filter(start_date__year=self.year, start_date__month=self.month)
|
||||
|
||||
cal = f'<table border="0" cellpadding="0" cellspacing="0" class="calendar">\n'
|
||||
cal += f'{self.formatmonthname(self.year, self.month, withyear=withyear)}\n'
|
||||
cal += f'{self.formatweekheader()}\n'
|
||||
for week in self.monthdays2calendar(self.year, self.month):
|
||||
cal += f'{self.formatweek(week, events, subhires)}\n'
|
||||
return cal
|
||||
|
||||
|
||||
def get_date(req_day):
|
||||
if req_day:
|
||||
year, month = (int(x) for x in req_day.split('-'))
|
||||
return date(year, month, day=1)
|
||||
return datetime.today()
|
||||
|
||||
def prev_month(d):
|
||||
first = d.replace(day=1)
|
||||
prev_month = first - timedelta(days=1)
|
||||
month = 'month=' + str(prev_month.year) + '-' + str(prev_month.month)
|
||||
return month
|
||||
|
||||
def next_month(d):
|
||||
days_in_month = calendar.monthrange(d.year, d.month)[1]
|
||||
last = d.replace(day=days_in_month)
|
||||
next_month = last + timedelta(days=1)
|
||||
month = 'month=' + str(next_month.year) + '-' + str(next_month.month)
|
||||
return month
|
||||
@@ -17,12 +17,13 @@ from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.html import mark_safe
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
|
||||
from PyRIGS import decorators
|
||||
from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin, PrintView, get_related
|
||||
from RIGS import models, forms
|
||||
from RIGS import models, forms, utils
|
||||
|
||||
__author__ = 'ghost'
|
||||
|
||||
@@ -40,14 +41,24 @@ class RigboardIndex(generic.TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
class WebCalendar(generic.TemplateView):
|
||||
class WebCalendar(generic.ListView):
|
||||
model = models.Event
|
||||
template_name = 'calendar.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['view'] = kwargs.get('view', '')
|
||||
context['date'] = kwargs.get('date', '')
|
||||
# context['page_title'] = "Calendar"
|
||||
|
||||
# use today's date for the calendar
|
||||
d = utils.get_date(self.request.GET.get('month', None))
|
||||
context['prev_month'] = utils.prev_month(d)
|
||||
context['next_month'] = utils.next_month(d)
|
||||
|
||||
# Instantiate our calendar class with today's year and date
|
||||
cal = utils.Calendar(d.year, d.month)
|
||||
|
||||
# Call the formatmonth method, which returns our calendar as a table
|
||||
html_cal = cal.formatmonth(withyear=True)
|
||||
context['calendar'] = mark_safe(html_cal)
|
||||
return context
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user