##// 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 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 54 Fabric = super.buildPythonPackage {
42 55 name = "Fabric-1.10.0";
43 56 buildInputs = with self; [];
@@ -558,6 +571,20 b''
558 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 588 docutils = super.buildPythonPackage {
562 589 name = "docutils-0.12";
563 590 buildInputs = with self; [];
@@ -1355,7 +1382,7 b''
1355 1382 name = "rhodecode-enterprise-ce-4.3.0";
1356 1383 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1357 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 1386 src = ./.;
1360 1387 meta = {
1361 1388 license = [ { fullName = "AGPLv3, and Commercial License"; } ];
@@ -60,6 +60,7 b' cov-core==1.15.0'
60 60 coverage==3.7.1
61 61 cssselect==0.9.1
62 62 decorator==3.4.2
63 git+https://github.com/Pylons/deform@08fb9de077c76951f6e70e28d4bf060a209d3d39#egg=deform
63 64 docutils==0.12
64 65 dogpile.cache==0.6.1
65 66 dogpile.core==0.4.1
@@ -316,9 +316,10 b' def includeme_first(config):'
316 316 config.add_route('favicon', '/favicon.ico')
317 317
318 318 config.add_static_view(
319 '_static/deform', 'deform:static')
320 config.add_static_view(
319 321 '_static', path='rhodecode:public', cache_max_age=3600 * 24)
320 322
321
322 323 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
323 324 """
324 325 Apply outer WSGI middlewares around the application.
@@ -19,6 +19,7 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22
22 23 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 24 from rhodecode.integrations.types import webhook, slack
24 25
@@ -35,7 +35,6 b' class IntegrationSettingsSchemaBase(cola'
35 35 description=lazy_ugettext('Enable or disable this integration.'),
36 36 missing=False,
37 37 title=lazy_ugettext('Enabled'),
38 widget='bool',
39 38 )
40 39
41 40 name = colander.SchemaNode(
@@ -43,6 +42,4 b' class IntegrationSettingsSchemaBase(cola'
43 42 description=lazy_ugettext('Short name for this integration.'),
44 43 missing=colander.required,
45 44 title=lazy_ugettext('Integration name'),
46 widget='string',
47 45 )
48
@@ -31,8 +31,7 b' class IntegrationTypeBase(object):'
31 31 self.settings = settings
32 32
33 33
34 @classmethod
35 def settings_schema(cls):
34 def settings_schema(self):
36 35 """
37 36 A colander schema of settings for the integration type
38 37
@@ -19,7 +19,7 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from __future__ import unicode_literals
22
22 import deform
23 23 import re
24 24 import logging
25 25 import requests
@@ -48,10 +48,11 b' class SlackSettingsSchema(IntegrationSet'
48 48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 49 'slack app manager</a>')),
50 50 default='',
51 placeholder='https://hooks.slack.com/services/...',
52 51 preparer=strip_whitespace,
53 52 validator=colander.url,
54 widget='string'
53 widget=deform.widget.TextInputWidget(
54 placeholder='https://hooks.slack.com/services/...',
55 ),
55 56 )
56 57 username = colander.SchemaNode(
57 58 colander.String(),
@@ -59,8 +60,9 b' class SlackSettingsSchema(IntegrationSet'
59 60 description=lazy_ugettext('Username to show notifications coming from.'),
60 61 missing='Rhodecode',
61 62 preparer=strip_whitespace,
62 widget='string',
63 placeholder='Rhodecode'
63 widget=deform.widget.TextInputWidget(
64 placeholder='Rhodecode'
65 ),
64 66 )
65 67 channel = colander.SchemaNode(
66 68 colander.String(),
@@ -68,8 +70,9 b' class SlackSettingsSchema(IntegrationSet'
68 70 description=lazy_ugettext('Channel to send notifications to.'),
69 71 missing='',
70 72 preparer=strip_whitespace,
71 widget='string',
72 placeholder='#general'
73 widget=deform.widget.TextInputWidget(
74 placeholder='#general'
75 ),
73 76 )
74 77 icon_emoji = colander.SchemaNode(
75 78 colander.String(),
@@ -77,8 +80,9 b' class SlackSettingsSchema(IntegrationSet'
77 80 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
78 81 missing='',
79 82 preparer=strip_whitespace,
80 widget='string',
81 placeholder=':studio_microphone:'
83 widget=deform.widget.TextInputWidget(
84 placeholder=':studio_microphone:'
85 ),
82 86 )
83 87
84 88
@@ -144,16 +148,19 b' class SlackIntegrationType(IntegrationTy'
144 148
145 149 run_task(post_text_to_slack, self.settings, text)
146 150
147 @classmethod
148 def settings_schema(cls):
151 def settings_schema(self):
149 152 schema = SlackSettingsSchema()
150 153 schema.add(colander.SchemaNode(
151 154 colander.Set(),
152 widget='checkbox_list',
153 choices=sorted([e.name for e in cls.valid_events]),
155 widget=deform.widget.CheckboxChoiceWidget(
156 values=sorted(
157 [(e.name, e.display_name) for e in self.valid_events]
158 )
159 ),
154 160 description="Events activated for this integration",
155 161 name='events'
156 162 ))
163
157 164 return schema
158 165
159 166 def format_pull_request_comment_event(self, event, data):
@@ -20,6 +20,7 b''
20 20
21 21 from __future__ import unicode_literals
22 22
23 import deform
23 24 import logging
24 25 import requests
25 26 import colander
@@ -41,16 +42,18 b' class WebhookSettingsSchema(IntegrationS'
41 42 description=lazy_ugettext('URL of the webhook to receive POST event.'),
42 43 default='',
43 44 validator=colander.url,
44 placeholder='https://www.example.com/webhook',
45 widget='string'
45 widget=deform.widget.TextInputWidget(
46 placeholder='https://www.example.com/webhook'
47 ),
46 48 )
47 49 secret_token = colander.SchemaNode(
48 50 colander.String(),
49 51 title=lazy_ugettext('Secret Token'),
50 52 description=lazy_ugettext('String used to validate received payloads.'),
51 53 default='',
52 placeholder='secret_token',
53 widget='string'
54 widget=deform.widget.TextInputWidget(
55 placeholder='secret_token'
56 ),
54 57 )
55 58
56 59
@@ -68,13 +71,15 b' class WebhookIntegrationType(Integration'
68 71 events.RepoCreateEvent,
69 72 ]
70 73
71 @classmethod
72 def settings_schema(cls):
74 def settings_schema(self):
73 75 schema = WebhookSettingsSchema()
74 76 schema.add(colander.SchemaNode(
75 77 colander.Set(),
76 widget='checkbox_list',
77 choices=sorted([e.name for e in cls.valid_events]),
78 widget=deform.widget.CheckboxChoiceWidget(
79 values=sorted(
80 [(e.name, e.display_name) for e in self.valid_events]
81 )
82 ),
78 83 description="Events activated for this integration",
79 84 name='events'
80 85 ))
@@ -21,6 +21,7 b''
21 21 import colander
22 22 import logging
23 23 import pylons
24 import deform
24 25
25 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 27 from pyramid.renderers import render
@@ -101,30 +102,41 b' class IntegrationSettingsViewBase(object'
101 102 return c
102 103
103 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 113 View that displays the plugin settings as a form.
109 114 """
110 115 defaults = defaults or {}
111 116 errors = errors or {}
112 117
113 schema = self._form_schema()
114
115 if not defaults:
116 if self.integration:
117 defaults['enabled'] = self.integration.enabled
118 defaults['name'] = self.integration.name
118 if self.integration:
119 defaults = self.integration.settings or {}
120 defaults['name'] = self.integration.name
121 defaults['enabled'] = self.integration.enabled
122 else:
123 if self.repo:
124 scope = self.repo.repo_name
119 125 else:
120 if self.repo:
121 scope = self.repo.repo_name
122 else:
123 scope = _('Global')
126 scope = _('Global')
127
128 defaults['name'] = '{} {} integration'.format(scope,
129 self.IntegrationType.display_name)
130 defaults['enabled'] = True
124 131
125 defaults['name'] = '{} {} integration'.format(scope,
126 self.IntegrationType.display_name)
127 defaults['enabled'] = True
132 schema = self._form_schema().bind(request=self.request)
133
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 141 for node in schema:
130 142 setting = self.settings.get(node.name)
@@ -135,6 +147,7 b' class IntegrationSettingsViewBase(object'
135 147 defaults.setdefault(node.name, node.default)
136 148
137 149 template_context = {
150 'form': form,
138 151 'defaults': defaults,
139 152 'errors': errors,
140 153 'schema': schema,
@@ -166,7 +179,9 b' class IntegrationSettingsViewBase(object'
166 179 redirect_to = self.request.route_url('global_integrations_home')
167 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 186 params = {}
172 187 for node in schema.children:
@@ -177,15 +192,15 b' class IntegrationSettingsViewBase(object'
177 192 if val:
178 193 params[node.name] = val
179 194
195 controls = self.request.POST.items()
180 196 try:
181 valid_data = schema.deserialize(params)
182 except colander.Invalid as e:
183 # Display error message and display form again.
197 valid_data = form.validate(controls)
198 except deform.ValidationFailure as e:
184 199 self.request.session.flash(
185 _('Errors exist when saving plugin settings. '
200 _('Errors exist when saving integration settings. '
186 201 'Please check the form inputs.'),
187 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 205 if not self.integration:
191 206 self.integration = Integration()
@@ -230,7 +245,6 b' class IntegrationSettingsViewBase(object'
230 245 template_context = {
231 246 'current_IntegrationType': self.IntegrationType,
232 247 'current_integrations': current_integrations,
233 'current_integration': 'none',
234 248 'available_integrations': integration_type_registry,
235 249 'c': self._template_c_context()
236 250 }
@@ -950,7 +950,8 b' def bool2icon(value):'
950 950 #==============================================================================
951 951 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
952 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 1880 from webhelpers.pylonslib.secure_form import insecure_form
1880 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1881 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 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 1890 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1886 1891 select_html = select(name, selected, options, **attrs)
1887 1892 select2 = """
@@ -1937,6 +1942,16 b' def route_path(*args, **kwds):'
1937 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 1955 def resource_path(*args, **kwds):
1941 1956 """
1942 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 54 import logging
45 55
46 56 import formencode
@@ -25,6 +25,7 b''
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 @import 'deform';
28 29
29 30
30 31 //--- BASE ------------------//
@@ -141,7 +142,7 b' input.inline[type="file"] {'
141 142 h1 {
142 143 color: @grey2;
143 144 }
144
145
145 146 .error-branding {
146 147 font-family: @text-semibold;
147 148 color: @grey4;
@@ -1022,9 +1023,9 b' label {'
1022 1023 padding: .9em;
1023 1024 color: @grey3;
1024 1025 background-color: @grey7;
1025 border-right: @border-thickness solid @border-default-color;
1026 border-bottom: @border-thickness solid @border-default-color;
1027 border-left: @border-thickness solid @border-default-color;
1026 border-right: @border-thickness solid @border-default-color;
1027 border-bottom: @border-thickness solid @border-default-color;
1028 border-left: @border-thickness solid @border-default-color;
1028 1029 }
1029 1030
1030 1031 #repo_vcs_settings {
@@ -1953,7 +1954,7 b' div.search-feedback-items {'
1953 1954 padding:0px 0px 0px 96px;
1954 1955 }
1955 1956
1956 div.search-code-body {
1957 div.search-code-body {
1957 1958 background-color: #ffffff; padding: 5px 0 5px 10px;
1958 1959 pre {
1959 1960 .match { background-color: #faffa6;}
@@ -27,8 +27,6 b''
27 27 ${integration.name}
28 28 %endif
29 29 </%def>
30
31
32 30 <div class="panel panel-default">
33 31 <div class="panel-heading">
34 32 <h2 class="panel-title">
@@ -39,70 +37,8 b''
39 37 %endif
40 38 </h2>
41 39 </div>
42 <div class="fields panel-body">
43 ${h.secure_form(request.url)}
44 <div class="form">
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()}
40 <div class="panel-body">
41 ## TODO: dan: find way to put h in the deform context properly
42 ${form.render(h=h) | n}
107 43 </div>
108 </div> No newline at end of file
44 </div>
@@ -83,9 +83,11 b" c.template_context['visual']['default_re"
83 83 <![endif]-->
84 84 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
85 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 87 ## avoide esaping the %N
87 88 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js'}";</script>
88 89
90
89 91 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
90 92 ${self.js_extra()}
91 93
@@ -72,6 +72,7 b' requirements = ['
72 72 'celery',
73 73 'colander',
74 74 'decorator',
75 'deform',
75 76 'docutils',
76 77 'gunicorn',
77 78 'infrae.cache',
General Comments 0
You need to be logged in to leave comments. Login now