diff --git a/bims/admin.py b/bims/admin.py index bb063a3bd..80d3620cd 100644 --- a/bims/admin.py +++ b/bims/admin.py @@ -11,6 +11,7 @@ from django.contrib import admin as django_admin from django.core.mail import send_mail +from django.contrib.auth.models import Permission from django.contrib.flatpages.admin import FlatPageAdmin from django.contrib.flatpages.models import FlatPage from django.db import models @@ -40,6 +41,7 @@ ShapefileUploadSession, Shapefile, NonBiodiversityLayer, + UserBoundary, ) from bims.conf import TRACK_PAGEVIEWS @@ -191,6 +193,11 @@ class ClusterAdmin(admin.ModelAdmin): list_filter = ('boundary', 'module') +class PermissionAdmin(admin.ModelAdmin): + list_display = ('name', 'codename') + list_filter = ('name', 'codename') + + class CarouselHeaderAdmin(OrderedModelAdmin): list_display = ('order', 'description', 'banner', 'move_up_down_links') @@ -238,6 +245,13 @@ class ShapefileAdmin(admin.ModelAdmin): ) +class UserBoundaryAdmin(admin.ModelAdmin): + list_display = ( + 'name', + 'user' + ) + + class LinkAdmin(admin.ModelAdmin): list_display = ('name', 'category') list_filter = ('category',) @@ -377,5 +391,9 @@ class PageviewAdmin(admin.ModelAdmin): admin.site.register(FlatPage, FlatPageCustomAdmin) admin.site.register(Visitor, VisitorAdmin) +admin.site.register(Permission, PermissionAdmin) + +admin.site.register(UserBoundary, UserBoundaryAdmin) + if TRACK_PAGEVIEWS: admin.site.register(Pageview, PageviewAdmin) diff --git a/bims/api_views/collection.py b/bims/api_views/collection.py index ce8a23810..1d8d395d1 100644 --- a/bims/api_views/collection.py +++ b/bims/api_views/collection.py @@ -26,6 +26,7 @@ update_min_bbox, geo_serializer ) +from bims.models.user_boundary import UserBoundary class GetCollectionAbstract(APIView): @@ -55,10 +56,30 @@ def apply_filter(query_value, filters, ignore_bbox=False): """ fuzzy_search = False + user_boundaries = None + filter_mode = False sqs = SearchQuerySet() settings.ELASTIC_MIN_SCORE = 0 + # All filters + taxon = filters.get('taxon', None) + bbox = filters.get('bbox', None) + query_collector = filters.get('collector', None) + boundary = filters.get('boundary', None) + user_boundary = filters.get('userBoundary', None) + query_category = filters.get('category', None) + reference_category = filters.get('referenceCategory', None) + year_from = filters.get('yearFrom', None) + year_to = filters.get('yearTo', None) + months = filters.get('months', None) + + if taxon or query_collector or \ + boundary or user_boundary or \ + query_category or reference_category or \ + year_from or year_to or months: + filter_mode = True + if query_value: clean_query = sqs.query.clean(query_value) results = SearchQuerySet().filter( @@ -82,10 +103,13 @@ def apply_filter(query_value, filters, ignore_bbox=False): ).models(BiologicalCollectionRecord) settings.ELASTIC_MIN_SCORE = 0 else: - results = SearchQuerySet().all().models(BiologicalCollectionRecord) - results = results.filter(validated=True) + if filter_mode: + results = SearchQuerySet().all().models( + BiologicalCollectionRecord) + results = results.filter(validated=True) + else: + results = [] - taxon = filters.get('taxon', None) if taxon: results = sqs.filter( taxon_gbif=taxon @@ -93,7 +117,6 @@ def apply_filter(query_value, filters, ignore_bbox=False): # get by bbox if not ignore_bbox: - bbox = filters.get('bbox', None) if bbox: bbox_array = bbox.split(',') downtown_bottom_left = Point( @@ -111,7 +134,6 @@ def apply_filter(query_value, filters, ignore_bbox=False): # additional filters # query by collectors - query_collector = filters.get('collector') if query_collector: qs_collector = SQ() qs = json.loads(query_collector) @@ -119,7 +141,6 @@ def apply_filter(query_value, filters, ignore_bbox=False): qs_collector.add(SQ(collector=query), SQ.OR) results = results.filter(qs_collector) - boundary = filters.get('boundary') if boundary: qs_collector = SQ() qs = json.loads(boundary) @@ -127,8 +148,19 @@ def apply_filter(query_value, filters, ignore_bbox=False): qs_collector.add(SQ(boundary=query), SQ.OR) results = results.filter(qs_collector) + if user_boundary: + qs = json.loads(user_boundary) + user_boundaries = UserBoundary.objects.filter( + pk__in=qs + ) + for user_boundary in user_boundaries: + for geom in user_boundary.geometry: + results = results.polygon( + 'location_center', + geom + ) + # query by category - query_category = filters.get('category') if query_category: qs_category = SQ() qs = json.loads(query_category) @@ -136,22 +168,27 @@ def apply_filter(query_value, filters, ignore_bbox=False): qs_category.add(SQ(category=query), SQ.OR) results = results.filter(qs_category) + # query by reference category + if reference_category: + qs_reference_category = SQ() + qs = json.loads(reference_category) + for query in qs: + qs_reference_category.add(SQ(reference_category=query), SQ.OR) + results = results.filter(qs_reference_category) + # query by year from - year_from = filters.get('yearFrom') if year_from: clean_query_year_from = sqs.query.clean(year_from) results = results.filter( collection_date_year__gte=clean_query_year_from) # query by year to - year_to = filters.get('yearTo') if year_to: clean_query_year_to = sqs.query.clean(year_to) results = results.filter( collection_date_year__lte=clean_query_year_to) # query by months - months = filters.get('months') if months: qs = months.split(',') qs_month = SQ() @@ -170,16 +207,41 @@ def apply_filter(query_value, filters, ignore_bbox=False): site_name__contains=query_value ).models(LocationSite) + location_site_results = location_site_search + location_site_user_boundary = None + if boundary: qs_collector = SQ() qs = json.loads(boundary) for query in qs: qs_collector.add(SQ(boundary=query), SQ.OR) - if isinstance(location_site_search, SearchQuerySet): - location_site_search = location_site_search.filter( + if isinstance(location_site_results, SearchQuerySet): + location_site_results = location_site_results.filter( qs_collector) - if len(location_site_search) > 0: + if user_boundaries and isinstance(location_site_search, + SearchQuerySet): + location_site_user_boundary = location_site_search + for user_boundary in user_boundaries: + for geom in user_boundary.geometry: + location_site_user_boundary = \ + location_site_user_boundary.polygon( + 'location_site_point', + geom) + + site_results = [] + + if user_boundaries: + if boundary: + site_results = \ + location_site_results | location_site_user_boundary + elif location_site_user_boundary: + site_results = location_site_user_boundary + else: + site_results = location_site_results + + if len(site_results) > 0 or isinstance( + location_site_user_boundary, SearchQuerySet): # If there are fuzzy results from collection search but we # got non fuzzy results from location site, then remove # all the fuzzy results from collection @@ -187,7 +249,6 @@ def apply_filter(query_value, filters, ignore_bbox=False): len(collection_results) > 0: collection_results = [] fuzzy_search = False - site_results = location_site_search return collection_results, site_results, fuzzy_search diff --git a/bims/api_views/non_validated_record.py b/bims/api_views/non_validated_record.py new file mode 100644 index 000000000..3f3f1ae7f --- /dev/null +++ b/bims/api_views/non_validated_record.py @@ -0,0 +1,58 @@ +# coding=utf-8 +from django.http.response import HttpResponse +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from rest_framework.views import APIView, Response +from rest_framework import status +from bims.models.biological_collection_record import BiologicalCollectionRecord +from bims.serializers.bio_collection_serializer import ( + BioCollectionSerializer, +) +from bims.permissions.api_permission import IsValidator, AllowedTaxon + + +class GetNonValidatedRecords(APIView): + + permission_classes = [IsValidator, ] + page_limit = 5 + + def get(self, request): + try: + allowed_taxon = AllowedTaxon() + taxon_list = allowed_taxon.get(request.user) + records = BiologicalCollectionRecord.objects.filter( + validated=False, + ready_for_validation=True, + taxon_gbif_id__in=taxon_list + ) + + paginator = Paginator(records, self.page_limit) + page = request.GET.get('page') + + try: + records = paginator.page(page) + current_page = int(page) + except PageNotAnInteger: + records = paginator.page(1) + current_page = 1 + except EmptyPage: + records = paginator.page(paginator.num_pages) + current_page = paginator.num_pages + + serializer = BioCollectionSerializer( + records, + many=True + ) + response_data = { + 'data': serializer.data, + 'pagination': { + 'current_page': current_page, + 'max_page': paginator.num_pages, + 'page_limit': self.page_limit + } + } + return Response(response_data) + except BiologicalCollectionRecord.DoesNotExist: + return HttpResponse( + 'Object Does Not Exist', + status=status.HTTP_400_BAD_REQUEST + ) diff --git a/bims/api_views/reference_category.py b/bims/api_views/reference_category.py new file mode 100644 index 000000000..faad3985c --- /dev/null +++ b/bims/api_views/reference_category.py @@ -0,0 +1,24 @@ +# coding=utf-8 +from django.db.models import Q +from rest_framework.views import APIView +from rest_framework.response import Response +from bims.models.biological_collection_record import BiologicalCollectionRecord + + +class ReferenceCategoryList(APIView): + """Return list of reference category""" + def get(self, request, *args): + reference_category = \ + BiologicalCollectionRecord.objects.filter( + ~Q(reference_category='') & Q(validated=True)).\ + values_list( + 'reference_category', flat=True).\ + distinct() + results = [] + for reference in reference_category: + results.append( + { + 'category': reference + } + ) + return Response(results) diff --git a/bims/api_views/search.py b/bims/api_views/search.py index 40b612ea6..6dbc184d6 100644 --- a/bims/api_views/search.py +++ b/bims/api_views/search.py @@ -45,8 +45,8 @@ def get(self, request): if len(collection_results) > 0: bio_ids = collection_results.values_list('model_pk', flat=True) - taxon_ids = list(set(collection_results.values_list( - 'taxon_gbif', flat=True))) + taxon_ids = collection_results.values_list( + 'taxon_gbif', flat=True) taxons = Taxon.objects.filter( id__in=taxon_ids).annotate( num_occurrences=Count( diff --git a/bims/api_views/send_notification_to_validator.py b/bims/api_views/send_notification_to_validator.py index acae50072..e43390a22 100644 --- a/bims/api_views/send_notification_to_validator.py +++ b/bims/api_views/send_notification_to_validator.py @@ -31,6 +31,19 @@ def get(self, request): if pk is not None: try: bio_record = BiologicalCollectionRecord.objects.get(pk=pk) + + taxon_classname = bio_record.taxon_gbif_id.taxon_class + class_permission = Permission.objects.filter( + content_type__app_label='bims', + codename='can_validate_%s' % taxon_classname.lower() + ) + class_validators = Profile.objects.filter( + Q(user_permissions=class_permission) | + Q(groups__permissions=class_permission) + ) + for validator in class_validators: + validator_emails.append(validator.email) + bio_record.ready_for_validation = True bio_record.save() send_mail( diff --git a/bims/api_views/user_boundary.py b/bims/api_views/user_boundary.py new file mode 100644 index 000000000..b8e2adb16 --- /dev/null +++ b/bims/api_views/user_boundary.py @@ -0,0 +1,16 @@ +# coding=utf-8 +from rest_framework.response import Response +from rest_framework.views import APIView +from bims.models.user_boundary import UserBoundary +from bims.serializers.boundary_serializer import UserBondarySerializer + + +class UserBoundaryList(APIView): + """API for listing boundary.""" + + def get(self, request, *args): + boundaries = UserBoundary.objects.filter( + user=request.user + ) + serializer = UserBondarySerializer(boundaries, many=True) + return Response(serializer.data) diff --git a/bims/indexes/biological_collection_record.py b/bims/indexes/biological_collection_record.py index ad40e9259..e66ab3965 100644 --- a/bims/indexes/biological_collection_record.py +++ b/bims/indexes/biological_collection_record.py @@ -102,6 +102,11 @@ class BiologicalCollectionIndex(indexes.SearchIndex, indexes.Indexable): indexed=True ) + reference_category = indexes.CharField( + model_attr='reference_category', + indexed=True + ) + boundary = indexes.IntegerField() def prepare_taxon_gbif(self, obj): diff --git a/bims/management/commands/generate_permissions.py b/bims/management/commands/generate_permissions.py new file mode 100644 index 000000000..15a152fd0 --- /dev/null +++ b/bims/management/commands/generate_permissions.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand +from bims.permissions.generate_permission import generate_permission +from bims.models.taxon import Taxon + + +class Command(BaseCommand): + """Generate permissions for all taxon class + """ + + def handle(self, *args, **options): + taxa = Taxon.objects.all().values('taxon_class').distinct() + for taxon in taxa: + generate_permission(taxon['taxon_class']) diff --git a/bims/migrations/0040_userboundary.py b/bims/migrations/0040_userboundary.py new file mode 100644 index 000000000..b91ca287f --- /dev/null +++ b/bims/migrations/0040_userboundary.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-24 09:18 +from __future__ import unicode_literals + +from django.conf import settings +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('bims', '0039_auto_20180903_0552'), + ] + + operations = [ + migrations.CreateModel( + name='UserBoundary', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128)), + ('geometry', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/bims/migrations/0041_auto_20180928_0548.py b/bims/migrations/0041_auto_20180928_0548.py new file mode 100644 index 000000000..2a6586b23 --- /dev/null +++ b/bims/migrations/0041_auto_20180928_0548.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-28 05:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0040_userboundary'), + ] + + operations = [ + migrations.AddField( + model_name='biologicalcollectionrecord', + name='endemism', + field=models.CharField(blank=True, default=b'', max_length=50), + ), + migrations.AddField( + model_name='biologicalcollectionrecord', + name='reference', + field=models.CharField(blank=True, default=b'', max_length=100), + ), + migrations.AddField( + model_name='biologicalcollectionrecord', + name='reference_category', + field=models.CharField(blank=True, default=b'', max_length=100), + ), + migrations.AddField( + model_name='biologicalcollectionrecord', + name='sampling_effort', + field=models.CharField(blank=True, default=b'', max_length=50), + ), + migrations.AddField( + model_name='biologicalcollectionrecord', + name='sampling_method', + field=models.CharField(blank=True, default=b'', max_length=50), + ), + migrations.AddField( + model_name='locationsite', + name='description', + field=models.CharField(blank=True, default=b'', max_length=200), + ), + ] diff --git a/bims/migrations/0042_locationsite_site_code.py b/bims/migrations/0042_locationsite_site_code.py new file mode 100644 index 000000000..45dec6950 --- /dev/null +++ b/bims/migrations/0042_locationsite_site_code.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-28 05:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0041_auto_20180928_0548'), + ] + + operations = [ + migrations.AddField( + model_name='locationsite', + name='site_code', + field=models.CharField(blank=True, default=b'', max_length=100), + ), + ] diff --git a/bims/migrations/0043_auto_20180928_0656.py b/bims/migrations/0043_auto_20180928_0656.py new file mode 100644 index 000000000..e387fa094 --- /dev/null +++ b/bims/migrations/0043_auto_20180928_0656.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-28 06:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0042_locationsite_site_code'), + ] + + operations = [ + migrations.RenameField( + model_name='locationsite', + old_name='description', + new_name='site_description', + ), + ] diff --git a/bims/migrations/0044_auto_20181001_0703.py b/bims/migrations/0044_auto_20181001_0703.py new file mode 100644 index 000000000..142263032 --- /dev/null +++ b/bims/migrations/0044_auto_20181001_0703.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-10-01 07:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0043_auto_20180928_0656'), + ] + + operations = [ + migrations.AlterField( + model_name='biologicalcollectionrecord', + name='institution_id', + field=models.CharField(default=b'LEDET', help_text=b'An identifier for the institution having custody of the object(s) or information referred to in the record.', max_length=100, verbose_name=b'Custodian'), + ), + migrations.AlterField( + model_name='biologicalcollectionrecord', + name='reference', + field=models.CharField(blank=True, default=b'', max_length=300), + ), + ] diff --git a/bims/migrations/0044_auto_20181001_0730.py b/bims/migrations/0044_auto_20181001_0730.py new file mode 100644 index 000000000..c187e018f --- /dev/null +++ b/bims/migrations/0044_auto_20181001_0730.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-10-01 07:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0043_auto_20180928_0656'), + ] + + operations = [ + migrations.AlterField( + model_name='locationsite', + name='site_description', + field=models.CharField(blank=True, default=b'', max_length=500), + ), + ] diff --git a/bims/migrations/0045_merge_20181002_0219.py b/bims/migrations/0045_merge_20181002_0219.py new file mode 100644 index 000000000..d763b5e1e --- /dev/null +++ b/bims/migrations/0045_merge_20181002_0219.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-10-02 02:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bims', '0044_auto_20181001_0703'), + ('bims', '0044_auto_20181001_0730'), + ] + + operations = [ + ] diff --git a/bims/models/__init__.py b/bims/models/__init__.py index 24fef0b64..9bdecd630 100644 --- a/bims/models/__init__.py +++ b/bims/models/__init__.py @@ -14,3 +14,4 @@ from bims.models.shapefile_upload_session import * #noqa from bims.models.non_biodiversity_layer import * # noqa from bims.models.tracking import * # noqa +from bims.models.user_boundary import * # noqa diff --git a/bims/models/biological_collection_record.py b/bims/models/biological_collection_record.py index e3f268f9a..ff8763606 100644 --- a/bims/models/biological_collection_record.py +++ b/bims/models/biological_collection_record.py @@ -82,10 +82,40 @@ class BiologicalCollectionRecord(models.Model): default=settings.INSTITUTION_ID_DEFAULT, help_text='An identifier for the institution having custody of the ' 'object(s) or information referred to in the record.', - max_length=50, + max_length=100, verbose_name='Custodian', ) + endemism = models.CharField( + max_length=50, + blank=True, + default='' + ) + + sampling_method = models.CharField( + max_length=50, + blank=True, + default='' + ) + + sampling_effort = models.CharField( + max_length=50, + blank=True, + default='' + ) + + reference = models.CharField( + max_length=300, + blank=True, + default='' + ) + + reference_category = models.CharField( + max_length=100, + blank=True, + default='' + ) + # noinspection PyClassicStyleClass class Meta: """Meta class for project.""" diff --git a/bims/models/location_site.py b/bims/models/location_site.py index 66d79487b..a5826c069 100644 --- a/bims/models/location_site.py +++ b/bims/models/location_site.py @@ -30,6 +30,16 @@ class LocationSite(models.Model): max_length=100, blank=False, ) + site_description = models.CharField( + max_length=500, + blank=True, + default='' + ) + site_code = models.CharField( + max_length=100, + blank=True, + default='' + ) location_type = models.ForeignKey( LocationType, models.CASCADE, diff --git a/bims/models/taxon.py b/bims/models/taxon.py index 1110dbb1f..93faf449d 100644 --- a/bims/models/taxon.py +++ b/bims/models/taxon.py @@ -8,6 +8,7 @@ from django.contrib.postgres.fields import ArrayField from bims.models.iucn_status import IUCNStatus from bims.utils.iucn import get_iucn_status +from bims.permissions.generate_permission import generate_permission class TaxonomyField(models.CharField): @@ -125,6 +126,9 @@ def __str__(self): @receiver(models.signals.pre_save, sender=Taxon) def taxon_pre_save_handler(sender, instance, **kwargs): """Get iucn status before save.""" + if instance.taxon_class: + generate_permission(instance.taxon_class) + if instance.common_name and not instance.iucn_status: iucn_status = get_iucn_status( species_name=instance.common_name diff --git a/bims/models/user_boundary.py b/bims/models/user_boundary.py new file mode 100644 index 000000000..35324a1ba --- /dev/null +++ b/bims/models/user_boundary.py @@ -0,0 +1,26 @@ +# coding=utf-8 +"""User boundary model definition. +""" +from django.conf import settings +from django.contrib.gis.db import models + + +class UserBoundary(models.Model): + """User Boundary model.""" + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE + ) + + name = models.CharField( + max_length=128, + blank=False, + ) + + geometry = models.MultiPolygonField( + blank=False, + null=False + ) + + def __str__(self): + return u'%s' % self.name diff --git a/bims/permissions/__init__.py b/bims/permissions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/bims/permissions/api_permission.py b/bims/permissions/api_permission.py new file mode 100644 index 000000000..1106e3ebb --- /dev/null +++ b/bims/permissions/api_permission.py @@ -0,0 +1,55 @@ +from rest_framework.permissions import BasePermission +from django.contrib.auth.models import Permission +from bims.models.taxon import Taxon + + +def user_has_permission_to_validate(user): + """ + Check if user has permission to validate data + """ + if user.is_active and user.is_superuser: + return True + if user.is_anonymous: + return False + return Permission.objects.filter( + group__user=user, + codename__contains='can_validate').exists() + + +class IsValidator(BasePermission): + """ + Allows access only to validators. + """ + def has_permission(self, request, view): + return user_has_permission_to_validate(request.user) + + +class AllowedTaxon(object): + """ + Get allowed taxon. + """ + + def get(self, user): + """Return taxon that user has permission to validate.""" + + if user.is_active and user.is_superuser: + return Taxon.objects.all() + + all_data_flag = 'data' + + permissions = Permission.objects.filter( + group__user=user, + codename__contains='can_validate').values('name') + allowed_classes = [] + + for permission in permissions: + try: + class_name = permission['name'].split(' ')[2] + if class_name.lower() == all_data_flag: + return Taxon.objects.all() + except (ValueError, KeyError): + continue + allowed_classes.append(class_name) + + taxa = Taxon.objects.filter(taxon_class__in=allowed_classes) + return taxa diff --git a/bims/permissions/generate_permission.py b/bims/permissions/generate_permission.py new file mode 100644 index 000000000..b91247514 --- /dev/null +++ b/bims/permissions/generate_permission.py @@ -0,0 +1,40 @@ +from django.contrib.auth.models import Permission + + +def generate_permission(class_name): + # Generate permission based on taxon class + if not class_name: + return None + + print('Generate permission for %s' % class_name) + default_permission = Permission.objects.filter( + codename='can_validate_data' + ) + if not default_permission: + return None + + permission_codename = 'can_validate_%s' % ( + class_name.lower().replace(' ', '_') + ) + permission_name = 'Can validate %s' % ( + class_name + ) + + if Permission.objects.filter( + name=permission_name, + codename=permission_codename, + ).exists(): + print('Permission already exists') + return None + + permission = default_permission[0] + permission.pk = None + permission.codename = 'can_validate_%s' % ( + class_name.lower().replace(' ', '_') + ) + permission.name = 'Can validate %s' % ( + class_name + ) + print('New permission added : %s' % permission_codename) + permission.save() + return permission diff --git a/bims/serializers/bio_collection_serializer.py b/bims/serializers/bio_collection_serializer.py index 065a52af5..c9e614cea 100644 --- a/bims/serializers/bio_collection_serializer.py +++ b/bims/serializers/bio_collection_serializer.py @@ -12,6 +12,19 @@ class BioCollectionSerializer(serializers.ModelSerializer): """ Serializer for biological collection record. """ + location = serializers.SerializerMethodField() + owner = serializers.SerializerMethodField() + taxonomy = serializers.SerializerMethodField() + + def get_taxonomy(self, obj): + return TaxonSerializer(obj.taxon_gbif_id).data + + def get_owner(self, obj): + return obj.owner.username + + def get_location(self, obj): + return obj.site.get_geometry().geojson + class Meta: model = BiologicalCollectionRecord fields = '__all__' diff --git a/bims/serializers/boundary_serializer.py b/bims/serializers/boundary_serializer.py index beadb93eb..5c8fdd33e 100644 --- a/bims/serializers/boundary_serializer.py +++ b/bims/serializers/boundary_serializer.py @@ -2,7 +2,7 @@ from rest_framework import serializers from rest_framework_gis.serializers import ( GeoFeatureModelSerializer, GeometrySerializerMethodField) -from bims.models import Boundary +from bims.models import Boundary, UserBoundary from bims.models.cluster import Cluster @@ -89,3 +89,15 @@ class Meta: def get_geometry(self, obj): return obj.geometry + + +class UserBondarySerializer(GeoFeatureModelSerializer): + geometry = GeometrySerializerMethodField() + + class Meta: + model = UserBoundary + geo_field = 'geometry' + fields = ['id', 'name'] + + def get_geometry(self, obj): + return obj.geometry diff --git a/bims/static/css/map.css b/bims/static/css/map.css index d89add6ac..3cb1da630 100755 --- a/bims/static/css/map.css +++ b/bims/static/css/map.css @@ -141,16 +141,12 @@ html, body, .map, .map-wrapper, #map-container { padding-top: 60px; } -.layer-switcher { - z-index: 100; -} - .map-control-panel, .download-control-panel { background-color: white; position: absolute; left: 0; - top: 185px; - transform: translateY(-50%); + top: 0; + margin-top: 90px; padding-left: 6px; padding-right: 6px; font-size: 18pt; @@ -164,14 +160,6 @@ html, body, .map, .map-wrapper, #map-container { z-index: 100; } -.no-upload { - top: 160px; -} - -.download-control-panel { - top: 349px; -} - .download-control-panel .download-format-selector-container { margin-left: 49px; margin-top: 0; @@ -677,13 +665,14 @@ html, body, .map, .map-wrapper, #map-container { .layer-switcher { position: absolute; - top: 280px !important; + /*top: 367px !important;*/ left: 0; right: auto !important; text-align: left; margin-left: 30px; cursor: pointer; padding: 0 !important; + z-index: 100; } .layer-switcher button { @@ -1576,3 +1565,201 @@ div:hover::-webkit-scrollbar { border: 1px solid rgba(0,0,0,.15); border-radius: .25rem; } + +.btn-primary:disabled { + background-color: rgb(198, 193, 197); +} + +.btn-primary:hover:disabled { + background-color: rgb(198, 193, 197) !important; +} + +.close-upload-boundary-modal span { + font-size: 25pt; +} + +.spatial-filter-container .content { + border-top: 1px solid darkgray; + margin-top: 3px; + padding: 5px; + text-align: center; +} + +#spatial-filter-panel-upload { + margin-left: auto; + margin-right: auto; +} + +.boundary-list { + max-height: 250px; + overflow-y: auto; +} + +.boundary-list div { + padding: 5px; + padding-left: 20px; + width: 100%; + text-align: left; + background-color: #f9f9f9; +} + +.boundary-list div:hover { + cursor: pointer; + color: white; + background-color: #ACD796; +} + +.spatial-selected { + color: white; + background-color: #ACD796 !important; +} + +.boundary-action { + padding: 5px; +} + +.boundary-action button { + margin-left: auto; + margin-right: auto; +} + +.module-filters img { + width: 35px; +} + +.module-filters img:hover { + cursor: pointer; +} + +.module-filters { + padding: 5px; + filter: grayscale(50%); + opacity: 0.4; + filter: alpha(opacity=40); /* msie */ + background-color: #a0a0a0; +} + +.tooltip { + z-index: 99999; +} + +.not-ready img:hover { + cursor: default; +} + +.ecological-condition { + filter: grayscale(50%); + opacity: 0.4; + filter: alpha(opacity=40); /* msie */ + background-color: #a0a0a0; + color: white; + padding-top: 8px; + padding-bottom: 8px; +} + +.ecological-condition .row { + margin-left: 0; +} + +.ecological-condition .col-lg-4 { + padding: 3px; + font-size: 9pt; + width: 80% !important; +} + +.neutral { + background-color: blue; +} + +.good { + background-color: green; +} + +.fair { + background-color: orange; +} + +.poor { + background-color: red; +} + +.seriously-modified { + background-color: purple; +} + +.critically-modified { + background-color: black; +} + +.conservation-status { + filter: grayscale(50%); + opacity: 0.4; + filter: alpha(opacity=40); /* msie */ + background-color: #a0a0a0; + padding: 10px; +} + +.study-reference { + filter: grayscale(50%); + opacity: 0.4; + filter: alpha(opacity=40); /* msie */ + background-color: #a0a0a0; + padding: 10px; +} + +.validate-data-detail-wrapper, .validate-data-pagination { + padding: 5px; +} + +.validate-data-detail-wrapper { + border-bottom: 1px solid rgba(168, 168, 168, 0.34); +} + +.detail-container, .hide-detail { + display: none; +} + +.validate-data-pagination { + width: 50%; + margin: 0 auto; +} + +.validate-data-detail-wrapper .badge { + font-size: 8pt; +} + +.detail-container-title { + font-weight: bold; +} + +.validate-data-header { + font-size: 9pt; +} + +.detail-container-buttons { + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + text-align: center; +} + +.refresh-list { + float: right; +} + +.validate-wrapper { + min-height: 300px; +} + +.validate-data-action { + padding: 5px; + background-color: rgba(29, 188, 13, 0.13); + text-align: center; +} + +.validate-data-empty { + height: 300px; + padding-top: 100px; + text-align: center; + color: grey; +} \ No newline at end of file diff --git a/bims/static/js/app.js b/bims/static/js/app.js index f17904f24..29c8f016a 100644 --- a/bims/static/js/app.js +++ b/bims/static/js/app.js @@ -9,7 +9,12 @@ require.config({ layerSwitcher: 'libs/ol-layerswitcher/ol-layerswitcher', olMapboxStyle: 'libs/ol-mapbox-style/olms', noUiSlider: 'libs/noUiSlider.11.1.0/nouislider', - chartJs: 'libs/chart/Chart-2.7.2' + chartJs: 'libs/chart/Chart-2.7.2', + 'jquery-ui/ui/widget': 'libs/jquery-fileupload/vendor/jquery.ui.widget', + 'jquery.iframe-transport': 'libs/jquery-fileupload/jquery.iframe-transport', + 'jquery.fileupload': 'libs/jquery-fileupload/jquery.fileupload', + 'jquery.fileupload-process': 'libs/jquery-fileupload/jquery.fileupload-process', + 'jquery.fileupload-validate': 'libs/jquery-fileupload/jquery.fileupload-validate' }, shim: { ol: { @@ -55,6 +60,11 @@ require([ // A $( document ).ready() block. $(document).ready(function () { + // Set top margin for layer switcher panel + var mapControlPanelHeight = $('.map-control-panel').height(); + $('.layer-switcher').css('top', (mapControlPanelHeight + 91) + 'px'); + $('.download-control-panel').css('top', (mapControlPanelHeight + 46) + 'px'); + $('#menu-dropdown-burger').click(function () { $('.dropdown-menu-left').toggle(); }); diff --git a/bims/static/js/collections/cluster_biological.js b/bims/static/js/collections/cluster_biological.js index eeb471fd1..cfb3d09b2 100644 --- a/bims/static/js/collections/cluster_biological.js +++ b/bims/static/js/collections/cluster_biological.js @@ -4,13 +4,14 @@ define(['backbone', 'models/location_site', 'views/location_site', 'shared'], fu apiParameters: _.template("?taxon=<%= taxon %>&search=<%= search %>" + "&icon_pixel_x=<%= clusterSize %>&icon_pixel_y=<%= clusterSize %>&zoom=<%= zoom %>&bbox=<%= bbox %>" + "&collector=<%= collector %>&category=<%= category %>" + - "&yearFrom=<%= yearFrom %>&yearTo=<%= yearTo %>&months=<%= months %>&boundary=<%= boundary %>"), + "&yearFrom=<%= yearFrom %>&yearTo=<%= yearTo %>&months=<%= months %>&boundary=<%= boundary %>&userBoundary=<%= userBoundary %>&referenceCategory=<%= referenceCategory %>"), clusterAPI: "/api/collection/cluster/", url: "", viewCollection: [], parameters: { taxon: '', zoom: 0, bbox: [], - collector: '', category: '', yearFrom: '', yearTo: '', months: '', boundary: '', + collector: '', category: '', yearFrom: '', yearTo: '', months: '', + boundary: '', userBoundary: '', referenceCategory: '', clusterSize: Shared.ClusterSize }, initialize: function (initExtent) { @@ -34,6 +35,8 @@ define(['backbone', 'models/location_site', 'views/location_site', 'shared'], fu && !this.parameters['category'] && !this.parameters['yearFrom'] && !this.parameters['yearTo'] + && !this.parameters['userBoundary'] + && !this.parameters['referenceCategory'] && !this.parameters['boundary']) { return false } else { diff --git a/bims/static/js/collections/reference_category.js b/bims/static/js/collections/reference_category.js new file mode 100644 index 000000000..e9d171da5 --- /dev/null +++ b/bims/static/js/collections/reference_category.js @@ -0,0 +1,6 @@ +define(['backbone', 'models/reference_category'], function (Backbone, ReferenceCategory) { + return Backbone.Collection.extend({ + model: ReferenceCategory, + url: "/api/list-reference-category/" + }) +}); \ No newline at end of file diff --git a/bims/static/js/collections/search_result.js b/bims/static/js/collections/search_result.js index 751a17e8a..80753163c 100644 --- a/bims/static/js/collections/search_result.js +++ b/bims/static/js/collections/search_result.js @@ -18,6 +18,8 @@ define(['jquery', 'backbone', 'models/search_result', 'views/search_result', 'sh this.yearTo = parameters['yearTo']; this.months = parameters['months']; this.boundary = parameters['boundary']; + this.userBoundary = parameters['userBoundary']; + this.referenceCategory = parameters['referenceCategory']; this.url = this.searchUrl + '?search=' + this.searchValue + @@ -26,7 +28,9 @@ define(['jquery', 'backbone', 'models/search_result', 'views/search_result', 'sh '&yearFrom=' + this.yearFrom + '&yearTo=' + this.yearTo + '&months=' + this.months + - '&boundary=' + this.boundary; + '&boundary=' + this.boundary + + '&userBoundary=' + this.userBoundary + + '&referenceCategory=' + this.referenceCategory; this.searchPanel = searchPanel; this.searchPanel.showSearchLoading(); }, diff --git a/bims/static/js/collections/validate_data.js b/bims/static/js/collections/validate_data.js new file mode 100644 index 000000000..9048782f2 --- /dev/null +++ b/bims/static/js/collections/validate_data.js @@ -0,0 +1,15 @@ +define(['backbone', 'models/validate_data'], function (Backbone, ValidateData) { + return Backbone.Collection.extend({ + model: ValidateData, + apiUrl: "/api/get-unvalidated-records/", + paginationData: null, + updateUrl: function (page) { + this.url = this.apiUrl + '?page=' + page + }, + parse: function (response) { + var result = response['data']; + this.paginationData = response['pagination']; + return result + } + }) +}); diff --git a/bims/static/js/models/reference_category.js b/bims/static/js/models/reference_category.js new file mode 100644 index 000000000..10bd8baf9 --- /dev/null +++ b/bims/static/js/models/reference_category.js @@ -0,0 +1,10 @@ +define(['backbone'], function (Backbone) { + return Backbone.Model.extend({ + category: function () { + }, + destroy: function () { + this.unbind(); + delete this; + } + }) +}); \ No newline at end of file diff --git a/bims/static/js/models/validate_data.js b/bims/static/js/models/validate_data.js new file mode 100644 index 000000000..b150ec7af --- /dev/null +++ b/bims/static/js/models/validate_data.js @@ -0,0 +1,8 @@ +define(['backbone'], function (Backbone) { + return Backbone.Model.extend({ + destroy: function () { + this.unbind(); + delete this; + } + }) +}); \ No newline at end of file diff --git a/bims/static/js/optimized.js b/bims/static/js/optimized.js index 395546660..c08f69bd9 100644 --- a/bims/static/js/optimized.js +++ b/bims/static/js/optimized.js @@ -29,6 +29,53 @@ * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ +/*! jQuery UI - v1.12.1+CommonJS - 2018-02-10 + * http://jqueryui.com + * Includes: widget.js + * Copyright jQuery Foundation and other contributors; Licensed MIT */ + +/*! + * jQuery UI Widget 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +/* + * jQuery File Upload Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* + * jQuery File Upload Processing Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* + * jQuery File Upload Validation Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + /* ol-mapbox-style - Use Mapbox Style objects with OpenLayers Copyright 2016-present Boundless Spatial, Inc. @@ -82,43 +129,45 @@ /* MIT license */ -var requirejs,require,define;!function(t){function e(t,e){return m.call(t,e)}function i(t,e){var i,o,n,r,s,a,l,h,p,u,c,d,f=e&&e.split("/"),g=y.map,v=g&&g["*"]||{};if(t){for(t=t.split("/"),s=t.length-1,y.nodeIdCompat&&x.test(t[s])&&(t[s]=t[s].replace(x,"")),"."===t[0].charAt(0)&&f&&(d=f.slice(0,f.length-1),t=d.concat(t)),p=0;p0&&(t.splice(p-1,2),p-=2)}t=t.join("/")}if((f||v)&&g){for(i=t.split("/"),p=i.length;p>0;p-=1){if(o=i.slice(0,p).join("/"),f)for(u=f.length;u>0;u-=1)if((n=g[f.slice(0,u).join("/")])&&(n=n[o])){r=n,a=p;break}if(r)break;!l&&v&&v[o]&&(l=v[o],h=p)}!r&&l&&(r=l,a=h),r&&(i.splice(0,a,r),t=i.join("/"))}return t}function o(e,i){return function(){var o=b.call(arguments,0);return"string"!=typeof o[0]&&1===o.length&&o.push(null),u.apply(t,o.concat([e,i]))}}function n(t){return function(e){return i(e,t)}}function r(t){return function(e){f[t]=e}}function s(i){if(e(g,i)){var o=g[i];delete g[i],v[i]=!0,p.apply(t,o)}if(!e(f,i)&&!e(v,i))throw new Error("No "+i);return f[i]}function a(t){var e,i=t?t.indexOf("!"):-1;return i>-1&&(e=t.substring(0,i),t=t.substring(i+1,t.length)),[e,t]}function l(t){return t?a(t):[]}function h(t){return function(){return y&&y.config&&y.config[t]||{}}}var p,u,c,d,f={},g={},y={},v={},m=Object.prototype.hasOwnProperty,b=[].slice,x=/\.js$/;c=function(t,e){var o,r=a(t),l=r[0],h=e[1];return t=r[1],l&&(l=i(l,h),o=s(l)),l?t=o&&o.normalize?o.normalize(t,n(h)):i(t,h):(t=i(t,h),r=a(t),l=r[0],t=r[1],l&&(o=s(l))),{f:l?l+"!"+t:t,n:t,pr:l,p:o}},d={require:function(t){return o(t)},exports:function(t){var e=f[t];return void 0!==e?e:f[t]={}},module:function(t){return{id:t,uri:"",exports:f[t],config:h(t)}}},p=function(i,n,a,h){var p,u,y,m,b,x,w,_=[],S=typeof a;if(h=h||i,x=l(h),"undefined"===S||"function"===S){for(n=!n.length&&a.length?["require","exports","module"]:n,b=0;b=0&&s>r;r+=t){var a=n?n[r]:r;o=i(o,e[a],a,e)}return o}return function(i,o,n,r){o=b(o,r,4);var s=!k(i)&&m.keys(i),a=(s||i).length,l=t>0?0:a-1;return arguments.length<3&&(n=i[s?s[l]:l],l+=t),e(i,o,n,s,l,a)}}function e(t){return function(e,i,o){i=x(i,o);for(var n=M(e),r=t>0?0:n-1;r>=0&&n>r;r+=t)if(i(e[r],r,e))return r;return-1}}function i(t,e,i){return function(o,n,r){var s=0,a=M(o);if("number"==typeof r)t>0?s=r>=0?r:Math.max(r+a,s):a=r>=0?Math.min(r+1,a):r+a+1;else if(i&&r&&a)return r=i(o,n),o[r]===n?r:-1;if(n!==n)return r=e(p.call(o,s,a),m.isNaN),r>=0?r+s:-1;for(r=t>0?s:a-1;r>=0&&a>r;r+=t)if(o[r]===n)return r;return-1}}function o(t,e){var i=D.length,o=t.constructor,n=m.isFunction(o)&&o.prototype||a,r="constructor";for(m.has(t,r)&&!m.contains(e,r)&&e.push(r);i--;)(r=D[i])in t&&t[r]!==n[r]&&!m.contains(e,r)&&e.push(r)}var n=this,r=n._,s=Array.prototype,a=Object.prototype,l=Function.prototype,h=s.push,p=s.slice,u=a.toString,c=a.hasOwnProperty,d=Array.isArray,f=Object.keys,g=l.bind,y=Object.create,v=function(){},m=function(t){return t instanceof m?t:this instanceof m?void(this._wrapped=t):new m(t)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):n._=m,m.VERSION="1.8.3";var b=function(t,e,i){if(void 0===e)return t;switch(null==i?3:i){case 1:return function(i){return t.call(e,i)};case 2:return function(i,o){return t.call(e,i,o)};case 3:return function(i,o,n){return t.call(e,i,o,n)};case 4:return function(i,o,n,r){return t.call(e,i,o,n,r)}}return function(){return t.apply(e,arguments)}},x=function(t,e,i){return null==t?m.identity:m.isFunction(t)?b(t,e,i):m.isObject(t)?m.matcher(t):m.property(t)};m.iteratee=function(t,e){return x(t,e,1/0)};var w=function(t,e){return function(i){var o=arguments.length;if(2>o||null==i)return i;for(var n=1;o>n;n++)for(var r=arguments[n],s=t(r),a=s.length,l=0;a>l;l++){var h=s[l];e&&void 0!==i[h]||(i[h]=r[h])}return i}},_=function(t){if(!m.isObject(t))return{};if(y)return y(t);v.prototype=t;var e=new v;return v.prototype=null,e},S=function(t){return function(e){return null==e?void 0:e[t]}},C=Math.pow(2,53)-1,M=S("length"),k=function(t){var e=M(t);return"number"==typeof e&&e>=0&&C>=e};m.each=m.forEach=function(t,e,i){e=b(e,i);var o,n;if(k(t))for(o=0,n=t.length;n>o;o++)e(t[o],o,t);else{var r=m.keys(t);for(o=0,n=r.length;n>o;o++)e(t[r[o]],r[o],t)}return t},m.map=m.collect=function(t,e,i){e=x(e,i);for(var o=!k(t)&&m.keys(t),n=(o||t).length,r=Array(n),s=0;n>s;s++){var a=o?o[s]:s;r[s]=e(t[a],a,t)}return r},m.reduce=m.foldl=m.inject=t(1),m.reduceRight=m.foldr=t(-1),m.find=m.detect=function(t,e,i){var o;return o=k(t)?m.findIndex(t,e,i):m.findKey(t,e,i),void 0!==o&&-1!==o?t[o]:void 0},m.filter=m.select=function(t,e,i){var o=[];return e=x(e,i),m.each(t,function(t,i,n){e(t,i,n)&&o.push(t)}),o},m.reject=function(t,e,i){return m.filter(t,m.negate(x(e)),i)},m.every=m.all=function(t,e,i){e=x(e,i);for(var o=!k(t)&&m.keys(t),n=(o||t).length,r=0;n>r;r++){var s=o?o[r]:r;if(!e(t[s],s,t))return!1}return!0},m.some=m.any=function(t,e,i){e=x(e,i);for(var o=!k(t)&&m.keys(t),n=(o||t).length,r=0;n>r;r++){var s=o?o[r]:r;if(e(t[s],s,t))return!0}return!1},m.contains=m.includes=m.include=function(t,e,i,o){return k(t)||(t=m.values(t)),("number"!=typeof i||o)&&(i=0),m.indexOf(t,e,i)>=0},m.invoke=function(t,e){var i=p.call(arguments,2),o=m.isFunction(e);return m.map(t,function(t){var n=o?e:t[e];return null==n?n:n.apply(t,i)})},m.pluck=function(t,e){return m.map(t,m.property(e))},m.where=function(t,e){return m.filter(t,m.matcher(e))},m.findWhere=function(t,e){return m.find(t,m.matcher(e))},m.max=function(t,e,i){var o,n,r=-1/0,s=-1/0;if(null==e&&null!=t){t=k(t)?t:m.values(t);for(var a=0,l=t.length;l>a;a++)(o=t[a])>r&&(r=o)}else e=x(e,i),m.each(t,function(t,i,o){((n=e(t,i,o))>s||n===-1/0&&r===-1/0)&&(r=t,s=n)});return r},m.min=function(t,e,i){var o,n,r=1/0,s=1/0;if(null==e&&null!=t){t=k(t)?t:m.values(t);for(var a=0,l=t.length;l>a;a++)o=t[a],r>o&&(r=o)}else e=x(e,i),m.each(t,function(t,i,o){n=e(t,i,o),(s>n||1/0===n&&1/0===r)&&(r=t,s=n)});return r},m.shuffle=function(t){for(var e,i=k(t)?t:m.values(t),o=i.length,n=Array(o),r=0;o>r;r++)e=m.random(0,r),e!==r&&(n[r]=n[e]),n[e]=i[r];return n},m.sample=function(t,e,i){return null==e||i?(k(t)||(t=m.values(t)),t[m.random(t.length-1)]):m.shuffle(t).slice(0,Math.max(0,e))},m.sortBy=function(t,e,i){return e=x(e,i),m.pluck(m.map(t,function(t,i,o){return{value:t,index:i,criteria:e(t,i,o)}}).sort(function(t,e){var i=t.criteria,o=e.criteria;if(i!==o){if(i>o||void 0===i)return 1;if(o>i||void 0===o)return-1}return t.index-e.index}),"value")};var P=function(t){return function(e,i,o){var n={};return i=x(i,o),m.each(e,function(o,r){var s=i(o,r,e);t(n,o,s)}),n}};m.groupBy=P(function(t,e,i){m.has(t,i)?t[i].push(e):t[i]=[e]}),m.indexBy=P(function(t,e,i){t[i]=e}),m.countBy=P(function(t,e,i){m.has(t,i)?t[i]++:t[i]=1}),m.toArray=function(t){return t?m.isArray(t)?p.call(t):k(t)?m.map(t,m.identity):m.values(t):[]},m.size=function(t){return null==t?0:k(t)?t.length:m.keys(t).length},m.partition=function(t,e,i){e=x(e,i);var o=[],n=[];return m.each(t,function(t,i,r){(e(t,i,r)?o:n).push(t)}),[o,n]},m.first=m.head=m.take=function(t,e,i){return null==t?void 0:null==e||i?t[0]:m.initial(t,t.length-e)},m.initial=function(t,e,i){return p.call(t,0,Math.max(0,t.length-(null==e||i?1:e)))},m.last=function(t,e,i){return null==t?void 0:null==e||i?t[t.length-1]:m.rest(t,Math.max(0,t.length-e))},m.rest=m.tail=m.drop=function(t,e,i){return p.call(t,null==e||i?1:e)},m.compact=function(t){return m.filter(t,m.identity)};var T=function(t,e,i,o){for(var n=[],r=0,s=o||0,a=M(t);a>s;s++){var l=t[s];if(k(l)&&(m.isArray(l)||m.isArguments(l))){e||(l=T(l,e,i));var h=0,p=l.length;for(n.length+=p;p>h;)n[r++]=l[h++]}else i||(n[r++]=l)}return n};m.flatten=function(t,e){return T(t,e,!1)},m.without=function(t){return m.difference(t,p.call(arguments,1))},m.uniq=m.unique=function(t,e,i,o){m.isBoolean(e)||(o=i,i=e,e=!1),null!=i&&(i=x(i,o));for(var n=[],r=[],s=0,a=M(t);a>s;s++){var l=t[s],h=i?i(l,s,t):l;e?(s&&r===h||n.push(l),r=h):i?m.contains(r,h)||(r.push(h),n.push(l)):m.contains(n,l)||n.push(l)}return n},m.union=function(){return m.uniq(T(arguments,!0,!0))},m.intersection=function(t){for(var e=[],i=arguments.length,o=0,n=M(t);n>o;o++){var r=t[o];if(!m.contains(e,r)){for(var s=1;i>s&&m.contains(arguments[s],r);s++);s===i&&e.push(r)}}return e},m.difference=function(t){var e=T(arguments,!0,!0,1);return m.filter(t,function(t){return!m.contains(e,t)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(t){for(var e=t&&m.max(t,M).length||0,i=Array(e),o=0;e>o;o++)i[o]=m.pluck(t,o);return i},m.object=function(t,e){for(var i={},o=0,n=M(t);n>o;o++)e?i[t[o]]=e[o]:i[t[o][0]]=t[o][1];return i},m.findIndex=e(1),m.findLastIndex=e(-1),m.sortedIndex=function(t,e,i,o){i=x(i,o,1);for(var n=i(e),r=0,s=M(t);s>r;){var a=Math.floor((r+s)/2);i(t[a])r;r++,t+=i)n[r]=t;return n};var E=function(t,e,i,o,n){if(!(o instanceof e))return t.apply(i,n);var r=_(t.prototype),s=t.apply(r,n);return m.isObject(s)?s:r};m.bind=function(t,e){if(g&&t.bind===g)return g.apply(t,p.call(arguments,1));if(!m.isFunction(t))throw new TypeError("Bind must be called on a function");var i=p.call(arguments,2),o=function(){return E(t,o,e,this,i.concat(p.call(arguments)))};return o},m.partial=function(t){var e=p.call(arguments,1),i=function(){for(var o=0,n=e.length,r=Array(n),s=0;n>s;s++)r[s]=e[s]===m?arguments[o++]:e[s];for(;o=o)throw new Error("bindAll must be passed function names");for(e=1;o>e;e++)i=arguments[e],t[i]=m.bind(t[i],t);return t},m.memoize=function(t,e){var i=function(o){var n=i.cache,r=""+(e?e.apply(this,arguments):o);return m.has(n,r)||(n[r]=t.apply(this,arguments)),n[r]};return i.cache={},i},m.delay=function(t,e){var i=p.call(arguments,2);return setTimeout(function(){return t.apply(null,i)},e)},m.defer=m.partial(m.delay,m,1),m.throttle=function(t,e,i){var o,n,r,s=null,a=0;i||(i={});var l=function(){a=!1===i.leading?0:m.now(),s=null,r=t.apply(o,n),s||(o=n=null)};return function(){var h=m.now();a||!1!==i.leading||(a=h);var p=e-(h-a);return o=this,n=arguments,0>=p||p>e?(s&&(clearTimeout(s),s=null),a=h,r=t.apply(o,n),s||(o=n=null)):s||!1===i.trailing||(s=setTimeout(l,p)),r}},m.debounce=function(t,e,i){var o,n,r,s,a,l=function(){var h=m.now()-s;e>h&&h>=0?o=setTimeout(l,e-h):(o=null,i||(a=t.apply(r,n),o||(r=n=null)))};return function(){r=this,n=arguments,s=m.now();var h=i&&!o;return o||(o=setTimeout(l,e)),h&&(a=t.apply(r,n),r=n=null),a}},m.wrap=function(t,e){return m.partial(e,t)},m.negate=function(t){return function(){return!t.apply(this,arguments)}},m.compose=function(){var t=arguments,e=t.length-1;return function(){for(var i=e,o=t[e].apply(this,arguments);i--;)o=t[i].call(this,o);return o}},m.after=function(t,e){return function(){return--t<1?e.apply(this,arguments):void 0}},m.before=function(t,e){var i;return function(){return--t>0&&(i=e.apply(this,arguments)),1>=t&&(e=null),i}},m.once=m.partial(m.before,2);var A=!{toString:null}.propertyIsEnumerable("toString"),D=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(t){if(!m.isObject(t))return[];if(f)return f(t);var e=[];for(var i in t)m.has(t,i)&&e.push(i);return A&&o(t,e),e},m.allKeys=function(t){if(!m.isObject(t))return[];var e=[];for(var i in t)e.push(i);return A&&o(t,e),e},m.values=function(t){for(var e=m.keys(t),i=e.length,o=Array(i),n=0;i>n;n++)o[n]=t[e[n]];return o},m.mapObject=function(t,e,i){e=x(e,i);for(var o,n=m.keys(t),r=n.length,s={},a=0;r>a;a++)o=n[a],s[o]=e(t[o],o,t);return s},m.pairs=function(t){for(var e=m.keys(t),i=e.length,o=Array(i),n=0;i>n;n++)o[n]=[e[n],t[e[n]]];return o},m.invert=function(t){for(var e={},i=m.keys(t),o=0,n=i.length;n>o;o++)e[t[i[o]]]=i[o];return e},m.functions=m.methods=function(t){var e=[];for(var i in t)m.isFunction(t[i])&&e.push(i);return e.sort()},m.extend=w(m.allKeys),m.extendOwn=m.assign=w(m.keys),m.findKey=function(t,e,i){e=x(e,i);for(var o,n=m.keys(t),r=0,s=n.length;s>r;r++)if(o=n[r],e(t[o],o,t))return o},m.pick=function(t,e,i){var o,n,r={},s=t;if(null==s)return r;m.isFunction(e)?(n=m.allKeys(s),o=b(e,i)):(n=T(arguments,!1,!1,1),o=function(t,e,i){return e in i},s=Object(s));for(var a=0,l=n.length;l>a;a++){var h=n[a],p=s[h];o(p,h,s)&&(r[h]=p)}return r},m.omit=function(t,e,i){if(m.isFunction(e))e=m.negate(e);else{var o=m.map(T(arguments,!1,!1,1),String);e=function(t,e){return!m.contains(o,e)}}return m.pick(t,e,i)},m.defaults=w(m.allKeys,!0),m.create=function(t,e){var i=_(t);return e&&m.extendOwn(i,e),i},m.clone=function(t){return m.isObject(t)?m.isArray(t)?t.slice():m.extend({},t):t},m.tap=function(t,e){return e(t),t},m.isMatch=function(t,e){var i=m.keys(e),o=i.length;if(null==t)return!o;for(var n=Object(t),r=0;o>r;r++){var s=i[r];if(e[s]!==n[s]||!(s in n))return!1}return!0};var I=function(t,e,i,o){if(t===e)return 0!==t||1/t==1/e;if(null==t||null==e)return t===e;t instanceof m&&(t=t._wrapped),e instanceof m&&(e=e._wrapped);var n=u.call(t);if(n!==u.call(e))return!1;switch(n){case"[object RegExp]":case"[object String]":return""+t==""+e;case"[object Number]":return+t!=+t?+e!=+e:0==+t?1/+t==1/e:+t==+e;case"[object Date]":case"[object Boolean]":return+t==+e}var r="[object Array]"===n;if(!r){if("object"!=typeof t||"object"!=typeof e)return!1;var s=t.constructor,a=e.constructor;if(s!==a&&!(m.isFunction(s)&&s instanceof s&&m.isFunction(a)&&a instanceof a)&&"constructor"in t&&"constructor"in e)return!1}i=i||[],o=o||[];for(var l=i.length;l--;)if(i[l]===t)return o[l]===e;if(i.push(t),o.push(e),r){if((l=t.length)!==e.length)return!1;for(;l--;)if(!I(t[l],e[l],i,o))return!1}else{var h,p=m.keys(t);if(l=p.length,m.keys(e).length!==l)return!1;for(;l--;)if(h=p[l],!m.has(e,h)||!I(t[h],e[h],i,o))return!1}return i.pop(),o.pop(),!0};m.isEqual=function(t,e){return I(t,e)},m.isEmpty=function(t){return null==t||(k(t)&&(m.isArray(t)||m.isString(t)||m.isArguments(t))?0===t.length:0===m.keys(t).length)},m.isElement=function(t){return!(!t||1!==t.nodeType)},m.isArray=d||function(t){return"[object Array]"===u.call(t)},m.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(t){m["is"+t]=function(e){return u.call(e)==="[object "+t+"]"}}),m.isArguments(arguments)||(m.isArguments=function(t){return m.has(t,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(t){return"function"==typeof t||!1}),m.isFinite=function(t){return isFinite(t)&&!isNaN(parseFloat(t))},m.isNaN=function(t){return m.isNumber(t)&&t!==+t},m.isBoolean=function(t){return!0===t||!1===t||"[object Boolean]"===u.call(t)},m.isNull=function(t){return null===t},m.isUndefined=function(t){return void 0===t},m.has=function(t,e){return null!=t&&c.call(t,e)},m.noConflict=function(){return n._=r,this},m.identity=function(t){return t},m.constant=function(t){return function(){return t}},m.noop=function(){},m.property=S,m.propertyOf=function(t){return null==t?function(){}:function(e){return t[e]}},m.matcher=m.matches=function(t){return t=m.extendOwn({},t),function(e){return m.isMatch(e,t)}},m.times=function(t,e,i){var o=Array(Math.max(0,t));e=b(e,i,1);for(var n=0;t>n;n++)o[n]=e(n);return o},m.random=function(t,e){return null==e&&(e=t,t=0),t+Math.floor(Math.random()*(e-t+1))},m.now=Date.now||function(){return(new Date).getTime()};var L={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},O=m.invert(L),j=function(t){var e=function(e){return t[e]},i="(?:"+m.keys(t).join("|")+")",o=RegExp(i),n=RegExp(i,"g");return function(t){return t=null==t?"":""+t,o.test(t)?t.replace(n,e):t}};m.escape=j(L),m.unescape=j(O),m.result=function(t,e,i){var o=null==t?void 0:t[e];return void 0===o&&(o=i),m.isFunction(o)?o.call(t):o};var R=0;m.uniqueId=function(t){var e=++R+"";return t?t+e:e},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var N=/(.)^/,F={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\u2028|\u2029/g,B=function(t){return"\\"+F[t]};m.template=function(t,e,i){!e&&i&&(e=i),e=m.defaults({},e,m.templateSettings);var o=RegExp([(e.escape||N).source,(e.interpolate||N).source,(e.evaluate||N).source].join("|")+"|$","g"),n=0,r="__p+='";t.replace(o,function(e,i,o,s,a){return r+=t.slice(n,a).replace(z,B),n=a+e.length,i?r+="'+\n((__t=("+i+"))==null?'':_.escape(__t))+\n'":o?r+="'+\n((__t=("+o+"))==null?'':__t)+\n'":s&&(r+="';\n"+s+"\n__p+='"),e}),r+="';\n",e.variable||(r="with(obj||{}){\n"+r+"}\n"),r="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+r+"return __p;\n";try{var s=new Function(e.variable||"obj","_",r)}catch(t){throw t.source=r,t}var a=function(t){return s.call(this,t,m)};return a.source="function("+(e.variable||"obj")+"){\n"+r+"}",a},m.chain=function(t){var e=m(t);return e._chain=!0,e};var H=function(t,e){return t._chain?m(e).chain():e};m.mixin=function(t){m.each(m.functions(t),function(e){var i=m[e]=t[e];m.prototype[e]=function(){var t=[this._wrapped];return h.apply(t,arguments),H(this,i.apply(m,t))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=s[t];m.prototype[t]=function(){var i=this._wrapped;return e.apply(i,arguments),"shift"!==t&&"splice"!==t||0!==i.length||delete i[0],H(this,i)}}),m.each(["concat","join","slice"],function(t){var e=s[t];m.prototype[t]=function(){return H(this,e.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}.call(this),function(t,e){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t.document?e(t,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return e(t)}:e(t)}("undefined"!=typeof window?window:this,function(t,e){"use strict";function i(t,e,i){var o,n=(e=e||st).createElement("script");if(n.text=t,i)for(o in xt)i[o]&&(n[o]=i[o]);e.head.appendChild(n).parentNode.removeChild(n)}function o(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?ct[dt.call(t)]||"object":typeof t}function n(t){var e=!!t&&"length"in t&&t.length,i=o(t);return!mt(t)&&!bt(t)&&("array"===i||0===e||"number"==typeof e&&e>0&&e-1 in t)}function r(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}function s(t,e,i){return mt(e)?wt.grep(t,function(t,o){return!!e.call(t,o,t)!==i}):e.nodeType?wt.grep(t,function(t){return t===e!==i}):"string"!=typeof e?wt.grep(t,function(t){return ut.call(e,t)>-1!==i}):wt.filter(e,t,i)}function a(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}function l(t){var e={};return wt.each(t.match(It)||[],function(t,i){e[i]=!0}),e}function h(t){return t}function p(t){throw t}function u(t,e,i,o){var n;try{t&&mt(n=t.promise)?n.call(t).done(e).fail(i):t&&mt(n=t.then)?n.call(t,e,i):e.apply(void 0,[t].slice(o))}catch(t){i.apply(void 0,[t])}}function c(){st.removeEventListener("DOMContentLoaded",c),t.removeEventListener("load",c),wt.ready()}function d(t,e){return e.toUpperCase()}function f(t){return t.replace(Rt,"ms-").replace(Nt,d)}function g(){this.expando=wt.expando+g.uid++}function y(t){return"true"===t||"false"!==t&&("null"===t?null:t===+t+""?+t:Ht.test(t)?JSON.parse(t):t)}function v(t,e,i){var o;if(void 0===i&&1===t.nodeType)if(o="data-"+e.replace(Wt,"-$&").toLowerCase(),"string"==typeof(i=t.getAttribute(o))){try{i=y(i)}catch(t){}Bt.set(t,e,i)}else i=void 0;return i}function m(t,e,i,o){var n,r,s=20,a=o?function(){return o.cur()}:function(){return wt.css(t,e,"")},l=a(),h=i&&i[3]||(wt.cssNumber[e]?"":"px"),p=(wt.cssNumber[e]||"px"!==h&&+l)&&Vt.exec(wt.css(t,e));if(p&&p[3]!==h){for(l/=2,h=h||p[3],p=+l||1;s--;)wt.style(t,e,p+h),(1-r)*(1-(r=a()/l||.5))<=0&&(s=0),p/=r;p*=2,wt.style(t,e,p+h),i=i||[]}return i&&(p=+p||+l||0,n=i[1]?p+(i[1]+1)*i[2]:+i[2],o&&(o.unit=h,o.start=p,o.end=n)),n}function b(t){var e,i=t.ownerDocument,o=t.nodeName,n=Xt[o];return n||(e=i.body.appendChild(i.createElement(o)),n=wt.css(e,"display"),e.parentNode.removeChild(e),"none"===n&&(n="block"),Xt[o]=n,n)}function x(t,e){for(var i,o,n=[],r=0,s=t.length;r-1)r&&r.push(s);else if(p=wt.contains(s.ownerDocument,s),a=w(c.appendChild(s),"script"),p&&_(a),i)for(u=0;s=a[u++];)$t.test(s.type||"")&&i.push(s);return c}function C(){return!0}function M(){return!1}function k(){try{return st.activeElement}catch(t){}}function P(t,e,i,o,n,r){var s,a;if("object"==typeof e){"string"!=typeof i&&(o=o||i,i=void 0);for(a in e)P(t,a,i,o,e[a],r);return t}if(null==o&&null==n?(n=i,o=i=void 0):null==n&&("string"==typeof i?(n=o,o=void 0):(n=o,o=i,i=void 0)),!1===n)n=M;else if(!n)return t;return 1===r&&(s=n,(n=function(t){return wt().off(t),s.apply(this,arguments)}).guid=s.guid||(s.guid=wt.guid++)),t.each(function(){wt.event.add(this,e,n,o,i)})}function T(t,e){return r(t,"table")&&r(11!==e.nodeType?e:e.firstChild,"tr")?wt(t).children("tbody")[0]||t:t}function E(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function A(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function D(t,e){var i,o,n,r,s,a,l,h;if(1===e.nodeType){if(zt.hasData(t)&&(r=zt.access(t),s=zt.set(e,r),h=r.events)){delete s.handle,s.events={};for(n in h)for(i=0,o=h[n].length;i1&&"string"==typeof f&&!vt.checkClone&&se.test(f))return t.each(function(i){var r=t.eq(i);g&&(e[0]=f.call(this,i,r.html())),L(r,e,o,n)});if(c&&(r=S(e,t[0].ownerDocument,!1,t,n),s=r.firstChild,1===r.childNodes.length&&(r=s),s||n)){for(l=(a=wt.map(w(r,"script"),E)).length;u=0&&(l+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-r-l-a-.5))),l}function H(t,e,i){var o=he(t),n=j(t,e,o),r="border-box"===wt.css(t,"boxSizing",!1,o),s=r;if(le.test(n)){if(!i)return n;n="auto"}return s=s&&(vt.boxSizingReliable()||n===t.style[e]),("auto"===n||!parseFloat(n)&&"inline"===wt.css(t,"display",!1,o))&&(n=t["offset"+e[0].toUpperCase()+e.slice(1)],s=!0),(n=parseFloat(n)||0)+B(t,e,i||(r?"border":"content"),s,o,n)+"px"}function W(t,e,i,o,n){return new W.prototype.init(t,e,i,o,n)}function U(){me&&(!1===st.hidden&&t.requestAnimationFrame?t.requestAnimationFrame(U):t.setTimeout(U,wt.fx.interval),wt.fx.tick())}function V(){return t.setTimeout(function(){ve=void 0}),ve=Date.now()}function G(t,e){var i,o=0,n={height:t};for(e=e?1:0;o<4;o+=2-e)n["margin"+(i=Gt[o])]=n["padding"+i]=t;return e&&(n.opacity=n.width=t),n}function q(t,e,i){for(var o,n=(K.tweeners[e]||[]).concat(K.tweeners["*"]),r=0,s=n.length;r=0&&iw.cacheLength&&delete t[e.shift()],t[i+" "]=o}var e=[];return t}function o(t){return t[F]=!0,t}function n(t){var e=D.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function r(t,e){for(var i=t.split("|"),o=i.length;o--;)w.attrHandle[i[o]]=e}function s(t,e){var i=e&&t,o=i&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(o)return o;if(i)for(;i=i.nextSibling;)if(i===e)return-1;return t?1:-1}function a(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&_t(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function l(t){return o(function(e){return e=+e,o(function(i,o){for(var n,r=t([],i.length,e),s=r.length;s--;)i[n=r[s]]&&(i[n]=!(o[n]=i[n]))})})}function h(t){return t&&void 0!==t.getElementsByTagName&&t}function p(){}function u(t){for(var e=0,i=t.length,o="";e1?function(e,i,o){for(var n=t.length;n--;)if(!t[n](e,i,o))return!1;return!0}:t[0]}function f(t,i,o){for(var n=0,r=i.length;n-1&&(o[h]=!(s[h]=u))}}else b=g(b===s?b.splice(y,b.length):b),r?r(null,s,b,l):Z.apply(s,b)})}function v(t){for(var e,i,o,n=t.length,r=w.relative[t[0].type],s=r||w.relative[" "],a=r?1:0,l=c(function(t){return t===e},s,!0),h=c(function(t){return J(e,t)>-1},s,!0),p=[function(t,i,o){var n=!r&&(o||i!==P)||((e=i).nodeType?l(t,i,o):h(t,i,o));return e=null,n}];a1&&d(p),a>1&&u(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(rt,"$1"),i,a0,r=t.length>0,s=function(o,s,a,l,h){var p,u,c,d=0,f="0",y=o&&[],v=[],m=P,b=o||r&&w.find.TAG("*",h),x=B+=null==m?1:Math.random()||.1,_=b.length;for(h&&(P=s===D||s||h);f!==_&&null!=(p=b[f]);f++){if(r&&p){for(u=0,s||p.ownerDocument===D||(A(p),a=!L);c=t[u++];)if(c(p,s||D,a)){l.push(p);break}h&&(B=x)}n&&((p=!c&&p)&&d--,o&&y.push(p))}if(d+=f,n&&f!==d){for(u=0;c=i[u++];)c(y,v,s,a);if(o){if(d>0)for(;f--;)y[f]||v[f]||(v[f]=X.call(l));v=g(v)}Z.apply(l,v),h&&!o&&v.length>0&&d+i.length>1&&e.uniqueSort(l)}return h&&(B=x,P=m),y};return n?o(s):s}var b,x,w,_,S,C,M,k,P,T,E,A,D,I,L,O,j,R,N,F="sizzle"+1*new Date,z=t.document,B=0,H=0,W=i(),U=i(),V=i(),G=function(t,e){return t===e&&(E=!0),0},q={}.hasOwnProperty,Y=[],X=Y.pop,K=Y.push,Z=Y.push,$=Y.slice,J=function(t,e){for(var i=0,o=t.length;i+~]|"+tt+")"+tt+"*"),lt=new RegExp("="+tt+"*([^\\]'\"]*?)"+tt+"*\\]","g"),ht=new RegExp(ot),pt=new RegExp("^"+et+"$"),ut={ID:new RegExp("^#("+et+")"),CLASS:new RegExp("^\\.("+et+")"),TAG:new RegExp("^("+et+"|[*])"),ATTR:new RegExp("^"+it),PSEUDO:new RegExp("^"+ot),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+tt+"*(even|odd|(([+-]|)(\\d*)n|)"+tt+"*(?:([+-]|)"+tt+"*(\\d+)|))"+tt+"*\\)|)","i"),bool:new RegExp("^(?:"+Q+")$","i"),needsContext:new RegExp("^"+tt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+tt+"*((?:-\\d)?\\d*)"+tt+"*\\)|)(?=[^-]|$)","i")},ct=/^(?:input|select|textarea|button)$/i,dt=/^h\d$/i,ft=/^[^{]+\{\s*\[native \w/,gt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,yt=/[+~]/,vt=new RegExp("\\\\([\\da-f]{1,6}"+tt+"?|("+tt+")|.)","ig"),mt=function(t,e,i){var o="0x"+e-65536;return o!==o||i?e:o<0?String.fromCharCode(o+65536):String.fromCharCode(o>>10|55296,1023&o|56320)},bt=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,xt=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},wt=function(){A()},_t=c(function(t){return!0===t.disabled&&("form"in t||"label"in t)},{dir:"parentNode",next:"legend"});try{Z.apply(Y=$.call(z.childNodes),z.childNodes),Y[z.childNodes.length].nodeType}catch(t){Z={apply:Y.length?function(t,e){K.apply(t,$.call(e))}:function(t,e){for(var i=t.length,o=0;t[i++]=e[o++];);t.length=i-1}}}x=e.support={},S=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},A=e.setDocument=function(t){var e,i,o=t?t.ownerDocument||t:z;return o!==D&&9===o.nodeType&&o.documentElement?(D=o,I=D.documentElement,L=!S(D),z!==D&&(i=D.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",wt,!1):i.attachEvent&&i.attachEvent("onunload",wt)),x.attributes=n(function(t){return t.className="i",!t.getAttribute("className")}),x.getElementsByTagName=n(function(t){return t.appendChild(D.createComment("")),!t.getElementsByTagName("*").length}),x.getElementsByClassName=ft.test(D.getElementsByClassName),x.getById=n(function(t){return I.appendChild(t).id=F,!D.getElementsByName||!D.getElementsByName(F).length}),x.getById?(w.filter.ID=function(t){var e=t.replace(vt,mt);return function(t){return t.getAttribute("id")===e}},w.find.ID=function(t,e){if(void 0!==e.getElementById&&L){var i=e.getElementById(t);return i?[i]:[]}}):(w.filter.ID=function(t){var e=t.replace(vt,mt);return function(t){var i=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return i&&i.value===e}},w.find.ID=function(t,e){if(void 0!==e.getElementById&&L){var i,o,n,r=e.getElementById(t);if(r){if((i=r.getAttributeNode("id"))&&i.value===t)return[r];for(n=e.getElementsByName(t),o=0;r=n[o++];)if((i=r.getAttributeNode("id"))&&i.value===t)return[r]}return[]}}),w.find.TAG=x.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):x.qsa?e.querySelectorAll(t):void 0}:function(t,e){var i,o=[],n=0,r=e.getElementsByTagName(t);if("*"===t){for(;i=r[n++];)1===i.nodeType&&o.push(i);return o}return r},w.find.CLASS=x.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&L)return e.getElementsByClassName(t)},j=[],O=[],(x.qsa=ft.test(D.querySelectorAll))&&(n(function(t){I.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&O.push("[*^$]="+tt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||O.push("\\["+tt+"*(?:value|"+Q+")"),t.querySelectorAll("[id~="+F+"-]").length||O.push("~="),t.querySelectorAll(":checked").length||O.push(":checked"),t.querySelectorAll("a#"+F+"+*").length||O.push(".#.+[+~]")}),n(function(t){t.innerHTML="";var e=D.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&O.push("name"+tt+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&O.push(":enabled",":disabled"),I.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&O.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),O.push(",.*:")})),(x.matchesSelector=ft.test(R=I.matches||I.webkitMatchesSelector||I.mozMatchesSelector||I.oMatchesSelector||I.msMatchesSelector))&&n(function(t){x.disconnectedMatch=R.call(t,"*"),R.call(t,"[s!='']:x"),j.push("!=",ot)}),O=O.length&&new RegExp(O.join("|")),j=j.length&&new RegExp(j.join("|")),e=ft.test(I.compareDocumentPosition),N=e||ft.test(I.contains)?function(t,e){var i=9===t.nodeType?t.documentElement:t,o=e&&e.parentNode;return t===o||!(!o||1!==o.nodeType||!(i.contains?i.contains(o):t.compareDocumentPosition&&16&t.compareDocumentPosition(o)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},G=e?function(t,e){if(t===e)return E=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i||(1&(i=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!x.sortDetached&&e.compareDocumentPosition(t)===i?t===D||t.ownerDocument===z&&N(z,t)?-1:e===D||e.ownerDocument===z&&N(z,e)?1:T?J(T,t)-J(T,e):0:4&i?-1:1)}:function(t,e){if(t===e)return E=!0,0;var i,o=0,n=t.parentNode,r=e.parentNode,a=[t],l=[e];if(!n||!r)return t===D?-1:e===D?1:n?-1:r?1:T?J(T,t)-J(T,e):0;if(n===r)return s(t,e);for(i=t;i=i.parentNode;)a.unshift(i);for(i=e;i=i.parentNode;)l.unshift(i);for(;a[o]===l[o];)o++;return o?s(a[o],l[o]):a[o]===z?-1:l[o]===z?1:0},D):D},e.matches=function(t,i){return e(t,null,null,i)},e.matchesSelector=function(t,i){if((t.ownerDocument||t)!==D&&A(t),i=i.replace(lt,"='$1']"),x.matchesSelector&&L&&!V[i+" "]&&(!j||!j.test(i))&&(!O||!O.test(i)))try{var o=R.call(t,i);if(o||x.disconnectedMatch||t.document&&11!==t.document.nodeType)return o}catch(t){}return e(i,D,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==D&&A(t),N(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==D&&A(t);var i=w.attrHandle[e.toLowerCase()],o=i&&q.call(w.attrHandle,e.toLowerCase())?i(t,e,!L):void 0;return void 0!==o?o:x.attributes||!L?t.getAttribute(e):(o=t.getAttributeNode(e))&&o.specified?o.value:null},e.escape=function(t){return(t+"").replace(bt,xt)},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,i=[],o=0,n=0;if(E=!x.detectDuplicates,T=!x.sortStable&&t.slice(0),t.sort(G),E){for(;e=t[n++];)e===t[n]&&(o=i.push(n));for(;o--;)t.splice(i[o],1)}return T=null,t},_=e.getText=function(t){var e,i="",o=0,n=t.nodeType;if(n){if(1===n||9===n||11===n){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)i+=_(t)}else if(3===n||4===n)return t.nodeValue}else for(;e=t[o++];)i+=_(e);return i},(w=e.selectors={cacheLength:50,createPseudo:o,match:ut,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(vt,mt),t[3]=(t[3]||t[4]||t[5]||"").replace(vt,mt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,i=!t[6]&&t[2];return ut.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":i&&ht.test(i)&&(e=C(i,!0))&&(e=i.indexOf(")",i.length-e)-i.length)&&(t[0]=t[0].slice(0,e),t[2]=i.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(vt,mt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=W[t+" "];return e||(e=new RegExp("(^|"+tt+")"+t+"("+tt+"|$)"))&&W(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,i,o){return function(n){var r=e.attr(n,t);return null==r?"!="===i:!i||(r+="","="===i?r===o:"!="===i?r!==o:"^="===i?o&&0===r.indexOf(o):"*="===i?o&&r.indexOf(o)>-1:"$="===i?o&&r.slice(-o.length)===o:"~="===i?(" "+r.replace(nt," ")+" ").indexOf(o)>-1:"|="===i&&(r===o||r.slice(0,o.length+1)===o+"-"))}},CHILD:function(t,e,i,o,n){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===o&&0===n?function(t){return!!t.parentNode}:function(e,i,l){var h,p,u,c,d,f,g=r!==s?"nextSibling":"previousSibling",y=e.parentNode,v=a&&e.nodeName.toLowerCase(),m=!l&&!a,b=!1;if(y){if(r){for(;g;){for(c=e;c=c[g];)if(a?c.nodeName.toLowerCase()===v:1===c.nodeType)return!1;f=g="only"===t&&!f&&"nextSibling"}return!0}if(f=[s?y.firstChild:y.lastChild],s&&m){for(b=(d=(h=(p=(u=(c=y)[F]||(c[F]={}))[c.uniqueID]||(u[c.uniqueID]={}))[t]||[])[0]===B&&h[1])&&h[2],c=d&&y.childNodes[d];c=++d&&c&&c[g]||(b=d=0)||f.pop();)if(1===c.nodeType&&++b&&c===e){p[t]=[B,d,b];break}}else if(m&&(b=d=(h=(p=(u=(c=e)[F]||(c[F]={}))[c.uniqueID]||(u[c.uniqueID]={}))[t]||[])[0]===B&&h[1]),!1===b)for(;(c=++d&&c&&c[g]||(b=d=0)||f.pop())&&((a?c.nodeName.toLowerCase()!==v:1!==c.nodeType)||!++b||(m&&((p=(u=c[F]||(c[F]={}))[c.uniqueID]||(u[c.uniqueID]={}))[t]=[B,b]),c!==e)););return(b-=n)===o||b%o==0&&b/o>=0}}},PSEUDO:function(t,i){var n,r=w.pseudos[t]||w.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[F]?r(i):r.length>1?(n=[t,t,"",i],w.setFilters.hasOwnProperty(t.toLowerCase())?o(function(t,e){for(var o,n=r(t,i),s=n.length;s--;)t[o=J(t,n[s])]=!(e[o]=n[s])}):function(t){return r(t,0,n)}):r}},pseudos:{not:o(function(t){var e=[],i=[],n=M(t.replace(rt,"$1"));return n[F]?o(function(t,e,i,o){for(var r,s=n(t,null,o,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))}):function(t,o,r){return e[0]=t,n(e,null,r,i),e[0]=null,!i.pop()}}),has:o(function(t){return function(i){return e(t,i).length>0}}),contains:o(function(t){return t=t.replace(vt,mt),function(e){return(e.textContent||e.innerText||_(e)).indexOf(t)>-1}}),lang:o(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(vt,mt).toLowerCase(),function(e){var i;do{if(i=L?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(i=i.toLowerCase())===t||0===i.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var i=t.location&&t.location.hash;return i&&i.slice(1)===e.id},root:function(t){return t===I},focus:function(t){return t===D.activeElement&&(!D.hasFocus||D.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:a(!1),disabled:a(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!w.pseudos.empty(t)},header:function(t){return dt.test(t.nodeName)},input:function(t){return ct.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:l(function(){return[0]}),last:l(function(t,e){return[e-1]}),eq:l(function(t,e,i){return[i<0?i+e:i]}),even:l(function(t,e){for(var i=0;i=0;)t.push(o);return t}),gt:l(function(t,e,i){for(var o=i<0?i+e:i;++o2&&"ID"===(s=r[0]).type&&9===e.nodeType&&L&&w.relative[r[1].type]){if(!(e=(w.find.ID(s.matches[0].replace(vt,mt),e)||[])[0]))return i;p&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(n=ut.needsContext.test(t)?0:r.length;n--&&(s=r[n],!w.relative[a=s.type]);)if((l=w.find[a])&&(o=l(s.matches[0].replace(vt,mt),yt.test(r[0].type)&&h(e.parentNode)||e))){if(r.splice(n,1),!(t=o.length&&u(r)))return Z.apply(i,o),i;break}}return(p||M(t,c))(o,e,!L,i,!e||yt.test(t)&&h(e.parentNode)||e),i},x.sortStable=F.split("").sort(G).join("")===F,x.detectDuplicates=!!E,A(),x.sortDetached=n(function(t){return 1&t.compareDocumentPosition(D.createElement("fieldset"))}),n(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,i){if(!i)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),x.attributes&&n(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,i){if(!i&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),n(function(t){return null==t.getAttribute("disabled")})||r(Q,function(t,e,i){var o;if(!i)return!0===t[e]?e.toLowerCase():(o=t.getAttributeNode(e))&&o.specified?o.value:null}),e}(t);wt.find=St,wt.expr=St.selectors,wt.expr[":"]=wt.expr.pseudos,wt.uniqueSort=wt.unique=St.uniqueSort,wt.text=St.getText,wt.isXMLDoc=St.isXML,wt.contains=St.contains,wt.escapeSelector=St.escape;var Ct=function(t,e,i){for(var o=[],n=void 0!==i;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(n&&wt(t).is(i))break;o.push(t)}return o},Mt=function(t,e){for(var i=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&i.push(t);return i},kt=wt.expr.match.needsContext,Pt=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;wt.filter=function(t,e,i){var o=e[0];return i&&(t=":not("+t+")"),1===e.length&&1===o.nodeType?wt.find.matchesSelector(o,t)?[o]:[]:wt.find.matches(t,wt.grep(e,function(t){return 1===t.nodeType}))},wt.fn.extend({find:function(t){var e,i,o=this.length,n=this;if("string"!=typeof t)return this.pushStack(wt(t).filter(function(){for(e=0;e1?wt.uniqueSort(i):i},filter:function(t){return this.pushStack(s(this,t||[],!1))},not:function(t){return this.pushStack(s(this,t||[],!0))},is:function(t){return!!s(this,"string"==typeof t&&kt.test(t)?wt(t):t||[],!1).length}});var Tt,Et=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(wt.fn.init=function(t,e,i){var o,n;if(!t)return this;if(i=i||Tt,"string"==typeof t){if(!(o="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:Et.exec(t))||!o[1]&&e)return!e||e.jquery?(e||i).find(t):this.constructor(e).find(t);if(o[1]){if(e=e instanceof wt?e[0]:e,wt.merge(this,wt.parseHTML(o[1],e&&e.nodeType?e.ownerDocument||e:st,!0)),Pt.test(o[1])&&wt.isPlainObject(e))for(o in e)mt(this[o])?this[o](e[o]):this.attr(o,e[o]);return this}return(n=st.getElementById(o[2]))&&(this[0]=n,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):mt(t)?void 0!==i.ready?i.ready(t):t(wt):wt.makeArray(t,this)}).prototype=wt.fn,Tt=wt(st);var At=/^(?:parents|prev(?:Until|All))/,Dt={children:!0,contents:!0,next:!0,prev:!0};wt.fn.extend({has:function(t){var e=wt(t,this),i=e.length;return this.filter(function(){for(var t=0;t-1:1===i.nodeType&&wt.find.matchesSelector(i,t))){r.push(i);break}return this.pushStack(r.length>1?wt.uniqueSort(r):r)},index:function(t){return t?"string"==typeof t?ut.call(wt(t),this[0]):ut.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(wt.uniqueSort(wt.merge(this.get(),wt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),wt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return Ct(t,"parentNode")},parentsUntil:function(t,e,i){return Ct(t,"parentNode",i)},next:function(t){return a(t,"nextSibling")},prev:function(t){return a(t,"previousSibling")},nextAll:function(t){return Ct(t,"nextSibling")},prevAll:function(t){return Ct(t,"previousSibling")},nextUntil:function(t,e,i){return Ct(t,"nextSibling",i)},prevUntil:function(t,e,i){return Ct(t,"previousSibling",i)},siblings:function(t){return Mt((t.parentNode||{}).firstChild,t)},children:function(t){return Mt(t.firstChild)},contents:function(t){return r(t,"iframe")?t.contentDocument:(r(t,"template")&&(t=t.content||t),wt.merge([],t.childNodes))}},function(t,e){wt.fn[t]=function(i,o){var n=wt.map(this,e,i);return"Until"!==t.slice(-5)&&(o=i),o&&"string"==typeof o&&(n=wt.filter(o,n)),this.length>1&&(Dt[t]||wt.uniqueSort(n),At.test(t)&&n.reverse()),this.pushStack(n)}});var It=/[^\x20\t\r\n\f]+/g;wt.Callbacks=function(t){t="string"==typeof t?l(t):wt.extend({},t);var e,i,n,r,s=[],a=[],h=-1,p=function(){for(r=r||t.once,n=e=!0;a.length;h=-1)for(i=a.shift();++h-1;)s.splice(i,1),i<=h&&h--}),this},has:function(t){return t?wt.inArray(t,s)>-1:s.length>0},empty:function(){return s&&(s=[]),this},disable:function(){return r=a=[],s=i="",this},disabled:function(){return!s},lock:function(){return r=a=[],i||e||(s=i=""),this},locked:function(){return!!r},fireWith:function(t,i){return r||(i=[t,(i=i||[]).slice?i.slice():i],a.push(i),e||p()),this},fire:function(){return u.fireWith(this,arguments),this},fired:function(){return!!n}};return u},wt.extend({Deferred:function(e){var i=[["notify","progress",wt.Callbacks("memory"),wt.Callbacks("memory"),2],["resolve","done",wt.Callbacks("once memory"),wt.Callbacks("once memory"),0,"resolved"],["reject","fail",wt.Callbacks("once memory"),wt.Callbacks("once memory"),1,"rejected"]],o="pending",n={state:function(){return o},always:function(){return r.done(arguments).fail(arguments),this},catch:function(t){return n.then(null,t)},pipe:function(){var t=arguments;return wt.Deferred(function(e){wt.each(i,function(i,o){var n=mt(t[o[4]])&&t[o[4]];r[o[1]](function(){var t=n&&n.apply(this,arguments);t&&mt(t.promise)?t.promise().progress(e.notify).done(e.resolve).fail(e.reject):e[o[0]+"With"](this,n?[t]:arguments)})}),t=null}).promise()},then:function(e,o,n){function r(e,i,o,n){return function(){var a=this,l=arguments,u=function(){var t,u;if(!(e=s&&(o!==p&&(a=void 0,l=[t]),i.rejectWith(a,l))}};e?c():(wt.Deferred.getStackHook&&(c.stackTrace=wt.Deferred.getStackHook()),t.setTimeout(c))}}var s=0;return wt.Deferred(function(t){i[0][3].add(r(0,t,mt(n)?n:h,t.notifyWith)),i[1][3].add(r(0,t,mt(e)?e:h)),i[2][3].add(r(0,t,mt(o)?o:p))}).promise()},promise:function(t){return null!=t?wt.extend(t,n):n}},r={};return wt.each(i,function(t,e){var s=e[2],a=e[5];n[e[1]]=s.add,a&&s.add(function(){o=a},i[3-t][2].disable,i[3-t][3].disable,i[0][2].lock,i[0][3].lock),s.add(e[3].fire),r[e[0]]=function(){return r[e[0]+"With"](this===r?void 0:this,arguments),this},r[e[0]+"With"]=s.fireWith}),n.promise(r),e&&e.call(r,r),r},when:function(t){var e=arguments.length,i=e,o=Array(i),n=lt.call(arguments),r=wt.Deferred(),s=function(t){return function(i){o[t]=this,n[t]=arguments.length>1?lt.call(arguments):i,--e||r.resolveWith(o,n)}};if(e<=1&&(u(t,r.done(s(i)).resolve,r.reject,!e),"pending"===r.state()||mt(n[i]&&n[i].then)))return r.then();for(;i--;)u(n[i],s(i),r.reject);return r.promise()}});var Lt=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;wt.Deferred.exceptionHook=function(e,i){t.console&&t.console.warn&&e&&Lt.test(e.name)&&t.console.warn("jQuery.Deferred exception: "+e.message,e.stack,i)},wt.readyException=function(e){t.setTimeout(function(){throw e})};var Ot=wt.Deferred();wt.fn.ready=function(t){return Ot.then(t).catch(function(t){wt.readyException(t)}),this},wt.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--wt.readyWait:wt.isReady)||(wt.isReady=!0,!0!==t&&--wt.readyWait>0||Ot.resolveWith(st,[wt]))}}),wt.ready.then=Ot.then,"complete"===st.readyState||"loading"!==st.readyState&&!st.documentElement.doScroll?t.setTimeout(wt.ready):(st.addEventListener("DOMContentLoaded",c),t.addEventListener("load",c));var jt=function(t,e,i,n,r,s,a){var l=0,h=t.length,p=null==i;if("object"===o(i)){r=!0;for(l in i)jt(t,e,l,i[l],!0,s,a)}else if(void 0!==n&&(r=!0,mt(n)||(a=!0),p&&(a?(e.call(t,n),e=null):(p=e,e=function(t,e,i){return p.call(wt(t),i)})),e))for(;l1,null,!0)},removeData:function(t){return this.each(function(){Bt.remove(this,t)})}}),wt.extend({queue:function(t,e,i){var o;if(t)return e=(e||"fx")+"queue",o=zt.get(t,e),i&&(!o||Array.isArray(i)?o=zt.access(t,e,wt.makeArray(i)):o.push(i)),o||[]},dequeue:function(t,e){e=e||"fx";var i=wt.queue(t,e),o=i.length,n=i.shift(),r=wt._queueHooks(t,e),s=function(){wt.dequeue(t,e)};"inprogress"===n&&(n=i.shift(),o--),n&&("fx"===e&&i.unshift("inprogress"),delete r.stop,n.call(t,s,r)),!o&&r&&r.empty.fire()},_queueHooks:function(t,e){var i=e+"queueHooks";return zt.get(t,i)||zt.access(t,i,{empty:wt.Callbacks("once memory").add(function(){zt.remove(t,[e+"queue",i])})})}}),wt.fn.extend({queue:function(t,e){var i=2;return"string"!=typeof t&&(e=t,t="fx",i--),arguments.length\x20\t\r\n\f]+)/i,$t=/^$|^module$|\/(?:java|ecma)script/i,Jt={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};Jt.optgroup=Jt.option,Jt.tbody=Jt.tfoot=Jt.colgroup=Jt.caption=Jt.thead,Jt.th=Jt.td;var Qt=/<|&#?\w+;/;!function(){var t=st.createDocumentFragment().appendChild(st.createElement("div")),e=st.createElement("input");e.setAttribute("type","radio"),e.setAttribute("checked","checked"),e.setAttribute("name","t"),t.appendChild(e),vt.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="",vt.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var te=st.documentElement,ee=/^key/,ie=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,oe=/^([^.]*)(?:\.(.+)|)/;wt.event={global:{},add:function(t,e,i,o,n){var r,s,a,l,h,p,u,c,d,f,g,y=zt.get(t);if(y)for(i.handler&&(i=(r=i).handler,n=r.selector),n&&wt.find.matchesSelector(te,n),i.guid||(i.guid=wt.guid++),(l=y.events)||(l=y.events={}),(s=y.handle)||(s=y.handle=function(e){return void 0!==wt&&wt.event.triggered!==e.type?wt.event.dispatch.apply(t,arguments):void 0}),h=(e=(e||"").match(It)||[""]).length;h--;)d=g=(a=oe.exec(e[h])||[])[1],f=(a[2]||"").split(".").sort(),d&&(u=wt.event.special[d]||{},d=(n?u.delegateType:u.bindType)||d,u=wt.event.special[d]||{},p=wt.extend({type:d,origType:g,data:o,handler:i,guid:i.guid,selector:n,needsContext:n&&wt.expr.match.needsContext.test(n),namespace:f.join(".")},r),(c=l[d])||((c=l[d]=[]).delegateCount=0,u.setup&&!1!==u.setup.call(t,o,f,s)||t.addEventListener&&t.addEventListener(d,s)),u.add&&(u.add.call(t,p),p.handler.guid||(p.handler.guid=i.guid)),n?c.splice(c.delegateCount++,0,p):c.push(p),wt.event.global[d]=!0)},remove:function(t,e,i,o,n){var r,s,a,l,h,p,u,c,d,f,g,y=zt.hasData(t)&&zt.get(t);if(y&&(l=y.events)){for(h=(e=(e||"").match(It)||[""]).length;h--;)if(a=oe.exec(e[h])||[],d=g=a[1],f=(a[2]||"").split(".").sort(),d){for(u=wt.event.special[d]||{},c=l[d=(o?u.delegateType:u.bindType)||d]||[],a=a[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=r=c.length;r--;)p=c[r],!n&&g!==p.origType||i&&i.guid!==p.guid||a&&!a.test(p.namespace)||o&&o!==p.selector&&("**"!==o||!p.selector)||(c.splice(r,1),p.selector&&c.delegateCount--,u.remove&&u.remove.call(t,p));s&&!c.length&&(u.teardown&&!1!==u.teardown.call(t,f,y.handle)||wt.removeEvent(t,d,y.handle),delete l[d])}else for(d in l)wt.event.remove(t,d+e[h],i,o,!0);wt.isEmptyObject(l)&&zt.remove(t,"handle events")}},dispatch:function(t){var e,i,o,n,r,s,a=wt.event.fix(t),l=new Array(arguments.length),h=(zt.get(this,"events")||{})[a.type]||[],p=wt.event.special[a.type]||{};for(l[0]=a,e=1;e=1))for(;h!==this;h=h.parentNode||this)if(1===h.nodeType&&("click"!==t.type||!0!==h.disabled)){for(r=[],s={},i=0;i-1:wt.find(n,this,null,[h]).length),s[n]&&r.push(o);r.length&&a.push({elem:h,handlers:r})}return h=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,re=/\s*$/g;wt.extend({htmlPrefilter:function(t){return t.replace(ne,"<$1>")},clone:function(t,e,i){var o,n,r,s,a=t.cloneNode(!0),l=wt.contains(t.ownerDocument,t);if(!(vt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||wt.isXMLDoc(t)))for(s=w(a),o=0,n=(r=w(t)).length;o0&&_(s,!l&&w(t,"script")),a},cleanData:function(t){for(var e,i,o,n=wt.event.special,r=0;void 0!==(i=t[r]);r++)if(Ft(i)){if(e=i[zt.expando]){if(e.events)for(o in e.events)n[o]?wt.event.remove(i,o):wt.removeEvent(i,o,e.handle);i[zt.expando]=void 0}i[Bt.expando]&&(i[Bt.expando]=void 0)}}}),wt.fn.extend({detach:function(t){return O(this,t,!0)},remove:function(t){return O(this,t)},text:function(t){return jt(this,function(t){return void 0===t?wt.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return L(this,arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||T(this,t).appendChild(t)})},prepend:function(){return L(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=T(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return L(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return L(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(wt.cleanData(w(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return wt.clone(this,t,e)})},html:function(t){return jt(this,function(t){var e=this[0]||{},i=0,o=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!re.test(t)&&!Jt[(Zt.exec(t)||["",""])[1].toLowerCase()]){t=wt.htmlPrefilter(t);try{for(;i1)}}),wt.Tween=W,W.prototype={constructor:W,init:function(t,e,i,o,n,r){this.elem=t,this.prop=i,this.easing=n||wt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=o,this.unit=r||(wt.cssNumber[i]?"":"px")},cur:function(){var t=W.propHooks[this.prop];return t&&t.get?t.get(this):W.propHooks._default.get(this)},run:function(t){var e,i=W.propHooks[this.prop];return this.options.duration?this.pos=e=wt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),i&&i.set?i.set(this):W.propHooks._default.set(this),this}},W.prototype.init.prototype=W.prototype,W.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=wt.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){wt.fx.step[t.prop]?wt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[wt.cssProps[t.prop]]&&!wt.cssHooks[t.prop]?t.elem[t.prop]=t.now:wt.style(t.elem,t.prop,t.now+t.unit)}}},W.propHooks.scrollTop=W.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},wt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},wt.fx=W.prototype.init,wt.fx.step={};var ve,me,be=/^(?:toggle|show|hide)$/,xe=/queueHooks$/;wt.Animation=wt.extend(K,{tweeners:{"*":[function(t,e){var i=this.createTween(t,e);return m(i.elem,t,Vt.exec(e),i),i}]},tweener:function(t,e){mt(t)?(e=t,t=["*"]):t=t.match(It);for(var i,o=0,n=t.length;o1)},removeAttr:function(t){return this.each(function(){wt.removeAttr(this,t)})}}),wt.extend({attr:function(t,e,i){var o,n,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return void 0===t.getAttribute?wt.prop(t,e,i):(1===r&&wt.isXMLDoc(t)||(n=wt.attrHooks[e.toLowerCase()]||(wt.expr.match.bool.test(e)?we:void 0)),void 0!==i?null===i?void wt.removeAttr(t,e):n&&"set"in n&&void 0!==(o=n.set(t,i,e))?o:(t.setAttribute(e,i+""),i):n&&"get"in n&&null!==(o=n.get(t,e))?o:null==(o=wt.find.attr(t,e))?void 0:o)},attrHooks:{type:{set:function(t,e){if(!vt.radioValue&&"radio"===e&&r(t,"input")){var i=t.value;return t.setAttribute("type",e),i&&(t.value=i),e}}}},removeAttr:function(t,e){var i,o=0,n=e&&e.match(It);if(n&&1===t.nodeType)for(;i=n[o++];)t.removeAttribute(i)}}),we={set:function(t,e,i){return!1===e?wt.removeAttr(t,i):t.setAttribute(i,i),i}},wt.each(wt.expr.match.bool.source.match(/\w+/g),function(t,e){var i=_e[e]||wt.find.attr;_e[e]=function(t,e,o){var n,r,s=e.toLowerCase();return o||(r=_e[s],_e[s]=n,n=null!=i(t,e,o)?s:null,_e[s]=r),n}});var Se=/^(?:input|select|textarea|button)$/i,Ce=/^(?:a|area)$/i;wt.fn.extend({prop:function(t,e){return jt(this,wt.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[wt.propFix[t]||t]})}}),wt.extend({prop:function(t,e,i){var o,n,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return 1===r&&wt.isXMLDoc(t)||(e=wt.propFix[e]||e,n=wt.propHooks[e]),void 0!==i?n&&"set"in n&&void 0!==(o=n.set(t,i,e))?o:t[e]=i:n&&"get"in n&&null!==(o=n.get(t,e))?o:t[e]},propHooks:{tabIndex:{get:function(t){var e=wt.find.attr(t,"tabindex");return e?parseInt(e,10):Se.test(t.nodeName)||Ce.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),vt.optSelected||(wt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),wt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){wt.propFix[this.toLowerCase()]=this}),wt.fn.extend({addClass:function(t){var e,i,o,n,r,s,a,l=0;if(mt(t))return this.each(function(e){wt(this).addClass(t.call(this,e,$(this)))});if((e=J(t)).length)for(;i=this[l++];)if(n=$(i),o=1===i.nodeType&&" "+Z(n)+" "){for(s=0;r=e[s++];)o.indexOf(" "+r+" ")<0&&(o+=r+" ");n!==(a=Z(o))&&i.setAttribute("class",a)}return this},removeClass:function(t){var e,i,o,n,r,s,a,l=0;if(mt(t))return this.each(function(e){wt(this).removeClass(t.call(this,e,$(this)))});if(!arguments.length)return this.attr("class","");if((e=J(t)).length)for(;i=this[l++];)if(n=$(i),o=1===i.nodeType&&" "+Z(n)+" "){for(s=0;r=e[s++];)for(;o.indexOf(" "+r+" ")>-1;)o=o.replace(" "+r+" "," ");n!==(a=Z(o))&&i.setAttribute("class",a)}return this},toggleClass:function(t,e){var i=typeof t,o="string"===i||Array.isArray(t);return"boolean"==typeof e&&o?e?this.addClass(t):this.removeClass(t):mt(t)?this.each(function(i){wt(this).toggleClass(t.call(this,i,$(this),e),e)}):this.each(function(){var e,n,r,s;if(o)for(n=0,r=wt(this),s=J(t);e=s[n++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==i||((e=$(this))&&zt.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":zt.get(this,"__className__")||""))})},hasClass:function(t){var e,i,o=0;for(e=" "+t+" ";i=this[o++];)if(1===i.nodeType&&(" "+Z($(i))+" ").indexOf(e)>-1)return!0;return!1}});var Me=/\r/g;wt.fn.extend({val:function(t){var e,i,o,n=this[0];return arguments.length?(o=mt(t),this.each(function(i){var n;1===this.nodeType&&(null==(n=o?t.call(this,i,wt(this).val()):t)?n="":"number"==typeof n?n+="":Array.isArray(n)&&(n=wt.map(n,function(t){return null==t?"":t+""})),(e=wt.valHooks[this.type]||wt.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,n,"value")||(this.value=n))})):n?(e=wt.valHooks[n.type]||wt.valHooks[n.nodeName.toLowerCase()])&&"get"in e&&void 0!==(i=e.get(n,"value"))?i:"string"==typeof(i=n.value)?i.replace(Me,""):null==i?"":i:void 0}}),wt.extend({valHooks:{option:{get:function(t){var e=wt.find.attr(t,"value");return null!=e?e:Z(wt.text(t))}},select:{get:function(t){var e,i,o,n=t.options,s=t.selectedIndex,a="select-one"===t.type,l=a?null:[],h=a?s+1:n.length;for(o=s<0?h:a?s:0;o-1)&&(i=!0);return i||(t.selectedIndex=-1),r}}}}),wt.each(["radio","checkbox"],function(){wt.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=wt.inArray(wt(t).val(),e)>-1}},vt.checkOn||(wt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),vt.focusin="onfocusin"in t;var ke=/^(?:focusinfocus|focusoutblur)$/,Pe=function(t){t.stopPropagation()};wt.extend(wt.event,{trigger:function(e,i,o,n){var r,s,a,l,h,p,u,c,d=[o||st],f=ft.call(e,"type")?e.type:e,g=ft.call(e,"namespace")?e.namespace.split("."):[];if(s=c=a=o=o||st,3!==o.nodeType&&8!==o.nodeType&&!ke.test(f+wt.event.triggered)&&(f.indexOf(".")>-1&&(f=(g=f.split(".")).shift(),g.sort()),h=f.indexOf(":")<0&&"on"+f,e=e[wt.expando]?e:new wt.Event(f,"object"==typeof e&&e),e.isTrigger=n?2:3,e.namespace=g.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=o),i=null==i?[e]:wt.makeArray(i,[e]),u=wt.event.special[f]||{},n||!u.trigger||!1!==u.trigger.apply(o,i))){if(!n&&!u.noBubble&&!bt(o)){for(l=u.delegateType||f,ke.test(l+f)||(s=s.parentNode);s;s=s.parentNode)d.push(s),a=s;a===(o.ownerDocument||st)&&d.push(a.defaultView||a.parentWindow||t)}for(r=0;(s=d[r++])&&!e.isPropagationStopped();)c=s,e.type=r>1?l:u.bindType||f,(p=(zt.get(s,"events")||{})[e.type]&&zt.get(s,"handle"))&&p.apply(s,i),(p=h&&s[h])&&p.apply&&Ft(s)&&(e.result=p.apply(s,i),!1===e.result&&e.preventDefault());return e.type=f,n||e.isDefaultPrevented()||u._default&&!1!==u._default.apply(d.pop(),i)||!Ft(o)||h&&mt(o[f])&&!bt(o)&&((a=o[h])&&(o[h]=null),wt.event.triggered=f,e.isPropagationStopped()&&c.addEventListener(f,Pe),o[f](),e.isPropagationStopped()&&c.removeEventListener(f,Pe),wt.event.triggered=void 0,a&&(o[h]=a)),e.result}},simulate:function(t,e,i){var o=wt.extend(new wt.Event,i,{type:t,isSimulated:!0});wt.event.trigger(o,null,e)}}),wt.fn.extend({trigger:function(t,e){return this.each(function(){wt.event.trigger(t,e,this)})},triggerHandler:function(t,e){var i=this[0];if(i)return wt.event.trigger(t,e,i,!0)}}),vt.focusin||wt.each({focus:"focusin",blur:"focusout"},function(t,e){var i=function(t){wt.event.simulate(e,t.target,wt.event.fix(t))};wt.event.special[e]={setup:function(){var o=this.ownerDocument||this,n=zt.access(o,e);n||o.addEventListener(t,i,!0),zt.access(o,e,(n||0)+1)},teardown:function(){var o=this.ownerDocument||this,n=zt.access(o,e)-1;n?zt.access(o,e,n):(o.removeEventListener(t,i,!0),zt.remove(o,e))}}});var Te=t.location,Ee=Date.now(),Ae=/\?/;wt.parseXML=function(e){var i;if(!e||"string"!=typeof e)return null;try{i=(new t.DOMParser).parseFromString(e,"text/xml")}catch(t){i=void 0}return i&&!i.getElementsByTagName("parsererror").length||wt.error("Invalid XML: "+e),i};var De=/\[\]$/,Ie=/\r?\n/g,Le=/^(?:submit|button|image|reset|file)$/i,Oe=/^(?:input|select|textarea|keygen)/i;wt.param=function(t,e){var i,o=[],n=function(t,e){var i=mt(e)?e():e;o[o.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==i?"":i)};if(Array.isArray(t)||t.jquery&&!wt.isPlainObject(t))wt.each(t,function(){n(this.name,this.value)});else for(i in t)Q(i,t[i],e,n);return o.join("&")},wt.fn.extend({serialize:function(){return wt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=wt.prop(this,"elements");return t?wt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!wt(this).is(":disabled")&&Oe.test(this.nodeName)&&!Le.test(t)&&(this.checked||!Kt.test(t))}).map(function(t,e){var i=wt(this).val();return null==i?null:Array.isArray(i)?wt.map(i,function(t){return{name:e.name,value:t.replace(Ie,"\r\n")}}):{name:e.name,value:i.replace(Ie,"\r\n")}}).get()}});var je=/%20/g,Re=/#.*$/,Ne=/([?&])_=[^&]*/,Fe=/^(.*?):[ \t]*([^\r\n]*)$/gm,ze=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Be=/^(?:GET|HEAD)$/,He=/^\/\//,We={},Ue={},Ve="*/".concat("*"),Ge=st.createElement("a");Ge.href=Te.href,wt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Te.href,type:"GET",isLocal:ze.test(Te.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ve,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":wt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?it(it(t,wt.ajaxSettings),e):it(wt.ajaxSettings,t)},ajaxPrefilter:tt(We),ajaxTransport:tt(Ue),ajax:function(e,i){function o(e,i,o,a){var h,c,d,x,w,_=i;p||(p=!0,l&&t.clearTimeout(l),n=void 0,s=a||"",S.readyState=e>0?4:0,h=e>=200&&e<300||304===e,o&&(x=ot(f,S,o)),x=nt(f,x,S,h),h?(f.ifModified&&((w=S.getResponseHeader("Last-Modified"))&&(wt.lastModified[r]=w),(w=S.getResponseHeader("etag"))&&(wt.etag[r]=w)),204===e||"HEAD"===f.type?_="nocontent":304===e?_="notmodified":(_=x.state,c=x.data,h=!(d=x.error))):(d=_,!e&&_||(_="error",e<0&&(e=0))),S.status=e,S.statusText=(i||_)+"",h?v.resolveWith(g,[c,_,S]):v.rejectWith(g,[S,_,d]),S.statusCode(b),b=void 0,u&&y.trigger(h?"ajaxSuccess":"ajaxError",[S,f,h?c:d]),m.fireWith(g,[S,_]),u&&(y.trigger("ajaxComplete",[S,f]),--wt.active||wt.event.trigger("ajaxStop")))}"object"==typeof e&&(i=e,e=void 0),i=i||{};var n,r,s,a,l,h,p,u,c,d,f=wt.ajaxSetup({},i),g=f.context||f,y=f.context&&(g.nodeType||g.jquery)?wt(g):wt.event,v=wt.Deferred(),m=wt.Callbacks("once memory"),b=f.statusCode||{},x={},w={},_="canceled",S={readyState:0,getResponseHeader:function(t){var e;if(p){if(!a)for(a={};e=Fe.exec(s);)a[e[1].toLowerCase()]=e[2];e=a[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return p?s:null},setRequestHeader:function(t,e){return null==p&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,x[t]=e),this},overrideMimeType:function(t){return null==p&&(f.mimeType=t),this},statusCode:function(t){var e;if(t)if(p)S.always(t[S.status]);else for(e in t)b[e]=[b[e],t[e]];return this},abort:function(t){var e=t||_;return n&&n.abort(e),o(0,e),this}};if(v.promise(S),f.url=((e||f.url||Te.href)+"").replace(He,Te.protocol+"//"),f.type=i.method||i.type||f.method||f.type,f.dataTypes=(f.dataType||"*").toLowerCase().match(It)||[""],null==f.crossDomain){h=st.createElement("a");try{h.href=f.url,h.href=h.href,f.crossDomain=Ge.protocol+"//"+Ge.host!=h.protocol+"//"+h.host}catch(t){f.crossDomain=!0}}if(f.data&&f.processData&&"string"!=typeof f.data&&(f.data=wt.param(f.data,f.traditional)),et(We,f,i,S),p)return S -;(u=wt.event&&f.global)&&0==wt.active++&&wt.event.trigger("ajaxStart"),f.type=f.type.toUpperCase(),f.hasContent=!Be.test(f.type),r=f.url.replace(Re,""),f.hasContent?f.data&&f.processData&&0===(f.contentType||"").indexOf("application/x-www-form-urlencoded")&&(f.data=f.data.replace(je,"+")):(d=f.url.slice(r.length),f.data&&(f.processData||"string"==typeof f.data)&&(r+=(Ae.test(r)?"&":"?")+f.data,delete f.data),!1===f.cache&&(r=r.replace(Ne,"$1"),d=(Ae.test(r)?"&":"?")+"_="+Ee+++d),f.url=r+d),f.ifModified&&(wt.lastModified[r]&&S.setRequestHeader("If-Modified-Since",wt.lastModified[r]),wt.etag[r]&&S.setRequestHeader("If-None-Match",wt.etag[r])),(f.data&&f.hasContent&&!1!==f.contentType||i.contentType)&&S.setRequestHeader("Content-Type",f.contentType),S.setRequestHeader("Accept",f.dataTypes[0]&&f.accepts[f.dataTypes[0]]?f.accepts[f.dataTypes[0]]+("*"!==f.dataTypes[0]?", "+Ve+"; q=0.01":""):f.accepts["*"]);for(c in f.headers)S.setRequestHeader(c,f.headers[c]);if(f.beforeSend&&(!1===f.beforeSend.call(g,S,f)||p))return S.abort();if(_="abort",m.add(f.complete),S.done(f.success),S.fail(f.error),n=et(Ue,f,i,S)){if(S.readyState=1,u&&y.trigger("ajaxSend",[S,f]),p)return S;f.async&&f.timeout>0&&(l=t.setTimeout(function(){S.abort("timeout")},f.timeout));try{p=!1,n.send(x,o)}catch(t){if(p)throw t;o(-1,t)}}else o(-1,"No Transport");return S},getJSON:function(t,e,i){return wt.get(t,e,i,"json")},getScript:function(t,e){return wt.get(t,void 0,e,"script")}}),wt.each(["get","post"],function(t,e){wt[e]=function(t,i,o,n){return mt(i)&&(n=n||o,o=i,i=void 0),wt.ajax(wt.extend({url:t,type:e,dataType:n,data:i,success:o},wt.isPlainObject(t)&&t))}}),wt._evalUrl=function(t){return wt.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},wt.fn.extend({wrapAll:function(t){var e;return this[0]&&(mt(t)&&(t=t.call(this[0])),e=wt(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return mt(t)?this.each(function(e){wt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=wt(this),i=e.contents();i.length?i.wrapAll(t):e.append(t)})},wrap:function(t){var e=mt(t);return this.each(function(i){wt(this).wrapAll(e?t.call(this,i):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){wt(this).replaceWith(this.childNodes)}),this}}),wt.expr.pseudos.hidden=function(t){return!wt.expr.pseudos.visible(t)},wt.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},wt.ajaxSettings.xhr=function(){try{return new t.XMLHttpRequest}catch(t){}};var qe={0:200,1223:204},Ye=wt.ajaxSettings.xhr();vt.cors=!!Ye&&"withCredentials"in Ye,vt.ajax=Ye=!!Ye,wt.ajaxTransport(function(e){var i,o;if(vt.cors||Ye&&!e.crossDomain)return{send:function(n,r){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(s in n)a.setRequestHeader(s,n[s]);i=function(t){return function(){i&&(i=o=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===t?a.abort():"error"===t?"number"!=typeof a.status?r(0,"error"):r(a.status,a.statusText):r(qe[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=i(),o=a.onerror=a.ontimeout=i("error"),void 0!==a.onabort?a.onabort=o:a.onreadystatechange=function(){4===a.readyState&&t.setTimeout(function(){i&&o()})},i=i("abort");try{a.send(e.hasContent&&e.data||null)}catch(t){if(i)throw t}},abort:function(){i&&i()}}}),wt.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),wt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return wt.globalEval(t),t}}}),wt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),wt.ajaxTransport("script",function(t){if(t.crossDomain){var e,i;return{send:function(o,n){e=wt(" {% block extra_head %} diff --git a/bims/templates/map_page/map-templates.html b/bims/templates/map_page/map-templates.html index 5484b865d..c66913f0a 100644 --- a/bims/templates/map_page/map-templates.html +++ b/bims/templates/map_page/map-templates.html @@ -49,7 +49,7 @@
Reset
- + SEARCH
@@ -75,9 +75,23 @@ FILTERS
+ + {% if modules_exists %} +
+
+ MODULE +
+
+ + {% endif %} +
- DATE + {{ date_title }}
- COLLECTOR + {{ spatial_scale_title }}
+ + + + + + \ No newline at end of file diff --git a/bims/tests/model_factories.py b/bims/tests/model_factories.py index 005d129a0..5cf27be00 100644 --- a/bims/tests/model_factories.py +++ b/bims/tests/model_factories.py @@ -5,6 +5,8 @@ from django.contrib.gis.geos import Point from django.utils import timezone from django.db.models import signals +from django.contrib.auth.models import Permission, Group +from django.contrib.contenttypes.models import ContentType from bims.models import ( LocationType, LocationSite, @@ -58,7 +60,6 @@ class Meta: sensitive = False -@factory.django.mute_signals(signals.pre_save) class TaxonF(factory.django.DjangoModelFactory): """ Taxon factory @@ -94,6 +95,29 @@ def sites(self, create, extracted, **kwargs): self.sites.add(site) +class GroupF(factory.DjangoModelFactory): + class Meta: + model = Group + + name = factory.Sequence(lambda n: 'group %s' % n) + + +class ContentTypeF(factory.DjangoModelFactory): + class Meta: + model = ContentType + + app_label = factory.Sequence(lambda n: 'app %s' % n) + model = factory.Sequence(lambda n: 'model %s' % n) + + +class PermissionF(factory.DjangoModelFactory): + class Meta: + model = Permission + + name = factory.Sequence(lambda n: 'permission %s' % n) + content_type = factory.SubFactory(ContentTypeF) + + class UserF(factory.DjangoModelFactory): class Meta: model = settings.AUTH_USER_MODEL diff --git a/bims/tests/test_api_views.py b/bims/tests/test_api_views.py index e708492ac..ade901b0b 100644 --- a/bims/tests/test_api_views.py +++ b/bims/tests/test_api_views.py @@ -1,7 +1,12 @@ from django.test import TestCase +from django.core.urlresolvers import reverse from rest_framework.test import APIRequestFactory from bims.tests.model_factories import ( BiologicalCollectionRecordF, + UserF, + ContentTypeF, + PermissionF, + GroupF, ) from bims.tests.model_factories import ( LocationSiteF, @@ -14,7 +19,11 @@ from bims.api_views.location_type import ( LocationTypeAllowedGeometryDetail ) +from bims.api_views.non_validated_record import ( + GetNonValidatedRecords +) from bims.api_views.taxon import TaxonDetail +from bims.api_views.reference_category import ReferenceCategoryList class TestApiView(TestCase): @@ -36,6 +45,10 @@ def setUp(self): original_species_name=u'Test fish species name 2', site=self.location_site ) + self.admin_user = UserF.create( + is_superuser=True, + is_staff=True + ) def test_get_all_location(self): view = LocationSiteList.as_view() @@ -73,3 +86,73 @@ def test_get_taxon_by_id(self): '/api/location-type/%s/allowed-geometry/' % pk) response = view(request, pk) self.assertEqual(response.data, 'POINT') + + def test_get_unvalidated_records_as_public(self): + view = GetNonValidatedRecords.as_view() + request = self.factory.get(reverse('get-unvalidated-records')) + response = view(request) + self.assertEqual(response.status_code, 403) + + def test_get_unvalidated_records_as_admin(self): + view = GetNonValidatedRecords.as_view() + request = self.factory.get(reverse('get-unvalidated-records')) + request.user = self.admin_user + response = view(request) + self.assertEqual(response.status_code, 200) + + def test_get_unvalidated_records_as_validator(self): + view = GetNonValidatedRecords.as_view() + user = UserF.create() + content_type = ContentTypeF.create( + app_label='bims', + model='bims' + ) + permission = PermissionF.create( + name='Can validate data', + content_type=content_type, + codename='can_validate_data' + ) + group = GroupF.create() + group.permissions.add(permission) + user.groups.add(group) + + request = self.factory.get(reverse('get-unvalidated-records')) + request.user = user + response = view(request) + self.assertEqual(response.status_code, 200) + + def test_only_get_aves_collection(self): + from django.contrib.auth.models import Permission + view = GetNonValidatedRecords.as_view() + taxon = TaxonF.create( + taxon_class='Aves', + ) + BiologicalCollectionRecordF.create( + pk=3, + site=self.location_site, + taxon_gbif_id=taxon, + validated=False + ) + user = UserF.create() + permission = Permission.objects.filter(codename='can_validate_aves')[0] + group = GroupF.create() + group.permissions.add(permission) + user.groups.add(group) + + request = self.factory.get(reverse('get-unvalidated-records')) + request.user = user + response = view(request) + self.assertEqual(response.status_code, 200) + + def test_get_referece_category(self): + view = ReferenceCategoryList.as_view() + BiologicalCollectionRecordF.create( + pk=5, + original_species_name=u'Test name', + site=self.location_site, + reference_category=u'Database' + ) + request = self.factory.get(reverse('list-reference-category')) + response = view(request) + self.assertEqual(response.status_code, 200) + self.assertTrue(len(response.data) > 0) diff --git a/bims/tests/test_permission.py b/bims/tests/test_permission.py new file mode 100644 index 000000000..9b7cb9277 --- /dev/null +++ b/bims/tests/test_permission.py @@ -0,0 +1,42 @@ +# coding=utf-8 +"""Tests for permissions.""" + +from django.test import TestCase +from bims.tests.model_factories import ( + PermissionF, + TaxonF, +) +from bims.permissions.generate_permission import generate_permission + + +class TestValidatorPermission(TestCase): + """ Tests permission for validator. + """ + + def setUp(self): + """ + Sets up before each test + """ + self.taxon = TaxonF.create( + taxon_class='Aves' + ) + self.permission = PermissionF.create( + name='Can validate data', + codename='can_validate_data' + ) + + def test_permission_generated(self): + """ + Tests if permission generated if there is new taxon class + """ + taxon_class = 'Animalia' + result = generate_permission(taxon_class) + self.assertIsNotNone(result) + + def test_permission_already_exists(self): + """ + Tests existed permission not generated + """ + taxon_class = 'Aves' + result = generate_permission(taxon_class) + self.assertIsNone(result) diff --git a/bims/tests/test_views_locate.py b/bims/tests/test_views_locate.py index 4fe90760c..b4121692a 100644 --- a/bims/tests/test_views_locate.py +++ b/bims/tests/test_views_locate.py @@ -46,8 +46,6 @@ def test_get_farm(self): farm_id = 'C00100000000000100001' farm = get_farm(farm_id) self.assertEqual(farm['farm_id'], farm_id) - expected_envelope_extent = (-32.22, 23.7354, -32.1787, 23.7978) - self.assertEqual(farm['envelope_extent'], expected_envelope_extent) def test_parse_farm(self): """Test parse_farm""" diff --git a/bims/urls.py b/bims/urls.py index 9f7dd9a52..e1cc26544 100644 --- a/bims/urls.py +++ b/bims/urls.py @@ -32,6 +32,7 @@ ClusterCollection ) from bims.api_views.collector import CollectorList +from bims.api_views.reference_category import ReferenceCategoryList from bims.api_views.category_filter import CategoryList from bims.api_views.search import SearchObjects from bims.api_views.validate_object import ValidateObject @@ -39,11 +40,16 @@ GetBioRecordDetail, GetBioRecords ) +from bims.api_views.non_validated_record import GetNonValidatedRecords from bims.api_views.hide_popup_info_user import HidePopupInfoUser from bims.views.links import LinksCategoryView from bims.views.activate_user import activate_user from bims.views.csv_upload import CsvUploadView -from bims.views.shapefile_upload import ShapefileUploadView, process_shapefiles +from bims.views.shapefile_upload import ( + ShapefileUploadView, + process_shapefiles, + process_user_boundary_shapefiles +) from bims.views.under_development import UnderDevelopmentView from bims.views.non_validated_list import NonValidatedObjectsView from bims.views.non_validated_user_list import NonValidatedObjectsUserView @@ -52,6 +58,7 @@ from bims.api_views.send_notification_to_validator import \ SendNotificationValidation from bims.views.locate import filter_farm_ids_view, get_farm_view +from bims.api_views.user_boundary import UserBoundaryList api_urls = [ url(r'^api/location-type/(?P[0-9]+)/allowed-geometry/$', @@ -79,6 +86,8 @@ BoundaryGeojson.as_view(), name='boundary-geojson'), url(r'^api/list-boundary/$', BoundaryList.as_view(), name='list-boundary'), + url(r'^api/list-user-boundary/$', + UserBoundaryList.as_view(), name='list-user-boundary'), url(r'^api/list-collector/$', CollectorList.as_view(), name='list-collector'), url(r'^api/list-category/$', @@ -92,6 +101,8 @@ GetBioRecordDetail.as_view(), name='get-bio-object'), url(r'^api/get-bio-records/(?P[\w-]+)/$', GetBioRecords.as_view(), name='get-bio-records'), + url(r'^api/get-unvalidated-records/$', + GetNonValidatedRecords.as_view(), name='get-unvalidated-records'), url(r'^api/send-email-validation/$', SendNotificationValidation.as_view(), name='send-email-validation'), url(r'^api/filter-farm-id/$', @@ -99,7 +110,9 @@ url(r'^api/get-farm/(?P[\w-]+)/$', get_farm_view, name='get-farm'), url(r'api/hide-popup-info/$', - HidePopupInfoUser.as_view(), name='hide-popup-user') + HidePopupInfoUser.as_view(), name='hide-popup-user'), + url(r'^api/list-reference-category/$', + ReferenceCategoryList.as_view(), name='list-reference-category'), ] @@ -117,6 +130,9 @@ name='shapefile-upload'), url(r'^process_shapefiles/$', process_shapefiles, name='process_shapefiles'), + url(r'^process_user_boundary_shapefiles/$', + process_user_boundary_shapefiles, + name='process_user_boundary_shapefiles'), url(r'^links/$', LinksCategoryView.as_view(), name = 'link_list'), url(r'^api/docs/', include_docs_urls(title='BIMS API')), url(r'^activate-user/(?P[\w-]+)/$', diff --git a/bims/utils/get_key.py b/bims/utils/get_key.py index caa8b447a..913262da5 100644 --- a/bims/utils/get_key.py +++ b/bims/utils/get_key.py @@ -1,4 +1,5 @@ import os +from django.core.exceptions import ImproperlyConfigured def get_key(key_name): @@ -12,11 +13,15 @@ def get_key(key_name): return '' try: - key = getattr(secret, key_name) - except AttributeError: + from django.conf import settings + key = getattr(settings, key_name) + except (AttributeError, ImproperlyConfigured): try: - key = os.environ[key_name] - except KeyError: - key = '' + key = getattr(secret, key_name) + except AttributeError: + try: + key = os.environ[key_name] + except KeyError: + key = '' return key diff --git a/bims/views/csv_upload.py b/bims/views/csv_upload.py index 7b7ea6eae..07de4d34d 100644 --- a/bims/views/csv_upload.py +++ b/bims/views/csv_upload.py @@ -46,9 +46,41 @@ def handle_no_permission(self): additional_fields = { 'present': 'bool', - 'absent': 'bool' + 'absent': 'bool', + 'endemism': 'str', + 'sampling_method': 'str', + 'sampling_effort': 'str', + 'reference': 'str', + 'reference_category': 'str', + 'site_description': 'str', + 'site_code': 'str' } + def add_additional_fields(self, fields): + if not isinstance(fields, list): + return + self.additional_fields = fields + self.additional_fields + + def parse_optional_record(self, record, field_type): + optional_record = None + if field_type == 'bool': + optional_record = record == '1' + elif field_type == 'char': + optional_record = record.lower() + elif field_type == 'str': + optional_record = record + elif field_type == 'float': + try: + optional_record = float(record) + except ValueError: + optional_record = 0.0 + elif field_type == 'int': + try: + optional_record = int(record) + except ValueError: + optional_record = 0 + return optional_record + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) context['data'] = self.context_data @@ -100,6 +132,37 @@ def form_valid(self, form): allowed_geometry='POINT' ) + # Optional records for location site + optional_site_records = {} + + # Optional fields and value + optional_records = {} + + if sys.version_info > (3, 0): + # Python 3 code in this block + optional_fields_iter = self.additional_fields.items() + else: + # Python 2 code in this block + optional_fields_iter = self.additional_fields.\ + iteritems() + + for (opt_field, field_type) in optional_fields_iter: + if opt_field in record: + optional_record = self.parse_optional_record( + record[opt_field], + field_type + ) + if not optional_record: + optional_record = '' + + if opt_field[:4] == 'site': + optional_site_records[opt_field] = \ + optional_record + else: + if optional_record: + optional_records[opt_field] = \ + optional_record + record_point = Point( float(record['longitude']), float(record['latitude'])) @@ -108,6 +171,7 @@ def form_valid(self, form): location_type=location_type, geometry_point=record_point, name=record['location_site'], + **optional_site_records ) location_sites.append(location_site) @@ -120,25 +184,6 @@ def form_valid(self, form): if collections: taxon_gbif = collections[0].taxon_gbif_id - # Optional fields and value - optional_records = {} - - if (sys.version_info > (3, 0)): - # Python 3 code in this block - optional_fields_iter = self.additional_fields.items() - else: - # Python 2 code in this block - optional_fields_iter = self.additional_fields.\ - iteritems() - - for (opt_field, field_type) in optional_fields_iter: - if opt_field in record: - if field_type == 'bool': - record[opt_field] = record[opt_field] == '1' - elif field_type == 'str': - record[opt_field] = record[opt_field].lower() - optional_records[opt_field] = record[opt_field] - # custodian field if 'custodian' in record: optional_records['institution_id'] = \ diff --git a/bims/views/map.py b/bims/views/map.py index ad7ba2d3f..c167dbeaa 100644 --- a/bims/views/map.py +++ b/bims/views/map.py @@ -2,12 +2,13 @@ import os from django.db.models import Max, Min from django.views.generic import TemplateView +from django.contrib.flatpages.models import FlatPage from bims.utils.get_key import get_key from bims.models.biological_collection_record import ( BiologicalCollectionRecord ) from bims.models.profile import Profile as BimsProfile -from django.contrib.flatpages.models import FlatPage +from bims.permissions.api_permission import user_has_permission_to_validate class MapPageView(TemplateView): @@ -37,6 +38,8 @@ def get_context_data(self, **kwargs): context['geocontext_collection_key'] = get_key( 'GEOCONTEXT_COLLECTION_KEY') context['center_point_map'] = get_key('CENTER_POINT_MAP') + context['can_validate'] = user_has_permission_to_validate( + self.request.user) categories = BiologicalCollectionRecord.CATEGORY_CHOICES context['collection_category'] = [list(x) for x in categories] @@ -48,8 +51,33 @@ def get_context_data(self, **kwargs): bio._meta.app_label: str(bio._meta.label) for bio in bio_childrens } # add base module + context['modules_exists'] = bool(context['biological_modules']) context['biological_modules']['base'] = 'base' + # Additional filters + context['use_ecological_condition'] = bool( + get_key('ECOLOGICAL_CONDITION_FILTER')) + context['use_conservation_status'] = bool( + get_key('CONSERVATION_STATUS_FILTER')) + context['use_reference_category'] = bool( + get_key('REFERENCE_CATEGORY_FILTER')) + + # Search panel titles + date_title = get_key('DATE_TITLE') + if not date_title: + date_title = 'DATE' + context['date_title'] = date_title + + spatial_scale = get_key('SPATIAL_SCALE_TITLE') + if not spatial_scale: + spatial_scale = 'ADMINISTRATIVE AREA' + context['spatial_scale_title'] = spatial_scale + + collector_title = get_key('COLLECTOR_TITLE') + if not collector_title: + collector_title = 'COLLECTOR' + context['collector_title'] = collector_title + # get date filter context['date_filter'] = {'min': '1900', 'max': '2008'} date_min = BiologicalCollectionRecord.objects.all( diff --git a/bims/views/non_validated_list.py b/bims/views/non_validated_list.py index 886f73ef9..d570b86c3 100644 --- a/bims/views/non_validated_list.py +++ b/bims/views/non_validated_list.py @@ -3,6 +3,10 @@ from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin from django.contrib import messages from bims.models.biological_collection_record import BiologicalCollectionRecord +from bims.permissions.api_permission import ( + user_has_permission_to_validate, + AllowedTaxon, +) class NonValidatedObjectsView( @@ -16,7 +20,7 @@ class NonValidatedObjectsView( paginate_by = 10 def test_func(self): - return self.request.user.has_perm('bims.can_validate_data') + return user_has_permission_to_validate(self.request.user) def handle_no_permission(self): messages.error(self.request, 'You don\'t have permission ' @@ -53,8 +57,11 @@ def get_queryset(self): filter_date_from = self.request.GET.get('date_from', None) filter_pk = self.request.GET.get('pk', None) if self.queryset is None: + allowed_taxon = AllowedTaxon().get(self.request.user) queryset = \ BiologicalCollectionRecord.objects.filter( + taxon_gbif_id__in=allowed_taxon, + ready_for_validation=True, validated=False).order_by('original_species_name') if filter_pk is not None: queryset = queryset.filter(pk=filter_pk) diff --git a/bims/views/shapefile_upload.py b/bims/views/shapefile_upload.py index e66a833fe..4f2331a78 100644 --- a/bims/views/shapefile_upload.py +++ b/bims/views/shapefile_upload.py @@ -57,6 +57,116 @@ def post(self, request): return JsonResponse(data) +def process_user_boundary_shapefiles(request): + from bims.models import UserBoundary + from django.contrib.gis.geos import Polygon, MultiPolygon + token = request.GET.get('token', None) + boundary_name = request.GET.get('name', None) + + if not token: + return JsonResponse({ + 'message': 'empty token' + }) + + shapefiles = Shapefile.objects.filter( + token=token + ) + + for shp in shapefiles: + shp.token = '' + shp.save() + + upload_session, created = ShapefileUploadSession.objects.get_or_create( + uploader=request.user, + token=token, + processed=False, + ) + + if created: + upload_session.shapefiles = shapefiles + upload_session.save() + + all_shapefiles = upload_session.shapefiles.all() + + needed_ext = ['.shx', '.shp', '.dbf'] + needed_files = {} + + # Check all needed files + for shp in all_shapefiles: + name, extension = os.path.splitext(shp.filename) + if extension in needed_ext: + needed_files[extension[1:]] = shp + needed_ext.remove(extension) + + if len(needed_ext) > 0: + data = { + 'message': 'missing %s' % ','.join(needed_ext) + } + upload_session.error = data['message'] + upload_session.save() + return JsonResponse(data) + + # Extract shapefile into dictionary + outputs = extract_shape_file( + shp_file=needed_files['shp'].shapefile, + shx_file=needed_files['shx'].shapefile, + dbf_file=needed_files['dbf'].shapefile, + ) + + geometry = None + geometries = [] + + for geojson in outputs: + try: + properties = geojson['properties'] + + if not boundary_name: + if 'name' in properties: + boundary_name = properties['name'] + else: + boundary_name, extension = os.path.splitext( + all_shapefiles[0].filename + ) + + geojson_json = json.dumps(geojson['geometry']) + geometry = GEOSGeometry(geojson_json) + + if isinstance(geometry, Polygon): + geometries.append(geometry) + elif not isinstance(geometry, MultiPolygon): + response_message = 'Only polygon and multipolygon allowed' + upload_session.error = response_message + upload_session.save() + return JsonResponse({'message': response_message}) + + except (ValueError, KeyError, TypeError) as e: + upload_session.error = str(e) + upload_session.save() + response_message = 'Failed : %s' % str(e) + return JsonResponse({'message': response_message}) + + if len(geometries) > 0: + geometry = MultiPolygon(geometries) + + user_boundary, created = UserBoundary.objects.get_or_create( + user=request.user, + name=boundary_name, + geometry=geometry + ) + upload_session.processed = True + upload_session.save() + + if created: + response_message = 'User boundary added' + else: + response_message = 'User boundary already exists' + + data = { + 'message': response_message + } + return JsonResponse(data) + + def process_shapefiles(request, collection=BiologicalCollectionRecord, additional_fields=None): @@ -87,7 +197,6 @@ def process_shapefiles(request, if created: upload_session.shapefiles = shapefiles - upload_session.token = '' upload_session.save() all_shapefiles = upload_session.shapefiles.all() diff --git a/deployment/production/REQUIREMENTS.txt b/deployment/production/REQUIREMENTS.txt index 10e1b3d6a..c4c625a42 100644 --- a/deployment/production/REQUIREMENTS.txt +++ b/deployment/production/REQUIREMENTS.txt @@ -45,7 +45,8 @@ django-contact-us==0.4.1 Pillow==5.1.0 django-ordered-model==1.4.3 -django-haystack==2.8.1 +# Django haystack support +git+https://github.com/dimasciput/django-haystack.git elasticsearch==5.0.1 # prometheus monitoring support