mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-03-05 19:48:23 +00:00
Merge branch 'revision-view' into hotfixes
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
import cStringIO as StringIO
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import connection
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.views import generic
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib import messages
|
||||
import datetime
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
|
||||
@@ -33,6 +39,34 @@ class InvoiceIndex(generic.ListView):
|
||||
class InvoiceDetail(generic.DetailView):
|
||||
model = models.Invoice
|
||||
|
||||
class InvoicePrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
invoice = get_object_or_404(models.Invoice, pk=pk)
|
||||
object = invoice.event
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
copies = ('TEC', 'Client')
|
||||
context = RequestContext(request, {
|
||||
'object': object,
|
||||
'fonts': {
|
||||
'opensans': {
|
||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||
}
|
||||
},
|
||||
'invoice':invoice,
|
||||
})
|
||||
|
||||
rml = template.render(context)
|
||||
buffer = StringIO.StringIO()
|
||||
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
|
||||
pdfData = buffer.read()
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, object.name)
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
class InvoiceVoid(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
|
||||
@@ -18,6 +18,10 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
|
||||
phone = forms.CharField(required=False, max_length=13)
|
||||
captcha = ReCaptchaField()
|
||||
|
||||
class Meta:
|
||||
model = models.Profile
|
||||
fields = ('first_name','last_name','initials','phone')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
Validate that the supplied initials are unique.
|
||||
|
||||
34
RIGS/ical.py
34
RIGS/ical.py
@@ -2,18 +2,26 @@ from RIGS import models, forms
|
||||
from django_ical.views import ICalFeed
|
||||
from django.db.models import Q
|
||||
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
import datetime
|
||||
import datetime, pytz
|
||||
|
||||
class CalendarICS(ICalFeed):
|
||||
"""
|
||||
A simple event calender
|
||||
"""
|
||||
#Metadata which is passed on to clients
|
||||
product_id = 'PyRIGS'
|
||||
title = 'PyRIGS Calendar'
|
||||
product_id = 'RIGS'
|
||||
title = 'RIGS Calendar'
|
||||
timezone = settings.TIME_ZONE
|
||||
file_name = "rigs.ics"
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
timezone.activate(timezone.UTC)
|
||||
return super(CalendarICS, self).get(*args, **kwargs)
|
||||
|
||||
|
||||
def items(self):
|
||||
#include events from up to 1 year ago
|
||||
start = datetime.datetime.now() - datetime.timedelta(days=365)
|
||||
@@ -45,11 +53,13 @@ class CalendarICS(ICalFeed):
|
||||
def item_start_datetime(self, item):
|
||||
#set start date to the earliest defined time for the event
|
||||
if item.meet_at:
|
||||
startDateTime = item.meet_at.replace(tzinfo=None)
|
||||
startDateTime = item.meet_at
|
||||
elif item.access_at:
|
||||
startDateTime = item.access_at.replace(tzinfo=None)
|
||||
startDateTime = item.access_at
|
||||
elif item.has_start_time:
|
||||
startDateTime = datetime.datetime.combine(item.start_date,item.start_time).replace(tzinfo=None)
|
||||
startDateTime = datetime.datetime.combine(item.start_date,item.start_time)
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
startDateTime = tz.normalize(tz.localize(startDateTime)).astimezone(pytz.timezone(self.timezone))
|
||||
else:
|
||||
startDateTime = item.start_date
|
||||
|
||||
@@ -64,9 +74,11 @@ class CalendarICS(ICalFeed):
|
||||
endDateTime = item.end_date
|
||||
|
||||
if item.has_start_time and item.has_end_time: # don't allow an event with specific end but no specific start
|
||||
endDateTime = datetime.datetime.combine(endDateTime,item.end_time).replace(tzinfo=None)
|
||||
endDateTime = datetime.datetime.combine(endDateTime,item.end_time)
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
endDateTime = tz.normalize(tz.localize(endDateTime)).astimezone(pytz.timezone(self.timezone))
|
||||
elif item.has_end_time: # if there's a start time specified then an end time should also be specified
|
||||
endDateTime = datetime.datetime.combine(endDateTime+datetime.timedelta(days=1),datetime.time(00, 00)).replace(tzinfo=None)
|
||||
endDateTime = datetime.datetime.combine(endDateTime+datetime.timedelta(days=1),datetime.time(00, 00))
|
||||
#elif item.end_time: # end time but no start time - this is weird - don't think ICS will like it so ignoring
|
||||
# do nothing
|
||||
|
||||
@@ -90,7 +102,7 @@ class CalendarICS(ICalFeed):
|
||||
|
||||
desc += '\n'
|
||||
if item.meet_at:
|
||||
desc += 'Crew Meet = ' + item.meet_at.strftime('%Y-%m-%d %H:%M') + (('('+item.meet_info+')') if item.meet_info else '---') + '\n'
|
||||
desc += 'Crew Meet = ' + (item.meet_at.strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
|
||||
if item.access_at:
|
||||
desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n'
|
||||
if item.start_date:
|
||||
@@ -104,7 +116,7 @@ class CalendarICS(ICalFeed):
|
||||
if item.notes:
|
||||
desc += 'Notes:\n'+item.notes+'\n\n'
|
||||
|
||||
base_url = "https://pyrigs.nottinghamtec.co.uk"
|
||||
base_url = "http://rigs.nottinghamtec.co.uk"
|
||||
desc += 'URL = '+base_url+str(item.get_absolute_url())
|
||||
|
||||
return desc
|
||||
@@ -118,7 +130,7 @@ class CalendarICS(ICalFeed):
|
||||
# return ''
|
||||
|
||||
def item_updated(self, item): # some ical clients will display this
|
||||
return item.last_edited_at.replace(tzinfo=None)
|
||||
return item.last_edited_at
|
||||
|
||||
def item_guid(self, item): # use the rig-id as the ical unique event identifier
|
||||
return item.pk
|
||||
@@ -78,6 +78,9 @@ class Person(models.Model, RevisionMixin):
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_person', 'Can view Persons'),
|
||||
@@ -114,6 +117,9 @@ class Organisation(models.Model, RevisionMixin):
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_organisation', 'Can view Organisations'),
|
||||
@@ -176,6 +182,9 @@ class Venue(models.Model, RevisionMixin):
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_venue', 'Can view Venues'),
|
||||
|
||||
13
RIGS/regbackend.py
Normal file
13
RIGS/regbackend.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from RIGS.models import Profile
|
||||
from RIGS.forms import ProfileRegistrationFormUniqueEmail
|
||||
|
||||
def user_created(sender, user, request, **kwargs):
|
||||
form = ProfileRegistrationFormUniqueEmail(request.POST)
|
||||
user.first_name = form.data['first_name']
|
||||
user.last_name = form.data['last_name']
|
||||
user.initials = form.data['initials']
|
||||
user.phone = form.data['phone']
|
||||
user.save()
|
||||
|
||||
from registration.signals import user_registered
|
||||
user_registered.connect(user_created)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
86
RIGS/static/js/moment-twitter.js
Normal file
86
RIGS/static/js/moment-twitter.js
Normal file
@@ -0,0 +1,86 @@
|
||||
(function() {
|
||||
var day, formats, hour, initialize, minute, second, week;
|
||||
|
||||
second = 1e3;
|
||||
|
||||
minute = 6e4;
|
||||
|
||||
hour = 36e5;
|
||||
|
||||
day = 864e5;
|
||||
|
||||
week = 6048e5;
|
||||
|
||||
formats = {
|
||||
seconds: {
|
||||
short: 's',
|
||||
long: ' sec'
|
||||
},
|
||||
minutes: {
|
||||
short: 'm',
|
||||
long: ' min'
|
||||
},
|
||||
hours: {
|
||||
short: 'h',
|
||||
long: ' hr'
|
||||
},
|
||||
days: {
|
||||
short: 'd',
|
||||
long: ' day'
|
||||
}
|
||||
};
|
||||
|
||||
initialize = function(moment) {
|
||||
var twitterFormat;
|
||||
twitterFormat = function(format) {
|
||||
var diff, num, unit, unitStr;
|
||||
diff = Math.abs(this.diff(moment()));
|
||||
unit = null;
|
||||
num = null;
|
||||
if (diff <= second) {
|
||||
unit = 'seconds';
|
||||
num = 1;
|
||||
} else if (diff < minute) {
|
||||
unit = 'seconds';
|
||||
} else if (diff < hour) {
|
||||
unit = 'minutes';
|
||||
} else if (diff < day) {
|
||||
unit = 'hours';
|
||||
} else if (format === 'short') {
|
||||
if (diff < week) {
|
||||
unit = 'days';
|
||||
} else {
|
||||
return this.format('M/D/YY');
|
||||
}
|
||||
} else {
|
||||
return this.format('MMM D');
|
||||
}
|
||||
if (!(num && unit)) {
|
||||
num = moment.duration(diff)[unit]();
|
||||
}
|
||||
unitStr = unit = formats[unit][format];
|
||||
if (format === 'long' && num > 1) {
|
||||
unitStr += 's';
|
||||
}
|
||||
return num + unitStr;
|
||||
};
|
||||
moment.fn.twitterLong = function() {
|
||||
return twitterFormat.call(this, 'long');
|
||||
};
|
||||
moment.fn.twitter = moment.fn.twitterShort = function() {
|
||||
return twitterFormat.call(this, 'short');
|
||||
};
|
||||
return moment;
|
||||
};
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('moment-twitter', ['moment'], function(moment) {
|
||||
return this.moment = initialize(moment);
|
||||
});
|
||||
} else if (typeof module !== 'undefined') {
|
||||
module.exports = initialize(require('moment'));
|
||||
} else if (typeof window !== "undefined" && window.moment) {
|
||||
this.moment = initialize(this.moment);
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
@@ -7,6 +7,9 @@
|
||||
@import "jq-ui-bootstrap/_menu";
|
||||
@import "jq-ui-bootstrap/_tooltip";
|
||||
|
||||
@import "compass/css3/animation";
|
||||
@import "compass/css3/transform";
|
||||
|
||||
body, .pad-top {
|
||||
padding-top: 50px;
|
||||
}
|
||||
@@ -17,7 +20,9 @@ body, .pad-top {
|
||||
|
||||
#userdropdown > li {
|
||||
padding: 0 0.3em;
|
||||
}
|
||||
|
||||
#userdropdown > li, #activity {
|
||||
.media-object {
|
||||
max-width: 3em;
|
||||
}
|
||||
@@ -63,3 +68,72 @@ textarea {
|
||||
.modal-dialog {
|
||||
z-index: inherit; // bug fix introduced in 52682ce
|
||||
}
|
||||
|
||||
.panel-default {
|
||||
.default {
|
||||
background-color: $panel-default-heading-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
position: relative;
|
||||
margin: 30px auto 0;
|
||||
|
||||
.circle {
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: 5px solid rgba(0,183,229,0.9);
|
||||
opacity: .9;
|
||||
border-right: 5px solid rgba(0,0,0,0);
|
||||
border-left: 5px solid rgba(0,0,0,0);
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 0 35px #2187e7;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 auto;
|
||||
@include animation(spinPulse 1s infinite ease-in-out);
|
||||
}
|
||||
|
||||
.circle1 {
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: 5px solid rgba(0,183,229,0.9);
|
||||
opacity: .9;
|
||||
border-left: 5px solid rgba(0,0,0,0);
|
||||
border-right: 5px solid rgba(0,0,0,0);
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 0 15px #2187e7;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
top: -40px;
|
||||
@include animation(spinoffPulse 1s infinite linear);
|
||||
}
|
||||
|
||||
@include keyframes(spinPulse) {
|
||||
0% {
|
||||
@include rotate(160deg);
|
||||
opacity: 0;
|
||||
box-shadow: 0 0 1px #2187e7;
|
||||
}
|
||||
|
||||
50% {
|
||||
@include rotate(145deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
@include rotate(-320deg);
|
||||
opacity: 0;
|
||||
};
|
||||
}
|
||||
|
||||
@include keyframes(spinoffPulse) {
|
||||
0% {
|
||||
@include rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
@include rotate(360deg);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
66
RIGS/templates/RIGS/activity_feed.html
Normal file
66
RIGS/templates/RIGS/activity_feed.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{% load static %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script src="{% static "js/popover.js" %}"></script>
|
||||
<script src="{% static "js/moment.min.js" %}"></script>
|
||||
<script src="{% static "js/moment-twitter.js" %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover().click(function(){
|
||||
if($(this).attr('href')){
|
||||
window.location.href = $(this).attr('href');
|
||||
}
|
||||
});
|
||||
|
||||
// This keeps timeago values correct, but uses an insane amount of resources
|
||||
// $(function () {
|
||||
// setInterval(function() {
|
||||
// $('.date').each(function (index, dateElem) {
|
||||
// var $dateElem = $(dateElem);
|
||||
// var formatted = moment($dateElem.attr('data-date')).fromNow();
|
||||
// $dateElem.text(formatted);
|
||||
// })
|
||||
// });
|
||||
// }, 10000);
|
||||
moment().twitter();
|
||||
|
||||
})
|
||||
$(document).ready(function() {
|
||||
$(function () {
|
||||
$( "#activity" ).hide();
|
||||
$( "#activity" ).load( "{% url 'activity_feed' %}", function() {
|
||||
$('#activity_loading').slideUp('slow',function(){
|
||||
$('#activity').slideDown('slow');
|
||||
});
|
||||
|
||||
$('#activity [data-toggle="popover"]').popover();
|
||||
|
||||
$('.date').each(function (index, dateElem) {
|
||||
var $dateElem = $(dateElem);
|
||||
var formatted = moment($dateElem.attr('data-date'),"DD/MM/YYYY HH:mm").twitterLong();
|
||||
$dateElem.text(formatted);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Recent Changes</h4>
|
||||
</div>
|
||||
|
||||
<div class="list-group">
|
||||
<div id="activity_loading" class="list-group-item loading-animation">
|
||||
<div class="circle"></div>
|
||||
<div class="circle1"></div>
|
||||
</div>
|
||||
<div id="activity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
49
RIGS/templates/RIGS/activity_feed_data.html
Normal file
49
RIGS/templates/RIGS/activity_feed_data.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load paginator from filters %}
|
||||
{% load to_class_name from filters %}
|
||||
|
||||
{% block content %}
|
||||
{% if request.is_ajax %}
|
||||
<div class="list-group-item">
|
||||
<div class="media">
|
||||
{% for version in object_list %}
|
||||
|
||||
{% if not version.withPrevious %}
|
||||
{% if not forloop.first %}
|
||||
</div> {#/.media-body#}
|
||||
</div> {#/.media#}
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<div class="media">
|
||||
{% endif %}
|
||||
<div class="media-left">
|
||||
<a href="#">
|
||||
<img class="media-object img-rounded" src="{{ version.revision.user.profile_picture}}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<h5>{{ version.revision.user.name }}
|
||||
<span class="pull-right"><small><span class="date" data-date="{{version.revision.date_created}}"></span></small></span>
|
||||
</h5>
|
||||
|
||||
{% endif %}
|
||||
<p>
|
||||
<small>
|
||||
{% if version.old == None %}
|
||||
Created
|
||||
{% else %}
|
||||
Changed {% include 'RIGS/version_changes.html' %} in
|
||||
{% endif %}
|
||||
|
||||
{% include 'RIGS/object_button.html' with object=version.new %}
|
||||
</small>
|
||||
</p>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
88
RIGS/templates/RIGS/activity_table.html
Normal file
88
RIGS/templates/RIGS/activity_table.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
|
||||
{% load static %}
|
||||
{% load paginator from filters %}
|
||||
{% load to_class_name from filters %}
|
||||
|
||||
{% block title %}Rigboard Activity Stream{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script src="{% static "js/popover.js" %}"></script>
|
||||
<script src="{% static "js/moment.min.js" %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover().click(function(){
|
||||
if($(this).attr('href')){
|
||||
window.location.href = $(this).attr('href');
|
||||
}
|
||||
});
|
||||
|
||||
// This keeps timeago values correct, but uses an insane amount of resources
|
||||
// $(function () {
|
||||
// setInterval(function() {
|
||||
// $('.date').each(function (index, dateElem) {
|
||||
// var $dateElem = $(dateElem);
|
||||
// var formatted = moment($dateElem.attr('data-date')).fromNow();
|
||||
// $dateElem.text(formatted);
|
||||
// })
|
||||
// });
|
||||
// }, 10000);
|
||||
|
||||
|
||||
$('.date').each(function (index, dateElem) {
|
||||
var $dateElem = $(dateElem);
|
||||
var formatted = moment($dateElem.attr('data-date')).fromNow();
|
||||
$dateElem.text(formatted);
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h3>Rigboard Activity Stream</h3>
|
||||
</div>
|
||||
<div class="text-right col-sm-12">{% paginator %}</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Object</td>
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for version in object_list %}
|
||||
|
||||
<tr>
|
||||
<td>{{ version.revision.date_created }}</td>
|
||||
<td><a href="{{ version.new.get_absolute_url }}">{{version.new|to_class_name}} {{ version.new.pk|stringformat:"05d" }}</a></td>
|
||||
<td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
|
||||
<td>{{ version.revision.user.name }}</td>
|
||||
<td>
|
||||
{% if version.old == None %}
|
||||
Object Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %} </td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="align-right">{% paginator %}</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -227,8 +227,10 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>Last edited at {{ object.last_edited_at|date:"SHORT_DATETIME_FORMAT" }}
|
||||
by {{ object.last_edited_by.name }}.
|
||||
<div>
|
||||
<a href="{% url 'event_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -240,7 +242,9 @@
|
||||
{% block footer %}
|
||||
<div class="row">
|
||||
<div class="col-sm-10 align-left">
|
||||
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }} by {{ object.last_edited_by.name }}
|
||||
<a href="{% url 'event_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="pull-right">
|
||||
|
||||
@@ -22,17 +22,25 @@
|
||||
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
|
||||
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
|
||||
<paraStyle name="center" alignment="center"/>
|
||||
<paraStyle name="invoice-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
||||
|
||||
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
|
||||
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
|
||||
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
|
||||
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
||||
<paraStyle name="style.invoice_titles" fontName="OpenSans-Bold" fontSize="10" />
|
||||
<paraStyle name="style.invoice_numbers" fontName="OpenSans" fontSize="10" />
|
||||
|
||||
<blockTableStyle id="eventSpecifics">
|
||||
<blockValign value="top"/>
|
||||
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
|
||||
</blockTableStyle>
|
||||
|
||||
<blockTableStyle id="invoiceLayout">
|
||||
<blockValign value="top"/>
|
||||
|
||||
</blockTableStyle>
|
||||
|
||||
<blockTableStyle id="eventDetails">
|
||||
<blockValign value="top"/>
|
||||
<blockTopPadding start="0,0" stop="-1,0" length="0"/>
|
||||
@@ -91,7 +99,7 @@
|
||||
|
||||
|
||||
<setFont name="OpenSans" size="10" />
|
||||
<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>
|
||||
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
|
||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||
</pageGraphics>
|
||||
|
||||
@@ -101,7 +109,7 @@
|
||||
<pageTemplate id="Main">
|
||||
<pageGraphics>
|
||||
<setFont name="OpenSans" size="10"/>
|
||||
<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>
|
||||
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
|
||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||
</pageGraphics>
|
||||
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
||||
|
||||
@@ -1,16 +1,65 @@
|
||||
<setNextFrame name="main"/>
|
||||
<nextFrame/>
|
||||
|
||||
{% if invoice %}
|
||||
|
||||
<blockTable style="invoiceLayout" colWidths="330,165">
|
||||
<tr>
|
||||
<td>
|
||||
{% endif %}
|
||||
|
||||
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
|
||||
|
||||
<para style="style.event_description">
|
||||
<b>{{object.start_date|date:"D jS N Y"}}</b>
|
||||
</para>
|
||||
|
||||
<keepInFrame maxHeight="30">
|
||||
<<<<<<< HEAD
|
||||
<para style="style.event_description">
|
||||
{{ object.description|default_if_none:""|linebreaksbr }}
|
||||
</para>
|
||||
=======
|
||||
<para style="style.event_description">
|
||||
{{ object.description|default_if_none:""|linebreaksbr }}
|
||||
</para>
|
||||
>>>>>>> revision-view
|
||||
</keepInFrame>
|
||||
|
||||
{% if invoice %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<para style="invoice-head">INVOICE</para>
|
||||
<spacer length="10"/>
|
||||
<blockTable style="eventDetails" colWidths="100,175">
|
||||
<tr>
|
||||
<td><para style="invoice_titles">Invoice Number</para></td>
|
||||
<td>
|
||||
<para style="invoice_numbers">{{ invoice.pk|stringformat:"05d" }}</para>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para style="invoice_titles">Invoice Date</para></td>
|
||||
<td>
|
||||
<para style="invoice_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para style="invoice_titles">PO Number</para></td>
|
||||
<td>
|
||||
<para style="invoice_numbers">{{ object.purchase_order|default_if_none:"" }}</para>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</blockTable>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
<spacer length="15"/>
|
||||
<blockTable style="eventSpecifics" colWidths="165,165,165">
|
||||
<tr>
|
||||
@@ -18,7 +67,15 @@
|
||||
<h2>Hirer</h2>
|
||||
<h3>{{ object.person.name }}</h3>
|
||||
<h3>{{ object.organisation.name|default_if_none:"" }}</h3>
|
||||
|
||||
{% if invoice %}
|
||||
<keepInFrame>
|
||||
{% if object.organisation.address %}
|
||||
<para style="specific_description">{{ object.organisation.address|default_if_none:""|linebreaksbr }}</para>
|
||||
{% elif object.person.address %}
|
||||
<para style="specific_description">{{ object.person.address|default_if_none:""|linebreaksbr }}</para>
|
||||
{% endif %}
|
||||
</keepInFrame>
|
||||
{% endif %}
|
||||
<keepInFrame>
|
||||
{% if object.person.phone %}
|
||||
<para style="specific_description">{{ object.person.phone }}</para>
|
||||
@@ -27,19 +84,29 @@
|
||||
{% endif %}
|
||||
</keepInFrame>
|
||||
<keepInFrame>
|
||||
{% if object.person.email %}
|
||||
<para style="specific_description">{{ object.person.email }}</para>
|
||||
{% elif object.organisation.email %}
|
||||
<para style="specific_description">{{ object.organisation.email }}</para>
|
||||
{% if invoice %}
|
||||
{% if object.organisation.email %}
|
||||
<para style="specific_description">{{ object.organisation.email }}</para>
|
||||
{% elif object.person.email %}
|
||||
<para style="specific_description">{{ object.person.email }}</para>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if object.person.email %}
|
||||
<para style="specific_description">{{ object.person.email }}</para>
|
||||
{% elif object.organisation.email %}
|
||||
<para style="specific_description">{{ object.organisation.email }}</para>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</keepInFrame>
|
||||
</td>
|
||||
<td>
|
||||
<h2>Venue</h2>
|
||||
<h3>{{ object.venue.name }}</h3>
|
||||
{% if not invoice %}
|
||||
<keepInFrame>
|
||||
<para style="specific_description">{{ object.venue.address|default_if_none:""|linebreaksbr }}</para>
|
||||
</keepInFrame>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td rightPadding="0">
|
||||
|
||||
@@ -61,7 +128,7 @@
|
||||
</para>
|
||||
</td>
|
||||
</tr>
|
||||
{% if object.access_at %}
|
||||
{% if object.access_at and not invoice%}
|
||||
<tr>
|
||||
<td leftPadding="0"><h3>Access</h3></td>
|
||||
<td>
|
||||
@@ -133,15 +200,21 @@
|
||||
<td>£ {{ object.sum_total|floatformat:2 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VAT Registration Number: 116252989</td>
|
||||
<td>{% if not invoice %}VAT Registration Number: 116252989{% endif %}</td>
|
||||
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
|
||||
<td>£ {{ object.vat|floatformat:2 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<para>
|
||||
<b>The full hire fee is payable at least 10 days before the event.</b>
|
||||
{% if invoice %}
|
||||
VAT Registration Number: 116252989
|
||||
{% else %}
|
||||
<b>The full hire fee is payable at least 10 days before the event.</b>
|
||||
{% endif %}
|
||||
</para>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<para>
|
||||
@@ -156,88 +229,90 @@
|
||||
</tr>
|
||||
</blockTable>
|
||||
</keepTogether>
|
||||
<keepTogether>
|
||||
<blockTable style="infoTable">
|
||||
<tr>
|
||||
<td>
|
||||
<para>Bookings will
|
||||
<b>not</b>
|
||||
be confirmed until payment is received and the contract is signed.
|
||||
</para>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>24 Hour Emergency Contacts: 07825 065681 or 07825 065678</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
</keepTogether>
|
||||
<spacer length="15"/>
|
||||
<keepTogether>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>To be signed on booking:</b>
|
||||
</para>
|
||||
{% if object.organisation.union_account %}
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I agree that am authorised to sign this invoice. I agree that I am the President/Treasurer of the hirer, or
|
||||
that I have provided written permission from either the President or Treasurer of the hirer stating that I can
|
||||
sign for this invoice.
|
||||
</i>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I have read, understood and fully accepted the current conditions of hire. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in the same condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>
|
||||
Conditions of hire available on request or on the TEC PA & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department.
|
||||
</para>
|
||||
|
||||
<blockTable style="signatureTable" colWidths="70,100,325">
|
||||
{% if not invoice %}
|
||||
<keepTogether>
|
||||
<blockTable style="infoTable">
|
||||
<tr>
|
||||
<td>Account Code</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<para>Bookings will
|
||||
<b>not</b>
|
||||
be confirmed until payment is received and the contract is signed.
|
||||
</para>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>24 Hour Emergency Contacts: 07825 065681 or 07825 065678</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
{% else %}
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a
|
||||
binding contract between TEC PA & Lighting and the hirer, the aforementioned conditions of hire forming
|
||||
an integral part of it.
|
||||
</i>
|
||||
</para>
|
||||
</keepTogether>
|
||||
<spacer length="15"/>
|
||||
<keepTogether>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>
|
||||
Conditions of hire available on request or on the TEC PA & Lighting website. E&OE
|
||||
</b>
|
||||
<b>To be signed on booking:</b>
|
||||
</para>
|
||||
{% if object.organisation.union_account %}
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I agree that am authorised to sign this invoice. I agree that I am the President/Treasurer of the hirer, or
|
||||
that I have provided written permission from either the President or Treasurer of the hirer stating that I can
|
||||
sign for this invoice.
|
||||
</i>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I have read, understood and fully accepted the current conditions of hire. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in the same condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>
|
||||
Conditions of hire available on request or on the TEC PA & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department.
|
||||
</para>
|
||||
|
||||
<blockTable style="signatureTable" colWidths="70,100,325">
|
||||
<tr>
|
||||
<td>Account Code</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
{% else %}
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a
|
||||
binding contract between TEC PA & Lighting and the hirer, the aforementioned conditions of hire forming
|
||||
an integral part of it.
|
||||
</i>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>
|
||||
Conditions of hire available on request or on the TEC PA & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
<spacer length="10"/>
|
||||
<para style="blockPara">
|
||||
<b>To be signed on the day of the event/hire:</b>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in a similar condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
{% endif %}
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
<spacer length="10"/>
|
||||
<para style="blockPara">
|
||||
<b>To be signed on the day of the event/hire:</b>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in a similar condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
{% endif %}
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
</keepTogether>
|
||||
</keepTogether>
|
||||
{% endif %}
|
||||
<namedString id="lastPage"><pageNumber/></namedString>
|
||||
@@ -33,7 +33,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<h4>
|
||||
<a href="{% url 'event_detail' event.pk %}">{{ event.name }}</a>
|
||||
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}>{{ event.name }}</a>
|
||||
{% if event.venue %}
|
||||
<small>at {{ event.venue }}</small>
|
||||
{% endif %}
|
||||
@@ -80,12 +80,12 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if event.mic or not event.is_rig %}
|
||||
{% if event.mic %}
|
||||
{{ event.mic.initials }}
|
||||
<div>
|
||||
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif event.is_rig %}
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -3,44 +3,76 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>Rig Information Gathering System</h2>
|
||||
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="well">
|
||||
{% if user.is_authenticated %}
|
||||
<h3>Welcome back {{ user.get_full_name }}.<br />
|
||||
<small>Your rigboard initials are {{ user.initials }}</small></h3>
|
||||
{% endif %}
|
||||
<h3>There are currently {{ rig_count }} rigs coming up.</h3>
|
||||
<a class="btn btn-default" href="{% url 'rigboard' %}">View Rigboard</a>
|
||||
<a class="btn btn-default" href="{% url 'event_create' %}">
|
||||
New Event <span class="glyphicon glyphicon-plus"></span>
|
||||
</a>
|
||||
<div class="col-sm-12">
|
||||
<p><h4 class="list-group-item-heading" style="margin:0;">Welcome back {{ user.get_full_name }}, there are {{ rig_count }} rigs coming up.</h4>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-{% if perms.RIGS.view_event %}6{% else %}12{% endif %}">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="list-group-item-heading">Quick Links</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<a class="list-group-item" href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a>
|
||||
<a class="list-group-item" href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a>
|
||||
{% if perms.RIGS.add_event %}<a class="list-group-item" href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a>{% endif %}
|
||||
|
||||
<div class="list-group-item default"></div>
|
||||
|
||||
<a class="list-group-item" href="//members.nottinghamtec.co.uk/forum" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Forum</a>
|
||||
<a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a>
|
||||
<a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="well">
|
||||
<div>
|
||||
<h4><a href="{% url 'person_list' %}">People</a></h4>
|
||||
<form class="form-inline" role="form" action="{% url 'person_list' %}" method="GET">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search People" />
|
||||
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</form>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Search Rigboard</h4>
|
||||
</div>
|
||||
<div>
|
||||
<h4><a href="{% url 'organisation_list' %}">Organisations</a></h4>
|
||||
<form class="form-inline" role="form" action="{% url 'organisation_list' %}" method="GET">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search Organisations" />
|
||||
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<h4><a href="{% url 'venue_list' %}">Venues</a></h4>
|
||||
<form class="form-inline" role="form" action="{% url 'venue_list' %}" method="GET">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search Venues" />
|
||||
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</form>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<form class="form" role="form" action="{% url 'person_list' %}" method="GET">
|
||||
<div class="input-group">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search People" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<form class="form" role="form" action="{% url 'organisation_list' %}" method="GET">
|
||||
<div class="input-group">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search Organisations" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<form class="form" role="form" action="{% url 'venue_list' %}" method="GET">
|
||||
<div class="input-group">
|
||||
<input type="search" name="q" class="form-control" placeholder="Search Venues" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.RIGS.view_event %}
|
||||
<div class="col-sm-6" >
|
||||
{% include 'RIGS/activity_feed.html' %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -10,9 +10,15 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 text-right">
|
||||
<div class="btn-group btn-page">
|
||||
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-default" title="Void Invoice">
|
||||
<span class="glyphicon glyphicon-ban-circle"></span>
|
||||
<span class="glyphicon glyphicon-ban-circle"></span> <span
|
||||
class="hidden-xs">Void</span>
|
||||
</a>
|
||||
<a href="{% url 'invoice_print' object.pk %}" target="_blank" class="btn btn-default"><span
|
||||
class="glyphicon glyphicon-print"></span> <span
|
||||
class="hidden-xs">Print</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
4
RIGS/templates/RIGS/object_button.html
Normal file
4
RIGS/templates/RIGS/object_button.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% load to_class_name from filters %}
|
||||
{# pass in variable "object" to this template #}
|
||||
|
||||
<a title="{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}'" href="{{ object.get_absolute_url }}">{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}'</a>
|
||||
@@ -8,7 +8,9 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h3>{{ object.name }}<br/>
|
||||
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}</span>
|
||||
<span class="small"><a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
|
||||
</a></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
@@ -67,8 +69,9 @@
|
||||
{% block footer %}
|
||||
<div class="row">
|
||||
<div class="col-sm-10 align-left">
|
||||
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }}
|
||||
by {{ object.last_edited_by.name }}
|
||||
<a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="pull-right">
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
<h4>Details</h4>
|
||||
{% if not request.is_ajax %}
|
||||
<h3>{{ object.name }}<br/>
|
||||
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}</span>
|
||||
<span class="small"><a href="{% url 'person_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
|
||||
</a></span>
|
||||
</h3>
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'person_update' object.pk %}" class="btn btn-primary">Edit <span
|
||||
@@ -59,7 +61,9 @@
|
||||
{% block footer %}
|
||||
<div class="row">
|
||||
<div class="col-sm-10 align-left">
|
||||
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }} by {{ object.last_edited_by.name }}
|
||||
<a href="{% url 'person_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="pull-right">
|
||||
|
||||
12
RIGS/templates/RIGS/profile_button.html
Normal file
12
RIGS/templates/RIGS/profile_button.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{# pass in variable "profile" to this template #}
|
||||
|
||||
<button title="{{profile.name}}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover focus' data-toggle="popover" data-content='
|
||||
<img src="{{profile.profile_picture}}" class="img-responsive img-rounded center-block" style="max-width:4em" />
|
||||
<dl class="dl-vertical">
|
||||
<dt>Email</dt>
|
||||
<dd>{{profile.email}}</dd>
|
||||
|
||||
<dt>Phone</dt>
|
||||
<dd>{{profile.phone}}</dd>
|
||||
</dl>
|
||||
'>{{profile.first_name}}</button>
|
||||
@@ -8,11 +8,14 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<h3>Rigboard</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.RIGS.add_event %}
|
||||
<div class="col-sm-2">
|
||||
<a href="{% url 'event_create' %}" class="btn btn-default pull-right">New <span
|
||||
class="glyphicon glyphicon-plus"></span></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% comment %}
|
||||
{# Bring search back at a later date #}
|
||||
<div class="col-sm-3 col-sm-offset-9">
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
{% if not request.is_ajax %}
|
||||
<h3>{{ object.name }}<br/>
|
||||
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by }}</span>
|
||||
<span class="small"><a href="{% url 'venue_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
|
||||
</a></span>
|
||||
</h3>
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'venue_update' object.pk %}" class="btn btn-primary">Edit <span
|
||||
@@ -49,7 +51,9 @@
|
||||
{% block footer %}
|
||||
<div class="row">
|
||||
<div class="col-sm-10 align-left">
|
||||
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }} by {{ object.last_edited_by }}
|
||||
<a href="{% url 'venue_history' object.pk %}" title="View Revision History">
|
||||
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="pull-right">
|
||||
|
||||
23
RIGS/templates/RIGS/version_changes.html
Normal file
23
RIGS/templates/RIGS/version_changes.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% for change in version.field_changes %}
|
||||
|
||||
<button title="Changes to {{ change.field.verbose_name }}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
|
||||
|
||||
{% if change.new %}<div class="alert alert-success">{{change.new}}</div>{% endif %}
|
||||
{% if change.old %}<div class="alert alert-danger">{{change.old}}</div>{% endif %}
|
||||
|
||||
'>{{ change.field.verbose_name }}</button>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for itemChange in version.item_changes %}
|
||||
<button title="Changes to item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
|
||||
{% for change in itemChange.changes %}
|
||||
<h4>{{ change.field.verbose_name }}</h4>
|
||||
|
||||
{% if change.new %}<div class="alert alert-success">{{change.new}}</div>{% endif %}
|
||||
{% if change.old %}<div class="alert alert-danger">{{change.old}}</div>{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button>
|
||||
{% endfor %}
|
||||
63
RIGS/templates/RIGS/version_history.html
Normal file
63
RIGS/templates/RIGS/version_history.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
|
||||
{% load to_class_name from filters %}
|
||||
{% load paginator from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{object|to_class_name}} {{ object.pk|stringformat:"05d" }} - Revision History{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script src="{% static "js/popover.js" %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover().click(function(){
|
||||
if($(this).attr('href')){
|
||||
window.location.href = $(this).attr('href');
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h3><a href="{{ object.get_absolute_url }}">{{object|to_class_name}} {{ object.pk|stringformat:"05d" }}</a> - Revision History</h3>
|
||||
</div>
|
||||
<div class="text-right col-sm-12">{% paginator %}</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for version in object_list %}
|
||||
{% if version.item_changes or version.field_changes or version.old == None %}
|
||||
<tr>
|
||||
<td>{{ version.revision.date_created }}</td>
|
||||
<td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
|
||||
<td>{{ version.revision.user.name }}</td>
|
||||
<td>
|
||||
{% if version.old == None %}
|
||||
Object Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="align-right">{% paginator %}</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -9,6 +9,10 @@ register = template.Library()
|
||||
def multiply(value, arg):
|
||||
return value*arg
|
||||
|
||||
@register.filter
|
||||
def to_class_name(value):
|
||||
return value.__class__.__name__
|
||||
|
||||
@register.filter
|
||||
def nice_errors(form, non_field_msg='General form errors'):
|
||||
nice_errors = ErrorDict()
|
||||
|
||||
27
RIGS/urls.py
27
RIGS/urls.py
@@ -1,6 +1,6 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from RIGS import views, rigboard, finance, ical, forms
|
||||
from RIGS import models, views, rigboard, finance, ical, versioning, forms
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from PyRIGS.decorators import permission_required_with_403
|
||||
@@ -10,7 +10,7 @@ urlpatterns = patterns('',
|
||||
# Examples:
|
||||
# url(r'^$', 'PyRIGS.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
url('^$', views.Index.as_view(), name='index'),
|
||||
url('^$', login_required(views.Index.as_view()), name='index'),
|
||||
url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'),
|
||||
|
||||
url('^user/login/$', 'RIGS.views.login', name='login'),
|
||||
@@ -25,6 +25,9 @@ urlpatterns = patterns('',
|
||||
url(r'^people/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_person')(views.PersonDetail.as_view()),
|
||||
name='person_detail'),
|
||||
url(r'^people/(?P<pk>\d+)/history/$',
|
||||
permission_required_with_403('RIGS.view_person')(versioning.VersionHistory.as_view()),
|
||||
name='person_history', kwargs={'model': models.Person}),
|
||||
url(r'^people/(?P<pk>\d+)/edit/$',
|
||||
permission_required_with_403('RIGS.change_person')(views.PersonUpdate.as_view()),
|
||||
name='person_update'),
|
||||
@@ -39,6 +42,9 @@ urlpatterns = patterns('',
|
||||
url(r'^organisations/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_organisation')(views.OrganisationDetail.as_view()),
|
||||
name='organisation_detail'),
|
||||
url(r'^organisations/(?P<pk>\d+)/history/$',
|
||||
permission_required_with_403('RIGS.view_organisation')(versioning.VersionHistory.as_view()),
|
||||
name='organisation_history', kwargs={'model': models.Organisation}),
|
||||
url(r'^organisations/(?P<pk>\d+)/edit/$',
|
||||
permission_required_with_403('RIGS.change_organisation')(views.OrganisationUpdate.as_view()),
|
||||
name='organisation_update'),
|
||||
@@ -53,6 +59,9 @@ urlpatterns = patterns('',
|
||||
url(r'^venues/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_venue')(views.VenueDetail.as_view()),
|
||||
name='venue_detail'),
|
||||
url(r'^venues/(?P<pk>\d+)/history/$',
|
||||
permission_required_with_403('RIGS.view_venue')(versioning.VersionHistory.as_view()),
|
||||
name='venue_history', kwargs={'model': models.Venue}),
|
||||
url(r'^venues/(?P<pk>\d+)/edit/$',
|
||||
permission_required_with_403('RIGS.change_venue')(views.VenueUpdate.as_view()),
|
||||
name='venue_update'),
|
||||
@@ -61,6 +70,12 @@ urlpatterns = patterns('',
|
||||
url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
||||
url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/archive/$', RedirectView.as_view(pattern_name='event_archive')),
|
||||
url(r'^rigboard/activity/$',
|
||||
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
|
||||
name='activity_table'),
|
||||
url(r'^rigboard/activity/feed/$',
|
||||
permission_required_with_403('RIGS.view_event')(versioning.ActivityFeed.as_view()),
|
||||
name='activity_feed'),
|
||||
|
||||
url(r'^event/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()),
|
||||
@@ -80,6 +95,11 @@ urlpatterns = patterns('',
|
||||
url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()),
|
||||
name='event_archive'),
|
||||
|
||||
url(r'^event/(?P<pk>\d+)/history/$',
|
||||
permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()),
|
||||
name='event_history', kwargs={'model': models.Event}),
|
||||
|
||||
|
||||
|
||||
# Finance
|
||||
url(r'^invoice/$',
|
||||
@@ -99,6 +119,9 @@ urlpatterns = patterns('',
|
||||
url(r'^invoice/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()),
|
||||
name='invoice_detail'),
|
||||
url(r'^invoice/(?P<pk>\d+)/print/$',
|
||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()),
|
||||
name='invoice_print'),
|
||||
url(r'^invoice/(?P<pk>\d+)/void/$',
|
||||
permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()),
|
||||
name='invoice_void'),
|
||||
|
||||
251
RIGS/versioning.py
Normal file
251
RIGS/versioning.py
Normal file
@@ -0,0 +1,251 @@
|
||||
import logging
|
||||
from django.views import generic
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.db.models import Q
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
# Versioning
|
||||
import reversion
|
||||
import simplejson
|
||||
from reversion.models import Version
|
||||
from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
|
||||
from RIGS import models, forms
|
||||
import datetime
|
||||
import re
|
||||
|
||||
logger = logging.getLogger('tec.pyrigs')
|
||||
|
||||
|
||||
def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
# recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects
|
||||
try:
|
||||
theFields = oldObj._meta.fields #This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available)
|
||||
except AttributeError:
|
||||
theFields = newObj._meta.fields
|
||||
|
||||
|
||||
class FieldCompare(object):
|
||||
def __init__(self, field=None, old=None, new=None):
|
||||
self.field = field
|
||||
self.old = old
|
||||
self.new = new
|
||||
|
||||
changes = []
|
||||
|
||||
for thisField in theFields:
|
||||
name = thisField.name
|
||||
|
||||
if name in excluded_keys:
|
||||
continue # if we're excluding this field, skip over it
|
||||
|
||||
oldValue = getattr(oldObj, name, None)
|
||||
newValue = getattr(newObj, name, None)
|
||||
|
||||
try:
|
||||
bothBlank = (not oldValue) and (not newValue)
|
||||
if oldValue != newValue and not bothBlank:
|
||||
compare = FieldCompare(thisField,oldValue,newValue)
|
||||
changes.append(compare)
|
||||
except TypeError: # logs issues with naive vs tz-aware datetimes
|
||||
logger.error('TypeError when comparing models')
|
||||
|
||||
return changes
|
||||
|
||||
def compare_event_items(old, new):
|
||||
# Recieves two event version objects and compares their items, returns an array of ItemCompare objects
|
||||
|
||||
item_type = ContentType.objects.get_for_model(models.EventItem)
|
||||
old_item_versions = old.revision.version_set.filter(content_type=item_type)
|
||||
new_item_versions = new.revision.version_set.filter(content_type=item_type)
|
||||
|
||||
class ItemCompare(object):
|
||||
def __init__(self, old=None, new=None, changes=None):
|
||||
self.old = old
|
||||
self.new = new
|
||||
self.changes = changes
|
||||
|
||||
# Build some dicts of what we have
|
||||
item_dict = {} # build a list of items, key is the item_pk
|
||||
for version in old_item_versions: # put all the old versions in a list
|
||||
compare = ItemCompare(old=version.object_version.object)
|
||||
item_dict[version.object_id] = compare
|
||||
|
||||
for version in new_item_versions: # go through the new versions
|
||||
try:
|
||||
compare = item_dict[version.object_id] # see if there's a matching old version
|
||||
compare.new = version.object_version.object # then add the new version to the dictionary
|
||||
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
|
||||
compare = ItemCompare(new=version.object_version.object)
|
||||
|
||||
item_dict[version.object_id] = compare # update the dictionary with the changes
|
||||
|
||||
changes = []
|
||||
for (_, compare) in item_dict.items():
|
||||
compare.changes = model_compare(compare.old, compare.new, ['id','event','order']) # see what's changed
|
||||
if len(compare.changes) >= 1:
|
||||
changes.append(compare) # transfer into a sequential array to make it easier to deal with later
|
||||
|
||||
return changes
|
||||
|
||||
def get_versions_for_model(models):
|
||||
content_types = []
|
||||
for model in models:
|
||||
content_types.append(ContentType.objects.get_for_model(model))
|
||||
|
||||
versions = reversion.models.Version.objects.filter(
|
||||
content_type__in = content_types,
|
||||
).select_related("revision","revision.version_set").order_by("-pk")
|
||||
|
||||
return versions
|
||||
|
||||
def get_previous_version(version):
|
||||
thisId = version.object_id
|
||||
thisVersionId = version.pk
|
||||
|
||||
versions = reversion.get_for_object_reference(version.content_type.model_class(), thisId)
|
||||
|
||||
try:
|
||||
previousVersions = versions.filter(revision_id__lt=version.revision_id).latest(field_name='revision__date_created')
|
||||
except ObjectDoesNotExist:
|
||||
return False
|
||||
|
||||
return previousVersions
|
||||
|
||||
def get_changes_for_version(newVersion, oldVersion=None):
|
||||
#Pass in a previous version if you already know it (for efficiancy)
|
||||
#if not provided then it will be looked up in the database
|
||||
|
||||
if oldVersion == None:
|
||||
oldVersion = get_previous_version(newVersion)
|
||||
|
||||
modelClass = newVersion.content_type.model_class()
|
||||
|
||||
compare = {
|
||||
'revision': newVersion.revision,
|
||||
'new': newVersion.object_version.object,
|
||||
'current': modelClass.objects.filter(pk=newVersion.pk).first(),
|
||||
'version': newVersion,
|
||||
|
||||
# Old things that may not be used
|
||||
'old': None,
|
||||
'field_changes': None,
|
||||
'item_changes': None,
|
||||
}
|
||||
|
||||
if oldVersion:
|
||||
compare['old'] = oldVersion.object_version.object
|
||||
compare['field_changes'] = model_compare(compare['old'], compare['new'])
|
||||
compare['item_changes'] = compare_event_items(oldVersion, newVersion)
|
||||
|
||||
return compare
|
||||
|
||||
class VersionHistory(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/version_history.html"
|
||||
paginate_by = 25
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
thisModel = self.kwargs['model']
|
||||
|
||||
# thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk'])
|
||||
versions = reversion.get_for_object_reference(thisModel, self.kwargs['pk'])
|
||||
|
||||
return versions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
thisModel = self.kwargs['model']
|
||||
|
||||
context = super(VersionHistory, self).get_context_data(**kwargs)
|
||||
|
||||
versions = context['object_list']
|
||||
thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk'])
|
||||
|
||||
items = []
|
||||
|
||||
for versionNo, thisVersion in enumerate(versions):
|
||||
if versionNo >= len(versions)-1:
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
else:
|
||||
thisItem = get_changes_for_version(thisVersion, versions[versionNo+1])
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
context['object_list'] = items
|
||||
context['object'] = thisObject
|
||||
|
||||
return context
|
||||
|
||||
class ActivityTable(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/activity_table.html"
|
||||
paginate_by = 25
|
||||
|
||||
def get_queryset(self):
|
||||
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
|
||||
return versions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
# Call the base implementation first to get a context
|
||||
context = super(ActivityTable, self).get_context_data(**kwargs)
|
||||
|
||||
items = []
|
||||
|
||||
for thisVersion in context['object_list']:
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
items.append(thisItem)
|
||||
|
||||
context ['object_list'] = items
|
||||
|
||||
return context
|
||||
|
||||
class ActivityFeed(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/activity_feed_data.html"
|
||||
paginate_by = 25
|
||||
|
||||
def get_queryset(self):
|
||||
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
|
||||
return versions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
maxTimeDelta = []
|
||||
|
||||
maxTimeDelta.append({ 'maxAge':datetime.timedelta(days=1), 'group':datetime.timedelta(hours=1)})
|
||||
maxTimeDelta.append({ 'maxAge':None, 'group':datetime.timedelta(days=1)})
|
||||
|
||||
# Call the base implementation first to get a context
|
||||
context = super(ActivityFeed, self).get_context_data(**kwargs)
|
||||
|
||||
items = []
|
||||
|
||||
for thisVersion in context['object_list']:
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
if thisItem['item_changes'] or thisItem['field_changes'] or thisItem['old'] == None:
|
||||
thisItem['withPrevious'] = False
|
||||
if len(items)>=1:
|
||||
timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem['revision'].date_created
|
||||
timeDiff = items[-1]['revision'].date_created - thisItem['revision'].date_created
|
||||
timeTogether = False
|
||||
for params in maxTimeDelta:
|
||||
if params['maxAge'] is None or timeAgo <= params['maxAge']:
|
||||
timeTogether = timeDiff < params['group']
|
||||
break
|
||||
|
||||
sameUser = thisItem['revision'].user == items[-1]['revision'].user
|
||||
thisItem['withPrevious'] = timeTogether & sameUser
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
context ['object_list'] = items
|
||||
|
||||
|
||||
return context
|
||||
@@ -10,6 +10,7 @@ import simplejson
|
||||
from django.contrib import messages
|
||||
import datetime
|
||||
import operator
|
||||
from registration.views import RegistrationView
|
||||
|
||||
from RIGS import models, forms
|
||||
|
||||
@@ -33,7 +34,6 @@ def login(request, **kwargs):
|
||||
|
||||
return login(request, authentication_form=forms.LoginForm)
|
||||
|
||||
|
||||
"""
|
||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||
May optionally also include some javascript in a success message to cause a load of
|
||||
|
||||
Reference in New Issue
Block a user