Merge branch 'master' into vagrant

This commit is contained in:
Tom Price
2015-07-21 21:26:14 +01:00
9 changed files with 393 additions and 149 deletions

View File

@@ -9,7 +9,9 @@ from django.utils.encoding import python_2_unicode_compatible
import reversion
import string
import random
from collections import Counter
from django.core.urlresolvers import reverse_lazy
from django.core.exceptions import ValidationError
from decimal import Decimal
@@ -91,9 +93,13 @@ class Person(models.Model, RevisionMixin):
def organisations(self):
o = []
for e in Event.objects.filter(person=self).select_related('organisation'):
if e.organisation and e.organisation not in o:
if e.organisation:
o.append(e.organisation)
return o
#Count up occurances and put them in descending order
c = Counter(o)
stats = c.most_common()
return stats
@property
def latest_events(self):
@@ -131,9 +137,13 @@ class Organisation(models.Model, RevisionMixin):
def persons(self):
p = []
for e in Event.objects.filter(organisation=self).select_related('person'):
if e.person and e.person not in p:
if e.person:
p.append(e.person)
return p
#Count up occurances and put them in descending order
c = Counter(p)
stats = c.most_common()
return stats
@property
def latest_events(self):
@@ -348,6 +358,20 @@ class Event(models.Model, RevisionMixin):
def __str__(self):
return str(self.pk) + ": " + self.name
def clean(self):
if self.end_date and self.start_date > self.end_date:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
startEndSameDay = not self.end_date or self.end_date == self.start_date
hasStartAndEnd = self.has_start_time and self.has_end_time
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
def save(self, *args, **kwargs):
"""Call :meth:`full_clean` before saving."""
self.full_clean()
super(Event, self).save(*args, **kwargs)
class Meta:
permissions = (
('view_event', 'Can view Events'),

View File

@@ -1,82 +1,109 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% load widget_tweaks %}
{% block title %}{{ object.name }}{% endblock %}
{% block title %}Organisation | {{ object.name }}{% endblock %}
{% block content %}
{% if not request.is_ajax %}
<div class="row">
<div class="col-sm-8">
<h3>{{ object.name }}<br/>
<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">
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-primary">Edit <span
class="glyphicon glyphicon-pencil"></span></a>
</div>
<div class="col-sm-4">
</div>
</div>
{% endif %}
<div class="row">
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>Organisation | {{ object.name }}</h1>
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
</div>
{% endif %}
<div class="col-sm-6">
<h4>Details</h4>
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<div class="panel panel-info">
<div class="panel-heading">Organisation Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}">{{ object.email }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
<dt>Union Account</dt>
<dd>{{ object.union_account|yesno|capfirst }}</dd>
</dl>
<dt>Union Account</dt>
<dd>{{ object.union_account|yesno|capfirst }}</dd>
</dl>
</div>
</div>
</div>
<div class="col-sm-6">
<h4>People</h4>
<ul class="list-unstyled">
{% for person in object.persons %}
<li><a href="{% url 'person_detail' person.pk %}">{{ person.name }}</a></li>
{% endfor %}
</ul>
<div class="panel panel-default">
<div class="panel-heading">Associated People</div>
<div class="panel-body">
<div class="list-group">
{% for person,count in object.persons %}
<a class="list-group-item" href="{% url 'person_detail' person.pk %}">{{ person.pk|stringformat:"05d" }} | {{ person.name }} <span class="badge" title="{{count}} events with {{person.name}} for {{object.name}}">{{count}}</span></a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h4>Events</h4>
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
<div class="panel panel-default">
<div class="panel-heading">Associated Events</div>
<div class="panel-body">
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
</div>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="row">
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% if request.is_ajax %}
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
<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">
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-primary">Edit <span
class="glyphicon glyphicon-pencil"></span></a>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'organisation_detail' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-eye-open"></span> Open Page</a>
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
{% load widget_tweaks %}
{% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Person{% endif %}{% endblock %}
{% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Organisation{% endif %}{% endblock %}
{% block content %}
<div class="col-sm-offset-1 col-sm-10">

View File

@@ -1,74 +1,106 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% load widget_tweaks %}
{% block title %}{{ object.name }}{% endblock %}
{% block title %}Person | {{ object.name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<h4>Details</h4>
{% if not request.is_ajax %}
<h3>{{ object.name }}<br/>
<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
class="glyphicon glyphicon-pencil"></span></a>
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>Person | {{ object.name }}</h1>
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'person_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
{% endif %}
<div class="">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
</div>
{% endif %}
<div class="col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">Person Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}">{{ object.email }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
</dl>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
</dl>
</div>
</div>
</div>
<div class="col-sm-6">
<h4>Organisations</h4>
<ul class="list-unstyled">
{% for organisation in object.organisations %}
<li><a href="{% url 'organisation_detail' organisation.pk %}">{{ organisation.name }}</a></li>
{% endfor %}
</ul>
<div class="panel panel-default">
<div class="panel-heading">Associated Organisations</div>
<div class="panel-body">
<div class="list-group">
{% for organisation,count in object.organisations %}
<a class="list-group-item" href="{% url 'organisation_detail' organisation.pk %}">{{ organisation.pk|stringformat:"05d" }} | {{ organisation.name }} <span class="badge" title="{{count}} events with {{object.name}} for {{organisation.name}}">{{count}}</span></a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h4>Events</h4>
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
<div class="panel panel-default">
<div class="panel-heading">Associated Events</div>
<div class="panel-body">
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
</div>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="row">
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'person_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'person_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% if request.is_ajax %}
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
<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">
<a href="{% url 'person_update' object.pk %}" class="btn btn-primary">Edit <span
class="glyphicon glyphicon-pencil"></span></a>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'person_detail' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-eye-open"></span> Open Page</a>
<a href="{% url 'person_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'person_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>

View File

@@ -1,64 +1,96 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% load widget_tweaks %}
{% block title %}{{ object.name }}{% endblock %}
{% block title %}Venue | {{ object.name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
{% if not request.is_ajax %}
<h3>{{ object.name }}<br/>
<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
class="glyphicon glyphicon-pencil"></span></a>
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>Venue | {{ object.name }}</h1>
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'venue_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
{% endif %}
<div class="">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
</div>
{% endif %}
<div class="col-sm-12">
<div class="panel panel-info">
<div class="panel-heading">Venue Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}">{{ object.email }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
</dl>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
<dt>Three Phase Available</dt>
<dd>{{ object.three_phase_available|yesno|capfirst }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
<div class="panel panel-default">
<div class="panel-heading">Associated Events</div>
<div class="panel-body">
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
</div>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="row">
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'venue_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'venue_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% if request.is_ajax %}
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
<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">
<a href="{% url 'venue_update' object.pk %}" class="btn btn-primary">Edit <span
class="glyphicon glyphicon-pencil"></span></a>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'venue_detail' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-eye-open"></span> Open Page</a>
<a href="{% url 'venue_update' object.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-pencil"></span> Edit</a>
</div>
<div>
<a href="{% url 'venue_history' object.pk %}" title="View Revision History">
Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
</div>

View File

@@ -58,7 +58,21 @@
{% render_field form.notes class+="form-control" placeholder=form.notes.label %}
</div>
</div>
<div class="form-group">
<div class="col-sm-10 col-sm-offset-2">
<div class="checkbox">
<label>
{% render_field form.three_phase_available %} {{ form.three_phase_available.label }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<input class="btn btn-primary pull-right" type="submit"/>

View File

@@ -414,6 +414,121 @@ class EventTest(LiveServerTestCase):
event = models.Event.objects.get(name='Test Event Name')
self.assertIn("N0000%d | Test Event Name"%event.pk, self.browser.find_element_by_xpath('//h1').text)
def testDateValidation(self):
self.browser.get(self.live_server_url + '/event/create/')
# Gets redirected to login and back
self.authenticate('/event/create/')
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
self.browser.implicitly_wait(3) #Set session-long wait (only works for non-existant DOM objects)
wait.until(animation_is_finished())
# Click Rig button
self.browser.find_element_by_xpath('//button[.="Rig"]').click()
form = self.browser.find_element_by_tag_name('form')
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
# Set title
e = self.browser.find_element_by_id('id_name')
e.send_keys('Test Event Name')
# Both dates, no times, end before start
form.find_element_by_id('id_start_date').clear()
form.find_element_by_id('id_start_date').send_keys('3015-04-24')
form.find_element_by_id('id_end_date').clear()
form.find_element_by_id('id_end_date').send_keys('3015-04-23')
# Attempt to save - should fail
save.click()
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
self.assertTrue(error.is_displayed())
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
# Same date, end time before start time
form = self.browser.find_element_by_tag_name('form')
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
form.find_element_by_id('id_start_date').clear()
form.find_element_by_id('id_start_date').send_keys('3015-04-24')
form.find_element_by_id('id_end_date').clear()
form.find_element_by_id('id_end_date').send_keys('3015-04-23')
form.find_element_by_id('id_start_time').clear()
form.find_element_by_id('id_start_time').send_keys('06:59')
form.find_element_by_id('id_end_time').clear()
form.find_element_by_id('id_end_time').send_keys('06:00')
# Attempt to save - should fail
save.click()
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
self.assertTrue(error.is_displayed())
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
# Same date, end time before start time
form = self.browser.find_element_by_tag_name('form')
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
form.find_element_by_id('id_start_date').clear()
form.find_element_by_id('id_start_date').send_keys('3015-04-24')
form.find_element_by_id('id_end_date').clear()
form.find_element_by_id('id_end_date').send_keys('3015-04-23')
form.find_element_by_id('id_start_time').clear()
form.find_element_by_id('id_start_time').send_keys('06:59')
form.find_element_by_id('id_end_time').clear()
form.find_element_by_id('id_end_time').send_keys('06:00')
# No end date, end time before start time
form = self.browser.find_element_by_tag_name('form')
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
form.find_element_by_id('id_start_date').clear()
form.find_element_by_id('id_start_date').send_keys('3015-04-24')
form.find_element_by_id('id_end_date').clear()
form.find_element_by_id('id_start_time').clear()
form.find_element_by_id('id_start_time').send_keys('06:59')
form.find_element_by_id('id_end_time').clear()
form.find_element_by_id('id_end_time').send_keys('06:00')
# Attempt to save - should fail
save.click()
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
self.assertTrue(error.is_displayed())
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
# 2 dates, end after start
form = self.browser.find_element_by_tag_name('form')
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
form.find_element_by_id('id_start_date').clear()
form.find_element_by_id('id_start_date').send_keys('3015-04-24')
form.find_element_by_id('id_end_date').clear()
form.find_element_by_id('id_end_date').send_keys('3015-04-26')
form.find_element_by_id('id_start_time').clear()
form.find_element_by_id('id_end_time').clear()
# Attempt to save - should succeed
save.click()
# See redirected to success page
event = models.Event.objects.get(name='Test Event Name')
self.assertIn("N0000%d | Test Event Name"%event.pk, self.browser.find_element_by_xpath('//h1').text)
def testEventDetail(self):
with transaction.atomic(), reversion.create_revision():
person = models.Person(name="Event Detail Person", email="eventdetail@person.tests.rigs", phone="123 123")

View File

@@ -144,16 +144,16 @@ class EventTestCase(TestCase):
events = models.Event.objects.all()
# Check person's organisations
self.assertIn(o1, p1.organisations)
self.assertIn(o2, p1.organisations)
self.assertIn(o1, p2.organisations)
self.assertNotIn(o2, p2.organisations)
self.assertIn((o1,2), p1.organisations)
self.assertIn((o2,1), p1.organisations)
self.assertIn((o1,2), p2.organisations)
self.assertEqual(len(p2.organisations), 1)
# Check organisation's persons
self.assertIn(p1, o1.persons)
self.assertIn(p2, o1.persons)
self.assertIn(p1, o2.persons)
self.assertNotIn(p2, o2.persons)
self.assertIn((p1,2), o1.persons)
self.assertIn((p2,2), o1.persons)
self.assertIn((p1,1), o2.persons)
self.assertEqual(len(o2.persons),1)
def test_cancelled_property(self):
event = models.Event.objects.all()[0]

View File

@@ -176,7 +176,7 @@ class VenueDetail(generic.DetailView):
class VenueCreate(generic.CreateView):
model = models.Venue
fields = ['name','phone','email','address','notes']
fields = ['name','phone','email','address','notes','three_phase_available']
def get_success_url(self):
if self.request.is_ajax():
@@ -193,7 +193,7 @@ class VenueCreate(generic.CreateView):
class VenueUpdate(generic.UpdateView):
model = models.Venue
fields = ['name','phone','email','address','notes']
fields = ['name','phone','email','address','notes','three_phase_available']
def get_success_url(self):
if self.request.is_ajax():