Compare commits

..

20 Commits

Author SHA1 Message Date
Tom Price
c0c2314fbe Start building a page to select items based on category.
Requires some new CSS for the side nav.
2016-01-05 20:03:16 +00:00
Tom Price
d62fcc8483 Add training database index 2016-01-05 00:17:33 +00:00
Tom Price
b838d25cc2 Refactor training status table down to the simplest elements so they can be included as needed. 2016-01-05 00:07:22 +00:00
Tom Price
ee4bd832fa Merge branch 'login' into training 2016-01-04 23:58:01 +00:00
Tom Price
8c231b931d Deleted left over incorrect .idea/ files. 2015-12-23 01:07:36 +00:00
Tom Price
c5970d2542 A first view that shows a user their current training status. 2015-12-23 01:06:18 +00:00
Tom Price
1922992633 Merge branch 'master' into training 2015-12-22 23:08:02 +00:00
Tom Price
ec4e033745 Merge branch 'master' into training
# Conflicts:
#	.idea/dataSources.ids
#	db.sqlite3
2015-11-10 12:18:53 +00:00
Tom Price
9379050e15 Add basic detail view for training record.
Only committing because I have other things to do
2015-10-28 17:41:02 +00:00
Tom Price
4689775a5f Several changes to models
Add __str__ methods with unicode compatibility.

Make trainer fields optional as they should be.
Include these migrations and apply to DB.

Install some test data in the DB.
2015-10-22 13:54:26 +01:00
Tom Price
32aabe14c7 Add training models to admin interface 2015-10-22 13:40:42 +01:00
Tom Price
697a5977c8 Add the unique together constraint to TrainingRecord model and actually commit the migration this time. 2015-10-22 00:19:05 +01:00
Tom Price
80ba59c3ba Fix models and create migration 2015-10-21 17:51:59 +01:00
Tom Price
c60e3fb9fe Add training app to installed apps 2015-10-21 17:03:46 +01:00
Tom Price
9beb7c1612 Update to allow pycharm to access the local postgres db 2015-10-21 17:00:03 +01:00
Tom Price
227b63e689 Add a sensible data structure.
This is sensible to the current structure but there are a few changes. These were done to maintain the same functionality, but to reduce the number of DB rows used.
2015-10-21 16:59:17 +01:00
Tom Price
fa231887d2 Add local postgres database for training to settings 2015-10-21 16:08:17 +01:00
Tom Price
37e5549736 Convert the export scripts from generic SQL to usable postgres including relationships 2015-10-21 15:57:47 +01:00
Tom Price
adbcd0733d Add initial data exported from access 2015-10-21 13:52:33 +01:00
Tom Price
0b35965fa1 Start the conversion of the training database as a priority.
Also includes a script to export access to SQL
2015-10-21 13:32:25 +01:00
73 changed files with 93826 additions and 1274 deletions

1
.idea/.name generated
View File

@@ -1 +0,0 @@
PyRIGS

5
.idea/encodings.xml generated
View File

@@ -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
View File

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

View File

@@ -1,5 +0,0 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -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:

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
# TEC PA & Lighting - PyRIGS #
[![wercker status](https://app.wercker.com/status/2dbe0517c3d83859c985ffc5a55a2802/m/master "wercker status")](https://app.wercker.com/project/bykey/2dbe0517c3d83859c985ffc5a55a2802)
[![wercker status](https://app.wercker.com/status/b26100ecccdfb46a9a9056553daac5b7/m/master "wercker status")](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.

View File

@@ -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)

View File

@@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}

View File

@@ -40,9 +40,6 @@
{% endif %}
{% include 'RIGS/object_button.html' with object=version.new %}
{% if version.revision.comment %}
({{ version.revision.comment }})
{% endif %}
</small>
</p>

View File

@@ -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 %}

View File

@@ -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 %}

View File

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

View File

@@ -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 %}

View File

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

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

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

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

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

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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/')

View File

@@ -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):

View File

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

View File

@@ -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()),

View File

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

Binary file not shown.

View File

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

View File

@@ -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)

View File

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

View File

@@ -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()

View File

@@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -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();

View File

@@ -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
View 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
View 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

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

View 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),
),
]

File diff suppressed because it is too large Load Diff

View 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)
);

View 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;

File diff suppressed because it is too large Load Diff

View 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)
);

View 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
View 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)

View 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 %}

View 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 %}

View 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 %}

View 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>
&nbsp;
</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 %}

View 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>

View 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>

View File

@@ -0,0 +1 @@
__author__ = 'Tom Price'

View 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
View 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
View 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'