mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-03-21 11:25:56 +00:00
Compare commits
129 Commits
training_l
...
hotfix/for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a93f337d17 | ||
|
|
bd0832f284 | ||
|
|
567f899a39 | ||
|
|
024f08562b | ||
|
|
b4246fe170 | ||
|
|
fc6db5bff2 | ||
|
|
c5d3e7c0f2 | ||
|
|
10b57adb37 | ||
|
|
4f839d05f9 | ||
|
|
84393e9e4a | ||
|
|
b94cef92d2 | ||
|
|
10c04be051 | ||
|
|
7ecd6212ac | ||
|
|
11180e507c | ||
|
|
1c90ce5b41 | ||
|
|
11dd9ad02f | ||
|
|
27c0deaba3 | ||
|
|
4c09258566 | ||
|
|
7d14429d84 | ||
|
|
190825f5ef | ||
|
|
021edfd39d | ||
|
|
3052f28329 | ||
|
|
4cec20e357 | ||
|
|
8a0cbe32bd | ||
|
|
8a3a52a21b | ||
|
|
98a9b22e0e | ||
|
|
9178cf6062 | ||
|
|
abbb20e49e | ||
|
|
01d2eae7bc | ||
|
|
39d27d2730 | ||
|
|
05b2de561e | ||
|
|
667b0c80ca | ||
|
|
67624eea6f | ||
|
|
e1578eb0d4 | ||
|
|
a7247c273e | ||
|
|
f265da2f1d | ||
|
|
1163b117e4 | ||
|
|
f92f418bc5 | ||
|
|
9108cb3c4e | ||
|
|
08d17adc8a | ||
|
|
68b35c2d24 | ||
|
|
5cc69cbb41 | ||
|
|
a48afb9157 | ||
|
|
3ccbdff737 | ||
|
|
0990f0bfbb | ||
|
|
f43635ee89 | ||
|
|
705f1bda2b | ||
|
|
0ff0d06eaf | ||
|
|
a769486c9c | ||
|
|
83302c4439 | ||
|
|
ba020b43f1 | ||
|
|
eaf5c9687e | ||
|
|
a725ef5caf | ||
|
|
aa79f3628e | ||
|
|
000351d884 | ||
|
|
db58c113aa | ||
|
|
7cb8503164 | ||
|
|
cc2450ff87 | ||
|
|
6b77393414 | ||
|
|
7ccc8faf20 | ||
|
|
6030288956 | ||
|
|
e4a955f323 | ||
|
|
e286d8bdee | ||
|
|
1faf8f95c8 | ||
|
|
2913b254b4 | ||
|
|
ef81536066 | ||
|
|
29aa13316d | ||
|
|
98f28aaafd | ||
|
|
f8a2a7a959 | ||
|
|
767b5512e3 | ||
|
|
f1bd1ca674 | ||
|
|
b47cfed5a9 | ||
|
|
71b69aa0f6 | ||
|
|
df61225b73 | ||
|
|
823db68a6a | ||
|
|
ebe08c3bf1 | ||
|
|
44ccead0a4 | ||
|
|
99dfdcd253 | ||
|
|
03ca65602f | ||
|
|
ca6cddb392 | ||
|
|
33ce4b622d | ||
|
|
46434977fb | ||
|
|
f13303490a | ||
|
|
84b0a57e14 | ||
|
|
2ee7c064af | ||
|
|
b2b8546e3c | ||
|
|
2945a607bf | ||
|
|
4370cf60d1 | ||
|
|
e7496a9d82 | ||
|
|
77f7a25fa8 | ||
|
|
10c5ea3e7c | ||
|
|
cd82712742 | ||
|
|
3067dcdda5 | ||
|
|
94679d6783 | ||
|
|
54dc29b4b2 | ||
|
|
e699826ce9 | ||
|
|
33fa19c15e | ||
|
|
d6e9b030fd | ||
|
|
f0a3c968a7 | ||
|
|
d8760a00ba | ||
|
|
8ee43ef3ab | ||
|
|
1b6ce32deb | ||
|
|
9964d33cc0 | ||
|
|
1e81b718f6 | ||
|
|
551737b882 | ||
|
|
77dd36d4b4 | ||
|
|
9bace9a6da | ||
|
|
c6b45da72c | ||
|
|
0721d61bef | ||
|
|
56bc084e60 | ||
|
|
481f56a4bd | ||
|
|
11e9931438 | ||
|
|
ea840eb43c | ||
|
|
2ed0a4bcf9 | ||
|
|
5d48d75f34 | ||
|
|
92beb8bf79 | ||
|
|
ed552b402a | ||
|
|
8279bec4bf | ||
|
|
67b2bc6d54 | ||
|
|
e3adfecd17 | ||
|
|
1681ab8fee | ||
|
|
a77bc65d7b | ||
|
|
73517ed443 | ||
|
|
f59e11ecc4 | ||
|
|
19f619b2d4 | ||
|
|
e0d03c2cc3 | ||
|
|
69f7152dd6 | ||
|
|
581f28757a | ||
|
|
660a54d955 |
32
.codeclimate.yml
Normal file
32
.codeclimate.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
engines:
|
||||
csslint:
|
||||
enabled: true
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- ruby
|
||||
- javascript
|
||||
- python
|
||||
- php
|
||||
eslint:
|
||||
enabled: true
|
||||
fixme:
|
||||
enabled: true
|
||||
radon:
|
||||
enabled: true
|
||||
rubocop:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.css"
|
||||
- "**.inc"
|
||||
- "**.js"
|
||||
- "**.jsx"
|
||||
- "**.module"
|
||||
- "**.php"
|
||||
- "**.py"
|
||||
- "**.rb"
|
||||
exclude_paths:
|
||||
- config/
|
||||
6
.coveragerc
Normal file
6
.coveragerc
Normal file
@@ -0,0 +1,6 @@
|
||||
[run]
|
||||
source =
|
||||
./
|
||||
|
||||
omit =
|
||||
*/migrations/*
|
||||
2
.csslintrc
Normal file
2
.csslintrc
Normal file
@@ -0,0 +1,2 @@
|
||||
--exclude-exts=.min.css
|
||||
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
|
||||
29
.editorconfig
Normal file
29
.editorconfig
Normal file
@@ -0,0 +1,29 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.hbs]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.{diff,md}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Python: PEP8 defines 4 spaces for indentation
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
# Salt state files, YAML format, 2 spaces
|
||||
[*.sls, *.yaml, *.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
**/*{.,-}min.js
|
||||
213
.eslintrc
Normal file
213
.eslintrc
Normal file
@@ -0,0 +1,213 @@
|
||||
ecmaFeatures:
|
||||
modules: true
|
||||
jsx: true
|
||||
|
||||
env:
|
||||
amd: true
|
||||
browser: true
|
||||
es6: true
|
||||
jquery: true
|
||||
node: true
|
||||
|
||||
# http://eslint.org/docs/rules/
|
||||
rules:
|
||||
# Possible Errors
|
||||
comma-dangle: [2, never]
|
||||
no-cond-assign: 2
|
||||
no-console: 0
|
||||
no-constant-condition: 2
|
||||
no-control-regex: 2
|
||||
no-debugger: 2
|
||||
no-dupe-args: 2
|
||||
no-dupe-keys: 2
|
||||
no-duplicate-case: 2
|
||||
no-empty: 2
|
||||
no-empty-character-class: 2
|
||||
no-ex-assign: 2
|
||||
no-extra-boolean-cast: 2
|
||||
no-extra-parens: 0
|
||||
no-extra-semi: 2
|
||||
no-func-assign: 2
|
||||
no-inner-declarations: [2, functions]
|
||||
no-invalid-regexp: 2
|
||||
no-irregular-whitespace: 2
|
||||
no-negated-in-lhs: 2
|
||||
no-obj-calls: 2
|
||||
no-regex-spaces: 2
|
||||
no-sparse-arrays: 2
|
||||
no-unexpected-multiline: 2
|
||||
no-unreachable: 2
|
||||
use-isnan: 2
|
||||
valid-jsdoc: 0
|
||||
valid-typeof: 2
|
||||
|
||||
# Best Practices
|
||||
accessor-pairs: 2
|
||||
block-scoped-var: 0
|
||||
complexity: [2, 6]
|
||||
consistent-return: 0
|
||||
curly: 0
|
||||
default-case: 0
|
||||
dot-location: 0
|
||||
dot-notation: 0
|
||||
eqeqeq: 2
|
||||
guard-for-in: 2
|
||||
no-alert: 2
|
||||
no-caller: 2
|
||||
no-case-declarations: 2
|
||||
no-div-regex: 2
|
||||
no-else-return: 0
|
||||
no-empty-label: 2
|
||||
no-empty-pattern: 2
|
||||
no-eq-null: 2
|
||||
no-eval: 2
|
||||
no-extend-native: 2
|
||||
no-extra-bind: 2
|
||||
no-fallthrough: 2
|
||||
no-floating-decimal: 0
|
||||
no-implicit-coercion: 0
|
||||
no-implied-eval: 2
|
||||
no-invalid-this: 0
|
||||
no-iterator: 2
|
||||
no-labels: 0
|
||||
no-lone-blocks: 2
|
||||
no-loop-func: 2
|
||||
no-magic-number: 0
|
||||
no-multi-spaces: 0
|
||||
no-multi-str: 0
|
||||
no-native-reassign: 2
|
||||
no-new-func: 2
|
||||
no-new-wrappers: 2
|
||||
no-new: 2
|
||||
no-octal-escape: 2
|
||||
no-octal: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 2
|
||||
no-return-assign: 2
|
||||
no-script-url: 2
|
||||
no-self-compare: 2
|
||||
no-sequences: 0
|
||||
no-throw-literal: 0
|
||||
no-unused-expressions: 2
|
||||
no-useless-call: 2
|
||||
no-useless-concat: 2
|
||||
no-void: 2
|
||||
no-warning-comments: 0
|
||||
no-with: 2
|
||||
radix: 2
|
||||
vars-on-top: 0
|
||||
wrap-iife: 2
|
||||
yoda: 0
|
||||
|
||||
# Strict
|
||||
strict: 0
|
||||
|
||||
# Variables
|
||||
init-declarations: 0
|
||||
no-catch-shadow: 2
|
||||
no-delete-var: 2
|
||||
no-label-var: 2
|
||||
no-shadow-restricted-names: 2
|
||||
no-shadow: 0
|
||||
no-undef-init: 2
|
||||
no-undef: 0
|
||||
no-undefined: 0
|
||||
no-unused-vars: 0
|
||||
no-use-before-define: 0
|
||||
|
||||
# Node.js and CommonJS
|
||||
callback-return: 2
|
||||
global-require: 2
|
||||
handle-callback-err: 2
|
||||
no-mixed-requires: 0
|
||||
no-new-require: 0
|
||||
no-path-concat: 2
|
||||
no-process-exit: 2
|
||||
no-restricted-modules: 0
|
||||
no-sync: 0
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing: 0
|
||||
block-spacing: 0
|
||||
brace-style: 0
|
||||
camelcase: 0
|
||||
comma-spacing: 0
|
||||
comma-style: 0
|
||||
computed-property-spacing: 0
|
||||
consistent-this: 0
|
||||
eol-last: 0
|
||||
func-names: 0
|
||||
func-style: 0
|
||||
id-length: 0
|
||||
id-match: 0
|
||||
indent: 0
|
||||
jsx-quotes: 0
|
||||
key-spacing: 0
|
||||
linebreak-style: 0
|
||||
lines-around-comment: 0
|
||||
max-depth: 0
|
||||
max-len: 0
|
||||
max-nested-callbacks: 0
|
||||
max-params: 0
|
||||
max-statements: [2, 30]
|
||||
new-cap: 0
|
||||
new-parens: 0
|
||||
newline-after-var: 0
|
||||
no-array-constructor: 0
|
||||
no-bitwise: 0
|
||||
no-continue: 0
|
||||
no-inline-comments: 0
|
||||
no-lonely-if: 0
|
||||
no-mixed-spaces-and-tabs: 0
|
||||
no-multiple-empty-lines: 0
|
||||
no-negated-condition: 0
|
||||
no-nested-ternary: 0
|
||||
no-new-object: 0
|
||||
no-plusplus: 0
|
||||
no-restricted-syntax: 0
|
||||
no-spaced-func: 0
|
||||
no-ternary: 0
|
||||
no-trailing-spaces: 0
|
||||
no-underscore-dangle: 0
|
||||
no-unneeded-ternary: 0
|
||||
object-curly-spacing: 0
|
||||
one-var: 0
|
||||
operator-assignment: 0
|
||||
operator-linebreak: 0
|
||||
padded-blocks: 0
|
||||
quote-props: 0
|
||||
quotes: 0
|
||||
require-jsdoc: 0
|
||||
semi-spacing: 0
|
||||
semi: 0
|
||||
sort-vars: 0
|
||||
space-after-keywords: 0
|
||||
space-before-blocks: 0
|
||||
space-before-function-paren: 0
|
||||
space-before-keywords: 0
|
||||
space-in-parens: 0
|
||||
space-infix-ops: 0
|
||||
space-return-throw-case: 0
|
||||
space-unary-ops: 0
|
||||
spaced-comment: 0
|
||||
wrap-regex: 0
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: 0
|
||||
arrow-parens: 0
|
||||
arrow-spacing: 0
|
||||
constructor-super: 0
|
||||
generator-star-spacing: 0
|
||||
no-arrow-condition: 0
|
||||
no-class-assign: 0
|
||||
no-const-assign: 0
|
||||
no-dupe-class-members: 0
|
||||
no-this-before-super: 0
|
||||
no-var: 0
|
||||
object-shorthand: 0
|
||||
prefer-arrow-callback: 0
|
||||
prefer-const: 0
|
||||
prefer-reflect: 0
|
||||
prefer-spread: 0
|
||||
prefer-template: 0
|
||||
require-yield: 0
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
tmp/
|
||||
db.sqlite3
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -25,9 +28,6 @@ var/
|
||||
|
||||
# Continer extras
|
||||
.vagrant
|
||||
_builds
|
||||
_steps
|
||||
_projects
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
|
||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
PyRIGS
|
||||
5
.idea/encodings.xml
generated
Normal file
5
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/PyRIGS.iml" filepath="$PROJECT_DIR$/.idea/PyRIGS.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/scopes/scope_settings.xml
generated
Normal file
5
.idea/scopes/scope_settings.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
1156
.rubocop.yml
Normal file
1156
.rubocop.yml
Normal file
File diff suppressed because it is too large
Load Diff
21
.travis.yml
Normal file
21
.travis.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
language: python
|
||||
python:
|
||||
"2.7"
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
|
||||
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install coveralls codeclimate-test-reporter
|
||||
|
||||
before_script:
|
||||
- python manage.py collectstatic --noinput
|
||||
|
||||
script:
|
||||
- coverage run manage.py test RIGS
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
- codeclimate-test-reporter
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
def user_passes_test_with_403(test_func, login_url=None):
|
||||
"""
|
||||
@@ -13,42 +14,53 @@ def user_passes_test_with_403(test_func, login_url=None):
|
||||
if not login_url:
|
||||
from django.conf import settings
|
||||
login_url = settings.LOGIN_URL
|
||||
|
||||
def _dec(view_func):
|
||||
def _checklogin(request, *args, **kwargs):
|
||||
if test_func(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
elif not request.user.is_authenticated():
|
||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
||||
return HttpResponseRedirect('%s?%s=%s' % (
|
||||
login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
||||
else:
|
||||
resp = render_to_response('403.html', context_instance=RequestContext(request))
|
||||
resp = render_to_response(
|
||||
'403.html', context_instance=RequestContext(request))
|
||||
resp.status_code = 403
|
||||
return resp
|
||||
|
||||
_checklogin.__doc__ = view_func.__doc__
|
||||
_checklogin.__dict__ = view_func.__dict__
|
||||
return _checklogin
|
||||
|
||||
return _dec
|
||||
|
||||
|
||||
def permission_required_with_403(perm, login_url=None):
|
||||
"""
|
||||
Decorator for views that checks whether a user has a particular permission
|
||||
enabled, redirecting to the log-in page or rendering a 403 as necessary.
|
||||
"""
|
||||
return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url)
|
||||
return user_passes_test_with_403(
|
||||
lambda u: u.has_perm(perm), login_url=login_url)
|
||||
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
def api_key_required(function):
|
||||
"""
|
||||
Decorator for views that checks api_pk and api_key.
|
||||
Failed users will be given a 403 error.
|
||||
Should only be used for urls which include <api_pk> and <api_key> kwargs
|
||||
"""
|
||||
|
||||
def wrap(request, *args, **kwargs):
|
||||
|
||||
userid = kwargs.get('api_pk')
|
||||
key = kwargs.get('api_key')
|
||||
|
||||
error_resp = render_to_response('403.html', context_instance=RequestContext(request))
|
||||
error_resp = render_to_response(
|
||||
'403.html', context_instance=RequestContext(request))
|
||||
error_resp.status_code = 403
|
||||
|
||||
if key is None:
|
||||
@@ -58,10 +70,11 @@ def api_key_required(function):
|
||||
|
||||
try:
|
||||
user_object = models.Profile.objects.get(pk=userid)
|
||||
except Profile.DoesNotExist:
|
||||
except models.Profile.DoesNotExist:
|
||||
return error_resp
|
||||
|
||||
if user_object.api_key != key:
|
||||
return error_resp
|
||||
return function(request, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
@@ -10,22 +10,35 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get('SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
|
||||
'SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
|
||||
|
||||
STAGING = bool(int(os.environ.get('STAGING'))
|
||||
) if os.environ.get('STAGING') else False
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
ALLOWED_HOSTS = [
|
||||
'pyrigs.nottinghamtec.co.uk',
|
||||
'rigs.nottinghamtec.co.uk',
|
||||
'pyrigs.herokuapp.com']
|
||||
|
||||
if STAGING:
|
||||
ALLOWED_HOSTS.append('.herokuapp.com')
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
if not DEBUG:
|
||||
SECURE_SSL_REDIRECT = True # Redirect all http requests to https
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
@@ -33,7 +46,6 @@ ADMINS = (
|
||||
('Tom Price', 'tomtom5152@gmail.com')
|
||||
)
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
@@ -44,7 +56,6 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'RIGS',
|
||||
'training',
|
||||
|
||||
'debug_toolbar',
|
||||
'registration',
|
||||
@@ -56,6 +67,7 @@ INSTALLED_APPS = (
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -70,26 +82,18 @@ ROOT_URLCONF = 'PyRIGS.urls'
|
||||
|
||||
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
},
|
||||
# Legacy training database
|
||||
'training': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'HOST': 'localhost',
|
||||
'NAME': 'tec_training',
|
||||
'USER': 'pyrigs',
|
||||
'PASSWORD': 'pyrigs',
|
||||
}
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
import dj_database_url
|
||||
|
||||
DATABASES['default'] = dj_database_url.config()
|
||||
|
||||
# Logging
|
||||
@@ -147,6 +151,7 @@ RAVEN_CONFIG = {
|
||||
# If you are using git, you can also automatically configure the
|
||||
# release based on the git info.
|
||||
# 'release': raven.fetch_git_sha(os.path.dirname(os.path.dirname(__file__))),
|
||||
'debug': DEBUG,
|
||||
}
|
||||
|
||||
# User system
|
||||
@@ -205,7 +210,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
)
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.conf import settings
|
||||
from registration.backends.default.views import RegistrationView
|
||||
|
||||
import RIGS
|
||||
from RIGS import regbackend
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Examples:
|
||||
@@ -12,8 +12,8 @@ urlpatterns = patterns('',
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
|
||||
url(r'^', include('RIGS.urls')),
|
||||
url('^training/', include('training.urls', namespace='training')),
|
||||
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
|
||||
url('^user/register/$',
|
||||
RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
|
||||
name="registration_register"),
|
||||
url('^user/', include('django.contrib.auth.urls')),
|
||||
url('^user/', include('registration.backends.default.urls')),
|
||||
|
||||
@@ -7,6 +7,7 @@ For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
20
README.md
20
README.md
@@ -1,5 +1,6 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://app.wercker.com/project/bykey/b26100ecccdfb46a9a9056553daac5b7)
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop)
|
||||
|
||||
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.
|
||||
|
||||
@@ -74,5 +75,22 @@ python manage.py runserver
|
||||
```
|
||||
Please refer to Django documentation for a full list of options available here.
|
||||
|
||||
### Sample Data ###
|
||||
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
|
||||
```
|
||||
python manage.py flush
|
||||
```
|
||||
Then load the sample data using the command:
|
||||
```
|
||||
python manage.py generateSampleData
|
||||
```
|
||||
4 user accounts are created for convenience:
|
||||
|Username |Password |
|
||||
|---------|---------|
|
||||
|superuser|superuser|
|
||||
|finance |finance |
|
||||
|keyholder|keyholder|
|
||||
|basic |basic |
|
||||
|
||||
### 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.
|
||||
113
RIGS/admin.py
113
RIGS/admin.py
@@ -1,19 +1,26 @@
|
||||
from django.contrib import admin
|
||||
from RIGS import models, forms
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import reversion
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin import helpers
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.forms import ModelForm
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from RIGS import models, forms
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.Person, reversion.VersionAdmin)
|
||||
admin.site.register(models.Organisation, reversion.VersionAdmin)
|
||||
admin.site.register(models.VatRate, reversion.VersionAdmin)
|
||||
admin.site.register(models.Venue, reversion.VersionAdmin)
|
||||
admin.site.register(models.Event, reversion.VersionAdmin)
|
||||
admin.site.register(models.EventItem, reversion.VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
|
||||
|
||||
@admin.register(models.Profile)
|
||||
class ProfileAdmin(UserAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
@@ -33,4 +40,94 @@ class ProfileAdmin(UserAdmin):
|
||||
form = forms.ProfileChangeForm
|
||||
add_form = forms.ProfileCreationForm
|
||||
|
||||
admin.site.register(models.Profile, ProfileAdmin)
|
||||
|
||||
class AssociateAdmin(reversion.VersionAdmin):
|
||||
list_display = ('id', 'name', 'number_of_events')
|
||||
search_fields = ['id', 'name']
|
||||
list_display_links = ['id', 'name']
|
||||
actions = ['merge']
|
||||
|
||||
merge_fields = ['name']
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(AssociateAdmin, self).get_queryset(
|
||||
request).annotate(event_count=Count('event'))
|
||||
|
||||
def number_of_events(self, obj):
|
||||
return obj.latest_events.count()
|
||||
|
||||
number_of_events.admin_order_field = 'event_count'
|
||||
|
||||
def merge(self, request, queryset):
|
||||
if request.POST.get(
|
||||
'post'): # Has the user confirmed which is the master record?
|
||||
try:
|
||||
masterObjectPk = request.POST.get('master')
|
||||
masterObject = queryset.get(pk=masterObjectPk)
|
||||
except ObjectDoesNotExist:
|
||||
self.message_user(
|
||||
request,
|
||||
"An error occured. Did you select a 'master' record?",
|
||||
level=messages.ERROR)
|
||||
return
|
||||
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
for obj in queryset.exclude(pk=masterObjectPk):
|
||||
events = obj.event_set.all()
|
||||
for event in events:
|
||||
masterObject.event_set.add(event)
|
||||
obj.delete()
|
||||
reversion.set_comment('Merging Objects')
|
||||
|
||||
self.message_user(request, "Objects successfully merged.")
|
||||
return
|
||||
else: # Present the confirmation screen
|
||||
|
||||
class TempForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = queryset.model
|
||||
fields = self.merge_fields
|
||||
|
||||
forms = []
|
||||
for obj in queryset:
|
||||
forms.append(TempForm(instance=obj))
|
||||
|
||||
context = {
|
||||
'title': _("Are you sure?"),
|
||||
'queryset': queryset,
|
||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||
'forms': forms
|
||||
}
|
||||
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context,
|
||||
current_app=self.admin_site.name)
|
||||
|
||||
|
||||
@admin.register(models.Person)
|
||||
class PersonAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes']
|
||||
|
||||
|
||||
@admin.register(models.Venue)
|
||||
class VenueAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = [
|
||||
'name',
|
||||
'phone',
|
||||
'email',
|
||||
'address',
|
||||
'notes',
|
||||
'three_phase_available']
|
||||
|
||||
|
||||
@admin.register(models.Organisation)
|
||||
class OrganisationAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = [
|
||||
'name',
|
||||
'phone',
|
||||
'email',
|
||||
'address',
|
||||
'notes',
|
||||
'union_account']
|
||||
|
||||
109
RIGS/finance.py
109
RIGS/finance.py
@@ -1,32 +1,42 @@
|
||||
import cStringIO as StringIO
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import connection
|
||||
from django.db.models import Q
|
||||
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 django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.views import generic
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
|
||||
import re
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list.html'
|
||||
template_name = 'RIGS/invoice_list_active.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceIndex, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for i in context['object_list']:
|
||||
total += i.balance
|
||||
context['total'] = total
|
||||
context['count'] = len(list(context['object_list']))
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
||||
# Manual query is the only way I have found to do this efficiently. Not
|
||||
# ideal but needs must
|
||||
sql = "SELECT * FROM " \
|
||||
"(SELECT " \
|
||||
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
||||
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
||||
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
|
||||
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
||||
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
||||
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
|
||||
"AS sub " \
|
||||
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
||||
@@ -40,6 +50,7 @@ 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)
|
||||
@@ -68,10 +79,12 @@ class InvoicePrint(generic.View):
|
||||
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName)
|
||||
response[
|
||||
'Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName)
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
|
||||
class InvoiceVoid(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
pk = kwargs.get('pk')
|
||||
@@ -81,27 +94,77 @@ class InvoiceVoid(generic.View):
|
||||
|
||||
if object.void:
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_list'))
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
|
||||
return HttpResponseRedirect(reverse_lazy(
|
||||
'invoice_detail', kwargs={'pk': object.pk}))
|
||||
|
||||
|
||||
class InvoiceDelete(generic.DeleteView):
|
||||
model = models.Invoice
|
||||
|
||||
def get(self, request, pk):
|
||||
obj = self.get_object()
|
||||
if obj.payment_set.all().count() > 0:
|
||||
messages.info(
|
||||
self.request,
|
||||
'To delete an invoice, delete the payments first.')
|
||||
return HttpResponseRedirect(reverse_lazy(
|
||||
'invoice_detail', kwargs={'pk': obj.pk}))
|
||||
return super(InvoiceDelete, self).get(pk)
|
||||
|
||||
def post(self, request, pk):
|
||||
obj = self.get_object()
|
||||
if obj.payment_set.all().count() > 0:
|
||||
messages.info(
|
||||
self.request,
|
||||
'To delete an invoice, delete the payments first.')
|
||||
return HttpResponseRedirect(reverse_lazy(
|
||||
'invoice_detail', kwargs={'pk': obj.pk}))
|
||||
return super(InvoiceDelete, self).post(pk)
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.POST.get('next')
|
||||
|
||||
|
||||
class InvoiceArchive(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_archive.html'
|
||||
paginate_by = 25
|
||||
|
||||
|
||||
class InvoiceWaiting(generic.ListView):
|
||||
model = models.Event
|
||||
paginate_by = 25
|
||||
# paginate_by = 25
|
||||
template_name = 'RIGS/event_invoice.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for obj in self.get_objects():
|
||||
total += obj.sum_total
|
||||
context['total'] = total
|
||||
context['count'] = len(self.get_objects())
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_objects()
|
||||
|
||||
def get_objects(self):
|
||||
# @todo find a way to select items
|
||||
events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(),
|
||||
invoice__isnull=True) \
|
||||
.order_by('start_date') \
|
||||
events = self.model.objects.filter(
|
||||
(
|
||||
# Starts before with no end
|
||||
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) |
|
||||
# Has end date, finishes before
|
||||
Q(end_date__lte=datetime.date.today())
|
||||
) & Q(invoice__isnull=True) # Has not already been invoiced
|
||||
& Q(is_rig=True) # Is a rig (not non-rig)
|
||||
|
||||
).order_by('start_date') \
|
||||
.select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
'venue', 'mic') \
|
||||
.prefetch_related('items')
|
||||
|
||||
return events
|
||||
|
||||
|
||||
@@ -113,8 +176,10 @@ class InvoiceEvent(generic.View):
|
||||
|
||||
if created:
|
||||
invoice.invoice_date = datetime.date.today()
|
||||
messages.success(self.request, 'Invoice created successfully')
|
||||
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
|
||||
return HttpResponseRedirect(reverse_lazy(
|
||||
'invoice_detail', kwargs={'pk': invoice.pk}))
|
||||
|
||||
|
||||
class PaymentCreate(generic.CreateView):
|
||||
@@ -123,8 +188,10 @@ class PaymentCreate(generic.CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(generic.CreateView, self).get_initial()
|
||||
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
|
||||
if invoicepk == None:
|
||||
invoicepk = self.request.GET.get(
|
||||
'invoice', self.request.POST.get(
|
||||
'invoice', None))
|
||||
if invoicepk is None:
|
||||
raise Http404()
|
||||
invoice = get_object_or_404(models.Invoice, pk=invoicepk)
|
||||
initial.update({'invoice': invoice})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
__author__ = 'Ghost'
|
||||
from django import forms
|
||||
from django.utils import formats
|
||||
from django.conf import settings
|
||||
from django.core import serializers
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
|
||||
from registration.forms import RegistrationFormUniqueEmail
|
||||
from captcha.fields import ReCaptchaField
|
||||
import simplejson
|
||||
from captcha.fields import ReCaptchaField
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordResetForm
|
||||
from django.core import serializers
|
||||
from django.utils import formats
|
||||
from registration.forms import RegistrationFormUniqueEmail
|
||||
|
||||
from RIGS import models
|
||||
|
||||
@@ -17,14 +17,22 @@ 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',
|
||||
'phone')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
Validate that the supplied initials are unique.
|
||||
"""
|
||||
if models.Profile.objects.filter(initials__iexact=self.cleaned_data['initials']):
|
||||
raise forms.ValidationError("These initials are already in use. Please supply different initials.")
|
||||
if models.Profile.objects.filter(
|
||||
initials__iexact=self.cleaned_data['initials']):
|
||||
raise forms.ValidationError(
|
||||
"These initials are already in use. Please supply different initials.")
|
||||
return self.cleaned_data['initials']
|
||||
|
||||
|
||||
@@ -45,9 +53,14 @@ class ProfileChangeForm(UserChangeForm):
|
||||
|
||||
# Events Shit
|
||||
class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
|
||||
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
datetime_input_formats = formats.get_format_lazy(
|
||||
"DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
|
||||
meet_at = forms.DateTimeField(
|
||||
input_formats=datetime_input_formats,
|
||||
required=False)
|
||||
access_at = forms.DateTimeField(
|
||||
input_formats=datetime_input_formats,
|
||||
required=False)
|
||||
|
||||
items_json = forms.CharField()
|
||||
|
||||
@@ -89,7 +102,8 @@ class EventForm(forms.ModelForm):
|
||||
items = {}
|
||||
for key in data:
|
||||
pk = int(key)
|
||||
items[pk] = self._get_or_initialise_item(pk, data[key]['fields'], event)
|
||||
items[pk] = self._get_or_initialise_item(
|
||||
pk, data[key]['fields'], event)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
58
RIGS/ical.py
58
RIGS/ical.py
@@ -1,11 +1,12 @@
|
||||
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 pytz
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django_ical.views import ICalFeed
|
||||
|
||||
from RIGS import models
|
||||
|
||||
import datetime, pytz
|
||||
|
||||
class CalendarICS(ICalFeed):
|
||||
"""
|
||||
@@ -32,14 +33,17 @@ class CalendarICS(ICalFeed):
|
||||
params['rig'] = request.GET.get('rig', 'true') == 'true'
|
||||
|
||||
params['cancelled'] = request.GET.get('cancelled', 'false') == 'true'
|
||||
params['provisional'] = request.GET.get('provisional','true') == 'true'
|
||||
params['provisional'] = request.GET.get(
|
||||
'provisional', 'true') == 'true'
|
||||
params['confirmed'] = request.GET.get('confirmed', 'true') == 'true'
|
||||
|
||||
return params
|
||||
|
||||
def description(self, params):
|
||||
desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + ('Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n'
|
||||
desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
|
||||
desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + (
|
||||
'Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n'
|
||||
desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + (
|
||||
'Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
|
||||
|
||||
return desc
|
||||
|
||||
@@ -48,7 +52,8 @@ class CalendarICS(ICalFeed):
|
||||
start = datetime.datetime.now() - datetime.timedelta(days=365)
|
||||
filter = Q(start_date__gte=start)
|
||||
|
||||
typeFilters = Q(pk=None) #Need something that is false for every entry
|
||||
# Need something that is false for every entry
|
||||
typeFilters = Q(pk=None)
|
||||
|
||||
if params['dry-hire']:
|
||||
typeFilters = typeFilters | Q(dry_hire=True, is_rig=True)
|
||||
@@ -59,18 +64,22 @@ class CalendarICS(ICalFeed):
|
||||
if params['rig']:
|
||||
typeFilters = typeFilters | Q(is_rig=True, dry_hire=False)
|
||||
|
||||
statusFilters = Q(pk=None) #Need something that is false for every entry
|
||||
# Need something that is false for every entry
|
||||
statusFilters = Q(pk=None)
|
||||
|
||||
if params['cancelled']:
|
||||
statusFilters = statusFilters | Q(status=models.Event.CANCELLED)
|
||||
if params['provisional']:
|
||||
statusFilters = statusFilters | Q(status=models.Event.PROVISIONAL)
|
||||
if params['confirmed']:
|
||||
statusFilters = statusFilters | Q(status=models.Event.CONFIRMED) | Q(status=models.Event.BOOKED)
|
||||
statusFilters = statusFilters | Q(
|
||||
status=models.Event.CONFIRMED) | Q(
|
||||
status=models.Event.BOOKED)
|
||||
|
||||
filter = filter & typeFilters & statusFilters
|
||||
|
||||
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
return models.Event.objects.filter(filter).order_by(
|
||||
'-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
def item_title(self, item):
|
||||
title = ''
|
||||
@@ -97,7 +106,8 @@ class CalendarICS(ICalFeed):
|
||||
return item.earliest_time
|
||||
|
||||
def item_end_datetime(self, item):
|
||||
if type(item.latest_time) is datetime.date: # Ical end_datetime is non-inclusive, so add a day
|
||||
if isinstance(
|
||||
item.latest_time, datetime.date): # Ical end_datetime is non-inclusive, so add a day
|
||||
return item.latest_time + datetime.timedelta(days=1)
|
||||
|
||||
return item.latest_time
|
||||
@@ -115,20 +125,26 @@ class CalendarICS(ICalFeed):
|
||||
desc += 'Event = ' + item.name + '\n'
|
||||
desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n'
|
||||
if item.is_rig and item.person:
|
||||
desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n'
|
||||
desc += 'Client = ' + item.person.name + \
|
||||
((' for ' + item.organisation.name)
|
||||
if item.organisation else '') + '\n'
|
||||
desc += 'Status = ' + str(item.get_status_display()) + '\n'
|
||||
desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n'
|
||||
|
||||
|
||||
desc += '\n'
|
||||
if item.meet_at:
|
||||
desc += 'Crew Meet = ' + (item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
|
||||
desc += 'Crew Meet = ' + \
|
||||
(item.meet_at.astimezone(tz).strftime(
|
||||
'%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
|
||||
if item.access_at:
|
||||
desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n'
|
||||
desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime(
|
||||
'%Y-%m-%d %H:%M') if item.access_at else '---') + '\n'
|
||||
if item.start_date:
|
||||
desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n'
|
||||
desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + (
|
||||
(' ' + item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n'
|
||||
if item.end_date:
|
||||
desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
|
||||
desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + (
|
||||
(' ' + item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
|
||||
|
||||
desc += '\n'
|
||||
if item.description:
|
||||
|
||||
321
RIGS/management/commands/generateSampleData.py
Normal file
321
RIGS/management/commands/generateSampleData.py
Normal file
@@ -0,0 +1,321 @@
|
||||
import datetime
|
||||
import random
|
||||
|
||||
import reversion
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Adds sample data to use for testing'
|
||||
can_import_settings = True
|
||||
|
||||
people = []
|
||||
organisations = []
|
||||
venues = []
|
||||
profiles = []
|
||||
|
||||
keyholder_group = None
|
||||
finance_group = None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
|
||||
if not (settings.DEBUG or settings.STAGING):
|
||||
raise CommandError('You cannot run this command in production')
|
||||
|
||||
random.seed(
|
||||
'Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
|
||||
|
||||
with transaction.atomic():
|
||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||
|
||||
self.setupGenericProfiles()
|
||||
|
||||
self.setupPeople()
|
||||
self.setupOrganisations()
|
||||
self.setupVenues()
|
||||
|
||||
self.setupGroups()
|
||||
|
||||
self.setupEvents()
|
||||
|
||||
self.setupUsefulProfiles()
|
||||
|
||||
def setupPeople(self):
|
||||
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
||||
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
|
||||
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
|
||||
"Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid",
|
||||
"Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom",
|
||||
"Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", "Remus Lupin", "Draco Malfoy",
|
||||
"Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody",
|
||||
"Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle",
|
||||
"Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks",
|
||||
"Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley",
|
||||
"George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "Ron Weasley", "Dobby", "Fluffy",
|
||||
"Hedwig", "Moaning Myrtle", "Aragog", "Grawp"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
newPerson = models.Person.objects.create(name=name)
|
||||
if i % 3 == 0:
|
||||
newPerson.email = "address@person.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newPerson.phone = "01234 567894"
|
||||
|
||||
newPerson.save()
|
||||
self.people.append(newPerson)
|
||||
|
||||
def setupOrganisations(self):
|
||||
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
|
||||
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
|
||||
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
|
||||
"Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp",
|
||||
"North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation",
|
||||
"Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp",
|
||||
"Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power",
|
||||
"Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank",
|
||||
"Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium",
|
||||
"Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors",
|
||||
"Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation",
|
||||
"U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation",
|
||||
"Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", "Input, Inc.",
|
||||
"Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles",
|
||||
"Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company",
|
||||
"Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech",
|
||||
"Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation",
|
||||
"Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation",
|
||||
"Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue",
|
||||
"Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways",
|
||||
"C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield",
|
||||
"Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode",
|
||||
"Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger",
|
||||
"Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys",
|
||||
"The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe",
|
||||
"Moes Tavern", "Central Perk", "Chasers"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newOrganisation = models.Organisation.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newOrganisation.has_su_account = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newOrganisation.email = "address@organisation.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newOrganisation.phone = "01234 567894"
|
||||
|
||||
newOrganisation.save()
|
||||
self.organisations.append(newOrganisation)
|
||||
|
||||
def setupVenues(self):
|
||||
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
|
||||
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
|
||||
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
|
||||
"The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark",
|
||||
"Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye",
|
||||
"The Golden Tooth", "Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport",
|
||||
"Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black",
|
||||
"Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass",
|
||||
"The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos",
|
||||
"Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria",
|
||||
"Village of the Lhazareen", "Volantis", "Yunkai"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newVenue = models.Venue.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newVenue.three_phase_available = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newVenue.email = "address@venue.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newVenue.phone = "01234 567894"
|
||||
|
||||
newVenue.save()
|
||||
self.venues.append(newVenue)
|
||||
|
||||
def setupGroups(self):
|
||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||
self.finance_group = Group.objects.create(name='Finance')
|
||||
|
||||
keyholderPerms = ["add_event", "change_event", "view_event", "add_eventitem", "change_eventitem",
|
||||
"delete_eventitem", "add_organisation", "change_organisation", "view_organisation",
|
||||
"add_person", "change_person", "view_person", "view_profile", "add_venue", "change_venue",
|
||||
"view_venue"]
|
||||
financePerms = ["change_event", "view_event", "add_eventitem", "change_eventitem", "add_invoice",
|
||||
"change_invoice", "view_invoice", "add_organisation", "change_organisation",
|
||||
"view_organisation", "add_payment", "change_payment", "delete_payment", "add_person",
|
||||
"change_person", "view_person"]
|
||||
|
||||
for permId in keyholderPerms:
|
||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
for permId in financePerms:
|
||||
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
def setupGenericProfiles(self):
|
||||
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
|
||||
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
|
||||
for i, name in enumerate(names):
|
||||
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
|
||||
last_name=name.split(" ")[-1],
|
||||
email=name.replace(" ", "") + "@example.com",
|
||||
initials="".join([j[0].upper() for j in name.split()]))
|
||||
if i % 2 == 0:
|
||||
newProfile.phone = "01234 567894"
|
||||
|
||||
newProfile.save()
|
||||
self.profiles.append(newProfile)
|
||||
|
||||
def setupUsefulProfiles(self):
|
||||
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
|
||||
initials="SU",
|
||||
email="superuser@example.com", is_superuser=True, is_active=True,
|
||||
is_staff=True)
|
||||
superUser.set_password('superuser')
|
||||
superUser.save()
|
||||
|
||||
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
||||
initials="FU",
|
||||
email="financeuser@example.com", is_active=True)
|
||||
financeUser.groups.add(self.finance_group)
|
||||
financeUser.groups.add(self.keyholder_group)
|
||||
financeUser.set_password('finance')
|
||||
financeUser.save()
|
||||
|
||||
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
||||
initials="KU",
|
||||
email="keyholderuser@example.com", is_active=True)
|
||||
keyholderUser.groups.add(self.keyholder_group)
|
||||
keyholderUser.set_password('keyholder')
|
||||
keyholderUser.save()
|
||||
|
||||
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
|
||||
email="basicuser@example.com", is_active=True)
|
||||
basicUser.set_password('basic')
|
||||
basicUser.save()
|
||||
|
||||
def setupEvents(self):
|
||||
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
|
||||
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event", "End of Term Show",
|
||||
"Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show", "Awards Ceremony",
|
||||
"Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"]
|
||||
descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen",
|
||||
"Warning: this has lots of kit"]
|
||||
notes = ["The client came into the office at some point", "Who knows if this will happen",
|
||||
"Probably should check this event", "Maybe not happening", "Run away!"]
|
||||
|
||||
itemOptions = [
|
||||
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
|
||||
'cost': 200.00},
|
||||
{'name': 'Projector',
|
||||
'description': 'Some kind of video thinamejig, probably with unnecessary processing for free',
|
||||
'quantity': 1, 'cost': 500.00},
|
||||
{'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1,
|
||||
'cost': 200.52},
|
||||
{'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
|
||||
{'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5,
|
||||
'cost': 0.50},
|
||||
{'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1,
|
||||
'cost': 100.00},
|
||||
{'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
|
||||
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
|
||||
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
|
||||
|
||||
dayDelta = -120 # start adding events from 4 months ago
|
||||
|
||||
for i in range(150): # Let's add 100 events
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
name = names[i % len(names)]
|
||||
|
||||
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
|
||||
dayDelta = dayDelta + random.randint(0, 3)
|
||||
|
||||
newEvent = models.Event.objects.create(name=name, start_date=startDate)
|
||||
|
||||
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
||||
newEvent.start_time = datetime.time(random.randint(15, 20))
|
||||
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
||||
newEvent.end_time = datetime.time(random.randint(21, 23))
|
||||
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
|
||||
newEvent.end_time = datetime.time(random.randint(0, 5))
|
||||
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4))
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
||||
newEvent.mic = random.choice(self.profiles)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
||||
newEvent.organisation = random.choice(self.organisations)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have person
|
||||
newEvent.person = random.choice(self.people)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
||||
newEvent.venue = random.choice(self.venues)
|
||||
|
||||
# Could have any status, equally weighted
|
||||
newEvent.status = random.choice(
|
||||
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
|
||||
|
||||
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
||||
|
||||
if random.randint(0, 1) > 0: # 1 in 2 have description
|
||||
newEvent.description = random.choice(descriptions)
|
||||
|
||||
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
||||
newEvent.notes = random.choice(notes)
|
||||
|
||||
newEvent.save()
|
||||
|
||||
# Now add some items
|
||||
for j in range(random.randint(1, 5)):
|
||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
while newEvent.sum_total < 0:
|
||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
|
||||
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
||||
newInvoice = models.Invoice.objects.create(event=newEvent)
|
||||
if newEvent.status is models.Event.CANCELLED: # void cancelled events
|
||||
newInvoice.void = True
|
||||
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
||||
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
|
||||
date=datetime.date.today())
|
||||
@@ -1,13 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
import django.utils.timezone
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0001_initial'),
|
||||
]
|
||||
@@ -19,18 +18,32 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(max_length=30, unique=True, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
|
||||
('is_superuser', models.BooleanField(default=False,
|
||||
help_text='Designates that this user has all permissions without explicitly assigning them.',
|
||||
verbose_name='superuser status')),
|
||||
('username', models.CharField(max_length=30, unique=True,
|
||||
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
|
||||
verbose_name='username', validators=[
|
||||
django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
|
||||
('first_name', models.CharField(max_length=30, blank=True, verbose_name='first name')),
|
||||
('last_name', models.CharField(max_length=30, blank=True, verbose_name='last name')),
|
||||
('email', models.EmailField(max_length=75, blank=True, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('is_staff', models.BooleanField(default=False,
|
||||
help_text='Designates whether the user can log into this admin site.',
|
||||
verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True,
|
||||
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
|
||||
verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('initials', models.CharField(max_length=5, unique=True)),
|
||||
('phone', models.CharField(max_length=13, blank=True, null=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups', related_name='user_set', related_query_name='user', to='auth.Group')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions', related_name='user_set', related_query_name='user', to='auth.Permission')),
|
||||
('groups', models.ManyToManyField(blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
|
||||
verbose_name='groups', related_name='user_set',
|
||||
related_query_name='user', to='auth.Group')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.',
|
||||
verbose_name='user permissions', related_name='user_set',
|
||||
related_query_name='user', to='auth.Permission')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'users',
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0001_initial'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0002_modelcomment_person'),
|
||||
]
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
import RIGS.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0003_auto_20141031_0219'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0004_organisation'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0012_auto_20141106_0253'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0013_auto_20141202_0041'),
|
||||
]
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0014_auto_20141208_0220'),
|
||||
]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0015_auto_20141208_0233'),
|
||||
]
|
||||
@@ -30,7 +29,9 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('date', models.DateField()),
|
||||
('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)),
|
||||
('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])),
|
||||
('method', models.CharField(max_length=2,
|
||||
choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'),
|
||||
(b'SU', b'SU Core'), (b'M', b'Members')])),
|
||||
('invoice', models.ForeignKey(to='RIGS.Invoice')),
|
||||
],
|
||||
options={
|
||||
@@ -40,7 +41,8 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='mic',
|
||||
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True,
|
||||
to=settings.AUTH_USER_MODEL, null=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0018_auto_20150130_0016'),
|
||||
]
|
||||
@@ -14,7 +13,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='method',
|
||||
field=models.CharField(blank=True, max_length=2, null=True, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core')]),
|
||||
field=models.CharField(blank=True, max_length=2, null=True,
|
||||
choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'),
|
||||
(b'SU', b'SU Core')]),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0019_auto_20150131_1919'),
|
||||
]
|
||||
@@ -14,7 +13,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='method',
|
||||
field=models.CharField(blank=True, max_length=2, null=True, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'T', b'TEC Adjustment')]),
|
||||
field=models.CharField(blank=True, max_length=2, null=True,
|
||||
choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'),
|
||||
(b'SU', b'SU Core'), (b'T', b'TEC Adjustment')]),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0020_auto_20150303_0243'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0021_auto_20150420_1155'),
|
||||
]
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
import django.contrib.auth.models
|
||||
import django.core.validators
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0022_auto_20150424_2104'),
|
||||
]
|
||||
@@ -46,7 +45,10 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
|
||||
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
|
||||
verbose_name='groups'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
@@ -56,7 +58,12 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='username',
|
||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
|
||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'},
|
||||
max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$',
|
||||
'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.',
|
||||
'invalid')],
|
||||
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
|
||||
unique=True, verbose_name='username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='venue',
|
||||
|
||||
20
RIGS/migrations/0024_auto_20160229_2042.py
Normal file
20
RIGS/migrations/0024_auto_20160229_2042.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
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),
|
||||
),
|
||||
]
|
||||
187
RIGS/models.py
187
RIGS/models.py
@@ -1,39 +1,49 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import datetime, pytz
|
||||
|
||||
from django.db import models, connection
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
import reversion
|
||||
import string
|
||||
import random
|
||||
import string
|
||||
from collections import Counter
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
import reversion
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
# Create your models here.
|
||||
@python_2_unicode_compatible
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||
initials = models.CharField(
|
||||
max_length=5,
|
||||
unique=True,
|
||||
null=True,
|
||||
blank=False)
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
api_key = models.CharField(max_length=40,blank=True,editable=False, null=True)
|
||||
api_key = models.CharField(
|
||||
max_length=40,
|
||||
blank=True,
|
||||
editable=False,
|
||||
null=True)
|
||||
|
||||
@classmethod
|
||||
def make_api_key(cls):
|
||||
size = 20
|
||||
chars = string.ascii_letters + string.digits
|
||||
new_api_key = ''.join(random.choice(chars) for x in range(size))
|
||||
return new_api_key;
|
||||
return new_api_key
|
||||
|
||||
@property
|
||||
def profile_picture(self):
|
||||
url = ""
|
||||
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
|
||||
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
|
||||
url = "https://www.gravatar.com/avatar/" + \
|
||||
hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
|
||||
return url
|
||||
|
||||
@property
|
||||
@@ -45,7 +55,8 @@ class Profile(AbstractUser):
|
||||
|
||||
@property
|
||||
def latest_events(self):
|
||||
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
return self.event_mic.order_by(
|
||||
'-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -55,6 +66,7 @@ class Profile(AbstractUser):
|
||||
('view_profile', 'Can view Profile'),
|
||||
)
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def last_edited_at(self):
|
||||
@@ -83,6 +95,7 @@ class RevisionMixin(object):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@reversion.register
|
||||
@python_2_unicode_compatible
|
||||
class Person(models.Model, RevisionMixin):
|
||||
@@ -104,7 +117,8 @@ class Person(models.Model, RevisionMixin):
|
||||
@property
|
||||
def organisations(self):
|
||||
o = []
|
||||
for e in Event.objects.filter(person=self).select_related('organisation'):
|
||||
for e in Event.objects.filter(
|
||||
person=self).select_related('organisation'):
|
||||
if e.organisation:
|
||||
o.append(e.organisation)
|
||||
|
||||
@@ -115,7 +129,8 @@ class Person(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
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})
|
||||
@@ -148,7 +163,8 @@ class Organisation(models.Model, RevisionMixin):
|
||||
@property
|
||||
def persons(self):
|
||||
p = []
|
||||
for e in Event.objects.filter(organisation=self).select_related('person'):
|
||||
for e in Event.objects.filter(
|
||||
organisation=self).select_related('person'):
|
||||
if e.person:
|
||||
p.append(e.person)
|
||||
|
||||
@@ -159,7 +175,8 @@ class Organisation(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
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})
|
||||
@@ -202,7 +219,8 @@ class VatRate(models.Model, RevisionMixin):
|
||||
get_latest_by = 'start_at'
|
||||
|
||||
def __str__(self):
|
||||
return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%"
|
||||
return self.comment + " " + \
|
||||
str(self.start_at) + " @ " + str(self.as_percent) + "%"
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -224,7 +242,8 @@ class Venue(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def latest_events(self):
|
||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
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})
|
||||
@@ -238,28 +257,47 @@ class Venue(models.Model, RevisionMixin):
|
||||
class EventManager(models.Manager):
|
||||
def current_events(self):
|
||||
events = self.filter(
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire
|
||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||
models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Active dry hire
|
||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
||||
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||
# Canceled but not started
|
||||
models.Q(
|
||||
status=Event.CANCELLED,
|
||||
start_date__gte=datetime.date.today())
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
return events
|
||||
|
||||
def events_in_bounds(self, start, end):
|
||||
events = self.filter(
|
||||
(models.Q(start_date__gte=start.date(), start_date__lte=end.date())) | # Start date in bounds
|
||||
(models.Q(end_date__gte=start.date(), end_date__lte=end.date())) | # End date in bounds
|
||||
(models.Q(access_at__gte=start, access_at__lte=end)) | # Access at in bounds
|
||||
# Start date in bounds
|
||||
(models.Q(start_date__gte=start.date(), start_date__lte=end.date())) |
|
||||
# End date in bounds
|
||||
(models.Q(end_date__gte=start.date(), end_date__lte=end.date())) |
|
||||
# Access at in bounds
|
||||
(models.Q(access_at__gte=start, access_at__lte=end)) |
|
||||
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
|
||||
|
||||
(models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after
|
||||
(models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after
|
||||
(models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after
|
||||
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after
|
||||
(models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after
|
||||
# Start before, end after
|
||||
(models.Q(start_date__lte=start, end_date__gte=end)) |
|
||||
# Access before, start after
|
||||
(models.Q(access_at__lte=start, start_date__gte=end)) |
|
||||
# Access before, end after
|
||||
(models.Q(access_at__lte=start, end_date__gte=end)) |
|
||||
# Meet before, start after
|
||||
(models.Q(meet_at__lte=start, start_date__gte=end)) |
|
||||
# Meet before, end after
|
||||
(models.Q(meet_at__lte=start, end_date__gte=end))
|
||||
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
return events
|
||||
|
||||
def rig_count(self):
|
||||
@@ -298,10 +336,13 @@ class Event(models.Model, RevisionMixin):
|
||||
venue = models.ForeignKey('Venue', blank=True, null=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||
status = models.IntegerField(
|
||||
choices=EVENT_STATUS_CHOICES,
|
||||
default=PROVISIONAL)
|
||||
dry_hire = models.BooleanField(default=False)
|
||||
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
|
||||
start_date = models.DateField()
|
||||
@@ -313,20 +354,33 @@ class Event(models.Model, RevisionMixin):
|
||||
meet_info = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
# Crew management
|
||||
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True)
|
||||
checked_in_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
related_name='event_checked_in',
|
||||
blank=True,
|
||||
null=True)
|
||||
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True,
|
||||
verbose_name="MIC")
|
||||
|
||||
# Monies
|
||||
payment_method = models.CharField(max_length=255, blank=True, null=True)
|
||||
payment_received = models.CharField(max_length=255, blank=True, null=True)
|
||||
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
||||
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
||||
purchase_order = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='PO')
|
||||
collector = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='collected by')
|
||||
|
||||
# Calculated values
|
||||
"""
|
||||
EX Vat
|
||||
"""
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
@@ -341,7 +395,8 @@ class Event(models.Model, RevisionMixin):
|
||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total += item.sum
|
||||
total = EventItem.objects.filter(event=self).aggregate(
|
||||
sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
)['sum_total']
|
||||
if total:
|
||||
return total
|
||||
@@ -358,6 +413,7 @@ class Event(models.Model, RevisionMixin):
|
||||
"""
|
||||
Inc VAT
|
||||
"""
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.sum_total + self.vat
|
||||
@@ -394,9 +450,11 @@ class Event(models.Model, RevisionMixin):
|
||||
# If there is no start time defined, pretend it's midnight
|
||||
startTimeFaked = False
|
||||
if self.has_start_time:
|
||||
startDateTime = datetime.datetime.combine(self.start_date,self.start_time)
|
||||
startDateTime = datetime.datetime.combine(
|
||||
self.start_date, self.start_time)
|
||||
else:
|
||||
startDateTime = datetime.datetime.combine(self.start_date,datetime.time(00,00))
|
||||
startDateTime = datetime.datetime.combine(
|
||||
self.start_date, datetime.time(00, 00))
|
||||
startTimeFaked = True
|
||||
|
||||
# timezoneIssues - apply the default timezone to the naiive datetime
|
||||
@@ -404,7 +462,8 @@ class Event(models.Model, RevisionMixin):
|
||||
startDateTime = tz.localize(startDateTime)
|
||||
datetime_list.append(startDateTime) # then add it to the list
|
||||
|
||||
earliest = min(datetime_list).astimezone(tz) #find the earliest datetime in the list
|
||||
# find the earliest datetime in the list
|
||||
earliest = min(datetime_list).astimezone(tz)
|
||||
|
||||
# if we faked it & it's the earliest, better own up
|
||||
if startTimeFaked and earliest == startDateTime:
|
||||
@@ -430,7 +489,6 @@ class Event(models.Model, RevisionMixin):
|
||||
else:
|
||||
return endDate
|
||||
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -441,12 +499,14 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
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.')
|
||||
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.')
|
||||
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."""
|
||||
@@ -475,7 +535,8 @@ class EventItem(models.Model):
|
||||
ordering = ['order']
|
||||
|
||||
def __str__(self):
|
||||
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
|
||||
return str(self.event.pk) + "." + str(self.order) + \
|
||||
": " + self.event.name + " | " + self.name
|
||||
|
||||
|
||||
class EventCrew(models.Model):
|
||||
@@ -503,15 +564,6 @@ class Invoice(models.Model):
|
||||
|
||||
@property
|
||||
def payment_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
#if connection.vendor == 'postgresql':
|
||||
# sql = "SELECT SUM(amount) AS total FROM \"RIGS_payment\" WHERE invoice_id=%i" % self.id
|
||||
#else:
|
||||
# sql = "SELECT id, SUM(amount) AS total FROM RIGS_payment WHERE invoice_id=%i" % self.id
|
||||
#total = self.payment_set.raw(sql)[0]
|
||||
#if total.total:
|
||||
# return total.total
|
||||
#return 0.0
|
||||
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
|
||||
if total:
|
||||
return total
|
||||
@@ -521,6 +573,10 @@ class Invoice(models.Model):
|
||||
def balance(self):
|
||||
return self.sum_total - self.payment_total
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.balance == 0 or self.void
|
||||
|
||||
def __str__(self):
|
||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||
|
||||
@@ -548,8 +604,15 @@ class Payment(models.Model):
|
||||
|
||||
invoice = models.ForeignKey('Invoice')
|
||||
date = models.DateField()
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
||||
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
|
||||
amount = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
help_text='Please use ex. VAT')
|
||||
method = models.CharField(
|
||||
max_length=2,
|
||||
choices=METHODS,
|
||||
null=True,
|
||||
blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %d" % (self.get_method_display(), self.amount)
|
||||
@@ -1,6 +1,6 @@
|
||||
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']
|
||||
@@ -9,5 +9,7 @@ def user_created(sender, user, request, **kwargs):
|
||||
user.phone = form.data['phone']
|
||||
user.save()
|
||||
|
||||
|
||||
from registration.signals import user_registered
|
||||
|
||||
user_registered.connect(user_created)
|
||||
@@ -1,24 +1,23 @@
|
||||
import os
|
||||
import cStringIO as StringIO
|
||||
from io import BytesIO
|
||||
import copy
|
||||
import datetime
|
||||
import re
|
||||
import urllib2
|
||||
from io import BytesIO
|
||||
|
||||
from django.views import generic
|
||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
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.views import generic
|
||||
from z3c.rml import rml2pdf
|
||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||
|
||||
from RIGS import models, forms
|
||||
import datetime
|
||||
import re
|
||||
import copy
|
||||
|
||||
__author__ = 'ghost'
|
||||
|
||||
@@ -34,9 +33,17 @@ class RigboardIndex(generic.TemplateView):
|
||||
context['events'] = models.Event.objects.current_events()
|
||||
return context
|
||||
|
||||
|
||||
class WebCalendar(generic.TemplateView):
|
||||
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):
|
||||
model = models.Event
|
||||
|
||||
@@ -52,10 +59,12 @@ class EventCreate(generic.CreateView):
|
||||
|
||||
form = context['form']
|
||||
if re.search('"-\d+"', form['items_json'].value()):
|
||||
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
|
||||
messages.info(
|
||||
self.request,
|
||||
"Your item changes have been saved. Please fix the errors and save the event.")
|
||||
|
||||
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
# Get some other objects to include in the form. Used when there are
|
||||
# errors but also nice and quick.
|
||||
for field, model in form.related_models.iteritems():
|
||||
value = form[field].value()
|
||||
if value is not None and value != '':
|
||||
@@ -76,7 +85,8 @@ class EventUpdate(generic.UpdateView):
|
||||
|
||||
form = context['form']
|
||||
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
# Get some other objects to include in the form. Used when there are
|
||||
# errors but also nice and quick.
|
||||
for field, model in form.related_models.iteritems():
|
||||
value = form[field].value()
|
||||
if value is not None and value != '':
|
||||
@@ -86,16 +96,21 @@ class EventUpdate(generic.UpdateView):
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventDuplicate(EventUpdate):
|
||||
def get_object(self, queryset=None):
|
||||
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
|
||||
# Get the object (the event you're duplicating)
|
||||
old = super(EventDuplicate, self).get_object(queryset)
|
||||
new = copy.copy(old) # Make a copy of the object in memory
|
||||
new.based_on = old # Make the new event based on the old event
|
||||
|
||||
if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
||||
if self.request.method in (
|
||||
'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
||||
new.pk = None # This means a new event will be created on save, and all items will be re-created
|
||||
|
||||
messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.')
|
||||
messages.info(
|
||||
self.request,
|
||||
'Event data duplicated but not yet saved. Click save to complete operation.')
|
||||
|
||||
return new
|
||||
|
||||
@@ -104,6 +119,7 @@ class EventDuplicate(EventUpdate):
|
||||
context["duplicate"] = True
|
||||
return context
|
||||
|
||||
|
||||
class EventPrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
object = get_object_or_404(models.Event, pk=pk)
|
||||
@@ -113,7 +129,6 @@ class EventPrint(generic.View):
|
||||
merger = PdfFileMerger()
|
||||
|
||||
for copy in copies:
|
||||
|
||||
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
|
||||
'object': object,
|
||||
'fonts': {
|
||||
@@ -126,7 +141,8 @@ class EventPrint(generic.View):
|
||||
'current_user': request.user,
|
||||
})
|
||||
|
||||
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
|
||||
# context['copy'] = copy # this is the way to do it once we upgrade
|
||||
# to Django 1.8.3
|
||||
|
||||
rml = template.render(context)
|
||||
buffer = StringIO.StringIO()
|
||||
@@ -147,10 +163,12 @@ class EventPrint(generic.View):
|
||||
|
||||
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
|
||||
response[
|
||||
'Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
|
||||
class EventArchive(generic.ArchiveIndexView):
|
||||
model = models.Event
|
||||
date_field = "start_date"
|
||||
@@ -184,6 +202,9 @@ class EventArchive(generic.ArchiveIndexView):
|
||||
qs.select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
if len(qs) == 0:
|
||||
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.WARNING,
|
||||
"No events have been found matching those criteria.")
|
||||
|
||||
return qs
|
||||
@@ -11,6 +11,7 @@ fonts_dir = "fonts"
|
||||
|
||||
# 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 = :compressed
|
||||
|
||||
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||
# 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
@@ -1,8 +1,8 @@
|
||||
function setupItemTable(items_json) {
|
||||
objectitems = JSON.parse(items_json)
|
||||
objectitems = JSON.parse(items_json);
|
||||
$.each(objectitems, function (key, val) {
|
||||
objectitems[key] = JSON.parse(val);
|
||||
})
|
||||
});
|
||||
newitem = -1;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ function updatePrices() {
|
||||
}
|
||||
|
||||
$('#item-table').on('click', '.item-delete', function () {
|
||||
delete objectitems[$(this).data('pk')]
|
||||
delete objectitems[$(this).data('pk')];
|
||||
$('#item-' + $(this).data('pk')).remove();
|
||||
updatePrices();
|
||||
});
|
||||
@@ -73,8 +73,8 @@ $('body').on('submit', '#item-form', function (e) {
|
||||
var fields;
|
||||
if (pk == newitem--) {
|
||||
// Create the new data structure and add it on.
|
||||
fields = new Object();
|
||||
fields['name'] = $('#item_name').val()
|
||||
fields = {};
|
||||
fields['name'] = $('#item_name').val();
|
||||
fields['description'] = $('#item_description').val();
|
||||
fields['cost'] = $('#item_cost').val();
|
||||
fields['quantity'] = $('#item_quantity').val();
|
||||
@@ -86,7 +86,7 @@ $('body').on('submit', '#item-form', function (e) {
|
||||
|
||||
fields['order'] = order;
|
||||
|
||||
objectitems[pk] = new Object();
|
||||
objectitems[pk] = {};
|
||||
objectitems[pk]['fields'] = fields;
|
||||
|
||||
// Add the new table
|
||||
@@ -96,7 +96,7 @@ $('body').on('submit', '#item-form', function (e) {
|
||||
// Existing item
|
||||
// update data structure
|
||||
fields = objectitems[pk].fields;
|
||||
fields.name = $('#item_name').val()
|
||||
fields.name = $('#item_name').val();
|
||||
fields.description = $('#item_description').val();
|
||||
fields.cost = $('#item_cost').val();
|
||||
fields.quantity = $('#item_quantity').val();
|
||||
@@ -129,7 +129,7 @@ $("#item-table tbody").sortable({
|
||||
helper: fixHelper,
|
||||
update: function (e, ui) {
|
||||
info = $(this).sortable("toArray");
|
||||
itemorder = new Array();
|
||||
itemorder = [];
|
||||
$.each(info, function (key, value) {
|
||||
pk = $('#' + value).data('pk');
|
||||
objectitems[pk].fields.order = key;
|
||||
|
||||
@@ -39,7 +39,7 @@ var Konami = function (callback) {
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
this.iphone.load(link);
|
||||
/*this.iphone.load(link);*/
|
||||
},
|
||||
code: function (link) {
|
||||
window.location = link
|
||||
|
||||
@@ -6,7 +6,6 @@ $bootstrap-sass-asset-helper: (twbs-font-path("") != unquote('twbs-font-path("")
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
//== Colors
|
||||
//
|
||||
//## Gray and brand colors for use across Bootstrap.
|
||||
@@ -23,7 +22,6 @@ $brand-info: #5bc0de !default;
|
||||
$brand-warning: #f0ad4e !default;
|
||||
$brand-danger: #d9534f !default;
|
||||
|
||||
|
||||
//== Scaffolding
|
||||
//
|
||||
//## Settings for some of the most global styles.
|
||||
@@ -38,7 +36,6 @@ $link-color: $brand-primary !default;
|
||||
//** Link hover color set via `darken()` function.
|
||||
$link-hover-color: darken($link-color, 15%) !default;
|
||||
|
||||
|
||||
//== Typography
|
||||
//
|
||||
//## Font, line-height, and color for body text, headings, and more.
|
||||
@@ -71,7 +68,6 @@ $headings-font-weight: 500 !default;
|
||||
$headings-line-height: 1.1 !default;
|
||||
$headings-color: inherit !default;
|
||||
|
||||
|
||||
//== Iconography
|
||||
//
|
||||
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
|
||||
@@ -83,7 +79,6 @@ $icon-font-name: "glyphicons-halflings-regular" !default;
|
||||
//** Element ID within SVG icon file.
|
||||
$icon-font-svg-id: "glyphicons_halflingsregular" !default;
|
||||
|
||||
|
||||
//== Components
|
||||
//
|
||||
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
|
||||
@@ -117,7 +112,6 @@ $caret-width-base: 4px !default;
|
||||
//** Carets increase slightly in size for larger components.
|
||||
$caret-width-large: 5px !default;
|
||||
|
||||
|
||||
//== Tables
|
||||
//
|
||||
//## Customizes the `.table` component with basic values, each used across all table variations.
|
||||
@@ -138,7 +132,6 @@ $table-bg-active: $table-bg-hover !default;
|
||||
//** Border color for table and cell borders.
|
||||
$table-border-color: #ddd !default;
|
||||
|
||||
|
||||
//== Buttons
|
||||
//
|
||||
//## For each of Bootstrap's buttons, define text, background and border color.
|
||||
@@ -171,7 +164,6 @@ $btn-danger-border: darken($btn-danger-bg, 5%) !default;
|
||||
|
||||
$btn-link-disabled-color: $gray-light !default;
|
||||
|
||||
|
||||
//== Forms
|
||||
//
|
||||
//##
|
||||
@@ -208,7 +200,6 @@ $input-group-addon-bg: $gray-lighter !default;
|
||||
//** Border color for textual input addons
|
||||
$input-group-addon-border-color: $input-border !default;
|
||||
|
||||
|
||||
//== Dropdowns
|
||||
//
|
||||
//## Dropdown menu container and contents.
|
||||
@@ -243,7 +234,6 @@ $dropdown-header-color: $gray-light !default;
|
||||
//** Deprecated `$dropdown-caret-color` as of v3.1.0
|
||||
$dropdown-caret-color: #000 !default;
|
||||
|
||||
|
||||
//-- Z-index master list
|
||||
//
|
||||
// Warning: Avoid customizing these values. They're used for a bird's eye view
|
||||
@@ -259,7 +249,6 @@ $zindex-navbar-fixed: 1030 !default;
|
||||
$zindex-modal-background: 1040 !default;
|
||||
$zindex-modal: 1050 !default;
|
||||
|
||||
|
||||
//== Media queries breakpoints
|
||||
//
|
||||
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
|
||||
@@ -298,7 +287,6 @@ $screen-xs-max: ($screen-sm-min - 1) !default;
|
||||
$screen-sm-max: ($screen-md-min - 1) !default;
|
||||
$screen-md-max: ($screen-lg-min - 1) !default;
|
||||
|
||||
|
||||
//== Grid system
|
||||
//
|
||||
//## Define your custom responsive grid.
|
||||
@@ -313,7 +301,6 @@ $grid-float-breakpoint: $screen-sm-min !default;
|
||||
//** Point at which the navbar begins collapsing.
|
||||
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
|
||||
|
||||
|
||||
//== Container sizes
|
||||
//
|
||||
//## Define the maximum width of `.container` for different screen sizes.
|
||||
@@ -333,7 +320,6 @@ $container-large-desktop: ((1140px + $grid-gutter-width)) !default;
|
||||
//** For `$screen-lg-min` and up.
|
||||
$container-lg: $container-large-desktop !default;
|
||||
|
||||
|
||||
//== Navbar
|
||||
//
|
||||
//##
|
||||
@@ -369,7 +355,6 @@ $navbar-default-toggle-hover-bg: #ddd !default;
|
||||
$navbar-default-toggle-icon-bar-bg: #888 !default;
|
||||
$navbar-default-toggle-border-color: #ddd !default;
|
||||
|
||||
|
||||
// Inverted navbar
|
||||
// Reset inverted navbar basics
|
||||
$navbar-inverse-color: $gray-light !default;
|
||||
@@ -395,7 +380,6 @@ $navbar-inverse-toggle-hover-bg: #333 !default;
|
||||
$navbar-inverse-toggle-icon-bar-bg: #fff !default;
|
||||
$navbar-inverse-toggle-border-color: #333 !default;
|
||||
|
||||
|
||||
//== Navs
|
||||
//
|
||||
//##
|
||||
@@ -426,7 +410,6 @@ $nav-pills-border-radius: $border-radius-base !default;
|
||||
$nav-pills-active-link-hover-bg: $component-active-bg !default;
|
||||
$nav-pills-active-link-hover-color: $component-active-color !default;
|
||||
|
||||
|
||||
//== Pagination
|
||||
//
|
||||
//##
|
||||
@@ -447,7 +430,6 @@ $pagination-disabled-color: $gray-light !default;
|
||||
$pagination-disabled-bg: #fff !default;
|
||||
$pagination-disabled-border: #ddd !default;
|
||||
|
||||
|
||||
//== Pager
|
||||
//
|
||||
//##
|
||||
@@ -463,7 +445,6 @@ $pager-active-color: $pagination-active-color !default;
|
||||
|
||||
$pager-disabled-color: $pagination-disabled-color !default;
|
||||
|
||||
|
||||
//== Jumbotron
|
||||
//
|
||||
//##
|
||||
@@ -474,7 +455,6 @@ $jumbotron-bg: $gray-lighter !default;
|
||||
$jumbotron-heading-color: inherit !default;
|
||||
$jumbotron-font-size: ceil(($font-size-base * 1.5)) !default;
|
||||
|
||||
|
||||
//== Form states and alerts
|
||||
//
|
||||
//## Define colors for form feedback states and, by default, alerts.
|
||||
@@ -495,7 +475,6 @@ $state-danger-text: #a94442 !default;
|
||||
$state-danger-bg: #f2dede !default;
|
||||
$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default;
|
||||
|
||||
|
||||
//== Tooltips
|
||||
//
|
||||
//##
|
||||
@@ -513,7 +492,6 @@ $tooltip-arrow-width: 5px !default;
|
||||
//** Tooltip arrow color
|
||||
$tooltip-arrow-color: $tooltip-bg !default;
|
||||
|
||||
|
||||
//== Popovers
|
||||
//
|
||||
//##
|
||||
@@ -542,7 +520,6 @@ $popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !defa
|
||||
//** Popover outer arrow fallback color
|
||||
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
|
||||
|
||||
|
||||
//== Labels
|
||||
//
|
||||
//##
|
||||
@@ -565,7 +542,6 @@ $label-color: #fff !default;
|
||||
//** Default text color of a linked label
|
||||
$label-link-hover-color: #fff !default;
|
||||
|
||||
|
||||
//== Modals
|
||||
//
|
||||
//##
|
||||
@@ -598,7 +574,6 @@ $modal-lg: 900px !default;
|
||||
$modal-md: 600px !default;
|
||||
$modal-sm: 300px !default;
|
||||
|
||||
|
||||
//== Alerts
|
||||
//
|
||||
//## Define alert colors, border radius, and padding.
|
||||
@@ -623,7 +598,6 @@ $alert-danger-bg: $state-danger-bg !default;
|
||||
$alert-danger-text: $state-danger-text !default;
|
||||
$alert-danger-border: $state-danger-border !default;
|
||||
|
||||
|
||||
//== Progress bars
|
||||
//
|
||||
//##
|
||||
@@ -644,7 +618,6 @@ $progress-bar-danger-bg: $brand-danger !default;
|
||||
//** Info progress bar color
|
||||
$progress-bar-info-bg: $brand-info !default;
|
||||
|
||||
|
||||
//== List group
|
||||
//
|
||||
//##
|
||||
@@ -678,7 +651,6 @@ $list-group-link-color: #555 !default;
|
||||
$list-group-link-hover-color: $list-group-link-color !default;
|
||||
$list-group-link-heading-color: #333 !default;
|
||||
|
||||
|
||||
//== Panels
|
||||
//
|
||||
//##
|
||||
@@ -717,7 +689,6 @@ $panel-danger-text: $state-danger-text !default;
|
||||
$panel-danger-border: $state-danger-border !default;
|
||||
$panel-danger-heading-bg: $state-danger-bg !default;
|
||||
|
||||
|
||||
//== Thumbnails
|
||||
//
|
||||
//##
|
||||
@@ -736,7 +707,6 @@ $thumbnail-caption-color: $text-color !default;
|
||||
//** Padding around the thumbnail caption
|
||||
$thumbnail-caption-padding: 9px !default;
|
||||
|
||||
|
||||
//== Wells
|
||||
//
|
||||
//##
|
||||
@@ -744,7 +714,6 @@ $thumbnail-caption-padding: 9px !default;
|
||||
$well-bg: #f5f5f5 !default;
|
||||
$well-border: darken($well-bg, 7%) !default;
|
||||
|
||||
|
||||
//== Badges
|
||||
//
|
||||
//##
|
||||
@@ -763,7 +732,6 @@ $badge-font-weight: bold !default;
|
||||
$badge-line-height: 1 !default;
|
||||
$badge-border-radius: 10px !default;
|
||||
|
||||
|
||||
//== Breadcrumbs
|
||||
//
|
||||
//##
|
||||
@@ -779,7 +747,6 @@ $breadcrumb-active-color: $gray-light !default;
|
||||
//** Textual separator for between breadcrumb elements
|
||||
$breadcrumb-separator: "/" !default;
|
||||
|
||||
|
||||
//== Carousel
|
||||
//
|
||||
//##
|
||||
@@ -796,7 +763,6 @@ $carousel-indicator-border-color: #fff !default;
|
||||
|
||||
$carousel-caption-color: #fff !default;
|
||||
|
||||
|
||||
//== Close
|
||||
//
|
||||
//##
|
||||
@@ -805,7 +771,6 @@ $close-font-weight: bold !default;
|
||||
$close-color: #000 !default;
|
||||
$close-text-shadow: 0 1px 0 #fff !default;
|
||||
|
||||
|
||||
//== Code
|
||||
//
|
||||
//##
|
||||
@@ -821,7 +786,6 @@ $pre-color: $gray-dark !default;
|
||||
$pre-border-color: #ccc !default;
|
||||
$pre-scrollable-max-height: 340px !default;
|
||||
|
||||
|
||||
//== Type
|
||||
//
|
||||
//##
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
@import "jq-ui-bootstrap/_autocomplete";
|
||||
@import "jq-ui-bootstrap/_menu";
|
||||
@import "jq-ui-bootstrap/_tooltip";
|
||||
|
||||
@import "compass/css3/animation";
|
||||
@import "compass/css3/transform";
|
||||
|
||||
@@ -75,6 +74,16 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
del {
|
||||
background-color: #f2dede;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
ins {
|
||||
background-color: #dff0d8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
position: relative;
|
||||
margin: 30px auto 0;
|
||||
@@ -124,7 +133,8 @@ textarea {
|
||||
100% {
|
||||
@include rotate(-320deg);
|
||||
opacity: 0;
|
||||
};
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
@include keyframes(spinoffPulse) {
|
||||
@@ -134,23 +144,7 @@ textarea {
|
||||
|
||||
100% {
|
||||
@include rotate(360deg);
|
||||
};
|
||||
}
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
|
||||
&.affix {
|
||||
top: $navbar-height;
|
||||
}
|
||||
}
|
||||
|
||||
.anchor {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -$navbar-height - 10px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// }, 10000);
|
||||
moment().twitter();
|
||||
|
||||
})
|
||||
});
|
||||
$(document).ready(function () {
|
||||
$(function () {
|
||||
$("#activity").hide();
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
<div class="media-left">
|
||||
{% if version.revision.user %}
|
||||
<a href="{% url 'profile_detail' pk=version.revision.user.pk %}" class="modal-href">
|
||||
<img class="media-object img-rounded" src="{{ version.revision.user.profile_picture}}" />
|
||||
<img class="media-object img-rounded"
|
||||
src="{{ version.revision.user.profile_picture }}"/>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<h5>{{ version.revision.user.name }}
|
||||
<span class="pull-right"><small><span class="date" data-date="{{version.revision.date_created|date:"c"}}"></span></small></span>
|
||||
<span class="pull-right"><small><span class="date"
|
||||
data-date="{{ version.revision.date_created|date:"c" }}"></span></small></span>
|
||||
</h5>
|
||||
|
||||
{% endif %}
|
||||
@@ -40,6 +42,9 @@
|
||||
{% endif %}
|
||||
|
||||
{% include 'RIGS/object_button.html' with object=version.new %}
|
||||
{% if version.revision.comment %}
|
||||
({{ version.revision.comment }})
|
||||
{% endif %}
|
||||
</small>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
<td>Comment</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -66,15 +67,18 @@
|
||||
|
||||
<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>
|
||||
<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
|
||||
{{ version.new|to_class_name }} Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %} </td>
|
||||
<td>{{ version.revision.comment }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
41
RIGS/templates/RIGS/admin_associate_merge.html
Normal file
41
RIGS/templates/RIGS/admin_associate_merge.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<p>The following objects will be merged. Please select the 'master' record which you would like to keep. Other
|
||||
records will have associated events moved to the 'master' copy, and then will be deleted.</p>
|
||||
|
||||
<table>
|
||||
{% for form in forms %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th> ID</th>
|
||||
{% for field in form %}
|
||||
<th>{{ field.label }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td><input type="radio" name="master" value="{{ form.instance.pk|unlocalize }}"></td>
|
||||
<td>{{ form.instance.pk }}</td>
|
||||
{% for field in form %}
|
||||
<td> {{ field.value }} </td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<div>
|
||||
{% for obj in queryset %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}"/>
|
||||
{% endfor %}
|
||||
<input type="hidden" name="action" value="merge"/>
|
||||
<input type="hidden" name="post" value="yes"/>
|
||||
<input type="submit" value="Merge them"/>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -14,8 +14,28 @@
|
||||
<script src="{% static "js/moment.min.js" %}"></script>
|
||||
<script src="{% static "js/fullcalendar.js" %}"></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 () {
|
||||
|
||||
viewToUrl = {
|
||||
'agendaWeek': 'week',
|
||||
'agendaDay': 'day',
|
||||
'month': 'month'
|
||||
};
|
||||
viewFromUrl = {
|
||||
'week': 'agendaWeek',
|
||||
'day': 'agendaDay',
|
||||
'month': 'month'
|
||||
};
|
||||
|
||||
$('#calendar').fullCalendar({
|
||||
editable: false,
|
||||
eventLimit: true, // allow "more" link when too many events
|
||||
@@ -49,16 +69,17 @@
|
||||
},
|
||||
success: function (doc) {
|
||||
var events = [];
|
||||
colours = {'Provisional': '#f0ad4e',
|
||||
colours = {
|
||||
'Provisional': '#f0ad4e',
|
||||
'Confirmed': '#5cb85c',
|
||||
'Booked': '#5cb85c',
|
||||
'Cancelled': 'grey',
|
||||
'non-rig': '#5bc0de'
|
||||
};
|
||||
$(doc).each(function () {
|
||||
end = $(this).attr('latest')
|
||||
end = $(this).attr('latest');
|
||||
if (end.indexOf("T") < 0) { //If latest does not contain a time
|
||||
end = moment(end).add(1, 'days') //End date is non-inclusive, so add a day
|
||||
end = moment(end).add(1, 'days'); //End date is non-inclusive, so add a day
|
||||
}
|
||||
|
||||
thisEvent = {
|
||||
@@ -67,7 +88,7 @@
|
||||
'className': 'modal-href',
|
||||
'title': $(this).attr('title'),
|
||||
'url': $(this).attr('url')
|
||||
}
|
||||
};
|
||||
|
||||
if ($(this).attr('is_rig') == true || $(this).attr('status') == "Cancelled") {
|
||||
thisEvent['color'] = colours[$(this).attr('status')];
|
||||
@@ -114,19 +135,33 @@
|
||||
$('#day-button').addClass('active');
|
||||
break;
|
||||
}
|
||||
|
||||
history.replaceState(null, null, '{% url 'web_calendar' %}' + viewToUrl[view.name] + '/' + view.intervalStart.format('YYYY-MM-DD') + '/');
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// set some button listeners
|
||||
|
||||
$('#next-button').click(function(){ $('#calendar').fullCalendar('next') });
|
||||
$('#prev-button').click(function(){ $('#calendar').fullCalendar('prev') });
|
||||
$('#today-button').click(function(){ $('#calendar').fullCalendar('today') });
|
||||
$('#next-button').click(function () {
|
||||
$('#calendar').fullCalendar('next')
|
||||
});
|
||||
$('#prev-button').click(function () {
|
||||
$('#calendar').fullCalendar('prev')
|
||||
});
|
||||
$('#today-button').click(function () {
|
||||
$('#calendar').fullCalendar('today')
|
||||
});
|
||||
|
||||
$('#month-button').click(function(){ $('#calendar').fullCalendar('changeView','month') });
|
||||
$('#week-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaWeek') });
|
||||
$('#day-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaDay') });
|
||||
$('#month-button').click(function () {
|
||||
$('#calendar').fullCalendar('changeView', 'month')
|
||||
});
|
||||
$('#week-button').click(function () {
|
||||
$('#calendar').fullCalendar('changeView', 'agendaWeek')
|
||||
});
|
||||
$('#day-button').click(function () {
|
||||
$('#calendar').fullCalendar('changeView', 'agendaDay')
|
||||
});
|
||||
|
||||
$('#go-to-date-input').change(function () {
|
||||
if (moment($('#go-to-date-input').val()).isValid()) {
|
||||
@@ -146,6 +181,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>
|
||||
@@ -173,8 +220,10 @@
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default" id="prev-button"><span class="glyphicon glyphicon-chevron-left"></span></button>
|
||||
<button type="button" class="btn btn-default" id="next-button"><span class="glyphicon glyphicon-chevron-right"></span></button>
|
||||
<button type="button" class="btn btn-default" id="prev-button"><span
|
||||
class="glyphicon glyphicon-chevron-left"></span></button>
|
||||
<button type="button" class="btn btn-default" id="next-button"><span
|
||||
class="glyphicon glyphicon-chevron-right"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="start">Start</label>
|
||||
<input type="date" name="start" id="start" value="{{ request.GET.start }}" placeholder="Start" class="form-control" />
|
||||
<input type="date" name="start" id="start" value="{{ request.GET.start }}" placeholder="Start"
|
||||
class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="end">End</label>
|
||||
<input type="date" name="end" id="end" value="{% if request.GET.end %}{{ request.GET.end }}{% else %}{% now "Y-m-d" %}{% endif %}" placeholder="End" class="form-control" />
|
||||
<input type="date" name="end" id="end"
|
||||
value="{% if request.GET.end %}{{ request.GET.end }}{% else %}{% now "Y-m-d" %}{% endif %}"
|
||||
placeholder="End" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary"/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
|
||||
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
|
||||
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} |
|
||||
{{ object.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
@@ -25,9 +26,17 @@
|
||||
class="hidden-xs">Duplicate</span></a>
|
||||
{% if event.is_rig %}
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span> <span
|
||||
class="hidden-xs">Invoice</span></a>
|
||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
||||
{% if event.invoice and event.invoice.is_closed %}
|
||||
btn-success
|
||||
{% elif event.invoice %}
|
||||
btn-warning
|
||||
{% else %}
|
||||
btn-danger
|
||||
{% endif %}
|
||||
" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span>
|
||||
<span class="hidden-xs">Invoice</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -70,7 +79,8 @@
|
||||
<dt>Organisation</dt>
|
||||
<dd>
|
||||
{% if object.organisation %}
|
||||
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
|
||||
<a href="{% url 'organisation_detail' object.organisation.pk %}"
|
||||
class="modal-href">
|
||||
{{ object.organisation }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -149,9 +159,17 @@
|
||||
<dd>
|
||||
{% if object.based_on %}
|
||||
<a href="{% url 'event_detail' pk=object.based_on.pk %}">
|
||||
{% if object.based_on.is_rig %}N{{ object.based_on.pk|stringformat:"05d" }}{% else %}
|
||||
{{ object.based_on.pk }}{% endif %}
|
||||
{{ object.base_on.name }} by {{ object.based_on.mic.name }}
|
||||
{% if object.based_on.is_rig %}
|
||||
N{{ object.based_on.pk|stringformat:"05d" }}
|
||||
{% else %}
|
||||
{{ object.based_on.pk }}
|
||||
{% endif %}
|
||||
|
||||
{{ object.based_on.name }}
|
||||
|
||||
{% if object.based_on.mic %}
|
||||
by {{ object.based_on.mic.name }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
@@ -190,9 +208,17 @@
|
||||
class="hidden-xs">Duplicate</span></a>
|
||||
{% if event.is_rig %}
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span> <span
|
||||
class="hidden-xs">Invoice</span></a>
|
||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
||||
{% if event.invoice and event.invoice.is_closed %}
|
||||
btn-success
|
||||
{% elif event.invoice %}
|
||||
btn-warning
|
||||
{% else %}
|
||||
btn-danger
|
||||
{% endif %}
|
||||
" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span>
|
||||
<span class="hidden-xs">Invoice</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -222,17 +248,31 @@
|
||||
class="glyphicon glyphicon-print"></span> <span
|
||||
class="hidden-xs">Print</span></a>
|
||||
{% endif %}
|
||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
|
||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default"
|
||||
title="Duplicate Rig"><span
|
||||
class="glyphicon glyphicon-duplicate"></span> <span
|
||||
class="hidden-xs">Duplicate</span></a>
|
||||
{% if event.is_rig %}
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span> <span
|
||||
class="hidden-xs">Invoice</span></a>
|
||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
||||
{% if event.invoice and event.invoice.is_closed %}
|
||||
btn-success
|
||||
{% elif event.invoice %}
|
||||
btn-warning
|
||||
{% else %}
|
||||
btn-danger
|
||||
{% endif %}
|
||||
" title="Invoice Rig"><span
|
||||
class="glyphicon glyphicon-gbp"></span>
|
||||
<span class="hidden-xs">Invoice</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not request.is_ajax %}
|
||||
<div class="col-sm-12 text-right">
|
||||
<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 }}
|
||||
@@ -240,7 +280,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
}
|
||||
|
||||
function setTime02Hours() {
|
||||
var id_start = "{{ form.start_date.id_for_label }}"
|
||||
var id_end_date = "{{ form.end_date.id_for_label }}"
|
||||
var id_end_time = "{{ form.end_time.id_for_label }}"
|
||||
var id_start = "{{ form.start_date.id_for_label }}";
|
||||
var id_end_date = "{{ form.end_date.id_for_label }}";
|
||||
var id_end_time = "{{ form.end_time.id_for_label }}";
|
||||
if ($('#' + id_start).val() == $('#' + id_end_date).val()) {
|
||||
var end_date = new Date($('#' + id_end_date).val());
|
||||
end_date.setDate(end_date.getDate() + 1);
|
||||
@@ -63,11 +63,12 @@
|
||||
} else {
|
||||
$('.form-is_rig').slideDown();
|
||||
}
|
||||
$('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible');
|
||||
} else {
|
||||
$('#{{form.is_rig.auto_id}}').prop('checked', false);
|
||||
$('.form-is_rig').slideUp();
|
||||
}
|
||||
})
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
function supportsDate() {
|
||||
@@ -78,6 +79,7 @@
|
||||
input.setAttribute('value', notADateValue);
|
||||
return !(input.value === notADateValue);
|
||||
}
|
||||
|
||||
if (supportsDate()) {
|
||||
//Good, we'll use the browser implementation
|
||||
} else {
|
||||
@@ -106,7 +108,7 @@
|
||||
});
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
|
||||
@@ -150,10 +152,12 @@
|
||||
<div class="col-md-12 well">
|
||||
<div class="form-group" id="is_rig-selector">
|
||||
<div class="col-sm-12">
|
||||
<span class="col-sm-6">
|
||||
<span class="col-sm-6" data-toggle="tooltip"
|
||||
title="Anything that involves TEC kit, crew, or otherwise us providing a service to anyone.">
|
||||
<button type="button" class="btn btn-primary col-xs-12" data-is_rig="1">Rig</button>
|
||||
</span>
|
||||
<span class="col-sm-6">
|
||||
<span class="col-sm-6" data-toggle="tooltip"
|
||||
title="Things that aren't service-based, like training, meetings and site visits.">
|
||||
<button type="button" class="btn btn-info col-xs-12" data-is_rig="0">Non-Rig</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -166,16 +170,20 @@
|
||||
<div class="panel panel-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="panel-heading">Contact Details</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group" data-toggle="tooltip" title="The main contact for the event, can be left blank if purely an organisation">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The main contact for the event, can be left blank if purely an organisation">
|
||||
<label for="{{ form.person.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.person.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
|
||||
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}"
|
||||
class="form-control selectpicker" data-live-search="true"
|
||||
data-sourceurl="{% url 'api_secure' model='person' %}">
|
||||
{% if person %}
|
||||
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
|
||||
<option value="{{ form.person.value }}" selected="selected"
|
||||
data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -185,7 +193,11 @@
|
||||
data-target="#{{ form.person.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</a>
|
||||
<a href="{% if form.person.value %}{% url 'person_update' form.person.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.person.id_for_label }}-update" data-target="#{{ form.person.id_for_label }}">
|
||||
<a href="
|
||||
|
||||
{% if form.person.value %}{% url 'person_update' form.person.value %}{% endif %}"
|
||||
class="btn btn-default modal-href" id="{{ form.person.id_for_label }}-update"
|
||||
data-target="#{{ form.person.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -193,16 +205,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The client organisation, leave blank if client is an individual">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The client organisation, leave blank if client is an individual">
|
||||
<label for="{{ form.organisation.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.organisation.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
|
||||
<select id="{{ form.organisation.id_for_label }}"
|
||||
name="{{ form.organisation.name }}" class="form-control selectpicker"
|
||||
data-live-search="true"
|
||||
data-sourceurl="{% url 'api_secure' model='organisation' %}">
|
||||
{% if organisation %}
|
||||
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
|
||||
<option value="{{ form.organisation.value }}" selected="selected"
|
||||
data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -212,7 +229,12 @@
|
||||
data-target="#{{ form.organisation.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</a>
|
||||
<a href="{% if form.organisation.value %}{% url 'organisation_update' form.organisation.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.organisation.id_for_label }}-update" data-target="#{{ form.organisation.id_for_label }}">
|
||||
<a href="
|
||||
|
||||
{% if form.organisation.value %}{% url 'organisation_update' form.organisation.value %}{% endif %}"
|
||||
class="btn btn-default modal-href"
|
||||
id="{{ form.organisation.id_for_label }}-update"
|
||||
data-target="#{{ form.organisation.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -225,7 +247,8 @@
|
||||
<div class="panel panel-default form-hws form-non_rig">
|
||||
<div class="panel-heading">Event Description</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group" data-toggle="tooltip" title="A short description of the event, shown on rigboard and on paperwork">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="A short description of the event, shown on rigboard and on paperwork">
|
||||
<label for="{{ form.description.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.description.label }}</label>
|
||||
|
||||
@@ -244,7 +267,8 @@
|
||||
<div class="panel-heading">Event Details</div>
|
||||
<div class="panel-body">
|
||||
<div id="form-hws">
|
||||
<div class="form-group" data-toggle="tooltip" title="Name of the event, displays on rigboard and on paperwork">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="Name of the event, displays on rigboard and on paperwork">
|
||||
<label for="{{ form.name.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.name.label }}</label>
|
||||
|
||||
@@ -252,16 +276,20 @@
|
||||
{% render_field form.name class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The venue for the rig, leave blank if unknown (e.g. for a dry hire)">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The venue for the rig, leave blank if unknown (e.g. for a dry hire)">
|
||||
<label for="{{ form.venue.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.venue.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}"
|
||||
class="form-control selectpicker" data-live-search="true"
|
||||
data-sourceurl="{% url 'api_secure' model='venue' %}">
|
||||
{% if venue %}
|
||||
<option value="{{form.venue.value}}" selected="selected" data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
|
||||
<option value="{{ form.venue.value }}" selected="selected"
|
||||
data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -271,7 +299,12 @@
|
||||
data-target="#{{ form.venue.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</a>
|
||||
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-default modal-href" id="{{ form.venue.id_for_label }}-update" data-target="#{{ form.venue.id_for_label }}">
|
||||
<a href="
|
||||
|
||||
{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}"
|
||||
class="btn btn-default modal-href"
|
||||
id="{{ form.venue.id_for_label }}-update"
|
||||
data-target="#{{ form.venue.id_for_label }}">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -285,10 +318,12 @@
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip"
|
||||
title="Start date for event, required">
|
||||
{% render_field form.start_date type="date" class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank">
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip"
|
||||
title="Start time of event, can be left blank">
|
||||
{% render_field form.start_time type="time" class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,10 +335,12 @@
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip"
|
||||
title="End date of event, leave blank if unknown or same as start date">
|
||||
{% render_field form.end_date type="date" class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown">
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip"
|
||||
title="End time of event, leave blank if unknown">
|
||||
{% render_field form.end_time type="time" class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -321,7 +358,8 @@
|
||||
|
||||
{# Rig only information #}
|
||||
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="form-group" data-toggle="tooltip" title="The date/time at which TEC have access to the venue">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The date/time at which TEC have access to the venue">
|
||||
<label for="{{ form.access_at.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.access_at.label }}</label>
|
||||
|
||||
@@ -329,7 +367,8 @@
|
||||
{% render_field form.access_at type="datetime-local" class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The date/time at which crew should meet for this event">
|
||||
<label for="{{ form.meet_at.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.meet_at.label }}</label>
|
||||
|
||||
@@ -340,7 +379,8 @@
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8">
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
|
||||
<label data-toggle="tooltip"
|
||||
title="Mark this event as a dry-hire, so it needs to be checked in at the end">
|
||||
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -349,7 +389,8 @@
|
||||
</div>
|
||||
|
||||
{# Status is needed on all events types and it looks good here in the form #}
|
||||
<div class="form-group" data-toggle="tooltip" title="The current status of the event. Only mark as booked once paperwork is received">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The current status of the event. Only mark as booked once paperwork is received">
|
||||
<label for="{{ form.status.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.status.label }}</label>
|
||||
|
||||
@@ -364,30 +405,39 @@
|
||||
class="col-sm-4 control-label">{{ form.mic.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}"
|
||||
class="form-control selectpicker" data-live-search="true"
|
||||
data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if mic %}
|
||||
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
|
||||
<option value="{{ form.mic.value }}"
|
||||
selected="selected">{{ mic.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.dry_hire %}
|
||||
<div class="form-group" data-toggle="tooltip" title="The person who checked-in this dry hire">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The person who checked-in this dry hire">
|
||||
<label for="{{ form.checked_in_by.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.checked_in_by.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
<select id="{{ form.checked_in_by.id_for_label }}"
|
||||
name="{{ form.checked_in_by.name }}" class="form-control selectpicker"
|
||||
data-live-search="true"
|
||||
data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if checked_in_by %}
|
||||
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
|
||||
<option value="{{ form.checked_in_by.value }}"
|
||||
selected="selected">{{ checked_in_by.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group" data-toggle="tooltip" title="The student ID of the client who collected the dry-hire">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The student ID of the client who collected the dry-hire">
|
||||
<label for="{{ form.collector.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.collector.label }}</label>
|
||||
|
||||
@@ -395,7 +445,8 @@
|
||||
{% render_field form.collector class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="The purchase order number (for external clients)">
|
||||
<label for="{{ form.purchase_order.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>
|
||||
|
||||
@@ -423,7 +474,8 @@
|
||||
<div class="panel panel-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="panel-body">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
|
||||
<div class="form-group" data-toggle="tooltip"
|
||||
title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
|
||||
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
|
||||
{% render_field form.notes class+="form-control" %}
|
||||
</div>
|
||||
|
||||
@@ -1,59 +1,84 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Events for Invoice{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>Events for Invoice</h2>
|
||||
<h2>Events for Invoice ({{ count }} Events, £ {{ total|floatformat:2 }})</h2>
|
||||
<p>These events have happened, but paperwork has not yet been sent to treasury</p>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-responsive table-hover">
|
||||
<div class="table-responsive col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hiddenx-xs">#</th>
|
||||
<th>Date</th>
|
||||
<th>Event</th>
|
||||
<th>Event #</th>
|
||||
<th>Start Date</th>
|
||||
<th>Event Name</th>
|
||||
<th>Client</th>
|
||||
<th>Cost</th>
|
||||
<th class="hidden-xs">MIC</th>
|
||||
<th>MIC</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="
|
||||
{% if event.cancelled %}
|
||||
active
|
||||
{% elif event.confirmed and event.mic or not event.is_rig %}
|
||||
{# interpreated as (booked and mic) or is non rig #}
|
||||
{% if object.cancelled %}
|
||||
active text-muted
|
||||
{% elif not object.is_rig %}
|
||||
info
|
||||
{% elif object.confirmed and object.mic %}
|
||||
{# interpreated as (booked and mic) #}
|
||||
success
|
||||
{% elif event.mic %}
|
||||
{% elif object.mic %}
|
||||
warning
|
||||
{% else %}
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td class="hidden-xs"><a href="{% url 'event_detail' object.pk %}" target="_blank">N{{ object.pk|stringformat:"05d" }}</a></td>
|
||||
<td>{{ object.end_date }}</td>
|
||||
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
|
||||
<span class="text-muted">{{ object.get_status_display }}</span></td>
|
||||
<td>{{ object.start_date }}</td>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>
|
||||
{% if object.organisation %}
|
||||
{{ object.organisation.name }}
|
||||
<br>
|
||||
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
|
||||
{% else %}
|
||||
{{ object.person.name }}
|
||||
<br>
|
||||
<span class="text-muted">External</span>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>{{ object.sum_total|floatformat:2 }}</td>
|
||||
<td class="text-center">
|
||||
{{ object.mic.initials }}<br/>
|
||||
{% if object.mic %}
|
||||
{{ object.mic.initials }}<br>
|
||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_event' object.pk %}" target="_blank" class="btn btn-default">
|
||||
<a href="{% url 'invoice_event' object.pk %}" class="btn btn-default" data-toggle="tooltip"
|
||||
title="'Invoice' this event - click this when paperwork has been sent to treasury">
|
||||
<span class="glyphicon glyphicon-gbp"></span>
|
||||
</a>
|
||||
</td>
|
||||
@@ -61,6 +86,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
<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="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"/>
|
||||
@@ -98,12 +99,17 @@
|
||||
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
|
||||
|
||||
|
||||
|
||||
<setFont name="OpenSans" size="10"/>
|
||||
{% 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>
|
||||
{% 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>
|
||||
<setFont name="OpenSans" size="7"/>
|
||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
|
||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{ current_user.name }}
|
||||
| {% now "d/m/Y H:i" %} | {{ object.current_version_id }}]
|
||||
</drawCenteredString>
|
||||
</pageGraphics>
|
||||
|
||||
<frame id="main" x1="50" y1="65" width="495" height="645"/>
|
||||
@@ -115,10 +121,16 @@
|
||||
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||
|
||||
<setFont name="OpenSans" size="10"/>
|
||||
{% 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>
|
||||
{% 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>
|
||||
<setFont name="OpenSans" size="7"/>
|
||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
|
||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{ current_user.name }}
|
||||
| {% now "d/m/Y H:i" %} | {{ object.current_version_id }}]
|
||||
</drawCenteredString>
|
||||
</pageGraphics>
|
||||
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
||||
</pageTemplate>
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
<td>
|
||||
{% endif %}
|
||||
|
||||
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
|
||||
<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>
|
||||
@@ -28,19 +32,25 @@
|
||||
<spacer length="10"/>
|
||||
<blockTable style="eventDetails" colWidths="100,175">
|
||||
<tr>
|
||||
<td><para style="invoice_titles">Invoice Number</para></td>
|
||||
<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_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_titles">PO Number</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="invoice_numbers">{{ object.purchase_order|default_if_none:"" }}</para>
|
||||
</td>
|
||||
@@ -107,7 +117,9 @@
|
||||
<h2>Timings</h2>
|
||||
<blockTable style="eventDetails" colWidths="55,75">
|
||||
<tr>
|
||||
<td leftPadding="0" topPadding="0"><h3>Start</h3></td>
|
||||
<td leftPadding="0" topPadding="0">
|
||||
<h3>Start</h3>
|
||||
</td>
|
||||
<td>
|
||||
<para style="times">{{ object.start_time|time:"H:i" }}
|
||||
{{ object.start_date|date:"d/m/Y" }}
|
||||
@@ -115,7 +127,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td leftPadding="0"><h3>End</h3></td>
|
||||
<td leftPadding="0">
|
||||
<h3>End</h3>
|
||||
</td>
|
||||
<td>
|
||||
<para style="times">{{ object.end_time|default_if_none:""|time:"H:i" }}
|
||||
{{ object.end_date|date:"d/m/Y" }}
|
||||
@@ -124,7 +138,9 @@
|
||||
</tr>
|
||||
{% if object.access_at and not invoice %}
|
||||
<tr>
|
||||
<td leftPadding="0"><h3>Access</h3></td>
|
||||
<td leftPadding="0">
|
||||
<h3>Access</h3>
|
||||
</td>
|
||||
<td>
|
||||
<para style="times">{{ object.access_at|time:"H:i" }}
|
||||
{{ object.access_at|date:"d/m/Y" }}
|
||||
@@ -189,7 +205,7 @@
|
||||
<keepTogether>
|
||||
<blockTable style="totalTable" colWidths="300,115,80">
|
||||
<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>£ {{ object.sum_total|floatformat:2 }}</td>
|
||||
</tr>
|
||||
@@ -209,7 +225,7 @@
|
||||
|
||||
<para>
|
||||
{% if invoice %}
|
||||
VAT Registration Number: 116252989
|
||||
VAT Registration Number: 170734807
|
||||
{% else %}
|
||||
<b>This contract is not an invoice.</b>
|
||||
{% endif %}
|
||||
@@ -254,14 +270,17 @@
|
||||
{% 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
|
||||
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
|
||||
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>
|
||||
@@ -287,8 +306,10 @@
|
||||
{% 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
|
||||
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>
|
||||
@@ -306,7 +327,8 @@
|
||||
</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
|
||||
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>
|
||||
@@ -315,4 +337,6 @@
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
</keepTogether>
|
||||
{% endif %}
|
||||
<namedString id="lastPage"><pageNumber/></namedString>
|
||||
<namedString id="lastPage">
|
||||
<pageNumber/>
|
||||
</namedString>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td class="hidden-xs">#</td>
|
||||
<td>#</td>
|
||||
<td>Event Date</td>
|
||||
<td>Event Details</td>
|
||||
<td>Event Timings</td>
|
||||
@@ -23,7 +23,7 @@
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td class="hidden-xs">{{ event.pk }}</td>
|
||||
<td>{{ event.pk }}</td>
|
||||
<td>
|
||||
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
|
||||
{% if event.end_date and event.end_date != event.start_date %}
|
||||
@@ -69,7 +69,8 @@
|
||||
{{ event.start_date|date:"(Y-m-d)" }}<br/>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if event.has_end_time%}{% if event.start_date != event.end_date or event.start_time != event.end_time %}
|
||||
{% if event.has_end_time %}
|
||||
{% if event.start_date != event.end_date or event.start_time != event.end_time %}
|
||||
<dt>Event ends</dt>
|
||||
<dd>
|
||||
{{ event.end_time|date:"H:i" }}<br/>
|
||||
|
||||
@@ -3,10 +3,19 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
|
||||
<h1>R
|
||||
<small>ig</small>
|
||||
I
|
||||
<small>nformation</small>
|
||||
G
|
||||
<small>athering</small>
|
||||
S
|
||||
<small>ystem</small>
|
||||
</h1>
|
||||
</div>
|
||||
<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><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">
|
||||
@@ -17,15 +26,21 @@
|
||||
<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 %}
|
||||
<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>
|
||||
<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>
|
||||
@@ -40,7 +55,8 @@
|
||||
<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>
|
||||
<button type="submit" class="btn btn-default"><span
|
||||
class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
@@ -50,7 +66,8 @@
|
||||
<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>
|
||||
<button type="submit" class="btn btn-default"><span
|
||||
class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
@@ -60,7 +77,8 @@
|
||||
<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>
|
||||
<button type="submit" class="btn btn-default"><span
|
||||
class="glyphicon glyphicon-search"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
35
RIGS/templates/RIGS/invoice_confirm_delete.html
Normal file
35
RIGS/templates/RIGS/invoice_confirm_delete.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-offset-2 col-sm-8">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h2>Delete invoice {{ object.pk }}</h2>
|
||||
|
||||
<p>Are you sure you wish to delete invoice {{ object.pk }}?</p>
|
||||
|
||||
<p class="text-center"><strong>This action cannot be undone!</strong></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<form action="{{ action_link }}" method="post">{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{% url 'invoice_list' %}"/>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
<div class="col-sm-4 text-right">
|
||||
<div class="btn-group btn-page">
|
||||
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-default" title="Void Invoice">
|
||||
<span class="glyphicon glyphicon-remove"></span> <span
|
||||
class="hidden-xs">Delete</span>
|
||||
</a>
|
||||
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-default" title="Void Invoice">
|
||||
<span class="glyphicon glyphicon-ban-circle"></span> <span
|
||||
class="hidden-xs">Void</span>
|
||||
@@ -38,8 +42,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-{% if object.void %}danger{% else %}info{% endif %}">
|
||||
<div class="panel-heading">Event Details</div>
|
||||
<div class="panel panel-{% if object.is_closed %}success{% else %}warning{% endif %}">
|
||||
<div class="panel-heading">Event Details<span class="pull-right">
|
||||
{% if object.void %}(VOID){% elif object.is_closed %}(PAID){% else %}(OUTSTANDING){% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Event Number</dt>
|
||||
@@ -109,6 +116,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td class="text-right"><strong>Balance:</strong></td>
|
||||
<td>{{ object.balance|floatformat:2 }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -5,17 +5,21 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>Invoices</h2>
|
||||
<h2>{% block heading %}Invoices{% endblock %}</h2>
|
||||
{% block description %}{% endblock %}
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-responsive table-hover">
|
||||
<div class="table-responsive col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Invoice #</th>
|
||||
<th>Event</th>
|
||||
<th>Client</th>
|
||||
<th>Event Date</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Balance</th>
|
||||
<th></th>
|
||||
@@ -23,9 +27,41 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
|
||||
<td>{{ object.pk }}</td>
|
||||
<td><a href="{% url 'event_detail' object.event.pk %}" target="_blank">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }}</td>
|
||||
<tr>
|
||||
<td class="{% if object.is_closed %}success{% else %}warning{% endif %}">{{ object.pk }}<br>
|
||||
<span class="text-muted">{% if object.void %}(VOID){% elif object.is_closed %}
|
||||
(PAID){% else %}(O/S){% endif %}</span></td>
|
||||
<td class="
|
||||
{% if object.event.cancelled %}
|
||||
active text-muted
|
||||
{% elif not object.event.is_rig %}
|
||||
info
|
||||
{% elif object.event.confirmed and object.event.mic %}
|
||||
{# interpreated as (booked and mic) #}
|
||||
success
|
||||
{% elif object.event.mic %}
|
||||
warning
|
||||
{% else %}
|
||||
danger
|
||||
{% endif %}
|
||||
"><a
|
||||
href="{% url 'event_detail' object.event.pk %}">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }}
|
||||
<br>
|
||||
<span class="text-muted">{{ object.event.get_status_display }}{% if not object.event.mic %},
|
||||
No MIC{% endif %}
|
||||
</span></td>
|
||||
</td>
|
||||
<td>{% if object.event.organisation %}
|
||||
{{ object.event.organisation.name }}
|
||||
<br>
|
||||
<span class="text-muted">{{ object.event.organisation.union_account|yesno:'Internal,External' }}</span>
|
||||
{% else %}
|
||||
{{ object.event.person.name }}
|
||||
<br>
|
||||
<span class="text-muted">External</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ object.event.start_date }}</td>
|
||||
<td>{{ object.invoice_date }}</td>
|
||||
<td>{{ object.balance|floatformat:2 }}</td>
|
||||
<td class="text-right">
|
||||
@@ -37,6 +73,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
|
||||
13
RIGS/templates/RIGS/invoice_list_active.html
Normal file
13
RIGS/templates/RIGS/invoice_list_active.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends 'RIGS/invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Outstanding Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
Outstanding Invoices ({{ count }} Events, £ {{ total|floatformat:2 }})
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger</p>
|
||||
{% endblock %}
|
||||
13
RIGS/templates/RIGS/invoice_list_archive.html
Normal file
13
RIGS/templates/RIGS/invoice_list_archive.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends 'RIGS/invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Invoice Archive
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
All Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>This page displays all invoices: outstanding, paid, and void</p>
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<tr id="item-{{ item.pk }}" data-pk="{{ item.pk }}" class="item_row">
|
||||
<td>
|
||||
<span class="name">{{ item.name }}</span>
|
||||
@@ -8,7 +7,8 @@
|
||||
</td>
|
||||
<td>£ <span class="cost">{{ item.cost|floatformat:2 }}</span></td>
|
||||
<td class="quantity">{{ item.quantity }}</td>
|
||||
<td>£ <span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td>
|
||||
<td>£ <span class="sub-total" data-subtotal="{{ item.total_cost }}">{{ item.total_cost|floatformat:2 }}</span>
|
||||
</td>
|
||||
{% if edit %}
|
||||
<td class="vert-align text-right">
|
||||
<button type="button" class="item-edit btn btn-xs btn-default"
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
<tr>
|
||||
{% if not object.pk %}
|
||||
<td id="vat-rate" data-rate="{{ currentVAT.rate }}">VAT @
|
||||
{{currentVAT.as_percent|floatformat}}% (TBC)</td>
|
||||
{{ currentVAT.as_percent|floatformat }}% (TBC)
|
||||
</td>
|
||||
{% else %}
|
||||
<td id="vat-rate" data-rate="{{ object.vat_rate.rate }}">VAT @
|
||||
{{object.vat_rate.as_percent|floatformat|default:"TBD"}}%</td>
|
||||
{{ object.vat_rate.as_percent|floatformat|default:"TBD" }}%
|
||||
</td>
|
||||
{% endif %}
|
||||
<td colspan="2">£ <span id="vat">{{ object.vat|default:0|floatformat:2 }}</span></td>
|
||||
</tr>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{% 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>
|
||||
<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>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
|
||||
|
||||
<dt>Email</dt>
|
||||
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
<dd><a href="mailto:{{ object.email }}"><span
|
||||
class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
|
||||
<dt>Address</dt>
|
||||
<dd>{{ object.address|linebreaksbr }}</dd>
|
||||
@@ -50,7 +51,10 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<td>{{ object.pk }}</td>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>{{ object.email }}</td>
|
||||
<td>{{ object.phone }}</td>
|
||||
<td><a href="tel:{{ object.phone }}">{{ object.phone }}</a></td>
|
||||
<td>{{ object.notes|yesno|capfirst }}</td>
|
||||
<td>{{ object.union_account|yesno|capfirst }}</td>
|
||||
<td>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
|
||||
|
||||
<dt>Email</dt>
|
||||
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
<dd><a href="mailto:{{ object.email }}"><span
|
||||
class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
|
||||
<dt>Address</dt>
|
||||
<dd>{{ object.address|linebreaksbr }}</dd>
|
||||
@@ -47,7 +48,10 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<td>{{ person.pk }}</td>
|
||||
<td>{{ person.name }}</td>
|
||||
<td>{{ person.email }}</td>
|
||||
<td>{{ person.phone }}</td>
|
||||
<td><a href="tel:{{ person.phone }}">{{ person.phone }}</a></td>
|
||||
<td>{{ person.notes|yesno|capfirst }}</td>
|
||||
<td>
|
||||
<a href="{% url 'person_detail' person.pk %}" class="btn btn-default modal-href">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{# 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='
|
||||
<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>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<dd>{{ object.initials }}</dd>
|
||||
|
||||
<dt>Phone</dt>
|
||||
<dd>{{object.phone}}</dd>
|
||||
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
|
||||
</dl>
|
||||
{% if not request.is_ajax %}
|
||||
{% if object.pk == user.pk %}
|
||||
@@ -104,19 +104,23 @@
|
||||
<input type="checkbox" value="rig" data-default="true" checked> Rigs
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" value="non-rig" data-default="true" checked> Non-Rigs
|
||||
<input type="checkbox" value="non-rig" data-default="true" checked>
|
||||
Non-Rigs
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" value="dry-hire" data-default="true" checked> Dry-Hires
|
||||
<input type="checkbox" value="dry-hire" data-default="true" checked>
|
||||
Dry-Hires
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" value="cancelled" data-default="false"> Cancelled
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" value="provisional" data-default="true" checked> Provisional
|
||||
<input type="checkbox" value="provisional" data-default="true" checked>
|
||||
Provisional
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" value="confirmed" data-default="true" checked> Confirmed/Booked
|
||||
<input type="checkbox" value="confirmed" data-default="true" checked>
|
||||
Confirmed/Booked
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -125,9 +129,19 @@
|
||||
<dt>Calendar URL</dt>
|
||||
<dd>
|
||||
{% if user.api_key %}
|
||||
<pre id="cal-url" data-url="http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}"></pre>
|
||||
<small><a id="gcal-link" data-url="http://www.google.com/calendar/render?cid=http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}" href="">Click here</a> to add to google calendar.<br/>
|
||||
To sync from google calendar to mobile device, visit <a href="https://www.google.com/calendar/syncselect" target="_blank">this page</a> on your device and tick "RIGS Calendar".</small>
|
||||
<pre id="cal-url" data-url="http{{ request.is_secure|yesno:"s," }}://
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}"></pre>
|
||||
<small><a id="gcal-link" data-url="https://support.google.com/calendar/answer/37100"
|
||||
href="">Click here</a> for instructions on adding to google calendar.<br/>
|
||||
To sync from google calendar to mobile device, visit <a
|
||||
href="https://www.google.com/calendar/syncselect" target="_blank">this
|
||||
page</a> on your device and tick "RIGS Calendar".
|
||||
</small>
|
||||
{% else %}
|
||||
<pre>No API Key Generated</pre>
|
||||
{% endif %}
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
{% include 'form_errors.html' %}
|
||||
<h3>Update Profile</h3>
|
||||
<div class="col-sm-6">
|
||||
<form action="{{form.action|default:request.path}}" method="post" class="form-horizontal">{% csrf_token %}
|
||||
<form action="{{ form.action|default:request.path }}" method="post"
|
||||
class="form-horizontal">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="{{form.first_name.id_for_label}}" class="col-sm-4 control-label">{{form.first_name.label}}</label>
|
||||
<label for="{{ form.first_name.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.first_name.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.first_name class+="form-control" placeholder=form.first_name.label %}
|
||||
@@ -19,7 +21,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.last_name.id_for_label}}" class="col-sm-4 control-label">{{form.last_name.label}}</label>
|
||||
<label for="{{ form.last_name.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.last_name.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.last_name class+="form-control" placeholder=form.last_name.label %}
|
||||
@@ -27,7 +30,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.email.id_for_label}}" class="col-sm-4 control-label">{{form.email.label}}</label>
|
||||
<label for="{{ form.email.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.email.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.email type="email" class+="form-control" placeholder=form.email.label %}
|
||||
@@ -35,7 +39,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.initials.id_for_label}}" class="col-sm-4 control-label">{{form.initials.label}}</label>
|
||||
<label for="{{ form.initials.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.initials.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.initials class+="form-control" placeholder=form.initials.label %}
|
||||
@@ -43,7 +48,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.phone.id_for_label}}" class="col-sm-4 control-label">{{form.phone.label}}</label>
|
||||
<label for="{{ form.phone.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.phone.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.phone type="tel" class+="form-control" placeholder=form.phone.label %}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
|
||||
|
||||
<dt>Email</dt>
|
||||
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
<dd><a href="mailto:{{ object.email }}"><span
|
||||
class="overflow-ellipsis">{{ object.email }}</span></a></dd>
|
||||
|
||||
<dt>Address</dt>
|
||||
<dd>{{ object.address|linebreaksbr }}</dd>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<td>{{ object.pk }}</td>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>{{ object.email }}</td>
|
||||
<td>{{ object.phone }}</td>
|
||||
<td><a href="tel:{{ object.phone }}">{{ object.phone }}</a></td>
|
||||
<td>{{ object.notes|yesno|capfirst }}</td>
|
||||
<td>
|
||||
<a href="{% url 'venue_detail' object.pk %}" class="btn btn-default modal-href">
|
||||
|
||||
@@ -1,40 +1,28 @@
|
||||
{% 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 {% 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>
|
||||
<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" %}
|
||||
{% endspaceless %}'>{{ 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='
|
||||
<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 %}
|
||||
<h4>{{ change.field.verbose_name }}</h4>
|
||||
|
||||
{% if change.new %}<div class="alert alert-success">{{change.new|linebreaksbr}}</div>{% endif %}
|
||||
{% if change.old %}<div class="alert alert-danger">{{change.old|linebreaksbr}}</div>{% endif %}
|
||||
|
||||
<li class="list-group-item">
|
||||
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4>
|
||||
{% include "RIGS/version_changes_change.html" %}
|
||||
</li>
|
||||
{% 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 %}
|
||||
34
RIGS/templates/RIGS/version_changes_change.html
Normal file
34
RIGS/templates/RIGS/version_changes_change.html
Normal 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 %}
|
||||
@@ -23,7 +23,9 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -35,6 +37,7 @@
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
<td>Comment</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -46,11 +49,14 @@
|
||||
<td>{{ version.revision.user.name }}</td>
|
||||
<td>
|
||||
{% if version.old == None %}
|
||||
Object Created
|
||||
{{ object|to_class_name }} Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ version.revision.comment }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
from django import template
|
||||
from django import forms
|
||||
from django import template
|
||||
from django.forms.forms import NON_FIELD_ERRORS
|
||||
from django.forms.utils import ErrorDict
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
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()
|
||||
@@ -25,6 +28,7 @@ def nice_errors(form, non_field_msg='General form errors'):
|
||||
nice_errors[key] = errors
|
||||
return nice_errors
|
||||
|
||||
|
||||
def paginator(context, adjacent_pages=3):
|
||||
"""
|
||||
To be used in conjunction with the object_list generic view.
|
||||
@@ -64,20 +68,22 @@ def paginator(context, adjacent_pages=3):
|
||||
dict['previous'] = page.previous_page_number()
|
||||
|
||||
return dict
|
||||
|
||||
|
||||
register.inclusion_tag('pagination.html', takes_context=True)(paginator)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def url_replace(request, field, value):
|
||||
|
||||
dict_ = request.GET.copy()
|
||||
|
||||
dict_[field] = value
|
||||
|
||||
return dict_.urlencode()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def orderby(request, field, attr):
|
||||
|
||||
dict_ = request.GET.copy()
|
||||
|
||||
if dict_.__contains__(field) and dict_[field] == attr:
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
from datetime import date, timedelta
|
||||
|
||||
import reversion
|
||||
from django.core import mail
|
||||
from django.db import transaction
|
||||
from django.test import LiveServerTestCase
|
||||
from django.test.client import Client
|
||||
from django.core import mail
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from RIGS import models
|
||||
import re
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
from django.db import transaction
|
||||
import reversion
|
||||
import json
|
||||
|
||||
|
||||
class UserRegistrationTest(LiveServerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.browser = webdriver.Firefox()
|
||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||
@@ -103,7 +103,7 @@ class UserRegistrationTest(LiveServerTestCase):
|
||||
# Check Email
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
email = mail.outbox[0]
|
||||
self.assertIn('activation required', email.subject)
|
||||
self.assertIn('John Smith "JS" activation required', email.subject)
|
||||
urls = re.findall(
|
||||
'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', email.body)
|
||||
self.assertEqual(len(urls), 1)
|
||||
@@ -148,17 +148,18 @@ class UserRegistrationTest(LiveServerTestCase):
|
||||
|
||||
|
||||
class EventTest(LiveServerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.profile = models.Profile(
|
||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||
self.profile.set_password("EventTestPassword")
|
||||
self.profile.save()
|
||||
|
||||
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.browser = webdriver.Firefox()
|
||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||
self.browser.maximize_window()
|
||||
os.environ['RECAPTCHA_TESTING'] = 'True'
|
||||
|
||||
def tearDown(self):
|
||||
@@ -195,12 +196,14 @@ 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
|
||||
self.authenticate('/event/create/')
|
||||
|
||||
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
|
||||
# setup WebDriverWait to use later (to wait for animations)
|
||||
wait = WebDriverWait(self.browser, 10)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
@@ -217,6 +220,7 @@ class EventTest(LiveServerTestCase):
|
||||
form = self.browser.find_element_by_tag_name('form')
|
||||
|
||||
# Create new person
|
||||
wait.until(animation_is_finished())
|
||||
add_person_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_person" and contains(@href, "add")]')
|
||||
add_person_button.click()
|
||||
@@ -225,7 +229,9 @@ class EventTest(LiveServerTestCase):
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
self.assertIn(
|
||||
"Add Person",
|
||||
modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
# Fill person form out and submit
|
||||
modal.find_element_by_xpath(
|
||||
@@ -245,11 +251,14 @@ class EventTest(LiveServerTestCase):
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Change mind and add another
|
||||
wait.until(animation_is_finished())
|
||||
add_person_button.click()
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
self.assertIn(
|
||||
"Add Person",
|
||||
modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Person 2")
|
||||
@@ -285,7 +294,9 @@ class EventTest(LiveServerTestCase):
|
||||
'//a[@data-target="#id_person" and contains(@href, "%s/edit/")]' % person1.pk).click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Edit Person", modal.find_element_by_tag_name('h3').text)
|
||||
self.assertIn(
|
||||
"Edit Person",
|
||||
modal.find_element_by_tag_name('h3').text)
|
||||
name = modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]')
|
||||
self.assertEqual(person1.name, name.get_attribute('value'))
|
||||
@@ -301,13 +312,15 @@ class EventTest(LiveServerTestCase):
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
|
||||
# Create organisation
|
||||
wait.until(animation_is_finished())
|
||||
add_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_organisation" and contains(@href, "add")]')
|
||||
add_button.click()
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Organisation", modal.find_element_by_tag_name('h3').text)
|
||||
self.assertIn("Add Organisation",
|
||||
modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Organisation")
|
||||
modal.find_element_by_xpath(
|
||||
@@ -324,14 +337,19 @@ class EventTest(LiveServerTestCase):
|
||||
'//select[@id="id_organisation"]//option[@selected="selected"]')
|
||||
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(
|
||||
'//a[@data-target="#id_venue" and contains(@href, "add")]')
|
||||
wait.until(animation_is_finished())
|
||||
add_button.click()
|
||||
wait.until(animation_is_finished())
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Venue", modal.find_element_by_tag_name('h3').text)
|
||||
self.assertIn(
|
||||
"Add Venue",
|
||||
modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Venue")
|
||||
modal.find_element_by_xpath(
|
||||
@@ -357,11 +375,13 @@ class EventTest(LiveServerTestCase):
|
||||
form.find_element_by_id('id_end_time').send_keys('07:00')
|
||||
|
||||
# Add item
|
||||
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
||||
form.find_element_by_xpath(
|
||||
'//button[contains(@class, "item-add")]').click()
|
||||
wait.until(animation_is_finished())
|
||||
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")
|
||||
modal.find_element_by_id("item_description").send_keys(
|
||||
"This is an item description\nthat for reasons unkown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
@@ -373,35 +393,54 @@ class EventTest(LiveServerTestCase):
|
||||
# Confirm item has been saved to json field
|
||||
objectitems = self.browser.execute_script("return objectitems;")
|
||||
self.assertEqual(1, len(objectitems))
|
||||
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
|
||||
# as we are deliberately creating this we know the ID
|
||||
testitem = objectitems["-1"]['fields']
|
||||
self.assertEqual("Test Item 1", testitem['name'])
|
||||
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
||||
# test a couple of "worse case" fields
|
||||
self.assertEqual("2", testitem['quantity'])
|
||||
|
||||
# See new item appear in table
|
||||
row = self.browser.find_element_by_id('item--1') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text)
|
||||
self.assertIn("This is an item description", row.find_element_by_xpath('//div[@class="item-description"]').text)
|
||||
self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text)
|
||||
self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text)
|
||||
self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text)
|
||||
row = self.browser.find_element_by_id(
|
||||
'item--1') # ID number is known, see above
|
||||
self.assertIn(
|
||||
"Test Item 1",
|
||||
row.find_element_by_xpath('//span[@class="name"]').text)
|
||||
self.assertIn(
|
||||
"This is an item description",
|
||||
row.find_element_by_xpath('//div[@class="item-description"]').text)
|
||||
self.assertEqual(u'£ 23.95', row.find_element_by_xpath(
|
||||
'//tr[@id="item--1"]/td[2]').text)
|
||||
self.assertEqual("2", row.find_element_by_xpath(
|
||||
'//td[@class="quantity"]').text)
|
||||
self.assertEqual(u'£ 47.90', row.find_element_by_xpath(
|
||||
'//tr[@id="item--1"]/td[4]').text)
|
||||
|
||||
# Check totals
|
||||
self.assertEqual("47.90", self.browser.find_element_by_id('sumtotal').text)
|
||||
self.assertIn("(TBC)", self.browser.find_element_by_id('vat-rate').text)
|
||||
self.assertEqual("9.58", self.browser.find_element_by_id('vat').text)
|
||||
self.assertEqual("57.48", self.browser.find_element_by_id('total').text)
|
||||
self.assertEqual(
|
||||
"47.90", self.browser.find_element_by_id('sumtotal').text)
|
||||
self.assertIn(
|
||||
"(TBC)",
|
||||
self.browser.find_element_by_id('vat-rate').text)
|
||||
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.click()
|
||||
|
||||
# See error
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
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)
|
||||
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()
|
||||
error.find_element_by_xpath(
|
||||
'//div[contains(@class, "alert-danger")]//button[@class="close"]').click()
|
||||
try:
|
||||
self.assertFalse(error.is_displayed())
|
||||
except StaleElementReferenceException:
|
||||
@@ -420,11 +459,22 @@ class EventTest(LiveServerTestCase):
|
||||
e.send_keys(Keys.ENTER)
|
||||
|
||||
# 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("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):
|
||||
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")
|
||||
|
||||
item1 = models.EventItem(
|
||||
event=testEvent,
|
||||
@@ -442,27 +492,31 @@ class EventTest(LiveServerTestCase):
|
||||
order=2,
|
||||
).save()
|
||||
|
||||
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk) + '/duplicate/')
|
||||
self.browser.get(self.live_server_url + '/event/' +
|
||||
str(testEvent.pk) + '/duplicate/')
|
||||
self.authenticate('/event/' + str(testEvent.pk) + '/duplicate/')
|
||||
|
||||
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
|
||||
# setup WebDriverWait to use later (to wait for animations)
|
||||
wait = WebDriverWait(self.browser, 10)
|
||||
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
form = self.browser.find_element_by_tag_name('form')
|
||||
|
||||
|
||||
# Check the items are visible
|
||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||
table = self.browser.find_element_by_id(
|
||||
'item-table') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", table.text)
|
||||
self.assertIn("Test Item 2", table.text)
|
||||
|
||||
# Add item
|
||||
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
||||
form.find_element_by_xpath(
|
||||
'//button[contains(@class, "item-add")]').click()
|
||||
wait.until(animation_is_finished())
|
||||
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")
|
||||
modal.find_element_by_id("item_description").send_keys(
|
||||
"This is an item description\nthat for reasons unkown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
@@ -474,27 +528,35 @@ class EventTest(LiveServerTestCase):
|
||||
# Attempt to save
|
||||
save.click()
|
||||
|
||||
self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text)
|
||||
self.assertNotIn(
|
||||
"N0000%d" %
|
||||
testEvent.pk,
|
||||
self.browser.find_element_by_xpath('//h1').text)
|
||||
|
||||
# Check the new items are visible
|
||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||
table = self.browser.find_element_by_id(
|
||||
'item-table') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", table.text)
|
||||
self.assertIn("Test Item 2", table.text)
|
||||
self.assertIn("Test Item 3", table.text)
|
||||
|
||||
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||
self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel = self.browser.find_element_by_xpath(
|
||||
'//div[contains(text(), "Event Info")]/..')
|
||||
self.assertIn("N0000%d" % testEvent.pk, infoPanel.find_element_by_xpath(
|
||||
'//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
|
||||
|
||||
|
||||
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) #Go back to the old event
|
||||
self.browser.get(self.live_server_url + '/event/' +
|
||||
str(testEvent.pk)) # Go back to the old event
|
||||
|
||||
# Check that based-on hasn't crept into the old event
|
||||
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||
self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel = self.browser.find_element_by_xpath(
|
||||
'//div[contains(text(), "Event Info")]/..')
|
||||
self.assertNotIn("N0000%d" % testEvent.pk, infoPanel.find_element_by_xpath(
|
||||
'//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
|
||||
# Check the items are as they were
|
||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||
table = self.browser.find_element_by_id(
|
||||
'item-table') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", table.text)
|
||||
self.assertIn("Test Item 2", table.text)
|
||||
self.assertNotIn("Test Item 3", table.text)
|
||||
@@ -504,7 +566,8 @@ class EventTest(LiveServerTestCase):
|
||||
# Gets redirected to login and back
|
||||
self.authenticate('/event/create/')
|
||||
|
||||
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
|
||||
# setup WebDriverWait to use later (to wait for animations)
|
||||
wait = WebDriverWait(self.browser, 10)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
@@ -512,7 +575,8 @@ class EventTest(LiveServerTestCase):
|
||||
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]')
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
@@ -527,14 +591,16 @@ class EventTest(LiveServerTestCase):
|
||||
|
||||
# Attempt to save - should fail
|
||||
save.click()
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
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)
|
||||
|
||||
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]')
|
||||
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')
|
||||
|
||||
@@ -549,14 +615,16 @@ class EventTest(LiveServerTestCase):
|
||||
|
||||
# Attempt to save - should fail
|
||||
save.click()
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
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)
|
||||
|
||||
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]')
|
||||
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')
|
||||
|
||||
@@ -569,10 +637,10 @@ class EventTest(LiveServerTestCase):
|
||||
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]')
|
||||
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')
|
||||
|
||||
@@ -586,14 +654,16 @@ class EventTest(LiveServerTestCase):
|
||||
|
||||
# Attempt to save - should fail
|
||||
save.click()
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
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)
|
||||
|
||||
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]')
|
||||
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')
|
||||
|
||||
@@ -608,16 +678,19 @@ class EventTest(LiveServerTestCase):
|
||||
save.click()
|
||||
|
||||
# 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("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):
|
||||
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)
|
||||
# setup WebDriverWait to use later (to wait for animations)
|
||||
wait = WebDriverWait(self.browser, 10)
|
||||
# Set session-long wait (only works for non-existant DOM objects)
|
||||
self.browser.implicitly_wait(3)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
@@ -628,7 +701,8 @@ class EventTest(LiveServerTestCase):
|
||||
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]')
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
@@ -640,16 +714,23 @@ class EventTest(LiveServerTestCase):
|
||||
|
||||
# Save the rig
|
||||
save.click()
|
||||
detail_panel = self.browser.find_element_by_xpath("//div[@id='content']/div/div[6]/div/div")
|
||||
detail_panel = self.browser.find_element_by_xpath(
|
||||
"//div[@id='content']/div/div[6]/div/div")
|
||||
self.assertTrue(detail_panel.is_displayed())
|
||||
self.assertIn("Event Detail", detail_panel.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")
|
||||
person = models.Person(
|
||||
name="Event Detail Person",
|
||||
email="eventdetail@person.tests.rigs",
|
||||
phone="123 123")
|
||||
person.save()
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
organisation = models.Organisation(name="Event Detail Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
||||
organisation = models.Organisation(
|
||||
name="Event Detail Organisation",
|
||||
email="eventdetail@organisation.tests.rigs",
|
||||
phone="123 456").save()
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
venue = models.Venue(name="Event Detail Venue").save()
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
@@ -679,56 +760,179 @@ class EventTest(LiveServerTestCase):
|
||||
order=2,
|
||||
).save()
|
||||
|
||||
|
||||
self.browser.get(self.live_server_url + '/event/%d' % event.pk)
|
||||
self.authenticate('/event/%d/' % event.pk)
|
||||
self.assertIn("N%05d | %s"%(event.pk, event.name), self.browser.find_element_by_xpath('//h1').text)
|
||||
self.assertIn("N%05d | %s" % (event.pk, event.name),
|
||||
self.browser.find_element_by_xpath('//h1').text)
|
||||
|
||||
personPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
||||
self.assertEqual(person.name, personPanel.find_element_by_xpath('//dt[text()="Person"]/following-sibling::dd[1]').text)
|
||||
self.assertEqual(person.email, personPanel.find_element_by_xpath('//dt[text()="Email"]/following-sibling::dd[1]').text)
|
||||
self.assertEqual(person.phone, personPanel.find_element_by_xpath('//dt[text()="Phone Number"]/following-sibling::dd[1]').text)
|
||||
personPanel = self.browser.find_element_by_xpath(
|
||||
'//div[contains(text(), "Contact Details")]/..')
|
||||
self.assertEqual(person.name, personPanel.find_element_by_xpath(
|
||||
'//dt[text()="Person"]/following-sibling::dd[1]').text)
|
||||
self.assertEqual(person.email, personPanel.find_element_by_xpath(
|
||||
'//dt[text()="Email"]/following-sibling::dd[1]').text)
|
||||
self.assertEqual(person.phone, personPanel.find_element_by_xpath(
|
||||
'//dt[text()="Phone Number"]/following-sibling::dd[1]').text)
|
||||
|
||||
organisationPanel = self.browser.find_element_by_xpath(
|
||||
'//div[contains(text(), "Contact Details")]/..')
|
||||
|
||||
organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
||||
|
||||
class IcalTest(LiveServerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.all_events = set(range(1, 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(
|
||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||
self.profile.set_password("EventTestPassword")
|
||||
self.profile.save()
|
||||
|
||||
# produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked
|
||||
models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end")
|
||||
models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), description="start today no end")
|
||||
models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), end_date=date.today(), description="start today with end today")
|
||||
models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, start_date=date.today()-timedelta(weeks=104), description="start past 2 years no end")
|
||||
models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date=date.today()-timedelta(days=1), description="start past 1 week with end past")
|
||||
models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, 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", status=models.Event.BOOKED, start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future")
|
||||
# produce 7 normal events - 5 current - 1 last week - 1 two years ago -
|
||||
# 2 provisional - 2 confirmed - 3 booked
|
||||
models.Event.objects.create(
|
||||
name="TE E1",
|
||||
status=models.Event.PROVISIONAL,
|
||||
start_date=date.today() +
|
||||
timedelta(
|
||||
days=6),
|
||||
description="start future no end")
|
||||
models.Event.objects.create(
|
||||
name="TE E2",
|
||||
status=models.Event.PROVISIONAL,
|
||||
start_date=date.today(),
|
||||
description="start today no end")
|
||||
models.Event.objects.create(
|
||||
name="TE E3",
|
||||
status=models.Event.CONFIRMED,
|
||||
start_date=date.today(),
|
||||
end_date=date.today(),
|
||||
description="start today with end today")
|
||||
models.Event.objects.create(
|
||||
name="TE E4",
|
||||
status=models.Event.CONFIRMED,
|
||||
start_date=date.today() -
|
||||
timedelta(
|
||||
weeks=104),
|
||||
description="start past 2 years no end")
|
||||
models.Event.objects.create(
|
||||
name="TE E5",
|
||||
status=models.Event.BOOKED,
|
||||
start_date=date.today() -
|
||||
timedelta(
|
||||
days=7),
|
||||
end_date=date.today() -
|
||||
timedelta(
|
||||
days=1),
|
||||
description="start past 1 week with end past")
|
||||
models.Event.objects.create(
|
||||
name="TE E6",
|
||||
status=models.Event.BOOKED,
|
||||
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",
|
||||
status=models.Event.BOOKED,
|
||||
start_date=date.today() +
|
||||
timedelta(
|
||||
days=2),
|
||||
end_date=date.today() +
|
||||
timedelta(
|
||||
days=2),
|
||||
description="start + end in future")
|
||||
|
||||
# 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 E9", start_date=date.today()-timedelta(days=1), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled and started")
|
||||
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 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 - 1 cancelled
|
||||
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 E12", start_date=date.today()-timedelta(days=1), dry_hire=True, 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")
|
||||
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 E12",
|
||||
start_date=date.today() -
|
||||
timedelta(
|
||||
days=1),
|
||||
dry_hire=True,
|
||||
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
|
||||
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 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")
|
||||
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 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")
|
||||
|
||||
self.browser = webdriver.Firefox()
|
||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||
@@ -762,24 +966,34 @@ class IcalTest(LiveServerTestCase):
|
||||
|
||||
# Completes and comes back to /user/
|
||||
# Checks that no api key is displayed
|
||||
self.assertEqual("No API Key Generated", self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text)
|
||||
self.assertEqual("No API Key Generated", self.browser.find_element_by_css_selector("pre").text)
|
||||
self.assertEqual("No API Key Generated", self.browser.find_element_by_xpath(
|
||||
"//div[@id='content']/div/div/div[3]/dl[2]/dd").text)
|
||||
self.assertEqual("No API Key Generated",
|
||||
self.browser.find_element_by_css_selector("pre").text)
|
||||
|
||||
# Now creates an API key, and check a URL is displayed one
|
||||
self.browser.find_element_by_link_text("Generate API Key").click()
|
||||
self.assertIn("rigs.ics", self.browser.find_element_by_id("cal-url").text)
|
||||
self.assertIn(
|
||||
"rigs.ics",
|
||||
self.browser.find_element_by_id("cal-url").text)
|
||||
self.assertNotIn("?", self.browser.find_element_by_id("cal-url").text)
|
||||
|
||||
# Lets change everything so it's not the default value
|
||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='dry-hire']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='cancelled']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='provisional']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='confirmed']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='dry-hire']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='cancelled']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='provisional']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='confirmed']").click()
|
||||
|
||||
# and then check the url is correct
|
||||
self.assertIn("rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false", self.browser.find_element_by_id("cal-url").text)
|
||||
self.assertIn(
|
||||
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false",
|
||||
self.browser.find_element_by_id("cal-url").text)
|
||||
|
||||
# Awesome - all seems to work
|
||||
|
||||
@@ -792,8 +1006,6 @@ class IcalTest(LiveServerTestCase):
|
||||
# Now creates an API key, and check a URL is displayed one
|
||||
self.browser.find_element_by_link_text("Generate API Key").click()
|
||||
|
||||
|
||||
|
||||
c = Client()
|
||||
|
||||
# Default settings - should have all non-cancelled events
|
||||
@@ -813,7 +1025,6 @@ class IcalTest(LiveServerTestCase):
|
||||
else:
|
||||
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||
|
||||
|
||||
# Only dry hires
|
||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
||||
@@ -829,11 +1040,12 @@ class IcalTest(LiveServerTestCase):
|
||||
else:
|
||||
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||
|
||||
|
||||
# Only provisional rigs
|
||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='dry-hire']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='confirmed']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='dry-hire']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='confirmed']").click()
|
||||
|
||||
icalUrl = self.browser.find_element_by_id("cal-url").text
|
||||
response = c.get(icalUrl)
|
||||
@@ -849,8 +1061,10 @@ class IcalTest(LiveServerTestCase):
|
||||
# Only cancelled non-rigs
|
||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='provisional']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='cancelled']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='provisional']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='cancelled']").click()
|
||||
|
||||
icalUrl = self.browser.find_element_by_id("cal-url").text
|
||||
response = c.get(icalUrl)
|
||||
@@ -865,7 +1079,8 @@ class IcalTest(LiveServerTestCase):
|
||||
|
||||
# Nothing selected
|
||||
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
||||
self.browser.find_element_by_xpath("//input[@value='cancelled']").click()
|
||||
self.browser.find_element_by_xpath(
|
||||
"//input[@value='cancelled']").click()
|
||||
|
||||
icalUrl = self.browser.find_element_by_id("cal-url").text
|
||||
response = c.get(icalUrl)
|
||||
@@ -880,8 +1095,10 @@ class IcalTest(LiveServerTestCase):
|
||||
|
||||
# Wow - that was a lot of tests
|
||||
|
||||
|
||||
class animation_is_finished(object):
|
||||
""" Checks if animation is done """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from django.test import TestCase
|
||||
from RIGS import models
|
||||
from datetime import date, timedelta
|
||||
from datetime import date, timedelta, datetime, time
|
||||
from decimal import *
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class ProfileTestCase(TestCase):
|
||||
def test_str(self):
|
||||
@@ -14,8 +18,10 @@ class ProfileTestCase(TestCase):
|
||||
|
||||
class VatRateTestCase(TestCase):
|
||||
def setUp(self):
|
||||
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='2014-03-01', rate=0.20, comment='test1')
|
||||
models.VatRate.objects.create(
|
||||
start_at='2016-03-01', rate=0.15, comment='test2')
|
||||
|
||||
def test_find_correct(self):
|
||||
r = models.VatRate.objects.find_rate('2015-03-01')
|
||||
@@ -32,18 +38,27 @@ class EventTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.all_events = set(range(1, 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.profile = models.Profile.objects.create(username="testuser1", email="1@test.com")
|
||||
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")
|
||||
|
||||
# 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 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 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 E6", start_date=date.today() - timedelta(days=2),
|
||||
@@ -60,7 +75,11 @@ class EventTestCase(TestCase):
|
||||
description="cancelled and started")
|
||||
|
||||
# 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 E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
|
||||
@@ -71,7 +90,11 @@ class EventTestCase(TestCase):
|
||||
status=models.Event.CANCELLED, description="dryhire today cancelled")
|
||||
|
||||
# 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 E17", start_date=date.today() - timedelta(days=1), is_rig=False,
|
||||
@@ -81,7 +104,8 @@ class EventTestCase(TestCase):
|
||||
|
||||
def test_count(self):
|
||||
# Santiy check we have the expected events created
|
||||
self.assertEqual(models.Event.objects.count(), 18, "Incorrect number of events, check setup")
|
||||
self.assertEqual(models.Event.objects.count(), 18,
|
||||
"Incorrect number of events, check setup")
|
||||
|
||||
def test_rig_count(self):
|
||||
# by my count this is 7
|
||||
@@ -91,10 +115,16 @@ class EventTestCase(TestCase):
|
||||
current_events = models.Event.objects.current_events()
|
||||
self.assertEqual(len(current_events), len(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:
|
||||
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):
|
||||
v1 = models.Venue.objects.create(name="TE V1")
|
||||
@@ -201,14 +231,120 @@ class EventTestCase(TestCase):
|
||||
event.status = models.Event.PROVISIONAL
|
||||
event.save()
|
||||
|
||||
def test_earliest_time(self):
|
||||
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
|
||||
|
||||
# Just a start date
|
||||
self.assertEqual(event.earliest_time, date(2016, 0o1, 0o1))
|
||||
|
||||
# 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, 0o3, 9, 57)
|
||||
self.assertEqual(event.earliest_time, event.access_at)
|
||||
|
||||
# With meet time
|
||||
event.meet_at = self.create_datetime(2015, 12, 0o3, 9, 55)
|
||||
self.assertEqual(event.earliest_time, event.meet_at)
|
||||
|
||||
# Check order isn't important
|
||||
event.start_date = date(2015, 12, 0o3)
|
||||
self.assertEqual(
|
||||
event.earliest_time,
|
||||
self.create_datetime(
|
||||
2015,
|
||||
12,
|
||||
0o3,
|
||||
9,
|
||||
00))
|
||||
|
||||
def test_latest_time(self):
|
||||
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
|
||||
|
||||
# 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):
|
||||
def setUp(self):
|
||||
self.e1 = models.Event.objects.create(name="TI E1", start_date=date.today())
|
||||
self.e2 = models.Event.objects.create(name="TI E2", start_date=date.today())
|
||||
self.e1 = models.Event.objects.create(
|
||||
name="TI E1", start_date=date.today())
|
||||
self.e2 = models.Event.objects.create(
|
||||
name="TI E2", start_date=date.today())
|
||||
|
||||
def test_item_cost(self):
|
||||
item = models.EventItem.objects.create(event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
|
||||
item = models.EventItem.objects.create(
|
||||
event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
|
||||
self.assertEqual(item.total_cost, 1.00)
|
||||
|
||||
item.cost = 2.50
|
||||
@@ -221,8 +357,10 @@ class EventItemTestCase(TestCase):
|
||||
item.delete()
|
||||
|
||||
def test_item_order(self):
|
||||
i1 = models.EventItem.objects.create(event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
|
||||
i2 = models.EventItem.objects.create(event=self.e1, name="TI I2", quantity=1, cost=1.00, order=2)
|
||||
i1 = models.EventItem.objects.create(
|
||||
event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
|
||||
i2 = models.EventItem.objects.create(
|
||||
event=self.e1, name="TI I2", quantity=1, cost=1.00, order=2)
|
||||
|
||||
items = self.e1.items.all()
|
||||
self.assertListEqual([i1, i2], list(items))
|
||||
@@ -230,17 +368,29 @@ class EventItemTestCase(TestCase):
|
||||
|
||||
class EventPricingTestCase(TestCase):
|
||||
def setUp(self):
|
||||
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))
|
||||
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())
|
||||
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))
|
||||
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())
|
||||
|
||||
# Create some items E1, total 70.40
|
||||
# Create some items E2, total 381.20
|
||||
self.i1 = models.EventItem.objects.create(event=self.e1, name="TP I1", quantity=1, cost=50.00, order=1)
|
||||
self.i2 = models.EventItem.objects.create(event=self.e1, name="TP I2", quantity=2, cost=3.20, order=2)
|
||||
self.i3 = models.EventItem.objects.create(event=self.e1, name="TP I3", quantity=7, cost=2.00, order=3)
|
||||
self.i4 = models.EventItem.objects.create(event=self.e2, name="TP I4", quantity=2, cost=190.60, order=1)
|
||||
self.i1 = models.EventItem.objects.create(
|
||||
event=self.e1, name="TP I1", quantity=1, cost=50.00, order=1)
|
||||
self.i2 = models.EventItem.objects.create(
|
||||
event=self.e1, name="TP I2", quantity=2, cost=3.20, order=2)
|
||||
self.i3 = models.EventItem.objects.create(
|
||||
event=self.e1, name="TP I3", quantity=7, cost=2.00, order=3)
|
||||
self.i4 = models.EventItem.objects.create(
|
||||
event=self.e2, name="TP I4", quantity=2, cost=190.60, order=1)
|
||||
|
||||
# Decimal type is needed here as that is what is returned from the model.
|
||||
# Using anything else results in a failure due to floating point arritmetic
|
||||
|
||||
276
RIGS/test_unit.py
Normal file
276
RIGS/test_unit.py
Normal file
@@ -0,0 +1,276 @@
|
||||
from datetime import date
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.management import call_command
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class TestAdminMergeObjects(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
|
||||
is_active=True, is_staff=True)
|
||||
|
||||
cls.persons = {
|
||||
1: models.Person.objects.create(name="Person 1"),
|
||||
2: models.Person.objects.create(name="Person 2"),
|
||||
3: models.Person.objects.create(name="Person 3"),
|
||||
}
|
||||
|
||||
cls.organisations = {
|
||||
1: models.Organisation.objects.create(name="Organisation 1"),
|
||||
2: models.Organisation.objects.create(name="Organisation 2"),
|
||||
3: models.Organisation.objects.create(name="Organisation 3"),
|
||||
}
|
||||
|
||||
cls.venues = {
|
||||
1: models.Venue.objects.create(name="Venue 1"),
|
||||
2: models.Venue.objects.create(name="Venue 2"),
|
||||
3: models.Venue.objects.create(name="Venue 3"),
|
||||
}
|
||||
|
||||
cls.events = {
|
||||
1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1],
|
||||
organisation=cls.organisations[3], venue=cls.venues[2]),
|
||||
2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2],
|
||||
organisation=cls.organisations[2], venue=cls.venues[3]),
|
||||
3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3],
|
||||
organisation=cls.organisations[1], venue=cls.venues[1]),
|
||||
4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3],
|
||||
organisation=cls.organisations[3], venue=cls.venues[3]),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(
|
||||
self.client.login(
|
||||
username=self.profile.username,
|
||||
password='testuser'))
|
||||
|
||||
def test_merge_confirmation(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
data = {
|
||||
'action': 'merge',
|
||||
'_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()]
|
||||
|
||||
}
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
|
||||
self.assertContains(response, "The following objects will be merged")
|
||||
for key, venue in self.venues.iteritems():
|
||||
self.assertContains(response, venue.name)
|
||||
|
||||
def test_merge_no_master(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()],
|
||||
'post': 'yes',
|
||||
}
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
|
||||
self.assertContains(response, "An error occured")
|
||||
|
||||
def test_venue_merge(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.venues[1].pk), unicode(self.venues[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.venues[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.venues[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk))
|
||||
|
||||
# Check the un-needed venue has been disposed of
|
||||
self.assertRaises(
|
||||
ObjectDoesNotExist,
|
||||
models.Venue.objects.get,
|
||||
pk=self.venues[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(
|
||||
models.Venue.objects.get(
|
||||
pk=self.venues[3].pk),
|
||||
self.venues[3])
|
||||
|
||||
# Check the events have been moved to the master venue
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.venue == self.venues[3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.venue, self.venues[1])
|
||||
|
||||
def test_person_merge(self):
|
||||
change_url = reverse('admin:RIGS_person_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.persons[1].pk), unicode(self.persons[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.persons[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.persons[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk))
|
||||
|
||||
# Check the un-needed people have been disposed of
|
||||
self.assertRaises(
|
||||
ObjectDoesNotExist,
|
||||
models.Person.objects.get,
|
||||
pk=self.persons[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(
|
||||
models.Person.objects.get(
|
||||
pk=self.persons[3].pk),
|
||||
self.persons[3])
|
||||
|
||||
# Check the events have been moved to the master person
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.person == self.persons[3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.person, self.persons[1])
|
||||
|
||||
def test_organisation_merge(self):
|
||||
change_url = reverse('admin:RIGS_organisation_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.organisations[1].pk), unicode(self.organisations[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.organisations[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.organisations[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(
|
||||
models.Organisation.objects.get(
|
||||
pk=self.organisations[1].pk))
|
||||
|
||||
# Check the un-needed organisations have been disposed of
|
||||
self.assertRaises(
|
||||
ObjectDoesNotExist,
|
||||
models.Organisation.objects.get,
|
||||
pk=self.organisations[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(
|
||||
models.Organisation.objects.get(
|
||||
pk=self.organisations[3].pk),
|
||||
self.organisations[3])
|
||||
|
||||
# Check the events have been moved to the master organisation
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.organisation == self.organisations[
|
||||
3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.organisation, self.organisations[1])
|
||||
|
||||
|
||||
class TestInvoiceDelete(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = models.Profile.objects.create(
|
||||
username="testuser1",
|
||||
email="1@test.com",
|
||||
is_superuser=True,
|
||||
is_active=True,
|
||||
is_staff=True)
|
||||
|
||||
cls.events = {
|
||||
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
|
||||
2: models.Event.objects.create(name="TE E2", start_date=date.today())
|
||||
}
|
||||
|
||||
cls.invoices = {
|
||||
1: models.Invoice.objects.create(event=cls.events[1]),
|
||||
2: models.Invoice.objects.create(event=cls.events[2])
|
||||
}
|
||||
|
||||
cls.payments = {
|
||||
1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34,
|
||||
method=models.Payment.CASH)
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(
|
||||
self.client.login(
|
||||
username=self.profile.username,
|
||||
password='testuser'))
|
||||
|
||||
def test_invoice_delete_allowed(self):
|
||||
request_url = reverse(
|
||||
'invoice_delete', kwargs={
|
||||
'pk': self.invoices[2].pk})
|
||||
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertContains(response, "Are you sure")
|
||||
|
||||
# Check the invoice still exists
|
||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk))
|
||||
|
||||
# Actually delete it
|
||||
response = self.client.post(request_url, follow=True)
|
||||
|
||||
# Check the invoice is deleted
|
||||
self.assertRaises(
|
||||
ObjectDoesNotExist,
|
||||
models.Invoice.objects.get,
|
||||
pk=self.invoices[2].pk)
|
||||
|
||||
def test_invoice_delete_not_allowed(self):
|
||||
request_url = reverse(
|
||||
'invoice_delete', kwargs={
|
||||
'pk': self.invoices[1].pk})
|
||||
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertContains(
|
||||
response,
|
||||
"To delete an invoice, delete the payments first.")
|
||||
|
||||
# Check the invoice still exists
|
||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
||||
|
||||
# Try to actually delete it
|
||||
response = self.client.post(request_url, follow=True)
|
||||
|
||||
# Check this didn't work
|
||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
||||
|
||||
|
||||
class TestSampleDataGenerator(TestCase):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_generate_sample_data(self):
|
||||
# Run the management command and check there are no exceptions
|
||||
call_command('generateSampleData')
|
||||
|
||||
# Check there are lots of events
|
||||
self.assertTrue(models.Event.objects.all().count() > 100)
|
||||
|
||||
def test_production_exception(self):
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
CommandError,
|
||||
".*production",
|
||||
call_command,
|
||||
'generateSampleData')
|
||||
173
RIGS/urls.py
173
RIGS/urls.py
@@ -1,156 +1,225 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from RIGS import models, views, rigboard, finance, ical, versioning, forms
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from PyRIGS.decorators import permission_required_with_403
|
||||
from PyRIGS.decorators import api_key_required
|
||||
from PyRIGS.decorators import permission_required_with_403
|
||||
from RIGS import models, views, rigboard, finance, ical, versioning, forms
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Examples:
|
||||
# url(r'^$', 'PyRIGS.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
url('^$', login_required(views.Index.as_view()), name='index'),
|
||||
url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'),
|
||||
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'),
|
||||
url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}),
|
||||
url(r'^user/password_reset/$',
|
||||
'django.contrib.auth.views.password_reset',
|
||||
{'password_reset_form': forms.PasswordReset}),
|
||||
|
||||
# People
|
||||
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
|
||||
name='person_list'),
|
||||
url(r'^people/add/$',
|
||||
permission_required_with_403('RIGS.add_person')(views.PersonCreate.as_view()),
|
||||
permission_required_with_403('RIGS.add_person')(
|
||||
views.PersonCreate.as_view()),
|
||||
name='person_create'),
|
||||
url(r'^people/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_person')(views.PersonDetail.as_view()),
|
||||
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()),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.change_person')(
|
||||
views.PersonUpdate.as_view()),
|
||||
name='person_update'),
|
||||
|
||||
# Organisations
|
||||
url(r'^organisations/$',
|
||||
permission_required_with_403('RIGS.view_organisation')(views.OrganisationList.as_view()),
|
||||
permission_required_with_403('RIGS.view_organisation')(
|
||||
views.OrganisationList.as_view()),
|
||||
name='organisation_list'),
|
||||
url(r'^organisations/add/$',
|
||||
permission_required_with_403('RIGS.add_organisation')(views.OrganisationCreate.as_view()),
|
||||
permission_required_with_403('RIGS.add_organisation')(
|
||||
views.OrganisationCreate.as_view()),
|
||||
name='organisation_create'),
|
||||
url(r'^organisations/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_organisation')(views.OrganisationDetail.as_view()),
|
||||
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()),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.change_organisation')(
|
||||
views.OrganisationUpdate.as_view()),
|
||||
name='organisation_update'),
|
||||
|
||||
# Venues
|
||||
url(r'^venues/$',
|
||||
permission_required_with_403('RIGS.view_venue')(views.VenueList.as_view()),
|
||||
permission_required_with_403('RIGS.view_venue')(
|
||||
views.VenueList.as_view()),
|
||||
name='venue_list'),
|
||||
url(r'^venues/add/$',
|
||||
permission_required_with_403('RIGS.add_venue')(views.VenueCreate.as_view()),
|
||||
permission_required_with_403('RIGS.add_venue')(
|
||||
views.VenueCreate.as_view()),
|
||||
name='venue_create'),
|
||||
url(r'^venues/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_venue')(views.VenueDetail.as_view()),
|
||||
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()),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.change_venue')(
|
||||
views.VenueUpdate.as_view()),
|
||||
name='venue_update'),
|
||||
|
||||
# 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/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
|
||||
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/(?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/activity/$',
|
||||
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
|
||||
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()),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.view_event')(
|
||||
rigboard.EventDetail.as_view()),
|
||||
name='event_detail'),
|
||||
url(r'^event/(?P<pk>\d+)/print/$',
|
||||
permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
|
||||
permission_required_with_403('RIGS.view_event')(
|
||||
rigboard.EventPrint.as_view()),
|
||||
name='event_print'),
|
||||
url(r'^event/create/$',
|
||||
permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()),
|
||||
permission_required_with_403('RIGS.add_event')(
|
||||
rigboard.EventCreate.as_view()),
|
||||
name='event_create'),
|
||||
url(r'^event/(?P<pk>\d+)/edit/$',
|
||||
permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()),
|
||||
permission_required_with_403('RIGS.change_event')(
|
||||
rigboard.EventUpdate.as_view()),
|
||||
name='event_update'),
|
||||
url(r'^event/(?P<pk>\d+)/duplicate/$',
|
||||
permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()),
|
||||
permission_required_with_403('RIGS.add_event')(
|
||||
rigboard.EventDuplicate.as_view()),
|
||||
name='event_duplicate'),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.view_event')(
|
||||
versioning.VersionHistory.as_view()),
|
||||
name='event_history', kwargs={'model': models.Event}),
|
||||
|
||||
|
||||
|
||||
# Finance
|
||||
url(r'^invoice/$',
|
||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()),
|
||||
permission_required_with_403('RIGS.view_invoice')(
|
||||
finance.InvoiceIndex.as_view()),
|
||||
name='invoice_list'),
|
||||
url(r'^invoice/archive/$',
|
||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceArchive.as_view()),
|
||||
permission_required_with_403('RIGS.view_invoice')(
|
||||
finance.InvoiceArchive.as_view()),
|
||||
name='invoice_archive'),
|
||||
url(r'^invoice/waiting/$',
|
||||
permission_required_with_403('RIGS.add_invoice')(finance.InvoiceWaiting.as_view()),
|
||||
permission_required_with_403('RIGS.add_invoice')(
|
||||
finance.InvoiceWaiting.as_view()),
|
||||
name='invoice_waiting'),
|
||||
|
||||
url(r'^event/(?P<pk>\d+)/invoice/$',
|
||||
permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()),
|
||||
permission_required_with_403('RIGS.add_invoice')(
|
||||
finance.InvoiceEvent.as_view()),
|
||||
name='invoice_event'),
|
||||
|
||||
url(r'^invoice/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()),
|
||||
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()),
|
||||
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()),
|
||||
permission_required_with_403('RIGS.change_invoice')(
|
||||
finance.InvoiceVoid.as_view()),
|
||||
name='invoice_void'),
|
||||
url(r'^invoice/(?P<pk>\d+)/delete/$',
|
||||
permission_required_with_403('RIGS.change_invoice')(
|
||||
finance.InvoiceDelete.as_view()),
|
||||
name='invoice_delete'),
|
||||
url(r'^payment/create/$',
|
||||
permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()),
|
||||
permission_required_with_403('RIGS.add_payment')(
|
||||
finance.PaymentCreate.as_view()),
|
||||
name='payment_create'),
|
||||
url(r'^payment/(?P<pk>\d+)/delete/$',
|
||||
permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()),
|
||||
permission_required_with_403('RIGS.add_payment')(
|
||||
finance.PaymentDelete.as_view()),
|
||||
name='payment_delete'),
|
||||
|
||||
# User editing
|
||||
url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'),
|
||||
url(r'^user/$',
|
||||
login_required(views.ProfileDetail.as_view()),
|
||||
name='profile_detail'),
|
||||
url(r'^user/(?P<pk>\d+)/$',
|
||||
permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()),
|
||||
permission_required_with_403('RIGS.view_profile')(
|
||||
views.ProfileDetail.as_view()),
|
||||
name='profile_detail'),
|
||||
url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()),
|
||||
name='profile_update_self'),
|
||||
url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'),
|
||||
url(r'^user/reset_api_key$',
|
||||
login_required(
|
||||
views.ResetApiKey.as_view(
|
||||
permanent=False)),
|
||||
name='reset_api_key'),
|
||||
|
||||
# ICS Calendar - API key authentication
|
||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"),
|
||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$',
|
||||
api_key_required(ical.CalendarICS()), name="ics_calendar"),
|
||||
|
||||
# API
|
||||
url(r'^api/(?P<model>\w+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"),
|
||||
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"),
|
||||
url(r'^api/(?P<model>\w+)/$',
|
||||
login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$',
|
||||
login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
|
||||
# Legacy URL's
|
||||
url(r'^rig/show/(?P<pk>\d+)/$', RedirectView.as_view(permanent=True,pattern_name='event_detail')),
|
||||
url(r'^bookings/$', RedirectView.as_view(permanent=True,pattern_name='rigboard')),
|
||||
url(r'^bookings/past/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
|
||||
url(r'^rig/show/(?P<pk>\d+)/$',
|
||||
RedirectView.as_view(permanent=True,
|
||||
pattern_name='event_detail')),
|
||||
url(r'^bookings/$',
|
||||
RedirectView.as_view(permanent=True,
|
||||
pattern_name='rigboard')),
|
||||
url(r'^bookings/past/$',
|
||||
RedirectView.as_view(permanent=True,
|
||||
pattern_name='event_archive')),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
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 django.db.models import ForeignKey, IntegerField, EmailField, TextField
|
||||
|
||||
from RIGS import models, forms
|
||||
import datetime
|
||||
import re
|
||||
import logging
|
||||
|
||||
import reversion
|
||||
from diff_match_patch import diff_match_patch
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import IntegerField, EmailField, TextField
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
|
||||
from RIGS import models
|
||||
|
||||
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
|
||||
# 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)
|
||||
# This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an
|
||||
# alternative becomes available)
|
||||
theFields = oldObj._meta.fields
|
||||
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
|
||||
|
||||
def display_value(self, value):
|
||||
if isinstance(self.field, IntegerField) and len(self.field.choices) > 0:
|
||||
if isinstance(self.field, IntegerField) and len(
|
||||
self.field.choices) > 0:
|
||||
return [x[1] for x in self.field.choices if x[0] == value][0]
|
||||
return value
|
||||
|
||||
@@ -64,6 +57,25 @@ def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
return True
|
||||
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 = []
|
||||
|
||||
for thisField in theFields:
|
||||
@@ -72,8 +84,15 @@ def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
if name in excluded_keys:
|
||||
continue # if we're excluding this field, skip over it
|
||||
|
||||
try:
|
||||
oldValue = getattr(oldObj, name, None)
|
||||
except ObjectDoesNotExist:
|
||||
oldValue = None
|
||||
|
||||
try:
|
||||
newValue = getattr(newObj, name, None)
|
||||
except ObjectDoesNotExist:
|
||||
newValue = None
|
||||
|
||||
try:
|
||||
bothBlank = (not oldValue) and (not newValue)
|
||||
@@ -85,14 +104,17 @@ def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def compare_event_items(old, new):
|
||||
# Recieves two event version objects and compares their items, returns an array of ItemCompare objects
|
||||
# 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
|
||||
@@ -101,26 +123,36 @@ def compare_event_items(old, new):
|
||||
# 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
|
||||
if version.field_dict["event"] == old.object_id_int:
|
||||
compare = ItemCompare(old=version.object_version.object)
|
||||
item_dict[version.object_id] = compare
|
||||
|
||||
for version in new_item_versions: # go through the new versions
|
||||
if version.field_dict["event"] == new.object_id_int:
|
||||
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
|
||||
# see if there's a matching old version
|
||||
compare = item_dict[version.object_id]
|
||||
# then add the new version to the dictionary
|
||||
compare.new = version.object_version.object
|
||||
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
|
||||
# update the dictionary with the changes
|
||||
item_dict[version.object_id] = compare
|
||||
|
||||
changes = []
|
||||
for (_, compare) in item_dict.items():
|
||||
compare.changes = model_compare(compare.old, compare.new, ['id','event','order']) # see what's changed
|
||||
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
|
||||
# transfer into a sequential array to make it easier to deal with
|
||||
# later
|
||||
changes.append(compare)
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def get_versions_for_model(models):
|
||||
content_types = []
|
||||
for model in models:
|
||||
@@ -132,24 +164,28 @@ def get_versions_for_model(models):
|
||||
|
||||
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)
|
||||
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')
|
||||
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:
|
||||
if oldVersion is None:
|
||||
oldVersion = get_previous_version(newVersion)
|
||||
|
||||
modelClass = newVersion.content_type.model_class()
|
||||
@@ -168,11 +204,13 @@ def get_changes_for_version(newVersion, oldVersion=None):
|
||||
|
||||
if oldVersion:
|
||||
compare['old'] = oldVersion.object_version.object
|
||||
compare['field_changes'] = model_compare(compare['old'], compare['new'])
|
||||
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"
|
||||
@@ -182,7 +220,8 @@ class VersionHistory(generic.ListView):
|
||||
thisModel = self.kwargs['model']
|
||||
|
||||
# thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk'])
|
||||
versions = reversion.get_for_object_reference(thisModel, self.kwargs['pk'])
|
||||
versions = reversion.get_for_object_reference(
|
||||
thisModel, self.kwargs['pk'])
|
||||
|
||||
return versions
|
||||
|
||||
@@ -200,7 +239,8 @@ class VersionHistory(generic.ListView):
|
||||
if versionNo >= len(versions) - 1:
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
else:
|
||||
thisItem = get_changes_for_version(thisVersion, versions[versionNo+1])
|
||||
thisItem = get_changes_for_version(
|
||||
thisVersion, versions[versionNo + 1])
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
@@ -209,17 +249,18 @@ class VersionHistory(generic.ListView):
|
||||
|
||||
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])
|
||||
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)
|
||||
|
||||
@@ -233,20 +274,24 @@ class ActivityTable(generic.ListView):
|
||||
|
||||
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])
|
||||
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)})
|
||||
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)
|
||||
@@ -255,23 +300,27 @@ class ActivityFeed(generic.ListView):
|
||||
|
||||
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:
|
||||
if thisItem['item_changes'] or thisItem[
|
||||
'field_changes'] or thisItem['old'] is 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
|
||||
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']:
|
||||
if params['maxAge'] is None or timeAgo <= params[
|
||||
'maxAge']:
|
||||
timeTogether = timeDiff < params['group']
|
||||
break
|
||||
|
||||
sameUser = thisItem['revision'].user == items[-1]['revision'].user
|
||||
sameUser = thisItem[
|
||||
'revision'].user == items[-1]['revision'].user
|
||||
thisItem['withPrevious'] = timeTogether & sameUser
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
context['object_list'] = items
|
||||
|
||||
|
||||
return context
|
||||
164
RIGS/views.py
164
RIGS/views.py
@@ -1,23 +1,25 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.views import generic
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.core import serializers
|
||||
from django.conf import settings
|
||||
import datetime
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
import simplejson
|
||||
from django.contrib import messages
|
||||
import datetime, pytz
|
||||
import operator
|
||||
from registration.views import RegistrationView
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
|
||||
from RIGS import models, forms
|
||||
from RIGS import models
|
||||
|
||||
"""
|
||||
Displays the current rig count along with a few other bits and pieces
|
||||
"""
|
||||
|
||||
|
||||
class Index(generic.TemplateView):
|
||||
template_name = 'RIGS/index.html'
|
||||
|
||||
@@ -26,6 +28,7 @@ class Index(generic.TemplateView):
|
||||
context['rig_count'] = models.Event.objects.rig_count()
|
||||
return context
|
||||
|
||||
|
||||
def login(request, **kwargs):
|
||||
if request.user.is_authenticated():
|
||||
next = request.REQUEST.get('next', '/')
|
||||
@@ -35,11 +38,14 @@ def login(request, **kwargs):
|
||||
|
||||
return login(request)
|
||||
|
||||
|
||||
"""
|
||||
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
|
||||
the new information onto the page.
|
||||
"""
|
||||
|
||||
|
||||
class CloseModal(generic.TemplateView):
|
||||
template_name = 'closemodal.html'
|
||||
|
||||
@@ -54,7 +60,8 @@ class PersonList(generic.ListView):
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
if len(q) >= 3:
|
||||
object_list = self.model.objects.filter(Q(name__icontains=q) | Q(email__icontains=q))
|
||||
object_list = self.model.objects.filter(
|
||||
Q(name__icontains=q) | Q(email__icontains=q))
|
||||
else:
|
||||
object_list = self.model.objects.all()
|
||||
orderBy = self.request.GET.get('orderBy', None)
|
||||
@@ -74,9 +81,17 @@ class PersonCreate(generic.CreateView):
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('person_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'person_update', kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('person_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -91,9 +106,17 @@ class PersonUpdate(generic.UpdateView):
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('person_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'person_update', kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('person_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -108,7 +131,8 @@ class OrganisationList(generic.ListView):
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
if len(q) >= 3:
|
||||
object_list = self.model.objects.filter(Q(name__icontains=q) | Q(address__icontains=q))
|
||||
object_list = self.model.objects.filter(
|
||||
Q(name__icontains=q) | Q(address__icontains=q))
|
||||
else:
|
||||
object_list = self.model.objects.all()
|
||||
orderBy = self.request.GET.get('orderBy', "")
|
||||
@@ -128,9 +152,18 @@ class OrganisationCreate(generic.CreateView):
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('organisation_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'organisation_update',
|
||||
kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('organisation_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -145,9 +178,18 @@ class OrganisationUpdate(generic.UpdateView):
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('organisation_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'organisation_update',
|
||||
kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('organisation_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -162,7 +204,8 @@ class VenueList(generic.ListView):
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
if len(q) >= 3:
|
||||
object_list = self.model.objects.filter(Q(name__icontains=q) | Q(address__icontains=q))
|
||||
object_list = self.model.objects.filter(
|
||||
Q(name__icontains=q) | Q(address__icontains=q))
|
||||
else:
|
||||
object_list = self.model.objects.all()
|
||||
orderBy = self.request.GET.get('orderBy', "")
|
||||
@@ -177,14 +220,28 @@ class VenueDetail(generic.DetailView):
|
||||
|
||||
class VenueCreate(generic.CreateView):
|
||||
model = models.Venue
|
||||
fields = ['name','phone','email','address','notes','three_phase_available']
|
||||
fields = [
|
||||
'name',
|
||||
'phone',
|
||||
'email',
|
||||
'address',
|
||||
'notes',
|
||||
'three_phase_available']
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('venue_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'venue_update', kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('venue_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -194,14 +251,28 @@ class VenueCreate(generic.CreateView):
|
||||
|
||||
class VenueUpdate(generic.UpdateView):
|
||||
model = models.Venue
|
||||
fields = ['name','phone','email','address','notes','three_phase_available']
|
||||
fields = [
|
||||
'name',
|
||||
'phone',
|
||||
'email',
|
||||
'address',
|
||||
'notes',
|
||||
'three_phase_available']
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('venue_update',kwargs={'pk':self.object.pk}))
|
||||
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
|
||||
update_url = str(
|
||||
reverse_lazy(
|
||||
'venue_update', kwargs={
|
||||
'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" +
|
||||
serializers.serialize("json", [self.object]))
|
||||
messages.info(
|
||||
self.request,
|
||||
"modalobject[0]['update_url']='" +
|
||||
update_url +
|
||||
"'")
|
||||
else:
|
||||
url = reverse_lazy('venue_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -273,7 +344,6 @@ class SecureAPIRequest(generic.View):
|
||||
qs.append(q)
|
||||
queries.append(reduce(operator.or_, qs))
|
||||
|
||||
|
||||
# Build the data response list
|
||||
results = []
|
||||
query = reduce(operator.and_, queries)
|
||||
@@ -285,24 +355,30 @@ class SecureAPIRequest(generic.View):
|
||||
'label': o.name,
|
||||
}
|
||||
try: # See if there is a valid update URL
|
||||
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
|
||||
data['update'] = reverse(
|
||||
"%s_update" %
|
||||
model, kwargs={
|
||||
'pk': o.pk})
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
results.append(data)
|
||||
|
||||
# return a data response
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
return HttpResponse(
|
||||
json, content_type="application/json") # Always json
|
||||
|
||||
start = request.GET.get('start', None)
|
||||
end = request.GET.get('end', None)
|
||||
|
||||
if model == "event" and start and end:
|
||||
# Probably a calendar request
|
||||
start_datetime = datetime.datetime.strptime( start, "%Y-%m-%dT%H:%M:%S" )
|
||||
start_datetime = datetime.datetime.strptime(
|
||||
start, "%Y-%m-%dT%H:%M:%S")
|
||||
end_datetime = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
objects = self.models[model].objects.events_in_bounds(start_datetime,end_datetime)
|
||||
objects = self.models[model].objects.events_in_bounds(
|
||||
start_datetime, end_datetime)
|
||||
|
||||
results = []
|
||||
for item in objects:
|
||||
@@ -318,10 +394,12 @@ class SecureAPIRequest(generic.View):
|
||||
|
||||
results.append(data)
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
return HttpResponse(
|
||||
json, content_type="application/json") # Always json
|
||||
|
||||
return HttpResponse(model)
|
||||
|
||||
|
||||
class ProfileDetail(generic.DetailView):
|
||||
model = models.Profile
|
||||
|
||||
@@ -334,6 +412,7 @@ class ProfileDetail(generic.DetailView):
|
||||
|
||||
return self.model.objects.filter(pk=pk)
|
||||
|
||||
|
||||
class ProfileUpdateSelf(generic.UpdateView):
|
||||
model = models.Profile
|
||||
fields = ['first_name', 'last_name', 'email', 'initials', 'phone']
|
||||
@@ -348,6 +427,7 @@ class ProfileUpdateSelf(generic.UpdateView):
|
||||
url = reverse_lazy('profile_detail')
|
||||
return url
|
||||
|
||||
|
||||
class ResetApiKey(generic.RedirectView):
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
self.request.user.api_key = self.request.user.make_api_key()
|
||||
|
||||
53
app.json
Normal file
53
app.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "PyRIGS",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"postdeploy": "python manage.py migrate && python manage.py generateSampleData"
|
||||
},
|
||||
"env": {
|
||||
"DEBUG": {
|
||||
"required": true
|
||||
},
|
||||
"STAGING": "1",
|
||||
"EMAIL_FROM": {
|
||||
"required": true
|
||||
},
|
||||
"EMAIL_HOST": {
|
||||
"required": true
|
||||
},
|
||||
"EMAIL_HOST_PASSWORD": {
|
||||
"required": true
|
||||
},
|
||||
"EMAIL_HOST_USER": {
|
||||
"required": true
|
||||
},
|
||||
"EMAIL_PORT": {
|
||||
"required": true
|
||||
},
|
||||
"EMAIL_USE_SSL": {
|
||||
"required": true
|
||||
},
|
||||
"RECAPTCHA_PRIVATE_KEY": {
|
||||
"required": true
|
||||
},
|
||||
"RECAPTCHA_PUBLIC_KEY": {
|
||||
"required": true
|
||||
},
|
||||
"SECRET_KEY": {
|
||||
"generator": "secret"
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
"web": {
|
||||
"quantity": 1
|
||||
}
|
||||
},
|
||||
"addons": [
|
||||
"heroku-postgresql"
|
||||
],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/python"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
27
importer.py
27
importer.py
@@ -14,11 +14,11 @@ from RIGS import models
|
||||
import reversion
|
||||
import datetime
|
||||
import uuid
|
||||
from multiprocessing import Process
|
||||
|
||||
# Slight fix for needing to restablish the connection
|
||||
connection.close()
|
||||
|
||||
|
||||
def fix_email(email):
|
||||
if not (email is None or email is "") and ("@" not in email):
|
||||
email += "@nottingham.ac.uk"
|
||||
@@ -110,7 +110,8 @@ def import_organisations(delete=False):
|
||||
notes = row[5]
|
||||
|
||||
object, created = models.Organisation.objects.get_or_create(pk=row[0], name=row[1], phone=row[2],
|
||||
address=row[3],
|
||||
address=row[
|
||||
3],
|
||||
union_account=row[4], notes=notes)
|
||||
if created:
|
||||
print("Created: " + object.__str__())
|
||||
@@ -325,13 +326,15 @@ def import_invoices(delete=False):
|
||||
payment.save()
|
||||
print(payment)
|
||||
|
||||
if invoice.invoice_date < (datetime.date.today() - datetime.timedelta(days=365)) and invoice.balance:
|
||||
if invoice.invoice_date < (
|
||||
datetime.date.today() - datetime.timedelta(days=365)) and invoice.balance:
|
||||
p2 = models.Payment(amount=invoice.balance)
|
||||
p2.invoice = invoice
|
||||
p2.method = payment.ADJUSTMENT
|
||||
p2.date = datetime.date.today()
|
||||
p2.save()
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def main():
|
||||
# processs = []
|
||||
@@ -357,16 +360,28 @@ def main():
|
||||
import_invoices(True)
|
||||
|
||||
# Do this before doing non rigs else it gets ugly
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % ('event', 'event')
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % (
|
||||
'event', 'event')
|
||||
cursor = connections['default'].cursor()
|
||||
cursor.execute(sql)
|
||||
import_nonrigs(False)
|
||||
|
||||
sequences = ['profile', 'person', 'organisation', 'vatrate', 'venue', 'event', 'eventitem', 'invoice', 'payment']
|
||||
sequences = [
|
||||
'profile',
|
||||
'person',
|
||||
'organisation',
|
||||
'vatrate',
|
||||
'venue',
|
||||
'event',
|
||||
'eventitem',
|
||||
'invoice',
|
||||
'payment']
|
||||
for seq in sequences:
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % (seq, seq)
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % (
|
||||
seq, seq)
|
||||
cursor = connections['default'].cursor()
|
||||
cursor.execute(sql)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
diff-match-patch==20121119
|
||||
dj-database-url==0.3.0
|
||||
dj-static==0.0.6
|
||||
Django==1.8.2
|
||||
@@ -19,7 +20,7 @@ python-dateutil==2.4.2
|
||||
pytz==2015.4
|
||||
raven==5.8.1
|
||||
reportlab==3.1.44
|
||||
selenium==2.46.0
|
||||
selenium==2.53.6
|
||||
simplejson==3.7.2
|
||||
six==1.9.0
|
||||
sqlparse==0.1.15
|
||||
|
||||
@@ -14,14 +14,16 @@
|
||||
|
||||
<link rel="icon" type="image/png" 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='https://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" %}">
|
||||
{% block css %}
|
||||
{% 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>Raven.config('{% sentry_public_dsn %}').install()</script>
|
||||
{% block preload_js %}
|
||||
@@ -50,14 +52,19 @@
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</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>
|
||||
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span>
|
||||
Rigboard</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 %}
|
||||
<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 %}
|
||||
{% 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 %}
|
||||
|
||||
</ul>
|
||||
@@ -67,11 +74,14 @@
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a></li>
|
||||
{% 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 text-danger"></span> Waiting</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li>
|
||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp text-warning"></span> Outstanding</a>
|
||||
</li>
|
||||
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
|
||||
Archive</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -144,10 +154,6 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="col-sm-12 text-center">
|
||||
Reminder: Please consider carefully before booking rigs at this moment
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{ user }} activation required
|
||||
{{ user|safe }} activation required
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form action="{% url 'login' %}" method="post" role="form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from training import models
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.TrainingCategory)
|
||||
admin.site.register(models.TrainingItem)
|
||||
admin.site.register(models.TrainingRecord)
|
||||
admin.site.register(models.TrainingLevelRecord)
|
||||
@@ -1,762 +0,0 @@
|
||||
Option Compare Database
|
||||
Option Explicit
|
||||
|
||||
' exportSQL version 3.2-dev
|
||||
' www.rot13.org/~dpavlin/projects.html#sql
|
||||
'
|
||||
' based on exportSQL version 2.0 from www.cynergi.net/prod/exportsql/
|
||||
'
|
||||
' (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
' (C) Pedro Freire - pedro.freire@cynergi.net (do not add to mailing lists without permission)
|
||||
' (c) 2000-2001 Dobrica Pavlinusic <dpavlin@rot13.org> - added PostgreSQL support
|
||||
'
|
||||
' This code is provided free for anyone's use and is therefore without guarantee or support.
|
||||
' This does NOT mean CYNERGI delegates its copyright to anyone using it! You may change the
|
||||
' code in any way, as long as this notice remains on the code and CYNERGI is notified (if you
|
||||
' publish the changes: if your changes/corrections prove valuable and are added to the code,
|
||||
' you will be listed in a credit list on this file).
|
||||
'
|
||||
' You may NOT sell this as part of a non-free package:
|
||||
' IF YOU HAVE PAID FOR THIS CODE, YOU HAVE BEEN ROBBED! CONTACT admin@cynergi.net!
|
||||
|
||||
' MODULE
|
||||
' "exportSQL"
|
||||
'
|
||||
' GOAL
|
||||
' Export all tables in a MS-Access database file to 2 text files:
|
||||
' one containing SQL instructions to delete the new tables to be created,
|
||||
' and the other with SQL instructions to create and insert data into
|
||||
' the new tables. The table structure and data will resemble as much as
|
||||
' possible the current Access database.
|
||||
'
|
||||
' HOW TO USE
|
||||
' Copy-and-paste this text file into an Access module and run the first
|
||||
' (and only public) function. in more detail, you:
|
||||
' * Open the Access .mdb file you wish to export
|
||||
' * in the default database objects window, click on "Modules", and then on "New"
|
||||
' * The code window that opens has some pre-written text (code). Delete it.
|
||||
' * Copy-and-paste this entire file to the code module window
|
||||
' * If you are using Microsoft Access 2000 you will have to make
|
||||
' one additional step: go into Tools/References and check following
|
||||
' component: Microsoft DAO Object 3.6 Library and uncheck Microsoft
|
||||
' ActiveX Data Objects Library
|
||||
' * You may hit the compile button (looks like 3 sheets of paper with an arrow on
|
||||
' top of them, pressing down on them), or select Debug, Compile Loaded Modules
|
||||
' from the top menu, just to make sure there are no errors, and that this code
|
||||
' works on your Access version (it works on Access'97 and should work on Access'95)
|
||||
' * Close the code module window - windows will prompt you to save the code:
|
||||
' answer "Yes", and when promped for a name for the module, type anything
|
||||
' (say, "MexportSQL")
|
||||
' The module is now part of your Access database. To run the export, you:
|
||||
' * Re-open the code module (by double-clicking on it, or clicking "Design"
|
||||
' with it selected). Move the cursor to where the first "Function" keyword appears.
|
||||
' Press F5 or select Run, Go/Continue from the top menu.
|
||||
' * Alternativelly, click on "Macros" on the database objects window,
|
||||
' and then on "New". On the macro window, select "RunCode" as the macro action,
|
||||
' and "exportSQL" as the function name, bellow. Save the macro similarly to the
|
||||
' module, and this time double-clicking on it, or clicking "Run" will run the export.
|
||||
'
|
||||
' BEFORE RUNNING THE EXPORT
|
||||
' Before running the export, be sure to check out the Export Options just bellow this
|
||||
' text, and change any according to your wishes and specs.
|
||||
'
|
||||
' TECH DATA
|
||||
' Public identifiers:
|
||||
' * Only one: "exportSQL", a function taking and returning no arguments. It runs the export.
|
||||
' Functionallity:
|
||||
' * Can export to mSQL v1, mSQL v2, MySQL or PostgreSQL recognised SQL statements
|
||||
' * Excellent respect for name conversion, namespace verification, type matching, etc.
|
||||
' * Detects default values "=Now()", "=Date()" and "=Time()" to create types like "TIMESTAMP"
|
||||
' * Fully configurable via private constants on top of code
|
||||
' * Exports two files: one for erasures, another for creations (useful when updating dbs)
|
||||
' * Generates compatibility warnings when necessary
|
||||
' * Code and generated files are paragraphed and easy to read
|
||||
' * Access text and memo fields can have any type of line termination: \n\r, \r\n, \n or \r
|
||||
' * Properly escapes text and memo fields, besides all types of binary fields
|
||||
' * Closes all open objects and files on error
|
||||
' * Known bugs / incomplete constructs are signalled with comments starting with "!!!!"
|
||||
' * Two alternatives on absent date/time type on mSQL: REAL or CHAR field
|
||||
' * Exports Primary key and Indexes for PostgreSQL
|
||||
' * Inserts Constrains as comments in SQL dump
|
||||
|
||||
' TODO:
|
||||
' + fix fields with non-valid characters (-, /, and friend)
|
||||
' + fix CR/LF in output
|
||||
' + fix boolean fields
|
||||
' + output of create table in separate file
|
||||
' - create index (FIX)
|
||||
|
||||
' Export Options - change at will
|
||||
|
||||
Private Const DB_ENGINE As String = "Pg" ' USE ONLY "M1" (mSQL v1), "M2" (mSQL v2), "MY" (MySQL) or "Pg" (PostgreSQL)
|
||||
Private Const DB_NAME As String = "" ' Use empty string for current. Else use filename or DSN name of database to export
|
||||
Private Const DB_CONNECT As String = "" ' Used only if above string is not empty
|
||||
Private Const MSQL_64kb_AVG As Long = 2048 ' ALWAYS < 65536 (to be consistent with MS Access). Set to max expected size of Access MEMO field (to preserve space in mSQL v1)
|
||||
Private Const WS_REPLACEMENT As String = "_" ' Use "" to simply eat whitespaces in identifiers (table and field names)
|
||||
Private Const IDENT_MAX_SIZE As Integer = 19 ' Suggest 64. Max size of identifiers (table and field names)
|
||||
Private Const PREFIX_ON_KEYWORD As String = "_" ' Prefix to add to identifier, if it is a reserved word
|
||||
Private Const SUFFIX_ON_KEYWORD As String = "" ' Suffix to add to identifier, if it is a reserved word
|
||||
Private Const PREFIX_ON_INDEX As String = "ix" ' Prefix to add to index identifier, to make it unique (mSQL v2)
|
||||
Private Const SUFFIX_ON_INDEX As String = "" ' Suffix to add to index identifier, to make it unique (mSQL v2)
|
||||
Private Const CREATE_SQL_FILE As String = "c:\temp\esql_create.txt" ' Use empty if open on #1. Will be overwritten if exists!
|
||||
Private Const DEL_SQL_FILE As String = "c:\temp\esql_del.txt" ' Use empty if open on #2. Will be overwritten if exists!
|
||||
Private Const ADD_SQL_FILE As String = "c:\temp\esql_add.txt" ' Use empty if open on #1. Will be overwritten if exists!
|
||||
Private Const LINE_BREAK As String = "\n" ' Try "<br>". String to replace line breaks in text fields
|
||||
Private Const COMMENTS As Boolean = True ' Dump comments into output file
|
||||
Private Const DISPLAY_WARNINGS As Boolean = True ' False to output the warnings to the files, only
|
||||
Private Const DATE_AS_STR As Boolean = True ' False to use real number data type for date, time and timestamp (in mSQL only)
|
||||
Private Const PARA_INSERT_AFTER As Integer = 3 ' Field count after which print INSERTs different lines
|
||||
Private Const INDENT_SIZE As Integer = 5 ' Number of spaces on indents
|
||||
|
||||
' Global var to store inter-funtion data
|
||||
Private warnings As String ' Not an option: do not set in any way
|
||||
Private COMMENT_PREFIX As String
|
||||
Private QUERY_SEPARATOR As String ' Terminator/separator of SQL queries (to instruct some monitor program to execute them)
|
||||
|
||||
|
||||
' Primary Export Function
|
||||
|
||||
Sub exportSQL()
|
||||
On Error GoTo exportSQL_error
|
||||
|
||||
Dim cdb As Database
|
||||
Dim ctableix As Integer, ctablename As String
|
||||
If COMMENTS Then
|
||||
If DB_ENGINE = "Pg" Then
|
||||
COMMENT_PREFIX = "--"
|
||||
QUERY_SEPARATOR = ";"
|
||||
Else
|
||||
COMMENT_PREFIX = "#"
|
||||
QUERY_SEPARATOR = "\g"
|
||||
End If
|
||||
End If
|
||||
|
||||
If DB_NAME = "" Then
|
||||
Set cdb = CurrentDb()
|
||||
Else
|
||||
Set cdb = OpenDatabase(DB_NAME, False, True, DB_CONNECT) ' Shared, read-only
|
||||
End If
|
||||
|
||||
If CREATE_SQL_FILE <> "" Then Open CREATE_SQL_FILE For Output As #1
|
||||
If DEL_SQL_FILE <> "" Then Open DEL_SQL_FILE For Output As #2
|
||||
If ADD_SQL_FILE <> "" Then Open ADD_SQL_FILE For Output As #3
|
||||
|
||||
DoCmd.Hourglass True
|
||||
|
||||
If COMMENTS Then
|
||||
Dim convert_to As String
|
||||
If (Left$(DB_ENGINE, 2) = "MY") Then
|
||||
convert_to = "MySQL"
|
||||
ElseIf (DB_ENGINE = "Pg") Then
|
||||
convert_to = "PostgreSQL"
|
||||
Else
|
||||
convert_to = "mSQL"
|
||||
End If
|
||||
Print #1, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #2, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #3, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #1, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
Print #2, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
Print #3, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
End If
|
||||
|
||||
'Go through the table definitions
|
||||
For ctableix = 0 To cdb.TableDefs.Count - 1
|
||||
|
||||
Dim cfieldix As Integer, cfieldname As String
|
||||
Dim fieldlst As String, sqlcode As String
|
||||
Dim primary_found As Boolean
|
||||
Dim crs As Recordset
|
||||
|
||||
' Let's take only the visible tables
|
||||
If (((cdb.TableDefs(ctableix).Attributes And DB_SYSTEMOBJECT) Or _
|
||||
(cdb.TableDefs(ctableix).Attributes And DB_HIDDENOBJECT))) = 0 Then
|
||||
|
||||
ctablename = conv_name("" & cdb.TableDefs(ctableix).Name)
|
||||
|
||||
Print #2,
|
||||
Print #2, "DROP TABLE " & ctablename & QUERY_SEPARATOR
|
||||
|
||||
' CREATE clause
|
||||
Print #1,
|
||||
Print #1, "CREATE TABLE " & ctablename
|
||||
Print #1, Space$(INDENT_SIZE) & "("
|
||||
|
||||
warnings = ""
|
||||
fieldlst = ""
|
||||
primary_found = False
|
||||
|
||||
' loop thorugh each field in the table
|
||||
For cfieldix = 0 To cdb.TableDefs(ctableix).Fields.Count - 1
|
||||
|
||||
Dim typestr As String, fieldsz As Integer, dvstr As String
|
||||
Dim found_ix As Boolean, cindex, tmpindex As Index, cfield, tmpfield As Field
|
||||
|
||||
' if this is not the first iteration, add separators
|
||||
If fieldlst <> "" Then
|
||||
fieldlst = fieldlst & ", "
|
||||
Print #1, ","
|
||||
End If
|
||||
|
||||
' get field name
|
||||
cfieldname = conv_name("" & cdb.TableDefs(ctableix).Fields(cfieldix).Name)
|
||||
fieldlst = fieldlst & cfieldname
|
||||
|
||||
' translate types
|
||||
If DB_ENGINE = "M1" Or DB_ENGINE = "M2" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbChar
|
||||
typestr = "CHAR(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "CHAR(" & fieldsz & ")"
|
||||
Case dbBoolean, dbByte, dbInteger, dbLong
|
||||
typestr = "INT"
|
||||
Case dbDouble, dbFloat, dbSingle
|
||||
typestr = "REAL"
|
||||
Case dbCurrency, dbDecimal, dbNumeric
|
||||
typestr = "REAL"
|
||||
warn "In new field '" & cfieldname & "', currency/BCD will be converted to REAL - there may be precision loss!", False
|
||||
Case dbDate
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(19)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & ".", False
|
||||
Case dbTime
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(8)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & ".", False
|
||||
Case dbTimeStamp
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(19)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & "." & IIf(DB_ENGINE = "M2", " Consider using pseudo field '_timestamp'.", ""), False
|
||||
Case dbMemo
|
||||
If DB_ENGINE = "M2" Then
|
||||
typestr = "TEXT(" & MSQL_64kb_AVG & ")"
|
||||
Else
|
||||
typestr = "CHAR(" & MSQL_64kb_AVG & ")"
|
||||
warn "In new field '" & cfieldname & "', dbMemo is not supported by mSQL v1 - fields larger than MSQL_64kb_AVG (" & MSQL_64kb_AVG & ") will not be accepted!", False
|
||||
End If
|
||||
Case dbBinary, dbVarBinary
|
||||
typestr = "CHAR(255)"
|
||||
warn "In new field '" & cfieldname & "', dbBinary and dbVarBinary are not supported by mSQL! - will use a text (CHAR(255)) field.", True
|
||||
Case dbLongBinary
|
||||
typestr = "CHAR(" & MSQL_64kb_AVG & ")"
|
||||
warn "In new field '" & cfieldname & "', dbLongBinary is not supported by mSQL! - will use a text (CHAR(" & MSQL_64kb_AVG & ")) field.", True
|
||||
Case Else
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End Select
|
||||
ElseIf DB_ENGINE = "MY" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbBinary
|
||||
typestr = "TINYBLOB"
|
||||
Case dbBoolean
|
||||
typestr = "TINYINT"
|
||||
Case dbByte
|
||||
typestr = "TINYINT UNSIGNED"
|
||||
Case dbChar
|
||||
typestr = "CHAR(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbCurrency
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDate
|
||||
typestr = "DATETIME"
|
||||
Case dbDecimal
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDouble
|
||||
typestr = "REAL"
|
||||
Case dbFloat
|
||||
typestr = "REAL"
|
||||
Case dbInteger
|
||||
typestr = "SMALLINT"
|
||||
Case dbLong
|
||||
typestr = "INT"
|
||||
Case dbLongBinary
|
||||
typestr = "LONGBLOB"
|
||||
Case dbMemo
|
||||
typestr = "LONGBLOB" ' !!!!! MySQL bug! Replace by LONGTEXT when corrected!
|
||||
Case dbNumeric
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbSingle
|
||||
typestr = "FLOAT"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "CHAR(" & fieldsz & ")"
|
||||
Case dbTime
|
||||
typestr = "TIME"
|
||||
Case dbTimeStamp
|
||||
typestr = "TIMESTAMP"
|
||||
Case dbVarBinary
|
||||
typestr = "TINYBLOB"
|
||||
Case dbBigInt, dbGUID
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
Case Else
|
||||
typestr = "LONGBLOB"
|
||||
End Select
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbBinary
|
||||
typestr = "int2"
|
||||
Case dbBoolean
|
||||
typestr = "bool"
|
||||
Case dbByte
|
||||
typestr = "int2"
|
||||
Case dbChar
|
||||
typestr = "varchar(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbCurrency
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDate
|
||||
typestr = "DATETIME"
|
||||
Case dbDecimal
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDouble
|
||||
typestr = "float8"
|
||||
Case dbFloat
|
||||
typestr = "float4"
|
||||
Case dbInteger
|
||||
typestr = "int4"
|
||||
Case dbLong
|
||||
typestr = "int8"
|
||||
Case dbLongBinary
|
||||
typestr = "text" ' hm?
|
||||
Case dbMemo
|
||||
typestr = "text"
|
||||
Case dbNumeric
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbSingle
|
||||
typestr = "float4"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "varchar(" & fieldsz & ")"
|
||||
Case dbTime
|
||||
typestr = "TIME"
|
||||
Case dbTimeStamp
|
||||
typestr = "TIMESTAMP"
|
||||
Case dbVarBinary
|
||||
typestr = "text" ' hm?
|
||||
Case dbBigInt, dbGUID
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
Case Else
|
||||
typestr = "text"
|
||||
End Select
|
||||
Else
|
||||
warn "unkown DB_ENGINE string " & DB_ENGINE, True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End If
|
||||
|
||||
' check not null and auto-increment properties
|
||||
If ((cdb.TableDefs(ctableix).Fields(cfieldix).Attributes And dbAutoIncrField) <> 0) Then
|
||||
If Left$(DB_ENGINE, 2) = "MY" Then
|
||||
typestr = typestr & " NOT NULL AUTO_INCREMENT"
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
typestr = " serial"
|
||||
Else
|
||||
typestr = typestr & " NOT NULL"
|
||||
warn "In new field '" & cfieldname & "', mSQL does not support auto-increment fields! - they will be pure INTs." & IIf(DB_ENGINE = "M2", " Consider using pseudo field '_rowid' or SEQUENCEs.", ""), False
|
||||
End If
|
||||
ElseIf cdb.TableDefs(ctableix).Fields(cfieldix).Required = True Then
|
||||
typestr = typestr & " NOT NULL"
|
||||
End If
|
||||
|
||||
' default value
|
||||
dvstr = cdb.TableDefs(ctableix).Fields(cfieldix).DefaultValue
|
||||
If dvstr <> "" Then
|
||||
If Left$(DB_ENGINE, 2) <> "MY" And DB_ENGINE <> "Pg" Then
|
||||
warn "In new field '" & cfieldname & "', mSQL does not support default values! - they won't be initialised.", False
|
||||
ElseIf Left$(DB_ENGINE, 2) = "MY" And cdb.TableDefs(ctableix).Fields(cfieldix).Required = False Then
|
||||
warn "In new field '" & cfieldname & "', MySQL needs NOT NULL to support default values! - it won't be set a default.", False
|
||||
ElseIf Left$(dvstr, 1) = """" Then
|
||||
typestr = typestr & " DEFAULT '" & conv_str(Mid$(dvstr, 2, Len(dvstr) - 2)) & "'"
|
||||
ElseIf ((LCase(dvstr) = "now()" Or LCase(dvstr) = "date()" Or LCase(dvstr) = "time()") And _
|
||||
(Left$(typestr, 5) = "DATE " Or Left$(typestr, 5) = "TIME " Or Left$(typestr, 9) = "DATETIME ")) Then
|
||||
typestr = "TIMESTAMP " & Right$(typestr, Len(typestr) - InStr(typestr, " "))
|
||||
ElseIf LCase(dvstr) = "no" Then
|
||||
typestr = typestr & " DEFAULT 0"
|
||||
ElseIf LCase(dvstr) = "yes" Then
|
||||
typestr = typestr & " DEFAULT 1"
|
||||
Else
|
||||
typestr = typestr & " DEFAULT " & dvstr
|
||||
End If
|
||||
End If
|
||||
|
||||
' add constrains
|
||||
Dim val_rule, val_text As String
|
||||
val_rule = cdb.TableDefs(ctableix).Fields(cfieldix).ValidationRule
|
||||
val_text = cdb.TableDefs(ctableix).Fields(cfieldix).ValidationText
|
||||
If DB_ENGINE = "Pg" And val_rule <> "" Then
|
||||
typestr = typestr & COMMENT_PREFIX & " check ( " & val_rule & " ) " & COMMENT_PREFIX & " " & val_text
|
||||
warn "Field '" & cfieldname & "' has constrain '" & val_rule & "' with text '" & val_text & "' which you have to convert manually (inserted as comment in SQL)", False
|
||||
End If
|
||||
|
||||
' check if primary key (for mSQL v1)
|
||||
If DB_ENGINE = "M1" Then
|
||||
found_ix = False
|
||||
For Each cindex In cdb.TableDefs(ctableix).Indexes
|
||||
If cindex.Primary Then
|
||||
For Each cfield In cindex.Fields
|
||||
If cfield.Name = cdb.TableDefs(ctableix).Fields(cfieldix).Name Then
|
||||
found_ix = True
|
||||
Exit For
|
||||
End If
|
||||
Next cfield
|
||||
If found_ix Then Exit For
|
||||
End If
|
||||
Next cindex
|
||||
If found_ix Then
|
||||
If primary_found Then
|
||||
warn "On new table '" & ctablename & "', mSQL v1 does not support more than one PRIMARY KEY! Only first key was set.", False
|
||||
Else
|
||||
typestr = typestr & " PRIMARY KEY"
|
||||
primary_found = True
|
||||
End If
|
||||
End If
|
||||
End If
|
||||
|
||||
'print out field info
|
||||
Print #1, Space$(INDENT_SIZE) & cfieldname & Space$(IDENT_MAX_SIZE - Len(cfieldname) + 2) & typestr;
|
||||
|
||||
Next cfieldix
|
||||
|
||||
' terminate CREATE clause
|
||||
If DB_ENGINE = "M2" Then
|
||||
Print #1,
|
||||
Print #1, Space$(INDENT_SIZE) & ")" & QUERY_SEPARATOR
|
||||
End If
|
||||
|
||||
' primary key and other index declaration
|
||||
If DB_ENGINE = "M2" Or Left$(DB_ENGINE, 2) = "MY" Or DB_ENGINE = "Pg" Then
|
||||
For Each cindex In cdb.TableDefs(ctableix).Indexes
|
||||
sqlcode = ""
|
||||
For Each cfield In cindex.Fields
|
||||
sqlcode = sqlcode & IIf(sqlcode = "", "", ", ") & conv_name(cfield.Name)
|
||||
Next cfield
|
||||
If DB_ENGINE = "M2" Then
|
||||
Print #1, "CREATE " & IIf(cindex.Unique, "UNIQUE ", "") & "INDEX " & _
|
||||
conv_name(PREFIX_ON_INDEX & cindex.Name & SUFFIX_ON_INDEX) & " ON " & _
|
||||
ctablename & " (" & sqlcode & ")" & QUERY_SEPARATOR
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
If cindex.Primary Then
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "PRIMARY KEY (" & sqlcode & ")";
|
||||
ElseIf cindex.Unique Then
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "UNIQUE INDEX (" & sqlcode & ")";
|
||||
Else
|
||||
' skip indexes which are part of primary key
|
||||
primary_found = False
|
||||
For Each tmpindex In cdb.TableDefs(ctableix).Indexes
|
||||
If tmpindex.Primary Then
|
||||
For Each tmpfield In tmpindex.Fields
|
||||
If sqlcode = conv_name(tmpfield.Name) Then
|
||||
primary_found = True
|
||||
Exit For
|
||||
End If
|
||||
Next tmpfield
|
||||
End If
|
||||
Next tmpindex
|
||||
If Not primary_found Then
|
||||
If DB_ENGINE = "Pg" Then
|
||||
' FIX: create index....
|
||||
Else
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "INDEX (" & sqlcode & ")";
|
||||
End If
|
||||
End If
|
||||
End If
|
||||
|
||||
Else
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & IIf(cindex.Primary, "PRIMARY ", "") & "KEY (" & sqlcode & ")";
|
||||
End If
|
||||
Next cindex
|
||||
End If
|
||||
|
||||
' terminate CREATE clause
|
||||
If DB_ENGINE <> "M2" Then
|
||||
Print #1,
|
||||
Print #1, Space$(INDENT_SIZE) & ")" & QUERY_SEPARATOR
|
||||
End If
|
||||
|
||||
' print any warnings bellow it
|
||||
If COMMENTS And warnings <> "" Then
|
||||
If DB_ENGINE = "M2" Then Print #1, COMMENT_PREFIX & " "
|
||||
Print #1, warnings
|
||||
warnings = ""
|
||||
End If
|
||||
|
||||
Print #1,
|
||||
|
||||
' INSERT clause
|
||||
Set crs = cdb.OpenRecordset(cdb.TableDefs(ctableix).Name)
|
||||
If crs.RecordCount <> 0 Then
|
||||
|
||||
' loop thorugh each record in the table
|
||||
crs.MoveFirst
|
||||
Do Until crs.EOF
|
||||
|
||||
' start paragraphing
|
||||
sqlcode = "INSERT INTO " & ctablename
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then
|
||||
Print #3, sqlcode
|
||||
If DB_ENGINE = "M1" Then Print #3, Space$(INDENT_SIZE) & "(" & fieldlst & ")"
|
||||
Print #3, "VALUES ("
|
||||
sqlcode = Space$(INDENT_SIZE)
|
||||
Else
|
||||
If DB_ENGINE = "M1" Then sqlcode = sqlcode & " (" & fieldlst & ")"
|
||||
sqlcode = sqlcode & " VALUES ("
|
||||
End If
|
||||
|
||||
' loop through each field in each record
|
||||
For cfieldix = 0 To crs.Fields.Count - 1
|
||||
|
||||
' based on type, prepare the field value
|
||||
If IsNull(crs.Fields(cfieldix).Value) Then
|
||||
sqlcode = sqlcode & "NULL"
|
||||
Else
|
||||
Select Case crs.Fields(cfieldix).Type
|
||||
Case dbBoolean
|
||||
If DB_ENGINE = "Pg" Then
|
||||
sqlcode = sqlcode & IIf(crs.Fields(cfieldix).Value = True, "'t'", "'f'")
|
||||
Else
|
||||
sqlcode = sqlcode & IIf(crs.Fields(cfieldix).Value = True, "1", "0")
|
||||
End If
|
||||
Case dbChar, dbText, dbMemo
|
||||
sqlcode = sqlcode & "'" & conv_str(crs.Fields(cfieldix).Value) & "'"
|
||||
Case dbDate, dbTimeStamp
|
||||
If Left$(DB_ENGINE, 2) = "MY" Or DATE_AS_STR Then
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "YYYY-MM-DD HH:MM:SS") & "'"
|
||||
Else
|
||||
'print in Access internal format: IEEE 64-bit (8-byte) FP
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "#.#########") & "'"
|
||||
End If
|
||||
Case dbTime
|
||||
If Left$(DB_ENGINE, 2) = "MY" Or DATE_AS_STR Then
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "HH:MM:SS") & "'"
|
||||
Else
|
||||
'print in Access internal format: IEEE 64-bit (8-byte) FP
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "#.#########") & "'"
|
||||
End If
|
||||
Case dbBinary, dbLongBinary, dbVarBinary
|
||||
sqlcode = sqlcode & "'" & conv_bin(crs.Fields(cfieldix).Value) & "'"
|
||||
Case dbCurrency, dbDecimal, dbDouble, dbFloat, dbNumeric, dbSingle
|
||||
sqlcode = sqlcode & conv_float(crs.Fields(cfieldix).Value)
|
||||
Case Else
|
||||
sqlcode = sqlcode & conv_str(crs.Fields(cfieldix).Value)
|
||||
End Select
|
||||
End If
|
||||
|
||||
' paragraph separators
|
||||
If cfieldix < crs.Fields.Count - 1 Then
|
||||
sqlcode = sqlcode & ", "
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then
|
||||
Print #3, sqlcode
|
||||
sqlcode = Space$(INDENT_SIZE)
|
||||
End If
|
||||
End If
|
||||
|
||||
Next cfieldix
|
||||
|
||||
' print out result and any warnings
|
||||
sqlcode = sqlcode & IIf(crs.Fields.Count > PARA_INSERT_AFTER, " )", ")") & QUERY_SEPARATOR
|
||||
Print #3, sqlcode
|
||||
If COMMENTS And warnings <> "" Then
|
||||
Print #3, warnings
|
||||
warnings = ""
|
||||
End If
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then Print #3,
|
||||
|
||||
crs.MoveNext
|
||||
Loop
|
||||
|
||||
Else
|
||||
|
||||
' if there is no data on the table
|
||||
If COMMENTS Then Print #3, COMMENT_PREFIX & " This table has no data"
|
||||
|
||||
End If
|
||||
|
||||
crs.Close
|
||||
Set crs = Nothing
|
||||
|
||||
End If 'print only unhidden tables
|
||||
|
||||
Next ctableix
|
||||
|
||||
exportSQL_exit:
|
||||
Close #3
|
||||
Close #2
|
||||
Close #1
|
||||
|
||||
cdb.Close
|
||||
Set cdb = Nothing
|
||||
|
||||
DoCmd.Hourglass False
|
||||
|
||||
Exit Sub
|
||||
|
||||
exportSQL_error:
|
||||
MsgBox Err.Description
|
||||
Resume exportSQL_exit
|
||||
|
||||
End Sub
|
||||
|
||||
|
||||
Private Function conv_name(strname As String) As String
|
||||
Dim i As Integer, str As String
|
||||
|
||||
' replace inner spaces with WS_REPLACEMENT
|
||||
str = strname
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case " ", Chr$(9), Chr$(10), Chr$(13), "-", "/" ' space, tab, newline, carriage return
|
||||
str = Left$(str, i - 1) & WS_REPLACEMENT & Right$(str, Len(str) - i)
|
||||
i = i + Len(WS_REPLACEMENT)
|
||||
Case Else
|
||||
i = i + 1
|
||||
End Select
|
||||
Wend
|
||||
' restrict tablename to IDENT_MAX_SIZE chars, *after* eating spaces
|
||||
str = Left$(str, IDENT_MAX_SIZE)
|
||||
' check for reserved words
|
||||
conv_name = str
|
||||
If Left$(DB_ENGINE, 2) = "MY" Then
|
||||
Select Case LCase$(str)
|
||||
Case "add", "all", "alter", "and", "as", "asc", "auto_increment", "between", _
|
||||
"bigint", "binary", "blob", "both", "by", "cascade", "char", "character", _
|
||||
"change", "check", "column", "columns", "create", "data", "datetime", "dec", _
|
||||
"decimal", "default", "delete", "desc", "describe", "distinct", "double", _
|
||||
"drop", "escaped", "enclosed", "explain", "fields", "float", "float4", _
|
||||
"float8", "foreign", "from", "for", "full", "grant", "group", "having", _
|
||||
"ignore", "in", "index", "infile", "insert", "int", "integer", "interval", _
|
||||
"int1", "int2", "int3", "int4", "int8", "into", "is", "key", "keys", _
|
||||
"leading", "like", "lines", "limit", "lock", "load", "long", "longblob", _
|
||||
"longtext", "match", "mediumblob", "mediumtext", "mediumint", "middleint", _
|
||||
"numeric", "not", "null", "on", "option", "optionally", "or", "order", _
|
||||
"outfile", "partial", "precision", "primary", "procedure", "privileges", _
|
||||
"read", "real", "references", "regexp", "repeat", "replace", "restrict", _
|
||||
"rlike", "select", "set", "show", "smallint", "sql_big_tables", _
|
||||
"sql_big_selects", "sql_select_limit", "straight_join", "table", "tables", _
|
||||
"terminated", "tinyblob", "tinytext", "tinyint", "trailing", "to", "unique", _
|
||||
"unlock", "unsigned", "update", "usage", "values", "varchar", "varying", _
|
||||
"with", "write", "where", "zerofill"
|
||||
conv_name = Left$(PREFIX_ON_KEYWORD & str & SUFFIX_ON_KEYWORD, IDENT_MAX_SIZE)
|
||||
If (str = conv_name) Then
|
||||
warn "In identifier '" & strname & "', the new form '" & strname & _
|
||||
"' is a reserved word, and PREFIX_ON_KEYWORD ('" & _
|
||||
PREFIX_ON_KEYWORD & "') and SUFFIX_ON_KEYWORD ('" & SUFFIX_ON_KEYWORD & _
|
||||
"') make it larger than IDENT_MAX_SIZE, and after cut it is the same as the original! " & _
|
||||
"This is usually caused by a void or empty PREFIX_ON_KEYWORD.", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End If
|
||||
End Select
|
||||
End If
|
||||
End Function
|
||||
|
||||
|
||||
Private Function conv_str(str As String) As String
|
||||
Dim i As Integer, nlstr As String, rstr As Variant
|
||||
|
||||
nlstr = ""
|
||||
rstr = Null
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case Chr$(0) ' ASCII NUL
|
||||
nlstr = ""
|
||||
rstr = "\0"
|
||||
Case Chr$(8) ' backspace
|
||||
nlstr = ""
|
||||
rstr = "\b"
|
||||
Case Chr$(9) ' tab
|
||||
nlstr = ""
|
||||
rstr = "\t"
|
||||
Case "'"
|
||||
nlstr = ""
|
||||
rstr = "\'"
|
||||
Case """"
|
||||
nlstr = ""
|
||||
rstr = "\"""
|
||||
Case "\"
|
||||
nlstr = ""
|
||||
rstr = "\\"
|
||||
Case Chr$(10), Chr$(13) ' line feed and carriage return
|
||||
If nlstr <> "" And nlstr <> Mid$(str, i, 1) Then
|
||||
' there was a previous newline and this is its pair: eat it
|
||||
rstr = ""
|
||||
nlstr = ""
|
||||
Else
|
||||
' this is a fresh newline
|
||||
rstr = LINE_BREAK
|
||||
nlstr = Mid$(str, i, 1)
|
||||
End If
|
||||
Case Else
|
||||
nlstr = ""
|
||||
End Select
|
||||
If Not IsNull(rstr) Then
|
||||
str = Left$(str, i - 1) & rstr & Right$(str, Len(str) - i)
|
||||
i = i + Len(rstr)
|
||||
rstr = Null
|
||||
Else
|
||||
i = i + 1
|
||||
End If
|
||||
Wend
|
||||
conv_str = str
|
||||
End Function
|
||||
|
||||
|
||||
Private Function conv_bin(str As String) As String
|
||||
Dim i As Integer, rstr As String
|
||||
|
||||
rstr = ""
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case Chr$(0) ' ASCII NUL
|
||||
rstr = "\0"
|
||||
Case Chr$(8) ' backspace
|
||||
rstr = "\b"
|
||||
Case Chr$(9) ' tab
|
||||
rstr = "\t"
|
||||
Case "'"
|
||||
rstr = "\'"
|
||||
Case """"
|
||||
rstr = "\"""
|
||||
Case "\"
|
||||
rstr = "\\"
|
||||
Case Chr$(10) ' line feed
|
||||
rstr = "\n"
|
||||
Case Chr$(13) ' carriage return
|
||||
rstr = "\r"
|
||||
End Select
|
||||
If rstr <> "" Then
|
||||
str = Left$(str, i - 1) & rstr & Right$(str, Len(str) - i)
|
||||
i = i + Len(rstr)
|
||||
rstr = ""
|
||||
Else
|
||||
i = i + 1
|
||||
End If
|
||||
Wend
|
||||
conv_bin = str
|
||||
End Function
|
||||
|
||||
' This function is used to convert local setting of decimal , to .
|
||||
Private Function conv_float(str As String) As String
|
||||
Dim i As Integer
|
||||
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
If Mid$(str, i, 1) = "," Then
|
||||
str = Left$(str, i - 1) & "." & Right$(str, Len(str) - i)
|
||||
End If
|
||||
i = i + 1
|
||||
Wend
|
||||
conv_float = str
|
||||
End Function
|
||||
|
||||
|
||||
Private Sub warn(str As String, abortq As Boolean)
|
||||
If DISPLAY_WARNINGS Then MsgBox str, vbOKOnly Or vbExclamation, "Warning"
|
||||
warnings = warnings & COMMENT_PREFIX & " Warning: " & str & Chr$(13) & Chr$(10)
|
||||
End Sub
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user