diff --git a/apps/assets/forms.py b/apps/assets/forms.py
index 4b2040e5f..1eb7d7c18 100644
--- a/apps/assets/forms.py
+++ b/apps/assets/forms.py
@@ -5,20 +5,42 @@ from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser
from django.utils.translation import gettext_lazy as _
-class AssetForm(forms.ModelForm):
+# class AssetForm(forms.ModelForm):
+# class Meta:
+# model = Asset
+#
+# fields = [
+# 'ip', 'other_ip', 'remote_card_ip', 'hostname', 'port', 'groups', 'username', 'password',
+# 'idc', 'mac_address', 'brand', 'cpu', 'memory', 'disk', 'os', 'cabinet_no', 'cabinet_pos',
+# 'number', 'status', 'type', 'env', 'sn', 'is_active', 'comment', 'admin_user', 'system_users'
+# ]
+#
+# widgets = {
+# 'groups': forms.SelectMultiple(attrs={'class': 'select2-groups', 'data-placeholder': _('Select asset groups')}),
+# 'system_user': forms.SelectMultiple(attrs={'class': 'select2-system-user', 'data-placeholder': _('Select asset system user')}),
+# 'admin_user': forms.SelectMultiple(attrs={'class': 'select2-admin-user', 'data-placeholder': _('Select asset admin user')}),
+ # }
+#
+
+class AssetCreateForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
- "ip", "other_ip", "remote_card_ip", "hostname", "port", "groups", "username", "password",
- "idc", "mac_address", "brand", "cpu", "memory", "disk", "os", "cabinet_no", "cabinet_pos",
- "number", "status", "type", "env", "sn", "is_active", "comment", "admin_user", "system_users"
+ 'hostname', 'ip', 'port', 'type', 'zone', 'comment', 'admin_user', 'system_users', 'idc', 'groups'
]
widgets = {
- 'groups': forms.SelectMultiple(attrs={'class': 'select2-groups', 'data-placeholder': _('Select asset groups')}),
- 'system_user': forms.SelectMultiple(attrs={'class': 'select2-system-user', 'data-placeholder': _('Select asset system user')}),
- # 'admin_user': forms.SelectMultiple(attrs={'class': 'select2-admin-user', 'data-placeholder': _('Select asset admin user')}),
+ 'groups': forms.SelectMultiple(attrs={'class': 'select2',
+ 'data-placeholder': _('Select asset groups')}),
+ 'system_users': forms.SelectMultiple(attrs={'class': 'select2',
+ 'data-placeholder': _('Select asset system users')}),
+ 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select asset admin user')}),
+ }
+ help_texts = {
+ 'hostname': '* required',
+ 'ip': '* required',
+ 'type': '* required',
}
diff --git a/apps/assets/models.py b/apps/assets/models.py
index 2d66992a3..56dc90a08 100644
--- a/apps/assets/models.py
+++ b/apps/assets/models.py
@@ -271,38 +271,38 @@ class AssetGroup(models.Model):
class Asset(models.Model):
- ip = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('IP'))
+ ip = models.CharField(max_length=32, verbose_name=_('IP'))
other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP'))
- hostname = models.CharField(max_length=128, unique=True, null=True, blank=True, verbose_name=_('Hostname'))
- port = models.IntegerField(default=22, null=True, blank=True, verbose_name=_('Port'))
+ hostname = models.CharField(max_length=128, blank=True, verbose_name=_('Hostname'))
+ port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
- username = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Admin user'))
- password = models.CharField(max_length=256, null=True, blank=True, verbose_name=_("Admin password"))
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User"))
idc = models.ForeignKey(IDC, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('IDC'))
mac_address = models.CharField(max_length=20, null=True, blank=True, verbose_name=_("Mac address"))
brand = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Brand'))
- cpu = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU'))
+ cpu = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU'))
memory = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Memory'))
disk = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
- status = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="asset_status_extend",
+ status = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="status_asset",
verbose_name=_('Asset status'))
- type = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="asset_type_extend",
+ type = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="type_asset",
verbose_name=_('Asset type'))
- env = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="asset_env_extend",
+ env = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="env_asset",
verbose_name=_('Asset environment'))
+ zone = models.ForeignKey(AssetExtend, null=True, blank=True, related_name="zone_asset",
+ verbose_name=_('Asset zone'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
date_created = models.DateTimeField(auto_now=True, null=True, blank=True, verbose_name=_('Date added'))
- comment = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Comment'))
+ comment = models.TextField(max_length=128, null=True, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port}
@@ -322,8 +322,8 @@ class Asset(models.Model):
seed()
for i in range(count):
- asset = cls(ip='%s.%s.%s.%s' % tuple([forgery_py.forgery.basic.text(length=3, digits=True)
- for i in range(0, 4)]),
+ asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
+ hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
idc=choice(IDC.objects.all()),
port=22,
@@ -338,13 +338,12 @@ class Asset(models.Model):
continue
-class Label(models.Model):
- key = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('KEY'))
- value = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('VALUE'))
+class Tag(models.Model):
+ key = models.CharField(max_length=64, blank=True, verbose_name=_('KEY'))
+ value = models.CharField(max_length=64, verbose_name=_('VALUE'))
asset = models.ForeignKey(Asset, null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_('Asset'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by"))
date_created = models.DateTimeField(auto_now=True, null=True)
- comment = models.CharField(max_length=128, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return self.key
diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html
index 2031dfa49..ed41a5cfe 100644
--- a/apps/assets/templates/assets/admin_user_list.html
+++ b/apps/assets/templates/assets/admin_user_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html
deleted file mode 100644
index f47cfb880..000000000
--- a/apps/assets/templates/assets/asset_create.html
+++ /dev/null
@@ -1,109 +0,0 @@
-{% extends 'base.html' %}
-{% load static %}
-{% load bootstrap %}
-{% block custom_head_css_js %}
-
-
-{% endblock %}
-{% block content %}
-
-{% endblock %}
-
-{% block custom_foot_js %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/asset_create_update.html b/apps/assets/templates/assets/asset_create_update.html
new file mode 100644
index 000000000..23351e8d9
--- /dev/null
+++ b/apps/assets/templates/assets/asset_create_update.html
@@ -0,0 +1,59 @@
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap %}
+{% load i18n %}
+
+{% block custom_head_css_js_create %}
+
+
+{% endblock %}
+
+{% block form %}
+
+{% endblock %}
+
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/asset_group_list.html b/apps/assets/templates/assets/asset_group_list.html
index c945127cd..ecaf13cd8 100644
--- a/apps/assets/templates/assets/asset_group_list.html
+++ b/apps/assets/templates/assets/asset_group_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index ca3feca9b..71d826010 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -1,431 +1,71 @@
-{% extends 'base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
-{% block content %}
+{% load common_tags %}
+{% block content_left_head %}
+ {% trans "Create asset" %}
+{% endblock %}
-
-
-
+
{% endblock %}
-{% block self_footer_js %}
-
-
-{% endblock %}
diff --git a/apps/assets/templates/assets/idc_list.html b/apps/assets/templates/assets/idc_list.html
index 3ce6f601b..94d131732 100644
--- a/apps/assets/templates/assets/idc_list.html
+++ b/apps/assets/templates/assets/idc_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 2a9b04178..fb1e41323 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/assets/views.py b/apps/assets/views.py
index 48503757d..33b19e12a 100644
--- a/apps/assets/views.py
+++ b/apps/assets/views.py
@@ -10,15 +10,30 @@ from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin
-from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser, Label
-from .forms import AssetForm, AssetGroupForm, IDCForm, AdminUserForm, SystemUserForm
+from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser, Tag
+from .forms import AssetCreateForm, AssetGroupForm, IDCForm, AdminUserForm, SystemUserForm
from .hands import AdminUserRequiredMixin
+class AssetListView(ListView):
+ paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
+ model = Asset
+ context_object_name = 'asset_list'
+ template_name = 'assets/asset_list.html'
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': 'Assets',
+ 'action': 'Asset list',
+ }
+ kwargs.update(context)
+ return super(AssetListView, self).get_context_data(**kwargs)
+
+
class AssetCreateView(AdminUserRequiredMixin, CreateView):
model = Asset
- form_class = AssetForm
- template_name = 'assets/asset_create.html'
+ form_class = AssetCreateForm
+ template_name = 'assets/asset_create_update.html'
success_url = reverse_lazy('assets:asset-list')
def form_valid(self, form):
@@ -26,14 +41,15 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView):
key = self.request.POST.get('key', '')
value = self.request.POST.get('value', '')
asset.save()
- Label.objects.create(key=key, value=value, asset=asset)
return super(AssetCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
- context = super(AssetCreateView, self).get_context_data(**kwargs)
- context.update({'admin_users': AdminUser.objects.all()})
- assert isinstance(context, object)
- return context
+ context = {
+ 'app': 'Assets',
+ 'action': 'Create asset',
+ }
+ kwargs.update(context)
+ return super(AssetCreateView, self).get_context_data(**kwargs)
class AssetUpdateView(UpdateView):
@@ -45,12 +61,6 @@ class AssetDeleteView(DeleteView):
success_url = reverse_lazy('assets:asset-list')
-class AssetListView(ListView):
- model = Asset
- context_object_name = 'assets'
- template_name = 'assets/asset_list.html'
-
-
class AssetDetailView(DetailView):
model = Asset
context_object_name = 'asset'
diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html
index d6e30a35b..6332e2e6d 100644
--- a/apps/perms/templates/perms/asset_permission_list.html
+++ b/apps/perms/templates/perms/asset_permission_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/static/css/plugins/inputTags.css b/apps/static/css/plugins/inputTags.css
new file mode 100644
index 000000000..89f953f01
--- /dev/null
+++ b/apps/static/css/plugins/inputTags.css
@@ -0,0 +1,214 @@
+@import url("https://fonts.useso.com/css?family=Open+Sans:300,400,600,700");
+@import url("https://fonts.useso.com/css?family=Roboto:400,300,500,700");
+/** {*/
+ /*box-sizing: border-box;*/
+/*}*/
+/*.color {*/
+ /*color: #19BC9C !important;*/
+/*}*/
+/*html,*/
+/*body {*/
+ /*width: 100%;*/
+ /*height: 100%;*/
+ /*margin: 0;*/
+ /*font-family: 'Ubuntu Condensed', sans-serif;*/
+ /*background-color: #fff;*/
+/*}*/
+/*h1 {*/
+ /*text-align: center;*/
+ /*margin-bottom: 32px;*/
+/*}*/
+div.container {
+ display: block;
+ width: 50%;
+ margin: 32px auto;
+ padding: 16px 32px;
+ background-color: #fff;
+ box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.1);
+}
+div.inputTags-list {
+ display: inline-block;
+ width: 100%;
+ padding: 6px;
+ border: 1px solid rgba(25, 188, 156, 0.35);
+ /*background-color: #f9f9f9;*/
+ box-shadow: 1px 2px 2px rgba(221, 221, 221, 0.2);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+}
+div.inputTags-list span.inputTags-item {
+ position: relative;
+ display: inline-block;
+ margin: 2px;
+ padding: 3px 22px 4px 8px;
+ background-color: #19BC9C;
+ text-align: center;
+ color: #fff;
+ opacity: 1;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+}
+div.inputTags-list span.inputTags-item.is-edit {
+ display: none;
+}
+div.inputTags-list span.inputTags-item.is-hidden {
+ display: none !important;
+}
+div.inputTags-list span.inputTags-item.is-exists {
+ background-color: rgba(231, 76, 60, 0.7);
+}
+div.inputTags-list span.inputTags-item span.value {
+ cursor: pointer;
+}
+div.inputTags-list span.inputTags-item i {
+ position: absolute;
+ top: 50%;
+ right: 6px;
+ font-size: 20px;
+ cursor: pointer;
+ z-index: 10;
+ font-weight: 400;
+ font-family: sans-serif;
+ line-height: 1;
+ font-style: normal;
+ -webkit-transition: color 0.2s;
+ -khtml-transition: color 0.2s;
+ -moz-transition: color 0.2s;
+ -ms-transition: color 0.2s;
+ -o-transition: color 0.2s;
+ -webkit-transform: translateY(-50%);
+ -khtml-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ -o-transform: translateY(-50%);
+}
+div.inputTags-list span.inputTags-item i:hover {
+ color: #e74c3c;
+}
+div.inputTags-list input.inputTags-field {
+ border: none;
+ margin-left: 4px;
+ background-color: transparent;
+}
+div.inputTags-list input.inputTags-field:focus,
+div.inputTags-list input.inputTags-field:active {
+ outline: none;
+}
+div.inputTags-list input.inputTags-field.is-edit {
+ margin: 0 2px;
+ padding: 4px 8px 3px 8px;
+ border: 1px dashed #c4c4c4;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+}
+div.inputTags-list ul.inputTags-autocomplete-list {
+ position: absolute;
+ max-height: 192px;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ overflow-y: auto;
+ z-index: 100;
+ opacity: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ -webkit-transform: scaleY(0);
+ -khtml-transform: scaleY(0);
+ -moz-transform: scaleY(0);
+ -ms-transform: scaleY(0);
+ -o-transform: scaleY(0);
+ -webkit-transform-origin: 50% 0;
+ -khtml-transform-origin: 50% 0;
+ -moz-transform-origin: 50% 0;
+ -ms-transform-origin: 50% 0;
+ -o-transform-origin: 50% 0;
+ -webkit-transition-duration: 0.2s;
+ -khtml-transition-duration: 0.2s;
+ -moz-transition-duration: 0.2s;
+ -ms-transition-duration: 0.2s;
+ -o-transition-duration: 0.2s;
+}
+div.inputTags-list ul.inputTags-autocomplete-list.is-active {
+ opacity: 1;
+ -webkit-transform: scaleY(1);
+ -khtml-transform: scaleY(1);
+ -moz-transform: scaleY(1);
+ -ms-transform: scaleY(1);
+ -o-transform: scaleY(1);
+}
+div.inputTags-list ul.inputTags-autocomplete-list li {
+ height: 32px;
+ line-height: 32px;
+ padding: 0 16px;
+ cursor: pointer;
+ border-bottom: 1px solid #ddd;
+ -webkit-transition-duration: 0.3s;
+ -khtml-transition-duration: 0.3s;
+ -moz-transition-duration: 0.3s;
+ -ms-transition-duration: 0.3s;
+ -o-transition-duration: 0.3s;
+ -webkit-transition-duration: 0.2s;
+ -khtml-transition-duration: 0.2s;
+ -moz-transition-duration: 0.2s;
+ -ms-transition-duration: 0.2s;
+ -o-transition-duration: 0.2s;
+}
+div.inputTags-list ul.inputTags-autocomplete-list li:last-child {
+ border: none;
+}
+div.inputTags-list ul.inputTags-autocomplete-list li:hover {
+ background-color: #19BC9C;
+ color: #fff;
+}
+div.inputTags-list ul.inputTags-autocomplete-list li.is-disabled {
+ cursor: default;
+ background-color: #f7f7f7;
+ color: initial;
+}
+p.inputTags-error {
+ position: relative;
+ margin: 0;
+ padding: 0.5em 1em;
+ color: #fff;
+ background-color: rgba(231, 76, 60, 0.7);
+ cursor: pointer;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+}
+p.inputTags-error:first-of-type {
+ margin-top: 8px;
+}
+p.inputTags-error:after {
+ position: absolute;
+ content: "\000D7";
+ top: 50%;
+ right: 0.5em;
+ -webkit-transform: translateY(-50%);
+ -khtml-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ -o-transform: translateY(-50%);
+ font-size: 28px;
+}
diff --git a/apps/static/js/plugins/inputTags.jquery.min.js b/apps/static/js/plugins/inputTags.jquery.min.js
new file mode 100644
index 000000000..db89547c2
--- /dev/null
+++ b/apps/static/js/plugins/inputTags.jquery.min.js
@@ -0,0 +1 @@
+!function(a){a.fn.inputTags=function(b){if("inputTags"in window||(window.inputTags={instances:[]}),window.inputTags.methods={tags:function(a,b){if(a){switch(typeof a){case"string":switch(a){case"_toString":var c=d.tags.toString();return b?b(c):c;case"_toObject":var e=d._toObject(d.tags);return b?b(e):e;case"_toJSON":var e=d._toObject(d.tags),f=JSON.stringify(e);return b?b(f):f;case"_toArray":return b?b(d.tags):d.tags}var g=a.split(",");if(g.length>1){var h=d.tags;d.tags=h.concat(g)}else d.tags.push(g[0]);break;case"object":var h=d.tags;"[object Object]"===Object.prototype.toString.call(a)&&(a=Object.keys(a).map(function(b){return a[b]})),d.tags=h.concat(a);break;case"function":return a(d.tags)}if(d._clean(),d._fill(),d._updateValue(),d.destroy(),d._setInstance(d),b)return b(d.tags)}return d.tags},event:function(a,b){d.options[a]=b,d._setInstance(d)},options:function(a,b){return a||b?b?(d.options[a]=b,void d._setInstance(d)):d.options[a]:d.options},destroy:function(){var b=a(this).attr("data-uniqid");delete window.inputTags.instances[b]}},"object"==typeof b||!b){var b=a.extend(!0,{},a.fn.inputTags.defaults,b);this.each(function(){var c=a(this);c.UNIQID=Math.round(Date.now()/(494*Math.random()-54)),c.DEFAULT_CLASS="inputTags",c.ELEMENT_CLASS=c.DEFAULT_CLASS+"-"+c.UNIQID,c.LIST_CLASS=c.DEFAULT_CLASS+"-list",c.ITEM_CLASS=c.DEFAULT_CLASS+"-item",c.ITEM_CONTENT='
%s × ',c.FIELD_CLASS=c.DEFAULT_CLASS+"-field",c.ERROR_CLASS=c.DEFAULT_CLASS+"-error",c.ERROR_CONTENT='
%s
',c.AUTOCOMPLETE_LIST_CLASS=c.DEFAULT_CLASS+"-autocomplete-list",c.AUTOCOMPLETE_ITEM_CLASS=c.DEFAULT_CLASS+"-autocomplete-item",c.AUTOCOMPLETE_ITEM_CONTENT='
%s ',c.options=b,c.keys=[13,188,27],c.tags=[],c.options.keys.length>0&&(c.keys=c.keys.concat(c.options.keys)),c.init=function(){c.addClass(c.ELEMENT_CLASS).attr("data-uniqid",c.UNIQID),c.$element=a("."+c.ELEMENT_CLASS),c.$element.hide(),c.build(),c.fill(),c.save(),c.edit(),c.destroy(),c._autocomplete()._init(),c._focus()},c.build=function(){c.$html=a("
").addClass(c.LIST_CLASS),c.$input=a("
").attr({type:"text",class:c.FIELD_CLASS}),c.$html.insertAfter(c.$element).prepend(c.$input),c.$list=c.$element.next("."+c.LIST_CLASS),c.$list.on("click",function(b){return!a(b.target).hasClass("inputTags-field")&&void c.$input.focus()})},c.fill=function(){return c._getDefaultValues(),0!==c.options.tags&&(c._concatenate(),c._updateValue(),void c._fill())},c._fill=function(){c.tags.forEach(function(a,b){var d=c._validate(a,!1);(!0===d||"max"===d&&b+1<=c.options.max)&&c._buildItem(a)})},c._clean=function(){a("."+c.ITEM_CLASS,c.$list).remove()},c.save=function(){c.$input.on("keyup",function(b){b.preventDefault();var d=b.keyCode||b.which,e=c.$input.val().trim();if(a.inArray(d,c.keys)<0)return!1;if(27===d)return c._cancel(),!1;if(e=188===d?e.slice(0,-1):e,!c._validate(e,!0))return!1;if(c.options.only&&c._exists(e))return c._errors("exists"),!1;if(c.$input.hasClass("is-edit")){var f=c.$input.attr("data-old-value");if(f===e)return c._cancel(),!0;c._update(f,e),c._clean(),c._fill()}else{if(c._autocomplete()._isSet()&&c._autocomplete()._get("only")&&a.inArray(e,c._autocomplete()._get("values"))<0)return c._autocomplete()._hide(),c._errors("autocomplete_only"),!1;if(c._exists(e)){c.$input.removeClass("is-autocomplete"),c._errors("exists");var g=a('[data-tag="'+e+'"]',c.$list);return g.addClass("is-exists"),setTimeout(function(){g.removeClass("is-exists")},300),!1}c._buildItem(e),c._insert(e)}return c._cancel(),c._updateValue(),c.destroy(),c._autocomplete()._build(),c._setInstance(c),c.$input.focus(),!1})},c.edit=function(){c.$list.on("click","."+c.ITEM_CLASS,function(b){if(a(b.target).hasClass("close-item")||!1===c.options.editable||c._autocomplete()._isSet()&&c._autocomplete()._get("only"))return c._cancel(),!0;var d=a(this).addClass("is-edit"),e=a(".value",d).text();c.$input.width(d.outerWidth()).insertAfter(d).addClass("is-edit").attr("data-old-value",e).val(e).focus(),c._bindEvent("selected"),c.$input.on("blur",function(){c._cancel(),c._bindEvent("unselected")})})},c.destroy=function(){a("."+c.ITEM_CLASS,c.$list).off("click").on("click",".close-item",function(){var b=a(this).parent("."+c.ITEM_CLASS),d=a(".value",b).text();b.addClass("is-closed"),setTimeout(function(){c._pop(d),c._updateValue(),b.remove(),c._autocomplete()._build(),c.$input.focus(),c._setInstance(c)},200)})},c._buildItem=function(b){var d=a(c.ITEM_CONTENT.replace("%s",b)),e=a("
").addClass(c.ITEM_CLASS+" is-closed").attr("data-tag",b).html(d);e.insertBefore(c.$input).delay(100).queue(function(){a(this).removeClass("is-closed")})},c._getIndex=function(a){return c.tags.indexOf(a)},c._concatenate=function(){(!1===typeof c.options.max||c.options.max>0)&&c.options.tags.length>c.options.max&&c.options.tags.splice(-Math.abs(c.options.tags.length-c.options.max)),c.tags=c.tags.concat(c.options.tags)},c._getDefaultValues=function(){c.$element.val().length>0?c.tags=c.tags.concat(c.$element.val().split(",")):c.$element.attr("value","")},c._insert=function(a){c.tags.push(a),c._bindEvent(["change","create"])},c._update=function(a,b){var d=c._getIndex(a);c.tags[d]=b,c._bindEvent(["change","update"])},c._pop=function(a){var b=c._getIndex(a);return!(b<0)&&(c.tags.splice(b,1),void c._bindEvent(["change","destroy"]))},c._cancel=function(){a("."+c.ITEM_CLASS).removeClass("is-edit"),c.$input.removeClass("is-edit is-autocomplete").removeAttr("data-old-value style").val("").appendTo(c.$list)},c._autocomplete=function(){var b=c.options.autocomplete.values;return{_isSet:function(){return b.length>0},_init:function(){return!!c._autocomplete()._isSet()&&void c._autocomplete()._build()},_build:function(){c._autocomplete()._exists()&&c.$autocomplete.remove(),c.$autocomplete=a("").addClass(c.AUTOCOMPLETE_LIST_CLASS),c._autocomplete()._get("values").forEach(function(b,d){var e=c.AUTOCOMPLETE_ITEM_CONTENT.replace("%s",b),f=a.inArray(b,c.tags)>=0?a(e).addClass("is-disabled"):a(e);f.appendTo(c.$autocomplete)}),c._autocomplete()._bindClick(),a(document).not(c.$autocomplete).on("click",function(){c._autocomplete()._hide()})},_bindClick:function(){a(c.$autocomplete).off("click").on("click","."+c.AUTOCOMPLETE_ITEM_CLASS,function(b){if(a(b.target).hasClass("is-disabled"))return!1;c.$input.addClass("is-autocomplete").val(a(this).text()),c._autocomplete()._hide(),c._bindEvent("autocompleteTagSelect");var b=a.Event("keyup");b.which=13,c.$input.trigger(b)})},_show:function(){return!!c._autocomplete()._isSet()&&(c.$autocomplete.css({left:c.$input[0].offsetLeft,minWidth:c.$input.width()}).insertAfter(c.$input),void setTimeout(function(){c._autocomplete()._bindClick(),c.$autocomplete.addClass("is-active")},100))},_hide:function(){c.$autocomplete.removeClass("is-active")},_get:function(a){return c.options.autocomplete[a]},_exists:function(){return void 0!==c.$autocomplete}}},c._updateValue=function(){c.$element.attr("value",c.tags.join(","))},c._focus=function(){c.$input.on("focus",function(){c._bindEvent("focus"),!c._autocomplete()._isSet()||c.$input.hasClass("is-autocomplete")||c.$input.hasClass("is-edit")||c._autocomplete()._show()})},c._toObject=function(a){return a.reduce(function(a,b,c){return a[c]=b,a},{})},c._validate=function(a,b){var d,e="";switch(!0){case!a:case void 0===a:case 0===a.length:c._cancel(),e="empty";break;case a.length>0&&a.lengthc.options.maxLength:e="maxLength";break;case c.options.max>0&&c.tags.length>=c.options.max:c.$input.hasClass("is-edit")||(e="max");break;case c.options.email:d=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,d.test(a)||(e="email")}return!(e.length>0)||(b?c._errors(e):e)},c._exists=function(b){return a.inArray(b,c.tags)>=0},c._errors=function(a){return 0!==a.length&&(c._autocomplete()._exists()&&c.$autocomplete.remove(),c._displayErrors(c.options.errors[a].replace("%s",c.options[a]),a),!1)},c._displayErrors=function(b,d){var e=a(c.ERROR_CONTENT.replace("%s",b)).attr("data-error",d),f=c.options.errors.timeout;return!a("."+c.ERROR_CLASS+'[data-error="'+d+'"]').length&&(e.hide().insertAfter(c.$list).slideDown(),!(!f||f<=0)&&(a("."+c.ERROR_CLASS).on("click",function(){c._collapseErrors(a(this))}),void setTimeout(function(){c._collapseErrors()},f)))},c._collapseErrors=function(b){var d=b?b:a("."+c.ERROR_CLASS);d.slideUp(300,function(){d.remove()})},c._getInstance=function(){return window.inputTags.instances[c.UNIQID]},c._setInstance=function(a){window.inputTags.instances[c.UNIQID]=c},c._isSet=function(a){return!(void 0===c.options[a]||!1===c.options[a]||c.options[a].length<=0)},c._callMethod=function(a,b){return void 0!==b.options[a]&&"function"==typeof b.options[a]&&void b.options[a].apply(this,Array.prototype.slice.call(arguments,1))},c._initEvent=function(a,b){if(!a)return!1;switch(typeof a){case"string":b(a,c);break;case"object":a.forEach(function(a,d){b(a,c)})}return!0},c._bindEvent=function(a){return c._initEvent(a,function(a,b){c._callMethod(a,b)})},c._unbindEvent=function(a){return c._initEvent(a,function(a,b){c.options[a]=!1})},c.init(),c._bindEvent("init"),c._setInstance(c)});return{on:function(a,b){window.inputTags.methods.event(a,b)}}}if(window.inputTags.methods[b]){var c=a(this).attr("data-uniqid"),d=window.inputTags.instances[c];return void 0===d?a.error("[undefined instance] No inputTags instance found."):window.inputTags.methods[b].apply(this,Array.prototype.slice.call(arguments,1))}a.error("[undefined method] The method ["+b+"] does not exists.")},a.fn.inputTags.defaults={tags:[],keys:[],minLength:2,maxLength:30,max:6,email:!1,only:!0,init:!1,create:!1,update:!1,destroy:!1,focus:!1,selected:!1,unselected:!1,change:!1,autocompleteTagSelect:!1,editable:!0,autocomplete:{values:[],only:!1},errors:{empty:"Attention, vous ne pouvez pas ajouter un tag vide.",minLength:"Attention, votre tag doit avoir au minimum %s caractères.",maxLength:"Attention, votre tag ne doit pas dépasser %s caractères.",max:"Attention, le nombre de tags ne doit pas dépasser %s.",email:"Attention, l'adresse email que vous avez entré n'est pas valide",exists:"Attention, ce tag existe déjà !",autocomplete_only:"Attention, vous devez sélectionner une valeur dans la liste.",timeout:8e3}}}(jQuery);
\ No newline at end of file
diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html
new file mode 100644
index 000000000..2e1a981f8
--- /dev/null
+++ b/apps/templates/_base_create_update.html
@@ -0,0 +1,37 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% load static %}
+{% load bootstrap %}
+{% block custom_head_css_js %}
+
+
+ {% block custom_head_css_js_create %} {% endblock %}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% block form %} {% endblock %}
+
+
+
+
+
+{% endblock %}
diff --git a/apps/templates/_list_base.html b/apps/templates/_base_list.html
similarity index 100%
rename from apps/templates/_list_base.html
rename to apps/templates/_base_list.html
diff --git a/apps/users/templates/users/user_create.html b/apps/users/templates/users/user_create.html
index 8337b3756..07235d018 100644
--- a/apps/users/templates/users/user_create.html
+++ b/apps/users/templates/users/user_create.html
@@ -1,6 +1,7 @@
{% extends 'users/_user.html' %}
{% load i18n %}
{% load bootstrap %}
+{% block user_template_title %}{% trans "Create user" %}{% endblock %}
{% block username %}
{{ form.username|bootstrap_horizontal }}
{% endblock %}
diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html
index 851291880..25649007d 100644
--- a/apps/users/templates/users/user_group_list.html
+++ b/apps/users/templates/users/user_group_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html
index d220a4a84..2017510ea 100644
--- a/apps/users/templates/users/user_list.html
+++ b/apps/users/templates/users/user_list.html
@@ -1,4 +1,4 @@
-{% extends '_list_base.html' %}
+{% extends '_base_list.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}