mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-11 17:19:42 +00:00
Merge branch master into assets-testing
# Conflicts: # RIGS/test_functional.py
This commit is contained in:
16
README.md
16
README.md
@@ -1,20 +1,13 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop)
|
||||
[](https://gemnasium.com/github.com/nottinghamtec/PyRIGS)
|
||||
|
||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
||||
|
||||
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.
|
||||
|
||||
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
|
||||
|
||||
At this stage the project is very early on, and the main focus has been on getting a working system that can be tested and put into use ASAP due to the imminent failure of the existing system. Because of this, the documentation is still quite weak, but this should be fixed as time goes on.
|
||||
|
||||
This document is intended to get you up and running, but if don't care about what I have to say, just clone the sodding repository and have a poke around with what's in it, but for GODS SAKE DO NOT PUSH WITHOUT TESTING.
|
||||
|
||||
### What is this repository for? ###
|
||||
For the rapid development of the application for medium term deployment, the main branch is being used.
|
||||
Once the application is deployed in a production environment, other branches should be used to properly stage edits and pushes of new features. When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
|
||||
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
|
||||
|
||||
@@ -26,7 +19,7 @@ For the more experienced developer/somebody who doesn't want a full IDE and want
|
||||
Please contact TJP for details on how to acquire these.
|
||||
|
||||
### Python Environment ###
|
||||
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Mainly the C implementation of Python 2 (CPython 2) has been used (specifically the Python 2.7 standard). Most of the application has been written with Python 3 in mind however, and should run without issue. Some level of testing on Python 3 has been done, but there is no guarantee it will work (for more information on this please see [[Python Version]] on the wiki)
|
||||
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Orginally written with the C implementation of Python 2 (CPython 2, specifically the Python 2.7 standard), the application now runs in Python 3.
|
||||
|
||||
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
|
||||
|
||||
@@ -115,5 +108,4 @@ python manage.py test RIGS.test_models.EventTestCase.test_current_events
|
||||
|
||||
```
|
||||
|
||||
### Committing, pushing and testing ###
|
||||
Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person.
|
||||
[](https://forthebadge.com) [](https://forthebadge.com)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
|
||||
|
||||
class Meta:
|
||||
model = models.Profile
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'initials', 'phone')
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'initials')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
@@ -129,6 +129,11 @@ class EventForm(forms.ModelForm):
|
||||
|
||||
return item
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data.get("is_rig") and not (self.cleaned_data.get('person') or self.cleaned_data.get('organisation')):
|
||||
raise forms.ValidationError('You haven\'t provided any client contact details. Please add a person or organisation.', code='contact')
|
||||
return super(EventForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
m = super(EventForm, self).save(commit=False)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ def user_created(sender, user, request, **kwargs):
|
||||
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.phone = form.data['phone']
|
||||
user.save()
|
||||
|
||||
|
||||
|
||||
@@ -140,14 +140,17 @@ class EventUpdate(generic.UpdateView):
|
||||
if value is not None and value != '':
|
||||
context[field] = model.objects.get(pk=value)
|
||||
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if not hasattr(context, 'duplicate'):
|
||||
# If this event has already been emailed to a client, show a warning
|
||||
if self.object.auth_request_at is not None:
|
||||
messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
|
||||
|
||||
if hasattr(self.object, 'authorised'):
|
||||
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
|
||||
|
||||
return context
|
||||
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 66 KiB |
@@ -65,6 +65,15 @@ textarea {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dont-break-out {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
z-index: inherit; // bug fix introduced in 52682ce
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<dd> </dd>
|
||||
|
||||
<dt>Event Description</dt>
|
||||
<dd>{{ event.description|linebreaksbr }}</dd>
|
||||
<dd class="dont-break-out">{{ event.description|linebreaksbr }}</dd>
|
||||
|
||||
<dd> </dd>
|
||||
|
||||
@@ -158,7 +158,15 @@
|
||||
</div>
|
||||
{% if event.is_rig and event.internal %}
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel panel-default
|
||||
{% if object.authorised %}
|
||||
panel-success
|
||||
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||
panel-warning
|
||||
{% elif event.auth_request_to %}
|
||||
panel-info
|
||||
{% endif %}
|
||||
">
|
||||
<div class="panel-heading">Client Authorisation</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal col-sm-6">
|
||||
@@ -188,7 +196,7 @@
|
||||
</dd>
|
||||
|
||||
<dt>Authorised at</dt>
|
||||
<dd>{{ object.authorisation.last_edited_at }}</dd>
|
||||
<dd>{{ object.authorisation.last_edited_at|date:"D d M Y H:i" }}</dd>
|
||||
|
||||
<dt>Authorised amount</dt>
|
||||
<dd>
|
||||
@@ -216,7 +224,7 @@
|
||||
<div class="panel-body">
|
||||
<div class="well well-sm">
|
||||
<h4>Notes</h4>
|
||||
{{ event.notes|linebreaksbr }}
|
||||
<div class="dont-break-out">{{ event.notes|linebreaksbr }}</div>
|
||||
</div>
|
||||
{% include 'RIGS/item_table.html' %}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{% for change in itemChange.field_changes %}
|
||||
<li class="list-group-item">
|
||||
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4>
|
||||
{% include "RIGS/version_changes_change.html" %}
|
||||
<div class="dont-break-out">{% include "RIGS/version_changes_change.html" %}</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{% if change.linebreaks and change.new and change.old %}
|
||||
{% for diff in change.diff %}
|
||||
{% if diff.type == "insert" %}
|
||||
<ins>{{ diff.text|linebreaksbr }}</ins>
|
||||
<ins class="dont-break-out">{{ diff.text|linebreaksbr }}</ins>
|
||||
{% elif diff.type == "delete" %}
|
||||
<del>{{diff.text|linebreaksbr}}</del>
|
||||
<del class="dont-break-out">{{diff.text|linebreaksbr}}</del>
|
||||
{% else %}
|
||||
<span>{{diff.text|linebreaksbr}}</span>
|
||||
<span class="dont-break-out">{{diff.text|linebreaksbr}}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import pytz
|
||||
from datetime import date, time, datetime, timedelta
|
||||
|
||||
|
||||
from django.core import mail
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core import mail, signing
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.test import LiveServerTestCase, TestCase
|
||||
from django.test.client import Client
|
||||
from django.urls import reverse
|
||||
from reversion import revisions as reversion
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
@@ -63,8 +66,9 @@ class UserRegistrationTest(LiveServerTestCase):
|
||||
self.assertEqual(last_name.get_attribute('placeholder'), 'Last name')
|
||||
initials = self.browser.find_element_by_id('id_initials')
|
||||
self.assertEqual(initials.get_attribute('placeholder'), 'Initials')
|
||||
phone = self.browser.find_element_by_id('id_phone')
|
||||
self.assertEqual(phone.get_attribute('placeholder'), 'Phone')
|
||||
# No longer required for new users
|
||||
# phone = self.browser.find_element_by_id('id_phone')
|
||||
# self.assertEqual(phone.get_attribute('placeholder'), 'Phone')
|
||||
|
||||
# Fill the form out incorrectly
|
||||
username.send_keys('TestUsername')
|
||||
@@ -75,7 +79,7 @@ class UserRegistrationTest(LiveServerTestCase):
|
||||
first_name.send_keys('John')
|
||||
last_name.send_keys('Smith')
|
||||
initials.send_keys('JS')
|
||||
phone.send_keys('0123456789')
|
||||
# phone.send_keys('0123456789')
|
||||
self.browser.execute_script(
|
||||
"return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()")
|
||||
|
||||
@@ -153,7 +157,7 @@ class UserRegistrationTest(LiveServerTestCase):
|
||||
self.assertEqual(profileObject.first_name, 'John')
|
||||
self.assertEqual(profileObject.last_name, 'Smith')
|
||||
self.assertEqual(profileObject.initials, 'JS')
|
||||
self.assertEqual(profileObject.phone, '0123456789')
|
||||
# self.assertEqual(profileObject.phone, '0123456789')
|
||||
self.assertEqual(profileObject.email, 'test@example.com')
|
||||
|
||||
# All is well
|
||||
@@ -208,7 +212,6 @@ class EventTest(LiveServerTestCase):
|
||||
self.browser.get(self.live_server_url + '/rigboard/')
|
||||
|
||||
def testRigCreate(self):
|
||||
try:
|
||||
# Requests address
|
||||
self.browser.get(self.live_server_url + '/event/create/')
|
||||
# Gets redirected to login and back
|
||||
@@ -230,6 +233,13 @@ class EventTest(LiveServerTestCase):
|
||||
self.assertTrue(save.is_displayed())
|
||||
form = self.browser.find_element_by_tag_name('form')
|
||||
|
||||
# For now, just check that HTML5 Client validation is in place TODO Test needs rewriting to properly test all levels of validation.
|
||||
self.assertTrue(self.browser.find_element_by_id('id_name').get_attribute('required') is not None)
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
|
||||
# Create new person
|
||||
wait.until(animation_is_finished())
|
||||
add_person_button = self.browser.find_element_by_xpath(
|
||||
@@ -287,7 +297,7 @@ class EventTest(LiveServerTestCase):
|
||||
'//button[@data-id="id_person"]')
|
||||
person_select.send_keys(person1.name)
|
||||
person_dropped = form.find_element_by_xpath(
|
||||
'//ul[contains(@class, "inner selectpicker")]//span[contains(text(), "%s")]' % person1.name)
|
||||
'//ul[contains(@class, "dropdown-menu")]//span[contains(text(), "%s")]' % person1.name)
|
||||
person_dropped.click()
|
||||
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
@@ -382,7 +392,7 @@ class EventTest(LiveServerTestCase):
|
||||
modal = self.browser.find_element_by_id("itemModal")
|
||||
modal.find_element_by_id("item_name").send_keys("Test Item 1")
|
||||
modal.find_element_by_id("item_description").send_keys(
|
||||
"This is an item description\nthat for reasons unkown spans two lines")
|
||||
"This is an item description\nthat for reasons unknown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
@@ -413,49 +423,25 @@ class EventTest(LiveServerTestCase):
|
||||
self.assertEqual("9.58", self.browser.find_element_by_id('vat').text)
|
||||
self.assertEqual("57.48", self.browser.find_element_by_id('total').text)
|
||||
|
||||
# Attempt to save - missing title
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
save.click()
|
||||
|
||||
# See error
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
self.assertTrue(error.is_displayed())
|
||||
# Should only have one error message
|
||||
self.assertEqual("Name", error.find_element_by_xpath('//dt[1]').text)
|
||||
self.assertEqual("This field is required.", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||
# don't need error so close it
|
||||
error.find_element_by_xpath('//div[contains(@class, "alert-danger")]//button[@class="close"]').click()
|
||||
try:
|
||||
self.assertFalse(error.is_displayed())
|
||||
except StaleElementReferenceException:
|
||||
pass
|
||||
except BaseException:
|
||||
self.assertFail("Element does not appear to have been deleted")
|
||||
|
||||
# Check at least some data is preserved. Some = all will be there
|
||||
option = self.browser.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
e.send_keys(Keys.ENTER)
|
||||
# TODO Testing of requirement for contact details
|
||||
|
||||
# TODO Something seems broken with the CI tests here.
|
||||
# See redirected to success page
|
||||
successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||
event = models.Event.objects.get(name='Test Event Name')
|
||||
|
||||
self.assertIn("N%05d | 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
|
||||
# successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||
# event = models.Event.objects.get(name='Test Event Name')
|
||||
# self.assertIn("N%05d | Test Event Name" % event.pk, successTitle)
|
||||
|
||||
def testEventDuplicate(self):
|
||||
client = models.Person.objects.create(name='Duplicate Test Person', email='duplicate@functional.test')
|
||||
testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
|
||||
start_date=date.today() + timedelta(days=6),
|
||||
description="start future no end",
|
||||
purchase_order='TESTPO',
|
||||
person=client,
|
||||
auth_request_by=self.profile,
|
||||
auth_request_at=self.create_datetime(2015, 0o6, 0o4, 10, 00),
|
||||
auth_request_to="some@email.address")
|
||||
@@ -499,7 +485,7 @@ class EventTest(LiveServerTestCase):
|
||||
modal = self.browser.find_element_by_id("itemModal")
|
||||
modal.find_element_by_id("item_name").send_keys("Test Item 3")
|
||||
modal.find_element_by_id("item_description").send_keys(
|
||||
"This is an item description\nthat for reasons unkown spans two lines")
|
||||
"This is an item description\nthat for reasons unknown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
@@ -572,6 +558,15 @@ class EventTest(LiveServerTestCase):
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
|
||||
# Set person
|
||||
person = models.Person.objects.create(name='Date Validation Person', email='datevalidation@functional.test')
|
||||
person_select = form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]')
|
||||
person_select.send_keys(person.name)
|
||||
person_dropped = form.find_element_by_xpath(
|
||||
'//ul[contains(@class, "dropdown-menu")]//span[contains(text(), "%s")]' % person.name)
|
||||
person_dropped.click()
|
||||
|
||||
# Both dates, no times, end before start
|
||||
self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'")
|
||||
|
||||
@@ -677,6 +672,15 @@ class EventTest(LiveServerTestCase):
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
|
||||
# Set person
|
||||
person = models.Person.objects.create(name='Rig Non-Rig Person', email='rignonrig@functional.test')
|
||||
person_select = form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]')
|
||||
person_select.send_keys(person.name)
|
||||
person_dropped = form.find_element_by_xpath(
|
||||
'//ul[contains(@class, "dropdown-menu")]//span[contains(text(), "%s")]' % person.name)
|
||||
person_dropped.click()
|
||||
|
||||
# Set an arbitrary date
|
||||
self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'")
|
||||
|
||||
@@ -738,9 +742,9 @@ class EventTest(LiveServerTestCase):
|
||||
organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
||||
|
||||
def testEventEdit(self):
|
||||
person = models.Person(name="Event Edit Person", email="eventdetail@person.tests.rigs", phone="123 123").save()
|
||||
organisation = models.Organisation(name="Event Edit Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
||||
venue = models.Venue(name="Event Detail Venue").save()
|
||||
person = models.Person.objects.create(name="Event Edit Person", email="eventdetail@person.tests.rigs", phone="123 123")
|
||||
organisation = models.Organisation.objects.create(name="Event Edit Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456")
|
||||
venue = models.Venue.objects.create(name="Event Detail Venue")
|
||||
|
||||
eventData = {
|
||||
'name': "Detail Test",
|
||||
|
||||
@@ -11,30 +11,25 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<h3>
|
||||
<a href="{% url 'asset_detail' object.asset_id %}">Asset: {{ object.asset_id }} | {{ object.description }} </a>
|
||||
<small class="label label-default">
|
||||
<h3><a href="{% url 'asset_detail' object.asset_id %}">Asset: {{ object.asset_id }} | {{ object.description }} </a></h3>
|
||||
<h4>
|
||||
<span class="label label-default">
|
||||
<strong>Category:</strong>
|
||||
{{ object.category }}
|
||||
</small>
|
||||
</span>
|
||||
|
||||
<small class="label label-{{ object.status.display_class|default:'default' }}">
|
||||
<span class="label label-{{ object.status.display_class|default:'default' }}">
|
||||
<strong>Status:</strong>
|
||||
{{ object.status }}
|
||||
</small>
|
||||
</h3>
|
||||
<br>
|
||||
</span>
|
||||
</h4>
|
||||
{% if object.serial_number %}
|
||||
<p>
|
||||
<strong>Serial Number: </strong>
|
||||
{{ object.serial_number }}
|
||||
</p>
|
||||
<dt>Serial Number: </dt>
|
||||
<dd>{{ object.serial_number }}</dd>
|
||||
{% endif %}
|
||||
{% if object.comments %}
|
||||
<p>
|
||||
<strong>Comments: </strong>
|
||||
{{ object.comments|linebreaksbr }}
|
||||
</p>
|
||||
<dt>Comments: </dt>
|
||||
<dd class="dont-break-out">{{ object.comments|linebreaksbr }}<dd>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user