+ {% endif %}
{% for item_group in item.children %}
{% with width=item_group|length|derive_col_width %}
diff --git a/wildlifelicensing/apps/applications/tests/helpers.py b/wildlifelicensing/apps/applications/tests/helpers.py
index 319a53adc7..14b363b044 100755
--- a/wildlifelicensing/apps/applications/tests/helpers.py
+++ b/wildlifelicensing/apps/applications/tests/helpers.py
@@ -1,20 +1,22 @@
from datetime import datetime
from django.test import TestCase
-from django_dynamic_fixture import get as get_ddf
+from django_dynamic_fixture import G
from django.core.urlresolvers import reverse, reverse_lazy
-from ledger.accounts.models import Profile
+from ledger.accounts.models import Profile, Address
from wildlifelicensing.apps.applications.views.entry import LICENCE_TYPE_NUM_CHARS, LODGEMENT_NUMBER_NUM_CHARS
from wildlifelicensing.apps.applications.models import Application, Assessment, Condition
from wildlifelicensing.apps.main.tests.helpers import create_random_customer, get_or_create_licence_type, \
- SocialClient, get_or_create_default_assessor_group, get_or_create_default_officer
+ SocialClient, get_or_create_default_assessor_group, get_or_create_default_officer, create_default_country
def create_profile(user):
- return get_ddf(Profile, user=user)
+ create_default_country()
+ address = G(Address, user=user, country='AU')
+ return G(Profile, adress=address, user=user)
def create_application(user=None, **kwargs):
@@ -28,7 +30,7 @@ def create_application(user=None, **kwargs):
kwargs['licence_type'] = get_or_create_licence_type()
if 'data' not in kwargs:
kwargs['data'] = {}
- application = get_ddf(Application, **kwargs)
+ application = G(Application, **kwargs)
return application
diff --git a/wildlifelicensing/apps/applications/tests/test_entry.py b/wildlifelicensing/apps/applications/tests/test_entry.py
index 2123841226..c3061a0b25 100755
--- a/wildlifelicensing/apps/applications/tests/test_entry.py
+++ b/wildlifelicensing/apps/applications/tests/test_entry.py
@@ -19,6 +19,7 @@ class ApplicationEntryTestCase(TestCase):
fixtures = ['licences.json', 'catalogue.json', 'partner.json']
def setUp(self):
+ helpers.create_default_country()
self.customer = get_or_create_default_customer()
self.client = SocialClient()
diff --git a/wildlifelicensing/apps/applications/utils.py b/wildlifelicensing/apps/applications/utils.py
index 54ecaf316b..5271020016 100755
--- a/wildlifelicensing/apps/applications/utils.py
+++ b/wildlifelicensing/apps/applications/utils.py
@@ -84,12 +84,7 @@ def _extract_licence_fields_from_item(item, data, licence_fields):
if 'children' not in item:
# label / checkbox types are extracted differently so skip here
if item['type'] not in ('label', 'checkbox'):
- licence_field = {
- 'name': item['name'],
- 'label': item['licenceFieldLabel'] if 'licenceFieldLabel' in item else item['label'],
- 'type': item['type'],
- 'readonly': item.get('isLicenceFieldReadonly', False)
- }
+ licence_field = _create_licence_field(item)
if 'options' in item:
licence_field['options'] = item['options']
@@ -99,13 +94,8 @@ def _extract_licence_fields_from_item(item, data, licence_fields):
licence_fields.append(licence_field)
else:
- licence_field = {
- 'name': item['name'],
- 'label': item['licenceFieldLabel'] if 'licenceFieldLabel' in item else item['label'],
- 'type': item['type'],
- 'readonly': item.get('isLicenceFieldReadonly', False),
- 'children': []
- }
+ licence_field = _create_licence_field(item)
+ licence_field['children'] = []
child_data = _extract_item_data(item['name'], data)
@@ -139,13 +129,8 @@ def _extract_licence_fields_from_item(item, data, licence_fields):
def _extract_label_and_checkboxes(current_item, items, data, licence_fields):
- licence_field = {
- 'name': current_item['name'],
- 'label': current_item['licenceFieldLabel'] if 'licenceFieldLabel' in current_item else current_item['label'],
- 'type': current_item['type'],
- 'readonly': current_item.get('isLicenceFieldReadonly', False),
- 'options': []
- }
+ licence_field = _create_licence_field(current_item)
+ licence_field['options'] = []
# find index of first checkbox after checkbox label within current item list
checkbox_index = 0
@@ -170,6 +155,16 @@ def _extract_label_and_checkboxes(current_item, items, data, licence_fields):
licence_fields.append(licence_field)
+def _create_licence_field(item):
+ return {
+ 'name': item['name'],
+ 'type': item['type'],
+ 'label': item['licenceFieldLabel'] if 'licenceFieldLabel' in item else item['label'],
+ 'help_text': item.get('licenceFieldHelpText', ''),
+ 'readonly': item.get('isLicenceFieldReadonly', False)
+ }
+
+
def _extract_item_data(name, data):
def ___extract_item_data(name, data):
if isinstance(data, dict):
diff --git a/wildlifelicensing/apps/applications/views/entry.py b/wildlifelicensing/apps/applications/views/entry.py
index ae9ff995d5..84a6628ca9 100755
--- a/wildlifelicensing/apps/applications/views/entry.py
+++ b/wildlifelicensing/apps/applications/views/entry.py
@@ -196,6 +196,8 @@ def get(self, request, *args, **kwargs):
application.licence_type = WildlifeLicenceType.objects.get(id=self.args[0])
+ application.data = None
+
application.variants.clear()
for index, variant_id in enumerate(request.GET.getlist('variants', [])):
diff --git a/wildlifelicensing/apps/main/fixtures/groups.json b/wildlifelicensing/apps/main/fixtures/groups.json
index 90567b2aff..db4f4b29e4 100644
--- a/wildlifelicensing/apps/main/fixtures/groups.json
+++ b/wildlifelicensing/apps/main/fixtures/groups.json
@@ -10,5 +10,11 @@
"fields": {
"name": "Assessors"
}
+ },
+ {
+ "model": "auth.Group",
+ "fields": {
+ "name": "API"
+ }
}
]
diff --git a/wildlifelicensing/apps/main/pdf.py b/wildlifelicensing/apps/main/pdf.py
index 3f233c505d..c2c3683f53 100755
--- a/wildlifelicensing/apps/main/pdf.py
+++ b/wildlifelicensing/apps/main/pdf.py
@@ -37,6 +37,7 @@
DEFAULT_FONTNAME = 'Helvetica'
BOLD_FONTNAME = 'Helvetica-Bold'
+ITALIC_FONTNAME = 'Helvetica-Oblique'
BOLD_ITALIC_FONTNAME = 'Helvetica-BoldOblique'
VERY_LARGE_FONTSIZE = 14
@@ -79,6 +80,8 @@
rightIndent=PAGE_WIDTH / 10))
styles.add(ParagraphStyle(name='BoldLeft', fontName=BOLD_FONTNAME, fontSize=MEDIUM_FONTSIZE, alignment=enums.TA_LEFT))
styles.add(ParagraphStyle(name='BoldRight', fontName=BOLD_FONTNAME, fontSize=MEDIUM_FONTSIZE, alignment=enums.TA_RIGHT))
+styles.add(ParagraphStyle(name='ItalicLeft', fontName=ITALIC_FONTNAME, fontSize=MEDIUM_FONTSIZE, alignment=enums.TA_LEFT))
+styles.add(ParagraphStyle(name='ItalifRight', fontName=ITALIC_FONTNAME, fontSize=MEDIUM_FONTSIZE, alignment=enums.TA_RIGHT))
styles.add(ParagraphStyle(name='Center', alignment=enums.TA_CENTER))
styles.add(ParagraphStyle(name='Left', alignment=enums.TA_LEFT))
styles.add(ParagraphStyle(name='Right', alignment=enums.TA_RIGHT))
@@ -264,6 +267,11 @@ def _layout_extracted_fields(extracted_fields):
elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
elements.append(Paragraph(field['label'], styles['BoldLeft']))
+ if field['help_text']:
+ elements.append(Paragraph(field['help_text'], styles['ItalicLeft']))
+
+ elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
+
if field['type'] in ['text', 'text_area']:
elements += _layout_paragraphs(field['data'])
elif field['type'] in ['radiobuttons', 'select']:
@@ -276,6 +284,12 @@ def _layout_extracted_fields(extracted_fields):
if any([option.get('data', 'off') == 'on' for option in field['options']]):
elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
elements.append(Paragraph(field['label'], styles['BoldLeft']))
+
+ if field['help_text']:
+ elements.append(Paragraph(field['help_text'], styles['ItalicLeft']))
+
+ elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
+
elements.append(Paragraph(', '.join([option['label'] for option in field['options']
if option.get('data', 'off') == 'on']),
styles['Left']))
@@ -283,6 +297,9 @@ def _layout_extracted_fields(extracted_fields):
elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
elements.append(Paragraph(field['label'], styles['BoldLeft']))
+ if field['help_text']:
+ elements.append(Paragraph(field['help_text'], styles['ItalicLeft']))
+
table_data = []
for index, group in enumerate(field['children']):
if index == 0:
@@ -398,7 +415,7 @@ def _create_letter_signature():
signature_elements = []
signature_elements.append(Paragraph('Yours sincerely', styles['LetterLeft']))
signature_elements.append(Spacer(1, SECTION_BUFFER_HEIGHT * 4))
- signature_elements.append(Paragraph('from Jim Sharp', styles['LetterLeft']))
+ signature_elements.append(Paragraph('for Jim Sharp', styles['LetterLeft']))
signature_elements.append(Paragraph('DIRECTOR GENERAL', styles['LetterLeft']))
signature_elements.append(Spacer(1, SECTION_BUFFER_HEIGHT))
diff --git a/wildlifelicensing/apps/main/tests/helpers.py b/wildlifelicensing/apps/main/tests/helpers.py
index b8640dafaa..6713c7f663 100644
--- a/wildlifelicensing/apps/main/tests/helpers.py
+++ b/wildlifelicensing/apps/main/tests/helpers.py
@@ -8,9 +8,9 @@
from django.core.urlresolvers import reverse
from django.test import Client, TestCase
from django.utils.encoding import smart_text
-from django_dynamic_fixture import get as get_ddf
+from django_dynamic_fixture import G
-from ledger.accounts.models import EmailUser, Profile, Address
+from ledger.accounts.models import EmailUser, Profile, Address, Country
from wildlifelicensing.apps.main import helpers as accounts_helpers
from wildlifelicensing.apps.main.models import WildlifeLicenceType, WildlifeLicence, AssessorGroup
@@ -48,6 +48,12 @@ class TestData(object):
'name': 'ass group',
'email': 'assessor@test.com',
}
+ DEFAULT_API_USER = {
+ 'email': 'apir@test.com',
+ 'first_name': 'api',
+ 'last_name': 'user',
+ 'dob': '1979-12-13',
+ }
class SocialClient(Client):
@@ -70,6 +76,10 @@ def logout(self):
self.get(reverse('accounts:logout'))
+def create_default_country():
+ return G(Country, iso_3166_1_a2='AU')
+
+
def is_client_authenticated(client):
return '_auth_user_id' in client.session
@@ -92,7 +102,7 @@ def get_or_create_user(email, defaults):
def create_random_user():
- return get_ddf(EmailUser, dob='1970-01-01')
+ return G(EmailUser, dob='1970-01-01')
def create_random_customer():
@@ -117,6 +127,13 @@ def get_or_create_default_officer():
return user
+def get_or_create_api_user():
+ user, created = get_or_create_user(TestData.DEFAULT_API_USER['email'], TestData.DEFAULT_API_USER)
+ if created:
+ add_to_group(user, 'API')
+ return user
+
+
def get_or_create_licence_type(product_code='regulation-17'):
return WildlifeLicenceType.objects.get_or_create(product_code=product_code)[0]
diff --git a/wildlifelicensing/apps/main/tests/tests.py b/wildlifelicensing/apps/main/tests/tests.py
index 0ed0423f5f..2dc20f773b 100644
--- a/wildlifelicensing/apps/main/tests/tests.py
+++ b/wildlifelicensing/apps/main/tests/tests.py
@@ -7,13 +7,14 @@
from ledger.accounts.models import EmailUser, Profile
from wildlifelicensing.apps.main.tests.helpers import SocialClient, get_or_create_default_customer, \
- get_or_create_default_officer, TestData, upload_id
+ get_or_create_default_officer, TestData, upload_id, create_default_country
TEST_ID_PATH = TestData.TEST_ID_PATH
class AccountsTestCase(TestCase):
def setUp(self):
+ create_default_country()
self.customer = get_or_create_default_customer()
self.officer = get_or_create_default_officer()
diff --git a/wildlifelicensing/apps/main/views.py b/wildlifelicensing/apps/main/views.py
index 3f4ec4ad40..70865a96a3 100755
--- a/wildlifelicensing/apps/main/views.py
+++ b/wildlifelicensing/apps/main/views.py
@@ -190,15 +190,13 @@ def post(self, request, *args, **kwargs):
original_last_name = emailuser.last_name
emailuser_form = EmailUserForm(request.POST, instance=emailuser)
if emailuser_form.is_valid():
- emailuser = emailuser_form.save(commit=False)
+ emailuser = emailuser_form.save()
is_name_changed = any([original_first_name != emailuser.first_name, original_last_name != emailuser.last_name])
# send signal if either first name or last name is changed
if is_name_changed:
messages.warning(request, "Please upload new identification after you changed your name.")
return redirect(self.identification_url)
- elif not emailuser.identification:
- messages.warning(request, "Please upload your identification.")
else:
messages.success(request, "User account was saved.")
diff --git a/wildlifelicensing/apps/payments/utils.py b/wildlifelicensing/apps/payments/utils.py
index aaf092f42d..5683344dd8 100644
--- a/wildlifelicensing/apps/payments/utils.py
+++ b/wildlifelicensing/apps/payments/utils.py
@@ -35,7 +35,7 @@ def __append_variant_codes(product_code, variant_group, current_variant_codes):
return
for variant in variant_group.variants.all():
- variant_code = '{}_{}'.format(product_code, variant.product_code)
+ variant_code = '{} {}'.format(product_code, variant.product_code)
__append_variant_codes(variant_code, variant_group.child, variant_codes)
@@ -50,8 +50,9 @@ def generate_product_code(application):
product_code = application.licence_type.product_code
if application.variants.exists():
- product_code += '_' + '_'.join(application.variants.through.objects.filter(application=application).
- order_by('order').values_list('variant__product_code', flat=True))
+ product_code = '{} {}'.format(product_code,
+ ' '.join(application.variants.through.objects.filter(application=application).
+ order_by('order').values_list('variant__product_code', flat=True)))
return product_code
diff --git a/wildlifelicensing/apps/payments/views.py b/wildlifelicensing/apps/payments/views.py
index 180322fb27..961cbfd9d1 100644
--- a/wildlifelicensing/apps/payments/views.py
+++ b/wildlifelicensing/apps/payments/views.py
@@ -98,7 +98,9 @@ def get(self, request):
data = {
'system': PAYMENT_SYSTEM_ID,
'start': start,
- 'end': end
+ 'end': end,
+ 'banked_start': start,
+ 'banked_end': end
}
if 'items' in request.GET:
data['items'] = True
diff --git a/wildlifelicensing/apps/reports/templates/wl/reports.html b/wildlifelicensing/apps/reports/templates/wl/reports.html
index 3d2f6dbcdb..6aef1a406b 100755
--- a/wildlifelicensing/apps/reports/templates/wl/reports.html
+++ b/wildlifelicensing/apps/reports/templates/wl/reports.html
@@ -125,7 +125,6 @@
Payments
-
diff --git a/wildlifelicensing/apps/returns/api/mixins.py b/wildlifelicensing/apps/returns/api/mixins.py
new file mode 100644
index 0000000000..63b0b1022a
--- /dev/null
+++ b/wildlifelicensing/apps/returns/api/mixins.py
@@ -0,0 +1,18 @@
+from django.contrib.auth.mixins import UserPassesTestMixin
+
+from wildlifelicensing.apps.main.helpers import belongs_to
+
+
+def is_api_user(user):
+ return belongs_to(user, 'API') or user.is_superuser
+
+
+class APIUserRequiredMixin(UserPassesTestMixin):
+ """
+ Mixin uses for API view.
+ """
+ # we don't want to be redirected to login page.
+ raise_exception = True
+
+ def test_func(self):
+ return is_api_user(self.request.user)
diff --git a/wildlifelicensing/apps/returns/api/views.py b/wildlifelicensing/apps/returns/api/views.py
index 0086d63d4a..392f503e34 100644
--- a/wildlifelicensing/apps/returns/api/views.py
+++ b/wildlifelicensing/apps/returns/api/views.py
@@ -5,12 +5,20 @@
from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django.views.generic import View
+from django.utils import timezone
+from wildlifelicensing.apps.returns.api.mixins import APIUserRequiredMixin
from wildlifelicensing.apps.returns.models import ReturnType, ReturnRow
from wildlifelicensing.apps.returns.utils_schema import Schema
+API_SESSION_TIMEOUT = 100 * 24 * 3600 # 100 days
-class ExplorerView(View):
+
+def set_api_session_timeout(request):
+ request.session.set_expiry(API_SESSION_TIMEOUT)
+
+
+class ExplorerView(APIUserRequiredMixin, View):
"""
Return a JSON representation of the ReturnTypes.
The main goal of this view is to provide for every resources (ReturnTable) a link to download the data
@@ -19,7 +27,15 @@ class ExplorerView(View):
def get(self, request):
queryset = ReturnType.objects.all()
- results = []
+ # for API purpose, increase the session timeout
+ set_api_session_timeout(request)
+ sessionid = self.request.session.session_key
+ payload = OrderedDict()
+ payload['auth'] = {
+ "sessionId": sessionid,
+ "expires": timezone.localtime(self.request.session.get_expiry_date())
+ }
+ data = []
for rt in queryset:
return_obj = OrderedDict({'id': rt.id})
licence_type = {
@@ -30,28 +46,39 @@ def get(self, request):
# resources
resources = []
for idx, resource in enumerate(rt.resources):
- resource_obj = OrderedDict()
- resource_obj['name'] = resource.get('name', '')
- resource_obj['data'] = request.build_absolute_uri(reverse('wl_returns:api:data', kwargs={
+ url = request.build_absolute_uri(reverse('wl_returns:api:data', kwargs={
'return_type_pk': rt.pk,
'resource_number': idx
}))
+ resource_obj = OrderedDict()
+ resource_obj['name'] = resource.get('name', '')
+ resource_obj['data'] = url
+ resource_obj['python'] = "requests.get('{0}', cookies={{'sessionid':'{1}'}}).content".format(
+ url,
+ sessionid
+ )
+ resource_obj['shell'] = "curl {0} --cookie 'sessionid={1}'".format(
+ url,
+ sessionid
+ )
resource_obj['schema'] = resource.get('schema', {})
resources.append(resource_obj)
return_obj['resources'] = resources
- results.append(return_obj)
-
- return JsonResponse(results, safe=False)
+ data.append(return_obj)
+ payload['data'] = data
+ return JsonResponse(payload, json_dumps_params={'indent': 2}, safe=False)
-class ReturnsDataView(View):
+class ReturnsDataView(APIUserRequiredMixin, View):
"""
Export returns data in CSV format.
"""
def get(self, request, *args, **kwargs):
return_type = get_object_or_404(ReturnType, pk=kwargs.get('return_type_pk'))
+ # for API purpose, increase the session timeout
+ set_api_session_timeout(request)
resource_number = kwargs.get('resource_number')
if not resource_number:
resource_number = 0
diff --git a/wildlifelicensing/apps/returns/static/wl/js/return_table.js b/wildlifelicensing/apps/returns/static/wl/js/return_table.js
index a020b31225..0c2380a1b7 100644
--- a/wildlifelicensing/apps/returns/static/wl/js/return_table.js
+++ b/wildlifelicensing/apps/returns/static/wl/js/return_table.js
@@ -1,8 +1,80 @@
-define(['jQuery', 'datatables.net', 'datatables.bootstrap', 'datatables.datetime'], function ($) {
+define([
+ 'jQuery',
+ 'datatables.net',
+ 'datatables.bootstrap',
+ 'datatables.datetime',
+ 'bootstrap-3-typeahead'
+], function ($) {
"use strict";
+ function querySpecies(speciesType, search, callback) {
+ var url = '/taxonomy/species_name',
+ params = {},
+ promise;
+ if (speciesType) {
+ params.type = speciesType;
+ }
+ if (search) {
+ params.search = search;
+ }
+ url += '?' + $.param(params);
+ promise = $.get(url);
+ if (typeof callback === 'function') {
+ promise.then(callback);
+ }
+ return promise;
+ }
+
+ function setSpeciesValid($field, valid) {
+ var validClass = 'text-success';
+ if (valid) {
+ $field.addClass(validClass);
+ } else {
+ $field.removeClass(validClass);
+ }
+ }
+
+ function validateSpeciesField($field, speciesType) {
+ // Rules: if only one species is returned from the api we consider the name to be valid.
+ // Trick: the species can be recorded as: species_name (common name) in this case the search will fail
+ // (species_name or common name but not both). We get rid of anything in parenthesis.
+ var value = $field.val();
+ if (value) {
+ value = value.replace(/\s?\(.*\)/, '');
+ querySpecies(speciesType, value.trim(), function (data) {
+ var valid = data && data.length === 1;
+ setSpeciesValid($field, valid);
+ });
+ }
+ }
+
+ function initSpeciesFields($parent) {
+ var $species_fields = $parent.find('input[data-species]');
+ if ($species_fields.length > 0) {
+ $species_fields.each(function () {
+ var $field = $(this),
+ speciesType = $field.attr('data-species'),
+ value;
+ $field.typeahead({
+ minLength: 2,
+ items: 'all',
+ source: function (query, process) {
+ querySpecies(speciesType, query, function (data) {
+ return process(data);
+ });
+ }
+ });
+ value = $field.val();
+ if (value) {
+ // already some data. We try to validate.
+ validateSpeciesField($field, speciesType);
+ }
+ });
+ }
+ }
+
return {
- initTables: function() {
+ initTables: function () {
var $tables = $('.return-table'),
$curationForm = $('#curationForm');
@@ -13,7 +85,9 @@ define(['jQuery', 'datatables.net', 'datatables.bootstrap', 'datatables.datetime
info: false
});
- $('.add-return-row').click(function() {
+ initSpeciesFields($tables);
+
+ $('.add-return-row').click(function () {
var $tbody = $(this).parent().find('table').find('tbody'),
$row = $tbody.find('tr:first');
// clone the top row
@@ -25,14 +99,15 @@ define(['jQuery', 'datatables.net', 'datatables.bootstrap', 'datatables.datetime
// append cloned row
$tbody.append($rowCopy);
+ initSpeciesFields($rowCopy);
});
- $('#accept').click(function() {
+ $('#accept').click(function () {
$curationForm.append($('').attr('name', 'accept').addClass('hidden'));
$curationForm.submit();
});
- $('#decline').click(function() {
+ $('#decline').click(function () {
$curationForm.append($('').attr('name', 'decline').addClass('hidden'));
$curationForm.submit();
});
diff --git a/wildlifelicensing/apps/returns/templates/wl/curate_return.html b/wildlifelicensing/apps/returns/templates/wl/curate_return.html
index 99f7c97cb3..2efa4537f5 100755
--- a/wildlifelicensing/apps/returns/templates/wl/curate_return.html
+++ b/wildlifelicensing/apps/returns/templates/wl/curate_return.html
@@ -115,7 +115,7 @@
{{ table.title }}
{% for header in table.headers %}
-
{{ header }}
+
{{ header.title }}{% if header.required %} *{% endif %}
{% endfor %}
@@ -123,10 +123,14 @@
{{ table.title }}
{% for row in table.data %}
{% for header in table.headers %}
- {% with value=row|get_item:header|get_item:'value' error=row|get_item:header|get_item:'error' %}
+ {% with value=row|get_item:header.title|get_item:'value' error=row|get_item:header.title|get_item:'error' %}