##// END OF EJS Templates
forms: add deform for integration settings forms
dan -
r518:9f9ffd3a default
parent child Browse files
Show More
@@ -0,0 +1,91 b''
1 .deform {
2
3 * {
4 box-sizing: border-box;
5 }
6
7 .required:after {
8 color: #e32;
9 content: '*';
10 display:inline;
11 }
12
13 .control-label {
14 width: 200px;
15 float: left;
16 }
17 .control-inputs {
18 width: 400px;
19 float: left;
20 }
21 .form-group .radio, .form-group .checkbox {
22 position: relative;
23 display: block;
24 /* margin-bottom: 10px; */
25 }
26
27 .form-group {
28 clear: left;
29 }
30
31 .form-control {
32 width: 100%;
33 }
34
35 .error-block {
36 color: red;
37 }
38
39 .deform-seq-container .control-inputs {
40 width: 100%;
41 }
42
43 .deform-seq-container .deform-seq-item-handle {
44 width: 8.3%;
45 float: left;
46 }
47
48 .deform-seq-container .deform-seq-item-group {
49 width: 91.6%;
50 float: left;
51 }
52
53 .form-control {
54 input {
55 height: 40px;
56 }
57 input[type=checkbox], input[type=radio] {
58 height: auto;
59 }
60 select {
61 height: 40px;
62 }
63 }
64
65 .form-control.select2-container { height: 40px; }
66
67 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
68 display: none;
69 }
70 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
71 display: block;
72 }
73 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
74 display: none;
75 }
76 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
77 background: red;
78 }
79 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
80 width: 45%; padding: 0 2px; float: left; clear: none;
81 }
82 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
83 padding: 0;
84 margin: 5px 0;
85 border: none;
86 }
87 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
88 padding: 0;
89 }
90
91 }
@@ -0,0 +1,20 b''
1 <div class="checkbox">
2 <input tal:define="name name|field.name;
3 true_val true_val|field.widget.true_val;
4 css_class css_class|field.widget.css_class;
5 style style|field.widget.style;
6 oid oid|field.oid"
7 type="checkbox"
8 name="${name}" value="${true_val}"
9 id="${oid}"
10 tal:attributes="checked cstruct == true_val;
11 class css_class;
12 style style;" />
13
14 <label for="${field.oid}">
15 <span tal:condition="hasattr(field, 'schema') and hasattr(field.schema, 'label')"
16 tal:replace="field.schema.label" class="checkbox-label" >
17 </span>
18
19 </label>
20 </div> No newline at end of file
@@ -0,0 +1,25 b''
1 <div tal:define="css_class css_class|field.widget.css_class;
2 style style|field.widget.style;
3 oid oid|field.oid;
4 inline getattr(field.widget, 'inline', False)"
5 tal:omit-tag="not inline">
6 ${field.start_sequence()}
7 <div tal:repeat="choice values | field.widget.values"
8 tal:omit-tag="inline"
9 class="checkbox">
10 <div tal:define="(value, title) choice">
11 <input tal:attributes="checked value in cstruct;
12 class css_class;
13 style style"
14 type="checkbox"
15 name="checkbox"
16 value="${value}"
17 id="${oid}-${repeat.choice.index}"/>
18 <label for="${oid}-${repeat.choice.index}"
19 tal:attributes="class inline and 'checkbox-inline'">
20 ${title}
21 </label>
22 </div>
23 </div>
24 ${field.end_sequence()}
25 </div> No newline at end of file
@@ -0,0 +1,100 b''
1 <form
2 tal:define="style style|field.widget.style;
3 css_class css_class|string:${field.widget.css_class or field.css_class or ''};
4 item_template item_template|field.widget.item_template;
5 autocomplete autocomplete|field.autocomplete;
6 title title|field.title;
7 errormsg errormsg|field.errormsg;
8 description description|field.description;
9 buttons buttons|field.buttons;
10 use_ajax use_ajax|field.use_ajax;
11 ajax_options ajax_options|field.ajax_options;
12 formid formid|field.formid;
13 action action|field.action or None;
14 method method|field.method;"
15 tal:attributes="autocomplete autocomplete;
16 style style;
17 class css_class;
18 action action;"
19 id="${formid}"
20 method="${method}"
21 enctype="multipart/form-data"
22 accept-charset="utf-8"
23 i18n:domain="deform"
24 >
25
26 <fieldset class="deform-form-fieldset">
27
28 <legend tal:condition="title">${title}</legend>
29
30 <input type="hidden" name="${h.csrf_token_key}" value="${h.get_csrf_token()}" />
31 <input type="hidden" name="_charset_" />
32 <input type="hidden" name="__formid__" value="${formid}"/>
33
34 <!--
35 <div class="alert alert-danger" tal:condition="field.error">
36 <div class="error-msg-lbl" i18n:translate=""
37 >There was a problem with your submission</div>
38 <div class="error-msg-detail" i18n:translate=""
39 >Errors have been highlighted below</div>
40 <p class="error-msg">${field.errormsg}</p>
41 </div>
42 -->
43
44 <p class="section first" tal:condition="description">
45 ${description}
46 </p>
47
48 <div tal:repeat="child field"
49 tal:replace="structure child.render_template(item_template)"/>
50
51 <div class="form-group">
52 <tal:loop tal:repeat="button buttons">
53 <button
54 tal:define="btn_disposition repeat.button.start and 'btn-primary' or (button.name == 'delete' and 'btn-danger' or 'btn-default');
55 btn_icon button.icon|None"
56 tal:attributes="disabled button.disabled if button.disabled else None"
57 id="${formid+button.name}"
58 name="${button.name}"
59 type="${button.type}"
60 class="btn ${button.css_class or btn_disposition}"
61 value="${button.value}">
62 <i tal:condition="btn_icon" class="${btn_icon}"> </i>
63 ${button.title}
64 </button>
65 </tal:loop>
66 </div>
67
68 </fieldset>
69
70 <script type="text/javascript" tal:condition="use_ajax">
71 deform.addCallback(
72 '${formid}',
73 function(oid) {
74 var target = '#' + oid;
75 var options = {
76 target: target,
77 replaceTarget: true,
78 success: function() {
79 deform.processCallbacks();
80 deform.focusFirstInput(target);
81 },
82 beforeSerialize: function() {
83 // See http://bit.ly/1agBs9Z (hack to fix tinymce-related ajax bug)
84 if ('tinymce' in window) {
85 $(tinymce.get()).each(
86 function(i, el) {
87 var content = el.getContent();
88 var editor_input = document.getElementById(el.id);
89 editor_input.value = content;
90 });
91 }
92 }
93 };
94 var extra_options = ${ajax_options} || {};
95 $('#' + oid).ajaxForm($.extend(options, extra_options));
96 }
97 );
98 </script>
99
100 </form> No newline at end of file
@@ -0,0 +1,33 b''
1 <tal:def tal:define="title title|field.title;
2 description description|field.description;
3 errormsg errormsg|field.errormsg;
4 item_template item_template|field.widget.item_template"
5 i18n:domain="deform">
6
7 <div class="panel panel-default">
8 <div class="panel-heading">${title}</div>
9 <div class="panel-body">
10
11 <div tal:condition="errormsg"
12 class="clearfix alert alert-danger">
13 <p i18n:translate="">
14 There was a problem with this section
15 </p>
16 <p>${errormsg}</p>
17 </div>
18
19 <div tal:condition="description">
20 ${description}
21 </div>
22
23 ${field.start_mapping()}
24 <div tal:repeat="child field.children"
25 tal:replace="structure child.render_template(item_template)" >
26 </div>
27 ${field.end_mapping()}
28
29 <div style="clear: both"></div>
30 </div>
31 </div>
32
33 </tal:def> No newline at end of file
@@ -0,0 +1,47 b''
1 <div tal:define="error_class error_class|field.widget.error_class;
2 description description|field.description;
3 title title|field.title;
4 oid oid|field.oid;
5 hidden hidden|field.widget.hidden;
6 category category|field.widget.category;
7 structural hidden or category == 'structural';
8 required required|field.required;"
9 class="form-group ${field.error and 'has-error' or ''} ${field.widget.item_css_class or ''}"
10 id="item-${oid}"
11 tal:omit-tag="structural"
12 i18n:domain="deform">
13
14 <label for="${oid}"
15 class="control-label ${required and 'required' or ''}"
16 tal:condition="not structural"
17 id="req-${oid}"
18 >
19 ${title}
20 </label>
21 <div class="control-inputs">
22 <div tal:define="input_prepend field.widget.input_prepend | None;
23 input_append field.widget.input_append | None"
24 tal:omit-tag="not (input_prepend or input_append)"
25 class="input-group">
26 <span class="input-group-addon"
27 tal:condition="input_prepend">${input_prepend}</span
28 ><span tal:replace="structure field.serialize(cstruct).strip()"
29 /><span class="input-group-addon"
30 tal:condition="input_append">${input_append}</span>
31 </div>
32 <p class="help-block error-block"
33 tal:define="errstr 'error-%s' % field.oid"
34 tal:repeat="msg field.error.messages()"
35 i18n:translate=""
36 tal:attributes="id repeat.msg.index==0 and errstr or
37 ('%s-%s' % (errstr, repeat.msg.index))"
38 tal:condition="field.error and not field.widget.hidden and not field.typ.__class__.__name__=='Mapping'">
39 ${msg}
40 </p>
41
42 <p tal:condition="field.description and not field.widget.hidden"
43 class="help-block" >
44 ${field.description}
45 </p>
46 </div>
47 </div> No newline at end of file
@@ -0,0 +1,24 b''
1 <div tal:define="
2 item_tmpl item_template|field.widget.readonly_item_template;
3 oid oid|field.oid;
4 name name|field.name;
5 title title|field.title;"
6 class="deform-seq"
7 id="${oid}">
8
9 <div class="panel panel-default">
10 <div class="panel-heading">${title}</div>
11 <div class="panel-body">
12
13 <div class="deform-seq-container">
14 <div tal:define="subfields [ x[1] for x in subfields ]"
15 tal:repeat="subfield subfields"
16 tal:replace="structure subfield.render_template(item_tmpl,
17 parent=field)" />
18 </div>
19
20 <div style="clear: both"></div>
21 </div>
22
23 </div>
24 </div> No newline at end of file
@@ -0,0 +1,11 b''
1 <div tal:omit-tag="field.widget.hidden"
2 tal:define="
3 hidden hidden|field.widget.hidden;
4 description description|field.description;"
5 title="${description}"
6 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
7 i18n:domain="deform">
8 <div class="deform-seq-item-group">
9 <span tal:replace="structure field.serialize(cstruct, readonly=True)"/>
10 </div>
11 </div>
@@ -0,0 +1,55 b''
1 <div tal:define="
2 name name|field.name;
3 style field.widget.style;
4 oid oid|field.oid;
5 css_class css_class|field.widget.css_class;
6 optgroup_class optgroup_class|field.widget.optgroup_class;
7 multiple multiple|field.widget.multiple;"
8 tal:omit-tag="">
9 <input type="hidden" name="__start__" value="${name}:sequence"
10 tal:condition="multiple" />
11
12 <select tal:attributes="
13 name name;
14 id oid;
15 class string: form-control ${css_class or ''};
16 data-placeholder field.widget.placeholder|None;
17 multiple multiple;
18 style style;">
19 <tal:loop tal:repeat="item values">
20 <optgroup tal:condition="isinstance(item, optgroup_class)"
21 tal:attributes="label item.label">
22 <option tal:repeat="(value, description) item.options"
23 tal:attributes="
24 selected (multiple and value in list(map(unicode, cstruct)) or value == list(map(unicode, cstruct))) and 'selected';
25 class css_class;
26 label field.widget.long_label_generator and description;
27 value value"
28 tal:content="field.widget.long_label_generator and field.widget.long_label_generator(item.label, description) or description"/>
29 </optgroup>
30 <option tal:condition="not isinstance(item, optgroup_class)"
31 tal:attributes="
32 selected (multiple and item[0] in list(map(unicode, cstruct)) or item[0] == unicode(cstruct)) and 'selected';
33 class css_class;
34 value item[0]">${item[1]}</option>
35 </tal:loop>
36 </select>
37
38 <script type="text/javascript">
39 deform.addCallback(
40 '${field.oid}',
41 function(oid) {
42 $('#' + oid).select2({
43 containerCssClass: 'form-control drop-menu',
44 dropdownCssClass: 'drop-menu-dropdown',
45 dropdownAutoWidth: true,
46 placeholder: "${str(field.widget.placeholder).replace('"','\\"')|""}",
47 allowClear: true
48 });
49 }
50 );
51 </script>
52
53 <input type="hidden" name="__end__" value="${name}:sequence"
54 tal:condition="multiple" />
55 </div> No newline at end of file
@@ -0,0 +1,105 b''
1 <div tal:define="item_tmpl item_template|field.widget.item_template;
2 oid oid|field.oid;
3 name name|field.name;
4 min_len min_len|field.widget.min_len;
5 min_len min_len or 0;
6 max_len max_len|field.widget.max_len;
7 max_len max_len or 100000;
8 now_len len(subfields);
9 orderable orderable|field.widget.orderable;
10 orderable orderable and 1 or 0;
11 prototype field.widget.prototype(field);
12 title title|field.title;"
13 class="deform-seq"
14 id="${oid}">
15
16 <style>
17 body.dragging, body.dragging * {
18 cursor: move !important;
19 }
20
21 .dragged {
22 position: absolute;
23 opacity: 0.5;
24 z-index: 2000;
25 }
26 </style>
27
28 <!-- sequence -->
29 <input type="hidden" name="__start__"
30 value="${field.name}:sequence"
31 class="deform-proto"
32 tal:attributes="prototype prototype"/>
33
34 <div class="panel panel-default">
35 <div class="panel-heading">${title}</div>
36 <div class="panel-body">
37
38 <div class="deform-seq-container"
39 id="${oid}-orderable">
40 <div tal:define="subfields [ x[1] for x in subfields ]"
41 tal:repeat="subfield subfields"
42 tal:replace="structure subfield.render_template(item_tmpl,
43 parent=field)" />
44 <span class="deform-insert-before"
45 tal:attributes="
46 min_len min_len;
47 max_len max_len;
48 now_len now_len;
49 orderable orderable;"></span>
50 </div>
51
52 <div style="clear: both"></div>
53 </div>
54
55 <div class="panel-footer">
56 <a href="#"
57 class="btn deform-seq-add"
58 id="${field.oid}-seqAdd"
59 onclick="javascript: return deform.appendSequenceItem(this);">
60 <small id="${field.oid}-addtext">${add_subitem_text}</small>
61 </a>
62
63 <script type="text/javascript">
64 deform.addCallback(
65 '${field.oid}',
66 function(oid) {
67 oid_node = $('#'+ oid);
68 deform.processSequenceButtons(oid_node, ${min_len},
69 ${max_len}, ${now_len},
70 ${orderable});
71 }
72 )
73 <tal:block condition="orderable">
74 $( "#${oid}-orderable" ).sortable({
75 handle: ".deform-order-button, .panel-heading",
76 containerSelector: "#${oid}-orderable",
77 itemSelector: ".deform-seq-item",
78 placeholder: '<span class="glyphicon glyphicon-arrow-right placeholder"></span>',
79 onDragStart: function ($item, container, _super) {
80 var offset = $item.offset(),
81 pointer = container.rootGroup.pointer
82
83 adjustment = {
84 left: pointer.left - offset.left,
85 top: pointer.top - offset.top
86 }
87
88 _super($item, container)
89 },
90 onDrag: function ($item, position) {
91 $item.css({
92 left: position.left - adjustment.left,
93 top: position.top - adjustment.top
94 })
95 }
96 });
97 </tal:block>
98 </script>
99
100 <input type="hidden" name="__end__" value="${field.name}:sequence"/>
101 <!-- /sequence -->
102 </div>
103
104 </div>
105 </div> No newline at end of file
@@ -0,0 +1,35 b''
1 <div tal:omit-tag="field.widget.hidden"
2 tal:define="hidden hidden|field.widget.hidden;
3 error_class error_class|field.widget.error_class;
4 description description|field.description;
5 title title|field.title;
6 oid oid|field.oid"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
8 i18n:domain="deform">
9 <div class="deform-seq-item-group">
10 <span tal:replace="structure field.serialize(cstruct)"/>
11 <tal:errors condition="field.error and not hidden"
12 define="errstr 'error-%s' % oid"
13 repeat="msg field.error.messages()">
14 <p tal:condition="msg"
15 id="${errstr if repeat.msg.index==0 else '%s-%s' % (errstr, repeat.msg.index)}"
16 class="${error_class} help-block"
17 i18n:translate="">${msg}</p>
18 </tal:errors>
19 </div>
20 <div class="deform-seq-item-handle" style="padding:0">
21 <!-- sequence_item -->
22 <span class="deform-order-button close glyphicon glyphicon-resize-vertical"
23 id="${oid}-order"
24 tal:condition="not hidden"
25 title="Reorder (via drag and drop)"
26 i18n:attributes="title"></span>
27 <a class="deform-close-button close"
28 id="${oid}-close"
29 tal:condition="not field.widget.hidden"
30 title="Remove"
31 i18n:attributes="title"
32 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
33 </div>
34 <!-- /sequence_item -->
35 </div>
@@ -0,0 +1,23 b''
1 <span tal:define="name name|field.name;
2 css_class css_class|field.widget.css_class;
3 oid oid|field.oid;
4 mask mask|field.widget.mask;
5 placeholder placeholder|field.widget.placeholder|field.placeholder|'';
6 mask_placeholder mask_placeholder|field.widget.mask_placeholder;
7 style style|field.widget.style;
8 "
9 tal:omit-tag="">
10 <input type="text" name="${name}" value="${cstruct}"
11 tal:attributes="class string: form-control ${css_class or ''};
12 style style"
13 placeholder="${placeholder}"
14 id="${oid}"/>
15 <script tal:condition="mask" type="text/javascript">
16 deform.addCallback(
17 '${oid}',
18 function (oid) {
19 $("#" + oid).mask("${mask}",
20 {placeholder:"${mask_placeholder}"});
21 });
22 </script>
23 </span> No newline at end of file
@@ -38,6 +38,19 b''
38 license = [ pkgs.lib.licenses.mit ];
38 license = [ pkgs.lib.licenses.mit ];
39 };
39 };
40 };
40 };
41 Chameleon = super.buildPythonPackage {
42 name = "Chameleon-2.24";
43 buildInputs = with self; [];
44 doCheck = false;
45 propagatedBuildInputs = with self; [];
46 src = fetchurl {
47 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
48 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
49 };
50 meta = {
51 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
52 };
53 };
41 Fabric = super.buildPythonPackage {
54 Fabric = super.buildPythonPackage {
42 name = "Fabric-1.10.0";
55 name = "Fabric-1.10.0";
43 buildInputs = with self; [];
56 buildInputs = with self; [];
@@ -558,6 +571,20 b''
558 license = [ pkgs.lib.licenses.bsdOriginal ];
571 license = [ pkgs.lib.licenses.bsdOriginal ];
559 };
572 };
560 };
573 };
574 deform = super.buildPythonPackage {
575 name = "deform-2.0a3.dev0";
576 buildInputs = with self; [];
577 doCheck = false;
578 propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation];
579 src = fetchgit {
580 url = "https://github.com/Pylons/deform";
581 rev = "08fb9de077c76951f6e70e28d4bf060a209d3d39";
582 sha256 = "0nmhajc4pfgp4lbwhs5szqfzswpij1qyr69m7qkyhncl2g2d759r";
583 };
584 meta = {
585 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
586 };
587 };
561 docutils = super.buildPythonPackage {
588 docutils = super.buildPythonPackage {
562 name = "docutils-0.12";
589 name = "docutils-0.12";
563 buildInputs = with self; [];
590 buildInputs = with self; [];
@@ -1355,7 +1382,7 b''
1355 name = "rhodecode-enterprise-ce-4.3.0";
1382 name = "rhodecode-enterprise-ce-4.3.0";
1356 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1383 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1357 doCheck = true;
1384 doCheck = true;
1358 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1385 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator deform docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1359 src = ./.;
1386 src = ./.;
1360 meta = {
1387 meta = {
1361 license = [ { fullName = "AGPLv3, and Commercial License"; } ];
1388 license = [ { fullName = "AGPLv3, and Commercial License"; } ];
@@ -60,6 +60,7 b' cov-core==1.15.0'
60 coverage==3.7.1
60 coverage==3.7.1
61 cssselect==0.9.1
61 cssselect==0.9.1
62 decorator==3.4.2
62 decorator==3.4.2
63 git+https://github.com/Pylons/deform@08fb9de077c76951f6e70e28d4bf060a209d3d39#egg=deform
63 docutils==0.12
64 docutils==0.12
64 dogpile.cache==0.6.1
65 dogpile.cache==0.6.1
65 dogpile.core==0.4.1
66 dogpile.core==0.4.1
@@ -316,9 +316,10 b' def includeme_first(config):'
316 config.add_route('favicon', '/favicon.ico')
316 config.add_route('favicon', '/favicon.ico')
317
317
318 config.add_static_view(
318 config.add_static_view(
319 '_static/deform', 'deform:static')
320 config.add_static_view(
319 '_static', path='rhodecode:public', cache_max_age=3600 * 24)
321 '_static', path='rhodecode:public', cache_max_age=3600 * 24)
320
322
321
322 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
323 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
323 """
324 """
324 Apply outer WSGI middlewares around the application.
325 Apply outer WSGI middlewares around the application.
@@ -19,6 +19,7 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 from rhodecode.integrations.types import webhook, slack
24 from rhodecode.integrations.types import webhook, slack
24
25
@@ -35,7 +35,6 b' class IntegrationSettingsSchemaBase(cola'
35 description=lazy_ugettext('Enable or disable this integration.'),
35 description=lazy_ugettext('Enable or disable this integration.'),
36 missing=False,
36 missing=False,
37 title=lazy_ugettext('Enabled'),
37 title=lazy_ugettext('Enabled'),
38 widget='bool',
39 )
38 )
40
39
41 name = colander.SchemaNode(
40 name = colander.SchemaNode(
@@ -43,6 +42,4 b' class IntegrationSettingsSchemaBase(cola'
43 description=lazy_ugettext('Short name for this integration.'),
42 description=lazy_ugettext('Short name for this integration.'),
44 missing=colander.required,
43 missing=colander.required,
45 title=lazy_ugettext('Integration name'),
44 title=lazy_ugettext('Integration name'),
46 widget='string',
47 )
45 )
48
@@ -31,8 +31,7 b' class IntegrationTypeBase(object):'
31 self.settings = settings
31 self.settings = settings
32
32
33
33
34 @classmethod
34 def settings_schema(self):
35 def settings_schema(cls):
36 """
35 """
37 A colander schema of settings for the integration type
36 A colander schema of settings for the integration type
38
37
@@ -19,7 +19,7 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22
22 import deform
23 import re
23 import re
24 import logging
24 import logging
25 import requests
25 import requests
@@ -48,10 +48,11 b' class SlackSettingsSchema(IntegrationSet'
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 'slack app manager</a>')),
49 'slack app manager</a>')),
50 default='',
50 default='',
51 placeholder='https://hooks.slack.com/services/...',
52 preparer=strip_whitespace,
51 preparer=strip_whitespace,
53 validator=colander.url,
52 validator=colander.url,
54 widget='string'
53 widget=deform.widget.TextInputWidget(
54 placeholder='https://hooks.slack.com/services/...',
55 ),
55 )
56 )
56 username = colander.SchemaNode(
57 username = colander.SchemaNode(
57 colander.String(),
58 colander.String(),
@@ -59,8 +60,9 b' class SlackSettingsSchema(IntegrationSet'
59 description=lazy_ugettext('Username to show notifications coming from.'),
60 description=lazy_ugettext('Username to show notifications coming from.'),
60 missing='Rhodecode',
61 missing='Rhodecode',
61 preparer=strip_whitespace,
62 preparer=strip_whitespace,
62 widget='string',
63 widget=deform.widget.TextInputWidget(
63 placeholder='Rhodecode'
64 placeholder='Rhodecode'
65 ),
64 )
66 )
65 channel = colander.SchemaNode(
67 channel = colander.SchemaNode(
66 colander.String(),
68 colander.String(),
@@ -68,8 +70,9 b' class SlackSettingsSchema(IntegrationSet'
68 description=lazy_ugettext('Channel to send notifications to.'),
70 description=lazy_ugettext('Channel to send notifications to.'),
69 missing='',
71 missing='',
70 preparer=strip_whitespace,
72 preparer=strip_whitespace,
71 widget='string',
73 widget=deform.widget.TextInputWidget(
72 placeholder='#general'
74 placeholder='#general'
75 ),
73 )
76 )
74 icon_emoji = colander.SchemaNode(
77 icon_emoji = colander.SchemaNode(
75 colander.String(),
78 colander.String(),
@@ -77,8 +80,9 b' class SlackSettingsSchema(IntegrationSet'
77 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
80 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
78 missing='',
81 missing='',
79 preparer=strip_whitespace,
82 preparer=strip_whitespace,
80 widget='string',
83 widget=deform.widget.TextInputWidget(
81 placeholder=':studio_microphone:'
84 placeholder=':studio_microphone:'
85 ),
82 )
86 )
83
87
84
88
@@ -144,16 +148,19 b' class SlackIntegrationType(IntegrationTy'
144
148
145 run_task(post_text_to_slack, self.settings, text)
149 run_task(post_text_to_slack, self.settings, text)
146
150
147 @classmethod
151 def settings_schema(self):
148 def settings_schema(cls):
149 schema = SlackSettingsSchema()
152 schema = SlackSettingsSchema()
150 schema.add(colander.SchemaNode(
153 schema.add(colander.SchemaNode(
151 colander.Set(),
154 colander.Set(),
152 widget='checkbox_list',
155 widget=deform.widget.CheckboxChoiceWidget(
153 choices=sorted([e.name for e in cls.valid_events]),
156 values=sorted(
157 [(e.name, e.display_name) for e in self.valid_events]
158 )
159 ),
154 description="Events activated for this integration",
160 description="Events activated for this integration",
155 name='events'
161 name='events'
156 ))
162 ))
163
157 return schema
164 return schema
158
165
159 def format_pull_request_comment_event(self, event, data):
166 def format_pull_request_comment_event(self, event, data):
@@ -20,6 +20,7 b''
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22
22
23 import deform
23 import logging
24 import logging
24 import requests
25 import requests
25 import colander
26 import colander
@@ -41,16 +42,18 b' class WebhookSettingsSchema(IntegrationS'
41 description=lazy_ugettext('URL of the webhook to receive POST event.'),
42 description=lazy_ugettext('URL of the webhook to receive POST event.'),
42 default='',
43 default='',
43 validator=colander.url,
44 validator=colander.url,
44 placeholder='https://www.example.com/webhook',
45 widget=deform.widget.TextInputWidget(
45 widget='string'
46 placeholder='https://www.example.com/webhook'
47 ),
46 )
48 )
47 secret_token = colander.SchemaNode(
49 secret_token = colander.SchemaNode(
48 colander.String(),
50 colander.String(),
49 title=lazy_ugettext('Secret Token'),
51 title=lazy_ugettext('Secret Token'),
50 description=lazy_ugettext('String used to validate received payloads.'),
52 description=lazy_ugettext('String used to validate received payloads.'),
51 default='',
53 default='',
52 placeholder='secret_token',
54 widget=deform.widget.TextInputWidget(
53 widget='string'
55 placeholder='secret_token'
56 ),
54 )
57 )
55
58
56
59
@@ -68,13 +71,15 b' class WebhookIntegrationType(Integration'
68 events.RepoCreateEvent,
71 events.RepoCreateEvent,
69 ]
72 ]
70
73
71 @classmethod
74 def settings_schema(self):
72 def settings_schema(cls):
73 schema = WebhookSettingsSchema()
75 schema = WebhookSettingsSchema()
74 schema.add(colander.SchemaNode(
76 schema.add(colander.SchemaNode(
75 colander.Set(),
77 colander.Set(),
76 widget='checkbox_list',
78 widget=deform.widget.CheckboxChoiceWidget(
77 choices=sorted([e.name for e in cls.valid_events]),
79 values=sorted(
80 [(e.name, e.display_name) for e in self.valid_events]
81 )
82 ),
78 description="Events activated for this integration",
83 description="Events activated for this integration",
79 name='events'
84 name='events'
80 ))
85 ))
@@ -21,6 +21,7 b''
21 import colander
21 import colander
22 import logging
22 import logging
23 import pylons
23 import pylons
24 import deform
24
25
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.renderers import render
27 from pyramid.renderers import render
@@ -101,30 +102,41 b' class IntegrationSettingsViewBase(object'
101 return c
102 return c
102
103
103 def _form_schema(self):
104 def _form_schema(self):
104 return self.IntegrationType.settings_schema()
105 if self.integration:
106 settings = self.integration.settings
107 else:
108 settings = {}
109 return self.IntegrationType(settings=settings).settings_schema()
105
110
106 def settings_get(self, defaults=None, errors=None):
111 def settings_get(self, defaults=None, errors=None, form=None):
107 """
112 """
108 View that displays the plugin settings as a form.
113 View that displays the plugin settings as a form.
109 """
114 """
110 defaults = defaults or {}
115 defaults = defaults or {}
111 errors = errors or {}
116 errors = errors or {}
112
117
113 schema = self._form_schema()
118 if self.integration:
114
119 defaults = self.integration.settings or {}
115 if not defaults:
120 defaults['name'] = self.integration.name
116 if self.integration:
121 defaults['enabled'] = self.integration.enabled
117 defaults['enabled'] = self.integration.enabled
122 else:
118 defaults['name'] = self.integration.name
123 if self.repo:
124 scope = self.repo.repo_name
119 else:
125 else:
120 if self.repo:
126 scope = _('Global')
121 scope = self.repo.repo_name
127
122 else:
128 defaults['name'] = '{} {} integration'.format(scope,
123 scope = _('Global')
129 self.IntegrationType.display_name)
130 defaults['enabled'] = True
124
131
125 defaults['name'] = '{} {} integration'.format(scope,
132 schema = self._form_schema().bind(request=self.request)
126 self.IntegrationType.display_name)
133
127 defaults['enabled'] = True
134 if self.integration:
135 buttons = ('submit', 'delete')
136 else:
137 buttons = ('submit',)
138
139 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
128
140
129 for node in schema:
141 for node in schema:
130 setting = self.settings.get(node.name)
142 setting = self.settings.get(node.name)
@@ -135,6 +147,7 b' class IntegrationSettingsViewBase(object'
135 defaults.setdefault(node.name, node.default)
147 defaults.setdefault(node.name, node.default)
136
148
137 template_context = {
149 template_context = {
150 'form': form,
138 'defaults': defaults,
151 'defaults': defaults,
139 'errors': errors,
152 'errors': errors,
140 'schema': schema,
153 'schema': schema,
@@ -166,7 +179,9 b' class IntegrationSettingsViewBase(object'
166 redirect_to = self.request.route_url('global_integrations_home')
179 redirect_to = self.request.route_url('global_integrations_home')
167 raise HTTPFound(redirect_to)
180 raise HTTPFound(redirect_to)
168
181
169 schema = self._form_schema()
182 schema = self._form_schema().bind(request=self.request)
183
184 form = deform.Form(schema, buttons=('submit', 'delete'))
170
185
171 params = {}
186 params = {}
172 for node in schema.children:
187 for node in schema.children:
@@ -177,15 +192,15 b' class IntegrationSettingsViewBase(object'
177 if val:
192 if val:
178 params[node.name] = val
193 params[node.name] = val
179
194
195 controls = self.request.POST.items()
180 try:
196 try:
181 valid_data = schema.deserialize(params)
197 valid_data = form.validate(controls)
182 except colander.Invalid as e:
198 except deform.ValidationFailure as e:
183 # Display error message and display form again.
184 self.request.session.flash(
199 self.request.session.flash(
185 _('Errors exist when saving plugin settings. '
200 _('Errors exist when saving integration settings. '
186 'Please check the form inputs.'),
201 'Please check the form inputs.'),
187 queue='error')
202 queue='error')
188 return self.settings_get(errors=e.asdict(), defaults=params)
203 return self.settings_get(errors={}, defaults=params, form=e)
189
204
190 if not self.integration:
205 if not self.integration:
191 self.integration = Integration()
206 self.integration = Integration()
@@ -230,7 +245,6 b' class IntegrationSettingsViewBase(object'
230 template_context = {
245 template_context = {
231 'current_IntegrationType': self.IntegrationType,
246 'current_IntegrationType': self.IntegrationType,
232 'current_integrations': current_integrations,
247 'current_integrations': current_integrations,
233 'current_integration': 'none',
234 'available_integrations': integration_type_registry,
248 'available_integrations': integration_type_registry,
235 'c': self._template_c_context()
249 'c': self._template_c_context()
236 }
250 }
@@ -950,7 +950,8 b' def bool2icon(value):'
950 #==============================================================================
950 #==============================================================================
951 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
951 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
952 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
952 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
953 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
953 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
954 csrf_token_key
954
955
955
956
956 #==============================================================================
957 #==============================================================================
@@ -1877,11 +1878,15 b' def secure_form(url, method="POST", mult'
1877
1878
1878 """
1879 """
1879 from webhelpers.pylonslib.secure_form import insecure_form
1880 from webhelpers.pylonslib.secure_form import insecure_form
1880 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1881 form = insecure_form(url, method, multipart, **attrs)
1881 form = insecure_form(url, method, multipart, **attrs)
1882 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1882 token = csrf_input()
1883 return literal("%s\n%s" % (form, token))
1883 return literal("%s\n%s" % (form, token))
1884
1884
1885 def csrf_input():
1886 return literal(
1887 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1888 csrf_token_key, csrf_token_key, get_csrf_token()))
1889
1885 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1890 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1886 select_html = select(name, selected, options, **attrs)
1891 select_html = select(name, selected, options, **attrs)
1887 select2 = """
1892 select2 = """
@@ -1937,6 +1942,16 b' def route_path(*args, **kwds):'
1937 return req.route_path(*args, **kwds)
1942 return req.route_path(*args, **kwds)
1938
1943
1939
1944
1945 def static_url(*args, **kwds):
1946 """
1947 Wrapper around pyramids `route_path` function. It is used to generate
1948 URLs from within pylons views or templates. This will be removed when
1949 pyramid migration if finished.
1950 """
1951 req = get_current_request()
1952 return req.static_url(*args, **kwds)
1953
1954
1940 def resource_path(*args, **kwds):
1955 def resource_path(*args, **kwds):
1941 """
1956 """
1942 Wrapper around pyramids `route_path` function. It is used to generate
1957 Wrapper around pyramids `route_path` function. It is used to generate
@@ -41,6 +41,16 b' for SELECT use formencode.All(OneOf(list'
41
41
42 """
42 """
43
43
44 import deform
45 from pkg_resources import resource_filename
46
47 deform_templates = resource_filename('deform', 'templates')
48 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
49 search_path = (rhodecode_templates, deform_templates)
50
51 deform.Form.set_zpt_renderer(search_path)
52
53
44 import logging
54 import logging
45
55
46 import formencode
56 import formencode
@@ -25,6 +25,7 b''
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28
29
29
30
30 //--- BASE ------------------//
31 //--- BASE ------------------//
@@ -141,7 +142,7 b' input.inline[type="file"] {'
141 h1 {
142 h1 {
142 color: @grey2;
143 color: @grey2;
143 }
144 }
144
145
145 .error-branding {
146 .error-branding {
146 font-family: @text-semibold;
147 font-family: @text-semibold;
147 color: @grey4;
148 color: @grey4;
@@ -1022,9 +1023,9 b' label {'
1022 padding: .9em;
1023 padding: .9em;
1023 color: @grey3;
1024 color: @grey3;
1024 background-color: @grey7;
1025 background-color: @grey7;
1025 border-right: @border-thickness solid @border-default-color;
1026 border-right: @border-thickness solid @border-default-color;
1026 border-bottom: @border-thickness solid @border-default-color;
1027 border-bottom: @border-thickness solid @border-default-color;
1027 border-left: @border-thickness solid @border-default-color;
1028 border-left: @border-thickness solid @border-default-color;
1028 }
1029 }
1029
1030
1030 #repo_vcs_settings {
1031 #repo_vcs_settings {
@@ -1953,7 +1954,7 b' div.search-feedback-items {'
1953 padding:0px 0px 0px 96px;
1954 padding:0px 0px 0px 96px;
1954 }
1955 }
1955
1956
1956 div.search-code-body {
1957 div.search-code-body {
1957 background-color: #ffffff; padding: 5px 0 5px 10px;
1958 background-color: #ffffff; padding: 5px 0 5px 10px;
1958 pre {
1959 pre {
1959 .match { background-color: #faffa6;}
1960 .match { background-color: #faffa6;}
@@ -27,8 +27,6 b''
27 ${integration.name}
27 ${integration.name}
28 %endif
28 %endif
29 </%def>
29 </%def>
30
31
32 <div class="panel panel-default">
30 <div class="panel panel-default">
33 <div class="panel-heading">
31 <div class="panel-heading">
34 <h2 class="panel-title">
32 <h2 class="panel-title">
@@ -39,70 +37,8 b''
39 %endif
37 %endif
40 </h2>
38 </h2>
41 </div>
39 </div>
42 <div class="fields panel-body">
40 <div class="panel-body">
43 ${h.secure_form(request.url)}
41 ## TODO: dan: find way to put h in the deform context properly
44 <div class="form">
42 ${form.render(h=h) | n}
45 %for node in schema:
46 <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %>
47 <div class="field">
48 <div class="label ${label_css_class}"><label for="${node.name}">${node.title}</label></div>
49 <div class="input">
50 %if node.widget in ["string", "int", "unicode"]:
51 ${h.text(node.name, defaults.get(node.name), class_="medium", placeholder=hasattr(node, 'placeholder') and node.placeholder or '')}
52 %elif node.widget in ["text"]:
53 ${h.textarea(node.name, defaults.get(node.name), class_="medium", placeholder=hasattr(node, 'placeholder') and node.placeholder or '')}
54 %elif node.widget == "password":
55 ${h.password(node.name, defaults.get(node.name), class_="medium")}
56 %elif node.widget == "bool":
57 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
58 %elif node.widget == "select":
59 ${h.select(node.name, defaults.get(node.name), node.choices)}
60 %elif node.widget == "checkbox_list":
61 %for i, choice in enumerate(node.choices):
62 <%
63 name, value = choice, choice
64 if isinstance(choice, tuple):
65 choice, name = choice
66 %>
67 <div>
68 <input id="${node.name}-${choice}"
69 name="${node.name}"
70 value="${value}"
71 type="checkbox"
72 ${value in defaults.get(node.name, []) and 'checked' or ''}>
73 <label for="${node.name}-${value}">
74 ${name}
75 </label>
76 </div>
77 %endfor
78 %elif node.widget == "readonly":
79 ${node.default}
80 %else:
81 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select|password|text|checkbox_list].
82 %endif
83 %if node.name in errors:
84 <span class="error-message">${errors.get(node.name)}</span>
85 <br />
86 %endif
87 <p class="help-block">${node.description}</p>
88 </div>
89 </div>
90 %endfor
91
92 ## Allow derived templates to add something below the form
93 ## input fields
94 %if hasattr(next, 'below_form_fields'):
95 ${next.below_form_fields()}
96 %endif
97
98 <div class="buttons">
99 ${h.submit('save',_('Save'),class_="btn")}
100 %if integration:
101 ${h.submit('delete',_('Delete'),class_="btn btn-danger")}
102 %endif
103 </div>
104
105 </div>
106 ${h.end_form()}
107 </div>
43 </div>
108 </div> No newline at end of file
44 </div>
@@ -83,9 +83,11 b" c.template_context['visual']['default_re"
83 <![endif]-->
83 <![endif]-->
84 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
84 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
85 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
85 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
86 <script language="javascript" type="text/javascript" src="${h.static_url('deform:static/scripts/deform.js')}"></script>
86 ## avoide esaping the %N
87 ## avoide esaping the %N
87 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js'}";</script>
88 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js'}";</script>
88
89
90
89 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
91 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
90 ${self.js_extra()}
92 ${self.js_extra()}
91
93
@@ -72,6 +72,7 b' requirements = ['
72 'celery',
72 'celery',
73 'colander',
73 'colander',
74 'decorator',
74 'decorator',
75 'deform',
75 'docutils',
76 'docutils',
76 'gunicorn',
77 'gunicorn',
77 'infrae.cache',
78 'infrae.cache',
General Comments 0
You need to be logged in to leave comments. Login now