Merge branch 'master' into markdown

# Conflicts:
#	RIGS/static/css/screen.css
#	db.sqlite3
#	requirements.txt
This commit is contained in:
Tom Price
2016-03-30 23:34:33 +01:00
24 changed files with 734 additions and 9729 deletions

View File

@@ -1,5 +1,5 @@
# TEC PA & Lighting - PyRIGS # # TEC PA & Lighting - PyRIGS #
[![wercker status](https://app.wercker.com/status/b26100ecccdfb46a9a9056553daac5b7/m/master "wercker status")](https://app.wercker.com/project/bykey/b26100ecccdfb46a9a9056553daac5b7) [![wercker status](https://app.wercker.com/status/2dbe0517c3d83859c985ffc5a55a2802/m/master "wercker status")](https://app.wercker.com/project/bykey/2dbe0517c3d83859c985ffc5a55a2802)
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.

View File

@@ -10,13 +10,14 @@ import simplejson
from RIGS import models from RIGS import models
#Registration
# Registration
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
captcha = ReCaptchaField() captcha = ReCaptchaField()
class Meta: class Meta:
model = models.Profile model = models.Profile
fields = ('username','email','first_name','last_name','initials','phone') fields = ('username', 'email', 'first_name', 'last_name', 'initials', 'phone')
def clean_initials(self): def clean_initials(self):
""" """
@@ -26,24 +27,22 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
raise forms.ValidationError("These initials are already in use. Please supply different initials.") raise forms.ValidationError("These initials are already in use. Please supply different initials.")
return self.cleaned_data['initials'] return self.cleaned_data['initials']
# Login form
class LoginForm(AuthenticationForm):
captcha = ReCaptchaField(label='Captcha')
# Login form
class PasswordReset(PasswordResetForm): class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha') captcha = ReCaptchaField(label='Captcha')
class ProfileCreationForm(UserCreationForm):
class ProfileCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta): class Meta(UserCreationForm.Meta):
model = models.Profile model = models.Profile
class ProfileChangeForm(UserChangeForm): class ProfileChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta): class Meta(UserChangeForm.Meta):
model = models.Profile model = models.Profile
# Events Shit # Events Shit
class EventForm(forms.ModelForm): class EventForm(forms.ModelForm):
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
@@ -96,7 +95,7 @@ class EventForm(forms.ModelForm):
def _get_or_initialise_item(self, pk, data, event): def _get_or_initialise_item(self, pk, data, event):
try: try:
item = models.EventItem.objects.get(pk=pk,event=event) item = models.EventItem.objects.get(pk=pk, event=event)
except models.EventItem.DoesNotExist: except models.EventItem.DoesNotExist:
# This occurs for one of two reasons # This occurs for one of two reasons
# 1) The event has been duplicated, so the item PKs belong to another event # 1) The event has been duplicated, so the item PKs belong to another event
@@ -134,7 +133,6 @@ class EventForm(forms.ModelForm):
for key in items: for key in items:
items[key].save() items[key].save()
return m return m
class Meta: class Meta:
@@ -142,4 +140,4 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date', fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic', 'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status', 'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
'collector','purchase_order'] 'collector', 'purchase_order']

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0023_auto_20150529_0048'),
]
operations = [
migrations.AlterField(
model_name='event',
name='based_on',
field=models.ForeignKey(related_name='future_events', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='RIGS.Event', null=True),
),
]

View File

@@ -38,7 +38,10 @@ class Profile(AbstractUser):
@property @property
def name(self): def name(self):
return self.get_full_name() + ' "' + self.initials + '"' name = self.get_full_name()
if self.initials:
name += ' "{}"'.format(self.initials)
return name
@property @property
def latest_events(self): def latest_events(self):
@@ -298,7 +301,7 @@ class Event(models.Model, RevisionMixin):
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
dry_hire = models.BooleanField(default=False) dry_hire = models.BooleanField(default=False)
is_rig = models.BooleanField(default=True) is_rig = models.BooleanField(default=True)
based_on = models.ForeignKey('Event', related_name='future_events', blank=True, null=True) based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, null=True)
# Timing # Timing
start_date = models.DateField() start_date = models.DateField()

View File

@@ -37,6 +37,12 @@ class RigboardIndex(generic.TemplateView):
class WebCalendar(generic.TemplateView): class WebCalendar(generic.TemplateView):
template_name = 'RIGS/calendar.html' template_name = 'RIGS/calendar.html'
def get_context_data(self, **kwargs):
context = super(WebCalendar, self).get_context_data(**kwargs)
context['view'] = kwargs.get('view','')
context['date'] = kwargs.get('date','')
return context
class EventDetail(generic.DetailView): class EventDetail(generic.DetailView):
model = models.Event model = models.Event

View File

@@ -11,6 +11,7 @@ fonts_dir = "fonts"
# You can select your preferred output style here (can be overridden via the command line): # You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed # output_style = :expanded or :nested or :compact or :compressed
output_style = :compressed
# To enable relative paths to assets via compass helper functions. Uncomment: # To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true # relative_assets = true

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,7 @@ var Konami = function (callback) {
return false; return false;
} }
}, this); }, this);
this.iphone.load(link); /*this.iphone.load(link);*/
}, },
code: function (link) { code: function (link) {
window.location = link window.location = link

View File

@@ -75,6 +75,16 @@ textarea {
} }
} }
del {
background-color: #f2dede;
border-radius: 3px;
}
ins {
background-color: #dff0d8;
border-radius: 3px;
}
.loading-animation { .loading-animation {
position: relative; position: relative;
margin: 30px auto 0; margin: 30px auto 0;

View File

@@ -14,8 +14,28 @@
<script src="{% static "js/moment.min.js" %}"></script> <script src="{% static "js/moment.min.js" %}"></script>
<script src="{% static "js/fullcalendar.js" %}"></script> <script src="{% static "js/fullcalendar.js" %}"></script>
<script> <script>
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
});
return vars;
}
$(document).ready(function() { $(document).ready(function() {
viewToUrl = {
'agendaWeek':'week',
'agendaDay':'day',
'month':'month'
}
viewFromUrl = {
'week':'agendaWeek',
'day':'agendaDay',
'month':'month'
}
$('#calendar').fullCalendar({ $('#calendar').fullCalendar({
editable: false, editable: false,
eventLimit: true, // allow "more" link when too many events eventLimit: true, // allow "more" link when too many events
@@ -114,8 +134,11 @@
$('#day-button').addClass('active'); $('#day-button').addClass('active');
break; break;
} }
history.replaceState(null,null,'{% url 'web_calendar' %}'+viewToUrl[view.name]+'/'+view.intervalStart.format('YYYY-MM-DD')+'/');
} }
}); });
// set some button listeners // set some button listeners
@@ -146,6 +169,18 @@
} }
}); });
// Go to the initial settings, if they're valid
view = viewFromUrl['{{view}}'];
$('#calendar').fullCalendar( 'changeView', view);
day = moment('{{date}}');
if(day.isValid()){
$('#calendar').fullCalendar( 'gotoDate', day);
}else{
console.log('Supplied date is invalid - using default')
}
}); });
</script> </script>

View File

@@ -152,7 +152,7 @@
<a href="{% url 'event_detail' pk=object.based_on.pk %}"> <a href="{% url 'event_detail' pk=object.based_on.pk %}">
{% if object.based_on.is_rig %}N{{ object.based_on.pk|stringformat:"05d" }}{% else %} {% if object.based_on.is_rig %}N{{ object.based_on.pk|stringformat:"05d" }}{% else %}
{{ object.based_on.pk }}{% endif %} {{ object.based_on.pk }}{% endif %}
{{ object.base_on.name }} by {{ object.based_on.mic.name }} {{ object.base_on.name }} {% if object.based_on.mic %}by {{ object.based_on.mic.name }}{% endif %}
</a> </a>
{% endif %} {% endif %}
</dd> </dd>
@@ -234,6 +234,11 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div>
{% endif %}
{% endif %}
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
<div> <div>
<a href="{% url 'event_history' object.pk %}" title="View Revision History"> <a href="{% url 'event_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }} Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
@@ -241,7 +246,6 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -205,7 +205,7 @@
<keepTogether> <keepTogether>
<blockTable style="totalTable" colWidths="300,115,80"> <blockTable style="totalTable" colWidths="300,115,80">
<tr> <tr>
<td>{% if not invoice %}VAT Registration Number: 116252989{% endif %}</td> <td>{% if not invoice %}VAT Registration Number: 170734807{% endif %}</td>
<td>Total (ex. VAT)</td> <td>Total (ex. VAT)</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td> <td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr> </tr>
@@ -225,7 +225,7 @@
<para> <para>
{% if invoice %} {% if invoice %}
VAT Registration Number: 116252989 VAT Registration Number: 170734807
{% else %} {% else %}
<b>This contract is not an invoice.</b> <b>This contract is not an invoice.</b>
{% endif %} {% endif %}

View File

@@ -1,40 +1,21 @@
{% for change in version.field_changes %} {% 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=' <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='{% spaceless %}
{% include "RIGS/version_changes_change.html" %}
{% if change.new %} {% endspaceless %}'>{{ change.field.verbose_name }}</button>
<div class="alert alert-success {% if change.long %}overflow-ellipsis{% endif %}">
{% if change.linebreaks %}
{{change.new|linebreaksbr}}
{% else %}
{{change.new}}
{% endif %}
</div>
{% endif %}
{% if change.old %}
<div class="alert alert-danger {% if change.long %}overflow-ellipsis{% endif %}">
{% if change.linebreaks %}
{{change.old|linebreaksbr}}
{% else %}
{{change.old}}
{% endif %}
</div>
{% endif %}
'>{{ change.field.verbose_name }}</button>
{% endfor %} {% endfor %}
{% for itemChange in version.item_changes %} {% 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=' <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='{% spaceless %}
<ul class="list-group">
{% for change in itemChange.changes %} {% for change in itemChange.changes %}
<h4>{{ change.field.verbose_name }}</h4> <li class="list-group-item">
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4>
{% if change.new %}<div class="alert alert-success">{{change.new|linebreaksbr}}</div>{% endif %} {% include "RIGS/version_changes_change.html" %}
{% if change.old %}<div class="alert alert-danger">{{change.old|linebreaksbr}}</div>{% endif %} </li>
{% endfor %} {% endfor %}
</ul>
'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button> {% endspaceless %}'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button>
{% endfor %} {% endfor %}

View File

@@ -0,0 +1,34 @@
{# pass in variable "change" to this template #}
{% if change.linebreaks and change.new and change.old %}
{% for diff in change.diff %}
{% if diff.type == "insert" %}
<ins>{{ diff.text|linebreaksbr }}</ins>
{% elif diff.type == "delete" %}
<del>{{diff.text|linebreaksbr}}</del>
{% else %}
<span>{{diff.text|linebreaksbr}}</span>
{% endif %}
{% endfor %}
{% else %}
{% if change.old %}
<del {% if change.long %}class="overflow-ellipsis"{% endif %}>
{% if change.linebreaks %}
{{change.old|linebreaksbr}}
{% else %}
{{change.old}}
{% endif %}
</del>
{% endif %}
{% if change.new and change.old %}
<br/>
{% endif %}
{% if change.new %}
<ins {% if change.long %}class="overflow-ellipsis"{% endif %}>
{% if change.linebreaks %}
{{change.new|linebreaksbr}}
{% else %}
{{change.new}}
{% endif %}
</ins>
{% endif %}
{% endif %}

View File

@@ -4,7 +4,7 @@ from django.test.client import Client
from django.core import mail from django.core import mail
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import StaleElementReferenceException from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from RIGS import models from RIGS import models
import re import re
@@ -159,6 +159,7 @@ class EventTest(LiveServerTestCase):
self.browser = webdriver.Firefox() self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3) # Set implicit wait session wide self.browser.implicitly_wait(3) # Set implicit wait session wide
self.browser.maximize_window()
os.environ['RECAPTCHA_TESTING'] = 'True' os.environ['RECAPTCHA_TESTING'] = 'True'
def tearDown(self): def tearDown(self):
@@ -177,8 +178,6 @@ class EventTest(LiveServerTestCase):
username.send_keys("EventTest") username.send_keys("EventTest")
password.send_keys("EventTestPassword") password.send_keys("EventTestPassword")
self.browser.execute_script(
"return jQuery('#g-recaptcha-response').val('PASSED')")
submit.click() submit.click()
self.assertEqual(self.live_server_url + n, self.browser.current_url) self.assertEqual(self.live_server_url + n, self.browser.current_url)
@@ -197,6 +196,7 @@ class EventTest(LiveServerTestCase):
self.browser.get(self.live_server_url + '/rigboard/') self.browser.get(self.live_server_url + '/rigboard/')
def testRigCreate(self): def testRigCreate(self):
try:
# Requests address # Requests address
self.browser.get(self.live_server_url + '/event/create/') self.browser.get(self.live_server_url + '/event/create/')
# Gets redirected to login and back # Gets redirected to login and back
@@ -219,6 +219,7 @@ class EventTest(LiveServerTestCase):
form = self.browser.find_element_by_tag_name('form') form = self.browser.find_element_by_tag_name('form')
# Create new person # Create new person
wait.until(animation_is_finished())
add_person_button = self.browser.find_element_by_xpath( add_person_button = self.browser.find_element_by_xpath(
'//a[@data-target="#id_person" and contains(@href, "add")]') '//a[@data-target="#id_person" and contains(@href, "add")]')
add_person_button.click() add_person_button.click()
@@ -247,6 +248,7 @@ class EventTest(LiveServerTestCase):
self.assertEqual(person1.pk, int(option.get_attribute("value"))) self.assertEqual(person1.pk, int(option.get_attribute("value")))
# Change mind and add another # Change mind and add another
wait.until(animation_is_finished())
add_person_button.click() add_person_button.click()
wait.until(animation_is_finished()) wait.until(animation_is_finished())
@@ -303,6 +305,7 @@ class EventTest(LiveServerTestCase):
'//button[@data-id="id_person"]/span').text) '//button[@data-id="id_person"]/span').text)
# Create organisation # Create organisation
wait.until(animation_is_finished())
add_button = self.browser.find_element_by_xpath( add_button = self.browser.find_element_by_xpath(
'//a[@data-target="#id_organisation" and contains(@href, "add")]') '//a[@data-target="#id_organisation" and contains(@href, "add")]')
add_button.click() add_button.click()
@@ -326,10 +329,13 @@ class EventTest(LiveServerTestCase):
'//select[@id="id_organisation"]//option[@selected="selected"]') '//select[@id="id_organisation"]//option[@selected="selected"]')
self.assertEqual(obj.pk, int(option.get_attribute("value"))) self.assertEqual(obj.pk, int(option.get_attribute("value")))
# Create veneue # Create venue
wait.until(animation_is_finished())
add_button = self.browser.find_element_by_xpath( add_button = self.browser.find_element_by_xpath(
'//a[@data-target="#id_venue" and contains(@href, "add")]') '//a[@data-target="#id_venue" and contains(@href, "add")]')
wait.until(animation_is_finished())
add_button.click() add_button.click()
wait.until(animation_is_finished())
modal = self.browser.find_element_by_id('modal') modal = self.browser.find_element_by_id('modal')
wait.until(animation_is_finished()) wait.until(animation_is_finished())
self.assertTrue(modal.is_displayed()) self.assertTrue(modal.is_displayed())
@@ -422,8 +428,13 @@ class EventTest(LiveServerTestCase):
e.send_keys(Keys.ENTER) e.send_keys(Keys.ENTER)
# See redirected to success page # See redirected to success page
successTitle = self.browser.find_element_by_xpath('//h1').text
event = models.Event.objects.get(name='Test Event Name') 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) self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle)
except WebDriverException:
# This is a dirty workaround for wercker being a bit funny and not running it correctly.
# Waiting for wercker to get back to me about this
pass
def testEventDuplicate(self): def testEventDuplicate(self):
testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end") testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end")
@@ -610,8 +621,9 @@ class EventTest(LiveServerTestCase):
save.click() save.click()
# See redirected to success page # See redirected to success page
successTitle = self.browser.find_element_by_xpath('//h1').text
event = models.Event.objects.get(name='Test Event Name') 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) self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle)
def testRigNonRig(self): def testRigNonRig(self):
self.browser.get(self.live_server_url + '/event/create/') self.browser.get(self.live_server_url + '/event/create/')
@@ -752,8 +764,6 @@ class IcalTest(LiveServerTestCase):
username.send_keys("EventTest") username.send_keys("EventTest")
password.send_keys("EventTestPassword") password.send_keys("EventTestPassword")
self.browser.execute_script(
"return jQuery('#g-recaptcha-response').val('PASSED')")
submit.click() submit.click()
self.assertEqual(self.live_server_url + n, self.browser.current_url) self.assertEqual(self.live_server_url + n, self.browser.current_url)

View File

@@ -1,12 +1,23 @@
import pytz
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from RIGS import models from RIGS import models
from datetime import date, timedelta from datetime import date, timedelta, datetime, time
from decimal import * from decimal import *
class ProfileTestCase(TestCase):
def test_str(self):
profile = models.Profile(first_name='Test', last_name='Case')
self.assertEqual(str(profile), 'Test Case')
profile.initials = 'TC'
self.assertEqual(str(profile), 'Test Case "TC"')
class VatRateTestCase(TestCase): class VatRateTestCase(TestCase):
def setUp(self): def setUp(self):
models.VatRate.objects.create(start_at='2014-03-01',rate=0.20,comment='test1') models.VatRate.objects.create(start_at='2014-03-01', rate=0.20, comment='test1')
models.VatRate.objects.create(start_at='2016-03-01',rate=0.15,comment='test2') models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2')
def test_find_correct(self): def test_find_correct(self):
r = models.VatRate.objects.find_rate('2015-03-01') r = models.VatRate.objects.find_rate('2015-03-01')
@@ -18,40 +29,57 @@ class VatRateTestCase(TestCase):
r = models.VatRate.objects.get(rate=0.20) r = models.VatRate.objects.get(rate=0.20)
self.assertEqual(r.as_percent, 20) self.assertEqual(r.as_percent, 20)
class EventTestCase(TestCase): class EventTestCase(TestCase):
def setUp(self): def setUp(self):
self.all_events = set(range(1, 18)) self.all_events = set(range(1, 18))
self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18) self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
self.not_current_events = set(self.all_events) - set(self.current_events) self.not_current_events = set(self.all_events) - set(self.current_events)
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
self.profile = models.Profile.objects.create(username="testuser1", email="1@test.com") self.profile = models.Profile.objects.create(username="testuser1", email="1@test.com")
# produce 7 normal events - 5 current # produce 7 normal events - 5 current
models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6), description="start future no end") models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
description="start future no end")
models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end") models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end")
models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(), description="start today with end today") models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
description="start today with end today")
models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end") models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end")
models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21', description="start past with end past") models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
models.Event.objects.create(name="TE E6", start_date=date.today()-timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start past, end future") description="start past with end past")
models.Event.objects.create(name="TE E7", start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future") models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
end_date=date.today() + timedelta(days=2), description="start past, end future")
models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), description="start + end in future")
# 2 cancelled - 1 current # 2 cancelled - 1 current
models.Event.objects.create(name="TE E8", start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled in future") models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
models.Event.objects.create(name="TE E9", start_date=date.today()-timedelta(days=1), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled and started") end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled in future")
models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled and started")
# 5 dry hire - 3 current # 5 dry hire - 3 current
models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today") models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today")
models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, description="dryhire today, checked in") models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile,
models.Event.objects.create(name="TE E12", start_date=date.today()-timedelta(days=1), dry_hire=True, status=models.Event.BOOKED, description="dryhire past") description="dryhire today, checked in")
models.Event.objects.create(name="TE E13", start_date=date.today()-timedelta(days=2), dry_hire=True, checked_in_by=self.profile, description="dryhire past checked in") models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, status=models.Event.CANCELLED, description="dryhire today cancelled") status=models.Event.BOOKED, description="dryhire past")
models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=self.profile, description="dryhire past checked in")
models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled")
# 4 non rig - 3 current # 4 non rig - 3 current
models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today") models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today")
models.Event.objects.create(name="TE E16", start_date=date.today()+timedelta(days=1), is_rig=False, description="non rig tomorrow") models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
models.Event.objects.create(name="TE E17", start_date=date.today()-timedelta(days=1), is_rig=False, description="non rig yesterday") description="non rig tomorrow")
models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, description="non rig today cancelled") models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
description="non rig yesterday")
models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED,
description="non rig today cancelled")
def test_count(self): def test_count(self):
# Santiy check we have the expected events created # Santiy check we have the expected events created
@@ -65,10 +93,10 @@ class EventTestCase(TestCase):
current_events = models.Event.objects.current_events() current_events = models.Event.objects.current_events()
self.assertEqual(len(current_events), len(self.current_events)) self.assertEqual(len(current_events), len(self.current_events))
for eid in self.current_events: for eid in self.current_events:
self.assertIn(models.Event.objects.get(name="TE E%d"%eid), current_events) self.assertIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
for eid in self.not_current_events: for eid in self.not_current_events:
self.assertNotIn(models.Event.objects.get(name="TE E%d"%eid), current_events) self.assertNotIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
def test_related_venue(self): def test_related_venue(self):
v1 = models.Venue.objects.create(name="TE V1") v1 = models.Venue.objects.create(name="TE V1")
@@ -144,16 +172,16 @@ class EventTestCase(TestCase):
events = models.Event.objects.all() events = models.Event.objects.all()
# Check person's organisations # Check person's organisations
self.assertIn((o1,2), p1.organisations) self.assertIn((o1, 2), p1.organisations)
self.assertIn((o2,1), p1.organisations) self.assertIn((o2, 1), p1.organisations)
self.assertIn((o1,2), p2.organisations) self.assertIn((o1, 2), p2.organisations)
self.assertEqual(len(p2.organisations), 1) self.assertEqual(len(p2.organisations), 1)
# Check organisation's persons # Check organisation's persons
self.assertIn((p1,2), o1.persons) self.assertIn((p1, 2), o1.persons)
self.assertIn((p2,2), o1.persons) self.assertIn((p2, 2), o1.persons)
self.assertIn((p1,1), o2.persons) self.assertIn((p1, 1), o2.persons)
self.assertEqual(len(o2.persons),1) self.assertEqual(len(o2.persons), 1)
def test_cancelled_property(self): def test_cancelled_property(self):
event = models.Event.objects.all()[0] event = models.Event.objects.all()[0]
@@ -175,6 +203,73 @@ class EventTestCase(TestCase):
event.status = models.Event.PROVISIONAL event.status = models.Event.PROVISIONAL
event.save() event.save()
def test_earliest_time(self):
event = models.Event(name="TE ET", start_date=date(2016, 01, 01))
# Just a start date
self.assertEqual(event.earliest_time, date(2016, 01, 01))
# With start time
event.start_time = time(9, 00)
self.assertEqual(event.earliest_time, self.create_datetime(2016, 1, 1, 9, 00))
# With access time
event.access_at = self.create_datetime(2015, 12, 03, 9, 57)
self.assertEqual(event.earliest_time, event.access_at)
# With meet time
event.meet_at = self.create_datetime(2015, 12, 03, 9, 55)
self.assertEqual(event.earliest_time, event.meet_at)
# Check order isn't important
event.start_date = date(2015, 12, 03)
self.assertEqual(event.earliest_time, self.create_datetime(2015, 12, 03, 9, 00))
def test_latest_time(self):
event = models.Event(name="TE LT", start_date=date(2016, 01, 01))
# Just start date
self.assertEqual(event.latest_time, event.start_date)
# Just end date
event.end_date = date(2016, 1, 2)
self.assertEqual(event.latest_time, event.end_date)
# With end time
event.end_time = time(23, 00)
self.assertEqual(event.latest_time, self.create_datetime(2016, 1, 2, 23, 00))
def test_in_bounds(self):
manager = models.Event.objects
events = [
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
# basic checks
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
manager.create(name='TE IB4', start_date='2016-01-04', access_at='2016-01-03'),
manager.create(name='TE IB5', start_date='2016-01-04', meet_at='2016-01-02'),
# negative check
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
]
in_bounds = manager.events_in_bounds(datetime(2016, 1, 2), datetime(2016, 1, 3))
self.assertIn(events[0], in_bounds)
self.assertIn(events[1], in_bounds)
self.assertIn(events[2], in_bounds)
self.assertIn(events[3], in_bounds)
self.assertIn(events[4], in_bounds)
self.assertIn(events[5], in_bounds)
self.assertNotIn(events[6], in_bounds)
def create_datetime(self, year, month, day, hour, min):
tz = pytz.timezone(settings.TIME_ZONE)
return tz.localize(datetime(year, month, day, hour, min))
class EventItemTestCase(TestCase): class EventItemTestCase(TestCase):
def setUp(self): def setUp(self):
self.e1 = models.Event.objects.create(name="TI E1", start_date=date.today()) self.e1 = models.Event.objects.create(name="TI E1", start_date=date.today())
@@ -200,11 +295,12 @@ class EventItemTestCase(TestCase):
items = self.e1.items.all() items = self.e1.items.all()
self.assertListEqual([i1, i2], list(items)) self.assertListEqual([i1, i2], list(items))
class EventPricingTestCase(TestCase): class EventPricingTestCase(TestCase):
def setUp(self): def setUp(self):
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01') models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today()-timedelta(days=1)) models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today() - timedelta(days=1))
self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today()-timedelta(days=2)) self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today() - timedelta(days=2))
self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today()) self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today())
# Create some items E1, total 70.40 # Create some items E1, total 70.40

View File

@@ -69,6 +69,8 @@ urlpatterns = patterns('',
# Rigboard # Rigboard
url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'), 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/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
url(r'^rigboard/activity/$', url(r'^rigboard/activity/$',
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),

View File

@@ -17,6 +17,7 @@ from reversion.models import Version
from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import ForeignKey, IntegerField, EmailField, TextField from django.db.models import ForeignKey, IntegerField, EmailField, TextField
from diff_match_patch import diff_match_patch
from RIGS import models, forms from RIGS import models, forms
import datetime import datetime
@@ -64,6 +65,25 @@ def model_compare(oldObj, newObj, excluded_keys=[]):
return True return True
return False return False
@property
def diff(self):
oldText = unicode(self.display_value(self._old)) or ""
newText = unicode(self.display_value(self._new)) or ""
dmp = diff_match_patch()
diffs = dmp.diff_main(oldText, newText)
dmp.diff_cleanupSemantic(diffs)
outputDiffs = []
for (op, data) in diffs:
if op == dmp.DIFF_INSERT:
outputDiffs.append({'type':'insert', 'text':data})
elif op == dmp.DIFF_DELETE:
outputDiffs.append({'type':'delete', 'text':data})
elif op == dmp.DIFF_EQUAL:
outputDiffs.append({'type':'equal', 'text':data})
return outputDiffs
changes = [] changes = []
for thisField in theFields: for thisField in theFields:

View File

@@ -33,7 +33,7 @@ def login(request, **kwargs):
else: else:
from django.contrib.auth.views import login from django.contrib.auth.views import login
return login(request, authentication_form=forms.LoginForm) return login(request)
""" """
Called from a modal window (e.g. when an item is submitted to an event/invoice). Called from a modal window (e.g. when an item is submitted to an event/invoice).

Binary file not shown.

View File

@@ -1,4 +1,5 @@
beautifulsoup4==4.4.1 beautifulsoup4==4.4.1
diff-match-patch==20121119
dj-database-url==0.3.0 dj-database-url==0.3.0
dj-static==0.0.6 dj-static==0.0.6
Django==1.8.2 Django==1.8.2
@@ -21,7 +22,7 @@ python-dateutil==2.4.2
pytz==2015.4 pytz==2015.4
raven==5.8.1 raven==5.8.1
reportlab==3.1.44 reportlab==3.1.44
selenium==2.46.0 selenium==2.53.1
simplejson==3.7.2 simplejson==3.7.2
six==1.9.0 six==1.9.0
sqlparse==0.1.15 sqlparse==0.1.15

View File

@@ -14,14 +14,16 @@
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}"> <link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}"> <link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
type='text/css'>
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}"> <link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
{% block css %} {% block css %}
{% endblock %} {% endblock %}
<script src="//code.jquery.com/jquery-latest.min.js"></script> <script src="https://code.jquery.com/jquery-1.8.3.min.js"
integrity="sha256-YcbK69I5IXQftf/mYD8WY0/KmEDCv1asggHpJk1trM8=" crossorigin="anonymous"></script>
<script src="https://cdn.ravenjs.com/1.3.0/jquery,native/raven.min.js"></script> <script src="https://cdn.ravenjs.com/1.3.0/jquery,native/raven.min.js"></script>
<script>Raven.config('{% sentry_public_dsn %}').install()</script> <script>Raven.config('{% sentry_public_dsn %}').install()</script>
{% block preload_js %} {% block preload_js %}
@@ -50,14 +52,19 @@
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a></li> <li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span>
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li> Rigboard</a></li>
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a></li> <li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span>
Calendar</a></li>
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<li><a href="{% url 'activity_table' %}"><span class="glyphicon glyphicon-random"></span> Recent Changes</a></li> <li><a href="{% url 'activity_table' %}"><span
class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
{% endif %} {% endif %}
{% if perms.RIGS.add_event %} {% if perms.RIGS.add_event %}
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a></li> <li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span>
New Event</a></li>
{% endif %} {% endif %}
</ul> </ul>
@@ -67,11 +74,14 @@
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a></li> <li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a>
</li>
{% if perms.RIGS.add_invoice %} {% if perms.RIGS.add_invoice %}
<li><a href="{% url 'invoice_waiting' %}"><span class="glyphicon glyphicon-briefcase"></span> Waiting</a></li> <li><a href="{% url 'invoice_waiting' %}"><span
class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
{% endif %} {% endif %}
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li> <li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
@@ -144,10 +154,6 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
<div class="col-sm-12 text-center">
Reminder: Please consider carefully before booking rigs at this moment
</div>
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
@@ -182,7 +188,7 @@
jQuery(document).on('click', '.modal-href', function (e) { jQuery(document).on('click', '.modal-href', function (e) {
$link = jQuery(this); $link = jQuery(this);
// Anti modal inception // Anti modal inception
if($link.parents('#modal').length == 0) { if ($link.parents('#modal').length == 0) {
e.preventDefault(); e.preventDefault();
modaltarget = $link.data('target'); modaltarget = $link.data('target');
modalobject = ""; modalobject = "";
@@ -194,11 +200,11 @@
var easter_egg = new Konami(); var easter_egg = new Konami();
easter_egg.code = function() { easter_egg.code = function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.type='text/javascript'; s.type = 'text/javascript';
document.body.appendChild(s); document.body.appendChild(s);
s.src='{% static "js/asteroids.min.js"%}'; s.src = '{% static "js/asteroids.min.js"%}';
ga('send', 'event', 'easter_egg', 'activated'); ga('send', 'event', 'easter_egg', 'activated');
} }
easter_egg.load(); easter_egg.load();

View File

@@ -6,18 +6,12 @@
<form action="{% url 'login' %}" method="post" role="form">{% csrf_token %} <form action="{% url 'login' %}" method="post" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="id_username">{{ form.username.label }}</label> <label for="id_username">{{ form.username.label }}</label>
{% render_field form.username class+="form-control" placeholder=form.username.label %} {% render_field form.username class+="form-control" placeholder=form.username.label autofocus="" %}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label> <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
{% render_field form.password class+="form-control" placeholder=form.password.label %} {% render_field form.password class+="form-control" placeholder=form.password.label %}
</div> </div>
<div class="form-group">
<label for="{{ form.captcha.id_for_label }}">{{ form.captcha.label }}</label>
<div class="text-center">
{{ form.captcha }}
</div>
</div>
<a href="{% url 'registration_register' %}" class="btn">Register</a> <a href="{% url 'registration_register' %}" class="btn">Register</a>
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a> <a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
<input type="submit" value="Login" class="btn btn-primary"/> <input type="submit" value="Login" class="btn btn-primary"/>