39 |
40 |
41 |
42 |

48 |
49 |
{% trans "Seafile forms" %}
50 |
51 |
{% trans "Survey? Poll? Inscriptions?" %}
52 |
53 |
{% blocktrans %}
54 | Easily create a form from a
55 | LibreOffice spreadsheet
56 | and directly get results in Seafile!
58 | {% endblocktrans %}
59 |
60 |
61 | {% if authenticated %}
62 |
63 | {% trans "Create a new form" %}
65 |
66 | {% endif %}
67 |
68 |
69 |
70 |
71 |
72 | {% if show_public %}
73 | {% if public_forms %}
74 |
75 |
76 |
77 | {% trans "Public forms" %}
78 |
79 |
80 |
81 |
82 | {% for form in public_forms %}
83 |
84 | |
85 | {{ form.title }}
86 | |
87 | {% if authenticated %}
88 |
89 | {{ form.owner.email }}
90 | |
91 | {% endif %}
92 |
93 | {% endfor %}
94 |
95 |
96 |
97 | {% else %}
98 |
99 | {% trans "No public form available" %}
100 |
101 | {% endif %}
102 | {% endif %}
103 |
104 | {% if justlogout %}
105 |
106 |
107 | {% trans "So long, and thanks for all the fish!" %}
108 |
109 | {% endif %}
110 |
111 |
112 | {% if not authenticated %}
113 |
114 |
115 |
116 | {% if allow_public and public_needauth %}
117 | {% trans "Log in to view forms or create a new one" %}
118 | {% else %}
119 | {% trans "Log in to create a form" %}
120 | {% endif %}
121 |
122 |
123 |
124 |
125 | {% blocktrans %}
126 | You must have a Seafile account on
127 | {{ seaf_root }}, and use the same
128 | credentials to log in.
129 | {% endblocktrans %}
130 |
131 |
132 | {% if autherror %}
133 |
134 |
135 | {% trans "Error:" %}
136 | {% trans "credentials not valid" %}
137 |
138 | {% endif %}
139 |
140 |
145 |
146 |
147 |
148 | {% endif %}
149 |
150 |
151 |
152 | {% endblock %}
153 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/templates/seafform/form_as_table.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {# encoding: utf-8 #}
3 | {% load bootstrap utils i18n %}
4 | {% block title %}{{ seafform.title }}{% endblock %}
5 |
6 | {% block headers %}
7 |
25 | {% endblock %}
26 |
27 | {% block body %}
28 |
29 |
30 |
31 |
32 |
33 |

40 |
41 |
{{ seafform.title }}
42 |
43 | {{ seafform.description|urlize|linebreaks }}
44 |
45 |
46 |
47 |
163 |
164 |
165 | {% blocktrans %}
166 | : champs obligatoires.
167 | {% endblocktrans %}
168 |
169 |
170 |
173 |
174 |
175 | {% endblock %}
176 |
177 | {% block scripts %}
178 |
209 | {% endblock %}
210 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/static/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=4883ecbae98d09844da6)
9 | * Config saved to config.json and https://gist.github.com/4883ecbae98d09844da6
10 | */
11 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(t){"use strict";var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var s=t(this),n=s.data("bs.button"),o="object"==typeof e&&e;n||s.data("bs.button",n=new i(this,o)),"toggle"==e?n.toggle():e&&n.setState(e)})}var i=function(e,s){this.$element=t(e),this.options=t.extend({},i.DEFAULTS,s),this.isLoading=!1};i.VERSION="3.3.5",i.DEFAULTS={loadingText:"loading..."},i.prototype.setState=function(e){var i="disabled",s=this.$element,n=s.is("input")?"val":"html",o=s.data();e+="Text",null==o.resetText&&s.data("resetText",s[n]()),setTimeout(t.proxy(function(){s[n](null==o[e]?this.options[e]:o[e]),"loadingText"==e?(this.isLoading=!0,s.addClass(i).attr(i,i)):this.isLoading&&(this.isLoading=!1,s.removeClass(i).removeAttr(i))},this),0)},i.prototype.toggle=function(){var t=!0,e=this.$element.closest('[data-toggle="buttons"]');if(e.length){var i=this.$element.find("input");"radio"==i.prop("type")?(i.prop("checked")&&(t=!1),e.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==i.prop("type")&&(i.prop("checked")!==this.$element.hasClass("active")&&(t=!1),this.$element.toggleClass("active")),i.prop("checked",this.$element.hasClass("active")),t&&i.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var s=t.fn.button;t.fn.button=e,t.fn.button.Constructor=i,t.fn.button.noConflict=function(){return t.fn.button=s,this},t(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(i){var s=t(i.target);s.hasClass("btn")||(s=s.closest(".btn")),e.call(s,"toggle"),t(i.target).is('input[type="radio"]')||t(i.target).is('input[type="checkbox"]')||i.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(e){t(e.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(e.type))})}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var s=t(this),n=s.data("bs.affix"),o="object"==typeof e&&e;n||s.data("bs.affix",n=new i(this,o)),"string"==typeof e&&n[e]()})}var i=function(e,s){this.options=t.extend({},i.DEFAULTS,s),this.$target=t(this.options.target).on("scroll.bs.affix.data-api",t.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",t.proxy(this.checkPositionWithEventLoop,this)),this.$element=t(e),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};i.VERSION="3.3.5",i.RESET="affix affix-top affix-bottom",i.DEFAULTS={offset:0,target:window},i.prototype.getState=function(t,e,i,s){var n=this.$target.scrollTop(),o=this.$element.offset(),a=this.$target.height();if(null!=i&&"top"==this.affixed)return i>n?"top":!1;if("bottom"==this.affixed)return null!=i?n+this.unpin<=o.top?!1:"bottom":t-s>=n+a?!1:"bottom";var r=null==this.affixed,l=r?n:o.top,h=r?a:e;return null!=i&&i>=n?"top":null!=s&&l+h>=t-s?"bottom":!1},i.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(i.RESET).addClass("affix");var t=this.$target.scrollTop(),e=this.$element.offset();return this.pinnedOffset=e.top-t},i.prototype.checkPositionWithEventLoop=function(){setTimeout(t.proxy(this.checkPosition,this),1)},i.prototype.checkPosition=function(){if(this.$element.is(":visible")){var e=this.$element.height(),s=this.options.offset,n=s.top,o=s.bottom,a=Math.max(t(document).height(),t(document.body).height());"object"!=typeof s&&(o=n=s),"function"==typeof n&&(n=s.top(this.$element)),"function"==typeof o&&(o=s.bottom(this.$element));var r=this.getState(a,e,n,o);if(this.affixed!=r){null!=this.unpin&&this.$element.css("top","");var l="affix"+(r?"-"+r:""),h=t.Event(l+".bs.affix");if(this.$element.trigger(h),h.isDefaultPrevented())return;this.affixed=r,this.unpin="bottom"==r?this.getPinnedOffset():null,this.$element.removeClass(i.RESET).addClass(l).trigger(l.replace("affix","affixed")+".bs.affix")}"bottom"==r&&this.$element.offset({top:a-e-o})}};var s=t.fn.affix;t.fn.affix=e,t.fn.affix.Constructor=i,t.fn.affix.noConflict=function(){return t.fn.affix=s,this},t(window).on("load",function(){t('[data-spy="affix"]').each(function(){var i=t(this),s=i.data();s.offset=s.offset||{},null!=s.offsetBottom&&(s.offset.bottom=s.offsetBottom),null!=s.offsetTop&&(s.offset.top=s.offsetTop),e.call(i,s)})})}(jQuery),+function(t){"use strict";function e(e){var i,s=e.attr("data-target")||(i=e.attr("href"))&&i.replace(/.*(?=#[^\s]+$)/,"");return t(s)}function i(e){return this.each(function(){var i=t(this),n=i.data("bs.collapse"),o=t.extend({},s.DEFAULTS,i.data(),"object"==typeof e&&e);!n&&o.toggle&&/show|hide/.test(e)&&(o.toggle=!1),n||i.data("bs.collapse",n=new s(this,o)),"string"==typeof e&&n[e]()})}var s=function(e,i){this.$element=t(e),this.options=t.extend({},s.DEFAULTS,i),this.$trigger=t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};s.VERSION="3.3.5",s.TRANSITION_DURATION=350,s.DEFAULTS={toggle:!0},s.prototype.dimension=function(){var t=this.$element.hasClass("width");return t?"width":"height"},s.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var e,n=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(n&&n.length&&(e=n.data("bs.collapse"),e&&e.transitioning))){var o=t.Event("show.bs.collapse");if(this.$element.trigger(o),!o.isDefaultPrevented()){n&&n.length&&(i.call(n,"hide"),e||n.data("bs.collapse",null));var a=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[a](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var r=function(){this.$element.removeClass("collapsing").addClass("collapse in")[a](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!t.support.transition)return r.call(this);var l=t.camelCase(["scroll",a].join("-"));this.$element.one("bsTransitionEnd",t.proxy(r,this)).emulateTransitionEnd(s.TRANSITION_DURATION)[a](this.$element[0][l])}}}},s.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var e=t.Event("hide.bs.collapse");if(this.$element.trigger(e),!e.isDefaultPrevented()){var i=this.dimension();this.$element[i](this.$element[i]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var n=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return t.support.transition?void this.$element[i](0).one("bsTransitionEnd",t.proxy(n,this)).emulateTransitionEnd(s.TRANSITION_DURATION):n.call(this)}}},s.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},s.prototype.getParent=function(){return t(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(t.proxy(function(i,s){var n=t(s);this.addAriaAndCollapsedClass(e(n),n)},this)).end()},s.prototype.addAriaAndCollapsedClass=function(t,e){var i=t.hasClass("in");t.attr("aria-expanded",i),e.toggleClass("collapsed",!i).attr("aria-expanded",i)};var n=t.fn.collapse;t.fn.collapse=i,t.fn.collapse.Constructor=s,t.fn.collapse.noConflict=function(){return t.fn.collapse=n,this},t(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(s){var n=t(this);n.attr("data-target")||s.preventDefault();var o=e(n),a=o.data("bs.collapse"),r=a?"toggle":n.data();i.call(o,r)})}(jQuery),+function(t){"use strict";function e(i,s){this.$body=t(document.body),this.$scrollElement=t(t(i).is(document.body)?window:i),this.options=t.extend({},e.DEFAULTS,s),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",t.proxy(this.process,this)),this.refresh(),this.process()}function i(i){return this.each(function(){var s=t(this),n=s.data("bs.scrollspy"),o="object"==typeof i&&i;n||s.data("bs.scrollspy",n=new e(this,o)),"string"==typeof i&&n[i]()})}e.VERSION="3.3.5",e.DEFAULTS={offset:10},e.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},e.prototype.refresh=function(){var e=this,i="offset",s=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),t.isWindow(this.$scrollElement[0])||(i="position",s=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var e=t(this),n=e.data("target")||e.attr("href"),o=/^#./.test(n)&&t(n);return o&&o.length&&o.is(":visible")&&[[o[i]().top+s,n]]||null}).sort(function(t,e){return t[0]-e[0]}).each(function(){e.offsets.push(this[0]),e.targets.push(this[1])})},e.prototype.process=function(){var t,e=this.$scrollElement.scrollTop()+this.options.offset,i=this.getScrollHeight(),s=this.options.offset+i-this.$scrollElement.height(),n=this.offsets,o=this.targets,a=this.activeTarget;if(this.scrollHeight!=i&&this.refresh(),e>=s)return a!=(t=o[o.length-1])&&this.activate(t);if(a&&e
=n[t]&&(void 0===n[t+1]||e, 2015
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: PACKAGE VERSION\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2015-11-11 17:21+0100\n"
11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 | "Last-Translator: Florian Birée \n"
13 | "Language-Team: French\n"
14 | "Language: French\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
19 |
20 | #. Translators: field type for spreadsheet
21 | #: seafform.py:80
22 | msgid "text"
23 | msgstr "texte"
24 |
25 | #. Translators: field type for spreadsheet
26 | #: seafform.py:85
27 | msgid "longtext"
28 | msgstr "texteLong"
29 |
30 | #. Translators: field type for spreadsheet
31 | #: seafform.py:90
32 | msgid "list"
33 | msgstr "liste"
34 |
35 | #. Translators: field type for spreadsheet
36 | #: seafform.py:101
37 | msgid "check"
38 | msgstr "caseÀCocher"
39 |
40 | #. Translators: field type for spreadsheet
41 | #: seafform.py:110
42 | msgid "checked"
43 | msgstr "caseCochée"
44 |
45 | #. Translators: field type for spreadsheet
46 | #: seafform.py:119
47 | msgid "date"
48 | msgstr "date"
49 |
50 | #. Translators: field type for spreadsheet
51 | #: seafform.py:124
52 | msgid "number"
53 | msgstr "nombre"
54 |
55 | #. Translators: field type for spreadsheet
56 | #: seafform.py:129
57 | msgid "static"
58 | msgstr "statique"
59 |
60 | #. Translators: ODS view mode
61 | #: seafform.py:183
62 | msgid "table"
63 | msgstr "tableau"
64 |
65 | #: seafform.py:183
66 | msgid "form"
67 | msgstr "formulaire"
68 |
69 | #. Translators: ODS edit, yes or no
70 | #. Translators: ODS public, yes or no
71 | #: seafform.py:187 seafform.py:191
72 | msgid "yes"
73 | msgstr "oui"
74 |
75 | #: seafform.py:187 seafform.py:191
76 | msgid "no"
77 | msgstr "non"
78 |
79 | #: templates/404.html:4 templates/404.html.py:20 templates/500.html:4
80 | #: templates/500.html.py:20 templates/seafform/index.html:4
81 | #: templates/seafform/index.html.py:49 templates/seafform/new.html:4
82 | #: templates/seafform/private.html:4
83 | msgid "Seafile forms"
84 | msgstr "Formulaires Seafile"
85 |
86 | #: templates/404.html:4
87 | msgid "Form not found"
88 | msgstr "Formulaire introuvable"
89 |
90 | #: templates/404.html:23
91 | msgid ""
92 | "\n"
93 | " The form you try to open is no more reachable.\n"
94 | " May be its owner put it off-line?\n"
95 | " "
96 | msgstr ""
97 | "\n"
98 | "Le formulaire que vous essayez d'ouvrir n'est plus accessible.\n"
99 | "Peut-être que son propriétaire l'a mis hors-ligne ?"
100 |
101 | #: templates/500.html:4
102 | msgid "Server fault"
103 | msgstr "Erreur du serveur"
104 |
105 | #: templates/500.html:23
106 | msgid ""
107 | "\n"
108 | " An error append server-side. May be a form is not well-built?\n"
109 | " "
110 | msgstr ""
111 | "\n"
112 | "Le serveur a rencontré une erreur. Peut-être que le formulaire n'était pas "
113 | "bien construit ?"
114 |
115 | #: templates/500.html:28
116 | msgid ""
117 | "\n"
118 | " You can contact the form owner to tell her you had a problem.\n"
119 | " "
120 | msgstr ""
121 | "\n"
122 | "Vous pouvez contacter le propriétaire du formulaire pour lui dire que vous "
123 | "avez eu un problème."
124 |
125 | #: templates/base.html:59
126 | msgid ""
127 | "\n"
128 | " It was a form build with\n"
129 | " free\n"
130 | " softwares: LibreOffice,\n"
132 | " Seafile and \n"
133 | " SeafForm "
134 | "(give\n"
135 | " me the source code)!\n"
136 | " "
137 | msgstr ""
138 | "\n"
139 | "C'était un formulaire produit avec les logiciels libres LibreOffice, Seafile et SeafForm (donnez-moi le "
143 | "code source) !"
144 |
145 | #: templates/seafform/form_as_form.html:42
146 | #: templates/seafform/form_as_table.html:158
147 | msgid ""
148 | "\n"
149 | " : champs obligatoires.\n"
150 | " "
151 | msgstr ""
152 | "\n"
153 | " : champs obligatoires."
154 |
155 | #: templates/seafform/form_as_form.html:77
156 | #: templates/seafform/form_as_table.html:69
157 | #: templates/seafform/form_as_table.html:147 templates/seafform/new.html:72
158 | #: templates/seafform/rowedit.html:21
159 | msgid "Submit"
160 | msgstr "Valider"
161 |
162 | #: templates/seafform/form_as_table.html:52 templates/seafform/private.html:103
163 | msgid "Action"
164 | msgstr "Action"
165 |
166 | #: templates/seafform/form_as_table.html:93
167 | #: templates/seafform/form_as_table.html:174
168 | #: templates/seafform/form_as_table.html:192
169 | msgid "Loading..."
170 | msgstr "Chargement..."
171 |
172 | #: templates/seafform/form_as_table.html:115
173 | msgid "(maximum)"
174 | msgstr "(maximum)"
175 |
176 | #: templates/seafform/form_as_table.html:121
177 | #, python-format
178 | msgid ""
179 | "\n"
180 | " %(nb)s answer\n"
181 | " "
182 | msgstr ""
183 | "\n"
184 | "%(nb)s réponses\n"
185 | " "
186 |
187 | #: templates/seafform/index.html:14 templates/seafform/new.html:17
188 | #: templates/seafform/private.html:13
189 | msgid "Toggle navigation"
190 | msgstr "Basculer la navigation"
191 |
192 | #: templates/seafform/index.html:26 templates/seafform/index.html.py:77
193 | #: templates/seafform/new.html:29 templates/seafform/private.html:25
194 | msgid "Public forms"
195 | msgstr "Formulaires publics"
196 |
197 | #: templates/seafform/index.html:28 templates/seafform/new.html:31
198 | #: templates/seafform/private.html:27
199 | msgid "My forms"
200 | msgstr "Mes formulaires"
201 |
202 | #: templates/seafform/index.html:29 templates/seafform/new.html:32
203 | #: templates/seafform/private.html:28
204 | msgid "Open Seafile"
205 | msgstr "Ouvrir Seafile"
206 |
207 | #: templates/seafform/index.html:30 templates/seafform/new.html:33
208 | #: templates/seafform/private.html:29
209 | msgid "Logout"
210 | msgstr "Se déconnecter"
211 |
212 | #: templates/seafform/index.html:51
213 | msgid "Survey? Poll? Inscriptions?"
214 | msgstr "Un sondage ? Une enquête ? Des inscriptions ?"
215 |
216 | #: templates/seafform/index.html:53
217 | msgid ""
218 | "\n"
219 | " Easily create a form from a \n"
220 | " LibreOffice "
221 | "spreadsheet\n"
222 | " and directly get results in Seafile!\n"
224 | " "
225 | msgstr ""
226 | "\n"
227 | "Créez simplement un formulaire à partir d'un tableur LibreOffice, et récupérez directement les résultats "
229 | "dans Seafile !"
230 |
231 | #: templates/seafform/index.html:64 templates/seafform/new.html:43
232 | #: templates/seafform/private.html:38 templates/seafform/private.html.py:56
233 | msgid "Create a new form"
234 | msgstr "Créer un nouveau formulaire"
235 |
236 | #: templates/seafform/index.html:99
237 | msgid "No public form available"
238 | msgstr "Pas de formulaires publics disponibles"
239 |
240 | #: templates/seafform/index.html:107
241 | msgid "So long, and thanks for all the fish!"
242 | msgstr "Salut, et encore merci pour le poisson !"
243 |
244 | #: templates/seafform/index.html:117
245 | msgid "Log in to view forms or create a new one"
246 | msgstr "Se connecter pour voir les formulaires publics ou en créer un nouveau"
247 |
248 | #: templates/seafform/index.html:119
249 | msgid "Log in to create a form"
250 | msgstr "Se connecter pour créer un formulaire"
251 |
252 | #: templates/seafform/index.html:125
253 | #, python-format
254 | msgid ""
255 | "\n"
256 | " You must have a Seafile account on\n"
257 | " %(seaf_root)s, and use the "
258 | "same\n"
259 | " credentials to log in.\n"
260 | " "
261 | msgstr ""
262 | "\n"
263 | "Vous devez avoir un compte sur le Seafile "
264 | "%(seaf_root)s, et utiliser les mêmes identifiants pour vous connecter."
265 |
266 | #: templates/seafform/index.html:135
267 | msgid "Error:"
268 | msgstr "Erreur :"
269 |
270 | #: templates/seafform/index.html:136
271 | msgid "credentials not valid"
272 | msgstr "identifiants invalides"
273 |
274 | #: templates/seafform/index.html:143
275 | msgid "Login"
276 | msgstr "Se connecter"
277 |
278 | #: templates/seafform/new.html:48
279 | msgid ""
280 | "\n"
281 | " Choose in your Seafile the LibreOffice spreadsheet (.ods) which will be\n"
282 | " the form:\n"
283 | " "
284 | msgstr ""
285 | "\n"
286 | "Sélectionnez dans votre Seafile le tableur LibreOffice (.ods) qui sera à la "
287 | "base du formulaire :"
288 |
289 | #: templates/seafform/new.html:57
290 | msgid "Conexion to Seafile..."
291 | msgstr "Connexion à Seafile..."
292 |
293 | #: templates/seafform/new.html:65 templates/seafform/rowedit.html:24
294 | msgid "Cancel"
295 | msgstr "Annuler"
296 |
297 | #: templates/seafform/private.html:41
298 | msgid "1. create the spreadsheet with questions"
299 | msgstr "1. créer le tableur avec les questions"
300 |
301 | #: templates/seafform/private.html:44
302 | msgid "Forms templates"
303 | msgstr "Modèles de formulaires"
304 |
305 | #: templates/seafform/private.html:47
306 | msgid ""
307 | "\n"
308 | " You just need to download a template, to edit it, and to "
309 | "synchronize\n"
310 | " it in Seafile.\n"
311 | " "
312 | msgstr ""
313 | "\n"
314 | "Il suffit de télécharger un modèle, de le modifier, et de le synchroniser "
315 | "dans Seafile."
316 |
317 | #: templates/seafform/private.html:53
318 | msgid "2. create the form"
319 | msgstr "2. créer le formulaire"
320 |
321 | #: templates/seafform/private.html:59
322 | msgid "Select the right spreadsheet."
323 | msgstr "Indiquez quel est le tableur en question."
324 |
325 | #: templates/seafform/private.html:62
326 | msgid "3. ask people to answer"
327 | msgstr "3. invitez vos amis à répondre"
328 |
329 | #: templates/seafform/private.html:65
330 | msgid ""
331 | "\n"
332 | " Send the form's public address to all affected persons!\n"
333 | " "
334 | msgstr ""
335 | "\n"
336 | "Envoyez l'adresse publique du formulaire à toutes les personnes concernées !"
337 |
338 | #: templates/seafform/private.html:75
339 | #, python-format
340 | msgid ""
341 | "\n"
342 | " The form %(title)s
was sucessfully created! Share the\n"
343 | " following address with interseted people:\n"
344 | " "
345 | msgstr ""
346 | "\n"
347 | "Le formulaire %(title)s
a été créé avec succès ! Communiquez "
348 | "l'adresse suivante à vos correspondants :"
349 |
350 | #: templates/seafform/private.html:91
351 | #, python-format
352 | msgid ""
353 | "\n"
354 | " The form %(deleted)s
was deleted. Its public link is "
355 | "disabled.\n"
356 | " "
357 | msgstr ""
358 | "\n"
359 | "Le formulaire %(deleted)s
a bien été supprimé. Son lien public a été "
360 | "désactivé."
361 |
362 | #: templates/seafform/private.html:100
363 | msgid "Title"
364 | msgstr "Titre"
365 |
366 | #: templates/seafform/private.html:100
367 | msgid "Address"
368 | msgstr "Adresse"
369 |
370 | #: templates/seafform/private.html:124
371 | msgid "Delete"
372 | msgstr "Supprimer"
373 |
374 | #: templates/seafform/thanks.html:4
375 | msgid "Thanks!"
376 | msgstr "Merci !"
377 |
378 | #: templates/seafform/thanks.html:30
379 | msgid "Thank you for taking time to answer this form!"
380 | msgstr "Merci d'avoir pris le temps de répondre à ce questionnaire !"
381 |
382 | #~ msgid "New form"
383 | #~ msgstr "Nouveau formulaire"
384 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/seafform.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###############################################################################
3 | # seafform/seafform.py
4 | #
5 | # Copyright © 2015, Florian Birée
6 | #
7 | # This file is a part of seafform.
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU Affero General Public License as
11 | # published by the Free Software Foundation, either version 3 of the
12 | # License, or (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU Affero General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU Affero General Public License
20 | # along with this program. If not, see .
21 | #
22 | ###############################################################################
23 | """Seafform forms description"""
24 |
25 | __author__ = "Florian Birée"
26 | __version__ = "0.2"
27 | __license__ = "AGPLv3"
28 | __copyright__ = "Copyright © 2015, Florian Birée "
29 |
30 | import os
31 | import ezodf
32 | import shutil
33 | import time
34 | import datetime
35 | from tempfile import NamedTemporaryFile
36 | if 'DJANGO_SETTINGS_MODULE' in os.environ:
37 | from django.utils.translation import ugettext_noop
38 | from django.utils.translation import ugettext as _
39 | else:
40 | # Not in a Django environment
41 | _ = lambda x:x
42 | ugettext_noop = _
43 |
44 | HEADERS_ROW = 4 # number of headers row in ods files
45 | #all_less_maxcount strategy delete formats
46 | # trying all_but_last, then all
47 | #ezodf.config.set_table_expand_strategy('all_but_last')
48 |
49 | class InvalidODS(Exception):
50 | """Raise when the ODS file doesn't respect the specification for Seafform
51 | """
52 | pass
53 |
54 | class Field:
55 | """Base class for form fields"""
56 |
57 | def __init__(self, label, description=None, params=None, required=False,
58 | value=None):
59 | """Initialize a new field"""
60 | self.label = label
61 | self.description = description
62 | self.params = params
63 | self.required = required
64 | self.value = value
65 |
66 | def __repr__(self):
67 | if hasattr(self, 'ident'):
68 | ftype = self.ident
69 | else:
70 | ftype = 'Field'
71 | return '<{0}({1}){2}>'.format(
72 | ftype,
73 | self.label,
74 | ('*' if self.required else '')
75 | )
76 |
77 | class TextField(Field):
78 | """Single-line text field"""
79 | # Translators: field type for spreadsheet
80 | ident = ugettext_noop('text')
81 |
82 | class LongTextField(Field):
83 | """Multiline text field"""
84 | # Translators: field type for spreadsheet
85 | ident = ugettext_noop('longtext')
86 |
87 | class ListField(Field):
88 | """List of choices field"""
89 | # Translators: field type for spreadsheet
90 | ident = ugettext_noop('list')
91 |
92 | def __init__(self, *args):
93 | Field.__init__(self, *args)
94 | self.choices = [
95 | ch.strip() for ch in self.params.split(',')
96 | ]
97 |
98 | class BooleanField(Field):
99 | """Checkbox field"""
100 | # Translators: field type for spreadsheet
101 | ident = ugettext_noop('check')
102 |
103 | def __init__(self, *args):
104 | Field.__init__(self, *args)
105 | self.value = False
106 |
107 | class BooleanTrueField(Field):
108 | """Checked checkbox field"""
109 | # Translators: field type for spreadsheet
110 | ident = ugettext_noop('checked')
111 |
112 | def __init__(self, *args):
113 | Field.__init__(self, *args)
114 | self.value = True
115 |
116 | class DateField(Field):
117 | """Date field"""
118 | # Translators: field type for spreadsheet
119 | ident = ugettext_noop('date')
120 |
121 | class NumberField(Field):
122 | """Number field"""
123 | # Translators: field type for spreadsheet
124 | ident = ugettext_noop('number')
125 |
126 | class StaticField(Field):
127 | """Non-editable field"""
128 | # Translators: field type for spreadsheet
129 | ident = ugettext_noop('static')
130 |
131 | def field_of(ident):
132 | """Return the Field subclass corresponding to `ident`"""
133 | return [
134 | cls
135 | for cls in Field.__subclasses__()
136 | if (cls.ident == ident) or (_(cls.ident) == ident)
137 | ][0]
138 |
139 | def untranslate(loc_val, raw_list, loc_list):
140 | """If loc_val in raw_list,
141 | return loc_val
142 | else:
143 | Return the raw value at the same position in raw_list than
144 | loc_val in loc_list
145 | default to the first value
146 | """
147 | if loc_val in raw_list:
148 | return loc_val
149 | else:
150 | try:
151 | return raw_list[loc_list.index(loc_val)]
152 | except ValueError:
153 | return raw_list[0]
154 |
155 | class SeafForm:
156 | """Build and fill a form from an OpenDocumentSpreadsheet file"""
157 | _availables_f_ident = (
158 | [cls.ident for cls in Field.__subclasses__()]
159 | +
160 | [_(cls.ident) for cls in Field.__subclasses__()]
161 | )
162 |
163 | def __init__(self, filepath, seaf=None, repo_id=None):
164 | """Initialize a form for the file `filepath`.
165 |
166 | If seaf is a Seafile instance, repo_id must be the
167 | Seafile identifier of the repository where `filepath` is.
168 |
169 | If seaf is None, load `filepath` from the local filesystem
170 | """
171 | # source properties
172 | self.filepath = filepath
173 | self.seaf = seaf
174 | self.repo_id = repo_id
175 | self.loaded = False
176 |
177 | # form properties
178 | self.title = None
179 | self.description = None
180 | self.fields = None
181 | self.data = None
182 | # Translators: ODS view mode
183 | self._view_as_values = (ugettext_noop('table'), ugettext_noop('form'))
184 | self._view_as_l10n = [_(v) for v in self._view_as_values]
185 | self.view_as = None # ('table' or 'form')
186 | # Translators: ODS edit, yes or no
187 | self._edit_values = (ugettext_noop('yes'), ugettext_noop('no'))
188 | self._edit_val10n = [_(v) for v in self._edit_values]
189 | self.edit = None
190 | # Translators: ODS public, yes or no
191 | self._public_values = (ugettext_noop('yes'), ugettext_noop('no'))
192 | self._public_val10n = [_(v) for v in self._public_values]
193 | self.public = None
194 |
195 | # cached items
196 | self.mtime = None
197 | self._odsfile = None
198 | self._first_empty_row = None
199 |
200 | def __repr__(self):
201 | """Representation of the form"""
202 | if self.loaded:
203 | return "".format(self.filepath, self.title)
204 | else:
205 | return "".format(self.filepath)
206 |
207 | def load(self):
208 | """Load form data from the ODS file"""
209 | odsopener = self._seaf_open if self.seaf else self._local_open
210 |
211 | seaf_f = odsopener()
212 | # save spreadsheet into a temporary file
213 | with NamedTemporaryFile(delete=False) as tmpfile:
214 | shutil.copyfileobj(seaf_f, tmpfile)
215 | tmpname = tmpfile.name
216 | seaf_f.close()
217 |
218 | # open spreadsheet document
219 | self.odsfile = ezodf.opendoc(tmpname)
220 | # delete tmp file
221 | os.unlink(tmpname)
222 |
223 |
224 | # get the Data sheet
225 | try:
226 | datash = self.odsfile.sheets['Data']
227 | except KeyError:
228 | raise InvalidODS
229 |
230 | # get Properties
231 | self.title = datash['A7'].value
232 | self.description = datash['A9'].value
233 | self.view_as = untranslate(
234 | datash['A11'].value,
235 | self._view_as_values,
236 | self._view_as_l10n
237 | )
238 | self.edit = ('yes' == (untranslate(
239 | datash['A13'].value,
240 | self._edit_values,
241 | self._edit_val10n
242 | )))
243 | self.public = ('yes' == (untranslate(
244 | datash['A15'].value,
245 | self._public_values,
246 | self._public_val10n
247 | )))
248 |
249 | # get fields
250 | self.fields = []
251 | for colid in range(1, datash.ncols()):
252 | # get column
253 | col = datash.column(colid)
254 | # get field data
255 | fname = col[0].value
256 | fformat = col[1].value
257 | fparams = col[2].value
258 | fdesc = col[3].value
259 | # col[4:] is data
260 |
261 | # build field object (if fformat is known)
262 | if fformat and fformat.strip('*') in self._availables_f_ident:
263 | frequired = (fformat.endswith('*'))
264 | FType = field_of(fformat.rstrip('*'))
265 | self.fields.append(FType(
266 | fname, fdesc, fparams, frequired
267 | ))
268 |
269 | # get data
270 | self.data = []
271 | first_empy_row = self._get_first_empty_row(recompute=True)
272 | for rowid in range(HEADERS_ROW, first_empy_row):
273 | row = datash.row(rowid)
274 | row_data = []
275 | for celid in range(1, len(self.fields) + 1):
276 | val = row[celid].value
277 | if self.fields[celid-1].ident == 'date' and val:
278 | try:
279 | s_time = time.strptime(val, '%Y-%m-%d')
280 | except ValueError:
281 | pass # keep val as str
282 | else:
283 | val = datetime.date(*s_time[:3])
284 | elif self.fields[celid-1].ident.startswith('check') and val:
285 | val = bool(val)
286 | row_data.append(val)
287 | self.data.append(row_data)
288 |
289 | # get mtime
290 | if self.seaf:
291 | s = self.seaf.stat_file(self.repo_id, self.filepath)
292 | self.mtime = float(s['mtime'])
293 | else:
294 | self.mtime = os.path.getmtime(self.filepath)
295 |
296 | self.loaded = True
297 |
298 | def _seaf_open(self):
299 | """Return an opened file-like object from Seafile"""
300 | return self.seaf.open_file(self.repo_id, self.filepath)
301 |
302 | def _local_open(self):
303 | """Return an opened file object from local filesystem"""
304 | return open(self.filepath, 'rb')
305 |
306 | def _get_first_empty_row(self, recompute=False):
307 | """Return the first empty row number
308 | if recompute, do not use the cached value
309 | """
310 | if self._first_empty_row is not None and not recompute:
311 | return self._first_empty_row
312 |
313 | # get the Data sheet
314 | try:
315 | datash = self.odsfile.sheets['Data']
316 | except KeyError:
317 | raise InvalidODS
318 |
319 | # find the first empty row
320 | rowid = datash.nrows() - 1
321 | bcol = datash.column(1)
322 | for celid in reversed(range(HEADERS_ROW, datash.nrows())):
323 | if not bcol[celid].value:
324 | rowid = celid
325 | else:
326 | break
327 | for colid in range(1, datash.ncols()):
328 | # check if rowid is empty for all the row
329 | # go down until empty
330 | while (rowid < datash.nrows() and datash[rowid, colid].value):
331 | rowid += 1
332 |
333 | self._first_empty_row = rowid
334 | return rowid
335 |
336 | def post(self, values, replace_row=None):
337 | """Post data from values into the ODS file
338 |
339 | values is the dict of {ident: value}
340 | optionaly replace values from the row `replace_row`
341 |
342 | WARNING: type and required verification must be done before
343 | """
344 | # get new mtime
345 | if self.seaf:
346 | s = self.seaf.stat_file(self.repo_id, self.filepath)
347 | new_mtime = float(s['mtime'])
348 | else:
349 | new_mtime = os.path.getmtime(self.filepath)
350 | # if mtime has changed:
351 | if self.mtime != new_mtime:
352 | self.load() # reload
353 |
354 | # get the Data sheet
355 | try:
356 | datash = self.odsfile.sheets['Data']
357 | except KeyError:
358 | raise InvalidODS
359 |
360 |
361 | # save data in a new line
362 | if replace_row is None:
363 | rowid = self._get_first_empty_row()
364 | self.data.append([])
365 | self._first_empty_row += 1
366 | else:
367 | rowid = replace_row
368 |
369 | # fill the row with values
370 | for colid in range(1, datash.ncols()):
371 | column = datash.column(colid)
372 | fname = column[0].value
373 | if fname in values and values[fname]:
374 | value = (1 if values[fname] is True else values[fname])
375 | try:
376 | column[rowid].set_value(value)
377 | except IndexError:
378 | # add row
379 | datash.append_rows(1)
380 | column = datash.column(colid)
381 | column[rowid].set_value(value)
382 |
383 | # convert to boolean for cached data
384 | if self.fields[colid-1].ident.startswith('check') and value:
385 | value = bool(value)
386 | try:
387 | self.data[rowid - HEADERS_ROW][colid - 1] = value
388 | except IndexError:
389 | self.data[rowid - HEADERS_ROW].append(value)
390 | else:
391 | try:
392 | self.data[rowid - HEADERS_ROW][colid - 1] = None
393 | except IndexError:
394 | self.data[rowid - HEADERS_ROW].append(None)
395 |
396 | if self.seaf:
397 | # save spreadsheet into a temporary file
398 | with NamedTemporaryFile(delete=False) as tmpfile:
399 | # ezodf realy doesn't like file-like objects…
400 | tmpname = tmpfile.name
401 | self.odsfile.saveas(tmpname)
402 |
403 | # update distant file
404 | with open(tmpname, 'rb') as fileo:
405 | fid = self.seaf.update_file(self.repo_id, self.filepath, fileo)
406 | # unlink tmp file
407 | os.unlink(tmpname)
408 | else:
409 | # save spreadsheet into the local file
410 | self.odsfile.saveas(self.filepath)
411 |
412 | def get_values_from_data(self, row_id):
413 | """Build the dict of {'name': value} for row_id from self.data"""
414 | vals = {}
415 | for i, field in enumerate(self.fields):
416 | vals[field.label] = self.data[row_id - HEADERS_ROW][i]
417 | return vals
418 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/seafile.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###############################################################################
3 | # seafform/seafile.py
4 | #
5 | # Copyright © 2015, Florian Birée
6 | #
7 | # This file is a part of seafform.
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU Affero General Public License as
11 | # published by the Free Software Foundation, either version 3 of the
12 | # License, or (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU Affero General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU Affero General Public License
20 | # along with this program. If not, see .
21 | #
22 | ###############################################################################
23 | """Seafile API wrapper"""
24 |
25 | __author__ = "Florian Birée"
26 | __version__ = "0.2"
27 | __license__ = "AGPLv3"
28 | __copyright__ = "Copyright © 2015, Florian Birée "
29 |
30 | import os
31 | from functools import wraps
32 | from urllib.parse import urljoin, urlparse
33 | import requests
34 | from requests.exceptions import HTTPError
35 | import json
36 |
37 | # HTTP verbs
38 | GET = requests.get
39 | POST = requests.post
40 | PUT = requests.put
41 | DELETE = requests.delete
42 |
43 | # Seafile exceptions
44 | class SeafileError(Exception):
45 | """Base class for all Seafile-related exceptions"""
46 |
47 | class NotAuthenticated(SeafileError):
48 | """Raised when trying an operation that need authentication"""
49 | pass
50 |
51 | class AuthError(SeafileError):
52 | """Authentification error"""
53 | pass
54 |
55 | class APIError(SeafileError):
56 | """API error"""
57 | msg = "Seafile API error"
58 | def __init__(self, curl_cmd=None, msg=None):
59 | """APIError(curl_cmd, *args)"""
60 | self.curl_cmd = curl_cmd
61 | if msg:
62 | self.msg = msg
63 |
64 | def __add__curl(self, msg):
65 | if self.curl_cmd:
66 | return msg + '\n$ ' + self.curl_cmd
67 | else:
68 | return msg
69 |
70 | def __str__(self):
71 | return self.__add__curl(self.msg)
72 |
73 | class BadPath(APIError):
74 | code = 400
75 | msg = "400 Path is missing/bad."
76 |
77 | class Forbidden(APIError):
78 | code = 403
79 | msg = "403 Forbidden"
80 |
81 | class NotFound(APIError):
82 | code = 404
83 | msg = "404 The path does not exists."
84 |
85 | class InvalidPath(APIError):
86 | code = 440
87 | msg = "440 Invalid path or filname, or encrypted repo."
88 |
89 | class FileExists(APIError):
90 | code = 441
91 | msg = "441 File already exists."
92 |
93 | class InternalServerError(APIError):
94 | code = 500
95 | msg = "500 Internal server error (may be out of quota)."
96 |
97 | class OperationFailed(APIError):
98 | code = 520
99 | msg = "520 Operation failed."
100 |
101 | EXCEPT_CODE = {
102 | '400': BadPath,
103 | '403': Forbidden,
104 | '404': NotFound,
105 | '440': InvalidPath,
106 | '441': FileExists,
107 | '500': InternalServerError,
108 | '520': OperationFailed,
109 | }
110 |
111 | def curlify(request):
112 | """Return a curl command line corresponding to the `request`"""
113 | return (
114 | 'curl -v ' +
115 | ('-X PUT ' if request.method == 'PUT' else '') +
116 | (('-d "%s" ' % request.body) if request.body else '') +
117 | ' '.join("-H '%s: %s'" % (k, v) for (k, v) in request.headers.items()) +
118 | ' ' +
119 | request.url
120 | )
121 |
122 | class Seafile:
123 | """Seafile connector"""
124 |
125 | base_api = 'api2/'
126 |
127 | # internals
128 |
129 | def __init__(self, url, verifycerts=True):
130 | """New seafile connector to `url` instance
131 |
132 | set verifycerts to False to disable TLS certificate verification
133 | """
134 | self.url = url
135 | self._api_url = urljoin(self.url, self.base_api)
136 | self.token = None
137 | self.email = None
138 | self.verify = verifycerts
139 |
140 | def _need_auth(func):
141 | """Decorator to ensure the connector is authentified before
142 | executing `func`"""
143 | @wraps(func)
144 | def wrapper(self, *args, **kwargs):
145 | if self.token is None:
146 | raise NotAuthenticated
147 | return func(self, *args, **kwargs)
148 | return wrapper
149 |
150 | def __repr__(self):
151 | """String representation of the object"""
152 | hostname = urlparse(self.url).hostname
153 | if self.token is None:
154 | return "".format({'hostname': hostname})
155 | else:
156 | return "".format({
157 | 'email': self.email,
158 | 'hostname': hostname
159 | })
160 |
161 | def _api(self, verb, cmd, params=None, data=None, headers=None, files=None,
162 | token=True, raw_url=False):
163 | """Execute the API command VERB `cmd`
164 |
165 | VERB is one of GET, POST, PUT, DELETE
166 |
167 | `params` : optionals ?= params
168 | `headers` : optionals headers
169 | `data` : optionals POST or PUT data
170 | `files` : POST data content using the Content-Type
171 | multipart/form-data (RFC 2388). If a value in the data dict is
172 | an opened file object, it will be sent as a file.
173 |
174 | if `token`, add the Authorization header
175 | if `raw_url`, use `cmd` as full url instead of concatenating `cmd`
176 | to the API url.
177 |
178 | Return json-loaded data
179 | """
180 | final_headers = {'Accept': 'application/json; indent=4; charset=utf-8'}
181 | if headers:
182 | final_headers.update(headers)
183 | if token:
184 | final_headers['Authorization'] = 'Token ' + self.token
185 |
186 | r = verb(
187 | urljoin(self._api_url, cmd) if not raw_url else cmd,
188 | params=params,
189 | headers=final_headers,
190 | data=data,
191 | files=files,
192 | verify=self.verify
193 | )
194 | try:
195 | r.raise_for_status()
196 | except HTTPError:
197 | apierror = EXCEPT_CODE.get(r.status_code, APIError)
198 | raise apierror(
199 | curlify(r.request),
200 | r.text
201 | )
202 | else:
203 | return r.json()
204 |
205 | def _multipart_filname_patching(self, prepped, filename):
206 | """Since Seafile doesn't handle well RFC2231
207 | wich specify to send filename*= field for utf-8 characters
208 | (which is what Requests does), we patch the request body
209 | to put just a raw utf-8 filename (like curl does)
210 |
211 | this is a dirty hack
212 |
213 | `prepped` is a prepared request
214 | `filename` is the str filename to encode in raw utf-8
215 |
216 | return the patched prepped
217 | """
218 | starbytes = b'filename*='
219 | # find the start of starbytes
220 | start = prepped.body.find(starbytes)
221 | if start == -1:
222 | # not here, return prepped unchanged
223 | return prepped
224 | # find the first \r\n sequence after starbytes (end of filename)
225 | end = prepped.body.find(b'\r\n', start)
226 |
227 | # patch the body
228 | prepped.body = (
229 | prepped.body[:start] +
230 | b'filename="' + filename.encode('utf8') + b'"' +
231 | prepped.body[end:]
232 | )
233 | # recompute the Content-Length header
234 | prepped.headers['Content-Length'] = str(len(prepped.body))
235 | return prepped
236 |
237 | # auth methods
238 |
239 | def authenticate(self, email, password=None, token=None, validate=True):
240 | """Authenticate against the Seafile server, with `email` and either:
241 | - `password` : to authenticate with a password
242 | - `token` : to reuse a token from a previous authentication
243 |
244 | if success, the token is available at self.token
245 | else raise AuthError
246 |
247 | if not validate, the Seafile connector will not check the validy of
248 | the token (if token authentication).
249 | No seafile request will be done. If you are sure about your
250 | token validy, this will save time.
251 | """
252 | if password is None and token is None:
253 | raise ValueError
254 | elif token is not None: # token auth
255 | if validate:
256 | # try to validate the token
257 | try:
258 | self._auth_ping(token)
259 | except SeafileError:
260 | raise AuthError
261 | else:
262 | self.token = token
263 | self.email = email
264 | else:
265 | self.token = token
266 | self.email = email
267 | else: # password auth
268 | try:
269 | resp = self._api(POST, 'auth-token/', data={
270 | 'username': email,
271 | 'password': password
272 | }, token=False)
273 | except:
274 | raise AuthError
275 | else:
276 | if resp['token']:
277 | self.token = resp['token']
278 | self.email = email
279 |
280 | # test methods
281 |
282 | def ping(self):
283 | """Ping the Seafile server
284 |
285 | raise SeafileError if not working
286 | """
287 | if not self._api(GET, 'ping/', token=False) == "pong":
288 | raise SeafileError
289 |
290 | def _auth_ping(self, token):
291 | """Ping the Seafile server with the token `token`
292 |
293 | raise SeafileError if not working
294 | """
295 | # here the token may not be validated, so we do not use _api(token=True)
296 | headers = {'Authorization': 'Token ' + token}
297 | resp = self._api(GET, 'auth/ping/', headers=headers, token=False)
298 | if resp != "pong":
299 | raise SeafileError
300 |
301 | @_need_auth
302 | def auth_ping(self):
303 | """Ping the Seafile server with the token `token`
304 |
305 | raise SeafileError if not working
306 | """
307 | self._auth_ping(self.token)
308 |
309 | # library methods
310 |
311 | @_need_auth
312 | def list_repos(self):
313 | """List repos/librarys
314 |
315 | return a list of {
316 | "permission": "rw",
317 | "encrypted": false,
318 | "mtime": 1400054900,
319 | "owner": "user@mail.com",
320 | "id": "f158d1dd-cc19-412c-b143-2ac83f352290",
321 | "size": 0,
322 | "name": "foo",
323 | "type": "repo",
324 | "virtual": false,
325 | "desc": "new library",
326 | "root": "0000000000000000000000000000000000000000"
327 | }
328 | """
329 | return self._api(GET, 'repos/')
330 |
331 | # directory methods
332 |
333 | @_need_auth
334 | def list_dir(self, repo_id, path="/"):
335 | """list the content of a directory from library `repo_id`/`path`
336 |
337 | return a list of {
338 | "id": "e4fe14c8cda2206bb9606907cf4fca6b30221cf9",
339 | "type": "file|dir",
340 | "name": "test",
341 | "size": 0, # only for files
342 | }
343 |
344 | Errors:
345 | 404 NotFound
346 | 440 InvalidPath (encrypted repo)
347 | 520 OperationFailed
348 | """
349 | return self._api(
350 | GET,
351 | 'repos/{repo_id}/dir/'.format(repo_id=repo_id),
352 | {'p': path}
353 | )
354 |
355 | # files methods
356 |
357 | @_need_auth
358 | def open_file(self, repo_id, path):
359 | """get the file `repo_id`/`path`.
360 |
361 | Return an opened requests.Response.raw file-like object
362 |
363 | Errors:
364 | 400 BadPath
365 | 404 NotFound
366 | 520 OperationFailed
367 | """
368 | # get the file link
369 | flink = self._api(
370 | GET,
371 | 'repos/{repo_id}/file/'.format(repo_id=repo_id),
372 | {'p': path}
373 | )
374 | resp = requests.get(flink, stream=True)
375 | resp.raw.decode_content = True
376 | return resp.raw
377 |
378 | @_need_auth
379 | def lock_file(self, repo_id, path):
380 | """lock the file `repo_id`/`path`.
381 |
382 | Not implemented server side.
383 | """
384 | raise NotImplementedError
385 | #return (self._api_cmd(
386 | # 'repos/{repo_id}/file/'.format(repo_id=repo_id),
387 | # {'operation': 'lock', 'p': path},
388 | # put=True
389 | #) == "success")
390 |
391 | @_need_auth
392 | def unlock_file(self, repo_id, path):
393 | """unlock the file `repo_id`/`path`.
394 |
395 | Not implemented server side.
396 | """
397 | raise NotImplementedError
398 | #return (self._api_cmd(
399 | # 'repos/{repo_id}/file/'.format(repo_id=repo_id),
400 | # {'operation': 'unlock', 'p': path},
401 | # put=True
402 | #) == "success")
403 |
404 | @_need_auth
405 | def upload_file(self, repo_id, parent, fileo):
406 | """upload the file object `fileo` to `repo_id`/`parent`
407 |
408 | Return the file id
409 |
410 | Errors:
411 | 400 BadPath
412 | 440 InvalidPath
413 | 441 FileExists
414 | 500 InternalServerError (out of quota)
415 | """
416 | # get upload link
417 | up_link = self._api(GET,
418 | 'repos/{repo_id}/upload-link/'.format(repo_id=repo_id)
419 | )
420 |
421 | filename = os.path.split(fileo.name)[1]
422 | # upload file
423 | s = requests.Session()
424 | req = requests.Request('POST',
425 | up_link,
426 | files={
427 | 'filename': (None, filename),
428 | 'parent_dir': (None, parent),
429 | 'file': (filename, fileo, 'application/octet-stream'),
430 | },
431 | verify=self.verify
432 | )
433 | prepped = self._multipart_filname_patching(req.prepare(), filename)
434 |
435 | r = s.send(prepped, stream=False)
436 | try:
437 | r.raise_for_status()
438 | except HTTPError:
439 | apierror = EXCEPT_CODE.get(r.status_code, APIError)
440 | raise apierror(
441 | curlify(r.request),
442 | r.text
443 | )
444 | else:
445 | return r.text
446 |
447 | @_need_auth
448 | def update_file(self, repo_id, filepath, fileo):
449 | """update the file `repo_id`/`filepathe` with the file object `fileo`
450 |
451 | Return the file id
452 |
453 | Errors:
454 | 400 BadPath
455 | 440 InvalidPath
456 | 500 InternalServerError (out of quota)
457 | """
458 | # get update link
459 | up_link = self._api(GET,
460 | 'repos/{repo_id}/update-link/'.format(repo_id=repo_id)
461 | )
462 |
463 | filename = os.path.split(fileo.name)[1]
464 | # upload file
465 | s = requests.Session()
466 | req = requests.Request('POST',
467 | up_link,
468 | files={
469 | #'filename': (None, filename),
470 | 'target_file': (None, filepath),
471 | 'file': (filename, fileo, 'application/octet-stream'),
472 | }
473 | )
474 | prepped = self._multipart_filname_patching(req.prepare(), filename)
475 |
476 | r = s.send(prepped, stream=False, verify=self.verify)
477 | try:
478 | r.raise_for_status()
479 | except HTTPError:
480 | apierror = EXCEPT_CODE.get(r.status_code, APIError)
481 | raise apierror(
482 | curlify(r.request),
483 | r.text
484 | )
485 | else:
486 | return r.text
487 |
488 |
489 | @_need_auth
490 | def delete_file(self, repo_id, path):
491 | """delete the file or directory `repo_id`/`path`
492 |
493 | Errors:
494 | 400 BadPath
495 | 520 OperationFailed
496 | """
497 | return (self._api(DELETE,
498 | 'repos/{repo_id}/file/'.format(repo_id=repo_id),
499 | params={'p': path}
500 | ) == "success")
501 |
502 | @_need_auth
503 | def stat_file(self, repo_id, path):
504 | """get data about the file `repo_id`/`path`
505 |
506 | Return {'id', 'mtime', 'type':'file', 'name', 'size'}
507 |
508 | Errors:
509 | 400 BadPath
510 | 520 OperationFailed
511 | """
512 | return self._api(GET,
513 | 'repos/{repo_id}/file/detail/'.format(repo_id=repo_id),
514 | params={'p': path}
515 | )
516 |
517 |
518 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/static/bootstrap/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "vars": {
3 | "@gray-base": "#000",
4 | "@gray-darker": "lighten(@gray-base, 13.5%)",
5 | "@gray-dark": "lighten(@gray-base, 20%)",
6 | "@gray": "lighten(@gray-base, 33.5%)",
7 | "@gray-light": "lighten(@gray-base, 46.7%)",
8 | "@gray-lighter": "lighten(@gray-base, 93.5%)",
9 | "@brand-primary": "#FD700F",
10 | "@brand-success": "#5cb85c",
11 | "@brand-info": "#FEAC74",
12 | "@brand-warning": "#f0ad4e",
13 | "@brand-danger": "#d9534f",
14 | "@body-bg": "#fff",
15 | "@text-color": "@gray-dark",
16 | "@link-color": "@brand-primary",
17 | "@link-hover-color": "darken(@link-color, 15%)",
18 | "@link-hover-decoration": "underline",
19 | "@font-family-sans-serif": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
20 | "@font-family-serif": "Georgia, \"Times New Roman\", Times, serif",
21 | "@font-family-monospace": "Menlo, Monaco, Consolas, \"Courier New\", monospace",
22 | "@font-family-base": "@font-family-sans-serif",
23 | "@font-size-base": "14px",
24 | "@font-size-large": "ceil((@font-size-base * 1.25))",
25 | "@font-size-small": "ceil((@font-size-base * 0.85))",
26 | "@font-size-h1": "floor((@font-size-base * 2.6))",
27 | "@font-size-h2": "floor((@font-size-base * 2.15))",
28 | "@font-size-h3": "ceil((@font-size-base * 1.7))",
29 | "@font-size-h4": "ceil((@font-size-base * 1.25))",
30 | "@font-size-h5": "@font-size-base",
31 | "@font-size-h6": "ceil((@font-size-base * 0.85))",
32 | "@line-height-base": "1.428571429",
33 | "@line-height-computed": "floor((@font-size-base * @line-height-base))",
34 | "@headings-font-family": "inherit",
35 | "@headings-font-weight": "500",
36 | "@headings-line-height": "1.1",
37 | "@headings-color": "inherit",
38 | "@icon-font-path": "\"../fonts/\"",
39 | "@icon-font-name": "\"glyphicons-halflings-regular\"",
40 | "@icon-font-svg-id": "\"glyphicons_halflingsregular\"",
41 | "@padding-base-vertical": "6px",
42 | "@padding-base-horizontal": "12px",
43 | "@padding-large-vertical": "10px",
44 | "@padding-large-horizontal": "16px",
45 | "@padding-small-vertical": "5px",
46 | "@padding-small-horizontal": "10px",
47 | "@padding-xs-vertical": "1px",
48 | "@padding-xs-horizontal": "5px",
49 | "@line-height-large": "1.3333333",
50 | "@line-height-small": "1.5",
51 | "@border-radius-base": "4px",
52 | "@border-radius-large": "6px",
53 | "@border-radius-small": "3px",
54 | "@component-active-color": "#fff",
55 | "@component-active-bg": "@brand-primary",
56 | "@caret-width-base": "4px",
57 | "@caret-width-large": "5px",
58 | "@table-cell-padding": "8px",
59 | "@table-condensed-cell-padding": "5px",
60 | "@table-bg": "transparent",
61 | "@table-bg-accent": "#f9f9f9",
62 | "@table-bg-hover": "#f5f5f5",
63 | "@table-bg-active": "@table-bg-hover",
64 | "@table-border-color": "#ddd",
65 | "@btn-font-weight": "normal",
66 | "@btn-default-color": "#333",
67 | "@btn-default-bg": "#fff",
68 | "@btn-default-border": "#ccc",
69 | "@btn-primary-color": "#fff",
70 | "@btn-primary-bg": "@brand-primary",
71 | "@btn-primary-border": "darken(@btn-primary-bg, 5%)",
72 | "@btn-success-color": "#fff",
73 | "@btn-success-bg": "@brand-success",
74 | "@btn-success-border": "darken(@btn-success-bg, 5%)",
75 | "@btn-info-color": "#fff",
76 | "@btn-info-bg": "@brand-info",
77 | "@btn-info-border": "darken(@btn-info-bg, 5%)",
78 | "@btn-warning-color": "#fff",
79 | "@btn-warning-bg": "@brand-warning",
80 | "@btn-warning-border": "darken(@btn-warning-bg, 5%)",
81 | "@btn-danger-color": "#fff",
82 | "@btn-danger-bg": "@brand-danger",
83 | "@btn-danger-border": "darken(@btn-danger-bg, 5%)",
84 | "@btn-link-disabled-color": "@gray-light",
85 | "@btn-border-radius-base": "@border-radius-base",
86 | "@btn-border-radius-large": "@border-radius-large",
87 | "@btn-border-radius-small": "@border-radius-small",
88 | "@input-bg": "#fff",
89 | "@input-bg-disabled": "@gray-lighter",
90 | "@input-color": "@gray",
91 | "@input-border": "#ccc",
92 | "@input-border-radius": "@border-radius-base",
93 | "@input-border-radius-large": "@border-radius-large",
94 | "@input-border-radius-small": "@border-radius-small",
95 | "@input-border-focus": "@brand-primary",
96 | "@input-color-placeholder": "#999",
97 | "@input-height-base": "(@line-height-computed + (@padding-base-vertical * 2) + 2)",
98 | "@input-height-large": "(ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2)",
99 | "@input-height-small": "(floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2)",
100 | "@form-group-margin-bottom": "15px",
101 | "@legend-color": "@gray-dark",
102 | "@legend-border-color": "@brand-primary",
103 | "@input-group-addon-bg": "@gray-lighter",
104 | "@input-group-addon-border-color": "@input-border",
105 | "@cursor-disabled": "not-allowed",
106 | "@dropdown-bg": "#fff",
107 | "@dropdown-border": "rgba(0,0,0,.15)",
108 | "@dropdown-fallback-border": "#ccc",
109 | "@dropdown-divider-bg": "#e5e5e5",
110 | "@dropdown-link-color": "@gray-dark",
111 | "@dropdown-link-hover-color": "darken(@gray-dark, 5%)",
112 | "@dropdown-link-hover-bg": "#f5f5f5",
113 | "@dropdown-link-active-color": "@component-active-color",
114 | "@dropdown-link-active-bg": "@component-active-bg",
115 | "@dropdown-link-disabled-color": "@gray-light",
116 | "@dropdown-header-color": "@gray-light",
117 | "@dropdown-caret-color": "#000",
118 | "@screen-xs": "480px",
119 | "@screen-xs-min": "@screen-xs",
120 | "@screen-phone": "@screen-xs-min",
121 | "@screen-sm": "768px",
122 | "@screen-sm-min": "@screen-sm",
123 | "@screen-tablet": "@screen-sm-min",
124 | "@screen-md": "992px",
125 | "@screen-md-min": "@screen-md",
126 | "@screen-desktop": "@screen-md-min",
127 | "@screen-lg": "1200px",
128 | "@screen-lg-min": "@screen-lg",
129 | "@screen-lg-desktop": "@screen-lg-min",
130 | "@screen-xs-max": "(@screen-sm-min - 1)",
131 | "@screen-sm-max": "(@screen-md-min - 1)",
132 | "@screen-md-max": "(@screen-lg-min - 1)",
133 | "@grid-columns": "12",
134 | "@grid-gutter-width": "30px",
135 | "@grid-float-breakpoint": "@screen-sm-min",
136 | "@grid-float-breakpoint-max": "(@grid-float-breakpoint - 1)",
137 | "@container-tablet": "(720px + @grid-gutter-width)",
138 | "@container-sm": "@container-tablet",
139 | "@container-desktop": "(940px + @grid-gutter-width)",
140 | "@container-md": "@container-desktop",
141 | "@container-large-desktop": "(1140px + @grid-gutter-width)",
142 | "@container-lg": "@container-large-desktop",
143 | "@navbar-height": "50px",
144 | "@navbar-margin-bottom": "@line-height-computed",
145 | "@navbar-border-radius": "@border-radius-base",
146 | "@navbar-padding-horizontal": "floor((@grid-gutter-width / 2))",
147 | "@navbar-padding-vertical": "((@navbar-height - @line-height-computed) / 2)",
148 | "@navbar-collapse-max-height": "340px",
149 | "@navbar-default-color": "#777",
150 | "@navbar-default-bg": "#f8f8f8",
151 | "@navbar-default-border": "darken(@navbar-default-bg, 6.5%)",
152 | "@navbar-default-link-color": "#777",
153 | "@navbar-default-link-hover-color": "#333",
154 | "@navbar-default-link-hover-bg": "transparent",
155 | "@navbar-default-link-active-color": "#555",
156 | "@navbar-default-link-active-bg": "darken(@navbar-default-bg, 6.5%)",
157 | "@navbar-default-link-disabled-color": "#ccc",
158 | "@navbar-default-link-disabled-bg": "transparent",
159 | "@navbar-default-brand-color": "@navbar-default-link-color",
160 | "@navbar-default-brand-hover-color": "darken(@navbar-default-brand-color, 10%)",
161 | "@navbar-default-brand-hover-bg": "transparent",
162 | "@navbar-default-toggle-hover-bg": "#ddd",
163 | "@navbar-default-toggle-icon-bar-bg": "#888",
164 | "@navbar-default-toggle-border-color": "#ddd",
165 | "@navbar-inverse-color": "lighten(@gray-light, 15%)",
166 | "@navbar-inverse-bg": "#222",
167 | "@navbar-inverse-border": "darken(@navbar-inverse-bg, 10%)",
168 | "@navbar-inverse-link-color": "lighten(@gray-light, 15%)",
169 | "@navbar-inverse-link-hover-color": "#fff",
170 | "@navbar-inverse-link-hover-bg": "transparent",
171 | "@navbar-inverse-link-active-color": "@navbar-inverse-link-hover-color",
172 | "@navbar-inverse-link-active-bg": "darken(@navbar-inverse-bg, 10%)",
173 | "@navbar-inverse-link-disabled-color": "#444",
174 | "@navbar-inverse-link-disabled-bg": "transparent",
175 | "@navbar-inverse-brand-color": "@navbar-inverse-link-color",
176 | "@navbar-inverse-brand-hover-color": "#fff",
177 | "@navbar-inverse-brand-hover-bg": "transparent",
178 | "@navbar-inverse-toggle-hover-bg": "#333",
179 | "@navbar-inverse-toggle-icon-bar-bg": "#fff",
180 | "@navbar-inverse-toggle-border-color": "#333",
181 | "@nav-link-padding": "10px 15px",
182 | "@nav-link-hover-bg": "@gray-lighter",
183 | "@nav-disabled-link-color": "@gray-light",
184 | "@nav-disabled-link-hover-color": "@gray-light",
185 | "@nav-tabs-border-color": "#ddd",
186 | "@nav-tabs-link-hover-border-color": "@gray-lighter",
187 | "@nav-tabs-active-link-hover-bg": "@body-bg",
188 | "@nav-tabs-active-link-hover-color": "@gray",
189 | "@nav-tabs-active-link-hover-border-color": "#ddd",
190 | "@nav-tabs-justified-link-border-color": "#ddd",
191 | "@nav-tabs-justified-active-link-border-color": "@body-bg",
192 | "@nav-pills-border-radius": "@border-radius-base",
193 | "@nav-pills-active-link-hover-bg": "@component-active-bg",
194 | "@nav-pills-active-link-hover-color": "@component-active-color",
195 | "@pagination-color": "@link-color",
196 | "@pagination-bg": "#fff",
197 | "@pagination-border": "#ddd",
198 | "@pagination-hover-color": "@link-hover-color",
199 | "@pagination-hover-bg": "@gray-lighter",
200 | "@pagination-hover-border": "#ddd",
201 | "@pagination-active-color": "#fff",
202 | "@pagination-active-bg": "@brand-primary",
203 | "@pagination-active-border": "@brand-primary",
204 | "@pagination-disabled-color": "@gray-light",
205 | "@pagination-disabled-bg": "#fff",
206 | "@pagination-disabled-border": "#ddd",
207 | "@pager-bg": "@pagination-bg",
208 | "@pager-border": "@pagination-border",
209 | "@pager-border-radius": "15px",
210 | "@pager-hover-bg": "@pagination-hover-bg",
211 | "@pager-active-bg": "@pagination-active-bg",
212 | "@pager-active-color": "@pagination-active-color",
213 | "@pager-disabled-color": "@pagination-disabled-color",
214 | "@jumbotron-padding": "30px",
215 | "@jumbotron-color": "inherit",
216 | "@jumbotron-bg": "@gray-lighter",
217 | "@jumbotron-heading-color": "inherit",
218 | "@jumbotron-font-size": "ceil((@font-size-base * 1.5))",
219 | "@jumbotron-heading-font-size": "ceil((@font-size-base * 4.5))",
220 | "@state-success-text": "#3c763d",
221 | "@state-success-bg": "#dff0d8",
222 | "@state-success-border": "darken(spin(@state-success-bg, -10), 5%)",
223 | "@state-info-text": "@text-color",
224 | "@state-info-bg": "lighten(@brand-info, 10%)",
225 | "@state-info-border": "darken(spin(@state-info-bg, -10), 7%)",
226 | "@state-warning-text": "#8a6d3b",
227 | "@state-warning-bg": "#fcf8e3",
228 | "@state-warning-border": "darken(spin(@state-warning-bg, -10), 5%)",
229 | "@state-danger-text": "#a94442",
230 | "@state-danger-bg": "#f2dede",
231 | "@state-danger-border": "darken(spin(@state-danger-bg, -10), 5%)",
232 | "@tooltip-max-width": "200px",
233 | "@tooltip-color": "#fff",
234 | "@tooltip-bg": "#000",
235 | "@tooltip-opacity": ".9",
236 | "@tooltip-arrow-width": "5px",
237 | "@tooltip-arrow-color": "@tooltip-bg",
238 | "@popover-bg": "#fff",
239 | "@popover-max-width": "276px",
240 | "@popover-border-color": "rgba(0,0,0,.2)",
241 | "@popover-fallback-border-color": "#ccc",
242 | "@popover-title-bg": "darken(@popover-bg, 3%)",
243 | "@popover-arrow-width": "10px",
244 | "@popover-arrow-color": "@popover-bg",
245 | "@popover-arrow-outer-width": "(@popover-arrow-width + 1)",
246 | "@popover-arrow-outer-color": "fadein(@popover-border-color, 5%)",
247 | "@popover-arrow-outer-fallback-color": "darken(@popover-fallback-border-color, 20%)",
248 | "@label-default-bg": "@gray-light",
249 | "@label-primary-bg": "@brand-primary",
250 | "@label-success-bg": "@brand-success",
251 | "@label-info-bg": "@brand-info",
252 | "@label-warning-bg": "@brand-warning",
253 | "@label-danger-bg": "@brand-danger",
254 | "@label-color": "#fff",
255 | "@label-link-hover-color": "#fff",
256 | "@modal-inner-padding": "15px",
257 | "@modal-title-padding": "15px",
258 | "@modal-title-line-height": "@line-height-base",
259 | "@modal-content-bg": "#fff",
260 | "@modal-content-border-color": "rgba(0,0,0,.2)",
261 | "@modal-content-fallback-border-color": "#999",
262 | "@modal-backdrop-bg": "#000",
263 | "@modal-backdrop-opacity": ".5",
264 | "@modal-header-border-color": "#e5e5e5",
265 | "@modal-footer-border-color": "@modal-header-border-color",
266 | "@modal-lg": "900px",
267 | "@modal-md": "600px",
268 | "@modal-sm": "300px",
269 | "@alert-padding": "15px",
270 | "@alert-border-radius": "@border-radius-base",
271 | "@alert-link-font-weight": "bold",
272 | "@alert-success-bg": "@state-success-bg",
273 | "@alert-success-text": "@state-success-text",
274 | "@alert-success-border": "@state-success-border",
275 | "@alert-info-bg": "@state-info-bg",
276 | "@alert-info-text": "@state-info-text",
277 | "@alert-info-border": "@state-info-border",
278 | "@alert-warning-bg": "@state-warning-bg",
279 | "@alert-warning-text": "@state-warning-text",
280 | "@alert-warning-border": "@state-warning-border",
281 | "@alert-danger-bg": "@state-danger-bg",
282 | "@alert-danger-text": "@state-danger-text",
283 | "@alert-danger-border": "@state-danger-border",
284 | "@progress-bg": "#f5f5f5",
285 | "@progress-bar-color": "#fff",
286 | "@progress-border-radius": "@border-radius-base",
287 | "@progress-bar-bg": "@brand-primary",
288 | "@progress-bar-success-bg": "@brand-success",
289 | "@progress-bar-warning-bg": "@brand-warning",
290 | "@progress-bar-danger-bg": "@brand-danger",
291 | "@progress-bar-info-bg": "@brand-info",
292 | "@list-group-bg": "#fff",
293 | "@list-group-border": "#ddd",
294 | "@list-group-border-radius": "@border-radius-base",
295 | "@list-group-hover-bg": "#f5f5f5",
296 | "@list-group-active-color": "@component-active-color",
297 | "@list-group-active-bg": "@component-active-bg",
298 | "@list-group-active-border": "@list-group-active-bg",
299 | "@list-group-active-text-color": "lighten(@list-group-active-bg, 40%)",
300 | "@list-group-disabled-color": "@gray-light",
301 | "@list-group-disabled-bg": "@gray-lighter",
302 | "@list-group-disabled-text-color": "@list-group-disabled-color",
303 | "@list-group-link-color": "#555",
304 | "@list-group-link-hover-color": "@list-group-link-color",
305 | "@list-group-link-heading-color": "#333",
306 | "@panel-bg": "#fff",
307 | "@panel-body-padding": "15px",
308 | "@panel-heading-padding": "10px 15px",
309 | "@panel-footer-padding": "@panel-heading-padding",
310 | "@panel-border-radius": "@border-radius-base",
311 | "@panel-inner-border": "#ddd",
312 | "@panel-footer-bg": "#f5f5f5",
313 | "@panel-default-text": "@gray-dark",
314 | "@panel-default-border": "#ddd",
315 | "@panel-default-heading-bg": "#f5f5f5",
316 | "@panel-primary-text": "#fff",
317 | "@panel-primary-border": "@brand-primary",
318 | "@panel-primary-heading-bg": "@brand-primary",
319 | "@panel-success-text": "@state-success-text",
320 | "@panel-success-border": "@state-success-border",
321 | "@panel-success-heading-bg": "@state-success-bg",
322 | "@panel-info-text": "@state-info-text",
323 | "@panel-info-border": "@state-info-border",
324 | "@panel-info-heading-bg": "@state-info-bg",
325 | "@panel-warning-text": "@state-warning-text",
326 | "@panel-warning-border": "@state-warning-border",
327 | "@panel-warning-heading-bg": "@state-warning-bg",
328 | "@panel-danger-text": "@state-danger-text",
329 | "@panel-danger-border": "@state-danger-border",
330 | "@panel-danger-heading-bg": "@state-danger-bg",
331 | "@thumbnail-padding": "4px",
332 | "@thumbnail-bg": "@body-bg",
333 | "@thumbnail-border": "#ddd",
334 | "@thumbnail-border-radius": "@border-radius-base",
335 | "@thumbnail-caption-color": "@text-color",
336 | "@thumbnail-caption-padding": "9px",
337 | "@well-bg": "#F2F2F2",
338 | "@well-border": "darken(@well-bg, 7%)",
339 | "@badge-color": "#fff",
340 | "@badge-link-hover-color": "#fff",
341 | "@badge-bg": "@gray-light",
342 | "@badge-active-color": "@link-color",
343 | "@badge-active-bg": "#fff",
344 | "@badge-font-weight": "bold",
345 | "@badge-line-height": "1",
346 | "@badge-border-radius": "10px",
347 | "@breadcrumb-padding-vertical": "8px",
348 | "@breadcrumb-padding-horizontal": "15px",
349 | "@breadcrumb-bg": "#f5f5f5",
350 | "@breadcrumb-color": "#ccc",
351 | "@breadcrumb-active-color": "@gray-light",
352 | "@breadcrumb-separator": "\"/\"",
353 | "@carousel-text-shadow": "0 1px 2px rgba(0,0,0,.6)",
354 | "@carousel-control-color": "#fff",
355 | "@carousel-control-width": "15%",
356 | "@carousel-control-opacity": ".5",
357 | "@carousel-control-font-size": "20px",
358 | "@carousel-indicator-active-bg": "#fff",
359 | "@carousel-indicator-border-color": "#fff",
360 | "@carousel-caption-color": "#fff",
361 | "@close-font-weight": "bold",
362 | "@close-color": "#000",
363 | "@close-text-shadow": "0 1px 0 #fff",
364 | "@code-color": "#c7254e",
365 | "@code-bg": "#f9f2f4",
366 | "@kbd-color": "#fff",
367 | "@kbd-bg": "#333",
368 | "@pre-bg": "#f5f5f5",
369 | "@pre-color": "@gray-dark",
370 | "@pre-border-color": "#ccc",
371 | "@pre-scrollable-max-height": "340px",
372 | "@component-offset-horizontal": "180px",
373 | "@text-muted": "@gray-light",
374 | "@abbr-border-color": "@gray-light",
375 | "@headings-small-color": "@gray-light",
376 | "@blockquote-small-color": "@gray-light",
377 | "@blockquote-font-size": "(@font-size-base * 1.25)",
378 | "@blockquote-border-color": "@gray-lighter",
379 | "@page-header-border-color": "@gray-lighter",
380 | "@dl-horizontal-offset": "@component-offset-horizontal",
381 | "@hr-border": "@gray-lighter"
382 | },
383 | "css": [
384 | "print.less",
385 | "type.less",
386 | "code.less",
387 | "grid.less",
388 | "tables.less",
389 | "forms.less",
390 | "buttons.less",
391 | "responsive-utilities.less",
392 | "glyphicons.less",
393 | "button-groups.less",
394 | "input-groups.less",
395 | "navs.less",
396 | "navbar.less",
397 | "badges.less",
398 | "jumbotron.less",
399 | "alerts.less",
400 | "panels.less",
401 | "wells.less",
402 | "component-animations.less"
403 | ],
404 | "js": [
405 | "button.js",
406 | "affix.js",
407 | "collapse.js",
408 | "scrollspy.js",
409 | "transition.js"
410 | ],
411 | "customizerUrl": "http://getbootstrap.com/customize/?id=4883ecbae98d09844da6"
412 | }
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###############################################################################
3 | # seafform/views.py
4 | #
5 | # Copyright © 2015, Florian Birée
6 | #
7 | # This file is a part of seafform.
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU Affero General Public License as
11 | # published by the Free Software Foundation, either version 3 of the
12 | # License, or (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU Affero General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU Affero General Public License
20 | # along with this program. If not, see .
21 | #
22 | ###############################################################################
23 | """Seafform views"""
24 |
25 | __author__ = "Florian Birée"
26 | __version__ = "0.2"
27 | __license__ = "AGPLv3"
28 | __copyright__ = "Copyright © 2015, Florian Birée "
29 |
30 | import os
31 | from urllib.parse import quote, unquote
32 | import itertools
33 | from django.utils.text import slugify
34 | from django.core.urlresolvers import reverse
35 | from django.contrib.auth.models import User
36 | from django.http import HttpResponseRedirect, Http404
37 | from django.shortcuts import render
38 | from django.contrib.auth.decorators import login_required
39 | from django.contrib.auth import authenticate, login, logout
40 | from django.views.decorators.csrf import csrf_exempt
41 | from django.utils import timezone
42 | from seafform.models import SeafileUser, Form
43 | from seafform.forms import LoginForm, DjForm
44 | from seafform.seafile import Seafile, AuthError, APIError
45 | from seafform.seafform import SeafForm, HEADERS_ROW
46 | from django.conf import settings
47 |
48 | def _log(request, email, password, nextview):
49 | """ Authenticate or create a new account """
50 | seaf_root = settings.SEAFILE_ROOT
51 | user = authenticate(username=email, password=password)
52 | # if known user:
53 | if user is not None and user.is_active:
54 | login(request, user)
55 | # login, -> nexturl
56 | return HttpResponseRedirect(reverse(nextview))
57 | elif user is not None: # not active
58 | raise AuthError
59 | else:
60 | # try to connect to seafile using credentials
61 | seaf = Seafile(seaf_root, verifycerts=settings.VERIFYCERTS)
62 | seaf.authenticate(email, password) # may raise AuthError
63 | token = seaf.token
64 | # create new user, save the token
65 | user = User.objects.create_user(email, email, password)
66 | user.save()
67 | seafuser = SeafileUser(user=user, seafroot=seaf_root,
68 | seaftoken=token)
69 | seafuser.save()
70 | # login
71 | user2 = authenticate(username=email, password=password)
72 | login(request, user2)
73 | # -> nextview
74 | return HttpResponseRedirect(reverse(nextview))
75 |
76 | def index(request):
77 | """Main login view"""
78 | #TODO: whatif the seafile password change?
79 | # the user should with it's old password, and should be able to
80 | # enter its new password and resync
81 | justlogout = False
82 | autherror = False
83 |
84 | # if authenticated, redirect to /private and no public forms
85 | if not settings.ALLOW_PUBLIC and request.user.is_authenticated():
86 | return HttpResponseRedirect(reverse('private'))
87 |
88 | # if this is a POST request we need to process the form data
89 | if request.method == 'POST':
90 | # create a form instance and populate it with data from the request:
91 | form = LoginForm(request.POST)
92 | # check whether it's valid:
93 | if form.is_valid():
94 |
95 | email = form.cleaned_data['email']
96 | password = form.cleaned_data['password']
97 |
98 | nextstep = (
99 | 'index'
100 | if (settings.ALLOW_PUBLIC and settings.PUBLIC_NEED_AUTH)
101 | else 'private'
102 | )
103 |
104 | try:
105 | return _log(request, email, password, nextstep)
106 | except AuthError:
107 | autherror = True
108 |
109 | # if a GET (or any other method) we'll create a blank form
110 | else:
111 | form = LoginForm()
112 |
113 | if 'action' in request.GET:
114 | justlogout = (request.GET['action'] == 'logout')
115 |
116 | return render(request, 'seafform/index.html', {
117 | 'loginform': form,
118 | 'autherror': autherror,
119 | 'justlogout': justlogout,
120 | 'seaf_root': settings.SEAFILE_ROOT,
121 | 'allow_public': settings.ALLOW_PUBLIC,
122 | 'public_needauth': settings.PUBLIC_NEED_AUTH,
123 | 'authenticated': request.user.is_authenticated(),
124 | 'public_forms': Form.objects.filter(public=True).\
125 | order_by('-creation_datetime'),
126 | 'show_public': (
127 | settings.ALLOW_PUBLIC and (
128 | request.user.is_authenticated()
129 | or
130 | not settings.PUBLIC_NEED_AUTH
131 | )),
132 | })
133 |
134 | @login_required(login_url='index')
135 | def private(request):
136 | """Home of private pages"""
137 | newform = None
138 | delformtitle = None
139 | deleted = None
140 | if request.method == 'POST': #delete
141 | deleteid = request.POST.get('deleteid', '')
142 | try:
143 | tobedeleted = Form.objects.get(formid=deleteid, owner=request.user)
144 | except Form.DoesNotExist:
145 | # do nothing
146 | pass
147 | else:
148 | # delete, redirect plus message
149 | deleted = tobedeleted.title
150 | tobedeleted.delete()
151 |
152 | if 'newform' in request.GET:
153 | try:
154 | newform = Form.objects.get(formid=request.GET['newform'],
155 | owner=request.user)
156 | except Form.DoesNotExist:
157 | pass
158 |
159 | return render(request, 'seafform/private.html', {
160 | 'user': request.user,
161 | 'forms': None,
162 | 'tplurl': settings.TPL_URL,
163 | 'forms': Form.objects.filter(owner=request.user).\
164 | order_by('-creation_datetime'),
165 | 'newform': newform,
166 | 'deleted': deleted,
167 | 'allow_public': settings.ALLOW_PUBLIC,
168 | })
169 |
170 | def logout_view(request):
171 | """Just… log out"""
172 | logout(request)
173 | # Redirect to a success page.
174 | return HttpResponseRedirect(reverse('index') + '?action=logout')
175 |
176 | @login_required(login_url='index')
177 | def new(request):
178 | """Create a new form"""
179 | # if this is a POST request we need to process the form data
180 | if request.method == 'POST':
181 | path = unquote(request.POST.get('path', ''))
182 | #print('new/path=' + path)
183 | if path.endswith('.ods'):
184 | if settings.LOCAL:
185 | filepath = os.path.join(settings.LOCAL_ROOT, path.lstrip('/'))
186 | seaf = None
187 | repoid = 'LOCAL'
188 | reponame = 'LOCAL'
189 | else:
190 | # Connect to Seafile
191 | seafu = request.user.seafileuser
192 | seaf = Seafile(seafu.seafroot, verifycerts=settings.VERIFYCERTS)
193 | seaf.authenticate(request.user.email, token=seafu.seaftoken, validate=False)
194 | # retreive path info
195 | parsed_path = parse(path)
196 | filepath = parsed_path['path']
197 | repoid = repo_id_from_name(seaf, parsed_path['repo_name'])
198 | reponame = parsed_path['repo_name']
199 | # load the form
200 | seafform = SeafForm(filepath, seaf, repoid)
201 | seafform.load()
202 | # create the slug/formid
203 | max_length = Form._meta.get_field('formid').max_length
204 | formid = orig = slugify(seafform.title)[:max_length]
205 |
206 | for x in itertools.count(1):
207 | if not Form.objects.filter(formid=formid).exists():
208 | break
209 | # Truncate the original slug dynamically. Minus 1 for the hyphen.
210 | formid = "%s-%d" % (orig[:max_length - len(str(x)) - 1], x)
211 |
212 | # add the new form
213 | newform = Form(
214 | owner = request.user,
215 | filepath = filepath,
216 | repoid = repoid,
217 | reponame = reponame,
218 | formid = formid,
219 | title = seafform.title,
220 | creation_datetime = timezone.now(),
221 | description = seafform.description,
222 | public = seafform.public,
223 | )
224 | newform.save()
225 | # Redirect + message
226 | return HttpResponseRedirect(reverse('private') + '?newform=' + formid)
227 |
228 | return render(request, 'seafform/new.html', {
229 | 'user': request.user,
230 | 'allow_public': settings.ALLOW_PUBLIC,
231 | })
232 |
233 | # utility function
234 | def parse(seafpath):
235 | """Return a seafile path under the form :
236 | {
237 | 'repo_name':
238 | 'path'
239 | }
240 | the root of all libraries has repo_id == None
241 | """
242 | seafpath = seafpath.strip('/').split('/')
243 | if seafpath == ['']:
244 | # root
245 | return {'repo_name': None, 'repo_id': None, 'path': None}
246 | else:
247 | return {
248 | 'repo_name': seafpath[0],
249 | 'path': '/' + '/'.join(seafpath[1:])
250 | }
251 |
252 | def repo_id_from_name(seaf, repo_name):
253 | """Return the repo_id from a repo_name"""
254 | repo_list = seaf.list_repos()
255 | for repo in repo_list:
256 | if repo['name'] == repo_name:
257 | return repo['id']
258 | return None
259 |
260 | @csrf_exempt # the javascript lib user POST, but no data changes here
261 | @login_required(login_url='index')
262 | def lsdir(request):
263 | """Return the list of files in a Seafile directory"""
264 | if request.method == 'POST':
265 | # dirty hack
266 | path = unquote(unquote(request.POST['dir'], encoding='latin9'))
267 | if settings.LOCAL:
268 | abspath = settings.LOCAL_ROOT.rstrip('/') + path
269 | result = [
270 | {
271 | 'name': name,
272 | 'type': (
273 | 'dir' if os.path.isdir(os.path.join(abspath, name))
274 | else 'file'),
275 | 'path': (
276 | os.path.join(abspath, name)[len(settings.LOCAL_ROOT.rstrip('/')):] +
277 | ('/' if os.path.isdir(os.path.join(abspath, name)) else '')
278 | )
279 | } for name in os.listdir(abspath)
280 | if (os.path.isdir(os.path.join(abspath, name))
281 | or name.endswith('.ods'))
282 | ]
283 | else:
284 | # Connect to Seafile
285 | seafu = request.user.seafileuser
286 | seaf = Seafile(seafu.seafroot, verifycerts=settings.VERIFYCERTS)
287 | seaf.authenticate(request.user.email, token=seafu.seaftoken, validate=False)
288 | # list the directory
289 | parsed_path = parse(path)
290 | # root of all libraries
291 | if parsed_path['repo_name'] is None:
292 | repo_list = seaf.list_repos()
293 | result = [
294 | {
295 | 'name': repo['name'],
296 | 'type': repo['type'],
297 | 'path': quote('/%s/' % repo['name']),
298 | } for repo in repo_list
299 | ]
300 | else:
301 | repo_name, dirpath = parsed_path['repo_name'], parsed_path['path']
302 | repo_id = repo_id_from_name(seaf, repo_name)
303 | ls = seaf.list_dir(repo_id, dirpath)
304 | result = [
305 | {
306 | 'name': node['name'],
307 | 'type': node['type'],
308 | 'path': quote( (
309 | path.rstrip('/') + '/' + node['name'] +
310 | ('/' if node['type'] == 'dir' else '') )
311 | )
312 | } for node in ls
313 | if (node['type'] == 'dir' or node['name'].endswith('.ods'))
314 | ]
315 | return render(request, 'seafform/lsdir.html', {'result': result})
316 | raise Http404("Bad request method")
317 |
318 | def _update_form_attr(dbform, seafform):
319 | """Update db attributes from ODS file"""
320 | needupdate = False
321 | if seafform.title != dbform.title:
322 | dbform.title = seafform.title
323 | needupdate = True
324 | if seafform.public != dbform.public:
325 | dbform.public = seafform.public
326 | needupdate = True
327 | if needupdate:
328 | dbform.save()
329 |
330 | def formview(request, formid):
331 | """Display a public form"""
332 | justaddedrow = None
333 | # get the form object
334 | try:
335 | form = Form.objects.get(formid=formid)
336 | except Form.DoesNotExist:
337 | raise Http404
338 | # get the seafform object (Seafile connexion)
339 | if settings.LOCAL:
340 | seaf = None
341 | else:
342 | seafu = form.owner.seafileuser
343 | seaf = Seafile(seafu.seafroot, verifycerts=settings.VERIFYCERTS)
344 | seaf.authenticate(form.owner.email, token=seafu.seaftoken, validate=False)
345 | seafform = SeafForm(form.filepath, seaf, form.repoid)
346 | try:
347 | seafform.load()
348 | except APIError:
349 | raise Http404
350 |
351 | _update_form_attr(form, seafform)
352 |
353 | # build the corresponding DjForm()
354 | if request.method == 'POST':
355 | results = False
356 | # results management
357 | djform = DjForm(request.POST, fieldlist=seafform.fields)
358 | if djform.is_valid():
359 | # check if we replace a row
360 | if seafform.edit and djform.cleaned_data['rowid'] != 'newrow':
361 | replace_row = int(djform.cleaned_data['rowid'])
362 | justaddedrow = replace_row - HEADERS_ROW
363 | else:
364 | replace_row = None
365 | justaddedrow = seafform._first_empty_row - HEADERS_ROW
366 | # save data
367 | seafform.post(djform.cleaned_data, replace_row)
368 |
369 | # if valid form and form and not edit:
370 | if seafform.view_as == 'form' and not seafform.edit:
371 | # redirect to thanks
372 | return HttpResponseRedirect(reverse('thanks',args=(formid,)))
373 | elif seafform.view_as == 'form':
374 | return HttpResponseRedirect(
375 | reverse('form',args=(formid,)) + '?results'
376 | )
377 | #results = True # redirect to table
378 |
379 | # clean fields
380 | djform = DjForm(fieldlist=seafform.fields)
381 | else:
382 | djform = DjForm(fieldlist=seafform.fields)
383 | results = ('results' in request.GET)
384 |
385 | if seafform.view_as == 'form' and not results:
386 | # form view
387 | return render(request, 'seafform/form_as_form.html', {
388 | 'seafform': seafform,
389 | 'modelform': form,
390 | 'djform': djform,
391 | })
392 |
393 | elif seafform.view_as == 'table' or (results and seafform.edit):
394 | # hightlight first column if static field
395 | first_is_static = (seafform.fields[0].ident == 'static')
396 | # compute some results
397 | max_chk = 0
398 | computations = []
399 | for colid, field in enumerate(seafform.fields):
400 | # if check boxe, number of checks
401 | if field.ident.startswith('check'):
402 | res = sum(row[colid] for row in seafform.data if row[colid])
403 | max_chk = max(max_chk, res)
404 | computations.append(res)
405 | # if number, the sum
406 | elif field.ident == 'number':
407 | computations.append(sum(
408 | row[colid] for row in seafform.data if row[colid]
409 | ))
410 | else:
411 | computations.append(None)
412 | # columns with a star
413 | max_chk_column = []
414 | for colid, field in enumerate(seafform.fields):
415 | if field.ident.startswith('check') and computations[colid] == max_chk:
416 | max_chk_column.append(colid)
417 |
418 | # table view
419 | return render(request, 'seafform/form_as_table.html', {
420 | 'seafform': seafform,
421 | 'modelform': form,
422 | 'djform': djform,
423 | 'justaddedrow': justaddedrow,
424 | 'first_is_static': first_is_static,
425 | 'computations': computations,
426 | 'max_chk_column': max_chk_column,
427 | })
428 |
429 | raise Http404
430 |
431 | def formrowedit(request, formid, rowid):
432 | """Generate a form to edit rowid in formid"""
433 | rowid = int(rowid) + HEADERS_ROW
434 | # get the form object
435 | try:
436 | form = Form.objects.get(formid=formid)
437 | except Form.DoesNotExist:
438 | raise Http404
439 | # get the seafform object (Seafile connexion)
440 | if settings.LOCAL:
441 | seaf = None
442 | else:
443 | seafu = form.owner.seafileuser
444 | seaf = Seafile(seafu.seafroot, verifycerts=settings.VERIFYCERTS)
445 | seaf.authenticate(form.owner.email, token=seafu.seaftoken, validate=False)
446 | seafform = SeafForm(form.filepath, seaf, form.repoid)
447 | seafform.load()
448 | # create the django form for a specific row
449 | initials = seafform.get_values_from_data(rowid)
450 | initials['rowid'] = rowid
451 | djform = DjForm(
452 | initials,
453 | fieldlist=seafform.fields
454 | )
455 | return render(request, 'seafform/rowedit.html', {
456 | 'seafform': seafform,
457 | 'djform': djform,
458 | 'modelform': form,
459 | 'rowid': rowid - HEADERS_ROW,
460 | })
461 |
462 | def thanks(request, formid):
463 | """Display the Thanks page"""
464 | # get the form object
465 | try:
466 | form = Form.objects.get(formid=formid)
467 | except Form.DoesNotExist:
468 | raise Http404
469 | return render(request, 'seafform/thanks.html', {
470 | 'seafform': form,
471 | })
472 |
473 |
--------------------------------------------------------------------------------
/venv/seafformsite/seafform/static/bootstrap/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=4883ecbae98d09844da6)
9 | * Config saved to config.json and https://gist.github.com/4883ecbae98d09844da6
10 | *//*!
11 | * Bootstrap v3.3.5 (http://getbootstrap.com)
12 | * Copyright 2011-2015 Twitter, Inc.
13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
14 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-default.disabled,.btn-primary.disabled,.btn-success.disabled,.btn-info.disabled,.btn-warning.disabled,.btn-danger.disabled,.btn-default[disabled],.btn-primary[disabled],.btn-success[disabled],.btn-info[disabled],.btn-warning[disabled],.btn-danger[disabled],fieldset[disabled] .btn-default,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-info,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-danger{-webkit-box-shadow:none;box-shadow:none}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:-o-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), to(#e0e0e0));background-image:linear-gradient(to bottom, #fff 0, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top, #fd700f 0, #cd5502 100%);background-image:-o-linear-gradient(top, #fd700f 0, #cd5502 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#cd5502));background-image:linear-gradient(to bottom, #fd700f 0, #cd5502 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#ffcd5502', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#c35002}.btn-primary:hover,.btn-primary:focus{background-color:#cd5502;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#cd5502;border-color:#c35002}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#cd5502;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5cb85c), to(#419641));background-image:linear-gradient(to bottom, #5cb85c 0, #419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top, #feac74 0, #fe8837 100%);background-image:-o-linear-gradient(top, #feac74 0, #fe8837 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #feac74), to(#fe8837));background-image:linear-gradient(to bottom, #feac74 0, #fe8837 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffeac74', endColorstr='#fffe8837', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#fd822d}.btn-info:hover,.btn-info:focus{background-color:#fe8837;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#fe8837;border-color:#fd822d}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#fe8837;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f0ad4e), to(#eb9316));background-image:linear-gradient(to bottom, #f0ad4e 0, #eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9534f), to(#c12e2a));background-image:linear-gradient(to bottom, #d9534f 0, #c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f5f5f5), to(#e8e8e8));background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-o-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#f06302));background-image:linear-gradient(to bottom, #fd700f 0, #f06302 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#fff06302', GradientType=0);background-color:#f06302}.navbar-default{background-image:-webkit-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:-o-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), to(#f8f8f8));background-image:linear-gradient(to bottom, #fff 0, #f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:-o-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dbdbdb), to(#e2e2e2));background-image:linear-gradient(to bottom, #dbdbdb 0, #e2e2e2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:-o-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #3c3c3c), to(#222));background-image:linear-gradient(to bottom, #3c3c3c 0, #222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:-o-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #080808), to(#0f0f0f));background-image:linear-gradient(to bottom, #080808 0, #0f0f0f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-o-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#f06302));background-image:linear-gradient(to bottom, #fd700f 0, #f06302 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#fff06302', GradientType=0)}}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dff0d8), to(#c8e5bc));background-image:linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top, #fecaa7 0, #feb481 100%);background-image:-o-linear-gradient(top, #fecaa7 0, #feb481 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fecaa7), to(#feb481));background-image:linear-gradient(to bottom, #fecaa7 0, #feb481 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffecaa7', endColorstr='#fffeb481', GradientType=0);border-color:#fe9d5b}.alert-warning{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fcf8e3), to(#f8efc0));background-image:linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:-o-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f2dede), to(#e7c3c3));background-image:linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:-o-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ebebeb), to(#f5f5f5));background-image:linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top, #fd700f 0, #d75902 100%);background-image:-o-linear-gradient(top, #fd700f 0, #d75902 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#d75902));background-image:linear-gradient(to bottom, #fd700f 0, #d75902 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#ffd75902', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5cb85c), to(#449d44));background-image:linear-gradient(to bottom, #5cb85c 0, #449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top, #feac74 0, #fe8e41 100%);background-image:-o-linear-gradient(top, #feac74 0, #fe8e41 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #feac74), to(#fe8e41));background-image:linear-gradient(to bottom, #feac74 0, #fe8e41 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffeac74', endColorstr='#fffe8e41', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f0ad4e), to(#ec971f));background-image:linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9534f), to(#c9302c));background-image:linear-gradient(to bottom, #d9534f 0, #c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #d75902;background-image:-webkit-linear-gradient(top, #fd700f 0, #e45e02 100%);background-image:-o-linear-gradient(top, #fd700f 0, #e45e02 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#e45e02));background-image:linear-gradient(to bottom, #fd700f 0, #e45e02 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#ffe45e02', GradientType=0);border-color:#e45e02}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f5f5f5), to(#e8e8e8));background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-o-linear-gradient(top, #fd700f 0, #f06302 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fd700f), to(#f06302));background-image:linear-gradient(to bottom, #fd700f 0, #f06302 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffd700f', endColorstr='#fff06302', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dff0d8), to(#d0e9c6));background-image:linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top, #fecaa7 0, #febb8d 100%);background-image:-o-linear-gradient(top, #fecaa7 0, #febb8d 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fecaa7), to(#febb8d));background-image:linear-gradient(to bottom, #fecaa7 0, #febb8d 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffecaa7', endColorstr='#fffebb8d', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fcf8e3), to(#faf2cc));background-image:linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:-o-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f2dede), to(#ebcccc));background-image:linear-gradient(to bottom, #f2dede 0, #ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top, #e5e5e5 0, #f2f2f2 100%);background-image:-o-linear-gradient(top, #e5e5e5 0, #f2f2f2 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #e5e5e5), to(#f2f2f2));background-image:linear-gradient(to bottom, #e5e5e5 0, #f2f2f2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5', endColorstr='#fff2f2f2', GradientType=0);border-color:#d9d9d9;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)}
--------------------------------------------------------------------------------