From d4a6e28fe68eef9a7761925f137c66b8a4069f01 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 27 Dec 2018 00:38:42 +0100 Subject: [PATCH] core: add custom group model with hierarchy , add tree admin --- passbook/admin/api/v1/groups.py | 36 ++++++++ passbook/admin/api/v1/routers.py | 8 -- passbook/admin/api/v1/source.py | 26 ------ passbook/admin/api/v1/urls.py | 9 ++ passbook/admin/api/v1/utils.py | 18 ---- passbook/admin/requirements.txt | 4 +- .../templates/administration/groups/list.html | 83 +++++++++++++++++++ passbook/admin/urls.py | 16 +++- passbook/admin/views/groups.py | 12 +++ .../migrations/0005_auto_20181226_2115.py | 35 ++++++++ .../core/migrations/0006_group_extra_data.py | 18 ++++ .../migrations/0007_auto_20181226_2142.py | 30 +++++++ .../migrations/0008_auto_20181226_2200.py | 27 ++++++ passbook/core/models.py | 17 +++- passbook/core/settings.py | 1 + .../static/css/bootstrap-treeview.min.css | 1 + .../core/static/js/bootstrap-treeview.min.js | 1 + 17 files changed, 284 insertions(+), 58 deletions(-) create mode 100644 passbook/admin/api/v1/groups.py delete mode 100644 passbook/admin/api/v1/routers.py delete mode 100644 passbook/admin/api/v1/source.py create mode 100644 passbook/admin/api/v1/urls.py delete mode 100644 passbook/admin/api/v1/utils.py create mode 100644 passbook/admin/templates/administration/groups/list.html create mode 100644 passbook/admin/views/groups.py create mode 100644 passbook/core/migrations/0005_auto_20181226_2115.py create mode 100644 passbook/core/migrations/0006_group_extra_data.py create mode 100644 passbook/core/migrations/0007_auto_20181226_2142.py create mode 100644 passbook/core/migrations/0008_auto_20181226_2200.py create mode 100644 passbook/core/static/css/bootstrap-treeview.min.css create mode 100644 passbook/core/static/js/bootstrap-treeview.min.js diff --git a/passbook/admin/api/v1/groups.py b/passbook/admin/api/v1/groups.py new file mode 100644 index 000000000..d1a115154 --- /dev/null +++ b/passbook/admin/api/v1/groups.py @@ -0,0 +1,36 @@ +"""passbook admin gorup API""" +from rest_framework.permissions import IsAdminUser +from rest_framework.serializers import ModelSerializer, Serializer +from rest_framework.viewsets import ModelViewSet + +from passbook.core.models import Group + + +class RecursiveField(Serializer): + """Recursive field for manytomanyfield""" + + def to_representation(self, value): + serializer = self.parent.parent.__class__(value, context=self.context) + return serializer.data + + def create(self): + raise NotImplementedError() + + def update(self): + raise NotImplementedError() + +class GroupSerializer(ModelSerializer): + """Group Serializer""" + + children = RecursiveField(many=True) + + class Meta: + model = Group + fields = '__all__' + +class GroupViewSet(ModelViewSet): + """Group Viewset""" + + permission_classes = [IsAdminUser] + serializer_class = GroupSerializer + queryset = Group.objects.filter(parent__isnull=True) diff --git a/passbook/admin/api/v1/routers.py b/passbook/admin/api/v1/routers.py deleted file mode 100644 index 328c9cb2a..000000000 --- a/passbook/admin/api/v1/routers.py +++ /dev/null @@ -1,8 +0,0 @@ -# from django.conf.urls import url, include - -# # Add this! -# from passbook.admin.api.v1.source import SourceResource - -# urlpatterns = [ -# url(r'source/', include(SourceResource.urls())), -# ] diff --git a/passbook/admin/api/v1/source.py b/passbook/admin/api/v1/source.py deleted file mode 100644 index cd34721a2..000000000 --- a/passbook/admin/api/v1/source.py +++ /dev/null @@ -1,26 +0,0 @@ -# from rest_framework.serializers import HyperlinkedModelSerializer -# from passbook.admin.api.v1.utils import LookupSerializer -# from passbook.core.models import Source -# from passbook.oauth_client.models import OAuthSource - -# from rest_framework.viewsets import ModelViewSet - -# class LookupSourceSerializer(HyperlinkedModelSerializer): - -# def to_representation(self, instance): -# if isinstance(instance, Source): -# return SourceSerializer(instance=instance).data -# elif isinstance(instance, OAuthSource): -# return OAuthSourceSerializer(instance=instance).data -# else: -# return LookupSourceSerializer(instance=instance).data - -# class Meta: -# model = Source -# fields = '__all__' - - -# class SourceViewSet(ModelViewSet): - -# serializer_class = LookupSourceSerializer -# queryset = Source.objects.select_subclasses() diff --git a/passbook/admin/api/v1/urls.py b/passbook/admin/api/v1/urls.py new file mode 100644 index 000000000..7fa40fbda --- /dev/null +++ b/passbook/admin/api/v1/urls.py @@ -0,0 +1,9 @@ +"""passbook admin API URLs""" +from rest_framework.routers import DefaultRouter + +from passbook.admin.api.v1.groups import GroupViewSet + +router = DefaultRouter() +router.register(r'groups', GroupViewSet) + +urlpatterns = router.urls diff --git a/passbook/admin/api/v1/utils.py b/passbook/admin/api/v1/utils.py deleted file mode 100644 index ee27f5f3a..000000000 --- a/passbook/admin/api/v1/utils.py +++ /dev/null @@ -1,18 +0,0 @@ -"""passbook admin api utils""" -# from django.db.models import Model -# from rest_framework.serializers import ModelSerializer - - -# class LookupSerializer(ModelSerializer): - -# mapping = {} - -# def to_representation(self, instance): -# for __model, __serializer in self.mapping.items(): -# if isinstance(instance, __model): -# return __serializer(instance=instance).to_representation(instance) -# raise KeyError(instance.__class__.__name__) - -# class Meta: -# model = Model -# fields = '__all__' diff --git a/passbook/admin/requirements.txt b/passbook/admin/requirements.txt index 44f4d7ce3..353ef0268 100644 --- a/passbook/admin/requirements.txt +++ b/passbook/admin/requirements.txt @@ -1 +1,3 @@ -django-crispy-forms \ No newline at end of file +django-crispy-forms +django-rest-framework +django-rest-swagger diff --git a/passbook/admin/templates/administration/groups/list.html b/passbook/admin/templates/administration/groups/list.html new file mode 100644 index 000000000..b8df11dc8 --- /dev/null +++ b/passbook/admin/templates/administration/groups/list.html @@ -0,0 +1,83 @@ +{% extends "administration/base.html" %} + +{% load i18n %} +{% load static %} +{% load utils %} + +{% block head %} +{{ block.super }} + +{% endblock %} + +{% block scripts %} +{{ block.super }} + + +{% endblock %} + +{% block title %} +{% title %} +{% endblock %} + +{% block content %} +
+
+
+
+
+

{% trans "Invitations" %}

+ + {% trans 'Create...' %} + +
+ + + + + + + + + + {% for invitation in object_list %} + + + + + + {% endfor %} + +
{% trans 'Expiry' %}{% trans 'Link' %}
{{ invitation.expires|default:"Never" }} +
{{ invitation.link }}
+
+ {% + trans 'Delete' %} +
+
+{% endblock %} diff --git a/passbook/admin/urls.py b/passbook/admin/urls.py index 0079ab81d..69cb03335 100644 --- a/passbook/admin/urls.py +++ b/passbook/admin/urls.py @@ -1,8 +1,12 @@ """passbook URL Configuration""" -from django.urls import path +from django.urls import include, path +from rest_framework_swagger.views import get_swagger_view + +from passbook.admin.views import (applications, audit, groups, invitations, + overview, providers, rules, sources, users) + +schema_view = get_swagger_view(title='passbook Admin Internal API') -from passbook.admin.views import (applications, audit, invitations, overview, - providers, rules, sources, users) urlpatterns = [ path('', overview.AdministrationOverviewView.as_view(), name='overview'), @@ -49,5 +53,9 @@ urlpatterns = [ users.UserDeleteView.as_view(), name='user-delete'), # Audit Log path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'), - # path('api/v1/', include('passbook.admin.api.v1.urls')) + # Groups + path('groups/', groups.GroupListView.as_view(), name='groups'), + # API + path('api/', schema_view), + path('api/v1/', include('passbook.admin.api.v1.urls')) ] diff --git a/passbook/admin/views/groups.py b/passbook/admin/views/groups.py new file mode 100644 index 000000000..629ed4aa0 --- /dev/null +++ b/passbook/admin/views/groups.py @@ -0,0 +1,12 @@ +"""passbook Group administration""" +from django.views.generic import ListView + +from passbook.admin.mixins import AdminRequiredMixin +from passbook.core.models import Group + + +class GroupListView(AdminRequiredMixin, ListView): + """Show list of all invitations""" + + model = Group + template_name = 'administration/groups/list.html' diff --git a/passbook/core/migrations/0005_auto_20181226_2115.py b/passbook/core/migrations/0005_auto_20181226_2115.py new file mode 100644 index 000000000..9e9b19067 --- /dev/null +++ b/passbook/core/migrations/0005_auto_20181226_2115.py @@ -0,0 +1,35 @@ +# Generated by Django 2.1.4 on 2018-12-26 21:15 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0009_alter_user_last_name_max_length'), + ('passbook_core', '0004_application_slug'), + ] + + operations = [ + migrations.CreateModel( + name='Group', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=80, verbose_name='name')), + ('parent_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='passbook_core.Group')), + ('permissions', models.ManyToManyField(blank=True, related_name='_group_permissions_+', to='auth.Permission')), + ], + ), + migrations.AlterField( + model_name='user', + name='groups', + field=models.ManyToManyField(to='passbook_core.Group'), + ), + migrations.AlterUniqueTogether( + name='group', + unique_together={('name', 'parent_group')}, + ), + ] diff --git a/passbook/core/migrations/0006_group_extra_data.py b/passbook/core/migrations/0006_group_extra_data.py new file mode 100644 index 000000000..7345bcc1c --- /dev/null +++ b/passbook/core/migrations/0006_group_extra_data.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2018-12-26 21:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passbook_core', '0005_auto_20181226_2115'), + ] + + operations = [ + migrations.AddField( + model_name='group', + name='extra_data', + field=models.TextField(blank=True), + ), + ] diff --git a/passbook/core/migrations/0007_auto_20181226_2142.py b/passbook/core/migrations/0007_auto_20181226_2142.py new file mode 100644 index 000000000..5fd764b35 --- /dev/null +++ b/passbook/core/migrations/0007_auto_20181226_2142.py @@ -0,0 +1,30 @@ +# Generated by Django 2.1.4 on 2018-12-26 21:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passbook_core', '0006_group_extra_data'), + ] + + operations = [ + migrations.AddField( + model_name='group', + name='children', + field=models.ManyToManyField(blank=True, to='passbook_core.Group'), + ), + migrations.AlterUniqueTogether( + name='group', + unique_together=set(), + ), + migrations.RemoveField( + model_name='group', + name='parent_group', + ), + migrations.RemoveField( + model_name='group', + name='permissions', + ), + ] diff --git a/passbook/core/migrations/0008_auto_20181226_2200.py b/passbook/core/migrations/0008_auto_20181226_2200.py new file mode 100644 index 000000000..d820a3089 --- /dev/null +++ b/passbook/core/migrations/0008_auto_20181226_2200.py @@ -0,0 +1,27 @@ +# Generated by Django 2.1.4 on 2018-12-26 22:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passbook_core', '0007_auto_20181226_2142'), + ] + + operations = [ + migrations.AddField( + model_name='group', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group'), + ), + migrations.RemoveField( + model_name='group', + name='children', + ), + migrations.AlterUniqueTogether( + name='group', + unique_together={('name', 'parent')}, + ), + ] diff --git a/passbook/core/models.py b/passbook/core/models.py index ca2e3ff48..ad0991b2c 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -16,13 +16,28 @@ from passbook.lib.models import CreatedUpdatedModel, UUIDModel LOGGER = getLogger(__name__) -@reversion.register() +class Group(UUIDModel): + """Custom Group model which supports a basic hierarchy""" + + name = models.CharField(_('name'), max_length=80) + parent = models.ForeignKey('Group', blank=True, null=True, + on_delete=models.SET_NULL, related_name='children') + extra_data = models.TextField(blank=True) + + def __str__(self): + return "Group %s" % self.name + + class Meta: + + unique_together = (('name', 'parent',),) + class User(AbstractUser): """Custom User model to allow easier adding o f user-based settings""" uuid = models.UUIDField(default=uuid4, editable=False) sources = models.ManyToManyField('Source', through='UserSourceConnection') applications = models.ManyToManyField('Application') + groups = models.ManyToManyField('Group') @reversion.register() class Provider(models.Model): diff --git a/passbook/core/settings.py b/passbook/core/settings.py index 1b6b42bbd..cbe6ea7c2 100644 --- a/passbook/core/settings.py +++ b/passbook/core/settings.py @@ -66,6 +66,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'reversion', 'rest_framework', + 'rest_framework_swagger', 'passbook.core.apps.PassbookCoreConfig', 'passbook.admin.apps.PassbookAdminConfig', 'passbook.api.apps.PassbookAPIConfig', diff --git a/passbook/core/static/css/bootstrap-treeview.min.css b/passbook/core/static/css/bootstrap-treeview.min.css new file mode 100644 index 000000000..9e40d885a --- /dev/null +++ b/passbook/core/static/css/bootstrap-treeview.min.css @@ -0,0 +1 @@ +.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon,.treeview span.image{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}.treeview .node-hidden{display:none}.treeview span.image{display:inline-block;height:1.19em;vertical-align:middle;background-size:contain;background-repeat:no-repeat;line-height:1em}.treeview span.icon.node-icon-background{padding:2px;width:calc(1em + 4px);height:calc(1em + 4px);line-height:1em} \ No newline at end of file diff --git a/passbook/core/static/js/bootstrap-treeview.min.js b/passbook/core/static/js/bootstrap-treeview.min.js new file mode 100644 index 000000000..7e3f2c2f0 --- /dev/null +++ b/passbook/core/static/js/bootstrap-treeview.min.js @@ -0,0 +1 @@ +!function(a,b,c,d){"use strict";var e="treeview",f={};f.settings={injectStyle:!0,levels:2,expandIcon:"glyphicon glyphicon-plus",collapseIcon:"glyphicon glyphicon-minus",loadingIcon:"glyphicon glyphicon-hourglass",emptyIcon:"glyphicon",nodeIcon:"",selectedIcon:"",checkedIcon:"glyphicon glyphicon-check",partiallyCheckedIcon:"glyphicon glyphicon-expand",uncheckedIcon:"glyphicon glyphicon-unchecked",tagsClass:"badge",color:d,backColor:d,borderColor:d,changedNodeColor:"#39A5DC",onhoverColor:"#F5F5F5",selectedColor:"#FFFFFF",selectedBackColor:"#428bca",searchResultColor:"#D9534F",searchResultBackColor:d,highlightSelected:!0,highlightSearchResults:!0,showBorder:!0,showIcon:!0,showImage:!1,showCheckbox:!1,checkboxFirst:!1,highlightChanges:!1,showTags:!1,multiSelect:!1,preventUnselect:!1,allowReselect:!1,hierarchicalCheck:!1,propagateCheckEvent:!1,wrapNodeText:!1,onLoading:d,onLoadingFailed:d,onInitialized:d,onNodeRendered:d,onRendered:d,onDestroyed:d,onNodeChecked:d,onNodeCollapsed:d,onNodeDisabled:d,onNodeEnabled:d,onNodeExpanded:d,onNodeChanged:d,onNodeSelected:d,onNodeUnchecked:d,onNodeUnselected:d,onSearchComplete:d,onSearchCleared:d},f.options={silent:!1,ignoreChildren:!1},f.searchOptions={ignoreCase:!0,exactMatch:!1,revealResults:!0},f.dataUrl={method:"GET",dataType:"json",cache:!1};var g=function(b,c){return this.$element=a(b),this._elementId=b.id,this._styleId=this._elementId+"-style",this._init(c),{options:this._options,init:a.proxy(this._init,this),remove:a.proxy(this._remove,this),findNodes:a.proxy(this.findNodes,this),getNodes:a.proxy(this.getNodes,this),getParents:a.proxy(this.getParents,this),getSiblings:a.proxy(this.getSiblings,this),getSelected:a.proxy(this.getSelected,this),getUnselected:a.proxy(this.getUnselected,this),getExpanded:a.proxy(this.getExpanded,this),getCollapsed:a.proxy(this.getCollapsed,this),getChecked:a.proxy(this.getChecked,this),getUnchecked:a.proxy(this.getUnchecked,this),getDisabled:a.proxy(this.getDisabled,this),getEnabled:a.proxy(this.getEnabled,this),addNode:a.proxy(this.addNode,this),addNodeAfter:a.proxy(this.addNodeAfter,this),addNodeBefore:a.proxy(this.addNodeBefore,this),removeNode:a.proxy(this.removeNode,this),updateNode:a.proxy(this.updateNode,this),selectNode:a.proxy(this.selectNode,this),unselectNode:a.proxy(this.unselectNode,this),toggleNodeSelected:a.proxy(this.toggleNodeSelected,this),collapseAll:a.proxy(this.collapseAll,this),collapseNode:a.proxy(this.collapseNode,this),expandAll:a.proxy(this.expandAll,this),expandNode:a.proxy(this.expandNode,this),toggleNodeExpanded:a.proxy(this.toggleNodeExpanded,this),revealNode:a.proxy(this.revealNode,this),checkAll:a.proxy(this.checkAll,this),checkNode:a.proxy(this.checkNode,this),uncheckAll:a.proxy(this.uncheckAll,this),uncheckNode:a.proxy(this.uncheckNode,this),toggleNodeChecked:a.proxy(this.toggleNodeChecked,this),unmarkCheckboxChanges:a.proxy(this.unmarkCheckboxChanges,this),disableAll:a.proxy(this.disableAll,this),disableNode:a.proxy(this.disableNode,this),enableAll:a.proxy(this.enableAll,this),enableNode:a.proxy(this.enableNode,this),toggleNodeDisabled:a.proxy(this.toggleNodeDisabled,this),search:a.proxy(this.search,this),clearSearch:a.proxy(this.clearSearch,this)}};g.prototype._init=function(b){this._tree=[],this._initialized=!1,this._options=a.extend({},f.settings,b),this._template.icon.empty.addClass(this._options.emptyIcon),this._destroy(),this._subscribeEvents(),this._triggerEvent("loading",null,f.options),this._load(b).then(a.proxy(function(b){return this._tree=a.extend(!0,[],b)},this),a.proxy(function(a){this._triggerEvent("loadingFailed",a,f.options)},this)).then(a.proxy(function(a){return this._setInitialStates({nodes:a},0)},this)).then(a.proxy(function(){this._render()},this))},g.prototype._load=function(b){var c=new a.Deferred;return b.data?this._loadLocalData(b,c):b.dataUrl&&this._loadRemoteData(b,c),c.promise()},g.prototype._loadRemoteData=function(b,c){a.ajax(a.extend(!0,{},f.dataUrl,b.dataUrl)).done(function(a){c.resolve(a)}).fail(function(a,b,d){c.reject(d)})},g.prototype._loadLocalData=function(b,c){c.resolve("string"==typeof b.data?JSON.parse(b.data):a.extend(!0,[],b.data))},g.prototype._remove=function(){this._destroy(),a.removeData(this,e),a("#"+this._styleId).remove()},g.prototype._destroy=function(){this._initialized&&(this._initialized=!1,this._triggerEvent("destroyed",null,f.options),this._unsubscribeEvents(),this.$wrapper.remove(),this.$wrapper=null)},g.prototype._unsubscribeEvents=function(){this.$element.off("loading"),this.$element.off("loadingFailed"),this.$element.off("initialized"),this.$element.off("nodeRendered"),this.$element.off("rendered"),this.$element.off("destroyed"),this.$element.off("click"),this.$element.off("nodeChecked"),this.$element.off("nodeCollapsed"),this.$element.off("nodeDisabled"),this.$element.off("nodeEnabled"),this.$element.off("nodeExpanded"),this.$element.off("nodeChanged"),this.$element.off("nodeSelected"),this.$element.off("nodeUnchecked"),this.$element.off("nodeUnselected"),this.$element.off("searchComplete"),this.$element.off("searchCleared")},g.prototype._subscribeEvents=function(){this._unsubscribeEvents(),"function"==typeof this._options.onLoading&&this.$element.on("loading",this._options.onLoading),"function"==typeof this._options.onLoadingFailed&&this.$element.on("loadingFailed",this._options.onLoadingFailed),"function"==typeof this._options.onInitialized&&this.$element.on("initialized",this._options.onInitialized),"function"==typeof this._options.onNodeRendered&&this.$element.on("nodeRendered",this._options.onNodeRendered),"function"==typeof this._options.onRendered&&this.$element.on("rendered",this._options.onRendered),"function"==typeof this._options.onDestroyed&&this.$element.on("destroyed",this._options.onDestroyed),this.$element.on("click",a.proxy(this._clickHandler,this)),"function"==typeof this._options.onNodeChecked&&this.$element.on("nodeChecked",this._options.onNodeChecked),"function"==typeof this._options.onNodeCollapsed&&this.$element.on("nodeCollapsed",this._options.onNodeCollapsed),"function"==typeof this._options.onNodeDisabled&&this.$element.on("nodeDisabled",this._options.onNodeDisabled),"function"==typeof this._options.onNodeEnabled&&this.$element.on("nodeEnabled",this._options.onNodeEnabled),"function"==typeof this._options.onNodeExpanded&&this.$element.on("nodeExpanded",this._options.onNodeExpanded),"function"==typeof this._options.onNodeChanged&&this.$element.on("nodeChanged",this._options.onNodeChanged),"function"==typeof this._options.onNodeSelected&&this.$element.on("nodeSelected",this._options.onNodeSelected),"function"==typeof this._options.onNodeUnchecked&&this.$element.on("nodeUnchecked",this._options.onNodeUnchecked),"function"==typeof this._options.onNodeUnselected&&this.$element.on("nodeUnselected",this._options.onNodeUnselected),"function"==typeof this._options.onSearchComplete&&this.$element.on("searchComplete",this._options.onSearchComplete),"function"==typeof this._options.onSearchCleared&&this.$element.on("searchCleared",this._options.onSearchCleared)},g.prototype._triggerEvent=function(b,c,d){d&&!d.silent&&this.$element.trigger(b,a.extend(!0,{},c))},g.prototype._setInitialStates=function(b,c){return this._nodes={},a.when.apply(this,this._setInitialState(b,c)).done(a.proxy(function(){this._orderedNodes=this._sortNodes(),this._inheritCheckboxChanges(),this._triggerEvent("initialized",this._orderedNodes,f.options)},this))},g.prototype._setInitialState=function(b,c,e){if(b.nodes){c+=1,e=e||[];var f=b;return a.each(b.nodes,a.proxy(function(b,g){var h=new a.Deferred;e.push(h.promise()),g.level=c,g.index=b,g.nodeId=f&&f.nodeId?f.nodeId+"."+g.index:c-1+"."+g.index,g.parentId=f.nodeId,g.hasOwnProperty("selectable")||(g.selectable=!0),g.hasOwnProperty("checkable")||(g.checkable=!0),g.state=g.state||{},g.state.hasOwnProperty("checked")||(g.state.checked=!1),this._options.hierarchicalCheck&&"undefined"===g.state.checked&&(g.state.checked=d),g.state.hasOwnProperty("disabled")||(g.state.disabled=!1),g.state.hasOwnProperty("expanded")||(!g.state.disabled&&c0?g.state.expanded=!0:g.state.expanded=!1),g.state.hasOwnProperty("selected")||(g.state.selected=!1),f&&f.state&&f.state.expanded||c<=this._options.levels?g.state.visible=!0:g.state.visible=!1,g.nodes&&(g.nodes.length>0?this._setInitialState(g,c,e):delete g.nodes),this._nodes[g.nodeId]=g,h.resolve()},this)),e}},g.prototype._sortNodes=function(){return a.map(Object.keys(this._nodes).sort(function(a,b){if(a===b)return 0;for(var a=a.split(".").map(function(a){return parseInt(a)}),b=b.split(".").map(function(a){return parseInt(a)}),c=Math.max(a.length,b.length),e=0;e0)return 1;if(a[e]-b[e]<0)return-1}}),a.proxy(function(a,b){return this._nodes[a]},this))},g.prototype._clickHandler=function(b){var c=a(b.target),d=this.targetNode(c);if(d&&!d.state.disabled){var e=c.attr("class")?c.attr("class").split(" "):[];e.indexOf("expand-icon")!==-1?this._toggleExpanded(d,a.extend({},f.options)):e.indexOf("check-icon")!==-1?d.checkable&&this._toggleChecked(d,a.extend({},f.options)):d.selectable?this._toggleSelected(d,a.extend({},f.options)):this._toggleExpanded(d,a.extend({},f.options))}},g.prototype.targetNode=function(a){var b=a.closest("li.list-group-item").attr("data-nodeId"),c=this._nodes[b];return c||console.log("Error: node does not exist"),c},g.prototype._toggleExpanded=function(a,b){a&&("function"==typeof this._options.lazyLoad&&a.lazyLoad?this._lazyLoad(a):this._setExpanded(a,!a.state.expanded,b))},g.prototype._lazyLoad=function(a){a.$el.children("span.expand-icon").removeClass(this._options.expandIcon).addClass(this._options.loadingIcon);var b=this;this._options.lazyLoad(a,function(c){b.addNode(c,a)}),delete a.lazyLoad},g.prototype._setExpanded=function(b,c,d){d&&c===b.state.expanded||(c&&b.nodes?(b.state.expanded=!0,b.$el&&b.$el.children("span.expand-icon").removeClass(this._options.expandIcon).removeClass(this._options.loadingIcon).addClass(this._options.collapseIcon),b.nodes&&d&&a.each(b.nodes,a.proxy(function(a,b){this._setVisible(b,!0,d)},this)),this._triggerEvent("nodeExpanded",b,d)):c||(b.state.expanded=!1,b.$el&&b.$el.children("span.expand-icon").removeClass(this._options.collapseIcon).addClass(this._options.expandIcon),b.nodes&&d&&a.each(b.nodes,a.proxy(function(a,b){this._setVisible(b,!1,d),this._setExpanded(b,!1,d)},this)),this._triggerEvent("nodeCollapsed",b,d)))},g.prototype._setVisible=function(a,b,c){c&&b===a.state.visible||(b?(a.state.visible=!0,a.$el&&a.$el.removeClass("node-hidden")):(a.state.visible=!1,a.$el&&a.$el.addClass("node-hidden")))},g.prototype._toggleSelected=function(a,b){if(a)return this._setSelected(a,!a.state.selected,b),this},g.prototype._setSelected=function(b,c,d,e){if(!d||c!==b.state.selected){if(c)this._options.multiSelect||a.each(this._findNodes("true","state.selected"),a.proxy(function(b,c){this._setSelected(c,!1,a.extend(d,{unselecting:!0}),!0)},this)),b.state.selected=!0,b.$el&&(b.$el.addClass("node-selected"),(b.selectedIcon||this._options.selectedIcon)&&b.$el.children("span.node-icon").removeClass(b.icon||this._options.nodeIcon).addClass(b.selectedIcon||this._options.selectedIcon)),this._triggerEvent("nodeSelected",b,d),this._triggerEvent("nodeChanged",b,d);else{if(this._options.preventUnselect&&d&&!d.unselecting&&1===this._findNodes("true","state.selected").length)return this._options.allowReselect&&(this._triggerEvent("nodeSelected",b,d),this._triggerEvent("nodeChanged",b,d)),this;b.state.selected=!1,b.$el&&(b.$el.removeClass("node-selected"),(b.selectedIcon||this._options.selectedIcon)&&b.$el.children("span.node-icon").removeClass(b.selectedIcon||this._options.selectedIcon).addClass(b.icon||this._options.nodeIcon)),this._triggerEvent("nodeUnselected",b,d),e||this._triggerEvent("nodeChanged",b,d)}return this}},g.prototype._inheritCheckboxChanges=function(){this._options.showCheckbox&&this._options.highlightChanges&&(this._checkedNodes=a.grep(this._orderedNodes,function(a){return a.state.checked}))},g.prototype._toggleChecked=function(b,c){if(b){if(this._options.hierarchicalCheck){var e,f=a.extend({},c,{silent:c.silent||!this._options.propagateCheckEvent}),g=b;for(b.state.checked=!b.state.checked;g=this._nodes[g.parentId];)e=g.nodes.reduce(function(a,b){return a===b.state.checked?a:d},g.nodes[0].state.checked),this._setChecked(g,e,f);if(b.nodes&&b.nodes.length>0)for(var h,i=b.nodes.slice();i&&i.length>0;)h=i.pop(),this._setChecked(h,b.state.checked,f),h.nodes&&h.nodes.length>0&&(i=i.concat(h.nodes.slice()));b.state.checked=!b.state.checked}this._setChecked(b,!b.state.checked,c)}},g.prototype._setChecked=function(a,b,c){c&&b===a.state.checked||(this._options.highlightChanges&&a.$el.toggleClass("node-check-changed",this._checkedNodes.indexOf(a)==-1==b),b?(a.state.checked=!0,a.$el&&(a.$el.addClass("node-checked").removeClass("node-checked-partial"),a.$el.children("span.check-icon").removeClass(this._options.uncheckedIcon).removeClass(this._options.partiallyCheckedIcon).addClass(this._options.checkedIcon)),this._triggerEvent("nodeChecked",a,c)):b===d&&this._options.hierarchicalCheck?(a.state.checked=d,a.$el&&(a.$el.addClass("node-checked-partial").removeClass("node-checked"),a.$el.children("span.check-icon").removeClass(this._options.uncheckedIcon).removeClass(this._options.checkedIcon).addClass(this._options.partiallyCheckedIcon)),this._triggerEvent("nodeUnchecked",a,c)):(a.state.checked=!1,a.$el&&(a.$el.removeClass("node-checked node-checked-partial"),a.$el.children("span.check-icon").removeClass(this._options.checkedIcon).removeClass(this._options.partiallyCheckedIcon).addClass(this._options.uncheckedIcon)),this._triggerEvent("nodeUnchecked",a,c)))},g.prototype._setDisabled=function(a,b,c){c&&b===a.state.disabled||(b?(a.state.disabled=!0,c&&!c.keepState&&(this._setSelected(a,!1,c),this._setChecked(a,!1,c),this._setExpanded(a,!1,c)),a.$el&&a.$el.addClass("node-disabled"),this._triggerEvent("nodeDisabled",a,c)):(a.state.disabled=!1,a.$el&&a.$el.removeClass("node-disabled"),this._triggerEvent("nodeEnabled",a,c)))},g.prototype._setSearchResult=function(a,b,c){c&&b===a.searchResult||(b?(a.searchResult=!0,a.$el&&a.$el.addClass("node-result")):(a.searchResult=!1,a.$el&&a.$el.removeClass("node-result")))},g.prototype._render=function(){this._initialized||(this.$wrapper=this._template.tree.clone(),this.$element.empty().addClass(e).append(this.$wrapper),this._injectStyle(),this._initialized=!0);var b;a.each(this._orderedNodes,a.proxy(function(a,c){this._renderNode(c,b),b=c},this)),this._triggerEvent("rendered",this._orderedNodes,f.options)},g.prototype._renderNode=function(b,c){if(b){b.$el?b.$el.empty():b.$el=this._newNodeEl(b,c).addClass("node-"+this._elementId),b.$el.addClass(b["class"]),b.id&&b.$el.attr("id",b.id),b.dataAttr&&a.each(b.dataAttr,function(a,c){b.$el.attr("data-"+a,c)}),b.$el.attr("data-nodeId",b.nodeId),b.tooltip&&b.$el.attr("title",b.tooltip);for(var e=0;e '+this._buildStyle()+" ").appendTo("head")},g.prototype._buildStyle=function(){var b=".node-"+this._elementId+"{";if(this._options.color&&(b+="color:"+this._options.color+";"),this._options.backColor&&(b+="background-color:"+this._options.backColor+";"),this._options.showBorder?this._options.borderColor&&(b+="border:1px solid "+this._options.borderColor+";"):b+="border:none;",b+="}",this._options.onhoverColor&&(b+=".node-"+this._elementId+":not(.node-disabled):hover{background-color:"+this._options.onhoverColor+";}"),this._options.highlightSearchResults&&(this._options.searchResultColor||this._options.searchResultBackColor)){var c="";this._options.searchResultColor&&(c+="color:"+this._options.searchResultColor+";"),this._options.searchResultBackColor&&(c+="background-color:"+this._options.searchResultBackColor+";"),b+=".node-"+this._elementId+".node-result{"+c+"}",b+=".node-"+this._elementId+".node-result:hover{"+c+"}"}if(this._options.highlightSelected&&(this._options.selectedColor||this._options.selectedBackColor)){var c="";this._options.selectedColor&&(c+="color:"+this._options.selectedColor+";"),this._options.selectedBackColor&&(c+="background-color:"+this._options.selectedBackColor+";"),b+=".node-"+this._elementId+".node-selected{"+c+"}",b+=".node-"+this._elementId+".node-selected:hover{"+c+"}"}if(this._options.highlightChanges){var c="color: "+this._options.changedNodeColor+";";b+=".node-"+this._elementId+".node-check-changed{"+c+"}"}return a.each(this._orderedNodes,a.proxy(function(a,c){if(c.color||c.backColor){var d="";c.color&&(d+="color:"+c.color+";"),c.backColor&&(d+="background-color:"+c.backColor+";"),b+=".node-"+this._elementId+'[data-nodeId="'+c.nodeId+'"]{'+d+"}"}},this)),this._css+b},g.prototype._template={tree:a('
    '),node:a('
  • '),indent:a(''),icon:{node:a(''),expand:a(''),check:a(''),empty:a('')},image:a(''),badge:a(""),text:a('')},g.prototype._css=".treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}",g.prototype.findNodes=function(a,b,c){return this._findNodes(a,b,c)},g.prototype.getNodes=function(){return this._orderedNodes},g.prototype.getParents=function(b){b instanceof Array||(b=[b]);var c=[];return a.each(b,a.proxy(function(a,b){var d=!!b.parentId&&this._nodes[b.parentId];d&&c.push(d)},this)),c},g.prototype.getSiblings=function(b){b instanceof Array||(b=[b]);var c=[];return a.each(b,a.proxy(function(a,b){var d=this.getParents([b]),e=d[0]?d[0].nodes:this._tree;c=e.filter(function(a){return a.nodeId!==b.nodeId})},this)),a.map(c,function(a){return a})},g.prototype.getSelected=function(){return this._findNodes("true","state.selected")},g.prototype.getUnselected=function(){return this._findNodes("false","state.selected")},g.prototype.getExpanded=function(){return this._findNodes("true","state.expanded")},g.prototype.getCollapsed=function(){return this._findNodes("false","state.expanded")},g.prototype.getChecked=function(){return this._findNodes("true","state.checked")},g.prototype.getUnchecked=function(){return this._findNodes("false","state.checked")},g.prototype.getDisabled=function(){return this._findNodes("true","state.disabled")},g.prototype.getEnabled=function(){return this._findNodes("false","state.disabled")},g.prototype.addNode=function(b,c,d,e){b instanceof Array||(b=[b]),c instanceof Array&&(c=c[0]),e=a.extend({},f.options,e);var g;g=c&&c.nodes?c.nodes:c?c.nodes=[]:this._tree,a.each(b,a.proxy(function(a,b){var c="number"==typeof d?d+a:g.length+1;g.splice(c,0,b)},this)),this._setInitialStates({nodes:this._tree},0).done(a.proxy(function(){c&&!c.state.expanded&&this._setExpanded(c,!0,e),this._render()},this))},g.prototype.addNodeAfter=function(b,c,d){b instanceof Array||(b=[b]),c instanceof Array&&(c=c[0]),d=a.extend({},f.options,d),this.addNode(b,this.getParents(c)[0],c.index+1,d)},g.prototype.addNodeBefore=function(b,c,d){b instanceof Array||(b=[b]),c instanceof Array&&(c=c[0]),d=a.extend({},f.options,d),this.addNode(b,this.getParents(c)[0],c.index,d)},g.prototype.removeNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c);var d,e;a.each(b,a.proxy(function(a,b){e=this._nodes[b.parentId],d=e?e.nodes:this._tree,d.splice(b.index,1),this._removeNodeEl(b)},this)),this._setInitialStates({nodes:this._tree},0).done(this._render.bind(this))},g.prototype.updateNode=function(b,c,d){b instanceof Array&&(b=b[0]),d=a.extend({},f.options,d);var e,g=this._nodes[b.parentId];e=g?g.nodes:this._tree,e.splice(b.index,1,c),this._removeNodeEl(b),this._setInitialStates({nodes:this._tree},0).done(this._render.bind(this))},g.prototype.selectNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setSelected(b,!0,c)},this))},g.prototype.unselectNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setSelected(b,!1,c)},this))},g.prototype.toggleNodeSelected=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._toggleSelected(b,c)},this))},g.prototype.collapseAll=function(b){b=a.extend({},f.options,b),b.levels=b.levels||999,this.collapseNode(this._tree,b)},g.prototype.collapseNode=function(b,c){c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setExpanded(b,!1,c)},this))},g.prototype.expandAll=function(b){b=a.extend({},f.options,b),b.levels=b.levels||999,this.expandNode(this._tree,b)},g.prototype.expandNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){b.state.expanded||("function"==typeof this._options.lazyLoad&&b.lazyLoad&&this._lazyLoad(b),this._setExpanded(b,!0,c),b.nodes&&this._expandLevels(b.nodes,c.levels-1,c))},this))},g.prototype._expandLevels=function(b,c,d){b instanceof Array||(b=[b]),d=a.extend({},f.options,d),a.each(b,a.proxy(function(a,b){this._setExpanded(b,c>0,d),b.nodes&&this._expandLevels(b.nodes,c-1,d)},this))},g.prototype.revealNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){for(var d,e=b;d=this.getParents([e])[0];)e=d,this._setExpanded(e,!0,c)},this))},g.prototype.toggleNodeExpanded=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._toggleExpanded(b,c)},this))},g.prototype.checkAll=function(b){b=a.extend({},f.options,b);var c=a.grep(this._orderedNodes,function(a){return!a.state.checked});a.each(c,a.proxy(function(a,c){this._setChecked(c,!0,b)},this))},g.prototype.checkNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setChecked(b,!0,c)},this))},g.prototype.uncheckAll=function(b){b=a.extend({},f.options,b);var c=a.grep(this._orderedNodes,function(a){return a.state.checked||a.state.checked===d});a.each(c,a.proxy(function(a,c){this._setChecked(c,!1,b)},this))},g.prototype.uncheckNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setChecked(b,!1,c)},this))},g.prototype.toggleNodeChecked=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._toggleChecked(b,c)},this))},g.prototype.unmarkCheckboxChanges=function(){this._inheritCheckboxChanges(),a.each(this._nodes,function(a,b){b.$el.removeClass("node-check-changed")})},g.prototype.disableAll=function(b){b=a.extend({},f.options,b);var c=this._findNodes("false","state.disabled");a.each(c,a.proxy(function(a,c){this._setDisabled(c,!0,b)},this))},g.prototype.disableNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setDisabled(b,!0,c)},this))},g.prototype.enableAll=function(b){b=a.extend({},f.options,b);var c=this._findNodes("true","state.disabled");a.each(c,a.proxy(function(a,c){this._setDisabled(c,!1,b)},this))},g.prototype.enableNode=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setDisabled(b,!1,c)},this))},g.prototype.toggleNodeDisabled=function(b,c){b instanceof Array||(b=[b]),c=a.extend({},f.options,c),a.each(b,a.proxy(function(a,b){this._setDisabled(b,!b.state.disabled,c)},this))},g.prototype.search=function(b,c){c=a.extend({},f.searchOptions,c);var d=this._getSearchResults(),e=[];if(b&&b.length>0){c.exactMatch&&(b="^"+b+"$");var g="g";c.ignoreCase&&(g+="i"),e=this._findNodes(b,"text",g)}return a.each(this._diffArray(e,d),a.proxy(function(a,b){this._setSearchResult(b,!1,c)},this)),a.each(this._diffArray(d,e),a.proxy(function(a,b){this._setSearchResult(b,!0,c)},this)),e&&c.revealResults&&this.revealNode(e),this._triggerEvent("searchComplete",e,c),e},g.prototype.clearSearch=function(b){b=a.extend({},{render:!0},b);var c=a.each(this._getSearchResults(),a.proxy(function(a,c){this._setSearchResult(c,!1,b)},this));this._triggerEvent("searchCleared",c,b)},g.prototype._getSearchResults=function(){return this._findNodes("true","searchResult")},g.prototype._diffArray=function(b,c){var d=[];return a.grep(c,function(c){a.inArray(c,b)===-1&&d.push(c)}),d},g.prototype._findNodes=function(b,c,d){return c=c||"text",d=d||"g",a.grep(this._orderedNodes,a.proxy(function(a){var e=this._getNodeValue(a,c);if("string"==typeof e)return e.match(new RegExp(b,d))},this))},g.prototype._getNodeValue=function(a,b){var c=b.indexOf(".");if(c>0){var e=a[b.substring(0,c)],f=b.substring(c+1,b.length);return this._getNodeValue(e,f)}return a.hasOwnProperty(b)&&a[b]!==d?a[b].toString():d};var h=function(a){b.console&&b.console.error(a)};a.fn[e]=function(b,c){var d;if(0==this.length)throw"No element has been found!";return this.each(function(){var f=a.data(this,e);"string"==typeof b?f?a.isFunction(f[b])&&"_"!==b.charAt(0)?(c instanceof Array||(c=[c]),d=f[b].apply(f,c)):h("No such method : "+b):h("Not initialized, can not call method : "+b):"boolean"==typeof b?d=f:a.data(this,e,new g(this,a.extend(!0,{},b)))}),d||this}}(jQuery,window,document); \ No newline at end of file