Compare commits

..

3 Commits

Author SHA1 Message Date
David Taylor
a93f337d17 Fixed random line break 2016-08-09 20:57:48 +01:00
Tom Price
bd0832f284 Revert changes that crossed over from feature/ember branch 2016-08-09 19:48:47 +01:00
Tom Price
567f899a39 Reformat all the things
Python code is now formatted to PEP8

All other files are defined in .editorconfig as far as possible.
2016-08-09 19:42:39 +01:00
153 changed files with 4794 additions and 7298 deletions

29
.editorconfig Normal file
View 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
.gitignore vendored
View File

@@ -53,7 +53,6 @@ coverage.xml
# Django stuff:
*.log
db.sqlite3
# Sphinx documentation
docs/_build/

2
.idea/modules.xml generated
View File

@@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pyrigs.iml" filepath="$PROJECT_DIR$/.idea/pyrigs.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/PyRIGS.iml" filepath="$PROJECT_DIR$/.idea/PyRIGS.iml" />
</modules>
</component>
</project>

View File

@@ -1,32 +1,21 @@
language: python
python:
"3.6"
cache: pip
"2.7"
addons:
chrome: stable
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:
- wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- export PATH=$PATH:$(pwd)
- chmod +x chromedriver
- pip install -r requirements.txt
- pip install coveralls codeclimate-test-reporter pep8
- pip install coveralls codeclimate-test-reporter
before_script:
- export PATH=$PATH:/usr/lib/chromium-browser/
- python manage.py collectstatic --noinput
script:
- pep8 . --exclude=migrations,importer*
- python manage.py check
- python manage.py makemigrations --check --dry-run
- coverage run manage.py test --verbosity=2
- coverage run manage.py test RIGS
after_success:
- coveralls
- codeclimate-test-reporter
notifications:
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify

View File

@@ -1,12 +0,0 @@
FROM python:3.6
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt && \
python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

View File

@@ -1,19 +1,15 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from RIGS import models
from django.shortcuts import render_to_response
from django.template import RequestContext
def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
def user_passes_test_with_403(test_func, login_url=None):
"""
Decorator for views that checks that the user passes the given test.
Anonymous users will be redirected to login_url, while users that fail
the test will be given a 403 error.
If embed_view is set, then a JS redirect will be used, and a application/json+oembed
meta tag set with the url of oembed_view
(oembed_view will be passed the kwargs from the main function)
"""
if not login_url:
from django.conf import settings
@@ -23,31 +19,32 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
def _checklogin(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
elif not request.user.is_authenticated:
if oembed_view is not None:
context = {}
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
resp = render(request, 'login_redirect.html', context=context)
return resp
else:
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
elif not request.user.is_authenticated():
return HttpResponseRedirect('%s?%s=%s' % (
login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
else:
resp = render(request, '403.html')
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, oembed_view=None):
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, oembed_view=oembed_view)
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):
@@ -56,12 +53,14 @@ def api_key_required(function):
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(request, '403.html')
error_resp = render_to_response(
'403.html', context_instance=RequestContext(request))
error_resp.status_code = 403
if key is None:
@@ -77,18 +76,5 @@ def api_key_required(function):
if user_object.api_key != key:
return error_resp
return function(request, *args, **kwargs)
return wrap
def nottinghamtec_address_required(function):
"""
Checks that the current user has an email address ending @nottinghamtec.co.uk
"""
def wrap(request, *args, **kwargs):
# Fail if current user's email address isn't @nottinghamtec.co.uk
if not request.user.email.endswith('@nottinghamtec.co.uk'):
error_resp = render(request, 'RIGS/eventauthorisation_request_error.html')
return error_resp
return function(request, *args, **kwargs)
return wrap

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals
DATETIME_FORMAT = ('d/m/Y H:i')
DATE_FORMAT = ('d/m/Y')

View File

@@ -10,8 +10,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import raven
import secrets
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -25,19 +23,19 @@ SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
# 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
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')
if DEBUG:
ALLOWED_HOSTS.append('localhost')
ALLOWED_HOSTS.append('example.com')
ALLOWED_HOSTS.append('127.0.0.1')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG:
SECURE_SSL_REDIRECT = True # Redirect all http requests to https
@@ -67,15 +65,15 @@ INSTALLED_APPS = (
'raven.contrib.django.raven_compat',
)
MIDDLEWARE = (
MIDDLEWARE_CLASSES = (
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
'django.middleware.security.SecurityMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
@@ -146,25 +144,28 @@ LOGGING = {
}
}
import raven
RAVEN_CONFIG = {
'dsn': os.environ.get('RAVEN_DSN'),
# 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
AUTH_USER_MODEL = 'RIGS.Profile'
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/user/login/'
LOGOUT_URL = '/user/logout/'
LOGIN_URL = '/user/login'
LOGOUT_URL = '/user/logout'
ACCOUNT_ACTIVATION_DAYS = 7
# reCAPTCHA settings
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', None)
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', None)
NOCAPTCHA = True
# Email
@@ -172,7 +173,7 @@ EMAILER_TEST = False
if not DEBUG or EMAILER_TEST:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25))
EMAIL_PORT = int(os.environ.get('EMAIL_PORT'))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0)))
@@ -198,6 +199,17 @@ USE_TZ = True
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.core.context_processors.request",
"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'
@@ -207,34 +219,10 @@ STATIC_DIRS = (
os.path.join(BASE_DIR, 'static/')
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
'debug': DEBUG
},
},
]
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
USE_GRAVATAR = True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
'RISK_ASSESSMENT_URL') else "http://example.com"
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)

View File

@@ -1,29 +1,25 @@
from django.conf.urls import include, url
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 = [
# Examples:
# url(r'^$', 'PyRIGS.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'PyRIGS.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^', include('RIGS.urls')),
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')),
url(r'^', include('RIGS.urls')),
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')),
url(r'^admin/', admin.site.urls),
]
url(r'^admin/', include(admin.site.urls)),
)
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns()
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns

View File

@@ -7,9 +7,10 @@ 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 # noqa
from dj_static import Cling # noqa
from django.core.wsgi import get_wsgi_application
from dj_static import Cling
application = Cling(get_wsgi_application())

View File

@@ -1,8 +1,6 @@
# TEC PA & Lighting - PyRIGS #
[![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=develop)](https://travis-ci.org/nottinghamtec/PyRIGS)
[![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg?branch=develop)](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop)
[![Dependency Status](https://gemnasium.com/badges/github.com/nottinghamtec/PyRIGS.svg)](https://gemnasium.com/github.com/nottinghamtec/PyRIGS)
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
@@ -77,13 +75,6 @@ python manage.py runserver
```
Please refer to Django documentation for a full list of options available here.
### Development using docker
```
docker build . -t pyrigs
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
```
### 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:
```
@@ -94,7 +85,6 @@ Then load the sample data using the command:
python manage.py generateSampleData
```
4 user accounts are created for convenience:
|Username |Password |
|---------|---------|
|superuser|superuser|
@@ -102,18 +92,5 @@ python manage.py generateSampleData
|keyholder|keyholder|
|basic |basic |
### Testing ###
Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`.
You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity:
```
python manage.py test
python manage.py test RIGS.test_models
python manage.py test RIGS.test_models.EventTestCase
python manage.py test RIGS.test_models.EventTestCase.test_current_events
```
### Committing, pushing and testing ###
Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person.

View File

@@ -1 +0,0 @@
default_app_config = 'RIGS.apps.RIGSAppConfig'

View File

@@ -1,23 +1,21 @@
import reversion
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 _
from reversion.admin import VersionAdmin
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
from django.contrib import messages
from django.db import transaction
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 reversion import revisions as reversion
from RIGS import models, forms
# Register your models here.
admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin)
admin.site.register(models.VatRate, 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)
@@ -43,7 +41,7 @@ class ProfileAdmin(UserAdmin):
add_form = forms.ProfileCreationForm
class AssociateAdmin(VersionAdmin):
class AssociateAdmin(reversion.VersionAdmin):
list_display = ('id', 'name', 'number_of_events')
search_fields = ['id', 'name']
list_display_links = ['id', 'name']
@@ -52,7 +50,8 @@ class AssociateAdmin(VersionAdmin):
merge_fields = ['name']
def get_queryset(self, request):
return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event'))
return super(AssociateAdmin, self).get_queryset(
request).annotate(event_count=Count('event'))
def number_of_events(self, obj):
return obj.latest_events.count()
@@ -60,12 +59,16 @@ class AssociateAdmin(VersionAdmin):
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?
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)
self.message_user(
request,
"An error occured. Did you select a 'master' record?",
level=messages.ERROR)
return
with transaction.atomic(), reversion.create_revision():
@@ -81,6 +84,7 @@ class AssociateAdmin(VersionAdmin):
else: # Present the confirmation screen
class TempForm(ModelForm):
class Meta:
model = queryset.model
fields = self.merge_fields
@@ -95,7 +99,8 @@ class AssociateAdmin(VersionAdmin):
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'forms': forms
}
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context)
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context,
current_app=self.admin_site.name)
@admin.register(models.Person)
@@ -107,10 +112,22 @@ class PersonAdmin(AssociateAdmin):
@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']
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']
merge_fields = [
'name',
'phone',
'email',
'address',
'notes',
'union_account']

View File

@@ -1,8 +0,0 @@
from django.apps import AppConfig
class RIGSAppConfig(AppConfig):
name = 'RIGS'
def ready(self):
import RIGS.signals

View File

@@ -1,22 +1,20 @@
import cStringIO as StringIO
import datetime
import re
from django.contrib import messages
from django.urls import reverse_lazy
from django.core.urlresolvers import reverse_lazy
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
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.views import generic
from django.db.models import Q
from z3c.rml import rml2pdf
from RIGS import models
from django import forms
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
class InvoiceIndex(generic.ListView):
model = models.Invoice
@@ -32,7 +30,8 @@ class InvoiceIndex(generic.ListView):
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\", " \
@@ -57,8 +56,8 @@ class InvoicePrint(generic.View):
invoice = get_object_or_404(models.Invoice, pk=pk)
object = invoice.event
template = get_template('RIGS/event_print.xml')
context = {
copies = ('TEC', 'Client')
context = RequestContext(request, {
'object': object,
'fonts': {
'opensans': {
@@ -68,9 +67,10 @@ class InvoicePrint(generic.View):
},
'invoice': invoice,
'current_user': request.user,
}
})
rml = template.render(context)
buffer = StringIO.StringIO()
buffer = rml2pdf.parseString(rml)
@@ -79,7 +79,8 @@ 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 - N%05d | %s.pdf" % (invoice.pk, invoice.event.pk, escapedEventName)
response[
'Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName)
response.write(pdfData)
return response
@@ -93,7 +94,8 @@ 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):
@@ -102,15 +104,21 @@ class InvoiceDelete(generic.DeleteView):
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}))
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}))
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):
@@ -125,7 +133,7 @@ class InvoiceArchive(generic.ListView):
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):
@@ -144,10 +152,12 @@ class InvoiceWaiting(generic.ListView):
# @todo find a way to select items
events = self.model.objects.filter(
(
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
) & Q(invoice__isnull=True) & # Has not already been invoiced
Q(is_rig=True) # Is a rig (not non-rig)
# 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',
@@ -168,7 +178,8 @@ class InvoiceEvent(generic.View):
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):
@@ -177,7 +188,9 @@ 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))
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)

View File

@@ -1,44 +1,42 @@
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
__author__ = 'Ghost'
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
# Override the django form defaults to use the HTML date/time/datetime UI elements
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
forms.TimeField.widget = forms.TextInput(attrs={'type': 'time'})
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'})
# Registration
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
captcha = ReCaptchaField()
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']
# Embedded Login form - remove the autofocus
class EmbeddedAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.pop('autofocus', None)
# Login form
class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha')
@@ -55,9 +53,14 @@ class ProfileChangeForm(UserChangeForm):
# Events Shit
class EventForm(forms.ModelForm):
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(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()
@@ -99,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
@@ -150,32 +154,4 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
'purchase_order', 'collector']
class BaseClientEventAuthorisationForm(forms.ModelForm):
tos = forms.BooleanField(required=True, label="Terms of hire")
name = forms.CharField(label="Your Name")
def clean(self):
if self.cleaned_data.get('amount') != self.instance.event.total:
self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).')
return super(BaseClientEventAuthorisationForm, self).clean()
class Meta:
abstract = True
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
def __init__(self, **kwargs):
super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
self.fields['uni_id'].required = True
self.fields['account_code'].required = True
class Meta:
model = models.EventAuthorisation
fields = ('tos', 'name', 'amount', 'uni_id', 'account_code')
class EventAuthorisationRequestForm(forms.Form):
email = forms.EmailField(required=True, label='Authoriser Email')
'collector', 'purchase_order']

View File

@@ -1,12 +1,11 @@
from RIGS import models, forms
from django_ical.views import ICalFeed
from django.db.models import Q
from django.urls 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
class CalendarICS(ICalFeed):
@@ -34,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
@@ -50,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)
@@ -61,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 = ''
@@ -99,7 +106,8 @@ class CalendarICS(ICalFeed):
return item.earliest_time
def item_end_datetime(self, item):
if type(item.latest_time) == 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
@@ -117,19 +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:
@@ -137,7 +152,7 @@ class CalendarICS(ICalFeed):
# if item.notes: // Need to add proper keyholder checks before this gets put back
# desc += 'Notes:\n'+item.notes+'\n\n'
base_url = "https://rigs.nottinghamtec.co.uk"
base_url = "http://rigs.nottinghamtec.co.uk"
desc += 'URL = ' + base_url + str(item.get_absolute_url())
return desc

View File

@@ -1,11 +1,11 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group, Permission
from django.db import transaction
from reversion import revisions as reversion
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
@@ -27,7 +27,8 @@ class Command(BaseCommand):
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
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')
@@ -45,8 +46,18 @@ class Command(BaseCommand):
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", # noqa
"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"] # noqa
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))
@@ -68,8 +79,32 @@ class Command(BaseCommand):
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", # noqa
"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"] # noqa
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))
@@ -93,8 +128,17 @@ class Command(BaseCommand):
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", # noqa
"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"] # noqa
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))
@@ -121,8 +165,14 @@ class Command(BaseCommand):
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"]
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))
@@ -131,9 +181,11 @@ class Command(BaseCommand):
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"]
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],
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:
@@ -143,19 +195,23 @@ class Command(BaseCommand):
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 = 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",
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",
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')
@@ -167,20 +223,31 @@ class Command(BaseCommand):
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!"]
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}]
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
@@ -218,7 +285,8 @@ class Command(BaseCommand):
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.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
@@ -249,4 +317,5 @@ class Command(BaseCommand):
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())
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
date=datetime.date.today())

View File

@@ -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',

View File

@@ -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'),
]
@@ -18,7 +17,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('postedAt', models.DateTimeField(auto_now=True)),
('message', models.TextField()),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
},

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0002_modelcomment_person'),
]

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import RIGS.models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0003_auto_20141031_0219'),
]

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0004_organisation'),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import RIGS.models

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
@@ -33,11 +33,11 @@ class Migration(migrations.Migration):
('payment_method', models.CharField(blank=True, null=True, max_length=255)),
('payment_received', models.CharField(blank=True, null=True, max_length=255)),
('purchase_order', models.CharField(blank=True, null=True, max_length=255)),
('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events', on_delete=models.CASCADE)),
('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', on_delete=models.CASCADE)),
('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', on_delete=models.CASCADE)),
('organisation', models.ForeignKey(to='RIGS.Organisation', on_delete=models.CASCADE)),
('person', models.ForeignKey(to='RIGS.Person', on_delete=models.CASCADE)),
('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events')),
('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in')),
('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic')),
('organisation', models.ForeignKey(to='RIGS.Organisation')),
('person', models.ForeignKey(to='RIGS.Person')),
],
options={
},
@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
('quantity', models.IntegerField()),
('cost', models.DecimalField(max_digits=10, decimal_places=2)),
('order', models.IntegerField()),
('event', models.ForeignKey(to='RIGS.Event', related_name='item', on_delete=models.CASCADE)),
('event', models.ForeignKey(to='RIGS.Event', related_name='item')),
],
options={
},
@@ -75,7 +75,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='event',
name='venue',
field=models.ForeignKey(to='RIGS.Venue', on_delete=models.CASCADE),
field=models.ForeignKey(to='RIGS.Venue'),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
@@ -14,26 +14,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='based_on',
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True, on_delete=models.CASCADE),
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='event',
name='checked_in_by',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True,
null=True, on_delete=models.CASCADE),
null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='event',
name='mic',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, on_delete=models.CASCADE),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='event',
name='organisation',
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True, on_delete=models.CASCADE),
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True),
preserve_default=True,
),
migrations.AlterField(

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
('run', models.BooleanField(default=False)),
('derig', models.BooleanField(default=False)),
('notes', models.TextField(blank=True, null=True)),
('event', models.ForeignKey(related_name='crew', to='RIGS.Event', on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('event', models.ForeignKey(related_name='crew', to='RIGS.Event')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
},
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='eventitem',
name='event',
field=models.ForeignKey(related_name='items', to='RIGS.Event', on_delete=models.CASCADE),
field=models.ForeignKey(related_name='items', to='RIGS.Event'),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0012_auto_20141106_0253'),
]
@@ -14,7 +13,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='person',
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person', on_delete=models.CASCADE),
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person'),
preserve_default=True,
),
]

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0013_auto_20141202_0041'),
]
@@ -14,13 +13,13 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='venue',
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True, on_delete=models.CASCADE),
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='eventitem',
name='event',
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event', on_delete=models.CASCADE),
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event'),
preserve_default=True,
),
]

View File

@@ -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'),
]

View File

@@ -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'),
]
@@ -18,7 +17,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('invoice_date', models.DateField(auto_now_add=True)),
('void', models.BooleanField()),
('event', models.OneToOneField(to='RIGS.Event', on_delete=models.CASCADE)),
('event', models.OneToOneField(to='RIGS.Event')),
],
options={
},
@@ -30,8 +29,10 @@ 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')])),
('invoice', models.ForeignKey(to='RIGS.Invoice', on_delete=models.CASCADE)),
('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, on_delete=models.CASCADE),
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True,
to=settings.AUTH_USER_MODEL, null=True),
preserve_default=True,
),
]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
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,
),
]

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
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,
),
]

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0020_auto_20150303_0243'),
]

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0021_auto_20150420_1155'),
]

View File

@@ -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',

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0023_auto_20150529_0048'),
]
@@ -15,6 +14,7 @@ class Migration(migrations.Migration):
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),
field=models.ForeignKey(related_name='future_events', on_delete=django.db.models.deletion.SET_NULL,
blank=True, to='RIGS.Event', null=True),
),
]

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-03-31 12:02
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0024_auto_20160229_2042'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
migrations.AlterField(
model_name='vatrate',
name='start_at',
field=models.DateField(),
),
]

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0024_auto_20160229_2042'),
]
operations = [
migrations.CreateModel(
name='EventAuthorisation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('email', models.EmailField(max_length=254)),
('name', models.CharField(max_length=255)),
('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)),
('account_code', models.CharField(max_length=50, null=True, blank=True)),
('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)),
('created_at', models.DateTimeField(auto_now_add=True)),
('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event', on_delete=models.CASCADE)),
],
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-10 17:46
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0025_auto_20160331_1302'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
),
]

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0025_eventauthorisation'),
]
operations = [
migrations.RemoveField(
model_name='eventauthorisation',
name='created_at',
),
]

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0026_remove_eventauthorisation_created_at'),
]
operations = [
migrations.AlterField(
model_name='eventauthorisation',
name='event',
field=models.OneToOneField(related_name='authorisation', to='RIGS.Event', on_delete=models.CASCADE),
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0027_eventauthorisation_event_singular'),
]
operations = [
migrations.AddField(
model_name='eventauthorisation',
name='sent_by',
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
preserve_default=False,
),
]

View File

@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0029_eventauthorisation_sent_by'),
]
operations = [
migrations.AddField(
model_name='event',
name='auth_request_at',
field=models.DateTimeField(null=True, blank=True),
),
migrations.AddField(
model_name='event',
name='auth_request_by',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='event',
name='auth_request_to',
field=models.EmailField(max_length=254, null=True, blank=True),
),
]

View File

@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-12 20:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0030_auth_request_sending'),
('RIGS', '0026_auto_20170510_1846'),
]
operations = [
]

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-04 22:55
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('reversion', '0001_squashed_0004_auto_20160611_1202'),
('RIGS', '0031_merge_20170512_2102'),
]
operations = [
migrations.CreateModel(
name='RIGSVersion',
fields=[
],
options={
'indexes': [],
'proxy': True,
},
bases=('reversion.version',),
),
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='collected by'),
),
migrations.AlterField(
model_name='event',
name='mic',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='event_mic', to=settings.AUTH_USER_MODEL, verbose_name='MIC'),
),
migrations.AlterField(
model_name='event',
name='purchase_order',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='PO'),
),
migrations.AlterField(
model_name='eventauthorisation',
name='amount',
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='authorisation amount'),
),
migrations.AlterField(
model_name='eventauthorisation',
name='uni_id',
field=models.CharField(blank=True, max_length=10, null=True, verbose_name='University ID'),
),
migrations.AlterField(
model_name='payment',
name='amount',
field=models.DecimalField(decimal_places=2, help_text='Please use ex. VAT', max_digits=10),
),
migrations.AlterField(
model_name='payment',
name='method',
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], max_length=2, null=True),
),
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.0.3 on 2018-03-25 00:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0032_auto_20170904_2355'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.0.5 on 2019-07-28 21:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0033_auto_20180325_0016'),
]
operations = [
migrations.AddField(
model_name='event',
name='risk_assessment_edit_url',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -1,32 +1,35 @@
import datetime
import hashlib
import datetime
import pytz
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.encoding import python_2_unicode_compatible
from reversion import revisions as reversion
from reversion.models import Version
import string
import random
import string
from collections import Counter
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.urls import reverse_lazy
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):
@@ -39,7 +42,8 @@ class Profile(AbstractUser):
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.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
url = "https://www.gravatar.com/avatar/" + \
hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
return url
@property
@@ -51,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
@@ -63,31 +68,32 @@ class Profile(AbstractUser):
class RevisionMixin(object):
@property
def current_version(self):
version = Version.objects.get_for_object(self).select_related('revision').first()
return version
@property
def last_edited_at(self):
version = self.current_version
if version is None:
versions = reversion.get_for_object(self)
if versions:
version = reversion.get_for_object(self)[0]
return version.revision.date_created
else:
return None
return version.revision.date_created
@property
def last_edited_by(self):
version = self.current_version
if version is None:
versions = reversion.get_for_object(self)
if versions:
version = reversion.get_for_object(self)[0]
return version.revision.user
else:
return None
return version.revision.user
@property
def current_version_id(self):
version = self.current_version
if version is None:
versions = reversion.get_for_object(self)
if versions:
version = reversion.get_for_object(self)[0]
return "V{0} | R{1}".format(version.pk, version.revision.pk)
else:
return None
return "V{0} | R{1}".format(version.pk, version.revision.pk)
@reversion.register
@@ -111,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)
@@ -122,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})
@@ -155,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)
@@ -166,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})
@@ -179,7 +189,7 @@ class Organisation(models.Model, RevisionMixin):
class VatManager(models.Manager):
def current_rate(self):
return self.find_rate(timezone.now())
return self.find_rate(datetime.datetime.now())
def find_rate(self, date):
# return self.filter(startAt__lte=date).latest()
@@ -194,7 +204,7 @@ class VatManager(models.Manager):
@reversion.register
@python_2_unicode_compatible
class VatRate(models.Model, RevisionMixin):
start_at = models.DateField()
start_at = models.DateTimeField()
rate = models.DecimalField(max_digits=6, decimal_places=6)
comment = models.CharField(max_length=255)
@@ -209,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
@@ -231,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})
@@ -245,26 +257,43 @@ class Venue(models.Model, RevisionMixin):
class EventManager(models.Manager):
def current_events(self):
events = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~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=timezone.now().date()) # 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',
@@ -273,12 +302,12 @@ class EventManager(models.Manager):
def rig_count(self):
event_count = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False,
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False,
is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False, is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
(models.Q(dry_hire=True, start_date__gte=datetime.date.today(), is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT
@@ -302,12 +331,14 @@ class Event(models.Model, RevisionMixin):
)
name = models.CharField(max_length=255)
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
person = models.ForeignKey('Person', null=True, blank=True)
organisation = models.ForeignKey('Organisation', blank=True, null=True)
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', on_delete=models.SET_NULL, related_name='future_events', blank=True,
@@ -323,23 +354,27 @@ 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, on_delete=models.CASCADE)
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", on_delete=models.CASCADE)
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')
# Authorisation request details
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True)
# Risk assessment info
risk_assessment_edit_url = 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')
# Calculated values
"""
@@ -373,7 +408,7 @@ class Event(models.Model, RevisionMixin):
@property
def vat(self):
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
return self.sum_total * self.vat_rate.rate
"""
Inc VAT
@@ -381,7 +416,7 @@ class Event(models.Model, RevisionMixin):
@property
def total(self):
return Decimal(self.sum_total + self.vat).quantize(Decimal('.01'))
return self.sum_total + self.vat
@property
def cancelled(self):
@@ -391,10 +426,6 @@ class Event(models.Model, RevisionMixin):
def confirmed(self):
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
@property
def authorised(self):
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
@property
def has_start_time(self):
return self.start_time is not None
@@ -419,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
@@ -429,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:
@@ -455,26 +489,24 @@ class Event(models.Model, RevisionMixin):
else:
return endDate
@property
def internal(self):
return self.organisation and self.organisation.union_account
objects = EventManager()
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
def __str__(self):
return str(self.pk) + ": " + self.name
return unicode(self.pk) + ": " + self.name
def clean(self):
if self.end_date and self.start_date > self.end_date:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
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."""
@@ -488,7 +520,7 @@ class Event(models.Model, RevisionMixin):
class EventItem(models.Model):
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
event = models.ForeignKey('Event', related_name='items', blank=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
quantity = models.IntegerField()
@@ -503,39 +535,22 @@ 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):
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
event = models.ForeignKey('Event', related_name='crew')
user = models.ForeignKey(settings.AUTH_USER_MODEL)
rig = models.BooleanField(default=False)
run = models.BooleanField(default=False)
derig = models.BooleanField(default=False)
notes = models.TextField(blank=True, null=True)
@reversion.register
class EventAuthorisation(models.Model, RevisionMixin):
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
email = models.EmailField()
name = models.CharField(max_length=255)
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
account_code = models.CharField(max_length=50, blank=True, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
sent_by = models.ForeignKey('RIGS.Profile', on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
@property
def activity_feed_string(self):
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
@python_2_unicode_compatible
class Invoice(models.Model):
event = models.OneToOneField('Event', on_delete=models.CASCADE)
event = models.OneToOneField('Event')
invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False)
@@ -587,10 +602,17 @@ class Payment(models.Model):
(ADJUSTMENT, 'TEC Adjustment'),
)
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
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)

View File

@@ -1,6 +1,4 @@
from RIGS.models import Profile
from RIGS.forms import ProfileRegistrationFormUniqueEmail
from registration.signals import user_registered
def user_created(sender, user, request, **kwargs):
@@ -12,4 +10,6 @@ def user_created(sender, user, request, **kwargs):
user.save()
from registration.signals import user_registered
user_registered.connect(user_created)

View File

@@ -1,34 +1,23 @@
import cStringIO as StringIO
import copy
import datetime
import re
import urllib2
from io import BytesIO
import urllib.request
import urllib.error
import urllib.parse
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.views import generic
from django.urls import reverse_lazy
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.urls import reverse
from django.core import signing
from django.http import HttpResponse
from django.core.exceptions import SuspiciousOperation
from django.db.models import Q
from django.contrib import messages
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views import generic
from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson
import premailer
from RIGS import models, forms
from PyRIGS import decorators
import datetime
import re
import copy
__author__ = 'ghost'
@@ -59,46 +48,6 @@ class EventDetail(generic.DetailView):
model = models.Event
class EventOembed(generic.View):
model = models.Event
def get(self, request, pk=None):
embed_url = reverse('event_embed', args=[pk])
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
data = {
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
'version': '1.0',
'type': 'rich',
'height': '250'
}
json = simplejson.JSONEncoderForHTML().encode(data)
return HttpResponse(json, content_type="application/json")
class EventEmbed(EventDetail):
template_name = 'RIGS/event_embed.html'
class EventRA(generic.base.RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
event = get_object_or_404(models.Event, pk=kwargs['pk'])
if event.risk_assessment_edit_url:
return event.risk_assessment_edit_url
params = {
'entry.708610078': f'N{event.pk:05}',
'entry.905899507': event.name,
'entry.139491562': event.venue.name if event.venue else '',
'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''),
'entry.902421165': event.mic.name if event.mic else ''
}
return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params)
class EventCreate(generic.CreateView):
model = models.Event
form_class = forms.EventForm
@@ -110,10 +59,13 @@ 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.
for field, model in form.related_models.items():
# 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 != '':
context[field] = model.objects.get(pk=value)
@@ -133,19 +85,12 @@ 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.
for field, model in form.related_models.items():
# 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 != '':
context[field] = model.objects.get(pk=value)
# If this event has already been emailed to a client, show a warning
if self.object.auth_request_at is not None:
messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
if hasattr(self.object, 'authorised'):
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
return context
def get_success_url(self):
@@ -154,25 +99,18 @@ class EventUpdate(generic.UpdateView):
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
new.purchase_order = None # Remove old PO
# Clear checked in by if it's a dry hire
if new.dry_hire is True:
new.checked_in_by = None
# Remove all the authorisation information from the new event
new.auth_request_to = None
new.auth_request_by = None
new.auth_request_at = None
if self.request.method in (
'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
'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
else:
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
@@ -186,29 +124,37 @@ class EventPrint(generic.View):
def get(self, request, pk):
object = get_object_or_404(models.Event, pk=pk)
template = get_template('RIGS/event_print.xml')
copies = ('TEC', 'Client')
merger = PdfFileMerger()
context = {
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'quote': True,
'current_user': request.user,
}
for copy in copies:
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'copy': copy,
'current_user': request.user,
})
rml = template.render(context)
# context['copy'] = copy # this is the way to do it once we upgrade
# to Django 1.8.3
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
rml = template.render(context)
buffer = StringIO.StringIO()
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
merger.append(BytesIO(terms.read()))
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
merger.append(StringIO.StringIO(terms.read()))
merged = BytesIO()
merger.write(merged)
@@ -217,7 +163,8 @@ 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
@@ -255,176 +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
class EventAuthorise(generic.UpdateView):
template_name = 'RIGS/eventauthorisation_form.html'
success_template = 'RIGS/eventauthorisation_success.html'
def form_valid(self, form):
self.object = form.save()
self.template_name = self.success_template
messages.add_message(self.request, messages.SUCCESS,
'Success! Your event has been authorised. ' +
'You will also receive email confirmation to %s.' % (self.object.email))
return self.render_to_response(self.get_context_data())
@property
def event(self):
return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk'])
def get_object(self, queryset=None):
return getattr(self.event, 'authorisation', None)
def get_form_class(self):
return forms.InternalClientEventAuthorisationForm
def get_context_data(self, **kwargs):
context = super(EventAuthorise, self).get_context_data(**kwargs)
context['event'] = self.event
context['tos_url'] = settings.TERMS_OF_HIRE_URL
return context
def get(self, request, *args, **kwargs):
if self.get_object() is not None and self.get_object().pk is not None:
if self.event.authorised:
messages.add_message(self.request, messages.WARNING,
"This event has already been authorised. "
"Reauthorising is not necessary at this time.")
else:
messages.add_message(self.request, messages.WARNING,
"This event has already been authorised, but the amount has changed. " +
"Please check the amount and reauthorise.")
return super(EventAuthorise, self).get(request, *args, **kwargs)
def get_form(self, **kwargs):
form = super(EventAuthorise, self).get_form(**kwargs)
form.instance.event = self.event
form.instance.email = self.request.email
form.instance.sent_by = self.request.sent_by
return form
def dispatch(self, request, *args, **kwargs):
# Verify our signature matches up and all is well with the integrity of the URL
try:
data = signing.loads(kwargs.get('hmac'))
assert int(kwargs.get('pk')) == int(data.get('pk'))
request.email = data['email']
request.sent_by = models.Profile.objects.get(pk=data['sent_by'])
except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist):
raise SuspiciousOperation(
"This URL is invalid. Please ask your TEC contact for a new URL")
return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
model = models.Event
form_class = forms.EventAuthorisationRequestForm
template_name = 'RIGS/eventauthorisation_request.html'
@method_decorator(decorators.nottinghamtec_address_required)
def dispatch(self, *args, **kwargs):
return super(EventAuthorisationRequest, self).dispatch(*args, **kwargs)
@property
def object(self):
return self.get_object()
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
messages.info(self.request, "location.reload()")
else:
url = reverse_lazy('event_detail', kwargs={
'pk': self.object.pk,
})
messages.add_message(self.request, messages.SUCCESS, "Authorisation request successfully sent.")
return url
def form_valid(self, form):
email = form.cleaned_data['email']
event = self.object
event.auth_request_by = self.request.user
event.auth_request_at = datetime.datetime.now()
event.auth_request_to = email
event.save()
context = {
'object': self.object,
'request': self.request,
'hmac': signing.dumps({
'pk': self.object.pk,
'email': email,
'sent_by': self.request.user.pk,
}),
}
if event.person is not None and email == event.person.email:
context['to_name'] = event.person.name
elif event.organisation is not None and email == event.organisation.email:
context['to_name'] = event.organisation.name
msg = EmailMultiAlternatives(
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
to=[email],
reply_to=[self.request.user.email],
)
css = staticfiles_storage.path('css/email.css')
html = premailer.Premailer(get_template("RIGS/eventauthorisation_client_request.html").render(context),
external_styles=css).transform()
msg.attach_alternative(html, 'text/html')
msg.send()
return super(EventAuthorisationRequest, self).form_valid(form)
class EventAuthoriseRequestEmailPreview(generic.DetailView):
template_name = "RIGS/eventauthorisation_client_request.html"
model = models.Event
def render_to_response(self, context, **response_kwargs):
from django.contrib.staticfiles.storage import staticfiles_storage
css = staticfiles_storage.path('css/email.css')
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
assert isinstance(response, HttpResponse)
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
return response
def get_context_data(self, **kwargs):
context = super(EventAuthoriseRequestEmailPreview, self).get_context_data(**kwargs)
context['hmac'] = signing.dumps({
'pk': self.object.pk,
'email': self.request.GET.get('email', 'hello@world.test'),
'sent_by': self.request.user.pk,
})
context['to_name'] = self.request.GET.get('to_name', None)
return context
@method_decorator(csrf_exempt, name='dispatch')
class LogRiskAssessment(generic.View):
http_method_names = ["post"]
def post(self, request, **kwargs):
data = request.POST
shared_secret = data.get("secret")
edit_url = data.get("editUrl")
rig_number = data.get("rigNum")
if shared_secret is None or edit_url is None or rig_number is None:
return HttpResponse(status=422)
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
return HttpResponse(status=403)
rig_number = int(re.sub("[^0-9]", "", rig_number))
event = get_object_or_404(models.Event, pk=rig_number)
event.risk_assessment_edit_url = edit_url
event.save()
return HttpResponse(status=200)

View File

@@ -1,104 +0,0 @@
import re
import urllib.request
import urllib.error
import urllib.parse
from io import BytesIO
from django.db.models.signals import post_save
from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.template.loader import get_template
from premailer import Premailer
from z3c.rml import rml2pdf
from RIGS import models
def send_eventauthorisation_success_email(instance):
# Generate PDF first to prevent context conflicts
context = {
'object': instance.event,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'receipt': True,
'current_user': False,
}
template = get_template('RIGS/event_print.xml')
merger = PdfFileMerger()
rml = template.render(context)
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
merger.append(BytesIO(terms.read()))
merged = BytesIO()
merger.write(merged)
# Produce email content
context = {
'object': instance,
}
if instance.event.person is not None and instance.email == instance.event.person.email:
context['to_name'] = instance.event.person.name
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
context['to_name'] = instance.event.organisation.name
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
client_email = EmailMultiAlternatives(
subject,
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
to=[instance.email],
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
)
css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context),
external_styles=css).transform()
client_email.attach_alternative(html, 'text/html')
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name)
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
merged.getvalue(),
'application/pdf'
)
if instance.event.mic:
mic_email_address = instance.event.mic.email
else:
mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS
mic_email = EmailMessage(
subject,
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
to=[mic_email_address]
)
# Now we have both emails successfully generated, send them out
client_email.send(fail_silently=True)
mic_email.send(fail_silently=True)
# Set event to booked now that it's authorised
instance.event.status = models.Event.BOOKED
instance.event.save()
def on_revision_commit(sender, instance, created, **kwargs):
if created:
send_eventauthorisation_success_email(instance)
post_save.connect(on_revision_commit, sender=models.EventAuthorisation)

12
RIGS/static/css/bootstrap-select.min.css vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
body{margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

12
RIGS/static/js/affix.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: affix.js v3.3.7
* Bootstrap: affix.js v3.3.2
* http://getbootstrap.com/javascript/#affix
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -21,14 +21,14 @@
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element)
this.affixed = null
this.unpin = null
this.affixed =
this.unpin =
this.pinnedOffset = null
this.checkPosition()
}
Affix.VERSION = '3.3.7'
Affix.VERSION = '3.3.2'
Affix.RESET = 'affix affix-top affix-bottom'
@@ -78,7 +78,7 @@
var offset = this.options.offset
var offsetTop = offset.top
var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height())
var scrollHeight = $('body').height()
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)

84
RIGS/static/js/ajax-bootstrap-select.js Normal file → Executable file
View File

@@ -3,16 +3,16 @@
*
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
*
* @version 1.4.1
* @version 1.3.1
* @author Adam Heim - https://github.com/truckingsim
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
* @copyright 2017 Adam Heim
* @copyright 2015 Adam Heim
* @license Released under the MIT license.
*
* Contributors:
* Mark Carver - https://github.com/markcarver
*
* Last build: 2017-07-21 1:08:54 PM GMT-0400
* Last build: 2015-01-06 8:43:11 PM EST
*/
!(function ($, window) {
@@ -186,25 +186,10 @@ var AjaxBootstrapSelect = function (element, options) {
if (dataKeys.length) {
// Object containing the data attribute options.
var dataOptions = {};
var flattenedOptions = ['locale'];
for (i = 0, l = dataKeys.length; i < l; i++) {
var name = dataKeys[i].replace(/^abs([A-Z])/, matchToLowerCase).replace(/([A-Z])/g, '-$1').toLowerCase();
var keys = name.split('-');
// Certain options should be flattened to a single object
// and not fully expanded (such as Locale).
if (keys[0] && keys.length > 1 && flattenedOptions.indexOf(keys[0]) !== -1) {
var newKeys = [keys.shift()];
var property = '';
// Combine the remaining keys as a single property.
for (var ii = 0; ii < keys.length; ii++) {
property += (ii === 0 ? keys[ii] : keys[ii].charAt(0).toUpperCase() + keys[ii].slice(1));
}
newKeys.push(property);
keys = newKeys;
}
this.log(this.LOG_DEBUG, 'Processing data attribute "data-abs-' + name + '":', data[dataKeys[i]]);
expandObject(keys, data[dataKeys[i]], dataOptions);
expandObject(name.split('-'), data[dataKeys[i]], dataOptions);
}
this.options = $.extend(true, {}, this.options, dataOptions);
this.log(this.LOG_DEBUG, 'Merged in the data attribute options: ', dataOptions, this.options);
@@ -330,12 +315,6 @@ AjaxBootstrapSelect.prototype.init = function () {
plugin.log(plugin.LOG_DEBUG, 'Key ignored.');
return;
}
// Don't process if below minimum query length
if (query.length < plugin.options.minLength) {
plugin.list.setStatus(plugin.t('statusTooShort'));
return;
}
// Clear out any existing timer.
clearTimeout(requestDelayTimer);
@@ -594,25 +573,8 @@ var AjaxBootstrapSelectList = function (plugin) {
this.title = null;
this.selectedTextFormat = plugin.selectpicker.options.selectedTextFormat;
// Save initial options
var initial_options = [];
plugin.$element.find('option').each(function() {
var $option = $(this);
var value = $option.attr('value');
initial_options.push({
value: value,
text: $option.text(),
'class': $option.attr('class') || '',
data: $option.data() || {},
preserved: plugin.options.preserveSelected,
selected: !!$option.attr('selected')
});
});
this.cacheSet(/*query=*/'', initial_options);
// Preserve selected options.
if (plugin.options.preserveSelected) {
that.selected = initial_options;
plugin.$element.on('change.abs.preserveSelected', function (e) {
var $selected = plugin.$element.find(':selected');
that.selected = [];
@@ -682,7 +644,7 @@ AjaxBootstrapSelectList.prototype.build = function (data) {
}
// Set various properties.
$option.val(item.value).text(item.text).attr('title', item.text);
$option.val(item.value).text(item.text);
if (item['class'].length) {
$option.attr('class', item['class']);
}
@@ -770,13 +732,7 @@ AjaxBootstrapSelectList.prototype.refresh = function (triggerChange) {
if (!this.plugin.$element.find('option').length && emptyTitle && emptyTitle.length) {
this.setTitle(emptyTitle);
}
else if (
this.title ||
(
this.selectedTextFormat !== 'static' &&
this.selectedTextFormat !== this.plugin.selectpicker.options.selectedTextFormat
)
) {
else if (this.title) {
this.restoreTitle();
}
this.plugin.selectpicker.refresh();
@@ -1268,14 +1224,6 @@ $.fn.ajaxSelectPicker.defaults = {
}
},
/**
* @member $.fn.ajaxSelectPicker.defaults
* @cfg {Number} minLength = 0
* @markdown
* Invoke a request for empty search values.
*/
minLength: 0,
/**
* @member $.fn.ajaxSelectPicker.defaults
* @cfg {String} ajaxSearchUrl
@@ -1348,7 +1296,8 @@ $.fn.ajaxSelectPicker.defaults = {
* 39: "right",
* 38: "up",
* 40: "down",
* 91: "meta"
* 91: "meta",
* 229: "unknown"
* }
* ```
*/
@@ -1362,7 +1311,8 @@ $.fn.ajaxSelectPicker.defaults = {
39: "right",
38: "up",
40: "down",
91: "meta"
91: "meta",
229: "unknown"
},
/**
@@ -1456,7 +1406,7 @@ $.fn.ajaxSelectPicker.defaults = {
* }
* ```
*/
preprocessData: function () { },
preprocessData: function(){},
/**
* @member $.fn.ajaxSelectPicker.defaults
@@ -1484,7 +1434,7 @@ $.fn.ajaxSelectPicker.defaults = {
* @markdown
* Process the data returned after this plugin, but before the list is built.
*/
processData: function () { },
processData: function(){},
/**
* @member $.fn.ajaxSelectPicker.defaults
@@ -1597,15 +1547,7 @@ $.fn.ajaxSelectPicker.locale['en-US'] = {
* @markdown
* The text to use in the status container when a request is being initiated.
*/
statusSearching: 'Searching...',
/**
* @member $.fn.ajaxSelectPicker.locale
* @cfg {String} statusToShort = 'Please enter more characters'
* @markdown
* The text used in the status container when the request returns no results.
*/
statusTooShort: 'Please enter more characters'
statusSearching: 'Searching...'
};
$.fn.ajaxSelectPicker.locale.en = $.fn.ajaxSelectPicker.locale['en-US'];

8
RIGS/static/js/alert.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: alert.js v3.3.7
* Bootstrap: alert.js v3.3.2
* http://getbootstrap.com/javascript/#alerts
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -18,7 +18,7 @@
$(el).on('click', dismiss, this.close)
}
Alert.VERSION = '3.3.7'
Alert.VERSION = '3.3.2'
Alert.TRANSITION_DURATION = 150
@@ -31,7 +31,7 @@
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = $(selector === '#' ? [] : selector)
var $parent = $(selector)
if (e) e.preventDefault()

View File

@@ -1,104 +1,104 @@
$(document).ready(function() {
clearSelectionLabel = '(no selection)';
$(document).ready(function () {
clearSelectionLabel = '(no selection)';
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
function changeSelectedValue(obj, pk, text, update_url) { //Pass in JQuery object and new parameters
//console.log('Changing selected value');
obj.find('option').remove(); //Remove all the available options
obj.append( //Add the new option
$("<option></option>")
.attr("value",pk)
.text(text)
.data('update_url',update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj.change(); //Trigger the change function manually
}
$("<option></option>")
.attr("value", pk)
.text(text)
.data('update_url', update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj.change(); //Trigger the change function manually
}
function refreshUpdateHref(obj) {
//console.log('Refreshing Update URL');
targetObject = $('#'+obj.attr('id')+'-update');
update_url = $('option:selected', obj).data('update_url');
function refreshUpdateHref(obj) {
//console.log('Refreshing Update URL');
targetObject = $('#' + obj.attr('id') + '-update');
update_url = $('option:selected', obj).data('update_url');
if (update_url=="") { //Probably "clear selection" has been chosen
// console.log('Trying to disable');
targetObject.attr('disabled', true);
} else {
targetObject.attr('href', update_url);
targetObject.attr('disabled', false);
}
}
if (update_url == "") { //Probably "clear selection" has been chosen
// console.log('Trying to disable');
targetObject.attr('disabled', true);
} else {
targetObject.attr('href', update_url);
targetObject.attr('disabled', false);
}
}
$(".selectpicker").each(function() {
var options = {
ajax: {
url: $(this).data('sourceurl'),
type: 'GET',
dataType: 'json',
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
// automatically replace it with the value of the search query.
data: {
term: '{{{q}}}'
}
},
locale: {
emptyTitle: ''
},
clearOnEmpty:false,
//log: 3,
preprocessData: function (data) {
var i, l = data.length, array = [];
array.push({
text: clearSelectionLabel,
value: '',
data:{
update_url: '',
subtext:''
}
});
$(".selectpicker").each(function () {
if (l) {
for(i = 0; i < l; i++){
array.push($.extend(true, data[i], {
text: data[i]['label'],
value: data[i]['pk'],
data:{
update_url: data[i]['update'],
subtext:''
}
}));
}
}
return array;
var options = {
ajax: {
url: $(this).data('sourceurl'),
type: 'GET',
dataType: 'json',
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
// automatically replace it with the value of the search query.
data: {
term: '{{{q}}}'
}
},
locale: {
emptyTitle: ''
},
clearOnEmpty: false,
//log: 3,
preprocessData: function (data) {
var i, l = data.length, array = [];
array.push({
text: clearSelectionLabel,
value: '',
data: {
update_url: '',
subtext: ''
}
});
if (l) {
for (i = 0; i < l; i++) {
array.push($.extend(true, data[i], {
text: data[i]['label'],
value: data[i]['pk'],
data: {
update_url: data[i]['update'],
subtext: ''
}
}));
}
}
return array;
}
};
$(this).prepend($("<option></option>")
.attr("value",'')
.text(clearSelectionLabel)
.data('update_url','')); //Add "clear selection" option
.attr("value", '')
.text(clearSelectionLabel)
.data('update_url', '')); //Add "clear selection" option
$(this).selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
$(this).change(function(){ //on change, update the edit button href
// console.log('Selectbox Changed');
refreshUpdateHref($(this));
});
$(this).selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
refreshUpdateHref($(this)); //Ensure href is correct at the beginning
});
//When update/edit modal box submitted
$('#modal').on('hide.bs.modal', function (e) {
if (modaltarget != undefined && modalobject != "") {
//Update the selector with new values
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
}
$(this).change(function () { //on change, update the edit button href
// console.log('Selectbox Changed');
refreshUpdateHref($(this));
});
});
refreshUpdateHref($(this)); //Ensure href is correct at the beginning
});
//When update/edit modal box submitted
$('#modal').on('hide.bs.modal', function (e) {
if (modaltarget != undefined && modalobject != "") {
//Update the selector with new values
changeSelectedValue($(modaltarget), modalobject[0]['pk'], modalobject[0]['fields']['name'], modalobject[0]['update_url']);
}
});
});

File diff suppressed because it is too large Load Diff

37
RIGS/static/js/button.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: button.js v3.3.7
* Bootstrap: button.js v3.3.2
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -19,7 +19,7 @@
this.isLoading = false
}
Button.VERSION = '3.3.7'
Button.VERSION = '3.3.2'
Button.DEFAULTS = {
loadingText: 'loading...'
@@ -31,7 +31,7 @@
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
state += 'Text'
state = state + 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
@@ -41,10 +41,10 @@
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d).prop(d, true)
$el.addClass(d).attr(d, d)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d).prop(d, false)
$el.removeClass(d).removeAttr(d)
}
}, this), 0)
}
@@ -56,19 +56,15 @@
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false
$parent.find('.active').removeClass('active')
this.$element.addClass('active')
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
this.$element.toggleClass('active')
if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
else $parent.find('.active').removeClass('active')
}
$input.prop('checked', this.$element.hasClass('active'))
if (changed) $input.trigger('change')
if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
this.$element.toggleClass('active')
}
if (changed) this.$element.toggleClass('active')
}
@@ -108,15 +104,10 @@
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target).closest('.btn')
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
// Prevent double click on radios, and the double selections (so cancellation) on checkboxes
e.preventDefault()
// The target component still receive the focus
if ($btn.is('input,button')) $btn.trigger('focus')
else $btn.find('input:visible,button:visible').first().trigger('focus')
}
e.preventDefault()
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))

14
RIGS/static/js/carousel.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: carousel.js v3.3.7
* Bootstrap: carousel.js v3.3.2
* http://getbootstrap.com/javascript/#carousel
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -17,10 +17,10 @@
this.$element = $(element)
this.$indicators = this.$element.find('.carousel-indicators')
this.options = options
this.paused = null
this.sliding = null
this.interval = null
this.$active = null
this.paused =
this.sliding =
this.interval =
this.$active =
this.$items = null
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
@@ -30,7 +30,7 @@
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
}
Carousel.VERSION = '3.3.7'
Carousel.VERSION = '3.3.2'
Carousel.TRANSITION_DURATION = 600

17
RIGS/static/js/collapse.js Executable file → Normal file
View File

@@ -1,12 +1,11 @@
/* ========================================================================
* Bootstrap: collapse.js v3.3.7
* Bootstrap: collapse.js v3.3.2
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+function ($) {
'use strict';
@@ -17,8 +16,7 @@
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
@@ -30,12 +28,13 @@
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.7'
Collapse.VERSION = '3.3.2'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
toggle: true,
trigger: '[data-toggle="collapse"]'
}
Collapse.prototype.dimension = function () {
@@ -173,7 +172,7 @@
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data && options.toggle && option == 'show') options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
@@ -204,7 +203,7 @@
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })
Plugin.call($target, option)
})

94
RIGS/static/js/dropdown.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: dropdown.js v3.3.7
* Bootstrap: dropdown.js v3.3.2
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -19,41 +19,7 @@
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.7'
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
Dropdown.VERSION = '3.3.2'
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
@@ -68,10 +34,7 @@
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
$('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
@@ -85,7 +48,7 @@
$parent
.toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget))
.trigger('shown.bs.dropdown', relatedTarget)
}
return false
@@ -104,25 +67,57 @@
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
if (!isActive && e.which != 27 || isActive && e.which == 27) {
if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
var desc = ' li:not(.disabled):visible a'
var $items = $parent.find('.dropdown-menu' + desc)
var desc = ' li:not(.divider):visible a'
var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
if (!$items.length) return
var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items.eq(index).trigger('focus')
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
})
}
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
@@ -160,6 +155,7 @@
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
}(jQuery);

View File

@@ -1,139 +1,139 @@
function setupItemTable(items_json) {
objectitems = JSON.parse(items_json)
$.each(objectitems, function (key, val) {
objectitems[key] = JSON.parse(val);
})
newitem = -1;
objectitems = JSON.parse(items_json);
$.each(objectitems, function (key, val) {
objectitems[key] = JSON.parse(val);
});
newitem = -1;
}
function nl2br (str, is_xhtml) {
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
function nl2br(str, is_xhtml) {
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2');
}
function escapeHtml (str) {
return $('<div/>').text(str).html();
function escapeHtml(str) {
return $('<div/>').text(str).html();
}
function updatePrices() {
// individual rows
var sum = 0;
for (var pk in objectitems) {
var fields = objectitems[pk].fields;
var sub = fields.cost * fields.quantity;
$('#item-' + pk + ' .sub-total').html(parseFloat(sub).toFixed(2)).data('subtotal', sub);
// individual rows
var sum = 0;
for (var pk in objectitems) {
var fields = objectitems[pk].fields;
var sub = fields.cost * fields.quantity;
$('#item-' + pk + ' .sub-total').html(parseFloat(sub).toFixed(2)).data('subtotal', sub);
sum += Number(sub);
}
sum += Number(sub);
}
$('#sumtotal').text(parseFloat(sum).toFixed(2));
var vat = sum * Number($('#vat-rate').data('rate'));
$('#vat').text(parseFloat(vat).toFixed(2));
$('#total').text(parseFloat(sum + vat).toFixed(2));
$('#sumtotal').text(parseFloat(sum).toFixed(2));
var vat = sum * Number($('#vat-rate').data('rate'));
$('#vat').text(parseFloat(vat).toFixed(2));
$('#total').text(parseFloat(sum + vat).toFixed(2));
}
$('#item-table').on('click', '.item-delete', function () {
delete objectitems[$(this).data('pk')]
$('#item-' + $(this).data('pk')).remove();
updatePrices();
delete objectitems[$(this).data('pk')];
$('#item-' + $(this).data('pk')).remove();
updatePrices();
});
$('#item-table').on('click', '.item-add', function () {
$('#item-form').data('pk', newitem);
$('#item-form').data('pk', newitem);
// Set the form values
$('#item_name').val('');
$('#item_description').val('');
$('#item_quantity').val('');
$('#item_cost').val('');
// Set the form values
$('#item_name').val('');
$('#item_description').val('');
$('#item_quantity').val('');
$('#item_cost').val('');
$($(this).data('target')).modal('show');
$($(this).data('target')).modal('show');
});
$('#item-table').on('click', '.item-edit', function () {
// set the pk as we will need this later
var pk = $(this).data('pk');
$('#item-form').data('pk', pk);
// set the pk as we will need this later
var pk = $(this).data('pk');
$('#item-form').data('pk', pk);
// Set the form values
var fields = objectitems[pk].fields;
$('#item_name').val(fields.name);
$('#item_description').val(fields.description);
$('#item_quantity').val(fields.quantity);
$('#item_cost').val(fields.cost);
// Set the form values
var fields = objectitems[pk].fields;
$('#item_name').val(fields.name);
$('#item_description').val(fields.description);
$('#item_quantity').val(fields.quantity);
$('#item_cost').val(fields.cost);
$($(this).data('target')).modal('show');
$($(this).data('target')).modal('show');
});
$('body').on('submit', '#item-form', function (e) {
e.preventDefault();
var pk = $(this).data('pk');
$('#itemModal').modal('hide');
e.preventDefault();
var pk = $(this).data('pk');
$('#itemModal').modal('hide');
var fields;
if (pk == newitem--) {
// Create the new data structure and add it on.
fields = new Object();
fields['name'] = $('#item_name').val()
fields['description'] = $('#item_description').val();
fields['cost'] = $('#item_cost').val();
fields['quantity'] = $('#item_quantity').val();
var order = 0;
for (item in objectitems) {
order++;
}
fields['order'] = order;
objectitems[pk] = new Object();
objectitems[pk]['fields'] = fields;
// Add the new table
$('#new-item-row').clone().attr('id', 'item-' + pk).data('pk', pk).appendTo('#item-table-body');
$('#item-'+pk+' .item-delete, #item-'+pk+' .item-edit').data('pk', pk)
} else {
// Existing item
// update data structure
fields = objectitems[pk].fields;
fields.name = $('#item_name').val()
fields.description = $('#item_description').val();
fields.cost = $('#item_cost').val();
fields.quantity = $('#item_quantity').val();
objectitems[pk].fields = fields;
var fields;
if (pk == newitem--) {
// Create the new data structure and add it on.
fields = {};
fields['name'] = $('#item_name').val();
fields['description'] = $('#item_description').val();
fields['cost'] = $('#item_cost').val();
fields['quantity'] = $('#item_quantity').val();
var order = 0;
for (item in objectitems) {
order++;
}
// update the table
$row = $('#item-' + pk);
$row.find('.name').html(escapeHtml(fields.name));
$row.find('.description').html(nl2br(escapeHtml(fields.description)));
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
$row.find('.quantity').html(fields.quantity);
updatePrices();
fields['order'] = order;
objectitems[pk] = {};
objectitems[pk]['fields'] = fields;
// Add the new table
$('#new-item-row').clone().attr('id', 'item-' + pk).data('pk', pk).appendTo('#item-table-body');
$('#item-' + pk + ' .item-delete, #item-' + pk + ' .item-edit').data('pk', pk)
} else {
// Existing item
// update data structure
fields = objectitems[pk].fields;
fields.name = $('#item_name').val();
fields.description = $('#item_description').val();
fields.cost = $('#item_cost').val();
fields.quantity = $('#item_quantity').val();
objectitems[pk].fields = fields;
}
// update the table
$row = $('#item-' + pk);
$row.find('.name').html(escapeHtml(fields.name));
$row.find('.description').html(nl2br(escapeHtml(fields.description)));
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
$row.find('.quantity').html(fields.quantity);
updatePrices();
});
$('body').on('submit', '.itemised_form', function (e) {
$('#id_items_json').val(JSON.stringify(objectitems));
$('#id_items_json').val(JSON.stringify(objectitems));
});
// Return a helper with preserved width of cells
var fixHelper = function (e, ui) {
ui.children().each(function () {
$(this).width($(this).width());
});
return ui;
ui.children().each(function () {
$(this).width($(this).width());
});
return ui;
};
$("#item-table tbody").sortable({
helper: fixHelper,
update: function (e, ui) {
info = $(this).sortable("toArray");
itemorder = new Array();
$.each(info, function (key, value) {
pk = $('#' + value).data('pk');
objectitems[pk].fields.order = key;
});
helper: fixHelper,
update: function (e, ui) {
info = $(this).sortable("toArray");
itemorder = [];
$.each(info, function (key, value) {
pk = $('#' + value).data('pk');
objectitems[pk].fields.order = key;
});
}
}
});

10
RIGS/static/js/modal.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: modal.js v3.3.7
* Bootstrap: modal.js v3.3.5
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -33,7 +33,7 @@
}
}
Modal.VERSION = '3.3.7'
Modal.VERSION = '3.3.5'
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
@@ -140,9 +140,7 @@
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
if (document !== e.target &&
this.$element[0] !== e.target &&
!this.$element.has(e.target).length) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.trigger('focus')
}
}, this))

13
RIGS/static/js/popover.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: popover.js v3.3.7
* Bootstrap: popover.js v3.3.2
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -19,7 +19,7 @@
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.VERSION = '3.3.7'
Popover.VERSION = '3.3.2'
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
@@ -75,6 +75,11 @@
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
}
Popover.prototype.tip = function () {
if (!this.$tip) this.$tip = $(this.options.template)
return this.$tip
}
// POPOVER PLUGIN DEFINITION
// =========================
@@ -85,7 +90,7 @@
var data = $this.data('bs.popover')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data && option == 'destroy') return
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})

39
RIGS/static/js/scrollspy.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: scrollspy.js v3.3.7
* Bootstrap: scrollspy.js v3.3.2
* http://getbootstrap.com/javascript/#scrollspy
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -14,8 +14,10 @@
// ==========================
function ScrollSpy(element, options) {
this.$body = $(document.body)
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
var process = $.proxy(this.process, this)
this.$body = $('body')
this.$scrollElement = $(element).is('body') ? $(window) : $(element)
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
this.selector = (this.options.target || '') + ' .nav li > a'
this.offsets = []
@@ -23,12 +25,12 @@
this.activeTarget = null
this.scrollHeight = 0
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
this.$scrollElement.on('scroll.bs.scrollspy', process)
this.refresh()
this.process()
}
ScrollSpy.VERSION = '3.3.7'
ScrollSpy.VERSION = '3.3.2'
ScrollSpy.DEFAULTS = {
offset: 10
@@ -39,19 +41,20 @@
}
ScrollSpy.prototype.refresh = function () {
var that = this
var offsetMethod = 'offset'
var offsetBase = 0
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
var offsetMethod = 'offset'
var offsetBase = 0
if (!$.isWindow(this.$scrollElement[0])) {
offsetMethod = 'position'
offsetBase = this.$scrollElement.scrollTop()
}
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
var self = this
this.$body
.find(this.selector)
.map(function () {
@@ -66,8 +69,8 @@
})
.sort(function (a, b) { return a[0] - b[0] })
.each(function () {
that.offsets.push(this[0])
that.targets.push(this[1])
self.offsets.push(this[0])
self.targets.push(this[1])
})
}
@@ -96,7 +99,7 @@
for (i = offsets.length; i--;) {
activeTarget != targets[i]
&& scrollTop >= offsets[i]
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
&& this.activate(targets[i])
}
}
@@ -107,8 +110,8 @@
this.clear()
var selector = this.selector +
'[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]'
'[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]'
var active = $(selector)
.parents('li')

12
RIGS/static/js/tab.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: tab.js v3.3.7
* Bootstrap: tab.js v3.3.2
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -14,12 +14,10 @@
// ====================
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.7'
Tab.VERSION = '3.3.2'
Tab.TRANSITION_DURATION = 150
@@ -67,7 +65,7 @@
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
&& (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
function next() {
$active
@@ -90,7 +88,7 @@
element.removeClass('fade')
}
if (element.parent('.dropdown-menu').length) {
if (element.parent('.dropdown-menu')) {
element
.closest('li.dropdown')
.addClass('active')

120
RIGS/static/js/tooltip.js Executable file → Normal file
View File

@@ -1,9 +1,9 @@
/* ========================================================================
* Bootstrap: tooltip.js v3.3.7
* Bootstrap: tooltip.js v3.3.2
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
@@ -15,18 +15,17 @@
// ===============================
var Tooltip = function (element, options) {
this.type = null
this.options = null
this.enabled = null
this.timeout = null
this.hoverState = null
this.type =
this.options =
this.enabled =
this.timeout =
this.hoverState =
this.$element = null
this.inState = null
this.init('tooltip', element, options)
}
Tooltip.VERSION = '3.3.7'
Tooltip.VERSION = '3.3.2'
Tooltip.TRANSITION_DURATION = 150
@@ -51,12 +50,7 @@
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
this.inState = { click: false, hover: false, focus: false }
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
}
this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
var triggers = this.options.trigger.split(' ')
@@ -111,20 +105,16 @@
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (self && self.$tip && self.$tip.is(':visible')) {
self.hoverState = 'in'
return
}
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
}
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in'
return
}
clearTimeout(self.timeout)
self.hoverState = 'in'
@@ -136,14 +126,6 @@
}, self.options.delay.show)
}
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
@@ -153,12 +135,6 @@
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
}
if (self.isInStateTrue()) return
clearTimeout(self.timeout)
self.hoverState = 'out'
@@ -205,7 +181,6 @@
.data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
@@ -213,12 +188,13 @@
if (autoPlace) {
var orgPlacement = placement
var viewportDim = this.getPosition(this.$viewport)
var $container = this.options.container ? $(this.options.container) : this.$element.parent()
var containerDim = this.getPosition($container)
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
placement
$tip
@@ -259,8 +235,8 @@
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
offset.top += marginTop
offset.left += marginLeft
offset.top = offset.top + marginTop
offset.left = offset.left + marginLeft
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
@@ -296,10 +272,10 @@
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
}
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
this.arrow()
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isVertical ? 'top' : 'left', '')
.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isHorizontal ? 'top' : 'left', '')
}
Tooltip.prototype.setContent = function () {
@@ -312,16 +288,14 @@
Tooltip.prototype.hide = function (callback) {
var that = this
var $tip = $(this.$tip)
var $tip = this.tip()
var e = $.Event('hide.bs.' + this.type)
function complete() {
if (that.hoverState != 'in') $tip.detach()
if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
}
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
callback && callback()
}
@@ -331,7 +305,7 @@
$tip.removeClass('in')
$.support.transition && $tip.hasClass('fade') ?
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
@@ -344,7 +318,7 @@
Tooltip.prototype.fixTitle = function () {
var $e = this.$element
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
@@ -364,10 +338,7 @@
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
}
var isSvg = window.SVGElement && el instanceof window.SVGElement
// Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
// See https://github.com/twbs/bootstrap/issues/20280
var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
@@ -402,7 +373,7 @@
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
@@ -428,13 +399,7 @@
}
Tooltip.prototype.tip = function () {
if (!this.$tip) {
this.$tip = $(this.options.template)
if (this.$tip.length != 1) {
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
}
}
return this.$tip
return (this.$tip = this.$tip || $(this.options.template))
}
Tooltip.prototype.arrow = function () {
@@ -463,13 +428,7 @@
}
}
if (e) {
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
Tooltip.prototype.destroy = function () {
@@ -477,13 +436,6 @@
clearTimeout(this.timeout)
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach()
}
that.$tip = null
that.$arrow = null
that.$viewport = null
that.$element = null
})
}
@@ -497,7 +449,7 @@
var data = $this.data('bs.tooltip')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data && option == 'destroy') return
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})

4
RIGS/static/js/transition.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ========================================================================
* Bootstrap: transition.js v3.3.7
* Bootstrap: transition.js v3.3.2
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +0,0 @@
$button_color: #357ebf;
body{
margin: 0px;
}
.main-table{
width: 100%;
border-collapse: collapse;
}
.client-header {
background-image: url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");
background-color: #222;
background-repeat: no-repeat;
background-position: center;
width: 100%;
margin-bottom: 28px;
.logos{
width: 100%;
max-width: 640px;
}
img {
height: 110px;
}
}
.content-container{
width: 100%;
.content {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 100%;
max-width: 600px;
padding: 10px;
text-align: left;
.button-container{
width: 100%;
.button {
padding: 6px 12px;
background-color: $button_color;
border-radius: 4px;
a {
color: #fff;
text-decoration: none;
}
}
}
}
}

View File

@@ -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";
@@ -90,105 +89,62 @@ ins {
margin: 30px auto 0;
.circle {
background-color: rgba(0,0,0,0);
border: 5px solid rgba(0,183,229,0.9);
opacity: .9;
border-right: 5px solid rgba(0,0,0,0);
border-left: 5px solid rgba(0,0,0,0);
border-radius: 50px;
box-shadow: 0 0 35px #2187e7;
width: 50px;
height: 50px;
margin: 0 auto;
@include animation(spinPulse 1s infinite ease-in-out);
background-color: rgba(0, 0, 0, 0);
border: 5px solid rgba(0, 183, 229, 0.9);
opacity: .9;
border-right: 5px solid rgba(0, 0, 0, 0);
border-left: 5px solid rgba(0, 0, 0, 0);
border-radius: 50px;
box-shadow: 0 0 35px #2187e7;
width: 50px;
height: 50px;
margin: 0 auto;
@include animation(spinPulse 1s infinite ease-in-out);
}
.circle1 {
background-color: rgba(0,0,0,0);
border: 5px solid rgba(0,183,229,0.9);
opacity: .9;
border-left: 5px solid rgba(0,0,0,0);
border-right: 5px solid rgba(0,0,0,0);
border-radius: 50px;
box-shadow: 0 0 15px #2187e7;
width: 30px;
height: 30px;
margin: 0 auto;
position: relative;
top: -40px;
@include animation(spinoffPulse 1s infinite linear);
background-color: rgba(0, 0, 0, 0);
border: 5px solid rgba(0, 183, 229, 0.9);
opacity: .9;
border-left: 5px solid rgba(0, 0, 0, 0);
border-right: 5px solid rgba(0, 0, 0, 0);
border-radius: 50px;
box-shadow: 0 0 15px #2187e7;
width: 30px;
height: 30px;
margin: 0 auto;
position: relative;
top: -40px;
@include animation(spinoffPulse 1s infinite linear);
}
@include keyframes(spinPulse) {
0% {
@include rotate(160deg);
opacity: 0;
box-shadow: 0 0 1px #2187e7;
}
0% {
@include rotate(160deg);
opacity: 0;
box-shadow: 0 0 1px #2187e7;
}
50% {
@include rotate(145deg);
opacity: 1;
}
50% {
@include rotate(145deg);
opacity: 1;
}
100% {
@include rotate(-320deg);
opacity: 0;
};
100% {
@include rotate(-320deg);
opacity: 0;
}
;
}
@include keyframes(spinoffPulse) {
0% {
@include rotate(0deg);
}
0% {
@include rotate(0deg);
}
100% {
@include rotate(360deg);
};
}
}
html.embedded{
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
max-height: 100%;
overflow: hidden;
justify-content: center;
body{
padding:0;
width:100%;
background:none;
overflow: scroll;
}
.embed_container{
border:5px solid #e9e9e9;
padding:12px 0px;
min-height:100%;
width:100%;
}
.source{
background: url('/static/imgs/pyrigs-avatar.png') no-repeat;
background-size: 16px 16px;
padding-left: 20px;
color: #000;
}
h3{
margin-top:10px;
margin-bottom:5px;
}
p{
margin-bottom:2px;
font-size: 11px;
}
.event-mic-photo{
max-width: 3em;
100% {
@include rotate(360deg);
}
;
}
}

View File

@@ -1,66 +1,66 @@
{% load static %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script src="{% static "js/popover.js" %}"></script>
<script src="{% static "js/moment.min.js" %}"></script>
<script src="{% static "js/moment-twitter.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function(){
if($(this).attr('href')){
window.location.href = $(this).attr('href');
}
});
<script src="{% static "js/tooltip.js" %}"></script>
<script src="{% static "js/popover.js" %}"></script>
<script src="{% static "js/moment.min.js" %}"></script>
<script src="{% static "js/moment-twitter.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function () {
if ($(this).attr('href')) {
window.location.href = $(this).attr('href');
}
});
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
moment().twitter();
})
$(document).ready(function() {
$(function () {
$( "#activity" ).hide();
$( "#activity" ).load( "{% url 'activity_feed' %}", function() {
$('#activity_loading').slideUp('slow',function(){
$('#activity').slideDown('slow');
});
$('#activity [data-toggle="popover"]').popover();
$('.date').each(function (index, dateElem) {
var $dateElem = $(dateElem);
var formatted = moment($dateElem.attr('data-date')).twitterLong();
$dateElem.text(formatted);
});
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
moment().twitter();
});
});
});
</script>
$(document).ready(function () {
$(function () {
$("#activity").hide();
$("#activity").load("{% url 'activity_feed' %}", function () {
$('#activity_loading').slideUp('slow', function () {
$('#activity').slideDown('slow');
});
$('#activity [data-toggle="popover"]').popover();
$('.date').each(function (index, dateElem) {
var $dateElem = $(dateElem);
var formatted = moment($dateElem.attr('data-date')).twitterLong();
$dateElem.text(formatted);
});
});
});
});
</script>
{% endblock %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Recent Changes</h4>
</div>
<div class="list-group">
</div>
<div class="list-group">
<div id="activity_loading" class="list-group-item loading-animation">
<div class="circle"></div>
<div class="circle1"></div>
<div class="circle"></div>
<div class="circle1"></div>
</div>
<div id="activity">
</div>
</div>
</div>
</div>

View File

@@ -5,50 +5,52 @@
{% load to_class_name from filters %}
{% block content %}
<div class="list-group-item">
<div class="media">
{% for version in object_list %}
<div class="media">
{% for version in object_list %}
{% if not version.withPrevious %}
{% if not forloop.first %}
</div> {#/.media-body#}
</div> {#/.media#}
</div>
<div class="list-group-item">
<div class="media">
{% endif %}
<div class="media-left">
{% if not forloop.first %}
</div> {#/.media-body#}
</div> {#/.media#}
</div>
<div class="list-group-item">
<div class="media">
{% endif %}
<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}}" />
</a>
<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 }}"/>
</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>
</h5>
</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>
</h5>
{% endif %}
<p>
<small>
{% if version.changes.old == None %}
Created
<p>
<small>
{% if version.old == None %}
Created
{% else %}
Changed {% include 'RIGS/version_changes.html' %} in
Changed {% include 'RIGS/version_changes.html' %} in
{% endif %}
{% include 'RIGS/object_button.html' with object=version.changes.new %}
{% if version.revision.comment %}
({{ version.revision.comment }})
{% endif %}
</small>
</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% include 'RIGS/object_button.html' with object=version.new %}
{% if version.revision.comment %}
({{ version.revision.comment }})
{% endif %}
</small>
</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -10,39 +10,39 @@
<script src="{% static "js/popover.js" %}"></script>
<script src="{% static "js/moment.min.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function(){
if($(this).attr('href')){
window.location.href = $(this).attr('href');
}
});
$(function () {
$('[data-toggle="popover"]').popover().click(function () {
if ($(this).attr('href')) {
window.location.href = $(this).attr('href');
}
});
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
$('.date').each(function (index, dateElem) {
var $dateElem = $(dateElem);
var formatted = moment($dateElem.attr('data-date')).fromNow();
$dateElem.text(formatted);
});
});
})
})
</script>
{% endblock %}
{% block content %}
<div class="col-sm-12">
<div class="col-sm-12">
<div class="row">
<div class="col-sm-12">
<h3>Rigboard Activity Stream</h3>
@@ -51,7 +51,7 @@
</div>
<div class="table-responsive">
<table class="table table-striped">
<table class="table table-striped">
<thead>
<tr>
<td>Date</td>
@@ -63,23 +63,25 @@
</tr>
</thead>
<tbody>
{% for version in object_list %}
<tr>
<td>{{ version.revision.date_created }}</td>
<td><a href="{{ version.changes.new.get_absolute_url }}">{{version.changes.new|to_class_name}} {{ version.changes.new.pk|stringformat:"05d" }}</a></td>
<td>{{ version.pk }}|{{ version.revision.pk }}</td>
<td>{{ version.revision.user.name }}</td>
<td>
{% if version.changes.old == None %}
{{version.changes.new|to_class_name}} Created
{% else %}
{% include 'RIGS/version_changes.html' %}
{% endif %} </td>
<td>{{ version.changes.revision.comment }}</td>
</tr>
{% endfor %}
{% for version in object_list %}
<tr>
<td>{{ version.revision.date_created }}</td>
<td>
<a href="{{ version.new.get_absolute_url }}">{{ version.new|to_class_name }} {{ version.new.pk|stringformat:"05d" }}</a>
</td>
<td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
<td>{{ version.revision.user.name }}</td>
<td>
{% if version.old == None %}
{{ version.new|to_class_name }} Created
{% else %}
{% include 'RIGS/version_changes.html' %}
{% endif %} </td>
<td>{{ version.revision.comment }}</td>
</tr>
{% endfor %}
</tbody>
</table>
@@ -87,4 +89,4 @@
</div>
<div class="align-right">{% paginator %}</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -2,39 +2,40 @@
{% 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>
<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>
<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 %}
</tr>
{% endfor %}
</table>
</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 %}
<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 %}

View File

@@ -5,8 +5,8 @@
{% block title %}Calendar{% endblock %}
{% block css %}
<link href="{% static "css/fullcalendar.css" %}" rel='stylesheet' />
<link href="{% static "css/fullcalendar.print.css" %}" rel='stylesheet' media='print' />
<link href="{% static "css/fullcalendar.css" %}" rel='stylesheet'/>
<link href="{% static "css/fullcalendar.print.css" %}" rel='stylesheet' media='print'/>
{% endblock %}
{% block js %}
@@ -17,24 +17,24 @@
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
vars[key] = value;
});
return vars;
}
$(document).ready(function() {
$(document).ready(function () {
viewToUrl = {
'agendaWeek':'week',
'agendaDay':'day',
'month':'month'
}
'agendaWeek': 'week',
'agendaDay': 'day',
'month': 'month'
};
viewFromUrl = {
'week':'agendaWeek',
'day':'agendaDay',
'month':'month'
}
'week': 'agendaWeek',
'day': 'agendaDay',
'month': 'month'
};
$('#calendar').fullCalendar({
editable: false,
@@ -50,16 +50,16 @@
// options apply to agendaWeek and agendaDay views
},
week: {
columnFormat:'ddd D/M'
columnFormat: 'ddd D/M'
},
day: {
// options apply to basicDay and agendaDay views
}
},
header:false,
header: false,
events: function (start_moment, end_moment, timezone, callback) {
events: function(start_moment, end_moment, timezone, callback) {
$.ajax({
url: '/api/event',
dataType: 'json',
@@ -67,18 +67,19 @@
start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss"),
end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss")
},
success: function(doc) {
success: function (doc) {
var events = [];
colours = {'Provisional': '#f0ad4e',
'Confirmed': '#5cb85c' ,
'Booked': '#5cb85c' ,
'Cancelled': 'grey' ,
colours = {
'Provisional': '#f0ad4e',
'Confirmed': '#5cb85c',
'Booked': '#5cb85c',
'Cancelled': 'grey',
'non-rig': '#5bc0de'
};
$(doc).each(function() {
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
$(doc).each(function () {
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
}
thisEvent = {
@@ -87,14 +88,14 @@
'className': 'modal-href',
'title': $(this).attr('title'),
'url': $(this).attr('url')
}
if($(this).attr('is_rig')==true || $(this).attr('status') == "Cancelled"){
};
if ($(this).attr('is_rig') == true || $(this).attr('status') == "Cancelled") {
thisEvent['color'] = colours[$(this).attr('status')];
}else{
} else {
thisEvent['color'] = colours['non-rig'];
}
events.push(thisEvent);
});
callback(events);
@@ -102,20 +103,20 @@
});
},
viewRender: function(view, element){
viewRender: function (view, element) {
// Set the title of the view
$('#calendar-header').text(view.title);
// Enable/Disable "Today" button as required
if(moment().isBetween(view.intervalStart, view.intervalEnd)){
if (moment().isBetween(view.intervalStart, view.intervalEnd)) {
//Today is within the current view
$('#today-button').prop('disabled', true);
}else{
} else {
$('#today-button').prop('disabled', false);
}
// Set active view select button
switch(view.name){
switch (view.name) {
case 'month':
$('#month-button').addClass('active');
$('#week-button').removeClass('active');
@@ -134,53 +135,64 @@
$('#day-button').addClass('active');
break;
}
history.replaceState(null,null,'{% url 'web_calendar' %}'+viewToUrl[view.name]+'/'+view.intervalStart.format('YYYY-MM-DD')+'/');
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() ){
$('#go-to-date-input').change(function () {
if (moment($('#go-to-date-input').val()).isValid()) {
$('#go-to-date-button').prop('disabled', false);
}else{
} else {
$('#go-to-date-button').prop('disabled', true);
}
});
$('#go-to-date-button').click(function(){
day = moment($('#go-to-date-input').val()) ;
if(day.isValid()){
$('#calendar').fullCalendar( 'gotoDate', day);
}else{
$('#go-to-date-button').click(function () {
day = moment($('#go-to-date-input').val());
if (day.isValid()) {
$('#calendar').fullCalendar('gotoDate', day);
} else {
alert('Invalid Date');
}
});
// Go to the initial settings, if they're valid
view = viewFromUrl['{{view}}'];
$('#calendar').fullCalendar( 'changeView', view);
$('#calendar').fullCalendar('changeView', view);
day = moment('{{date}}');
if(day.isValid()){
$('#calendar').fullCalendar( 'gotoDate', day);
}else{
if (day.isValid()) {
$('#calendar').fullCalendar('gotoDate', day);
} else {
console.log('Supplied date is invalid - using default')
}
});
</script>
@@ -189,7 +201,7 @@
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="pull-left">
<span id="calendar-header" class="h2"></span>
@@ -197,33 +209,35 @@
<div class="form-inline pull-right btn-page">
<div class="input-group">
<input type="date" class="form-control" id="go-to-date-input" placeholder="Go to date...">
<span class="input-group-btn">
<input type="date" class="form-control" id="go-to-date-input" placeholder="Go to date...">
<span class="input-group-btn">
<button class="btn btn-default" id="go-to-date-button" type="button" disabled>Go!</button>
</span>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" id="today-button">Today</button>
<button type="button" class="btn btn-primary" id="today-button">Today</button>
</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">
<button type="button" class="btn btn-default" id="month-button">Month</button>
<button type="button" class="btn btn-default" id="week-button">Week</button>
<button type="button" class="btn btn-default" id="day-button">Day</button>
<button type="button" class="btn btn-default" id="month-button">Month</button>
<button type="button" class="btn btn-default" id="week-button">Week</button>
<button type="button" class="btn btn-default" id="day-button">Day</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div id='calendar'>
</div>
<div class="col-sm-12">
<div id='calendar'>
</div>
{% endblock %}
</div>
{% endblock %}

View File

@@ -1,78 +0,0 @@
<div class="row">
<div class="col-sm-12 col-md-6 col-lg-5">
<div class="panel panel-default">
<div class="panel-heading">Contact Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Person</dt>
<dd>
{% if event.person %}
{{ event.person.name }}
{% endif %}
</dd>
<dt>Email</dt>
<dd>
<span class="overflow-ellipsis">{{ event.person.email }}</span>
</dd>
<dt>Phone Number</dt>
<dd>{{ event.person.phone }}</dd>
</dl>
</div>
</div>
{% if event.organisation %}
<div class="panel panel-default">
<div class="panel-heading">Organisation</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Organisation</dt>
<dd>
{{ event.organisation.name }}
</dd>
<dt>Phone Number</dt>
<dd>{{ object.organisation.phone }}</dd>
</dl>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-12 col-md-6 col-lg-7">
<div class="panel panel-info">
<div class="panel-heading">Event Info</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Venue</dt>
<dd>
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
<dt>Status</dt>
<dd>{{ event.get_status_display }}</dd>
<dd>&nbsp;</dd>
<dt>Access From</dt>
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt>Event Starts</dt>
<dd>{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt>Event Ends</dt>
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd>&nbsp;</dd>
<dt>Event Description</dt>
<dd>{{ event.description|linebreaksbr }}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@@ -11,14 +11,17 @@
<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" />
<input type="submit" class="btn btn-primary"/>
</div>
</form>
</div>
@@ -42,4 +45,4 @@
</div>
</div>
{% endif %}
{% endblock %}
{% endblock %}

View File

@@ -1,244 +1,286 @@
{% 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">
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
</h1>
</div>
<div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %}
</div>
{% endif %}
{% if object.is_rig %}
{# only need contact details for a rig #}
<div class="col-sm-12 col-md-6 col-lg-5">
<div class="panel panel-default">
<div class="panel-heading">Contact Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Person</dt>
<dd>
{% if object.person %}
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
{{ object.person }}
</a>
{% endif %}
</dd>
<dt>Email</dt>
<dd>
<a href="mailto:{{object.person.email}}" target="_blank">
<span class="overflow-ellipsis">{{ object.person.email }}</span>
</a>
</dd>
<dt>Phone Number</dt>
<dd><a href="tel:{{object.person.phone}}">{{ object.person.phone }}</a></dd>
</dl>
<div class="row">
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
</h1>
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
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
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<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>
{% if event.organisation %}
{% endif %}
{% if object.is_rig %}
{# only need contact details for a rig #}
<div class="col-sm-12 col-md-6 col-lg-5">
<div class="panel panel-default">
<div class="panel-heading">Organisation</div>
<div class="panel-heading">Contact Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Organisation</dt>
<dt>Person</dt>
<dd>
{% if object.organisation %}
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
{{ object.organisation }}
{% if object.person %}
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
{{ object.person }}
</a>
{% endif %}
</dd>
<dt>Email</dt>
<dd>
<a href="mailto:{{ object.person.email }}" target="_blank">
<span class="overflow-ellipsis">{{ object.person.email }}</span>
</a>
</dd>
<dt>Phone Number</dt>
<dd>
<a href="tel:{{object.person.phone}}">
{{ object.organisation.phone }}
</a>
</dd>
<dt>Has SU Account</dt>
<dd>{{ event.organisation.union_account|yesno|capfirst }}</dd>
<dd><a href="tel:{{ object.person.phone }}">{{ object.person.phone }}</a></dd>
</dl>
</div>
</div>
{% endif %}
{% if event.organisation %}
<div class="panel panel-default">
<div class="panel-heading">Organisation</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Organisation</dt>
<dd>
{% if object.organisation %}
<a href="{% url 'organisation_detail' object.organisation.pk %}"
class="modal-href">
{{ object.organisation }}
</a>
{% endif %}
</dd>
{% if event.is_rig and event.internal %}
<div class="panel panel-default">
<div class="panel-heading">Client Authorisation</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Authorised</dt>
<dd>{{ object.authorised|yesno:"Yes,No" }}</dd>
<dt>Phone Number</dt>
<dd>
<a href="tel:{{ object.person.phone }}">
{{ object.organisation.phone }}
</a>
</dd>
<dt>Authorised by</dt>
<dd>
{% if object.authorisation %}
{{ object.authorisation.name }}
(<a href="mailto:{{ object.authorisation.email }}">{{ object.authorisation.email }}</a>)
{% endif %}
</dd>
<dt>Authorised at</dt>
<dd>{{ object.authorisation.last_edited_at }}</dd>
<dt>Authorised amount</dt>
<dd>
{% if object.authorisation %}
£ {{ object.authorisation.amount|floatformat:"2" }}
{% endif %}
</dd>
<dt>Requested by</dt>
<dd>{{ object.authorisation.sent_by }}</dd>
</dl>
<dt>Has SU Account</dt>
<dd>{{ event.organisation.union_account|yesno|capfirst }}</dd>
</dl>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
<div class="panel panel-info">
<div class="panel-heading">Event Info</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Venue</dt>
<dd>
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
{% if event.is_rig %}
<dt>Event MIC</dt>
{% endif %}
</div>
{% endif %}
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
<div class="panel panel-info">
<div class="panel-heading">Event Info</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Venue</dt>
<dd>
{% if event.mic and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{{ event.mic.name }}
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% else %}
{{ event.mic.name }}
{% endif %}
</dd>
{% endif %}
<dt>Status</dt>
<dd>{{ event.get_status_display }}</dd>
<dd>&nbsp;</dd>
{% if event.is_rig %}
<dt>Crew Meet</dt>
<dd>{{ event.meet_at|date:"D d M Y H:i"|default:"" }}</dd>
<dd>{{ event.meet_info|default:"" }}</dd>
<dt>Access From</dt>
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
{% endif %}
<dt>Event Starts</dt>
<dd>{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt>Event Ends</dt>
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd>&nbsp;</dd>
<dt>Event Description</dt>
<dd>{{ event.description|linebreaksbr }}</dd>
<dd>&nbsp;</dd>
<dt>Based On</dt>
<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.based_on.name }} {% if object.based_on.mic %}by {{ object.based_on.mic.name }}{% endif %}
</a>
{% if event.is_rig %}
<dt>Event MIC</dt>
<dd>
{% if event.mic and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{{ event.mic.name }}
</a>
{% else %}
{{ event.mic.name }}
{% endif %}
</dd>
{% endif %}
</dd>
{% if event.dry_hire %}
<dt>Checked In By</dt>
<dd>{{ object.checked_in_by.name }}</dd>
{% endif %}
<dt>Status</dt>
<dd>{{ event.get_status_display }}</dd>
{% if event.is_rig %}
<dt>Collected By</dt>
<dd>{{ object.collector }}</dd>
{% endif %}
{% if event.is_rig %}
<dd>&nbsp;</dd>
{% if object.internal %}
<dt>Authorisation Request</dt>
<dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd>
{% if event.is_rig %}
<dt>Crew Meet</dt>
<dd>{{ event.meet_at|date:"D d M Y H:i"|default:"" }}</dd>
<dd>{{ event.meet_info|default:"" }}</dd>
<dt>By</dt>
<dd>{{ object.auth_request_by }}</dd>
<dt>Access From</dt>
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
{% endif %}
<dt>At</dt>
<dd>{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt>Event Starts</dt>
<dd>{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt>To</dt>
<dd>{{ object.auth_request_to }}</dd>
<dt>Event Ends</dt>
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
{% else %}
<dd>&nbsp;</dd>
<dt>Event Description</dt>
<dd>{{ event.description|linebreaksbr }}</dd>
<dd>&nbsp;</dd>
<dt>Based On</dt>
<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.based_on.name }}
{% if object.based_on.mic %}
by {{ object.based_on.mic.name }}
{% endif %}
</a>
{% endif %}
</dd>
{% if event.dry_hire %}
<dt>Checked In By</dt>
<dd>{{ object.checked_in_by.name }}</dd>
{% endif %}
{% if event.is_rig %}
<dt>Collected By</dt>
<dd>{{ object.collector }}</dd>
{% endif %}
{% if event.is_rig %}
<dt>PO</dt>
<dd>{{ object.purchase_order }}</dd>
{% endif %}
{% endif %}
</dl>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %}
</div>
{% endif %}
{% if event.is_rig %}
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Event Details</div>
<div class="panel-body">
<div class="well well-sm">
<h4>Notes</h4>
{{ event.notes|linebreaksbr }}
</div>
{% include 'RIGS/item_table.html' %}
</dl>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %}
</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 }}
</a>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
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
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<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>
</div>
{% endif %}
</div>
{% endif %}
{% if event.is_rig %}
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Event Details</div>
<div class="panel-body">
<div class="well well-sm">
<h4>Notes</h4>
{{ event.notes|linebreaksbr }}
</div>
{% include 'RIGS/item_table.html' %}
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
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
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<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 }}
</a>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% if request.is_ajax %}

View File

@@ -1,65 +0,0 @@
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
{% if not event.dry_hire %}
<a href="{% url 'event_ra' event.pk %}" class="btn btn-default
{% if event.risk_assessment_edit_url %}
btn-success
{% else %}
btn-warning
{% endif %}
"><span
class="glyphicon glyphicon-paperclip"></span> <span
class="hidden-xs">RA</span></a>
{% endif %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
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
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if event.internal %}
<a class="btn btn-default item-add modal-href event-authorise-request
{% if event.authorised %}
btn-success
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
btn-warning
{% elif event.auth_request_to %}
btn-info
{% endif %}
"
href="{% url 'event_authorise_request' object.pk %}">
<span class="glyphicon glyphicon-send"></span>
<span class="hidden-xs">
{% if event.authorised %}
Authorised
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
Authorisation Issue
{% elif event.auth_request_to %}
Awaiting Authorisation
{% else %}
Request Authorisation
{% endif %}
</span>
</a>
{% endif %}
{% if perms.RIGS.add_invoice %}
<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>

View File

@@ -1,106 +0,0 @@
{% extends 'base_embed.html' %}
{% load static from staticfiles %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<a href="/">
<span class="source"> R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></span>
</a>
</div>
<div class="col-sm-12">
<span class="pull-right">
{% if object.mic %}
<div class="text-center">
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo img-rounded"/>
</div>
{% elif object.is_rig %}
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% endif %}
</span>
<h3>
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' object.pk %}"{% endif %}>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} </a>
{% if object.venue %}
<small>at {{ object.venue }}</small>
{% endif %}
<br/><small>
{{ object.start_date|date:"D d/m/Y" }}
{% if object.has_start_time %}
{{ object.start_time|date:"H:i" }}
{% endif %}
{% if object.end_date or object.has_end_time %}
&ndash;
{% endif %}
{% if object.end_date and object.end_date != object.start_date %}
{{ object.end_date|date:"D d/m/Y" }}
{% endif %}
{% if object.has_end_time %}
{{ object.end_time|date:"H:i" }}
{% endif %}
</small>
</h3>
<div class="row">
<div class="col-xs-6">
<p>
<strong>Status:</strong>
{{ object.get_status_display }}
</p>
<p>
{% if object.is_rig %}
<strong>Client:</strong> {{ object.person.name }}
{% if object.organisation %}
for {{ object.organisation.name }}
{% endif %}
{% if object.dry_hire %}(Dry Hire){% endif %}
{% else %}
<strong>Non-Rig</strong>
{% endif %}
</p>
<p>
<strong>MIC:</strong>
{% if object.mic %}
{{object.mic.name}}
{% else %}
None
{% endif %}
</p>
</div>
<div class="col-xs-6">
{% if object.meet_at %}
<p>
<strong>Crew meet:</strong>
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
</p>
{% endif %}
{% if object.access_at %}
<p>
<strong>Access at:</strong>
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
</p>
{% endif %}
<p>
<strong>Last updated:</strong>
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
</p>
</div>
</div>
{% if object.description %}
<p>
<strong>Description: </strong>
{{ object.description|linebreaksbr }}
</p>
{% endif %}
</table>
</div>
</div>
{% endblock %}

View File

@@ -32,12 +32,12 @@
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());
if ($('#' + id_start).val() == $('#' + id_end_date).val()) {
var end_date = new Date($('#' + id_end_date).val());
end_date.setDate(end_date.getDate() + 1);
$('#'+id_end_date).val(end_date.getISOString());
$('#' + id_end_date).val(end_date.getISOString());
}
$('#'+id_end_time).val('02:00');
$('#' + id_end_time).val('02:00');
}
$(document).ready(function () {
@@ -74,33 +74,34 @@
function supportsDate() {
//return false; //for development
var input = document.createElement('input');
input.setAttribute('type','date');
input.setAttribute('type', 'date');
var notADateValue = 'not-a-date';
input.setAttribute('value', notADateValue);
input.setAttribute('value', notADateValue);
return !(input.value === notADateValue);
}
if(supportsDate()){
if (supportsDate()) {
//Good, we'll use the browser implementation
}else{
} else {
//Rubbish browser - do JQuery backup
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', '{% static "css/bootstrap-datetimepicker.min.css" %}');
.appendTo('head')
.attr({type: 'text/css', rel: 'stylesheet'})
.attr('href', '{% static "css/bootstrap-datetimepicker.min.css" %}');
$.when(
$.getScript( "{% static "js/moment.min.js" %}" ),
$.getScript( "{% static "js/bootstrap-datetimepicker.min.js" %}" ),
$.Deferred(function( deferred ){
$( deferred.resolve );
})
).done(function(){
$('input[type=date]').attr('type','text').datetimepicker({
$.getScript("{% static "js/moment.min.js" %}"),
$.getScript("{% static "js/bootstrap-datetimepicker.min.js" %}"),
$.Deferred(function (deferred) {
$(deferred.resolve);
})
).done(function () {
$('input[type=date]').attr('type', 'text').datetimepicker({
format: 'YYYY-MM-DD',
});
$('input[type=time]').attr('type','text').datetimepicker({
$('input[type=time]').attr('type', 'text').datetimepicker({
format: 'HH:mm',
});
$('input[type=datetime-local]').attr('type','text').datetimepicker({
$('input[type=datetime-local]').attr('type', 'text').datetimepicker({
format: 'YYYY-MM-DD[T]HH:mm',
sideBySide: true,
});
@@ -113,168 +114,90 @@
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
});
$(function () {
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="tooltip"]').tooltip();
})
</script>
{% endblock %}
{% block content %}
<form class="form-horizontal itemised_form" role="form" method="POST">{% csrf_token %}
<div class="row">
<div class="col-sm-12">
<div class="col-sm-8">
<h2>
{% if duplicate %}
Duplicate of Event N{{ object.pk|stringformat:"05d" }}
{% elif object.pk %}
Event N{{ object.pk|stringformat:"05d" }}
{% else %}
New Event
{% endif %}
</h2>
</div>
<div class="col-sm-4 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span></button>
<div class="row">
<div class="col-sm-12">
<div class="col-sm-8">
<h2>
{% if duplicate %}
Duplicate of Event N{{ object.pk|stringformat:"05d" }}
{% elif object.pk %}
Event N{{ object.pk|stringformat:"05d" }}
{% else %}
New Event
{% endif %}
</h2>
</div>
<div class="col-sm-4 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span></button>
</div>
</div>
</div>
</div>
</div>
{% include 'form_errors.html' %}
{% render_field form.is_rig style="display: none" %}
<input type="hidden" name="{{ form.items_json.name }}" id="{{ form.items_json.id_for_label }}"
value="{{ form.items_json.value }}"/>
{% include 'form_errors.html' %}
{% render_field form.is_rig style="display: none" %}
<input type="hidden" name="{{ form.items_json.name }}" id="{{ form.items_json.id_for_label }}"
value="{{ form.items_json.value }}"/>
{# New rig buttons #}
{% if not object.pk %}
<div class="col-md-12 well">
<div class="form-group" id="is_rig-selector">
<div class="col-sm-12">
{# New rig buttons #}
{% if not object.pk %}
<div class="col-md-12 well">
<div class="form-group" id="is_rig-selector">
<div class="col-sm-12">
<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" data-toggle="tooltip"
title="Things that aren't service-based, like training, meetings and site visits.">
<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>
</div>
</div>
{% endif %}
{# Contact details #}
<div class="col-sm-12 col-md-6">
<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">
<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' %}">
{% if person %}
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'person_create' %}" class="btn btn-default modal-href"
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 }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
</div>
</div>
</div>
</div>
<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' %}" >
{% if organisation %}
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'organisation_create' %}" class="btn btn-default modal-href"
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 }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<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">
<label for="{{ form.description.id_for_label }}"
class="col-sm-4 control-label">{{ form.description.label }}</label>
{% endif %}
<div class="col-sm-8">
{% render_field form.description class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
<!-- /.col-sm-12 .col-md-6 -->
{# Event details #}
<div class="col-sm-12 col-md-6">
<div class="panel panel-default form-hws form-non_rig">
<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">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 control-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% 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)">
<label for="{{ form.venue.id_for_label }}"
class="col-sm-4 control-label">{{ form.venue.label }}</label>
{# Contact details #}
<div class="col-sm-12 col-md-6">
<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">
<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.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>
{% endif %}
<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>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'venue_create' %}" class="btn btn-default modal-href"
data-target="#{{ form.venue.id_for_label }}">
<a href="{% url 'person_create' %}" class="btn btn-default modal-href"
data-target="#{{ form.person.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 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>
@@ -282,167 +205,292 @@
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.start_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.start_date.label }}</label>
<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-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
{% render_field form.start_date class+="form-control" %}
<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' %}">
{% if organisation %}
<option value="{{ form.organisation.value }}" selected="selected"
data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
{% endif %}
</select>
</div>
<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 class+="form-control" %}
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.end_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.end_date.label }}</label>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'organisation_create' %}" class="btn btn-default modal-href"
data-target="#{{ form.organisation.id_for_label }}">
<span class="glyphicon glyphicon-plus"></span>
</a>
<a href="
<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">
{% render_field form.end_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">
{% render_field form.end_time class+="form-control" %}
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-offset-7 col-md-5">
<div class="btn-group btn-group-justified">
<btn class="btn btn-default btn-xs" onclick="setTime23Hours()">23:00</btn>
<btn class="btn btn-default btn-xs" onclick="setTime02Hours()">02:00</btn>
{% 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>
</div>
</div>
</div>
</div>
</div>
</div>
<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">
<label for="{{ form.description.id_for_label }}"
class="col-sm-4 control-label">{{ form.description.label }}</label>
{# 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">
<label for="{{ form.access_at.id_for_label }}"
class="col-sm-4 control-label">{{ form.access_at.label }}</label>
<div class="col-sm-8">
{% render_field form.description class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
<!-- /.col-sm-12 .col-md-6 -->
{# Event details #}
<div class="col-sm-12 col-md-6">
<div class="panel panel-default form-hws form-non_rig">
<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">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 control-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% render_field form.access_at class+="form-control" %}
{% render_field form.name class+="form-control" %}
</div>
</div>
<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>
<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">
{% render_field form.meet_at class+="form-control" %}
</div>
</div>
<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">
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
</label>
<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' %}">
{% if venue %}
<option value="{{ form.venue.value }}" selected="selected"
data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'venue_create' %}" class="btn btn-default modal-href"
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 }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
</div>
</div>
</div>
</div>
</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">
<label for="{{ form.status.id_for_label }}"
class="col-sm-4 control-label">{{ form.status.label }}</label>
<div class="col-sm-8">
{% render_field form.status class+="form-control" %}
</div>
</div>
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The Member in Charge of this event">
<label for="{{ form.mic.id_for_label }}"
class="col-sm-4 control-label">{{ form.mic.label }}</label>
<div class="form-group">
<label for="{{ form.start_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.start_date.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">
{% if mic %}
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
{% endif %}
</select>
<div class="row">
<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">
{% render_field form.start_time type="time" class+="form-control" %}
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.end_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.end_date.label }}</label>
<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">
{% 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">
{% render_field form.end_time type="time" class+="form-control" %}
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-offset-7 col-md-5">
<div class="btn-group btn-group-justified">
<btn class="btn btn-default btn-xs" onclick="setTime23Hours()">23:00</btn>
<btn class="btn btn-default btn-xs" onclick="setTime02Hours()">02:00</btn>
</div>
</div>
</div>
</div>
</div>
{% if object.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>
{# 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">
<label for="{{ form.access_at.id_for_label }}"
class="col-sm-4 control-label">{{ form.access_at.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">
{% if checked_in_by %}
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
{% 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">
<label for="{{ form.meet_at.id_for_label }}"
class="col-sm-4 control-label">{{ form.meet_at.label }}</label>
<div class="col-sm-8">
{% render_field form.meet_at type="datetime-local" class+="form-control" %}
</div>
</div>
<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">
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
</label>
</div>
</div>
</div>
</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">
<label for="{{ form.status.id_for_label }}"
class="col-sm-4 control-label">{{ form.status.label }}</label>
<div class="col-sm-8">
{% render_field form.status class+="form-control" %}
</div>
</div>
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The Member in Charge of this event">
<label for="{{ form.mic.id_for_label }}"
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">
{% if mic %}
<option value="{{ form.mic.value }}"
selected="selected">{{ mic.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">
<label for="{{ form.collector.id_for_label }}"
class="col-sm-4 control-label">{{ form.collector.label }}</label>
{% if object.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">
{% render_field form.collector class+="form-control" %}
<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">
{% if checked_in_by %}
<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">
<label for="{{ form.collector.id_for_label }}"
class="col-sm-4 control-label">{{ form.collector.label }}</label>
<div class="col-sm-8">
{% render_field form.collector class+="form-control" %}
</div>
</div>
</div>
<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>
<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>
<div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %}
<div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.col-sm-12 .col-md-6 -->
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
{# Notes and item shit #}
<div class="col-sm-12">
<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">
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
{% render_field form.notes class+="form-control" %}
</div>
</div>
{% include "RIGS/item_table.html" %}
<!-- /.col-sm-12 .col-md-6 -->
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
</div>
<div class="col-sm-12 text-right form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
{# Notes and item shit #}
<div class="col-sm-12">
<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">
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
{% render_field form.notes class+="form-control" %}
</div>
</div>
{% include "RIGS/item_table.html" %}
</div>
</div>
</div>
<div class="col-sm-12 text-right form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
</div>
</form>
{% include 'RIGS/item_modal.html' %}

View File

@@ -5,17 +5,17 @@
{% block title %}Events for Invoice{% endblock %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<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 ({{count}} Events, £ {{ total|floatformat:2 }})</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">
@@ -54,22 +54,20 @@
<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>
{{ object.name }}
{% if object.is_rig and perms.RIGS.view_event and object.authorised %}
<span class="glyphicon glyphicon-check"></span>
{% 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.organisation.name }}
<br>
<span class="text-muted">{{ object.internal|yesno:'Internal,External' }}</span>
</td>
<td>
{{ object.sum_total|floatformat:2 }}
<br />
<span class="text-muted">{% if not object.internal %}{{ object.purchase_order }}{% endif %}</span>
</td>
<td>{{ object.sum_total|floatformat:2 }}</td>
<td class="text-center">
{% if object.mic %}
{{ object.mic.initials }}<br>
@@ -79,9 +77,7 @@
{% endif %}
</td>
<td class="text-right">
<a href="{% url 'invoice_event' object.pk %}"
class="btn btn-default"
data-toggle="tooltip"
<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>

View File

@@ -16,29 +16,30 @@
<color id="DarkGray" RGB="#707070"/>
</initialize>
<paraStyle name="style.para" fontName="OpenSans" />
<paraStyle name="style.para" fontName="OpenSans"/>
<paraStyle name="blockPara" spaceAfter="5" spaceBefore="5"/>
<paraStyle name="style.Heading1" fontName="OpenSans" fontSize="16" leading="18" spaceAfter="0"/>
<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="page-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" />
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" />
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" />
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray"/>
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10"/>
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10"/>
<paraStyle name="style.times" fontName="OpenSans" fontSize="10"/>
<paraStyle name="style.invoice_titles" fontName="OpenSans-Bold" fontSize="10"/>
<paraStyle name="style.invoice_numbers" fontName="OpenSans" fontSize="10"/>
<blockTableStyle id="eventSpecifics">
<blockValign value="top"/>
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
</blockTableStyle>
<blockTableStyle id="headLayout">
<blockTableStyle id="invoiceLayout">
<blockValign value="top"/>
</blockTableStyle>
<blockTableStyle id="eventDetails">
@@ -71,7 +72,7 @@
</blockTableStyle>
<blockTableStyle id="signatureTable">
<blockTopPadding length="20" />
<blockTopPadding length="20"/>
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
<lineStyle kind="linebelow" start="1,0" stop="1,0" colorName="black"/>
<lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/>
@@ -79,55 +80,64 @@
</blockTableStyle>
</stylesheet>
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
<pageTemplate id="Headed" >
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<template> {# Note: page is 595x842 points (1 point=1/72in) #}
<pageTemplate id="Headed">
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
{# logo positioned 42 from left, 33 from top #}
<image file="RIGS/static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
<drawString x="137" y="780">TEC PA &amp; Lighting</drawString>
{# logo positioned 42 from left, 33 from top #}
<image file="RIGS/static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
<setFont name="OpenSans" size="9"/>
<drawString x="137" y="760">Portland Building, University Park, Nottingham, NG7 2RD</drawString>
<drawString x="137" y="746">www.nottinghamtec.co.uk</drawString>
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
<drawString x="137" y="780">TEC PA &amp; Lighting</drawString>
<setFont name="OpenSans" size="9"/>
<drawString x="137" y="760">Portland Building, University Park, Nottingham, NG7 2RD</drawString>
<drawString x="137" y="746">www.nottinghamtec.co.uk</drawString>
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
<setFont name="OpenSans" size="10" />
<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{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
</drawCenteredString>
</pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="645"/>
</pageTemplate>
<pageTemplate id="Main">
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<setFont name="OpenSans" size="10"/>
<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{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
</drawCenteredString>
</pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="727"/>
</pageTemplate>
<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>
<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>
</pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="645"/>
</pageTemplate>
<pageTemplate id="Main">
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
<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>
<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>
</pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="727"/>
</pageTemplate>
</template>
<story firstPageTemplate="Headed">
{% include "RIGS/event_print_page.xml" %}
{% include "RIGS/event_print_page.xml" %}
</story>
</document>

Some files were not shown because too many files have changed in this diff Show More