mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-03-17 09:25:57 +00:00
Compare commits
20 Commits
feature/su
...
training_l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c2314fbe | ||
|
|
d62fcc8483 | ||
|
|
b838d25cc2 | ||
|
|
ee4bd832fa | ||
|
|
8c231b931d | ||
|
|
c5970d2542 | ||
|
|
1922992633 | ||
|
|
ec4e033745 | ||
|
|
9379050e15 | ||
|
|
4689775a5f | ||
|
|
32aabe14c7 | ||
|
|
697a5977c8 | ||
|
|
80ba59c3ba | ||
|
|
c60e3fb9fe | ||
|
|
9beb7c1612 | ||
|
|
227b63e689 | ||
|
|
fa231887d2 | ||
|
|
37e5549736 | ||
|
|
adbcd0733d | ||
|
|
0b35965fa1 |
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
PyRIGS
|
||||
5
.idea/encodings.xml
generated
5
.idea/encodings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/pyrigs.iml" filepath="$PROJECT_DIR$/.idea/pyrigs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/scopes/scope_settings.xml
generated
5
.idea/scopes/scope_settings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/vcs.xml
generated
7
.idea/vcs.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
@@ -58,7 +58,7 @@ def api_key_required(function):
|
||||
|
||||
try:
|
||||
user_object = models.Profile.objects.get(pk=userid)
|
||||
except models.Profile.DoesNotExist:
|
||||
except Profile.DoesNotExist:
|
||||
return error_resp
|
||||
|
||||
if user_object.api_key != key:
|
||||
|
||||
@@ -12,6 +12,8 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
import os
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
@@ -25,10 +27,6 @@ TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
if not DEBUG:
|
||||
SECURE_SSL_REDIRECT = True # Redirect all http requests to https
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
ADMINS = (
|
||||
@@ -46,7 +44,7 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'RIGS',
|
||||
'subhire',
|
||||
'training',
|
||||
|
||||
'debug_toolbar',
|
||||
'registration',
|
||||
@@ -58,7 +56,6 @@ INSTALLED_APPS = (
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -80,6 +77,14 @@ DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
},
|
||||
# Legacy training database
|
||||
'training': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'HOST': 'localhost',
|
||||
'NAME': 'tec_training',
|
||||
'USER': 'pyrigs',
|
||||
'PASSWORD': 'pyrigs',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ urlpatterns = patterns('',
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
|
||||
url(r'^', include('RIGS.urls')),
|
||||
url('^training/', include('training.urls', namespace='training')),
|
||||
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
|
||||
name="registration_register"),
|
||||
url('^user/', include('django.contrib.auth.urls')),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://app.wercker.com/project/bykey/2dbe0517c3d83859c985ffc5a55a2802)
|
||||
[](https://app.wercker.com/project/bykey/b26100ecccdfb46a9a9056553daac5b7)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -4,32 +4,25 @@ from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import reversion
|
||||
|
||||
from django.contrib.admin import helpers
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Count
|
||||
from django.forms import ModelForm
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.Person, reversion.VersionAdmin)
|
||||
admin.site.register(models.Organisation, reversion.VersionAdmin)
|
||||
admin.site.register(models.VatRate, reversion.VersionAdmin)
|
||||
admin.site.register(models.Venue, reversion.VersionAdmin)
|
||||
admin.site.register(models.Event, reversion.VersionAdmin)
|
||||
admin.site.register(models.EventItem, reversion.VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
|
||||
|
||||
@admin.register(models.Profile)
|
||||
class ProfileAdmin(UserAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {
|
||||
'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}),
|
||||
'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||
'groups', 'user_permissions')}),
|
||||
(_('Important dates'), {
|
||||
'fields': ('last_login', 'date_joined')}),
|
||||
'fields': ('last_login', 'date_joined')}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
@@ -40,76 +33,4 @@ class ProfileAdmin(UserAdmin):
|
||||
form = forms.ProfileChangeForm
|
||||
add_form = forms.ProfileCreationForm
|
||||
|
||||
|
||||
class AssociateAdmin(reversion.VersionAdmin):
|
||||
list_display = ('id', 'name', 'number_of_events')
|
||||
search_fields = ['id', 'name']
|
||||
list_display_links = ['id', 'name']
|
||||
actions = ['merge']
|
||||
|
||||
merge_fields = ['name']
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event'))
|
||||
|
||||
def number_of_events(self, obj):
|
||||
return obj.latest_events.count()
|
||||
|
||||
number_of_events.admin_order_field = 'event_count'
|
||||
|
||||
def merge(self, request, queryset):
|
||||
if request.POST.get('post'): # Has the user confirmed which is the master record?
|
||||
try:
|
||||
masterObjectPk = request.POST.get('master')
|
||||
masterObject = queryset.get(pk=masterObjectPk)
|
||||
except ObjectDoesNotExist:
|
||||
self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR)
|
||||
return
|
||||
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
for obj in queryset.exclude(pk=masterObjectPk):
|
||||
events = obj.event_set.all()
|
||||
for event in events:
|
||||
masterObject.event_set.add(event)
|
||||
obj.delete()
|
||||
reversion.set_comment('Merging Objects')
|
||||
|
||||
self.message_user(request, "Objects successfully merged.")
|
||||
return
|
||||
else: # Present the confirmation screen
|
||||
|
||||
class TempForm(ModelForm):
|
||||
class Meta:
|
||||
model = queryset.model
|
||||
fields = self.merge_fields
|
||||
|
||||
forms = []
|
||||
for obj in queryset:
|
||||
forms.append(TempForm(instance=obj))
|
||||
|
||||
context = {
|
||||
'title': _("Are you sure?"),
|
||||
'queryset': queryset,
|
||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||
'forms': forms
|
||||
}
|
||||
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context,
|
||||
current_app=self.admin_site.name)
|
||||
|
||||
|
||||
@admin.register(models.Person)
|
||||
class PersonAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes']
|
||||
|
||||
|
||||
@admin.register(models.Venue)
|
||||
class VenueAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'three_phase_available']
|
||||
|
||||
|
||||
@admin.register(models.Organisation)
|
||||
class OrganisationAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account']
|
||||
admin.site.register(models.Profile, ProfileAdmin)
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
import cStringIO as StringIO
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import connection
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.views import generic
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib import messages
|
||||
import datetime
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
|
||||
import re
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_active.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceIndex, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for i in context['object_list']:
|
||||
total += i.balance
|
||||
context['total'] = total
|
||||
context['count'] = len(list(context['object_list']))
|
||||
return context
|
||||
template_name = 'RIGS/invoice_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
||||
sql = "SELECT * FROM " \
|
||||
"(SELECT " \
|
||||
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
||||
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
||||
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
|
||||
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
||||
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
||||
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
|
||||
"AS sub " \
|
||||
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
||||
@@ -49,7 +40,6 @@ class InvoiceIndex(generic.ListView):
|
||||
class InvoiceDetail(generic.DetailView):
|
||||
model = models.Invoice
|
||||
|
||||
|
||||
class InvoicePrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
invoice = get_object_or_404(models.Invoice, pk=pk)
|
||||
@@ -64,8 +54,8 @@ class InvoicePrint(generic.View):
|
||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||
}
|
||||
},
|
||||
'invoice': invoice,
|
||||
'current_user': request.user,
|
||||
'invoice':invoice,
|
||||
'current_user':request.user,
|
||||
})
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -82,7 +72,6 @@ class InvoicePrint(generic.View):
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
|
||||
class InvoiceVoid(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
pk = kwargs.get('pk')
|
||||
@@ -97,7 +86,6 @@ class InvoiceVoid(generic.View):
|
||||
|
||||
class InvoiceArchive(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_archive.html'
|
||||
paginate_by = 25
|
||||
|
||||
|
||||
@@ -106,33 +94,14 @@ class InvoiceWaiting(generic.ListView):
|
||||
paginate_by = 25
|
||||
template_name = 'RIGS/event_invoice.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for obj in self.get_objects():
|
||||
total += obj.sum_total
|
||||
context['total'] = total
|
||||
context['count'] = len(self.get_objects())
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_objects()
|
||||
|
||||
def get_objects(self):
|
||||
# @todo find a way to select items
|
||||
events = self.model.objects.filter(
|
||||
(
|
||||
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)
|
||||
|
||||
).order_by('start_date') \
|
||||
events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(),
|
||||
invoice__isnull=True) \
|
||||
.order_by('start_date') \
|
||||
.select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic') \
|
||||
.prefetch_related('items')
|
||||
|
||||
'venue', 'mic')
|
||||
return events
|
||||
|
||||
|
||||
@@ -150,7 +119,7 @@ class InvoiceEvent(generic.View):
|
||||
|
||||
class PaymentCreate(generic.CreateView):
|
||||
model = models.Payment
|
||||
fields = ['invoice', 'date', 'amount', 'method']
|
||||
fields = ['invoice','date','amount','method']
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(generic.CreateView, self).get_initial()
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0023_auto_20150529_0048'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='based_on',
|
||||
field=models.ForeignKey(related_name='future_events', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='RIGS.Event', null=True),
|
||||
),
|
||||
]
|
||||
115
RIGS/models.py
115
RIGS/models.py
@@ -1,32 +1,31 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import pytz
|
||||
import random
|
||||
import string
|
||||
from collections import Counter
|
||||
from decimal import Decimal
|
||||
import datetime, pytz
|
||||
|
||||
import reversion
|
||||
from django.conf import settings
|
||||
from django.db import models, connection
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
import reversion
|
||||
import string
|
||||
import random
|
||||
from collections import Counter
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
# Create your models here.
|
||||
@python_2_unicode_compatible
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
|
||||
api_key = models.CharField(max_length=40,blank=True,editable=False, null=True)
|
||||
|
||||
@classmethod
|
||||
def make_api_key(cls):
|
||||
size = 20
|
||||
chars = string.ascii_letters + string.digits
|
||||
size=20
|
||||
chars=string.ascii_letters + string.digits
|
||||
new_api_key = ''.join(random.choice(chars) for x in range(size))
|
||||
return new_api_key;
|
||||
|
||||
@@ -56,7 +55,6 @@ class Profile(AbstractUser):
|
||||
('view_profile', 'Can view Profile'),
|
||||
)
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def last_edited_at(self):
|
||||
@@ -81,11 +79,10 @@ class RevisionMixin(object):
|
||||
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)
|
||||
return "V{0} | R{1}".format(version.pk,version.revision.pk)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@reversion.register
|
||||
@python_2_unicode_compatible
|
||||
class Person(models.Model, RevisionMixin):
|
||||
@@ -100,7 +97,7 @@ class Person(models.Model, RevisionMixin):
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
if len(self.notes) > 0:
|
||||
if len(self.notes) > 0:
|
||||
string += "*"
|
||||
return string
|
||||
|
||||
@@ -111,7 +108,7 @@ class Person(models.Model, RevisionMixin):
|
||||
if e.organisation:
|
||||
o.append(e.organisation)
|
||||
|
||||
# Count up occurances and put them in descending order
|
||||
#Count up occurances and put them in descending order
|
||||
c = Counter(o)
|
||||
stats = c.most_common()
|
||||
return stats
|
||||
@@ -144,7 +141,7 @@ class Organisation(models.Model, RevisionMixin):
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
if len(self.notes) > 0:
|
||||
if len(self.notes) > 0:
|
||||
string += "*"
|
||||
return string
|
||||
|
||||
@@ -155,7 +152,7 @@ class Organisation(models.Model, RevisionMixin):
|
||||
if e.person:
|
||||
p.append(e.person)
|
||||
|
||||
# Count up occurances and put them in descending order
|
||||
#Count up occurances and put them in descending order
|
||||
c = Counter(p)
|
||||
stats = c.most_common()
|
||||
return stats
|
||||
@@ -241,18 +238,12 @@ class Venue(models.Model, RevisionMixin):
|
||||
class EventManager(models.Manager):
|
||||
def current_events(self):
|
||||
events = self.filter(
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Active dry hire
|
||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
||||
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire
|
||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||
models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
).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):
|
||||
@@ -260,17 +251,15 @@ class EventManager(models.Manager):
|
||||
(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
|
||||
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
|
||||
(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
|
||||
(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
|
||||
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||
return events
|
||||
|
||||
def rig_count(self):
|
||||
@@ -312,8 +301,7 @@ class Event(models.Model, RevisionMixin):
|
||||
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,
|
||||
null=True)
|
||||
based_on = models.ForeignKey('Event', related_name='future_events', blank=True, null=True)
|
||||
|
||||
# Timing
|
||||
start_date = models.DateField()
|
||||
@@ -339,7 +327,6 @@ class Event(models.Model, RevisionMixin):
|
||||
"""
|
||||
EX Vat
|
||||
"""
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
@@ -347,15 +334,14 @@ class Event(models.Model, RevisionMixin):
|
||||
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
|
||||
# else:
|
||||
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
|
||||
# total = self.items.raw(sql)[0]
|
||||
# if total.sum_total:
|
||||
#total = self.items.raw(sql)[0]
|
||||
#if total.sum_total:
|
||||
# return total.sum_total
|
||||
# total = 0.0
|
||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
#total = 0.0
|
||||
#for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total += item.sum
|
||||
total = EventItem.objects.filter(event=self).aggregate(
|
||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
)['sum_total']
|
||||
if total:
|
||||
return total
|
||||
@@ -372,7 +358,6 @@ class Event(models.Model, RevisionMixin):
|
||||
"""
|
||||
Inc VAT
|
||||
"""
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.sum_total + self.vat
|
||||
@@ -397,7 +382,7 @@ class Event(models.Model, RevisionMixin):
|
||||
def earliest_time(self):
|
||||
"""Finds the earliest time defined in the event - this function could return either a tzaware datetime, or a naiive date object"""
|
||||
|
||||
# Put all the datetimes in a list
|
||||
#Put all the datetimes in a list
|
||||
datetime_list = []
|
||||
|
||||
if self.access_at:
|
||||
@@ -409,20 +394,20 @@ 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
|
||||
#timezoneIssues - apply the default timezone to the naiive datetime
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
startDateTime = tz.localize(startDateTime)
|
||||
datetime_list.append(startDateTime) # then add it to the list
|
||||
datetime_list.append(startDateTime) # then add it to the list
|
||||
|
||||
earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list
|
||||
earliest = min(datetime_list).astimezone(tz) #find the earliest datetime in the list
|
||||
|
||||
# if we faked it & it's the earliest, better own up
|
||||
if startTimeFaked and earliest == startDateTime:
|
||||
if startTimeFaked and earliest==startDateTime:
|
||||
return self.start_date
|
||||
|
||||
return earliest
|
||||
@@ -436,7 +421,7 @@ class Event(models.Model, RevisionMixin):
|
||||
endDate = self.start_date
|
||||
|
||||
if self.has_end_time:
|
||||
endDateTime = datetime.datetime.combine(endDate, self.end_time)
|
||||
endDateTime = datetime.datetime.combine(endDate,self.end_time)
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
endDateTime = tz.localize(endDateTime)
|
||||
|
||||
@@ -445,6 +430,7 @@ class Event(models.Model, RevisionMixin):
|
||||
else:
|
||||
return endDate
|
||||
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -517,6 +503,15 @@ class Invoice(models.Model):
|
||||
|
||||
@property
|
||||
def payment_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
#if connection.vendor == 'postgresql':
|
||||
# sql = "SELECT SUM(amount) AS total FROM \"RIGS_payment\" WHERE invoice_id=%i" % self.id
|
||||
#else:
|
||||
# sql = "SELECT id, SUM(amount) AS total FROM RIGS_payment WHERE invoice_id=%i" % self.id
|
||||
#total = self.payment_set.raw(sql)[0]
|
||||
#if total.total:
|
||||
# return total.total
|
||||
#return 0.0
|
||||
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
|
||||
if total:
|
||||
return total
|
||||
|
||||
@@ -37,12 +37,6 @@ class RigboardIndex(generic.TemplateView):
|
||||
class WebCalendar(generic.TemplateView):
|
||||
template_name = 'RIGS/calendar.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(WebCalendar, self).get_context_data(**kwargs)
|
||||
context['view'] = kwargs.get('view','')
|
||||
context['date'] = kwargs.get('date','')
|
||||
return context
|
||||
|
||||
class EventDetail(generic.DetailView):
|
||||
model = models.Event
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ fonts_dir = "fonts"
|
||||
|
||||
# You can select your preferred output style here (can be overridden via the command line):
|
||||
# output_style = :expanded or :nested or :compact or :compressed
|
||||
output_style = :compressed
|
||||
|
||||
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||
# relative_assets = true
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@ var Konami = function (callback) {
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
/*this.iphone.load(link);*/
|
||||
this.iphone.load(link);
|
||||
},
|
||||
code: function (link) {
|
||||
window.location = link
|
||||
|
||||
@@ -75,16 +75,6 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
del {
|
||||
background-color: #f2dede;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
ins {
|
||||
background-color: #dff0d8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
position: relative;
|
||||
margin: 30px auto 0;
|
||||
@@ -147,3 +137,20 @@ ins {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
|
||||
&.affix {
|
||||
top: $navbar-height;
|
||||
}
|
||||
}
|
||||
|
||||
.anchor {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -$navbar-height - 10px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
{% endif %}
|
||||
|
||||
{% include 'RIGS/object_button.html' with object=version.new %}
|
||||
{% if version.revision.comment %}
|
||||
({{ version.revision.comment }})
|
||||
{% endif %}
|
||||
</small>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
<td>Comment</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -72,11 +71,10 @@
|
||||
<td>{{ version.revision.user.name }}</td>
|
||||
<td>
|
||||
{% if version.old == None %}
|
||||
{{version.new|to_class_name}} Created
|
||||
Object Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %} </td>
|
||||
<td>{{ version.revision.comment }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<p>The following objects will be merged. Please select the 'master' record which you would like to keep. Other records will have associated events moved to the 'master' copy, and then will be deleted.</p>
|
||||
|
||||
<table>
|
||||
{% for form in forms %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th> ID </th>
|
||||
{% for field in form %}
|
||||
<th>{{ field.label }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td><input type="radio" name="master" value="{{form.instance.pk|unlocalize}}"></td>
|
||||
<td>{{form.instance.pk}}</td>
|
||||
{% for field in form %}
|
||||
<td> {{ field.value }} </td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<div>
|
||||
{% for obj in queryset %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
|
||||
{% endfor %}
|
||||
<input type="hidden" name="action" value="merge" />
|
||||
<input type="hidden" name="post" value="yes" />
|
||||
<input type="submit" value="Merge them" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -14,28 +14,8 @@
|
||||
<script src="{% static "js/moment.min.js" %}"></script>
|
||||
<script src="{% static "js/fullcalendar.js" %}"></script>
|
||||
<script>
|
||||
|
||||
function getUrlVars() {
|
||||
var vars = {};
|
||||
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
|
||||
vars[key] = value;
|
||||
});
|
||||
return vars;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
viewToUrl = {
|
||||
'agendaWeek':'week',
|
||||
'agendaDay':'day',
|
||||
'month':'month'
|
||||
}
|
||||
viewFromUrl = {
|
||||
'week':'agendaWeek',
|
||||
'day':'agendaDay',
|
||||
'month':'month'
|
||||
}
|
||||
|
||||
$('#calendar').fullCalendar({
|
||||
editable: false,
|
||||
eventLimit: true, // allow "more" link when too many events
|
||||
@@ -134,11 +114,8 @@
|
||||
$('#day-button').addClass('active');
|
||||
break;
|
||||
}
|
||||
history.replaceState(null,null,'{% url 'web_calendar' %}'+viewToUrl[view.name]+'/'+view.intervalStart.format('YYYY-MM-DD')+'/');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// set some button listeners
|
||||
@@ -169,18 +146,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Go to the initial settings, if they're valid
|
||||
view = viewFromUrl['{{view}}'];
|
||||
$('#calendar').fullCalendar( 'changeView', view);
|
||||
|
||||
day = moment('{{date}}');
|
||||
if(day.isValid()){
|
||||
$('#calendar').fullCalendar( 'gotoDate', day);
|
||||
}else{
|
||||
console.log('Supplied date is invalid - using default')
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
<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 %}
|
||||
{{ object.base_on.name }} by {{ object.based_on.mic.name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
@@ -233,17 +233,13 @@
|
||||
{% 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 %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
}
|
||||
|
||||
function setTime02Hours() {
|
||||
var id_start = "{{ form.start_date.id_for_label }}";
|
||||
var id_end_date = "{{ form.end_date.id_for_label }}";
|
||||
var id_end_time = "{{ form.end_time.id_for_label }}";
|
||||
var id_start = "{{ form.start_date.id_for_label }}"
|
||||
var id_end_date = "{{ form.end_date.id_for_label }}"
|
||||
var id_end_time = "{{ form.end_time.id_for_label }}"
|
||||
if ($('#'+id_start).val() == $('#'+id_end_date).val()) {
|
||||
var end_date = new Date($('#'+id_end_date).val());
|
||||
end_date.setDate(end_date.getDate() + 1);
|
||||
@@ -63,12 +63,11 @@
|
||||
} else {
|
||||
$('.form-is_rig').slideDown();
|
||||
}
|
||||
$('.form-hws').css('overflow', 'visible');
|
||||
} else {
|
||||
$('#{{form.is_rig.auto_id}}').prop('checked', false);
|
||||
$('.form-is_rig').slideUp();
|
||||
}
|
||||
});
|
||||
})
|
||||
{% endif %}
|
||||
|
||||
function supportsDate() {
|
||||
@@ -107,7 +106,7 @@
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
$(document).ready(function () {
|
||||
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
|
||||
@@ -151,12 +150,10 @@
|
||||
<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.">
|
||||
<span class="col-sm-6">
|
||||
<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">
|
||||
<button type="button" class="btn btn-info col-xs-12" data-is_rig="0">Non-Rig</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,91 +1,66 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Events for Invoice{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>Events for Invoice ({{count}} Events, £ {{ total|floatformat:2 }})</h2>
|
||||
<p>These events have happened, but paperwork has not yet been sent to treasury</p>
|
||||
<h2>Events for Invoice</h2>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="table-responsive col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Start Date</th>
|
||||
<th>Event Name</th>
|
||||
<th>Client</th>
|
||||
<th>Cost</th>
|
||||
<th>MIC</th>
|
||||
<th></th>
|
||||
<table class="table table-responsive table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hiddenx-xs">#</th>
|
||||
<th>Date</th>
|
||||
<th>Event</th>
|
||||
<th>Client</th>
|
||||
<th>Cost</th>
|
||||
<th class="hidden-xs">MIC</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="
|
||||
{% if event.cancelled %}
|
||||
active
|
||||
{% elif event.confirmed and event.mic or not event.is_rig %}
|
||||
{# interpreated as (booked and mic) or is non rig #}
|
||||
success
|
||||
{% elif event.mic %}
|
||||
warning
|
||||
{% else %}
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td class="hidden-xs"><a href="{% url 'event_detail' object.pk %}" target="_blank">N{{ object.pk|stringformat:"05d" }}</a></td>
|
||||
<td>{{ object.end_date }}</td>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>
|
||||
{% if object.organisation %}
|
||||
{{ object.organisation.name }}
|
||||
{% else %}
|
||||
{{ object.person.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ object.sum_total|floatformat:2 }}</td>
|
||||
<td class="text-center">
|
||||
{{ object.mic.initials }}<br/>
|
||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_event' object.pk %}" target="_blank" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-gbp"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="
|
||||
{% if object.cancelled %}
|
||||
active text-muted
|
||||
{% elif not object.is_rig %}
|
||||
info
|
||||
{% elif object.confirmed and object.mic %}
|
||||
{# interpreated as (booked and mic) #}
|
||||
success
|
||||
{% elif object.mic %}
|
||||
warning
|
||||
{% else %}
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
|
||||
<span class="text-muted">{{ object.get_status_display }}</span></td>
|
||||
<td>{{ object.start_date }}</td>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>
|
||||
{% if object.organisation %}
|
||||
{{ object.organisation.name }}
|
||||
<br>
|
||||
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
|
||||
{% else %}
|
||||
{{ object.person.name }}
|
||||
<br>
|
||||
<span class="text-muted">External</span>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>{{ object.sum_total|floatformat:2 }}</td>
|
||||
<td class="text-center">
|
||||
{% if object.mic %}
|
||||
{{ object.mic.initials }}<br>
|
||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_event' object.pk %}" class="btn btn-default" data-toggle="tooltip" title="'Invoice' this event - click this when paperwork has been sent to treasury">
|
||||
<span class="glyphicon glyphicon-gbp"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
<keepTogether>
|
||||
<blockTable style="totalTable" colWidths="300,115,80">
|
||||
<tr>
|
||||
<td>{% if not invoice %}VAT Registration Number: 170734807{% endif %}</td>
|
||||
<td>{% if not invoice %}VAT Registration Number: 116252989{% endif %}</td>
|
||||
<td>Total (ex. VAT)</td>
|
||||
<td>£ {{ object.sum_total|floatformat:2 }}</td>
|
||||
</tr>
|
||||
@@ -209,7 +209,7 @@
|
||||
|
||||
<para>
|
||||
{% if invoice %}
|
||||
VAT Registration Number: 170734807
|
||||
VAT Registration Number: 116252989
|
||||
{% else %}
|
||||
<b>This contract is not an invoice.</b>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td>#</td>
|
||||
<td class="hidden-xs">#</td>
|
||||
<td>Event Date</td>
|
||||
<td>Event Details</td>
|
||||
<td>Event Timings</td>
|
||||
@@ -23,7 +23,7 @@
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td>{{ event.pk }}</td>
|
||||
<td class="hidden-xs">{{ event.pk }}</td>
|
||||
<td>
|
||||
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
|
||||
{% if event.end_date and event.end_date != event.start_date %}
|
||||
|
||||
@@ -109,11 +109,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td class="text-right"><strong>Balance:</strong></td>
|
||||
<td>{{ object.balance|floatformat:2 }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -5,56 +5,38 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>{% block heading %}Invoices{% endblock %}</h2>
|
||||
{% block description %}{% endblock %}
|
||||
<h2>Invoices</h2>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="table-responsive col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Event</th>
|
||||
<th>Client</th>
|
||||
<th>Event Date</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Balance</th>
|
||||
<th></th>
|
||||
<table class="table table-responsive table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Event</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Balance</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
|
||||
<td>{{ object.pk }}</td>
|
||||
<td><a href="{% url 'event_detail' object.event.pk %}" target="_blank">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }}</td>
|
||||
<td>{{ object.invoice_date }}</td>
|
||||
<td>{{ object.balance|floatformat:2 }}</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
|
||||
<td>{{ object.pk }}</td>
|
||||
<td><a href="{% url 'event_detail' object.event.pk %}">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }} <br>
|
||||
<span class="text-muted">{{ object.event.get_status_display }}</span></td>
|
||||
</td>
|
||||
<td>{% if object.event.organisation %}
|
||||
{{ object.event.organisation.name }}
|
||||
<br>
|
||||
<span class="text-muted">{{ object.event.organisation.union_account|yesno:'Internal,External' }}</span>
|
||||
{% else %}
|
||||
{{ object.event.person.name }}
|
||||
<br>
|
||||
<span class="text-muted">External</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ object.event.start_date }}</td>
|
||||
<td>{{ object.invoice_date }}</td>
|
||||
<td>{{ object.balance|floatformat:2 }}</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||
{% paginator %}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{% extends 'RIGS/invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Outstanding Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
Outstanding Invoices ({{ count }} Events, £ {{ total|floatformat:2 }})
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger</p>
|
||||
{% endblock %}
|
||||
@@ -1,13 +0,0 @@
|
||||
{% extends 'RIGS/invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Invoice Archive
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
All Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>This page displays all invoices: outstanding, paid, and void</p>
|
||||
{% endblock %}
|
||||
@@ -126,7 +126,7 @@
|
||||
<dd>
|
||||
{% if user.api_key %}
|
||||
<pre id="cal-url" data-url="http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}"></pre>
|
||||
<small><a id="gcal-link" data-url="https://support.google.com/calendar/answer/37100" href="">Click here</a> for instructions on adding to google calendar.<br/>
|
||||
<small><a id="gcal-link" data-url="http://www.google.com/calendar/render?cid=http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}" href="">Click here</a> to add to google calendar.<br/>
|
||||
To sync from google calendar to mobile device, visit <a href="https://www.google.com/calendar/syncselect" target="_blank">this page</a> on your device and tick "RIGS Calendar".</small>
|
||||
{% else %}
|
||||
<pre>No API Key Generated</pre>
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
{% for change in version.field_changes %}
|
||||
|
||||
<button title="Changes to {{ change.field.verbose_name }}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}
|
||||
{% include "RIGS/version_changes_change.html" %}
|
||||
{% endspaceless %}'>{{ change.field.verbose_name }}</button>
|
||||
<button title="Changes to {{ change.field.verbose_name }}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
|
||||
|
||||
{% if change.new %}
|
||||
<div class="alert alert-success {% if change.long %}overflow-ellipsis{% endif %}">
|
||||
{% if change.linebreaks %}
|
||||
{{change.new|linebreaksbr}}
|
||||
{% else %}
|
||||
{{change.new}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if change.old %}
|
||||
<div class="alert alert-danger {% if change.long %}overflow-ellipsis{% endif %}">
|
||||
{% if change.linebreaks %}
|
||||
{{change.old|linebreaksbr}}
|
||||
{% else %}
|
||||
{{change.old}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
'>{{ change.field.verbose_name }}</button>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for itemChange in version.item_changes %}
|
||||
<button title="Changes to item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}
|
||||
<ul class="list-group">
|
||||
<button title="Changes to item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
|
||||
{% for change in itemChange.changes %}
|
||||
<li class="list-group-item">
|
||||
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4>
|
||||
{% include "RIGS/version_changes_change.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h4>{{ change.field.verbose_name }}</h4>
|
||||
|
||||
{% endspaceless %}'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button>
|
||||
{% if change.new %}<div class="alert alert-success">{{change.new|linebreaksbr}}</div>{% endif %}
|
||||
{% if change.old %}<div class="alert alert-danger">{{change.old|linebreaksbr}}</div>{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button>
|
||||
{% endfor %}
|
||||
@@ -1,34 +0,0 @@
|
||||
{# pass in variable "change" to this template #}
|
||||
{% if change.linebreaks and change.new and change.old %}
|
||||
{% for diff in change.diff %}
|
||||
{% if diff.type == "insert" %}
|
||||
<ins>{{ diff.text|linebreaksbr }}</ins>
|
||||
{% elif diff.type == "delete" %}
|
||||
<del>{{diff.text|linebreaksbr}}</del>
|
||||
{% else %}
|
||||
<span>{{diff.text|linebreaksbr}}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if change.old %}
|
||||
<del {% if change.long %}class="overflow-ellipsis"{% endif %}>
|
||||
{% if change.linebreaks %}
|
||||
{{change.old|linebreaksbr}}
|
||||
{% else %}
|
||||
{{change.old}}
|
||||
{% endif %}
|
||||
</del>
|
||||
{% endif %}
|
||||
{% if change.new and change.old %}
|
||||
<br/>
|
||||
{% endif %}
|
||||
{% if change.new %}
|
||||
<ins {% if change.long %}class="overflow-ellipsis"{% endif %}>
|
||||
{% if change.linebreaks %}
|
||||
{{change.new|linebreaksbr}}
|
||||
{% else %}
|
||||
{{change.new}}
|
||||
{% endif %}
|
||||
</ins>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -35,7 +35,6 @@
|
||||
<td>Version ID</td>
|
||||
<td>User</td>
|
||||
<td>Changes</td>
|
||||
<td>Comment</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -47,14 +46,11 @@
|
||||
<td>{{ version.revision.user.name }}</td>
|
||||
<td>
|
||||
{% if version.old == None %}
|
||||
{{object|to_class_name}} Created
|
||||
Object Created
|
||||
{% else %}
|
||||
{% include 'RIGS/version_changes.html' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ version.revision.comment }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.test.client import Client
|
||||
from django.core import mail
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from RIGS import models
|
||||
import re
|
||||
@@ -159,7 +159,6 @@ class EventTest(LiveServerTestCase):
|
||||
|
||||
self.browser = webdriver.Firefox()
|
||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||
self.browser.maximize_window()
|
||||
os.environ['RECAPTCHA_TESTING'] = 'True'
|
||||
|
||||
def tearDown(self):
|
||||
@@ -196,245 +195,233 @@ class EventTest(LiveServerTestCase):
|
||||
self.browser.get(self.live_server_url + '/rigboard/')
|
||||
|
||||
def testRigCreate(self):
|
||||
# Requests address
|
||||
self.browser.get(self.live_server_url + '/event/create/')
|
||||
# Gets redirected to login and back
|
||||
self.authenticate('/event/create/')
|
||||
|
||||
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
# Check has slided up correctly - second save button hidden
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
self.assertFalse(save.is_displayed())
|
||||
|
||||
# Click Rig button
|
||||
self.browser.find_element_by_xpath('//button[.="Rig"]').click()
|
||||
|
||||
# Slider expands and save button visible
|
||||
self.assertTrue(save.is_displayed())
|
||||
form = self.browser.find_element_by_tag_name('form')
|
||||
|
||||
# Create new person
|
||||
add_person_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_person" and contains(@href, "add")]')
|
||||
add_person_button.click()
|
||||
|
||||
# See modal has opened
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
# Fill person form out and submit
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Person 1")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
|
||||
# See new person selected
|
||||
person1 = models.Person.objects.get(name="Test Person 1")
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Change mind and add another
|
||||
add_person_button.click()
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Person 2")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
|
||||
person2 = models.Person.objects.get(name="Test Person 2")
|
||||
self.assertEqual(person2.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
# Have to do this explcitly to force the wait for it to update
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person2.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Was right the first time, change it back
|
||||
person_select = form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]')
|
||||
person_select.send_keys(person1.name)
|
||||
person_dropped = form.find_element_by_xpath(
|
||||
'//ul[contains(@class, "inner selectpicker")]//span[contains(text(), "%s")]' % person1.name)
|
||||
person_dropped.click()
|
||||
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Edit Person 1 to have a better name
|
||||
form.find_element_by_xpath(
|
||||
'//a[@data-target="#id_person" and contains(@href, "%s/edit/")]' % person1.pk).click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Edit Person", modal.find_element_by_tag_name('h3').text)
|
||||
name = modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]')
|
||||
self.assertEqual(person1.name, name.get_attribute('value'))
|
||||
name.clear()
|
||||
name.send_keys('Rig ' + person1.name)
|
||||
name.send_keys(Keys.ENTER)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
self.assertFalse(modal.is_displayed())
|
||||
person1 = models.Person.objects.get(pk=person1.pk)
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
|
||||
# Create organisation
|
||||
add_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_organisation" and contains(@href, "add")]')
|
||||
add_button.click()
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Organisation", modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Organisation")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
|
||||
# See it is selected
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
obj = models.Organisation.objects.get(name="Test Organisation")
|
||||
self.assertEqual(obj.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_organisation"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_organisation"]//option[@selected="selected"]')
|
||||
self.assertEqual(obj.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Create veneue
|
||||
add_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_venue" and contains(@href, "add")]')
|
||||
add_button.click()
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Venue", modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Venue")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
|
||||
# See it is selected
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
obj = models.Venue.objects.get(name="Test Venue")
|
||||
self.assertEqual(obj.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_venue"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_venue"]//option[@selected="selected"]')
|
||||
self.assertEqual(obj.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Set start date/time
|
||||
form.find_element_by_id('id_start_date').send_keys('3015-05-25')
|
||||
form.find_element_by_id('id_start_time').send_keys('06:59')
|
||||
|
||||
# Set end date/time
|
||||
form.find_element_by_id('id_end_date').send_keys('4000-06-27')
|
||||
form.find_element_by_id('id_end_time').send_keys('07:00')
|
||||
|
||||
# Add item
|
||||
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
||||
wait.until(animation_is_finished())
|
||||
modal = self.browser.find_element_by_id("itemModal")
|
||||
modal.find_element_by_id("item_name").send_keys("Test Item 1")
|
||||
modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
e.send_keys(Keys.UP)
|
||||
e = modal.find_element_by_id("item_cost")
|
||||
e.send_keys("23.95")
|
||||
e.send_keys(Keys.ENTER) # enter submit
|
||||
|
||||
# Confirm item has been saved to json field
|
||||
objectitems = self.browser.execute_script("return objectitems;")
|
||||
self.assertEqual(1, len(objectitems))
|
||||
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
|
||||
self.assertEqual("Test Item 1", testitem['name'])
|
||||
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
||||
|
||||
# See new item appear in table
|
||||
row = self.browser.find_element_by_id('item--1') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text)
|
||||
self.assertIn("This is an item description", row.find_element_by_xpath('//div[@class="item-description"]').text)
|
||||
self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text)
|
||||
self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text)
|
||||
self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text)
|
||||
|
||||
# Check totals
|
||||
self.assertEqual("47.90", self.browser.find_element_by_id('sumtotal').text)
|
||||
self.assertIn("(TBC)", self.browser.find_element_by_id('vat-rate').text)
|
||||
self.assertEqual("9.58", self.browser.find_element_by_id('vat').text)
|
||||
self.assertEqual("57.48", self.browser.find_element_by_id('total').text)
|
||||
|
||||
# Attempt to save - missing title
|
||||
save.click()
|
||||
|
||||
# See error
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
self.assertTrue(error.is_displayed())
|
||||
# Should only have one error message
|
||||
self.assertEqual("Name", error.find_element_by_xpath('//dt[1]').text)
|
||||
self.assertEqual("This field is required.", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||
# don't need error so close it
|
||||
error.find_element_by_xpath('//div[contains(@class, "alert-danger")]//button[@class="close"]').click()
|
||||
try:
|
||||
# Requests address
|
||||
self.browser.get(self.live_server_url + '/event/create/')
|
||||
# Gets redirected to login and back
|
||||
self.authenticate('/event/create/')
|
||||
|
||||
wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
# Check has slided up correctly - second save button hidden
|
||||
save = self.browser.find_element_by_xpath(
|
||||
'(//button[@type="submit"])[3]')
|
||||
self.assertFalse(save.is_displayed())
|
||||
|
||||
# Click Rig button
|
||||
self.browser.find_element_by_xpath('//button[.="Rig"]').click()
|
||||
|
||||
# Slider expands and save button visible
|
||||
self.assertTrue(save.is_displayed())
|
||||
form = self.browser.find_element_by_tag_name('form')
|
||||
|
||||
# Create new person
|
||||
wait.until(animation_is_finished())
|
||||
add_person_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_person" and contains(@href, "add")]')
|
||||
add_person_button.click()
|
||||
|
||||
# See modal has opened
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
# Fill person form out and submit
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Person 1")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
|
||||
# See new person selected
|
||||
person1 = models.Person.objects.get(name="Test Person 1")
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Change mind and add another
|
||||
wait.until(animation_is_finished())
|
||||
add_person_button.click()
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Person", modal.find_element_by_tag_name('h3').text)
|
||||
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Person 2")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
|
||||
person2 = models.Person.objects.get(name="Test Person 2")
|
||||
self.assertEqual(person2.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
# Have to do this explcitly to force the wait for it to update
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person2.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Was right the first time, change it back
|
||||
person_select = form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]')
|
||||
person_select.send_keys(person1.name)
|
||||
person_dropped = form.find_element_by_xpath(
|
||||
'//ul[contains(@class, "inner selectpicker")]//span[contains(text(), "%s")]' % person1.name)
|
||||
person_dropped.click()
|
||||
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Edit Person 1 to have a better name
|
||||
form.find_element_by_xpath(
|
||||
'//a[@data-target="#id_person" and contains(@href, "%s/edit/")]' % person1.pk).click()
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Edit Person", modal.find_element_by_tag_name('h3').text)
|
||||
name = modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]')
|
||||
self.assertEqual(person1.name, name.get_attribute('value'))
|
||||
name.clear()
|
||||
name.send_keys('Rig ' + person1.name)
|
||||
name.send_keys(Keys.ENTER)
|
||||
|
||||
wait.until(animation_is_finished())
|
||||
|
||||
self.assertFalse(modal.is_displayed())
|
||||
person1 = models.Person.objects.get(pk=person1.pk)
|
||||
self.assertEqual(person1.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_person"]/span').text)
|
||||
|
||||
# Create organisation
|
||||
wait.until(animation_is_finished())
|
||||
add_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_organisation" and contains(@href, "add")]')
|
||||
add_button.click()
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Organisation", modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Organisation")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
|
||||
# See it is selected
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
obj = models.Organisation.objects.get(name="Test Organisation")
|
||||
self.assertEqual(obj.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_organisation"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_organisation"]//option[@selected="selected"]')
|
||||
self.assertEqual(obj.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Create venue
|
||||
wait.until(animation_is_finished())
|
||||
add_button = self.browser.find_element_by_xpath(
|
||||
'//a[@data-target="#id_venue" and contains(@href, "add")]')
|
||||
wait.until(animation_is_finished())
|
||||
add_button.click()
|
||||
wait.until(animation_is_finished())
|
||||
modal = self.browser.find_element_by_id('modal')
|
||||
wait.until(animation_is_finished())
|
||||
self.assertTrue(modal.is_displayed())
|
||||
self.assertIn("Add Venue", modal.find_element_by_tag_name('h3').text)
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@id="id_name"]').send_keys("Test Venue")
|
||||
modal.find_element_by_xpath(
|
||||
'//div[@id="modal"]//input[@type="submit"]').click()
|
||||
|
||||
# See it is selected
|
||||
wait.until(animation_is_finished())
|
||||
self.assertFalse(modal.is_displayed())
|
||||
obj = models.Venue.objects.get(name="Test Venue")
|
||||
self.assertEqual(obj.name, form.find_element_by_xpath(
|
||||
'//button[@data-id="id_venue"]/span').text)
|
||||
# and backend
|
||||
option = form.find_element_by_xpath(
|
||||
'//select[@id="id_venue"]//option[@selected="selected"]')
|
||||
self.assertEqual(obj.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Set start date/time
|
||||
form.find_element_by_id('id_start_date').send_keys('3015-05-25')
|
||||
form.find_element_by_id('id_start_time').send_keys('06:59')
|
||||
|
||||
# Set end date/time
|
||||
form.find_element_by_id('id_end_date').send_keys('4000-06-27')
|
||||
form.find_element_by_id('id_end_time').send_keys('07:00')
|
||||
|
||||
# Add item
|
||||
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
||||
wait.until(animation_is_finished())
|
||||
modal = self.browser.find_element_by_id("itemModal")
|
||||
modal.find_element_by_id("item_name").send_keys("Test Item 1")
|
||||
modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines")
|
||||
e = modal.find_element_by_id("item_quantity")
|
||||
e.click()
|
||||
e.send_keys(Keys.UP)
|
||||
e.send_keys(Keys.UP)
|
||||
e = modal.find_element_by_id("item_cost")
|
||||
e.send_keys("23.95")
|
||||
e.send_keys(Keys.ENTER) # enter submit
|
||||
|
||||
# Confirm item has been saved to json field
|
||||
objectitems = self.browser.execute_script("return objectitems;")
|
||||
self.assertEqual(1, len(objectitems))
|
||||
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
|
||||
self.assertEqual("Test Item 1", testitem['name'])
|
||||
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
||||
|
||||
# See new item appear in table
|
||||
row = self.browser.find_element_by_id('item--1') # ID number is known, see above
|
||||
self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text)
|
||||
self.assertIn("This is an item description", row.find_element_by_xpath('//div[@class="item-description"]').text)
|
||||
self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text)
|
||||
self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text)
|
||||
self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text)
|
||||
|
||||
# Check totals
|
||||
self.assertEqual("47.90", self.browser.find_element_by_id('sumtotal').text)
|
||||
self.assertIn("(TBC)", self.browser.find_element_by_id('vat-rate').text)
|
||||
self.assertEqual("9.58", self.browser.find_element_by_id('vat').text)
|
||||
self.assertEqual("57.48", self.browser.find_element_by_id('total').text)
|
||||
|
||||
# Attempt to save - missing title
|
||||
save.click()
|
||||
|
||||
# See error
|
||||
error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]')
|
||||
self.assertTrue(error.is_displayed())
|
||||
# Should only have one error message
|
||||
self.assertEqual("Name", error.find_element_by_xpath('//dt[1]').text)
|
||||
self.assertEqual("This field is required.", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||
# don't need error so close it
|
||||
error.find_element_by_xpath('//div[contains(@class, "alert-danger")]//button[@class="close"]').click()
|
||||
try:
|
||||
self.assertFalse(error.is_displayed())
|
||||
except StaleElementReferenceException:
|
||||
pass
|
||||
except:
|
||||
self.assertFail("Element does not appear to have been deleted")
|
||||
|
||||
# Check at least some data is preserved. Some = all will be there
|
||||
option = self.browser.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
e.send_keys(Keys.ENTER)
|
||||
|
||||
# See redirected to success page
|
||||
successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||
event = models.Event.objects.get(name='Test Event Name')
|
||||
self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle)
|
||||
except WebDriverException:
|
||||
# This is a dirty workaround for wercker being a bit funny and not running it correctly.
|
||||
# Waiting for wercker to get back to me about this
|
||||
self.assertFalse(error.is_displayed())
|
||||
except StaleElementReferenceException:
|
||||
pass
|
||||
except:
|
||||
self.assertFail("Element does not appear to have been deleted")
|
||||
|
||||
# Check at least some data is preserved. Some = all will be there
|
||||
option = self.browser.find_element_by_xpath(
|
||||
'//select[@id="id_person"]//option[@selected="selected"]')
|
||||
self.assertEqual(person1.pk, int(option.get_attribute("value")))
|
||||
|
||||
# Set title
|
||||
e = self.browser.find_element_by_id('id_name')
|
||||
e.send_keys('Test Event Name')
|
||||
e.send_keys(Keys.ENTER)
|
||||
|
||||
# See redirected to success page
|
||||
event = models.Event.objects.get(name='Test Event Name')
|
||||
self.assertIn("N0000%d | Test Event Name"%event.pk, self.browser.find_element_by_xpath('//h1').text)
|
||||
|
||||
def testEventDuplicate(self):
|
||||
testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end")
|
||||
@@ -621,9 +608,8 @@ class EventTest(LiveServerTestCase):
|
||||
save.click()
|
||||
|
||||
# See redirected to success page
|
||||
successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||
event = models.Event.objects.get(name='Test Event Name')
|
||||
self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle)
|
||||
self.assertIn("N0000%d | Test Event Name"%event.pk, self.browser.find_element_by_xpath('//h1').text)
|
||||
|
||||
def testRigNonRig(self):
|
||||
self.browser.get(self.live_server_url + '/event/create/')
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from RIGS import models
|
||||
from datetime import date, timedelta, datetime, time
|
||||
from datetime import date, timedelta
|
||||
from decimal import *
|
||||
|
||||
|
||||
@@ -203,72 +201,6 @@ class EventTestCase(TestCase):
|
||||
event.status = models.Event.PROVISIONAL
|
||||
event.save()
|
||||
|
||||
def test_earliest_time(self):
|
||||
event = models.Event(name="TE ET", start_date=date(2016, 01, 01))
|
||||
|
||||
# Just a start date
|
||||
self.assertEqual(event.earliest_time, date(2016, 01, 01))
|
||||
|
||||
# With start time
|
||||
event.start_time = time(9, 00)
|
||||
self.assertEqual(event.earliest_time, self.create_datetime(2016, 1, 1, 9, 00))
|
||||
|
||||
# With access time
|
||||
event.access_at = self.create_datetime(2015, 12, 03, 9, 57)
|
||||
self.assertEqual(event.earliest_time, event.access_at)
|
||||
|
||||
# With meet time
|
||||
event.meet_at = self.create_datetime(2015, 12, 03, 9, 55)
|
||||
self.assertEqual(event.earliest_time, event.meet_at)
|
||||
|
||||
# Check order isn't important
|
||||
event.start_date = date(2015, 12, 03)
|
||||
self.assertEqual(event.earliest_time, self.create_datetime(2015, 12, 03, 9, 00))
|
||||
|
||||
def test_latest_time(self):
|
||||
event = models.Event(name="TE LT", start_date=date(2016, 01, 01))
|
||||
|
||||
# Just start date
|
||||
self.assertEqual(event.latest_time, event.start_date)
|
||||
|
||||
# Just end date
|
||||
event.end_date = date(2016, 1, 2)
|
||||
self.assertEqual(event.latest_time, event.end_date)
|
||||
|
||||
# With end time
|
||||
event.end_time = time(23, 00)
|
||||
self.assertEqual(event.latest_time, self.create_datetime(2016, 1, 2, 23, 00))
|
||||
|
||||
def test_in_bounds(self):
|
||||
manager = models.Event.objects
|
||||
events = [
|
||||
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
|
||||
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
|
||||
|
||||
# basic checks
|
||||
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
|
||||
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
|
||||
manager.create(name='TE IB4', start_date='2016-01-04', access_at='2016-01-03'),
|
||||
manager.create(name='TE IB5', start_date='2016-01-04', meet_at='2016-01-02'),
|
||||
|
||||
# negative check
|
||||
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
|
||||
]
|
||||
|
||||
in_bounds = manager.events_in_bounds(datetime(2016, 1, 2), datetime(2016, 1, 3))
|
||||
self.assertIn(events[0], in_bounds)
|
||||
self.assertIn(events[1], in_bounds)
|
||||
self.assertIn(events[2], in_bounds)
|
||||
self.assertIn(events[3], in_bounds)
|
||||
self.assertIn(events[4], in_bounds)
|
||||
self.assertIn(events[5], in_bounds)
|
||||
|
||||
self.assertNotIn(events[6], in_bounds)
|
||||
|
||||
def create_datetime(self, year, month, day, hour, min):
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
return tz.localize(datetime(year, month, day, hour, min))
|
||||
|
||||
|
||||
class EventItemTestCase(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from datetime import date
|
||||
|
||||
from RIGS import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
|
||||
class TestAdminMergeObjects(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
|
||||
is_active=True, is_staff=True)
|
||||
|
||||
cls.persons = {
|
||||
1: models.Person.objects.create(name="Person 1"),
|
||||
2: models.Person.objects.create(name="Person 2"),
|
||||
3: models.Person.objects.create(name="Person 3"),
|
||||
}
|
||||
|
||||
cls.organisations = {
|
||||
1: models.Organisation.objects.create(name="Organisation 1"),
|
||||
2: models.Organisation.objects.create(name="Organisation 2"),
|
||||
3: models.Organisation.objects.create(name="Organisation 3"),
|
||||
}
|
||||
|
||||
cls.venues = {
|
||||
1: models.Venue.objects.create(name="Venue 1"),
|
||||
2: models.Venue.objects.create(name="Venue 2"),
|
||||
3: models.Venue.objects.create(name="Venue 3"),
|
||||
}
|
||||
|
||||
cls.events = {
|
||||
1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1],
|
||||
organisation=cls.organisations[3], venue=cls.venues[2]),
|
||||
2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2],
|
||||
organisation=cls.organisations[2], venue=cls.venues[3]),
|
||||
3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3],
|
||||
organisation=cls.organisations[1], venue=cls.venues[1]),
|
||||
4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3],
|
||||
organisation=cls.organisations[3], venue=cls.venues[3]),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test_merge_confirmation(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
data = {
|
||||
'action': 'merge',
|
||||
'_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()]
|
||||
|
||||
}
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
|
||||
self.assertContains(response, "The following objects will be merged")
|
||||
for key, venue in self.venues.iteritems():
|
||||
self.assertContains(response, venue.name)
|
||||
|
||||
def test_merge_no_master(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()],
|
||||
'post': 'yes',
|
||||
}
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
|
||||
self.assertContains(response, "An error occured")
|
||||
|
||||
def test_venue_merge(self):
|
||||
change_url = reverse('admin:RIGS_venue_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.venues[1].pk), unicode(self.venues[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.venues[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.venues[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk))
|
||||
|
||||
# Check the un-needed venue has been disposed of
|
||||
self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(models.Venue.objects.get(pk=self.venues[3].pk), self.venues[3])
|
||||
|
||||
# Check the events have been moved to the master venue
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.venue == self.venues[3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.venue, self.venues[1])
|
||||
|
||||
def test_person_merge(self):
|
||||
change_url = reverse('admin:RIGS_person_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.persons[1].pk), unicode(self.persons[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.persons[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.persons[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk))
|
||||
|
||||
# Check the un-needed people have been disposed of
|
||||
self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(models.Person.objects.get(pk=self.persons[3].pk), self.persons[3])
|
||||
|
||||
# Check the events have been moved to the master person
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.person == self.persons[3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.person, self.persons[1])
|
||||
|
||||
def test_organisation_merge(self):
|
||||
change_url = reverse('admin:RIGS_organisation_changelist')
|
||||
|
||||
data = {'action': 'merge',
|
||||
'_selected_action': [unicode(self.organisations[1].pk), unicode(self.organisations[2].pk)],
|
||||
'post': 'yes',
|
||||
'master': self.organisations[1].pk
|
||||
}
|
||||
|
||||
response = self.client.post(change_url, data, follow=True)
|
||||
self.assertContains(response, "Objects successfully merged")
|
||||
self.assertContains(response, self.organisations[1].name)
|
||||
|
||||
# Check the master copy still exists
|
||||
self.assertTrue(models.Organisation.objects.get(pk=self.organisations[1].pk))
|
||||
|
||||
# Check the un-needed organisations have been disposed of
|
||||
self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[2].pk)
|
||||
|
||||
# Check the one we didn't delete is still there
|
||||
self.assertEqual(models.Organisation.objects.get(pk=self.organisations[3].pk), self.organisations[3])
|
||||
|
||||
# Check the events have been moved to the master organisation
|
||||
for key, event in self.events.iteritems():
|
||||
updatedEvent = models.Event.objects.get(pk=event.pk)
|
||||
if event.organisation == self.organisations[3]: # The one we left in place
|
||||
continue
|
||||
self.assertEqual(updatedEvent.organisation, self.organisations[1])
|
||||
@@ -69,8 +69,6 @@ urlpatterns = patterns('',
|
||||
# Rigboard
|
||||
url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
||||
url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
|
||||
url(r'^rigboard/activity/$',
|
||||
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.db.models import Q
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
# Versioning
|
||||
import reversion
|
||||
import simplejson
|
||||
from reversion.models import Version
|
||||
from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type
|
||||
from django.db.models import IntegerField, EmailField, TextField
|
||||
from diff_match_patch import diff_match_patch
|
||||
from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.db.models import ForeignKey, IntegerField, EmailField, TextField
|
||||
|
||||
from RIGS import models
|
||||
from RIGS import models, forms
|
||||
import datetime
|
||||
import re
|
||||
|
||||
logger = logging.getLogger('tec.pyrigs')
|
||||
|
||||
@@ -20,10 +28,11 @@ logger = logging.getLogger('tec.pyrigs')
|
||||
def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
# recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects
|
||||
try:
|
||||
theFields = oldObj._meta.fields # This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available)
|
||||
theFields = oldObj._meta.fields #This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available)
|
||||
except AttributeError:
|
||||
theFields = newObj._meta.fields
|
||||
|
||||
|
||||
class FieldCompare(object):
|
||||
def __init__(self, field=None, old=None, new=None):
|
||||
self.field = field
|
||||
@@ -55,54 +64,27 @@ def model_compare(oldObj, newObj, excluded_keys=[]):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def diff(self):
|
||||
oldText = unicode(self.display_value(self._old)) or ""
|
||||
newText = unicode(self.display_value(self._new)) or ""
|
||||
dmp = diff_match_patch()
|
||||
diffs = dmp.diff_main(oldText, newText)
|
||||
dmp.diff_cleanupSemantic(diffs)
|
||||
|
||||
outputDiffs = []
|
||||
|
||||
for (op, data) in diffs:
|
||||
if op == dmp.DIFF_INSERT:
|
||||
outputDiffs.append({'type': 'insert', 'text': data})
|
||||
elif op == dmp.DIFF_DELETE:
|
||||
outputDiffs.append({'type': 'delete', 'text': data})
|
||||
elif op == dmp.DIFF_EQUAL:
|
||||
outputDiffs.append({'type': 'equal', 'text': data})
|
||||
return outputDiffs
|
||||
|
||||
changes = []
|
||||
|
||||
for thisField in theFields:
|
||||
name = thisField.name
|
||||
|
||||
if name in excluded_keys:
|
||||
continue # if we're excluding this field, skip over it
|
||||
continue # if we're excluding this field, skip over it
|
||||
|
||||
try:
|
||||
oldValue = getattr(oldObj, name, None)
|
||||
except ObjectDoesNotExist:
|
||||
oldValue = None
|
||||
|
||||
try:
|
||||
newValue = getattr(newObj, name, None)
|
||||
except ObjectDoesNotExist:
|
||||
newValue = None
|
||||
oldValue = getattr(oldObj, name, None)
|
||||
newValue = getattr(newObj, name, None)
|
||||
|
||||
try:
|
||||
bothBlank = (not oldValue) and (not newValue)
|
||||
if oldValue != newValue and not bothBlank:
|
||||
compare = FieldCompare(thisField, oldValue, newValue)
|
||||
compare = FieldCompare(thisField,oldValue,newValue)
|
||||
changes.append(compare)
|
||||
except TypeError: # logs issues with naive vs tz-aware datetimes
|
||||
except TypeError: # logs issues with naive vs tz-aware datetimes
|
||||
logger.error('TypeError when comparing models')
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def compare_event_items(old, new):
|
||||
# Recieves two event version objects and compares their items, returns an array of ItemCompare objects
|
||||
|
||||
@@ -117,43 +99,39 @@ def compare_event_items(old, new):
|
||||
self.changes = changes
|
||||
|
||||
# Build some dicts of what we have
|
||||
item_dict = {} # build a list of items, key is the item_pk
|
||||
for version in old_item_versions: # put all the old versions in a list
|
||||
if version.field_dict["event"] == old.object_id_int:
|
||||
compare = ItemCompare(old=version.object_version.object)
|
||||
item_dict[version.object_id] = compare
|
||||
item_dict = {} # build a list of items, key is the item_pk
|
||||
for version in old_item_versions: # put all the old versions in a list
|
||||
compare = ItemCompare(old=version.object_version.object)
|
||||
item_dict[version.object_id] = compare
|
||||
|
||||
for version in new_item_versions: # go through the new versions
|
||||
if version.field_dict["event"] == new.object_id_int:
|
||||
try:
|
||||
compare = item_dict[version.object_id] # see if there's a matching old version
|
||||
compare.new = version.object_version.object # then add the new version to the dictionary
|
||||
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
|
||||
compare = ItemCompare(new=version.object_version.object)
|
||||
for version in new_item_versions: # go through the new versions
|
||||
try:
|
||||
compare = item_dict[version.object_id] # see if there's a matching old version
|
||||
compare.new = version.object_version.object # then add the new version to the dictionary
|
||||
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
|
||||
compare = ItemCompare(new=version.object_version.object)
|
||||
|
||||
item_dict[version.object_id] = compare # update the dictionary with the changes
|
||||
item_dict[version.object_id] = compare # update the dictionary with the changes
|
||||
|
||||
changes = []
|
||||
for (_, compare) in item_dict.items():
|
||||
compare.changes = model_compare(compare.old, compare.new, ['id', 'event', 'order']) # see what's changed
|
||||
compare.changes = model_compare(compare.old, compare.new, ['id','event','order']) # see what's changed
|
||||
if len(compare.changes) >= 1:
|
||||
changes.append(compare) # transfer into a sequential array to make it easier to deal with later
|
||||
changes.append(compare) # transfer into a sequential array to make it easier to deal with later
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def get_versions_for_model(models):
|
||||
content_types = []
|
||||
for model in models:
|
||||
content_types.append(ContentType.objects.get_for_model(model))
|
||||
|
||||
versions = reversion.models.Version.objects.filter(
|
||||
content_type__in=content_types,
|
||||
content_type__in = content_types,
|
||||
).select_related("revision").order_by("-pk")
|
||||
|
||||
return versions
|
||||
|
||||
|
||||
def get_previous_version(version):
|
||||
thisId = version.object_id
|
||||
thisVersionId = version.pk
|
||||
@@ -161,17 +139,15 @@ def get_previous_version(version):
|
||||
versions = reversion.get_for_object_reference(version.content_type.model_class(), thisId)
|
||||
|
||||
try:
|
||||
previousVersions = versions.filter(revision_id__lt=version.revision_id).latest(
|
||||
field_name='revision__date_created')
|
||||
previousVersions = versions.filter(revision_id__lt=version.revision_id).latest(field_name='revision__date_created')
|
||||
except ObjectDoesNotExist:
|
||||
return False
|
||||
|
||||
return previousVersions
|
||||
|
||||
|
||||
def get_changes_for_version(newVersion, oldVersion=None):
|
||||
# Pass in a previous version if you already know it (for efficiancy)
|
||||
# if not provided then it will be looked up in the database
|
||||
#Pass in a previous version if you already know it (for efficiancy)
|
||||
#if not provided then it will be looked up in the database
|
||||
|
||||
if oldVersion == None:
|
||||
oldVersion = get_previous_version(newVersion)
|
||||
@@ -197,7 +173,6 @@ def get_changes_for_version(newVersion, oldVersion=None):
|
||||
|
||||
return compare
|
||||
|
||||
|
||||
class VersionHistory(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/version_history.html"
|
||||
@@ -222,10 +197,10 @@ class VersionHistory(generic.ListView):
|
||||
items = []
|
||||
|
||||
for versionNo, thisVersion in enumerate(versions):
|
||||
if versionNo >= len(versions) - 1:
|
||||
if versionNo >= len(versions)-1:
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
else:
|
||||
thisItem = get_changes_for_version(thisVersion, versions[versionNo + 1])
|
||||
thisItem = get_changes_for_version(thisVersion, versions[versionNo+1])
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
@@ -234,17 +209,17 @@ class VersionHistory(generic.ListView):
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ActivityTable(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/activity_table.html"
|
||||
paginate_by = 25
|
||||
|
||||
def get_queryset(self):
|
||||
versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation])
|
||||
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
|
||||
return versions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
# Call the base implementation first to get a context
|
||||
context = super(ActivityTable, self).get_context_data(**kwargs)
|
||||
|
||||
@@ -254,25 +229,24 @@ class ActivityTable(generic.ListView):
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
items.append(thisItem)
|
||||
|
||||
context['object_list'] = items
|
||||
context ['object_list'] = items
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ActivityFeed(generic.ListView):
|
||||
model = reversion.revisions.Version
|
||||
template_name = "RIGS/activity_feed_data.html"
|
||||
paginate_by = 25
|
||||
|
||||
def get_queryset(self):
|
||||
versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation])
|
||||
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
|
||||
return versions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
maxTimeDelta = []
|
||||
|
||||
maxTimeDelta.append({'maxAge': datetime.timedelta(days=1), 'group': datetime.timedelta(hours=1)})
|
||||
maxTimeDelta.append({'maxAge': None, 'group': datetime.timedelta(days=1)})
|
||||
maxTimeDelta.append({ 'maxAge':datetime.timedelta(days=1), 'group':datetime.timedelta(hours=1)})
|
||||
maxTimeDelta.append({ 'maxAge':None, 'group':datetime.timedelta(days=1)})
|
||||
|
||||
# Call the base implementation first to get a context
|
||||
context = super(ActivityFeed, self).get_context_data(**kwargs)
|
||||
@@ -283,9 +257,8 @@ class ActivityFeed(generic.ListView):
|
||||
thisItem = get_changes_for_version(thisVersion, None)
|
||||
if thisItem['item_changes'] or thisItem['field_changes'] or thisItem['old'] == None:
|
||||
thisItem['withPrevious'] = False
|
||||
if len(items) >= 1:
|
||||
timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem[
|
||||
'revision'].date_created
|
||||
if len(items)>=1:
|
||||
timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem['revision'].date_created
|
||||
timeDiff = items[-1]['revision'].date_created - thisItem['revision'].date_created
|
||||
timeTogether = False
|
||||
for params in maxTimeDelta:
|
||||
@@ -298,6 +271,7 @@ class ActivityFeed(generic.ListView):
|
||||
|
||||
items.append(thisItem)
|
||||
|
||||
context['object_list'] = items
|
||||
context ['object_list'] = items
|
||||
|
||||
|
||||
return context
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
@@ -1,4 +1,3 @@
|
||||
diff-match-patch==20121119
|
||||
dj-database-url==0.3.0
|
||||
dj-static==0.0.6
|
||||
Django==1.8.2
|
||||
@@ -20,7 +19,7 @@ python-dateutil==2.4.2
|
||||
pytz==2015.4
|
||||
raven==5.8.1
|
||||
reportlab==3.1.44
|
||||
selenium==2.53.1
|
||||
selenium==2.46.0
|
||||
simplejson==3.7.2
|
||||
six==1.9.0
|
||||
sqlparse==0.1.15
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from django.contrib import admin
|
||||
import models
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.Hire)
|
||||
admin.site.register(models.Provider)
|
||||
@@ -1,43 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Hire',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(null=True, blank=True)),
|
||||
('start_date', models.DateField()),
|
||||
('end_date', models.DateField()),
|
||||
('start_transport', models.IntegerField(blank=True, null=True, choices=[(0, b'TEC Transport'), (1, b'Provider Transports')])),
|
||||
('mic', models.ForeignKey(related_name='hire_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('phone', models.CharField(max_length=15, null=True, blank=True)),
|
||||
('email', models.EmailField(max_length=254, null=True, blank=True)),
|
||||
('address', models.TextField(null=True, blank=True)),
|
||||
('notes', models.TextField(null=True, blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hire',
|
||||
name='provider',
|
||||
field=models.ForeignKey(blank=True, to='subhire.Provider', null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
import reversion
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
# Create your models here.
|
||||
class Hire(models.Model):
|
||||
WE_TRANSPORT = 0
|
||||
THEY_TRANSPORT = 1
|
||||
TRANSPORT_CHOICES = (
|
||||
(WE_TRANSPORT, 'TEC Transport'),
|
||||
(THEY_TRANSPORT, 'Provider Transports'),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
provider = models.ForeignKey('Provider', blank=True, null=True)
|
||||
|
||||
start_date = models.DateField()
|
||||
end_date = models.DateField()
|
||||
|
||||
start_transport = models.IntegerField(
|
||||
choices=TRANSPORT_CHOICES, blank=True, null=True)
|
||||
|
||||
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='hire_mic', blank=True, null=True,
|
||||
verbose_name="MIC")
|
||||
|
||||
class Provider(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||
email = models.EmailField(blank=True, null=True)
|
||||
|
||||
address = models.TextField(blank=True, null=True)
|
||||
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def latest_hires(self):
|
||||
return self.hire_set.order_by('-start_date').select_related()
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -14,16 +14,14 @@
|
||||
|
||||
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
|
||||
type='text/css'>
|
||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet' type='text/css'>
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.8.3.min.js"
|
||||
integrity="sha256-YcbK69I5IXQftf/mYD8WY0/KmEDCv1asggHpJk1trM8=" crossorigin="anonymous"></script>
|
||||
<script src="//code.jquery.com/jquery-latest.min.js"></script>
|
||||
<script src="https://cdn.ravenjs.com/1.3.0/jquery,native/raven.min.js"></script>
|
||||
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
|
||||
{% block preload_js %}
|
||||
@@ -48,40 +46,32 @@
|
||||
<div class="navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span>
|
||||
Rigboard</a></li>
|
||||
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span>
|
||||
Archive</a></li>
|
||||
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span>
|
||||
Calendar</a></li>
|
||||
{% if perms.RIGS.view_event %}
|
||||
<li><a href="{% url 'activity_table' %}"><span
|
||||
class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.add_event %}
|
||||
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span>
|
||||
New Event</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a></li>
|
||||
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li>
|
||||
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a></li>
|
||||
{% if perms.RIGS.view_event %}
|
||||
<li><a href="{% url 'activity_table' %}"><span class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.add_event %}
|
||||
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a></li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a></li>
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<li><a href="{% url 'invoice_waiting' %}"><span
|
||||
class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
|
||||
<li><a href="{% url 'invoice_waiting' %}"><span class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Outstanding</a>
|
||||
</li>
|
||||
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
|
||||
Archive</a></li>
|
||||
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -154,6 +144,10 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="col-sm-12 text-center">
|
||||
Reminder: Please consider carefully before booking rigs at this moment
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
@@ -187,7 +181,7 @@
|
||||
jQuery(document).on('click', '.modal-href', function (e) {
|
||||
$link = jQuery(this);
|
||||
// Anti modal inception
|
||||
if ($link.parents('#modal').length == 0) {
|
||||
if($link.parents('#modal').length == 0) {
|
||||
e.preventDefault();
|
||||
modaltarget = $link.data('target');
|
||||
modalobject = "";
|
||||
@@ -199,11 +193,11 @@
|
||||
|
||||
|
||||
var easter_egg = new Konami();
|
||||
easter_egg.code = function () {
|
||||
easter_egg.code = function() {
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.type='text/javascript';
|
||||
document.body.appendChild(s);
|
||||
s.src = '{% static "js/asteroids.min.js"%}';
|
||||
s.src='{% static "js/asteroids.min.js"%}';
|
||||
ga('send', 'event', 'easter_egg', 'activated');
|
||||
}
|
||||
easter_egg.load();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form action="{% url 'login' %}" method="post" role="form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="id_username">{{ form.username.label }}</label>
|
||||
{% render_field form.username class+="form-control" placeholder=form.username.label autofocus="" %}
|
||||
{% render_field form.username class+="form-control" placeholder=form.username.label %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
|
||||
|
||||
8
training/admin.py
Normal file
8
training/admin.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
from training import models
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.TrainingCategory)
|
||||
admin.site.register(models.TrainingItem)
|
||||
admin.site.register(models.TrainingRecord)
|
||||
admin.site.register(models.TrainingLevelRecord)
|
||||
762
training/exportSQL3.txt
Normal file
762
training/exportSQL3.txt
Normal file
@@ -0,0 +1,762 @@
|
||||
Option Compare Database
|
||||
Option Explicit
|
||||
|
||||
' exportSQL version 3.2-dev
|
||||
' www.rot13.org/~dpavlin/projects.html#sql
|
||||
'
|
||||
' based on exportSQL version 2.0 from www.cynergi.net/prod/exportsql/
|
||||
'
|
||||
' (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
' (C) Pedro Freire - pedro.freire@cynergi.net (do not add to mailing lists without permission)
|
||||
' (c) 2000-2001 Dobrica Pavlinusic <dpavlin@rot13.org> - added PostgreSQL support
|
||||
'
|
||||
' This code is provided free for anyone's use and is therefore without guarantee or support.
|
||||
' This does NOT mean CYNERGI delegates its copyright to anyone using it! You may change the
|
||||
' code in any way, as long as this notice remains on the code and CYNERGI is notified (if you
|
||||
' publish the changes: if your changes/corrections prove valuable and are added to the code,
|
||||
' you will be listed in a credit list on this file).
|
||||
'
|
||||
' You may NOT sell this as part of a non-free package:
|
||||
' IF YOU HAVE PAID FOR THIS CODE, YOU HAVE BEEN ROBBED! CONTACT admin@cynergi.net!
|
||||
|
||||
' MODULE
|
||||
' "exportSQL"
|
||||
'
|
||||
' GOAL
|
||||
' Export all tables in a MS-Access database file to 2 text files:
|
||||
' one containing SQL instructions to delete the new tables to be created,
|
||||
' and the other with SQL instructions to create and insert data into
|
||||
' the new tables. The table structure and data will resemble as much as
|
||||
' possible the current Access database.
|
||||
'
|
||||
' HOW TO USE
|
||||
' Copy-and-paste this text file into an Access module and run the first
|
||||
' (and only public) function. in more detail, you:
|
||||
' * Open the Access .mdb file you wish to export
|
||||
' * in the default database objects window, click on "Modules", and then on "New"
|
||||
' * The code window that opens has some pre-written text (code). Delete it.
|
||||
' * Copy-and-paste this entire file to the code module window
|
||||
' * If you are using Microsoft Access 2000 you will have to make
|
||||
' one additional step: go into Tools/References and check following
|
||||
' component: Microsoft DAO Object 3.6 Library and uncheck Microsoft
|
||||
' ActiveX Data Objects Library
|
||||
' * You may hit the compile button (looks like 3 sheets of paper with an arrow on
|
||||
' top of them, pressing down on them), or select Debug, Compile Loaded Modules
|
||||
' from the top menu, just to make sure there are no errors, and that this code
|
||||
' works on your Access version (it works on Access'97 and should work on Access'95)
|
||||
' * Close the code module window - windows will prompt you to save the code:
|
||||
' answer "Yes", and when promped for a name for the module, type anything
|
||||
' (say, "MexportSQL")
|
||||
' The module is now part of your Access database. To run the export, you:
|
||||
' * Re-open the code module (by double-clicking on it, or clicking "Design"
|
||||
' with it selected). Move the cursor to where the first "Function" keyword appears.
|
||||
' Press F5 or select Run, Go/Continue from the top menu.
|
||||
' * Alternativelly, click on "Macros" on the database objects window,
|
||||
' and then on "New". On the macro window, select "RunCode" as the macro action,
|
||||
' and "exportSQL" as the function name, bellow. Save the macro similarly to the
|
||||
' module, and this time double-clicking on it, or clicking "Run" will run the export.
|
||||
'
|
||||
' BEFORE RUNNING THE EXPORT
|
||||
' Before running the export, be sure to check out the Export Options just bellow this
|
||||
' text, and change any according to your wishes and specs.
|
||||
'
|
||||
' TECH DATA
|
||||
' Public identifiers:
|
||||
' * Only one: "exportSQL", a function taking and returning no arguments. It runs the export.
|
||||
' Functionallity:
|
||||
' * Can export to mSQL v1, mSQL v2, MySQL or PostgreSQL recognised SQL statements
|
||||
' * Excellent respect for name conversion, namespace verification, type matching, etc.
|
||||
' * Detects default values "=Now()", "=Date()" and "=Time()" to create types like "TIMESTAMP"
|
||||
' * Fully configurable via private constants on top of code
|
||||
' * Exports two files: one for erasures, another for creations (useful when updating dbs)
|
||||
' * Generates compatibility warnings when necessary
|
||||
' * Code and generated files are paragraphed and easy to read
|
||||
' * Access text and memo fields can have any type of line termination: \n\r, \r\n, \n or \r
|
||||
' * Properly escapes text and memo fields, besides all types of binary fields
|
||||
' * Closes all open objects and files on error
|
||||
' * Known bugs / incomplete constructs are signalled with comments starting with "!!!!"
|
||||
' * Two alternatives on absent date/time type on mSQL: REAL or CHAR field
|
||||
' * Exports Primary key and Indexes for PostgreSQL
|
||||
' * Inserts Constrains as comments in SQL dump
|
||||
|
||||
' TODO:
|
||||
' + fix fields with non-valid characters (-, /, and friend)
|
||||
' + fix CR/LF in output
|
||||
' + fix boolean fields
|
||||
' + output of create table in separate file
|
||||
' - create index (FIX)
|
||||
|
||||
' Export Options - change at will
|
||||
|
||||
Private Const DB_ENGINE As String = "Pg" ' USE ONLY "M1" (mSQL v1), "M2" (mSQL v2), "MY" (MySQL) or "Pg" (PostgreSQL)
|
||||
Private Const DB_NAME As String = "" ' Use empty string for current. Else use filename or DSN name of database to export
|
||||
Private Const DB_CONNECT As String = "" ' Used only if above string is not empty
|
||||
Private Const MSQL_64kb_AVG As Long = 2048 ' ALWAYS < 65536 (to be consistent with MS Access). Set to max expected size of Access MEMO field (to preserve space in mSQL v1)
|
||||
Private Const WS_REPLACEMENT As String = "_" ' Use "" to simply eat whitespaces in identifiers (table and field names)
|
||||
Private Const IDENT_MAX_SIZE As Integer = 19 ' Suggest 64. Max size of identifiers (table and field names)
|
||||
Private Const PREFIX_ON_KEYWORD As String = "_" ' Prefix to add to identifier, if it is a reserved word
|
||||
Private Const SUFFIX_ON_KEYWORD As String = "" ' Suffix to add to identifier, if it is a reserved word
|
||||
Private Const PREFIX_ON_INDEX As String = "ix" ' Prefix to add to index identifier, to make it unique (mSQL v2)
|
||||
Private Const SUFFIX_ON_INDEX As String = "" ' Suffix to add to index identifier, to make it unique (mSQL v2)
|
||||
Private Const CREATE_SQL_FILE As String = "c:\temp\esql_create.txt" ' Use empty if open on #1. Will be overwritten if exists!
|
||||
Private Const DEL_SQL_FILE As String = "c:\temp\esql_del.txt" ' Use empty if open on #2. Will be overwritten if exists!
|
||||
Private Const ADD_SQL_FILE As String = "c:\temp\esql_add.txt" ' Use empty if open on #1. Will be overwritten if exists!
|
||||
Private Const LINE_BREAK As String = "\n" ' Try "<br>". String to replace line breaks in text fields
|
||||
Private Const COMMENTS As Boolean = True ' Dump comments into output file
|
||||
Private Const DISPLAY_WARNINGS As Boolean = True ' False to output the warnings to the files, only
|
||||
Private Const DATE_AS_STR As Boolean = True ' False to use real number data type for date, time and timestamp (in mSQL only)
|
||||
Private Const PARA_INSERT_AFTER As Integer = 3 ' Field count after which print INSERTs different lines
|
||||
Private Const INDENT_SIZE As Integer = 5 ' Number of spaces on indents
|
||||
|
||||
' Global var to store inter-funtion data
|
||||
Private warnings As String ' Not an option: do not set in any way
|
||||
Private COMMENT_PREFIX As String
|
||||
Private QUERY_SEPARATOR As String ' Terminator/separator of SQL queries (to instruct some monitor program to execute them)
|
||||
|
||||
|
||||
' Primary Export Function
|
||||
|
||||
Sub exportSQL()
|
||||
On Error GoTo exportSQL_error
|
||||
|
||||
Dim cdb As Database
|
||||
Dim ctableix As Integer, ctablename As String
|
||||
If COMMENTS Then
|
||||
If DB_ENGINE = "Pg" Then
|
||||
COMMENT_PREFIX = "--"
|
||||
QUERY_SEPARATOR = ";"
|
||||
Else
|
||||
COMMENT_PREFIX = "#"
|
||||
QUERY_SEPARATOR = "\g"
|
||||
End If
|
||||
End If
|
||||
|
||||
If DB_NAME = "" Then
|
||||
Set cdb = CurrentDb()
|
||||
Else
|
||||
Set cdb = OpenDatabase(DB_NAME, False, True, DB_CONNECT) ' Shared, read-only
|
||||
End If
|
||||
|
||||
If CREATE_SQL_FILE <> "" Then Open CREATE_SQL_FILE For Output As #1
|
||||
If DEL_SQL_FILE <> "" Then Open DEL_SQL_FILE For Output As #2
|
||||
If ADD_SQL_FILE <> "" Then Open ADD_SQL_FILE For Output As #3
|
||||
|
||||
DoCmd.Hourglass True
|
||||
|
||||
If COMMENTS Then
|
||||
Dim convert_to As String
|
||||
If (Left$(DB_ENGINE, 2) = "MY") Then
|
||||
convert_to = "MySQL"
|
||||
ElseIf (DB_ENGINE = "Pg") Then
|
||||
convert_to = "PostgreSQL"
|
||||
Else
|
||||
convert_to = "mSQL"
|
||||
End If
|
||||
Print #1, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #2, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #3, COMMENT_PREFIX & " Exported from MS Access to " & convert_to
|
||||
Print #1, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
Print #2, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
Print #3, COMMENT_PREFIX & " (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net"
|
||||
End If
|
||||
|
||||
'Go through the table definitions
|
||||
For ctableix = 0 To cdb.TableDefs.Count - 1
|
||||
|
||||
Dim cfieldix As Integer, cfieldname As String
|
||||
Dim fieldlst As String, sqlcode As String
|
||||
Dim primary_found As Boolean
|
||||
Dim crs As Recordset
|
||||
|
||||
' Let's take only the visible tables
|
||||
If (((cdb.TableDefs(ctableix).Attributes And DB_SYSTEMOBJECT) Or _
|
||||
(cdb.TableDefs(ctableix).Attributes And DB_HIDDENOBJECT))) = 0 Then
|
||||
|
||||
ctablename = conv_name("" & cdb.TableDefs(ctableix).Name)
|
||||
|
||||
Print #2,
|
||||
Print #2, "DROP TABLE " & ctablename & QUERY_SEPARATOR
|
||||
|
||||
' CREATE clause
|
||||
Print #1,
|
||||
Print #1, "CREATE TABLE " & ctablename
|
||||
Print #1, Space$(INDENT_SIZE) & "("
|
||||
|
||||
warnings = ""
|
||||
fieldlst = ""
|
||||
primary_found = False
|
||||
|
||||
' loop thorugh each field in the table
|
||||
For cfieldix = 0 To cdb.TableDefs(ctableix).Fields.Count - 1
|
||||
|
||||
Dim typestr As String, fieldsz As Integer, dvstr As String
|
||||
Dim found_ix As Boolean, cindex, tmpindex As Index, cfield, tmpfield As Field
|
||||
|
||||
' if this is not the first iteration, add separators
|
||||
If fieldlst <> "" Then
|
||||
fieldlst = fieldlst & ", "
|
||||
Print #1, ","
|
||||
End If
|
||||
|
||||
' get field name
|
||||
cfieldname = conv_name("" & cdb.TableDefs(ctableix).Fields(cfieldix).Name)
|
||||
fieldlst = fieldlst & cfieldname
|
||||
|
||||
' translate types
|
||||
If DB_ENGINE = "M1" Or DB_ENGINE = "M2" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbChar
|
||||
typestr = "CHAR(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "CHAR(" & fieldsz & ")"
|
||||
Case dbBoolean, dbByte, dbInteger, dbLong
|
||||
typestr = "INT"
|
||||
Case dbDouble, dbFloat, dbSingle
|
||||
typestr = "REAL"
|
||||
Case dbCurrency, dbDecimal, dbNumeric
|
||||
typestr = "REAL"
|
||||
warn "In new field '" & cfieldname & "', currency/BCD will be converted to REAL - there may be precision loss!", False
|
||||
Case dbDate
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(19)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & ".", False
|
||||
Case dbTime
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(8)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & ".", False
|
||||
Case dbTimeStamp
|
||||
typestr = IIf(DATE_AS_STR, "CHAR(19)", "REAL") ' use Access internal format: IEEE 64-bit (8-byte) FP
|
||||
warn "In new field '" & cfieldname & "', date/time/timestamp will be converted to " & typestr & "." & IIf(DB_ENGINE = "M2", " Consider using pseudo field '_timestamp'.", ""), False
|
||||
Case dbMemo
|
||||
If DB_ENGINE = "M2" Then
|
||||
typestr = "TEXT(" & MSQL_64kb_AVG & ")"
|
||||
Else
|
||||
typestr = "CHAR(" & MSQL_64kb_AVG & ")"
|
||||
warn "In new field '" & cfieldname & "', dbMemo is not supported by mSQL v1 - fields larger than MSQL_64kb_AVG (" & MSQL_64kb_AVG & ") will not be accepted!", False
|
||||
End If
|
||||
Case dbBinary, dbVarBinary
|
||||
typestr = "CHAR(255)"
|
||||
warn "In new field '" & cfieldname & "', dbBinary and dbVarBinary are not supported by mSQL! - will use a text (CHAR(255)) field.", True
|
||||
Case dbLongBinary
|
||||
typestr = "CHAR(" & MSQL_64kb_AVG & ")"
|
||||
warn "In new field '" & cfieldname & "', dbLongBinary is not supported by mSQL! - will use a text (CHAR(" & MSQL_64kb_AVG & ")) field.", True
|
||||
Case Else
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End Select
|
||||
ElseIf DB_ENGINE = "MY" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbBinary
|
||||
typestr = "TINYBLOB"
|
||||
Case dbBoolean
|
||||
typestr = "TINYINT"
|
||||
Case dbByte
|
||||
typestr = "TINYINT UNSIGNED"
|
||||
Case dbChar
|
||||
typestr = "CHAR(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbCurrency
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDate
|
||||
typestr = "DATETIME"
|
||||
Case dbDecimal
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDouble
|
||||
typestr = "REAL"
|
||||
Case dbFloat
|
||||
typestr = "REAL"
|
||||
Case dbInteger
|
||||
typestr = "SMALLINT"
|
||||
Case dbLong
|
||||
typestr = "INT"
|
||||
Case dbLongBinary
|
||||
typestr = "LONGBLOB"
|
||||
Case dbMemo
|
||||
typestr = "LONGBLOB" ' !!!!! MySQL bug! Replace by LONGTEXT when corrected!
|
||||
Case dbNumeric
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbSingle
|
||||
typestr = "FLOAT"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "CHAR(" & fieldsz & ")"
|
||||
Case dbTime
|
||||
typestr = "TIME"
|
||||
Case dbTimeStamp
|
||||
typestr = "TIMESTAMP"
|
||||
Case dbVarBinary
|
||||
typestr = "TINYBLOB"
|
||||
Case dbBigInt, dbGUID
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
Case Else
|
||||
typestr = "LONGBLOB"
|
||||
End Select
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
Select Case cdb.TableDefs(ctableix).Fields(cfieldix).Type
|
||||
Case dbBinary
|
||||
typestr = "int2"
|
||||
Case dbBoolean
|
||||
typestr = "bool"
|
||||
Case dbByte
|
||||
typestr = "int2"
|
||||
Case dbChar
|
||||
typestr = "varchar(" & cdb.TableDefs(ctableix).Fields(cfieldix).Size & ")"
|
||||
Case dbCurrency
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDate
|
||||
typestr = "DATETIME"
|
||||
Case dbDecimal
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbDouble
|
||||
typestr = "float8"
|
||||
Case dbFloat
|
||||
typestr = "float4"
|
||||
Case dbInteger
|
||||
typestr = "int4"
|
||||
Case dbLong
|
||||
typestr = "int8"
|
||||
Case dbLongBinary
|
||||
typestr = "text" ' hm?
|
||||
Case dbMemo
|
||||
typestr = "text"
|
||||
Case dbNumeric
|
||||
typestr = "DECIMAL(20,4)"
|
||||
Case dbSingle
|
||||
typestr = "float4"
|
||||
Case dbText
|
||||
fieldsz = cdb.TableDefs(ctableix).Fields(cfieldix).Size
|
||||
If fieldsz = 0 Then fieldsz = 255
|
||||
typestr = "varchar(" & fieldsz & ")"
|
||||
Case dbTime
|
||||
typestr = "TIME"
|
||||
Case dbTimeStamp
|
||||
typestr = "TIMESTAMP"
|
||||
Case dbVarBinary
|
||||
typestr = "text" ' hm?
|
||||
Case dbBigInt, dbGUID
|
||||
warn "In new field '" & cfieldname & "', dbBigInt and dbGUID are not currently supported!", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
Case Else
|
||||
typestr = "text"
|
||||
End Select
|
||||
Else
|
||||
warn "unkown DB_ENGINE string " & DB_ENGINE, True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End If
|
||||
|
||||
' check not null and auto-increment properties
|
||||
If ((cdb.TableDefs(ctableix).Fields(cfieldix).Attributes And dbAutoIncrField) <> 0) Then
|
||||
If Left$(DB_ENGINE, 2) = "MY" Then
|
||||
typestr = typestr & " NOT NULL AUTO_INCREMENT"
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
typestr = " serial"
|
||||
Else
|
||||
typestr = typestr & " NOT NULL"
|
||||
warn "In new field '" & cfieldname & "', mSQL does not support auto-increment fields! - they will be pure INTs." & IIf(DB_ENGINE = "M2", " Consider using pseudo field '_rowid' or SEQUENCEs.", ""), False
|
||||
End If
|
||||
ElseIf cdb.TableDefs(ctableix).Fields(cfieldix).Required = True Then
|
||||
typestr = typestr & " NOT NULL"
|
||||
End If
|
||||
|
||||
' default value
|
||||
dvstr = cdb.TableDefs(ctableix).Fields(cfieldix).DefaultValue
|
||||
If dvstr <> "" Then
|
||||
If Left$(DB_ENGINE, 2) <> "MY" And DB_ENGINE <> "Pg" Then
|
||||
warn "In new field '" & cfieldname & "', mSQL does not support default values! - they won't be initialised.", False
|
||||
ElseIf Left$(DB_ENGINE, 2) = "MY" And cdb.TableDefs(ctableix).Fields(cfieldix).Required = False Then
|
||||
warn "In new field '" & cfieldname & "', MySQL needs NOT NULL to support default values! - it won't be set a default.", False
|
||||
ElseIf Left$(dvstr, 1) = """" Then
|
||||
typestr = typestr & " DEFAULT '" & conv_str(Mid$(dvstr, 2, Len(dvstr) - 2)) & "'"
|
||||
ElseIf ((LCase(dvstr) = "now()" Or LCase(dvstr) = "date()" Or LCase(dvstr) = "time()") And _
|
||||
(Left$(typestr, 5) = "DATE " Or Left$(typestr, 5) = "TIME " Or Left$(typestr, 9) = "DATETIME ")) Then
|
||||
typestr = "TIMESTAMP " & Right$(typestr, Len(typestr) - InStr(typestr, " "))
|
||||
ElseIf LCase(dvstr) = "no" Then
|
||||
typestr = typestr & " DEFAULT 0"
|
||||
ElseIf LCase(dvstr) = "yes" Then
|
||||
typestr = typestr & " DEFAULT 1"
|
||||
Else
|
||||
typestr = typestr & " DEFAULT " & dvstr
|
||||
End If
|
||||
End If
|
||||
|
||||
' add constrains
|
||||
Dim val_rule, val_text As String
|
||||
val_rule = cdb.TableDefs(ctableix).Fields(cfieldix).ValidationRule
|
||||
val_text = cdb.TableDefs(ctableix).Fields(cfieldix).ValidationText
|
||||
If DB_ENGINE = "Pg" And val_rule <> "" Then
|
||||
typestr = typestr & COMMENT_PREFIX & " check ( " & val_rule & " ) " & COMMENT_PREFIX & " " & val_text
|
||||
warn "Field '" & cfieldname & "' has constrain '" & val_rule & "' with text '" & val_text & "' which you have to convert manually (inserted as comment in SQL)", False
|
||||
End If
|
||||
|
||||
' check if primary key (for mSQL v1)
|
||||
If DB_ENGINE = "M1" Then
|
||||
found_ix = False
|
||||
For Each cindex In cdb.TableDefs(ctableix).Indexes
|
||||
If cindex.Primary Then
|
||||
For Each cfield In cindex.Fields
|
||||
If cfield.Name = cdb.TableDefs(ctableix).Fields(cfieldix).Name Then
|
||||
found_ix = True
|
||||
Exit For
|
||||
End If
|
||||
Next cfield
|
||||
If found_ix Then Exit For
|
||||
End If
|
||||
Next cindex
|
||||
If found_ix Then
|
||||
If primary_found Then
|
||||
warn "On new table '" & ctablename & "', mSQL v1 does not support more than one PRIMARY KEY! Only first key was set.", False
|
||||
Else
|
||||
typestr = typestr & " PRIMARY KEY"
|
||||
primary_found = True
|
||||
End If
|
||||
End If
|
||||
End If
|
||||
|
||||
'print out field info
|
||||
Print #1, Space$(INDENT_SIZE) & cfieldname & Space$(IDENT_MAX_SIZE - Len(cfieldname) + 2) & typestr;
|
||||
|
||||
Next cfieldix
|
||||
|
||||
' terminate CREATE clause
|
||||
If DB_ENGINE = "M2" Then
|
||||
Print #1,
|
||||
Print #1, Space$(INDENT_SIZE) & ")" & QUERY_SEPARATOR
|
||||
End If
|
||||
|
||||
' primary key and other index declaration
|
||||
If DB_ENGINE = "M2" Or Left$(DB_ENGINE, 2) = "MY" Or DB_ENGINE = "Pg" Then
|
||||
For Each cindex In cdb.TableDefs(ctableix).Indexes
|
||||
sqlcode = ""
|
||||
For Each cfield In cindex.Fields
|
||||
sqlcode = sqlcode & IIf(sqlcode = "", "", ", ") & conv_name(cfield.Name)
|
||||
Next cfield
|
||||
If DB_ENGINE = "M2" Then
|
||||
Print #1, "CREATE " & IIf(cindex.Unique, "UNIQUE ", "") & "INDEX " & _
|
||||
conv_name(PREFIX_ON_INDEX & cindex.Name & SUFFIX_ON_INDEX) & " ON " & _
|
||||
ctablename & " (" & sqlcode & ")" & QUERY_SEPARATOR
|
||||
ElseIf DB_ENGINE = "Pg" Then
|
||||
If cindex.Primary Then
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "PRIMARY KEY (" & sqlcode & ")";
|
||||
ElseIf cindex.Unique Then
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "UNIQUE INDEX (" & sqlcode & ")";
|
||||
Else
|
||||
' skip indexes which are part of primary key
|
||||
primary_found = False
|
||||
For Each tmpindex In cdb.TableDefs(ctableix).Indexes
|
||||
If tmpindex.Primary Then
|
||||
For Each tmpfield In tmpindex.Fields
|
||||
If sqlcode = conv_name(tmpfield.Name) Then
|
||||
primary_found = True
|
||||
Exit For
|
||||
End If
|
||||
Next tmpfield
|
||||
End If
|
||||
Next tmpindex
|
||||
If Not primary_found Then
|
||||
If DB_ENGINE = "Pg" Then
|
||||
' FIX: create index....
|
||||
Else
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & "INDEX (" & sqlcode & ")";
|
||||
End If
|
||||
End If
|
||||
End If
|
||||
|
||||
Else
|
||||
Print #1, ","
|
||||
Print #1, Space$(INDENT_SIZE) & IIf(cindex.Primary, "PRIMARY ", "") & "KEY (" & sqlcode & ")";
|
||||
End If
|
||||
Next cindex
|
||||
End If
|
||||
|
||||
' terminate CREATE clause
|
||||
If DB_ENGINE <> "M2" Then
|
||||
Print #1,
|
||||
Print #1, Space$(INDENT_SIZE) & ")" & QUERY_SEPARATOR
|
||||
End If
|
||||
|
||||
' print any warnings bellow it
|
||||
If COMMENTS And warnings <> "" Then
|
||||
If DB_ENGINE = "M2" Then Print #1, COMMENT_PREFIX & " "
|
||||
Print #1, warnings
|
||||
warnings = ""
|
||||
End If
|
||||
|
||||
Print #1,
|
||||
|
||||
' INSERT clause
|
||||
Set crs = cdb.OpenRecordset(cdb.TableDefs(ctableix).Name)
|
||||
If crs.RecordCount <> 0 Then
|
||||
|
||||
' loop thorugh each record in the table
|
||||
crs.MoveFirst
|
||||
Do Until crs.EOF
|
||||
|
||||
' start paragraphing
|
||||
sqlcode = "INSERT INTO " & ctablename
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then
|
||||
Print #3, sqlcode
|
||||
If DB_ENGINE = "M1" Then Print #3, Space$(INDENT_SIZE) & "(" & fieldlst & ")"
|
||||
Print #3, "VALUES ("
|
||||
sqlcode = Space$(INDENT_SIZE)
|
||||
Else
|
||||
If DB_ENGINE = "M1" Then sqlcode = sqlcode & " (" & fieldlst & ")"
|
||||
sqlcode = sqlcode & " VALUES ("
|
||||
End If
|
||||
|
||||
' loop through each field in each record
|
||||
For cfieldix = 0 To crs.Fields.Count - 1
|
||||
|
||||
' based on type, prepare the field value
|
||||
If IsNull(crs.Fields(cfieldix).Value) Then
|
||||
sqlcode = sqlcode & "NULL"
|
||||
Else
|
||||
Select Case crs.Fields(cfieldix).Type
|
||||
Case dbBoolean
|
||||
If DB_ENGINE = "Pg" Then
|
||||
sqlcode = sqlcode & IIf(crs.Fields(cfieldix).Value = True, "'t'", "'f'")
|
||||
Else
|
||||
sqlcode = sqlcode & IIf(crs.Fields(cfieldix).Value = True, "1", "0")
|
||||
End If
|
||||
Case dbChar, dbText, dbMemo
|
||||
sqlcode = sqlcode & "'" & conv_str(crs.Fields(cfieldix).Value) & "'"
|
||||
Case dbDate, dbTimeStamp
|
||||
If Left$(DB_ENGINE, 2) = "MY" Or DATE_AS_STR Then
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "YYYY-MM-DD HH:MM:SS") & "'"
|
||||
Else
|
||||
'print in Access internal format: IEEE 64-bit (8-byte) FP
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "#.#########") & "'"
|
||||
End If
|
||||
Case dbTime
|
||||
If Left$(DB_ENGINE, 2) = "MY" Or DATE_AS_STR Then
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "HH:MM:SS") & "'"
|
||||
Else
|
||||
'print in Access internal format: IEEE 64-bit (8-byte) FP
|
||||
sqlcode = sqlcode & "'" & Format(crs.Fields(cfieldix).Value, "#.#########") & "'"
|
||||
End If
|
||||
Case dbBinary, dbLongBinary, dbVarBinary
|
||||
sqlcode = sqlcode & "'" & conv_bin(crs.Fields(cfieldix).Value) & "'"
|
||||
Case dbCurrency, dbDecimal, dbDouble, dbFloat, dbNumeric, dbSingle
|
||||
sqlcode = sqlcode & conv_float(crs.Fields(cfieldix).Value)
|
||||
Case Else
|
||||
sqlcode = sqlcode & conv_str(crs.Fields(cfieldix).Value)
|
||||
End Select
|
||||
End If
|
||||
|
||||
' paragraph separators
|
||||
If cfieldix < crs.Fields.Count - 1 Then
|
||||
sqlcode = sqlcode & ", "
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then
|
||||
Print #3, sqlcode
|
||||
sqlcode = Space$(INDENT_SIZE)
|
||||
End If
|
||||
End If
|
||||
|
||||
Next cfieldix
|
||||
|
||||
' print out result and any warnings
|
||||
sqlcode = sqlcode & IIf(crs.Fields.Count > PARA_INSERT_AFTER, " )", ")") & QUERY_SEPARATOR
|
||||
Print #3, sqlcode
|
||||
If COMMENTS And warnings <> "" Then
|
||||
Print #3, warnings
|
||||
warnings = ""
|
||||
End If
|
||||
If crs.Fields.Count > PARA_INSERT_AFTER Then Print #3,
|
||||
|
||||
crs.MoveNext
|
||||
Loop
|
||||
|
||||
Else
|
||||
|
||||
' if there is no data on the table
|
||||
If COMMENTS Then Print #3, COMMENT_PREFIX & " This table has no data"
|
||||
|
||||
End If
|
||||
|
||||
crs.Close
|
||||
Set crs = Nothing
|
||||
|
||||
End If 'print only unhidden tables
|
||||
|
||||
Next ctableix
|
||||
|
||||
exportSQL_exit:
|
||||
Close #3
|
||||
Close #2
|
||||
Close #1
|
||||
|
||||
cdb.Close
|
||||
Set cdb = Nothing
|
||||
|
||||
DoCmd.Hourglass False
|
||||
|
||||
Exit Sub
|
||||
|
||||
exportSQL_error:
|
||||
MsgBox Err.Description
|
||||
Resume exportSQL_exit
|
||||
|
||||
End Sub
|
||||
|
||||
|
||||
Private Function conv_name(strname As String) As String
|
||||
Dim i As Integer, str As String
|
||||
|
||||
' replace inner spaces with WS_REPLACEMENT
|
||||
str = strname
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case " ", Chr$(9), Chr$(10), Chr$(13), "-", "/" ' space, tab, newline, carriage return
|
||||
str = Left$(str, i - 1) & WS_REPLACEMENT & Right$(str, Len(str) - i)
|
||||
i = i + Len(WS_REPLACEMENT)
|
||||
Case Else
|
||||
i = i + 1
|
||||
End Select
|
||||
Wend
|
||||
' restrict tablename to IDENT_MAX_SIZE chars, *after* eating spaces
|
||||
str = Left$(str, IDENT_MAX_SIZE)
|
||||
' check for reserved words
|
||||
conv_name = str
|
||||
If Left$(DB_ENGINE, 2) = "MY" Then
|
||||
Select Case LCase$(str)
|
||||
Case "add", "all", "alter", "and", "as", "asc", "auto_increment", "between", _
|
||||
"bigint", "binary", "blob", "both", "by", "cascade", "char", "character", _
|
||||
"change", "check", "column", "columns", "create", "data", "datetime", "dec", _
|
||||
"decimal", "default", "delete", "desc", "describe", "distinct", "double", _
|
||||
"drop", "escaped", "enclosed", "explain", "fields", "float", "float4", _
|
||||
"float8", "foreign", "from", "for", "full", "grant", "group", "having", _
|
||||
"ignore", "in", "index", "infile", "insert", "int", "integer", "interval", _
|
||||
"int1", "int2", "int3", "int4", "int8", "into", "is", "key", "keys", _
|
||||
"leading", "like", "lines", "limit", "lock", "load", "long", "longblob", _
|
||||
"longtext", "match", "mediumblob", "mediumtext", "mediumint", "middleint", _
|
||||
"numeric", "not", "null", "on", "option", "optionally", "or", "order", _
|
||||
"outfile", "partial", "precision", "primary", "procedure", "privileges", _
|
||||
"read", "real", "references", "regexp", "repeat", "replace", "restrict", _
|
||||
"rlike", "select", "set", "show", "smallint", "sql_big_tables", _
|
||||
"sql_big_selects", "sql_select_limit", "straight_join", "table", "tables", _
|
||||
"terminated", "tinyblob", "tinytext", "tinyint", "trailing", "to", "unique", _
|
||||
"unlock", "unsigned", "update", "usage", "values", "varchar", "varying", _
|
||||
"with", "write", "where", "zerofill"
|
||||
conv_name = Left$(PREFIX_ON_KEYWORD & str & SUFFIX_ON_KEYWORD, IDENT_MAX_SIZE)
|
||||
If (str = conv_name) Then
|
||||
warn "In identifier '" & strname & "', the new form '" & strname & _
|
||||
"' is a reserved word, and PREFIX_ON_KEYWORD ('" & _
|
||||
PREFIX_ON_KEYWORD & "') and SUFFIX_ON_KEYWORD ('" & SUFFIX_ON_KEYWORD & _
|
||||
"') make it larger than IDENT_MAX_SIZE, and after cut it is the same as the original! " & _
|
||||
"This is usually caused by a void or empty PREFIX_ON_KEYWORD.", True
|
||||
Error 5 ' invalid Procedure Call
|
||||
End If
|
||||
End Select
|
||||
End If
|
||||
End Function
|
||||
|
||||
|
||||
Private Function conv_str(str As String) As String
|
||||
Dim i As Integer, nlstr As String, rstr As Variant
|
||||
|
||||
nlstr = ""
|
||||
rstr = Null
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case Chr$(0) ' ASCII NUL
|
||||
nlstr = ""
|
||||
rstr = "\0"
|
||||
Case Chr$(8) ' backspace
|
||||
nlstr = ""
|
||||
rstr = "\b"
|
||||
Case Chr$(9) ' tab
|
||||
nlstr = ""
|
||||
rstr = "\t"
|
||||
Case "'"
|
||||
nlstr = ""
|
||||
rstr = "\'"
|
||||
Case """"
|
||||
nlstr = ""
|
||||
rstr = "\"""
|
||||
Case "\"
|
||||
nlstr = ""
|
||||
rstr = "\\"
|
||||
Case Chr$(10), Chr$(13) ' line feed and carriage return
|
||||
If nlstr <> "" And nlstr <> Mid$(str, i, 1) Then
|
||||
' there was a previous newline and this is its pair: eat it
|
||||
rstr = ""
|
||||
nlstr = ""
|
||||
Else
|
||||
' this is a fresh newline
|
||||
rstr = LINE_BREAK
|
||||
nlstr = Mid$(str, i, 1)
|
||||
End If
|
||||
Case Else
|
||||
nlstr = ""
|
||||
End Select
|
||||
If Not IsNull(rstr) Then
|
||||
str = Left$(str, i - 1) & rstr & Right$(str, Len(str) - i)
|
||||
i = i + Len(rstr)
|
||||
rstr = Null
|
||||
Else
|
||||
i = i + 1
|
||||
End If
|
||||
Wend
|
||||
conv_str = str
|
||||
End Function
|
||||
|
||||
|
||||
Private Function conv_bin(str As String) As String
|
||||
Dim i As Integer, rstr As String
|
||||
|
||||
rstr = ""
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
Select Case Mid$(str, i, 1)
|
||||
Case Chr$(0) ' ASCII NUL
|
||||
rstr = "\0"
|
||||
Case Chr$(8) ' backspace
|
||||
rstr = "\b"
|
||||
Case Chr$(9) ' tab
|
||||
rstr = "\t"
|
||||
Case "'"
|
||||
rstr = "\'"
|
||||
Case """"
|
||||
rstr = "\"""
|
||||
Case "\"
|
||||
rstr = "\\"
|
||||
Case Chr$(10) ' line feed
|
||||
rstr = "\n"
|
||||
Case Chr$(13) ' carriage return
|
||||
rstr = "\r"
|
||||
End Select
|
||||
If rstr <> "" Then
|
||||
str = Left$(str, i - 1) & rstr & Right$(str, Len(str) - i)
|
||||
i = i + Len(rstr)
|
||||
rstr = ""
|
||||
Else
|
||||
i = i + 1
|
||||
End If
|
||||
Wend
|
||||
conv_bin = str
|
||||
End Function
|
||||
|
||||
' This function is used to convert local setting of decimal , to .
|
||||
Private Function conv_float(str As String) As String
|
||||
Dim i As Integer
|
||||
|
||||
i = 1
|
||||
While i <= Len(str)
|
||||
If Mid$(str, i, 1) = "," Then
|
||||
str = Left$(str, i - 1) & "." & Right$(str, Len(str) - i)
|
||||
End If
|
||||
i = i + 1
|
||||
Wend
|
||||
conv_float = str
|
||||
End Function
|
||||
|
||||
|
||||
Private Sub warn(str As String, abortq As Boolean)
|
||||
If DISPLAY_WARNINGS Then MsgBox str, vbOKOnly Or vbExclamation, "Warning"
|
||||
warnings = warnings & COMMENT_PREFIX & " Warning: " & str & Chr$(13) & Chr$(10)
|
||||
End Sub
|
||||
73
training/migrations/0001_initial.py
Normal file
73
training/migrations/0001_initial.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TrainingCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('category_number', models.PositiveSmallIntegerField()),
|
||||
('category_name', models.CharField(max_length=50)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrainingItem',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('item_number', models.PositiveSmallIntegerField()),
|
||||
('item_name', models.CharField(max_length=100)),
|
||||
('category', models.ForeignKey(to='training.TrainingCategory')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrainingLevelRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('technical_assistant', models.DateField(null=True, blank=True)),
|
||||
('sound_technician', models.DateField(null=True, blank=True)),
|
||||
('sound_supervisor', models.DateField(null=True, blank=True)),
|
||||
('lighting_technician', models.DateField(null=True, blank=True)),
|
||||
('lighting_supervisor', models.DateField(null=True, blank=True)),
|
||||
('power_technician', models.DateField(null=True, blank=True)),
|
||||
('power_supervisor', models.DateField(null=True, blank=True)),
|
||||
('haulage_supervisor', models.DateField(null=True, blank=True)),
|
||||
('trainee', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrainingRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('started_date', models.DateField(null=True, blank=True)),
|
||||
('started_notes', models.TextField(null=True, blank=True)),
|
||||
('completed_date', models.DateField(null=True, blank=True)),
|
||||
('completed_notes', models.TextField(null=True, blank=True)),
|
||||
('assessed_date', models.DateField(null=True, blank=True)),
|
||||
('assessed_notes', models.TextField(null=True, blank=True)),
|
||||
('assessed_trainer', models.ForeignKey(related_name='trainingrecords_assessed', to=settings.AUTH_USER_MODEL)),
|
||||
('completed_trainer', models.ForeignKey(related_name='trainingrecords_completed', to=settings.AUTH_USER_MODEL)),
|
||||
('started_trainer', models.ForeignKey(related_name='trainingrecords_started', to=settings.AUTH_USER_MODEL)),
|
||||
('trainee', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
('training_item', models.ForeignKey(to='training.TrainingItem')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trainingitem',
|
||||
name='training_records',
|
||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='training.TrainingRecord'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='trainingrecord',
|
||||
unique_together=set([('trainee', 'training_item')]),
|
||||
),
|
||||
]
|
||||
30
training/migrations/0002_required_fields.py
Normal file
30
training/migrations/0002_required_fields.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('training', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='trainingrecord',
|
||||
name='assessed_trainer',
|
||||
field=models.ForeignKey(related_name='trainingrecords_assessed', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='trainingrecord',
|
||||
name='completed_trainer',
|
||||
field=models.ForeignKey(related_name='trainingrecords_completed', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='trainingrecord',
|
||||
name='started_trainer',
|
||||
field=models.ForeignKey(related_name='trainingrecords_started', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
]
|
||||
41355
training/migrations/data/esql_add.txt
Normal file
41355
training/migrations/data/esql_add.txt
Normal file
File diff suppressed because it is too large
Load Diff
74
training/migrations/data/esql_create.txt
Normal file
74
training/migrations/data/esql_create.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
-- Exported from MS Access to PostgreSQL
|
||||
-- (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
|
||||
CREATE TABLE Categories
|
||||
(
|
||||
ID serial,
|
||||
Category_Number int8,
|
||||
Category_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Members
|
||||
(
|
||||
ID serial,
|
||||
Member_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE tblrequirements
|
||||
(
|
||||
ID serial,
|
||||
level_id int8,
|
||||
item_id int8,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Items
|
||||
(
|
||||
ID serial,
|
||||
Category_ID int8,
|
||||
Item_Number int8,
|
||||
Item_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Level_Reco
|
||||
(
|
||||
ID serial,
|
||||
Member_ID int8,
|
||||
Training_Level_ID int8,
|
||||
Date_Level_Awarded DATETIME,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Levels
|
||||
(
|
||||
ID serial,
|
||||
Level_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Records
|
||||
(
|
||||
ID serial,
|
||||
Member_ID int8,
|
||||
Training_Item_ID int8,
|
||||
Traning_Started_Dat DATETIME,
|
||||
Training_Started_As int8,
|
||||
Training_Started_No text,
|
||||
Traning_Complete_Da DATETIME,
|
||||
Training_Complete_A int8,
|
||||
Training_Complete_N text,
|
||||
Competency_Assessed DATETIME,
|
||||
Competency_Assessed int8,
|
||||
Competency_Assessed text,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
16
training/migrations/data/esql_del.txt
Normal file
16
training/migrations/data/esql_del.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Exported from MS Access to PostgreSQL
|
||||
-- (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
|
||||
DROP TABLE Categories;
|
||||
|
||||
DROP TABLE Members;
|
||||
|
||||
DROP TABLE tblrequirements;
|
||||
|
||||
DROP TABLE Training_Items;
|
||||
|
||||
DROP TABLE Training_Level_Reco;
|
||||
|
||||
DROP TABLE Training_Levels;
|
||||
|
||||
DROP TABLE Training_Records;
|
||||
41355
training/migrations/data/psql_add.sql
Normal file
41355
training/migrations/data/psql_add.sql
Normal file
File diff suppressed because it is too large
Load Diff
74
training/migrations/data/psql_create.sql
Normal file
74
training/migrations/data/psql_create.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
-- Exported from MS Access to PostgreSQL
|
||||
-- (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
|
||||
CREATE TABLE Categories
|
||||
(
|
||||
ID serial,
|
||||
Category_Number int8,
|
||||
Category_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Members
|
||||
(
|
||||
ID serial,
|
||||
Member_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE tblrequirements
|
||||
(
|
||||
ID serial,
|
||||
level_id int8,
|
||||
item_id int8,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Items
|
||||
(
|
||||
ID serial,
|
||||
Category_ID int8 REFERENCES Categories,
|
||||
Item_Number int8,
|
||||
Item_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Level_Records
|
||||
(
|
||||
ID serial,
|
||||
Member_ID int8,
|
||||
Training_Level_ID int8,
|
||||
Date_Level_Awarded DATE,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Levels
|
||||
(
|
||||
ID serial,
|
||||
Level_Name varchar(255),
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE Training_Records
|
||||
(
|
||||
ID serial,
|
||||
Member_ID int8 REFERENCES Members,
|
||||
Training_Item_ID int8 REFERENCES Training_Items,
|
||||
Started_Date DATE,
|
||||
Started_Assessor int8 REFERENCES Members,
|
||||
Started_Notes text,
|
||||
Complete_Date DATE,
|
||||
Complete_Assessor int8 REFERENCES Members,
|
||||
Complete_Notes text,
|
||||
Assessed_Date DATE,
|
||||
Assessed_Assessor int8 REFERENCES Members,
|
||||
Assessed_Notes text,
|
||||
PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
16
training/migrations/data/psql_del.sql
Normal file
16
training/migrations/data/psql_del.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Exported from MS Access to PostgreSQL
|
||||
-- (C) 1997-98 CYNERGI - www.cynergi.net, info@cynergi.net
|
||||
|
||||
DROP TABLE Categories;
|
||||
|
||||
DROP TABLE Members;
|
||||
|
||||
DROP TABLE tblrequirements;
|
||||
|
||||
DROP TABLE Training_Items;
|
||||
|
||||
DROP TABLE Training_Level_Records;
|
||||
|
||||
DROP TABLE Training_Levels;
|
||||
|
||||
DROP TABLE Training_Records;
|
||||
90
training/models.py
Normal file
90
training/models.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
import reversion
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@reversion.register
|
||||
class TrainingCategory(models.Model):
|
||||
category_number = models.PositiveSmallIntegerField()
|
||||
category_name = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return "{0}: {1}".format(self.category_number, self.category_name)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@reversion.register
|
||||
class TrainingItem(models.Model):
|
||||
category = models.ForeignKey(TrainingCategory)
|
||||
item_number = models.PositiveSmallIntegerField()
|
||||
item_name = models.CharField(max_length=100)
|
||||
training_records = models.ManyToManyField(settings.AUTH_USER_MODEL,
|
||||
through='TrainingRecord', through_fields=('training_item', 'trainee'))
|
||||
|
||||
def __str__(self):
|
||||
return "{0}.{1}: {2}".format(self.category.category_number, self.item_number, self.item_name)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@reversion.register
|
||||
class TrainingRecord(models.Model):
|
||||
trainee = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='trainingrecords')
|
||||
training_item = models.ForeignKey(TrainingItem)
|
||||
|
||||
started_date = models.DateField(blank=True, null=True)
|
||||
started_trainer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='trainingrecords_started', blank=True,
|
||||
null=True)
|
||||
started_notes = models.TextField(blank=True, null=True)
|
||||
completed_date = models.DateField(blank=True, null=True)
|
||||
completed_trainer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='trainingrecords_completed',
|
||||
blank=True, null=True)
|
||||
completed_notes = models.TextField(blank=True, null=True)
|
||||
assessed_date = models.DateField(blank=True, null=True)
|
||||
assessed_trainer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='trainingrecords_assessed', blank=True,
|
||||
null=True)
|
||||
assessed_notes = models.TextField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def started(self):
|
||||
return self.started_date and self.started_trainer
|
||||
|
||||
@property
|
||||
def complete(self):
|
||||
return self.completed_date and self.completed_trainer
|
||||
|
||||
@property
|
||||
def assessed(self):
|
||||
return self.assessed_date and self.assessed_trainer
|
||||
|
||||
def __str__(self):
|
||||
return "{0} - {1}".format(self.trainee, self.training_item)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('trainee', 'training_item')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@reversion.register
|
||||
class TrainingLevelRecord(models.Model):
|
||||
trainee = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
|
||||
technical_assistant = models.DateField(blank=True, null=True)
|
||||
|
||||
sound_technician = models.DateField(blank=True, null=True)
|
||||
sound_supervisor = models.DateField(blank=True, null=True)
|
||||
|
||||
lighting_technician = models.DateField(blank=True, null=True)
|
||||
lighting_supervisor = models.DateField(blank=True, null=True)
|
||||
|
||||
power_technician = models.DateField(blank=True, null=True)
|
||||
power_supervisor = models.DateField(blank=True, null=True)
|
||||
|
||||
haulage_supervisor = models.DateField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{0}".format(self.trainee)
|
||||
17
training/templates/training/index.html
Normal file
17
training/templates/training/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Training Database{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-4">
|
||||
<h2>Training Database</h2>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-8 align-right">
|
||||
{# @todo: Add nav buttons and other useful things in here #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>My Training</h3>
|
||||
{% include "training/profile_detail_table.html" %}
|
||||
{% endblock %}
|
||||
9
training/templates/training/profile_detail.html
Normal file
9
training/templates/training/profile_detail.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ profile }} Training{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Training Profile for {{ profile }}</h2>
|
||||
|
||||
{% include "training/profile_detail_table.html" %}
|
||||
{% endblock %}
|
||||
7
training/templates/training/profile_detail_table.html
Normal file
7
training/templates/training/profile_detail_table.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% for category in categories %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{% include "training/trainingcategory_item_table.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
52
training/templates/training/trainingcategory_item_list.html
Normal file
52
training/templates/training/trainingcategory_item_list.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Training Items{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Training Items</h2>
|
||||
|
||||
<div class="row toc-wrapper">
|
||||
<div class="col-xs-12 hidden-md hidden-lg">
|
||||
<ul class="nav nav-pills">
|
||||
{% for category in object_list %}
|
||||
<li role="presentation"><a href="#{{ category.category_name|slugify }}">{{ category }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-lg-9" id="fish">
|
||||
{% for category in object_list %}
|
||||
<div style="margin-bottom: 50em;">
|
||||
<a class="anchor" id="{{ category.category_name|slugify }}"></a>
|
||||
<h3>{{ category }}</h3>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 visible-md visible-lg">
|
||||
<div class="toc-nav">
|
||||
<h3>Available Categories</h3>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
{% for category in object_list %}
|
||||
<li role="presentation"><a href="#{{ category.category_name|slugify }}">{{ category }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/affix.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.toc-nav').affix({
|
||||
offset: {
|
||||
top: ($('.toc-wrapper').offset().top - $('.navbar').height())
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
20
training/templates/training/trainingcategory_item_table.html
Normal file
20
training/templates/training/trainingcategory_item_table.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% load training_extras %}
|
||||
|
||||
<h4>{{ category }}</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Item</td>
|
||||
<td>Started</td>
|
||||
<td>Complete</td>
|
||||
<td>Assessed</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in category.trainingitem_set.all %}
|
||||
{% with record=item|item_record:request.user %}
|
||||
{% include "training/trainingitem_detail_row.html" %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
31
training/templates/training/trainingitem_detail_row.html
Normal file
31
training/templates/training/trainingitem_detail_row.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<tr class="
|
||||
{% if record.assessed %}
|
||||
success
|
||||
{% elif record.complete %}
|
||||
info
|
||||
{% elif record.started %}
|
||||
warning
|
||||
{% else %}
|
||||
danger
|
||||
{% endif %}
|
||||
">
|
||||
<td>{{ record.training_item }}</td>
|
||||
<td>
|
||||
{{ record.started_date|default_if_none:"No" }} <em>{{ record.started_trainer|default_if_none:"" }}</em>
|
||||
{% if record.started_notes %}
|
||||
<pre>{{ record.started_notes }}</pre>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ record.completed_date|default_if_none:"No" }} <em>{{ record.completed_trainer|default_if_none:"" }}</em>
|
||||
{% if record.completed_notes %}
|
||||
<pre>{{ record.completed_notes }}</pre>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ record.assessed_date|default_if_none:"No" }} <em>{{ record.assessed_trainer|default_if_none:"" }}</em>
|
||||
{% if record.assessed_notes %}
|
||||
<pre>{{ record.assessed_notes }}</pre>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
1
training/templatetags/__init__.py
Normal file
1
training/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'Tom Price'
|
||||
14
training/templatetags/training_extras.py
Normal file
14
training/templatetags/training_extras.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django import template
|
||||
|
||||
from training import models
|
||||
|
||||
__author__ = 'Tom Price'
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def item_record(item, user):
|
||||
if item in user.trainingitem_set.all():
|
||||
return user.trainingrecords.get(training_item=item)
|
||||
return models.TrainingRecord(training_item=item)
|
||||
10
training/urls.py
Normal file
10
training/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
__author__ = 'Tom Price'
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from training import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', views.SelfUserTrainingRecordView.as_view()),
|
||||
url(r'user/(?P<pk>\d+)/$', views.UserTrainingRecordView.as_view()),
|
||||
url(r'item/', views.TrainingCategoryItemListView.as_view()),
|
||||
)
|
||||
34
training/views.py
Normal file
34
training/views.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render
|
||||
from django.views import generic
|
||||
from training import models
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class UserTrainingRecordView(generic.DetailView):
|
||||
model = get_user_model()
|
||||
template_name = 'training/profile_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserTrainingRecordView, self).get_context_data(**kwargs)
|
||||
context['categories'] = models.TrainingCategory.objects.all()
|
||||
return context
|
||||
|
||||
|
||||
class SelfUserTrainingRecordView(UserTrainingRecordView):
|
||||
template_name = 'training/index.html'
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.request.user.id
|
||||
self.kwargs['pk'] = pk
|
||||
|
||||
return self.model.objects.filter(pk=pk)
|
||||
|
||||
|
||||
class TrainingCategoryItemListView(generic.ListView):
|
||||
model = models.TrainingCategory
|
||||
template_name = 'training/trainingcategory_item_list.html'
|
||||
Reference in New Issue
Block a user