Compare commits

...

55 Commits

Author SHA1 Message Date
David Taylor
458059fd06 Update to heroku-18 stack 2019-10-15 23:39:39 +01:00
David Taylor
1a49bb50e5 Further version history improvements 2019-07-28 23:40:35 +01:00
David Taylor
86b349f60e Tidy up version history for risk assessments 2019-07-28 23:32:54 +01:00
David Taylor
35997aa882 Add API hook for logging risk assessment completion (#341) 2019-07-28 23:08:18 +01:00
David Taylor
faa4573f6d Add dash to date range 2019-07-14 23:15:13 +01:00
David Taylor
7babaee44c Add link to pre-filled risk assessment form 2019-07-14 23:09:44 +01:00
David Taylor
c0fe176495 Add docker dev instructions 2019-07-14 23:04:12 +01:00
David Taylor
b269069b6a Improve embedding style 2019-06-20 00:56:11 +01:00
David Taylor
7e06b5a162 Increase height of forum embeds 2019-06-20 00:29:10 +01:00
David Taylor
a18bb07d78 Update views.py 2019-06-20 00:15:16 +01:00
David Taylor
bd28d2054e Remove autofocus from form 2019-06-20 00:13:24 +01:00
David Taylor
14836f135c Update subhire form location
Requested by Jonny
2019-02-25 21:29:14 +00:00
David Taylor
5f8a77586a Revert "Add warning"
This reverts commit e5b7fdbae1.
2018-10-07 00:30:00 +01:00
David Taylor
e5b7fdbae1 Add warning 2018-09-13 14:01:44 +01:00
imgbot[bot]
f1c8dca8c4 [ImgBot] optimizes images (#339)
*Total -- 680.70kb -> 575.32kb (15.48%)

/RIGS/static/imgs/pyrigs-avatar.png -- 4.79kb -> 2.16kb (54.98%)
/RIGS/static/imgs/404.jpg -- 131.98kb -> 108.89kb (17.49%)
/RIGS/static/imgs/403.jpg -- 155.95kb -> 129.73kb (16.81%)
/RIGS/static/imgs/500.jpg -- 118.90kb -> 100.13kb (15.79%)
/RIGS/static/imgs/401.jpg -- 151.27kb -> 129.03kb (14.7%)
/RIGS/static/imgs/400.jpg -- 104.61kb -> 93.13kb (10.97%)
/RIGS/static/imgs/paperwork/tec-logo.jpg -- 13.21kb -> 12.26kb (7.19%)
2018-08-19 19:08:35 +01:00
David Taylor
843b76d8ea [requires.io] dependency update on master branch (#336)
* [requires.io] dependency update

* [requires.io] dependency update

* [requires.io] dependency update
2018-05-25 16:22:07 +01:00
David Taylor
e81af9e479 Merge pull request #335 from nottinghamtec/requires-io-master
[requires.io] dependency update on master branch
2018-05-08 17:57:11 +01:00
requires.io
efab8c8cef [requires.io] dependency update 2018-05-08 16:41:37 +01:00
requires.io
3f7531e157 [requires.io] dependency update 2018-05-02 03:12:55 +01:00
requires.io
988f3dced4 [requires.io] dependency update 2018-04-30 19:49:11 +01:00
requires.io
bd3240c2bc [requires.io] dependency update 2018-04-28 20:25:35 +01:00
requires.io
7fbbe2871f [requires.io] dependency update 2018-04-22 00:34:34 +01:00
requires.io
109ece508b [requires.io] dependency update 2018-04-19 07:36:06 +01:00
requires.io
971144c2e6 [requires.io] dependency update 2018-04-18 11:16:00 +01:00
David Taylor
a3c6edda0b Merge pull request #334 from nottinghamtec/requires-io-master
[requires.io] dependency update on master branch
2018-04-16 23:59:36 +01:00
requires.io
71bb4696b8 [requires.io] dependency update 2018-04-16 23:19:10 +01:00
requires.io
2a3ed0b763 [requires.io] dependency update 2018-04-12 01:29:58 +01:00
requires.io
632fa56353 [requires.io] dependency update 2018-04-11 06:08:19 +01:00
requires.io
f3020fc783 [requires.io] dependency update 2018-04-10 19:16:12 +01:00
requires.io
9fdf5e674e [requires.io] dependency update 2018-04-10 12:08:43 +01:00
requires.io
cfe03a8628 [requires.io] dependency update 2018-04-10 05:17:39 +01:00
requires.io
eccf224b63 [requires.io] dependency update 2018-04-03 04:33:32 +01:00
requires.io
0b2c86ebb5 [requires.io] dependency update 2018-04-03 00:19:18 +01:00
David Taylor
f616857131 Merge pull request #328 from nottinghamtec/django2
Upgrade to Django 2
2018-04-02 21:36:10 +01:00
David Taylor
60fb90a50c Merge pull request #333 from nottinghamtec/hotfix/fix-jonos-commit
Remove un-necessary use of reversion
2018-03-26 19:46:06 +01:00
David Taylor
66f024e961 Remove un-necessary use of reversion 2018-03-26 19:41:13 +01:00
Johnathan Graydon
06daacf611 Automatically set event to booked when the client authorises it (#332)
* Automatically set rig to booked when event is authorised
Will close #320
2018-03-26 14:51:42 +01:00
Johnathan Graydon
c74bc945b6 Not error if no person
Will close #330
2018-03-26 14:09:51 +01:00
Johnathan Graydon
3c605d2976 Fix pep8 2018-03-25 15:30:05 +01:00
Johnathan Graydon
9720066fd7 Remove checked in by on event duplication
Will close #327
2018-03-25 15:30:05 +01:00
Johnathan Graydon
b157e3b187 Add 127.0.0.1 to Allowed_Hosts for debug 2018-03-25 15:30:05 +01:00
David Taylor
19030fdf2f Use django-widget-tweaks from GitHub until latest version is on PyPI
See https://github.com/jazzband/django-widget-tweaks/issues/62
2018-03-25 00:48:17 +00:00
David Taylor
42450b5a22 User.is_authenticated is no longer callable 2018-03-25 00:28:37 +00:00
David Taylor
ce11df9bbc Rename MIDDLEWARE_CLASSES to MIDDLEWARE 2018-03-25 00:21:30 +00:00
David Taylor
82e664c5e0 SessionAuthenticationMiddleware is no longer required (as of Django 1.10) 2018-03-25 00:21:15 +00:00
David Taylor
8098b33698 Migrate profile to have longer last_name field (Django 2.0 updated AbstractUser model) 2018-03-25 00:20:51 +00:00
David Taylor
9c2603557c Merge pull request #324 from nottinghamtec/hotfix/auth-request
Fix null person on authorisation request
2018-03-25 00:01:05 +00:00
David Taylor
f4209f21dc Remove include( from admin.site.urls 2018-03-24 23:58:54 +00:00
David Taylor
1e3c021a76 Add on_delete=models.CASCADE to old migrations 2018-03-24 23:58:54 +00:00
David Taylor
d8f9256252 Add on_delete=models.CASCADE to OneToOneFields 2018-03-24 23:58:54 +00:00
David Taylor
014bab6c1f Add on_delete=models.CASCADE to all foreign keys. This replicates the previous default behaviour 2018-03-24 23:58:54 +00:00
David Taylor
8872084cab Import URL functions from django.urls 2018-03-24 23:58:39 +00:00
David Taylor
76ceb15000 Bump versions 2018-03-24 23:58:07 +00:00
Johnathan Graydon
7dff951f28 Fix null person on authorisation request
Will close #319
2018-03-24 23:54:03 +00:00
David Taylor
05feb7df1c Merge pull request #325 from nottinghamtec/travis-fix
Bump chrome and chromedriver, update recaptcha keys, add --no-sandbox to chrome
2018-03-24 23:52:43 +00:00
43 changed files with 242 additions and 88 deletions

12
Dockerfile Normal file
View File

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

View File

@@ -1,7 +1,7 @@
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.urls import reverse
from RIGS import models from RIGS import models
@@ -23,7 +23,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
def _checklogin(request, *args, **kwargs): def _checklogin(request, *args, **kwargs):
if test_func(request.user): if test_func(request.user):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
elif not request.user.is_authenticated(): elif not request.user.is_authenticated:
if oembed_view is not None: if oembed_view is not None:
context = {} context = {}
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import raven import raven
import secrets
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -35,6 +36,7 @@ if STAGING:
if DEBUG: if DEBUG:
ALLOWED_HOSTS.append('localhost') ALLOWED_HOSTS.append('localhost')
ALLOWED_HOSTS.append('example.com') ALLOWED_HOSTS.append('example.com')
ALLOWED_HOSTS.append('127.0.0.1')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG: if not DEBUG:
@@ -65,7 +67,7 @@ INSTALLED_APPS = (
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE = (
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware',
@@ -74,7 +76,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) )
@@ -233,3 +234,7 @@ USE_GRAVATAR = True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
'RISK_ASSESSMENT_URL') else "http://example.com"
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)

View File

@@ -17,7 +17,7 @@ urlpatterns = [
url('^user/', include('django.contrib.auth.urls')), url('^user/', include('django.contrib.auth.urls')),
url('^user/', include('registration.backends.default.urls')), url('^user/', include('registration.backends.default.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', admin.site.urls),
] ]
if settings.DEBUG: if settings.DEBUG:

View File

@@ -77,6 +77,13 @@ python manage.py runserver
``` ```
Please refer to Django documentation for a full list of options available here. Please refer to Django documentation for a full list of options available here.
### Development using docker
```
docker build . -t pyrigs
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
```
### Sample Data ### ### Sample Data ###
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty: Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
``` ```

View File

@@ -2,7 +2,7 @@ import datetime
import re import re
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse_lazy from django.urls import reverse_lazy
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404

View File

@@ -33,7 +33,12 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
return self.cleaned_data['initials'] return self.cleaned_data['initials']
# Login form # Embedded Login form - remove the autofocus
class EmbeddedAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.pop('autofocus', None)
class PasswordReset(PasswordResetForm): class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha') captcha = ReCaptchaField(label='Captcha')

View File

@@ -1,7 +1,7 @@
from RIGS import models, forms from RIGS import models, forms
from django_ical.views import ICalFeed from django_ical.views import ICalFeed
from django.db.models import Q from django.db.models import Q
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch from django.urls import reverse_lazy, reverse, NoReverseMatch
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings

View File

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

View File

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

View File

@@ -14,26 +14,26 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='based_on', name='based_on',
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True), field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='checked_in_by', name='checked_in_by',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True,
null=True), null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='mic', name='mic',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='organisation', name='organisation',
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True), field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(

View File

@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
('run', models.BooleanField(default=False)), ('run', models.BooleanField(default=False)),
('derig', models.BooleanField(default=False)), ('derig', models.BooleanField(default=False)),
('notes', models.TextField(blank=True, null=True)), ('notes', models.TextField(blank=True, null=True)),
('event', models.ForeignKey(related_name='crew', to='RIGS.Event')), ('event', models.ForeignKey(related_name='crew', to='RIGS.Event', on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
}, },
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='eventitem', model_name='eventitem',
name='event', name='event',
field=models.ForeignKey(related_name='items', to='RIGS.Event'), field=models.ForeignKey(related_name='items', to='RIGS.Event', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='person', name='person',
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person'), field=models.ForeignKey(blank=True, null=True, to='RIGS.Person', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -14,13 +14,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='venue', name='venue',
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True), field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='eventitem', model_name='eventitem',
name='event', name='event',
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event'), field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('invoice_date', models.DateField(auto_now_add=True)), ('invoice_date', models.DateField(auto_now_add=True)),
('void', models.BooleanField()), ('void', models.BooleanField()),
('event', models.OneToOneField(to='RIGS.Event')), ('event', models.OneToOneField(to='RIGS.Event', on_delete=models.CASCADE)),
], ],
options={ options={
}, },
@@ -31,7 +31,7 @@ class Migration(migrations.Migration):
('date', models.DateField()), ('date', models.DateField()),
('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)), ('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)),
('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])), ('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])),
('invoice', models.ForeignKey(to='RIGS.Invoice')), ('invoice', models.ForeignKey(to='RIGS.Invoice', on_delete=models.CASCADE)),
], ],
options={ options={
}, },
@@ -40,7 +40,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='mic', name='mic',
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True), field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
('account_code', models.CharField(max_length=50, null=True, blank=True)), ('account_code', models.CharField(max_length=50, null=True, blank=True)),
('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)), ('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)),
('created_at', models.DateTimeField(auto_now_add=True)), ('created_at', models.DateTimeField(auto_now_add=True)),
('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event')), ('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event', on_delete=models.CASCADE)),
], ],
), ),
] ]

View File

@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='eventauthorisation', model_name='eventauthorisation',
name='event', name='event',
field=models.OneToOneField(related_name='authorisation', to='RIGS.Event'), field=models.OneToOneField(related_name='authorisation', to='RIGS.Event', on_delete=models.CASCADE),
), ),
] ]

View File

@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='eventauthorisation', model_name='eventauthorisation',
name='sent_by', name='sent_by',
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL), field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='auth_request_by', name='auth_request_by',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True), field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ from collections import Counter
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse_lazy from django.urls import reverse_lazy
# Create your models here. # Create your models here.
@@ -302,9 +302,9 @@ class Event(models.Model, RevisionMixin):
) )
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
person = models.ForeignKey('Person', null=True, blank=True) person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
organisation = models.ForeignKey('Organisation', blank=True, null=True) organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
venue = models.ForeignKey('Venue', blank=True, null=True) venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
@@ -323,9 +323,9 @@ class Event(models.Model, RevisionMixin):
meet_info = models.CharField(max_length=255, blank=True, null=True) meet_info = models.CharField(max_length=255, blank=True, null=True)
# Crew management # Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True) checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, on_delete=models.CASCADE)
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True,
verbose_name="MIC") verbose_name="MIC", on_delete=models.CASCADE)
# Monies # Monies
payment_method = models.CharField(max_length=255, blank=True, null=True) payment_method = models.CharField(max_length=255, blank=True, null=True)
@@ -334,10 +334,13 @@ class Event(models.Model, RevisionMixin):
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
# Authorisation request details # Authorisation request details
auth_request_by = models.ForeignKey('Profile', null=True, blank=True) auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
auth_request_at = models.DateTimeField(null=True, blank=True) auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True) auth_request_to = models.EmailField(null=True, blank=True)
# Risk assessment info
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
# Calculated values # Calculated values
""" """
EX Vat EX Vat
@@ -485,7 +488,7 @@ class Event(models.Model, RevisionMixin):
class EventItem(models.Model): class EventItem(models.Model):
event = models.ForeignKey('Event', related_name='items', blank=True) event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
quantity = models.IntegerField() quantity = models.IntegerField()
@@ -504,8 +507,8 @@ class EventItem(models.Model):
class EventCrew(models.Model): class EventCrew(models.Model):
event = models.ForeignKey('Event', related_name='crew') event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
rig = models.BooleanField(default=False) rig = models.BooleanField(default=False)
run = models.BooleanField(default=False) run = models.BooleanField(default=False)
derig = models.BooleanField(default=False) derig = models.BooleanField(default=False)
@@ -514,13 +517,13 @@ class EventCrew(models.Model):
@reversion.register @reversion.register
class EventAuthorisation(models.Model, RevisionMixin): class EventAuthorisation(models.Model, RevisionMixin):
event = models.OneToOneField('Event', related_name='authorisation') event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
email = models.EmailField() email = models.EmailField()
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
account_code = models.CharField(max_length=50, blank=True, null=True) account_code = models.CharField(max_length=50, blank=True, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
sent_by = models.ForeignKey('RIGS.Profile') sent_by = models.ForeignKey('RIGS.Profile', on_delete=models.CASCADE)
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk}) return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
@@ -532,7 +535,7 @@ class EventAuthorisation(models.Model, RevisionMixin):
@python_2_unicode_compatible @python_2_unicode_compatible
class Invoice(models.Model): class Invoice(models.Model):
event = models.OneToOneField('Event') event = models.OneToOneField('Event', on_delete=models.CASCADE)
invoice_date = models.DateField(auto_now_add=True) invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False) void = models.BooleanField(default=False)
@@ -584,7 +587,7 @@ class Payment(models.Model):
(ADJUSTMENT, 'TEC Adjustment'), (ADJUSTMENT, 'TEC Adjustment'),
) )
invoice = models.ForeignKey('Invoice') invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
date = models.DateField() date = models.DateField()
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT') amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True) method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)

View File

@@ -6,18 +6,19 @@ import urllib.parse
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.views import generic from django.views import generic
from django.core.urlresolvers import reverse_lazy from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import get_template from django.template.loader import get_template
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.urls import reverse
from django.core import signing from django.core import signing
from django.http import HttpResponse from django.http import HttpResponse
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.db.models import Q from django.db.models import Q
from django.contrib import messages from django.contrib import messages
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson import simplejson
@@ -80,6 +81,24 @@ class EventEmbed(EventDetail):
template_name = 'RIGS/event_embed.html' template_name = 'RIGS/event_embed.html'
class EventRA(generic.base.RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
event = get_object_or_404(models.Event, pk=kwargs['pk'])
if event.risk_assessment_edit_url:
return event.risk_assessment_edit_url
params = {
'entry.708610078': f'N{event.pk:05}',
'entry.905899507': event.name,
'entry.139491562': event.venue.name if event.venue else '',
'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''),
'entry.902421165': event.mic.name if event.mic else ''
}
return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params)
class EventCreate(generic.CreateView): class EventCreate(generic.CreateView):
model = models.Event model = models.Event
form_class = forms.EventForm form_class = forms.EventForm
@@ -138,7 +157,11 @@ class EventDuplicate(EventUpdate):
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
new = copy.copy(old) # Make a copy of the object in memory new = copy.copy(old) # Make a copy of the object in memory
new.based_on = old # Make the new event based on the old event new.based_on = old # Make the new event based on the old event
new.purchase_order = None new.purchase_order = None # Remove old PO
# Clear checked in by if it's a dry hire
if new.dry_hire is True:
new.checked_in_by = None
# Remove all the authorisation information from the new event # Remove all the authorisation information from the new event
new.auth_request_to = None new.auth_request_to = None
@@ -340,8 +363,10 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
'sent_by': self.request.user.pk, 'sent_by': self.request.user.pk,
}), }),
} }
if email == event.person.email: if event.person is not None and email == event.person.email:
context['to_name'] = event.person.name context['to_name'] = event.person.name
elif event.organisation is not None and email == event.organisation.email:
context['to_name'] = event.organisation.name
msg = EmailMultiAlternatives( msg = EmailMultiAlternatives(
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name), "N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
@@ -380,3 +405,26 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
}) })
context['to_name'] = self.request.GET.get('to_name', None) context['to_name'] = self.request.GET.get('to_name', None)
return context return context
@method_decorator(csrf_exempt, name='dispatch')
class LogRiskAssessment(generic.View):
http_method_names = ["post"]
def post(self, request, **kwargs):
data = request.POST
shared_secret = data.get("secret")
edit_url = data.get("editUrl")
rig_number = data.get("rigNum")
if shared_secret is None or edit_url is None or rig_number is None:
return HttpResponse(status=422)
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
return HttpResponse(status=403)
rig_number = int(re.sub("[^0-9]", "", rig_number))
event = get_object_or_404(models.Event, pk=rig_number)
event.risk_assessment_edit_url = edit_url
event.save()
return HttpResponse(status=200)

View File

@@ -50,8 +50,10 @@ def send_eventauthorisation_success_email(instance):
'object': instance, 'object': instance,
} }
if instance.email == instance.event.person.email: if instance.event.person is not None and instance.email == instance.event.person.email:
context['to_name'] = instance.event.person.name context['to_name'] = instance.event.person.name
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
context['to_name'] = instance.event.organisation.name
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name) subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
@@ -89,6 +91,10 @@ def send_eventauthorisation_success_email(instance):
client_email.send(fail_silently=True) client_email.send(fail_silently=True)
mic_email.send(fail_silently=True) mic_email.send(fail_silently=True)
# Set event to booked now that it's authorised
instance.event.status = models.Event.BOOKED
instance.event.save()
def on_revision_commit(sender, instance, created, **kwargs): def on_revision_commit(sender, instance, created, **kwargs):
if created: if created:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -149,16 +149,19 @@ ins {
} }
html.embedded{ html.embedded{
min-height:100%; display: flex;
display: table; flex-direction: column;
width: 100%; width: 100%;
height: 100%;
max-height: 100%;
overflow: hidden;
justify-content: center;
body{ body{
padding:0; padding:0;
display: table-cell;
vertical-align: middle;
width:100%; width:100%;
background:none; background:none;
overflow: scroll;
} }
.embed_container{ .embed_container{

View File

@@ -3,6 +3,17 @@
class="glyphicon glyphicon-edit"></span> <span class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a> class="hidden-xs">Edit</span></a>
{% if event.is_rig %} {% if event.is_rig %}
{% if not event.dry_hire %}
<a href="{% url 'event_ra' event.pk %}" class="btn btn-default
{% if event.risk_assessment_edit_url %}
btn-success
{% else %}
btn-warning
{% endif %}
"><span
class="glyphicon glyphicon-paperclip"></span> <span
class="hidden-xs">RA</span></a>
{% endif %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span <a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a> class="hidden-xs">Print</span></a>

View File

@@ -27,7 +27,7 @@
<a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a> <a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a>
<a class="list-group-item" href="http://members.nottinghamtec.co.uk/wiki/images/2/22/Event_Risk_Assesment.pdf" target="_blank"><span class="glyphicon glyphicon-link"></span> Pre-Event Risk Assessment</a> <a class="list-group-item" href="http://members.nottinghamtec.co.uk/wiki/images/2/22/Event_Risk_Assesment.pdf" target="_blank"><span class="glyphicon glyphicon-link"></span> Pre-Event Risk Assessment</a>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a> <a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a>
<a class="list-group-item" href="https://form.jotformeu.com/62203600438344" target="_blank"><span class="glyphicon glyphicon-link"></span> Subhire Insurance Form</a> <a class="list-group-item" href="https://goo.gl/forms/jdPWov8PCNPoXtbn2" target="_blank"><span class="glyphicon glyphicon-link"></span> Subhire Insurance Form</a>
</div> </div>
</div> </div>

View File

@@ -18,7 +18,7 @@ from selenium.webdriver.support.ui import WebDriverWait
from RIGS import models from RIGS import models
from reversion import revisions as reversion from reversion import revisions as reversion
from django.core.urlresolvers import reverse from django.urls import reverse
from django.core import mail, signing from django.core import mail, signing

View File

@@ -2,7 +2,7 @@ from datetime import date
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.management import call_command from django.core.management import call_command
from django.core.urlresolvers import reverse from django.urls import reverse
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings

View File

@@ -101,6 +101,9 @@ urlpatterns = [
url(r'^event/(?P<pk>\d+)/print/$', url(r'^event/(?P<pk>\d+)/print/$',
permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
name='event_print'), name='event_print'),
url(r'^event/(?P<pk>\d+)/ra/$',
permission_required_with_403('RIGS.change_event')(rigboard.EventRA.as_view()),
name='event_ra'),
url(r'^event/create/$', url(r'^event/create/$',
permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()), permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()),
name='event_create'), name='event_create'),
@@ -185,6 +188,9 @@ urlpatterns = [
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()), url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()),
name="api_secure"), name="api_secure"),
# Risk assessment API
url(r'^log_risk_assessment/$', rigboard.LogRiskAssessment.as_view(), name='log_risk_assessment'),
# Legacy URL's # Legacy URL's
url(r'^rig/show/(?P<pk>\d+)/$', url(r'^rig/show/(?P<pk>\d+)/$',
RedirectView.as_view(permanent=True, pattern_name='event_detail')), RedirectView.as_view(permanent=True, pattern_name='event_detail')),

View File

@@ -27,6 +27,8 @@ class FieldComparison(object):
def display_value(self, value): def display_value(self, value):
if isinstance(self.field, IntegerField) and len(self.field.choices) > 0: if isinstance(self.field, IntegerField) and len(self.field.choices) > 0:
return [x[1] for x in self.field.choices if x[0] == value][0] return [x[1] for x in self.field.choices if x[0] == value][0]
if self.field.name == "risk_assessment_edit_url":
return "completed" if value else ""
return value return value
@property @property

View File

@@ -1,7 +1,7 @@
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.http import HttpResponse from django.http import HttpResponse
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch from django.urls import reverse_lazy, reverse, NoReverseMatch
from django.views import generic from django.views import generic
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@@ -34,7 +34,7 @@ class Index(generic.TemplateView):
def login(request, **kwargs): def login(request, **kwargs):
if request.user.is_authenticated(): if request.user.is_authenticated:
next = request.GET.get('next', '/') next = request.GET.get('next', '/')
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
else: else:
@@ -49,7 +49,7 @@ def login(request, **kwargs):
# check for it before logging the user in # check for it before logging the user in
@csrf_exempt @csrf_exempt
def login_embed(request, **kwargs): def login_embed(request, **kwargs):
if request.user.is_authenticated(): if request.user.is_authenticated:
next = request.GET.get('next', '/') next = request.GET.get('next', '/')
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
else: else:
@@ -62,7 +62,7 @@ def login_embed(request, **kwargs):
messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.')
request.method = 'GET' # Render the page without trying to login request.method = 'GET' # Render the page without trying to login
return login(request, template_name="registration/login_embed.html") return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm)
""" """

View File

@@ -49,5 +49,6 @@
{ {
"url": "heroku/python" "url": "heroku/python"
} }
] ],
"stack": "heroku-18"
} }

View File

@@ -1,37 +1,38 @@
beautifulsoup4==4.6.0 beautifulsoup4==4.6.0
contextlib2==0.5.5 contextlib2==0.5.5
diff-match-patch==20121119 diff-match-patch==20121119
dj-database-url==0.4.2 dj-database-url==0.5.0
dj-static==0.0.6 dj-static==0.0.6
Django==1.11.7 Django==2.0.13
django-debug-toolbar==1.9.1 django-debug-toolbar==1.9.1
django-ical==1.4 django-ical==1.4
django-recaptcha==1.3.1 django-recaptcha==1.4.0
django-registration-redux==1.9 django-registration-redux==2.4
django-reversion==2.0.11 django-reversion==2.0.13
django-toolbelt==0.0.1 django-toolbelt==0.0.1
premailer==3.1.1 premailer==3.2.0
django-widget-tweaks==1.4.1 #django-widget-tweaks==1.4.1
gunicorn==19.7.1 git+git://github.com/jazzband/django-widget-tweaks.git@1.4.2
icalendar==4.0.0 gunicorn==19.8.1
lxml==4.1.1 icalendar==4.0.1
Markdown==2.6.9 lxml==4.2.1
Pillow==4.3.0 Markdown==2.6.11
psycopg2==2.7.3.2 Pillow==5.1.0
psycopg2==2.7.4
Pygments==2.2.0 Pygments==2.2.0
PyPDF2==1.26.0 PyPDF2==1.26.0
python-dateutil==2.6.1 python-dateutil==2.7.3
pytz==2017.3 pytz==2018.4
raven==6.3.0 raven==6.8.0
reportlab==3.4.0 reportlab==3.4.0
selenium==3.11.0 selenium==3.12.0
simplejson==3.13.2 simplejson==3.15.0
six==1.11.0 six==1.11.0
sqlparse==0.2.4 sqlparse==0.2.4
static3==0.7.0 static3==0.7.0
svg2rlg==0.3 svg2rlg==0.3
yolk==0.4.3 yolk==0.4.3
z3c.rml==3.2.0 z3c.rml==3.5.0
zope.event==4.3.0 zope.event==4.3.0
zope.interface==4.4.3 zope.interface==4.5.0
zope.schema==4.5.0 zope.schema==4.5.0