flask-mongoengine-0.9.5/0000755000372000037200000000000013241547757016007 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/0000755000372000037200000000000013241547757016737 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_static/0000755000372000037200000000000013241547757020365 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_static/debugtoolbar.png0000644000372000037200000022613713241547561023550 0ustar travistravis00000000000000PNG  IHDR\N! IDATxw|U[{iB/tTPA˪vWEQwgUkY{õ .!4-3ܹ{s <|;;=d;9!H$D"4jKg@"H$)$D"H)$D"H{KXQ-H$DPu{J1D"H$'qb"!WLI%H$Xi4}s&"wH$D"҄\\uMImD"H$Gh&Զu \u‚ BXhi^"H$$D% b1jXBK/D"H$MxS F׉\M[%NTI$D"4U\56/>1Wt [Md cPdIH$DLSiM8F59u<ɂ(c7 uD"H$h,,EUSC MR'2"S )~,H$̣ߏ ` uҤW#ULoXD"H$ϣFq\7ukD뺎-|VEAQt]oPִ}o6`[냏۠|H_/?_[[[{>zqPPu 樂K o)2mҮ]Gkf_hu?\ Q|h`3;~}B kt_/K˒%KeddбcǨv 0ZR- 0((to)KlB̫iZ"pCUn,MP+}ڤ*)V/K*ݻwg۶ml6^/iZ8>1逩i4&LKN}&B-7&uͪj+\Fj}SDzWpP{pcJ_/o~W\Idd$:^ ]b'!`ej].+JQDyŲ.MP"+WuT¡Ԫ( r` No)2ac_៬ X/K_4v?n'<!9 %5Z@e[YQVkp+pv{yZ&)+˟ ^ֻ~EXbw%/FBJs!/u3|獤Ķb/~{[*#tԂz\P|iU:g a<. K;U|+-Uo?t5*vzՃm[CEEEYNTU 13lP!4Om|WvCL['R3]7_/?B$&&6\d6W%9G.B5 ˖&:sZ|kR&w+vm#%1A4 t(  Vyj}>۷jw-j!i@:(W fcoݘABX4rFڏ?Ru2z48n7p8'71h1L1 (dd| 3fۂG4_LTT`<-ql~㴯 \P7Rbbc*+*3{58x 񄅅QUUETTTFv;iiigqݥ׼;e5o0cYۂ >YYq:!KgQQQDDDSlߖ)B.F9Ga:5MSqm!golmՉW={`5Q1أS:{jmĵ [wp6ZM"%2 nc\3;7h@fTUU"P ,-n(^^]+ S= |u 6ÍFkbboK'\`WﱬlÃ}Ȑnb|c}׳{qnTfp^?ߜɯFed\qNpjv݂{o%˚jOTd5[Ĺ̋{C: ySMƏOiNO)Jt_Utŷ2}p8(xno?))[CXXDFFܽ8^oȗO8`kNݝD`L/IJ/K FYeZl'i^ Z{-UQ7FEv8lلZ r:#P&d^"qF%zc6kV]C%3FoX9*Q<4sQm[\:< !4:3C͹/$܉$+r~MC^Lm8UxS_&aջr=̙m?~tXKwxO#!a,t վ/Y1ǁ%ٳOy8>gqd3 [<+9_Ϻ>`Op59߭[w֯_@޽mׄ >>4 < \qqq_ZwOV?MUUmO}X'lHOpWVbl۬W8jRhh .XxO<٬tdZ Á㡶6-"n Hx OfpqhGuu?"vii)ݻw'X %YE):vąG Ci, ܅kz\3f}\qϣ,2y/n6ʧSz pɁ.GB5<8ѿS*?P);Lށ lˋV;}/:}[ס*6* Y8v!.tB|t59CK#n94-իW5k87:mdu㉌VZذ[O&f}ZO*8Tטe3 (8AoR@p ΃5[_/B`Kqe±#\^bu]1Fu]z!ÊSQQQ04X5ѬJ"Xnbccq\FE!Ku~3` D|D4}hEer+yM+^munszΜoaGCթ=yT~9Qq\2F[#c;oBK/}M,0fmvGd;w"*:2~ll .rx< :ϛm?h/kPvl6>a-z]ծ55O8IM/K~aLAe~d`&Gk-6؀_;-;noK\V͗W:Ml>RUE |Za.Xڶm>| t+P|]Ʌ1$bYF |k+7טEZ *O Un7W1ǰ/\|k^GBQ͝*999iѺuk"""o1srrHJJ:j[~v{S'T tj_oy뭷x;cn߲uykd ߼!U;ߦ_s>92i*ckXÚ4+>mnTUEUU\6ۓyb1XO>f{sݠy~cLk4Z9+Mj*ZM-}!Cx E= eX=}ҥKHMMEUU"""چ( SǼkN̚*Dz–n_/;[ֱI(RxY9o)rAPU$$$TU4*4аnpz+.Tgy郦iDFFp%pa`+j'cMjߖn_؇+x&xQq烕`RT%""B ~,4?r~@֤4,&7:7d̿/KҾz }r\wy@%h:2EB9oR E tPaS#N3KҾ/KҾ/B7x"h9a!g(x}CB@}%`J_r3h8ۅ$dۯp WJ$DK@4߰91RbXt Bk`KWF'! 10?!7Bf5ZQo9gggcِH$Ν;ׁ4b7]l\HrBciױRE#Ev5f,D5VBSVVFvv6aaachbKRQQAn8CU"H$Au\&%70`+BfΜI^ԩS|A{yߵkגA֭l_{k=<]DsK6l.B0#\PjfϞM]]^x!YYYG?MX4- IjXS|T*B7>1#YaQ> /BۅOPըc޽9Rܨ}]]G"{1x ^z%N{3f‹/o}̘17w6 >Z| /?KO0-eGdސD"i94McǏsq<qk$~|\梪*+WlѮ{B M馛Xlq7Di4nmuq0뇰p:uĞ={x8sq<-+G 45å4a>EF.0~P\T?'\q۟Lccb"pCUU5.HLL 曤0uTX`'22ҢCJOΝ)--%55nݺ_͕W^O[UG(6܋39Gm},?r~+~2N6Zf}Ϗyd֙˝Î+D"9yݻLvTnZJҤsRF\wؓMΝCZ ˾}6l-b(B۶miӦ `Dt[;#[ UXXȬY`}S9ɓss:AQK)bQ<*)W$c"G*PvCh)J`ʗBjjjݻ7{e۶mlܴt_7ic4Uۜ>\PO͂Q55+˷|ʽ{ 8Du#F9@PUl6bbcәQ\\o~k#),(`Æ V?Ӱ µqF –-[HJJ"==!iiil߾=d!}S[_ɑm̻3WsE^;6X ϭߩz'mՙ?_oeh }7euftW,~5põcw3tn"ʄ,6y%.syv:=:eN,]H$dRUUEXe9֢et@ޏX: ҥK9r$dff2rHرck׮ .!v&_~xgfVn7v $''3}#8^{5{9-ZɓIyo)^_+Ar+-Sy՗Rx$wigRvk7<q]{f*ݯMІ;lWpṃPF6nE5_tZвV"HZG$RQ{zx{tOǸq;v,ʕ+z9ҟV B_gep~(r*~x7ޟ\'TGŴp}k.ϟπ4hP=ލ\@A$U-o&{6OkآE!..[oٳdҥh1cBꇖ .E2INN?&));3Hnl6UUUTVVbw8OL@yhΝ9w<T c##e_s.Hsw?c^w6oS{bQ%%lVȄQdQØ,H$Cux8 5 8bw5޾}{hk "b }}t2>̘M2\:m@szkcfذa?M1bDЬ{f*M:<}J؉D2(d}לsS#E#Bc欳zrٽ4+W2|/_NYy9::Bl=XhC}}=h׷_߾m9 Nbb")))X;wҽ{\n^&C4.ttMUGqq1N< IDATDnI|BQHMMoaժU[sҾ}{V00:N1޺g$''c… 48y:biچog~՟{'a%ml+X6,ac/&+3҉M mGƌ|AoG.QLju=ҦGO hFtT1q$MN\mr4]va9sڷn,H$#33ͺJ/H݇}6J%>OU,X@LL se˖-dddr:e$%3s:bp ZGX1#m۶t҅;ROUu5IIԿ:nmpCcʨq3(m2v%0* iǿ]EA6mp8QOyy9t+W:jժUBL7x!&,+%X)#{Y6=hrNoo:9& %RZZ(ƆJrT<7IiUU͛7SRRB.]СCTu={5Mc:t<ڵkE$D"hwަaBreM *BꫯҹsgJQQgϦ_~۟nǎ!ul 9zrVۦ|Z-"bccl?qy7N11~/[UUXz5ƍk0)1C3S~i_ڗ}i_?y].Qm-KQKe[Kf+CIt:ni CKC"tEڗ}i_ڗǾA_d'XZ0U0"1?)|[| gƾ_1%"KҾ/KҾ߸}N=-œ_K _ +"8̨?F9T/KҾ/K~cO%-2q"tsV4v_0 _b.~jt *Lf\ %&&o7}T{"> ֢mmP/$Ҿ/KҾ -A F r<)^FfMyn~:XǬUA1: *BM:ՔŴeKSZ9Ŵbw.%<:X£bAuU)7gs *ˢiZYYEǎIbb"TUUh n˂YPD%0|n!3ѬOyWs7)p!y,D 9"ŮKϫo^~ur7oF򧯏Z>˿/~jGS;}C>=|'V7Zmxʧ_ڗ}ido/yXYTuI< *f/?kr-H\^Ae n.t].mQ;n)MycB/ GOf `y箫'*F cx+(**b֒MXXiӆ[.ieB~wpʗCuY V_EwpUwޚr3ߤTi<0mӯGgI57ɠXqzR%[rxyu]9Vur2ӊw֊»'s΄QDf9܉=hE$,"ߕĘ׳"= pob14=L@oM}:l>.%u[1 txϴnۍ*egřI҉,u%qFTO5ww(_wX$~Cby?wfs/K'_ Be님=/ưۅ̌:;q,yuLb>\Vo0"Ptn^yYڜ݅[7L2}#;> ‹("\F'7pZJ]VQ^֣Dh,@hےNhU,)*#%Nn0B_9 J}G<>:]t#Gt:IOOG&GW'ߢnkZfՔyApڟua}D'uG Vz8~,}ȭ}`ƍn[xشkO*;դoK.} GAE$zT:8C7دBA(:!/eײe+;fTᆬxr$! 5c20#w^~sV[F#.Ym3 ֽ+kpO+f5WMثt\0wZ\6\x=+"5/"tW歡z?6awxф`섋W.0J@2NcUI&q6M1僈eоDؕYj߯ێZRFgF˗-#954]0,0J2N%w@kWP^q{ٱilo}E6N|լج1 3FgSg\#Ctڜ?o?͔d̄f(qLƏiYӧ`Wt/GQ #0s{h:"e04('bS۵0EӍ8|~ n^d~3Pߓ!|k}i_?zy~n?x/m.lyUkp#;a8>|'|yՋf3ge.u't B1\=OOyז2}<_ψ[y뭀yq n|9'p+ b\2;ϝYb1+UM˧%8R;&qC8-mLap:Kfw ]#O˟8ӆ{Qm6l;̋ TE 㭷"**A;n͎5Վ(<.?TN x<EaSхUQ ]xT /Ϻf|5p"<6W Q&@GAh^l6^ݡph_Ow/6 ' Daz༴/K믢b ݤihWCU}UcZ @uS6nnGAA<(6nݮ nw_ PF_,]3D v;cm;|^}EǫW:t!9 *(Gk[DlfC:jëi4h9}GqY}R#쓐OXL9)~ie%nϧUn"0p@bcc۾"OQUUռUKҾ/ 'K-WZ@pLy`~*<,_0 KYsC("Ϸ q9towʴ !*(;PIMMCդM'Ds{ :,^NFT *i_ڗad\H.|Ԭ](+W_D(,zQ#KҾ/KҾ4SeD"H$/ɜJN7;{+H$I3y饙- S~̖i^"H$ @ .D"H$f>#H$q ]?hDrH%H$0*+x<6cM@QFL8W PKD .D"H[ ` n#GTTѺuNRK"H$gBz5<yy1WJ%i2;v,k)%̿]3ōWr]^ /Ym-VnyaI\?ia,ygᣥ;l%?T˫_F$DAA_l%$$ֿW111SZZѰ6m*-O}ttR&MĒ%Kѣwf߾}7tovE9RwݔWyeWS/f}wg1kuze_}@]LW"Ys)t(U!nuy Н}I[!/>4?!B*sc4%ƷsĖ$$$clJOOM6ݻ͆ (((H۫WfˇpZGEya]H9K']]Ŗ9b5_Rщp({%*v ~嗑BT +(q.IE2z:#7N.}磿/#3k:z{䢎=_X79s[pknL2 &;Jū(^7vښd]{a#;3W .pUcbunS9y)%=;0t]gx4ԾR„)Ӓ8\FBFڥFQF$KEqbJAuu5QQQTWWSYY륢IL*v?p饗n:^/=dffpwӮ];xtvq8x<%8#\g}b~[p;p']OjǴms ?WҮM.Pv!Q)c=]}%]zCFСμԋ? <Y]&q5>C0G{+ Jc4Ghe|bnpDS:a/q9mGӽ5Iv4]OC֥u7+Y@Tt< Ɛ F\|+ɱ#Y|+Sv!g?!9r.q//aSrTeDrz#\ ϧBjj됪DG7.zY8\zL>ӧӻwo"##q8tML6{wjڵ+y0aNrr2< W^y%_~yeX(j׷;.H{_u1;' j{Tr|/h!yc#BVUO=˳4o>[IEM1Cxe!j\(Bc?^q+f={%OOy-cѳ#UueU7n92hҋzIq)-fk &cڴ3PϰYUXzw9__,-˟À)]Fi P~7`߶U6, <>yj-Ƅfڭy |d@P{˴S˴>t޽WrٟT"l7Gy:22Xjkk!&& Lj[|I|I֮]-99 t]Ddɒ%ǓRr8Ws٨|:Gh|&MW;TƣST3lڙcHɝםfcŃpy{&iӐpinf5?+PqdPΦg2fCSwӿc?}t\qqᙾ?6atgTeHgٱ~3=TA3o}F}M>#ط,x3~=|m1* v‰iIrG9tUG^'oaDbx2z!Cm ֬Ye]Fܹ3n3w\HLLu:҅J_u9rSGƂsvs'X9z5Nm(^Xv6aۦc;팻7Ov =s8!4x-}={4M@Ftonz6iUH~g{&ZKDK Ԟ~e>#߸+y)w :J$EQ <܈^UUUi``>PϟF׳&%OE)/CDLߝ0Y@sh^թ#F#-ѸZKyp ?y5k'9#WOt?h."b-&MRB"ESWgt$99?른bD&ř|stxQqq&e#c4ߤ[~W uZ BheC柯eᒫ^M?0Gi%}"}M4Χ"K$*UU>rա5HOfkc;)N/ZBp 5zI?N_D1G\K1Q%ŖD"9ӰERSD6HU&-T =#s huӁҹH$}l6Ajj5.WNuTUQQ.bc݄imt%iYˏ2S3qF .3cw8v8Z"H$gaaZՒ\BQzv +!iYZFgH$X( 1$/b.D"H$_2RpI$D"43RpI$D"43WUU,KD"9sm,HZS.l6fD"H ls!ɩC>RH$4&%%ſ|}x6$RpI$DrRPPc@>ȚpI$Drwswx?{-w@:X(Rv ,4EzAPADH MBMH%\q% 9q>ϓ';3;3|W\IVؾ};}xyy1fڶmKvرc͚5#66+2h >C _|AFF9:#puZ-Fb6 -"IOO'77///ڷoϴi%44!CP^=}Yٸq#۶m^`ʔ)&RJZ-'NQF}:999 6ݻsN=ʹsHKK`%+K ;ɓ6qqq^ںk|Q=j>s W^eժU111Ŏ)zbWI{1$X@7~I5@ ®p3RˇZZNZ4?>ۛCq<3@ [`ׄ!5o^=Cٻڽ @ʍ$ҳrPtR02pI""GQ9Y$%`J n s'C(8_EQdYFQ̜;y}$e(x]V ]c1-?A4BCB:M1R}FN`Ԩ3c$Rm;2}/eYoC<^z5ۗ+W ?LIHH 774|JEΝINNҥK\~Ih׮\xׯo`Wש9>֔L*g*U aPozbb~ MO}mSݹx9W\Jmr/K[cN#%c{|Fh%*Թ/%@VVqrrDZl޽{R X8m3ؽ7Lol֠kD %rrrAX`dY___ΦFTR~ dYFc6[Srss#==7n\zg 4e'?;vTl[UQ*۾UxԷ`rTeÿ2ݞ@ ARRYYYYnKINN&11Zja4v~~~$$$HF0$$$ၻ;ZΥoa@ eN>l]K AA\@ e@ p @ 1bҼ@ eNյq 6JOO'##pss+`U(( }ilnݺ@ wEttty C.@ 2썈 "Љ\9{cgu^px&^g1Qܵ-=3- ldǖȥl*(K0/9zz޾nS&~ߴ n(ѧ9{9/cR3L3.ߌ۵ϊp1Ƨ<2oE>CN`g9v˯Ud="Acd_(2" /"%>-K!vpŜӳys7k~_G8|r7 2,s=ߟK%_[xۯ s|>uj18Tl+Y;t( z*w&.Kjՙۖ5?,stt4Ŕ͖l_i|ܩ+fS-$ݷhSa+ l˦OpnSfvgm`v۔ms2}|ہ45cK+bNY0SM~m <8oryy+ƻo"c֩ђg឵Ѣ iYFIkw?}^yL:17ʢM7:Of㺵4 ]Ʒ_ "N'1~ȋNZxyy8~> OWg|@+jY<%IGP|Bhp~q$Wo UH[=!1 7wC;%rS$W"~ 00 c1٢a] l6 Ύ3 ō6o봭ZΑIjk /@w'i$rc$zs& ERYBBCqwqk#K{1fm۞},޹Ncr1[*IBBJMFۗS7-[Mwf/8GHsC^|/W)^ 4,GWob GnՆC;^ T X}{("1Ѽb%*[3 <\ܨ\&//xgprIҍ"7F{̦ ,m]*C6Y+i \#Y>a[JgԸ w\0o>B3OWōnb^_Ns# Q,d"8M~=WF^}8#1et/jvʺuyrc[CU9죻4:!CqcotGrx1ҵޑOSW׮xRk~E03vfk~ޣX9$䱭HOc@P}KNѩ$\\5d4@љ=Сc'J%sP5slOMt.f$\*&Cn@%L}"9Q4~i}>&gu LY/y~:d'7#sl4xjCޤF`-Tr'#żt4X~ek5+Ԥfg%Y熣˳JM`TKzmB]\}.8=#Yo?@C qYamYOR <ctxZ}bxhJȿ[O?720td%utvgG$$BCb}-/Cez?Uyg  kIAGUSS0+R8fEPZ!7;S[g2f1vڍԨL`'`Je٦_{~Sˉ<JlX;*ofϞM@@uܹs>|gϖg/eA-pWAJFR9V@ U<7q/X[7GPԀR^v:{$^ ޭ#j4tn^*y 쎇+}fs@]+Iׯt@]'ooitM,{1=I? ێgS}y> &Ms8NTx&L<]'qSz.ſ~vΊwViQ4USKv.e R.\J;ˉS #GM`ҧh1h:=o;aY4"o.Iqh{@M[K$%%{Kſ:oЍke^?b ݲ>Aw=H*P4(:5c&{&̔gU>>PL~jn|8;_AoKg탏G74&Ϟ*|DCN0]Z'`Dk~鞨Tjz C):6"J ٣Qy3b$Iۗdzm6.^Xޮ JTtR$Yg HyBՅ!+ty>6aÆc)))8iQQ$IBnl6"+*_xƬ+<Й ' v.\E1Lh:2.+kwAQٌdF! %^!f#&NC`{]Q/GP0-eUvG1e5?Ym$05ZԪ{sɈYN[l3ȨШU2( Tj-]]/˧~ *T`ȑ\zeyPIJJړ40"Bȟ9O._)_P4RX@q'Zhw8Wb-h%_EV,!=R$ Iz~oimh4: 5lY.(m&*#ޕx?VWaU/ZjMoh\]ߒA_'jn?Cnֆm%5"TR}j`LM^CJx}O]NlJ}WVɧcʢ? ?/-6 R)4'ܻtjZ-~-`ѢE|nj; ϰaèS=z`ƌAu/$ Jw|eĿRXy $IEX$I\PbOupf3yf///t:7`ӦM颠vp@ S.^;BjNKKv@  Æ ~^v-\r,F#'N`ܸq裏aΜ)ݲF{]\&?4iA.@P^\|999뾘-Rg)))/C`rEa烩_{ ӠAmBe5ߢ]AQhؠ>/}EdSAC]Kg+De?sXjBQ. aw\x4rN1~}oőSyq6y%mv{7Xum+EݚҴol9;Gnjϔ7_kK(bdYXO46F6$nݺL]u۱noߣ;vp+Ǝ=YkEr盈?ؿi>w%|5\46|1d>3MFL hElcIMH"Dңi0q Ցmojy a~6Su$CƭŘzDQle+nɎ dpd]FRjɘ6>]Q䍗s*F4{tsɼ=a&)0ؔ5%_QQ0\`5,q|Ǯ ֋ Ym6mʊ 5x`Hf%G/:&χfƺ֯kgѴiOƧ(z?L(I+ƴYHO)fKpo`LK=c?fwzZ>+ɲL?L\#ۯޡoWF0G˘2m}R{v(˹V;G/{6ày^=$,A销 IDAT4mڍWj, cAR L6*XYc?BZ{PN%ÉeF듯Iy4!ų3h`Nfq:Ov_Fz4R: FØ1|'S=ޣ3rzΪN]G'-5첩۠( >mo7q3I=_lfT.D+JϼE75L:q˚K$swDf4k0%|wgfҰu]gmy+4q4,qv㹗\!_Z-fF&,?]0㏘9n0ϫ\=&M.bOA]r;lB1c?CRө1KZ%߮=7Cğ (E J;iCs0MXjU2/1pV~4i>"[>:le%Bw4ryg,Y>O't4{Z?Eu˙- ǡls'pk0̢Uz*cpz|9>e>T;}ҺeB?RӐT:L1zx긒 Ղ| QA²Ƈ¾'^Bʩ$%)W<;{x5-A$IbslOG{`wJWG}&]2xwoZvȌQA ̓~1贖P<^+^_&IA$0TyR2i* d ERQlQ4ɭUخJHr2Tϒe!H*U]adT*\".( ]ċŒ_ dJ|̮d&JA>wcߴ$QPY%VdY.n(2jͯao&YFV[WJ+!$>2Mbr',ѵx(b3ޫ^`~>HA@qq*fT6o-֬Y;mMV<;Z<; U[ڽJ{I\\=$ 233o}F$T*fQhAVI\ٌWEQHJ'E6qvQ(̲UP`(fҒoWTxcgs#5WOo\ue6 YHqvА~d}"{pUV www\c=ƍ֭4k֌;vЦMnJ`` 8q|OOOٱc]v%>>WJNݝ~5jԈ .Fёby4mڔߋQjFq~@oy[7C$qvZ~18;XED@xZ%sr!0(W'2m:;*v'1ժUȑ#$%%e|ӱbŊ[ӧ =zի͡CPTаEf]vOkBZZǏŅ?&|^WWWN: AAA66۵k$I4h___ڴiC1xzziozcy AMQ<==IJJ-{A[T*L&@6ọ>ʥKedyfe~\nnE{LJP233"..t"IFF,yv^}Urss!,, ///khZF}Ǯ9$@ ׈nݺ9sƍ믿EllT*ժU#** WWWf3999$''[ߎiӦ(BBB^^^$''ӨQ#J*䄫+B  ++k׮QN=hVDxx8Ǐ,) GJXbZ"..f͚Ym:99@zz:DDDb]QA .߆yp+oN>ܹ}e*TcHOOGY={ deeqQ^mge4INNFYT9;;s)N8Ave)ݻw[~ MT*5rssܹsL*؂.EQTZߢ⊼c擷PZ5_Mgԑ{xfMU?+BvIW3,fTZ^G#y6cn| {2lIJW( wi^f:8{8!!!|H}nońO IOrr2111#?`0p5>`=d2q5iii$$$-FYmL&bccM&zz=)))6AЉ'puu%++˺pYYY 0 `20L 'O) `WJ07]xF"J8sb'=z[ҩyS8ߖpdǚr}y;uSzb{8SӠWR0m-jT&'.kbJ/H8ςE~/֩?lLcbdYf_{Σ( .`7/YC~9Έ[ԕ+މ81I"i4^ՙ`:7e]Yb/! DtӱR F]9~s ,D}|>k9g+LوUY}ҭmN}ne9jЅb[}EԸUڧfOcNFFF-WeKa>2sp50\^WFµb8LI7p<DzԾY{^rХ7|uqiA#&g~HId̯r.ʛoϘ^2a%Т|*?b~u[\QkU@9fH>sa#1f\bL\j4aOscz^ll Fk3 _JX IBy8[")a%*y-Ψ bά/J8F_Lre3&V"S/aZ54jޙFy?O2kV6(OjGD_,ٹBhZjШդʲ9OgҨ},r4=7~寋&{*TEOiJV$IhZ>)_ WaʨT>1-*rOᶺx q'pLp>Z}B>6@`}s`槓dH85"vR|=Yۡ}FnBg.Ws;P9J΅)];Sڃɴz#7"u>gX1&>:gw \IA#kT+ v 9M9MzڿTSu9`f>C>c=wy5OHLwҥf6xcTTv$3CK>9鸹W'744|( q*f.bϘ{V hS&|fuZ֭G/2hn\!)*x6ӠV5I>Q]מּĈ\Oxwi_Win$xB*עǨǯc!?܋֦WxGsQ5*= A4 DNo}dǎ P  ɏn |ڡ37o`^ԨJ.0sOIP&퓧*7svCL"R7p9Μ9Ù3gHY"i<߰6616<ѾW2y|7xm춾j =0*6k /!(TEϧG:R0aQz5K#o8Ա^j6}o=qM Wz;B2&iP S|*^&#"/Veɜ0EHF7>o ?bްwU%6 $0 s(cF>Sxem%,[eEP($X~Ng˷eA1i^̙35k>E')1 _?o2o$ñ4h-VF;m-_'F"w<;}f>Tup/.ҥKYlY9y$(JLL̞X!( YY88Q~P6dgtb˳܍&($%ᅣV" 'P7t<8(LFj*EfCiY8yX>t'̴4.SRUZ-EAP>b񲋫svW<g"$ o?m훛 Fw"2,OȲ(L۟c 9&SFuzWOyn">Y?}qzɤw%3铷y'v]Nq6+e*3XvG>ܡόI+WӁ(r9~!JbJ^܂V( 139Y,͞]&w'ᱚ>8UcŎ-k* /J|h9Wk[iimy>KZ}} X}Lꋎ[N0W{~1w;H}wO%I+ƌ9Ͱ:s:{'SпdYZ>?݂/fHyίʩJD[Ie˺Viw&sv(˹V;}|p>~hE_#5ᒷ1;qC9ڙWB-ϓS# ॱSȺe$ü)l=Zsϵg˽6iK0uF/t[\QiVq!r͐rO3hD^aa̺ƒ99i;`UX{2?ViFNe {~Ւ+ZZEm9#+L?a=1{[\9&M.bOC-D]>f$ VIFèO+pL=zcl$Hn?ϝñC8tMYc }䌋6ڃDڵu'44%K}v̙3K׼ys5kUM3gδ~f̘1wcz޽{ .sdgÈɌG hXbjfvEŎ=H6?_~=g$$ӯ v)" aQp6m%d'z1#MdBy>.( p:elO{а@:c` wG>q͔ZLEwEI#~z=k/Kزnmmzh"ժNV|w<޺5^.^oeoN纾eY-j<֬U:̷vhU5} -5007>~5m:oQN F*{W̵D^~e kXC+|O`˺uT\IeӁM|cJx}b ۑ/irB3nţիh܃КVL9w?>BGKjI|3i\ld1ֶ?8%}p$IpX>j?|"u\y2AVZz^Cnn.`Qζ9>HNNfX`$剕ƍCQUc9rF7|իWiݺ5=aaa̘1;v,֭CVvZBBBfڵl߾WԿ^i>000OFdVh~+ H*sۥoݶilImeJ"˘t[xd,O>U>(]2 q;YV,:ڙ|8TDeȓ e+K;8EQ8z F'59\#~x:;XdP};gط(Hy>dKZ dk?_n}<EA6S80cydkGBp̜9ٳge3Tˤfi]% Eɓ4Ԧ(ԬYN|;jg<;<bɒ%Jf٧*rnwabsh [4m VZ@J%O ,+1-''1rH\]]QTTZʕ+ڵ+UTadddly-Jb޼y ,oO>PƍGll,SN-MU#F$ptrF]BpUZ'<ܜ ʊ[a*$I{xZztZ$wW* OOOPG@uQ6>mxߟzE͚5 f޽4i҄N8A[nݻpf3 d.AAA,_7;{аGx@ _#EGGGGpdggc2Yƍ6F!+ Rpss#-- '''7n˗z*ZZjiiiT*֭KTTuXHHAAA>|^'hZ7n,=zxWPG*`@ ?9>tV}z^ +2iiiQ}iF'O;?پx"/^nZϏ@ 2F\@ e@ p @ 1v;i^ ݋! ʋ/ D%AK^P2@ p @ 1"@ (cD%@Pu -╡(BQ H@ {Ů.P1<'GzD5GQO(d|}Z"{w%15cv*{m 4~i=kDbYIN#!9rYO\\Y%ڑe3,@@  R kqaT ZU) 7:6Ѩ1̨h074:68K]1›"`;Ձi?Ert+|Cke2mޜ´EvtR6͉dnJOr cҤIeݨ ~2$IVI&1c ]3?~Ϫ?x]D*Fz8 $IAATRJH("! 9rnƉqʥH\e͑lf_M\~/.Dgpb=Ia_C珚.i)pE-ƳLL\Zx@vAA2FêUxwiӦ קhѢ9_ݺuiذa#GЬY3\eߛXr%[fpG˻(Ňpt^ +&?U)eֱZǔnЖ-]{6A M7Ο`lOhҦ=oAzDAxfF#ggg(^8۷ogذaL<ŋ3h lBǎꫯ(_<sয়~b„ _EҡCƌ hѢkצQFT?h;vQF4i҄۷h")^8ݻwg۶mtЁ5kлwoV^7U/)vjX-72?]?c?ƕHp,5'sB{biR +낼ߩ>lYݩҍgŢׁi7 Z7'oѲ{nAm_ЦMx" 0!Cp}ONug8;;T*Q(/(Jh4?%KҶm[dYh42h $IBPh0@baaaxxxдiSƏ@.\Rٳ9t{,_YKRv C"9 AЊ+W oooۛ3ghtipttY&&&+V0k,VZϐ!Cҍ۶mlVKxx8Vۛ+W$?0BBB-[hѢl۶ WWR>m|'Jшi4<K]{xCP?;XAa 5燭\:wLJ8%^~,Ԡ#~~Qk*@Vjҍ GjʖJ{JAxVfӇcDzzjtž={x1}%J͍;wMDDCeΝ/_˗/n:Zj^Osؾ}o޷!&&Ǐ3qD.\seСk8(Rwgxr2tgRHl|imһU/ >AJAKt,VQ*g$ ш^^^\xɄxxxŚ5k(PQQQL&h4TTJ|H|à tڢ鐤GÇll6FrtR(44Phh<R)S9%xSrV>% o+d]8|@@._Lݺue9;'F#NJn692[cbbINGJaaaU*j #KkncI-ZFVZeՒ?~Viggfq&/$%,\ë~PbjQFF(iETads: }oY5'dLVo.E:Ohj4T͘uwvv̈́WD GE& DRa]#qQ!WVúJ׆]&sy&6Qi|e2[L Xi(U*V0E J֪<uG GQz1ZjiS23gk#7ңEIFBAak9j$z !z⢣{UJʾl޴"ywq)(U̥L0JLڝk&-Tr/^5 }pPFQ'zD=)[F3=t迃O<;PUDw1M % IF`\ge:Nc6p'srx$o+Խ(=9{oqmWEx!}j(DIUhk56l}rE:l$VE+]5RtQLPŁm0LܹW76i8o[E %].g51/0lF׳۷sҥի_:9s; gg_kJI!P($̒dPcIIn*f*yY;'ZU7 IB yH3cI]N8(h_ W .;KmVl)Ʊ uƳnAilMֽ058=MWsJ:8jFzgt~ۚNM|q.Or]o_ּ/&M]@.-e cCJ^Rʠ@Rk1C^ԡMIKpr,=JB/?c̰QόKPrsYq7Mot={PW\B tTTNS 4xn!RZljQ(Uvܨ^ J2B>yUʰIMkv(ݓuT`ϐygmj|؃jYv'6zeyLͽb{bNZw36oj=#V_h>V4ٛ;;f*<= s;n6`Eo{IqTeQ%lsҟQ󮤼R+&N_g/_7?BG0jۜ JJ6^\^(`>Ϳ >ۯ/kp7qySGR=sG,rW*U(Uv]=0ˣon=KP @%_ٹs'oÜ>}jժ?/_ɉZjq1J*E>O,,[cǎѼysFEZ֭[̙3'9aÆm6Zhg}ʷÛB&CBORxWxWIڤw)_Ç>|3{,( &5=2 "):}LNg'~eUe(3+d5Od:Y!26Rl‡e]ӍGPX:ț|2!Is,ɱ(ez?X^hzFa3/MǧK.eҥ/ڵkС'Ofʕ<|~oyѭ[7̑#GsÇ'((pYf̘I4iGT̰aؽ{7ZFC͚5-;v,7}ƍHԾBCC$%O1RI/)Ż]SU<IzʗR|˲PEl3IΥq{onA'm,܇ y{2M|~BH.,Rxsw,JfNzf6Ym#fkkFٙ)S@\\\(ccct7nٙ3gX4zSNFb߾}gYxs㍻{-=WOrfXή cȲZ 7e|[VLRe=k&nVXΎ1O!V`_C^W?P.gOOX&6N;Pƃp)?Wbc}o:[ic'1cG>yN6FMӐ)TY*"!.ѣ\;?FeϚ_l$qiٕpbon} :YƶP >M/jx){*'GCGV*J~eҨ*R-ۓX{d9 _5Rϸ,C( ,S}}eI{(S(C&/DtLВJ͛s&EtlZzAxk`5|U{1EݤUe6u kudW-8mP_ WNdʸkb*iA+k݇\}|d8,qpT,!ˀBh#F`ȑٟ\]pҦM6l@GÕڙ5ޔ}^bx~zHPfO3Rde7{Cvm[5r GQG^Dxx8/_֤Iy8;;sM ,Ȏ;(Wxxxpq+{\/r .&eľj燧':,TR@hh(wjժؤRL^^9yuN  dsNݻp,/_>PAAAɃ$/ 9'6\\\\AI!uΟ?ٳg-_xb]ܹs./d/ќAAa% DKA!  B .AAgRA7NNiFrGn/\ z=QQQ !`xqK  9L4AAjq IDATrXmpɲLB|, N  Kɳ .0B5_, \JN3ʽ  Bv7ͫeY&*'\/\A8ڨ18qh/"TU(08cRԩ^JI}?<2DD#@tB<638xРnUl4j1  /-O720me5&ɭ.g݂쿯J$>AOk Bke٨\/gXl 4k҆=BRlw0f}bmcɠR7$[  <|I1" :؟i}AhoVvdޯ4[.`㬾pg@9 Xn-PiWNŎ4WmX/a2fnAYvh۶KӫW/*U )^kr+QuHY -5AT/W{{{\aƼFe$3`{ f/,ոG8% * F#zkkkf3FJFIXl$hjzL&SefsMZJ-^~YIwqI*[k}uߤ$0$dL&3Ӧũxcgi YA oXҥKS~}/^̌3زe ~k.ڵkǗ_~IŊѣ֭s,Z(ep)_28dHX25myC(U|Ycmo)Y-8>Mφ:ӿL 3^hQ-3' ϯdɒ~z.]D6mo߾ű`|}}r ӦMxzzZ?pIVe앇\ZVK&綣gvtmm  dg{.>#2NY0vfNBZˣ~rcP;P§@gQAqqqh4$IB"IRSe +6CmYST=nt ;'J yC:u`ԨQ///:uĺueԨQܹs"Ep{Μ9C>}Xn]ageY  6l ,oߞ%J0vX ׯ_gӦML&|}Uҥ z>8w\r8}EPYV9%  YqFjժ1bbbbXx1,3oZ`ǎ4h;;;ܹ$I-Zs93W9wUw[:XQfe),Yw ۍJS8gMei"2.b%dz~^`[dKzql^7PZS~pzʨ'Ψ)Qʹ?jyR(x:,,K\>}w$~:hV$ё88Vf絓1:|)JkƤf35L|9יV@c4&n0|}} ,,`L&Z/&RI8uJ;;; фEhh(ӷCi>6!Ϟ`\,X7MT]|qqq #AoD2 &<"$#27wwY*#::"c&<2$M@@q  W*Owa9B"Sy|*5Z<>M|- MtT: }B,AAvRc;weՑY2|ոgƠ' 8$IBD,#^gl2d6eX)!IfbbbL*[3LӁc1 ›e7]ǏnmjQ2l..P K~dɜ1/'ұ?tMFBCC dNlk= K &.>)D7eYt1N@tt Qa 4%$)HC4ш'aaaܽ{ׯɓ' _|ɟHHH@;z%JǥKh4rʖ-[}3\kbVf:'ӡkGa/fά+`0g';5ve@H/ہƘS0k@V.\FF0`]\ݝ }L$+F(U=OON~Y3Hșbıyl="m~Ƶ~aR)ιNcnjXs%[nF#4NXaM^CkW&'qH<&t:C0l;Ưk3&R[D;Dizqq8ظT՞b,T=~pk:4f{ˉt_2~3YK!hajf9ƅ5p+4 ;%Fq.if|X;Fy80?j!ͻl{59t666l߾=yڅ ]VӡV Be)/T*9yW^EpɌpG$RCs`PZ|?]!!A1,W{6%w]|J@Z=W~Y"#0بl`8P` Bd /K^~q EU2ZsֵSP*FDMƝfO,!)$.Nٺ~[.g2@B(Z@R~yٸ2R+At_e< +4r,׍;P&ph9t-9c3XuN޾MOA<8QnԶ•)H[E,Q;Զ zFIF! \_цkf{ [V'O/2Po~\RU},CӴxq)ᙄ9?̫ פ-_A58"9ʇ@ecC|nMa\]w'Һf%f:f,VFK|\ [QOT ռ,~V?7ʭ[p-1.:BGPYٖPX@:u7gB2/s,8ھ+[ G ejb`qoTtˣxpr>:Z;hU];kܼsD qIQxx3GŊ?Pɧl7KA4QIY?D:l}Y8>=5EF\g?ƩZ>/4?MgӅ@ilx;OJmM>hT)0?@iCyH}QQEK]]Eb-Khޭ>|io |||2<%P(Tlk=L g &vpq> Bgeeeџt**B  N,c2~6^^eYd2eq% ){˗^{N@eLfX$Y!u:$9{̦t"d4|^'ј!Ðd ^-lZ {꒐1р^ox:,o @!sAtIɒ%6z g_t̙<"lu\`&:(J(M:KɳBx(OE > D~西oc;1`,iXn'pm\θD mw6۠RFUKWF)dc'M*1oq"^TzYƦ` ^,9+ai_1s.+Vs1Dgg{RN8F̑S=YʾK{(WƄPݚ+SD!Y̓e9yg(NAJWAbh~'O YX1Rg/'ro N`!|0r=G,Ȝ|X}ӷ=G]g2jnV1"q6)mt.]0j>?ɘ Lr~$nu3Rt믰yMjN]`Q%NH7 }L{) QM'e"oZ߳dTϦ&9ZKVbchqşBȉ`Sc":Fˤ#,U~Rv GCbRyKY,_Ax .0Hf d/}< G6 Ce(S!#z5\w[W|Tc-;G$c$)y<4mpwRpU~,)ބdD2ZTX̓Sm!t[ϛEz)7c(^Ne} dX&#&7^!<ILk֠n=3&T2&YAݳ Z*XKk>]ed`ԧ[+72~p#lgof݉(֕$3$[Q@EO޴Ytvx2H3Vq/|Z`^S*ٜf"zogԥ3ɯ2G9+bC4jUjգq&*_2EI@sسʗ+X7:hkvʕI_ppʖ-h'V˟˙z1>2ԭ\NF ,6Rm8`ZUC2y^=&qٛ(c4JǴW@A|4徦"<T:5R\iJ]KŊ9KTP1?o/tdM:Z €"=:7M^Nh+e|l߼ k4zunNZհ+[5YK}^+ *쉎̹iQ w೶n`-TT=7"qp+̲=mSAUiT")ŧho,bآ+.`wof?iP3M,KJ y';>UrǠPٲy}ofuɢʖlQ~ԿeRճG-.t ӘiU*WyirgTs;Q1RcڣlmSp+EٹYr]-X[2w=eJ&zWj5&29ƷEEwg>/U4F*Ug_/OKUy_z-F]/?K}oy_z-N}Mɟ?-H9e"3"KgmKa=>渲 LxpKcn1xa˜l;+ìD'3f`EhE!0Sǟr~Fӟ5/O+ݱJmNG 2o2ꈊ+Qd%N(3IKr yG```NN/믗3DTp+oCoӨ1%fJ%2`R`6cdT 6jIUP+HLYs34ey7' o!z@B^#\ŧ۷oCFBf慃,7i˶fSXoՉgiߩh-挩ma^nCVh~V賌{YyBIBȴqٟ' Nd -q ( +aQusl)lύ|C>)c)ȩp&ʺ: 6"vl{dץ(:,( 4J%V"^Vn .Ax^DN&Ƚ yxW ^Wq vqZEɌVڊp+Q$(LaSw+t %0^VM97g< d֪wwU+xڠQ)ШTXk_3\A4le4Q5@Y2c0z!ShByVϝfUj5JшBF:˓3]J5QNoDV(qCP$S+ߋzPUJLzq: A<2QΨdYFLL2VZMrɈTD"IũxjLenEP)X&%>R*;6%MI&VI˪D%ul0 @Ɍ=,mo^+c4eGոFt|MgeG'(ns$l` ihFKI|ӽT_pe%UqǾDAdÿ,KںU[eϩr4f/Zb^FegXt7W 3(ݢ=wBqǍ9솙%򋼹)N?~q]rKV(\0dBzz{+2.< 0NG;7z{p+27[H4f ά3 YV:5K}zΕf)Gґ IDAT,qY?m&?ϚǢaW"F}֏;iѨ}>AɨR,Aϕx(СGS}2&/It:4Ğ#'ܯ ?NWXۇ6\9x3B.nbx]9H -(aط;K"S(vo\ʮxӏN!$$?^̸ɿҮae8.nr&3+ tR,=KA TOɤNvkvŭKxkıؠ*%t)9ISSBE C"*ef/筧 ښ3fЪU+Fc0W/[{3}$v< 0^lj0x'ެ~pFGq*(H%=Ns,0e'u]Ns"(&hp /L2sa u>z,X*~uԣ\ؿb:7d$!|X#ןc2SװkK33F 7F\iOΘ+`4cIciXZ䄦E:]TYyat:T3kηtQՇ )G炘c|5w:h-\z?n ZG~r5jS޷(T_X6w+Ǒ n]O)ׅ?tӡv4%cQ<*]΅'s(3OO3Grx u61kB[ {fĉjFcR?ewv6TǜAx2/ΎfxzDKxa dD͓ ~z/BF#t&LfFSQ O\:-xQr[avؖӁmJLz I-e=L7BA@6HTZ3]ҙ*f#qqq.a+7k!NVz%\cX!ׁA1}bu& #e?u#R.ZJ-G\mx7}R!sE>%:}qJl2a6IOI%N,g@lAf8>!UV+W$IrfGlM~b`WL4,Yv-Ň>`֭lݺ{axZZDPӞ/Q@Z֮_N4Z/*+R2n?s] ( s/ Ӥoє^gѣ(Kv>gOL\j5Z(֜*/i4ӾF5*VO>?֮/lҨ444izĪOե!\bW7;lZ6sϤ y%~\j5w&44UO%n|}I9e-ueaᤱtI [Ӑ(ۃ6`@83%~&f#) /BXUghuד)\YJ9Y]Z1=fl̜1mMNlP3X32O7xk^@؛뉮&Ϗ$`*ki,?ׯ_caa9H{ SqJ ߪ~1܂1Eǚ?I5\?fҤIT*LMM133!$R4/K=Ԩb_ZG=/EΟ?ʕ+E LMM111_.oBT_nQqj%roYQ\\*hDFcS*|eY/QDj ATR'~|"*:51AJςVu(RD EA@Vb@j/cZ_TQ41wS %AA5BIHcnn5"ْdX[[cUX"kkkLMc6fgEj$RU2z@G+g˹6ʃJ] EQ/\Oreȝ+f-ΒY,eGzmU9-n}Q#W3kcnD#"[& a ܶutPKSL?Nܔ-S3$J`Tȸ/\3~삠aTZ+rV*:жPJ_:UJRC+gRjUwJHp\EgK'rd"K a cwN[y'q4Yޝb٩[p8Ыj<^16?2Rw2J|aoyBɐ!5jԠrPfM*WLƌQUVʊE3gNgNݺuqrrhѢ:kQti찷ɉr-Z(UT‚"E=cTOmmmɕ+AA_ňMMM  2gLődTV SSSJ*E֬Yl.TE9`I6>lKf__€yb=zV/8p˽np}-C*.ڵݰJMׄ󦏣Ť=ۏ"Д%ײm6`}wF&'lJPX1jBFe?FCQ&CWaXF<;f=Ryfùp&Xgb(S 9 IؽNa&´ Iap:=Qh?SHܾk_"eWX6~s~]==uYEώt,M&fLOO4T jefA_O-UJZfKkYdݻh_/հʕ+cffFPPJenaTTAAAhZF&ajjJLܺu+ѵd2666 [RP$5o޼FZܹsܸqŋ@ܹMH5*pA|ܿ""`! Uvմ.(er"EA!נѪQk4jS4ZڒLy[J4.H^HayOoq(PXKO ڥ>5{:M7$?ZI综u-NnX5{ToZ%KSƖ^clFhH{Kr+ǘ 4*5 ZcZ\|Sعv jkį9+-O;/yDh?+oȢenD=KJS5XD052nCq-[D// 1cƸZ-666XXXh Q LFPP>| w:W|'gώ o߾FoYdIߠ 133CrIsXYYQlYrE@@Z;;;Ȑ!C6_JCQ!T%N'5l@PgӴtswIpV8CjPř5ڲw8rWS di.<3kcj2wclӧy1;fĖ.~{.g>j̇ ٜ+c%_^dqb%ȎɓRa֔6M^#c3/`ɘ^,9ۏ3d6i 55 *zNl'cܻ/[`EelwM˧a94B"_>ftqϯ9;1|4e fF TٞI.SۥKvr&\wU^ҕ ooxlO:Z< {h Y ?\eqܾ};/_bnnΓ'O?gs^xVMMM 4xׯߣGΝ;ヅVo/IK"|5A]?ϞfS`_'?YN3[ӘyYӔbhʹK3X۠ xst+!񫓘׾}طo߿DB$.5̲0`(fk\ vYӘyY39]5R;Cϖe{%$$$$t. 킄7#ݥ(!!!!!!!C(hQXZZ F1[2SLM@PXZ! "I-JyPA ٠R*01Թ0Mm J-,MKHP"ֶd25j?R!7*/VB41T+X{Fʑ3gWI)"QQXOsMU 9jӑnp(T17@&#I_t|D[Xaa'kcP)娵Iů\.'8BmV^mp*_Mgޭ ñ4*esBbe]-* wr2Ru>(>!=1 ŷv8uRdX%pxúhwgOtT:L2C{B xB4sE0kpE[)sC,@eNxmjg+i̊"j0{kVIz='ĉcx_Ad&8_W/2 DQ ٰl*n ڭ}pSѣV5d??/}PyӨ7`uУڨ;OB`hz#T%vco4mԋ5 RlCCkЀmgP"EϹ= zzw}ulX P]JMyanaӫ!5Ykc^ K օįp a[M29Hy1|MVNmܽHgDŽq_=1 slm0҅ːghpSSOmICX9?2q48bv_ߔTE ӸqSʸ6D=e)!oPxVz8_L{k&mroR+69,3cnfuL\X&k.|OI?=օs;wզ?q.sWE%p8Ǯ.fmے<ťxQ6¹lqyIO5Gu9e mgk$$fgzr[/{}~o|yџ^X/iMru3a\<~YALu!!7\֙rcQ`,w/ A$/~_?RŒ^uUi^fX#M0 @VRkQk4ޫѭ7*-Ki):;ݭ`tVѨP͙ډ2.0r"ymu#rP<^gˮh܋M1kd+n(C$j/>(@Zň@Fm,>P5J9vY20ɉcnk㧊s+ρ!ʸ$U˟Z5s.s Lnƒs.U)u62[7(ڳ={43˨\م˯C)զ'KW5 KޛЯZTuxtr-5gBş Q|˗[FyR}uXTv Ki7vs5.#+trMș+g90,ymkcn,FIK܊|'gZٱzVܮNqi3y(""r} 16cQ'5t:yI1Cm{ܾ\;BT͙k|p_Lg%ofBr91WK bbn˟FcF^5qqust¦2>X*o]aKܸqFrյKȓ;T]TJl% <-HlIzuHW(;:?/m?C17[?> Gl-lŸ-㺬,N5j[7w/1n@bY0q4]/$E}K,6Q@J t﷉aI"U~%&}%6:Oc&JӃv()mnRNI>klޓE|LMi avƗƜwc^HʕW# 6=fMɑC7s޼yz[lܸ7KI$$00b``wo$~> 5o#5M`{o7ӆspϟ"5\?>F_x\Ⱦ_% o凿KFjv9et4jM5 Sh1h5*/OVAL\EVI' ZcƗX>5*%jm.Nm>4DFFT'CBZYbQJEj$ ŧ8ERŹgyY^Ғ7S!e(rh;5|\k;>Q>pFJy {CP%;˕coˈ}HzeZO>4*Sr`Wp)Snm  gFT\W5b _!=R'o(>|h %-i(/E%m\Tɉɫ}AZ. IDATL[}j%9{bGGVP\)eNMS~[= c.o*Oq. kصm G"HrɁKY5y$=cװA.} @u:*0)q̕B^ocf*Fv=[dބn<MCg"c֝,?92i~6lIvw]KG10"?ܡd>4ϟ_#빆Nn L2fj (fo„Q'|*?0W#o@>(]ҍ#qrɭ%Ju\^z /I S_,ۏ фZ ZD]x! 9.%꺹~ AgIŮ `b-UISN^<'.x^fszr1/дnuZwAJCkhռ<4&<:b= oEKېebqh޼%L{KGsͤN|seaX MfT4?.nIKi)A#q/3~EPې޴|2^)ʘ>to74ȄOWY&W84fz?HƝ^,qD^T.;66QAyߵyXJ >c9{8vNggsA捸h:i8tܸw_rs%-6*awѿR~5DL5"5&sLIӸskI~`djl*i|{ 0s'":{v?0g>o 36~kcOBBB[>& FFPUZlIWŃ( P"+t5 |m Oq68&VN,G6SZIe9w.c2VQ~2Q{ކLz$c QJ"dxNw6:(OBBBK" ժr#$jf Nf_ff+GN=tҀ>wP?e̊]zۗ!I kѥV |Sݐd0nMF_ۙ.mqNUzysaa?_\:it>AFaq ݔ6(L3r8~“3^qkL]5+goaw`k, 58-c X c<Ƙu%u RcOg;> CH wgIAK"#"FIx\XϤ%ZtFct@/ID.W3EECPVf4VLj|VVQ@!WbrUP(TJ;+Ay VUⱄ(PiDQCHH!bD&)L1 cIIJHHH"5\߄ Jd달sQuBOCbE׏P֥&uvUX,Z>țKgxEhɝR* RѩDQԬ߉?EQI((SɃ K0ix{>+cT>Ȗ<{9'x TXo//R|U mGZHrMu??M ԩUAl߄U+#[4hʏV5RsUۍBZmM秷QPݕkCv]dpvԪ׍+K8(LҊ]7@s<(K=c%1QjWBBB"!R%MX)Pr ^M1w|)3Wcn"޼9~?@oE q56 ,g||vu*x%B_KQd67C2&x4Bٺ]kػn?qqcpbh҉w|of;kn^=Fa;s?Q 57F0Uk5ez{zXeQTmؖFjOQI'PzCwp b r6lې4=ѷJunz9w6ۖ#]?Q!IyW'WٶϛVYnNFI/jƒxp"@P2}PZ^Gn3YzKIEy-ʌ0~D&,&HKpeChBWJp~e;Oy/yy=Z_,> 3 [8ͭS/fĩmOyokWBBB">R%M$FFF+՘cm!ȱ#Rtpކȹ p^B>ETrP"~4tgXZGThj \AtVL#RQUئ?yċzcYgU4d`\9mɔð{̂eУu( &\$"*붲eƺdg__V'Bȣ1Ej8J&ZcN&k}VБ3f~ǖödjE#Y|?߫s1_g>FE1:jgޖѡ0r,GlJ* J:A~c :=/%("y1y":3dK =2"Y"2Qԩ;SS1|ZftJHHHGKQ"MEKs5l6;nݟg2o;<[0{3Z?: FLMnޒ]PEߔwXTlc8CVnF0moLY>L:Dv=ނ#+ sւU{S\3UnZ>&P,{x2JW hFd'E3ӵIG|]jhgS6R,u*NiZ.bvߎtlR Yg͉<5k9deqg>Ѹ1krfusԩEOcilej#`Gqpf 0n^{dRʼnWРuo|ddMz-37撁nZ𘰌 GM]ΔG8nf1[ewbB^^+-1[2˫;pS=^˹xj/|"gknƝFsӥy F lBDX4Ջ׬%ԌI!vl?-] H§iBrBɉx&&diH߹p\L8U" }=U3$\Y3KnԌ%/iYw$ HNLO1(l߅*{ 3Gt_3AGǵM֦ߪ㼼rJӰ}gBr祵 hظ Ek\t jUfLjǛbiToَ/پb.K DJQ󏥬c;ԕ x̤qycEs9s5GTXJd=#eW1W`C?s>u6+U;˼[g7L=7ҿ@NmdῨX%-}qkі3{%,5qcUj=QYhH5ڐ,3=S̙:3#|-_ 7ԩRϹ)߲3NcNN3VURUh2B"eoAosO݋`[l%G©f;ã#ϙr&du:cl5o onF4ĄeJRsP^3;4o6{9KGAk'2ad&.66_> K1km)gfϢ?C̽MFj!R>b[HxIg맑B8uֹѻ( }Qg k>f. gfHQrC!iWҞrq?'r@TX(/>ėѽ<8b*!| ϋ N%Te^șOO7"5\FVakx,u'&'*ZN&33\1k+JgZJ5R ZЛ'"~,:9frI9x{8'臆F(P(% ^l>OwLhjEl3ۡ4A\Lg^y&KKF"+ɪym޺FP)U*5GîdʀZ+ SDb ,>m#$DѠUiT) ";ςqI;VX{vyP ::=Q4O`t|2Ss rsJiػ<<}l:UC. /)S}/$&_FRl8ٗHkK"XؗxҾyIkEVmhќp9d6 ᳖gl+5B(\<6f`1'QPNgDyJU+lدڵdS7JVw'WAV]xA eҠ~#dhÜ4W;.E&^_X 2++ݲLO00&[8_!6oچwe 89o(<y3Z^!3Ec%38q zL.f QqI͋˅/u4ޙY꜅ZSq,㻱 :nܴ+G-7eںQ]8ףn㎌_:pV5\)^'/VõkQv"9dC g[BBB@>H5ɉ~omQQ2vX'/8AlJ77w!sF4( h Z^NU=牢⩃B;0clsTfǯ`@Ø5% B>7OKB;9gM~y?:R#)KHӢ?< "?to !!!!!!!HKHHHHHHH'm=#n{@ȣ?Vrs\|ewACBo:yTX}EydJ? U'|𞐈hS:a!f#?JOC/ɣ# $S[:-h< JԪ C4Bhʵ!Ï".Q]iӢk]m$ m؄v2d No^N{wBGBԯW K~`.Dwo^-vvXG}w(c殌OB {r9c;^aGN'N%]s]'*8q?cn e\"oI>} 4/m]T^O6|TUEsYqGoX{!s _zbعʓ/K[P0IԖ'틊=:[w=%a^bQKl'WrwڇSf+\zO?Er~zNuGq6x.]kI&V9RQSfMOvS(`_Sfaʠi\ r68ya3ݓr=hV>?o{:,uJ|ӫU}j8ףCVnu];׳{.cپK9sXp! fŭEG|^5Z5[{?ZТ$U W1nECsA_&P\9BU:d둓hdɒY;s$k=>L֖[se9(*رqNqLOt<sQi{~|=ٳ{3|Ӝ߶b\&,s\u~ŸrE1^v' K01;ON'kG6q/AVgsI\12K|_~4z5^ui# 7eK ڞ#Ъ .ZKc|d16">ߣKt1'U?2*EOmX7#wJyLsا LgED=hfpoG\c)-4cEt7`]HHj V4!0'# Ӳx6~){aAP§~Egs\/%i\!%&_F6%P# "f–çy}qoZz#(}vD![S#+jBR%Hz9`|%7.Yց]b Җ+7% gn;kܺlĭd]M0g>dh̅j-K)^' /^5:~~zF91,r/yVpͽu3 (𤎟c. ʻcL.k~YLe-_ڐg:yI}ivgW(rFwi&Y}/ʏu%ŒJZ:v_'%R^ JN$` IDATsNՙ:>/.fo+)P,^!B@z9OkÙ+=ɡm0Sh.$$~5~+C<y}V]#U f5hQ*9'Cʐ}c#M0 ٦EMV C"א?gAu4Ȕv#'cq/L!arT*r)ۺ\guޒ˗/ %q0j-Y7}<pW~ w/'<-Pn=[)BPF#7Ӑ.S^={RB-Ӝ;[T&) uTcOYR $/4ȨhrQ±ZOQc,G):(:_^R|Kk(^zg$.eD 2l>u[EB6[A-SKlc<(hd13ǥ^CF󢙣NY()R;wƑz:ƐT)r3Y6DDu2fPתIow7}쌤BBWoua tnߛf!.ދH4X(Bѣcokiִw3u}Q?#M0 ! "wBXf}cƤS0 zo">Yv-k׮echD JvAmS*sع3&X!MӮuk6̻kҥu(i"EAϷ"E32ookǣi[󾡭GL٠Ʉ\\I;.OEALs{dm'dF,B@O6EtU;VYVG\ D0s{N t|1ML8͍CՂī%0̳Kszr|V<:$H鱌w6USk%~o%CƎ֋1%=Ptuۻ'M[)Ia<1_Z`Ň-b㳑u!!+C+ȑ1)K."ƦSMqlПرwfƪvFMS.-̝WK(sml|iy7v(m6qyD0>5W>X L\!p}pI|$7ٷiC՟I!5\?>>p,f?{~dR? ďQ5T)!:"5KV2Ga1m'`b%p8#/5mKۅ_kvUK4 Nc󗈺-2c,qIhJ ň3o-}GNhBϊۙؿ9 ,Di3 ټOT1{[.߷L?4ɜ˱ki=]X?nudwعd S'vV\C~<^uV,_nIqZӴYE1$u|M[Eqg|vԊup(:q_foŎ_CRuغy%d%$Mim '|>]pj;nYlO!.qw ڳxj+ҩSB-g=5ş9b[W|ԨX5*Suu_Lz<{@dH|$}qLaJT`֍< 43qG̣pJ +nň^y[?bT$Nz M}|߫$]-g0G΢57\ C@a.KI+"?ŀSt#GNoLhR&?.{?gMB 3\}'s7b5lcbzlZC덋?~NȔ)S|o̚2^?ϑTc}j뵽@}xQ^?z]8t-,bca ,oeT)m?ٞB- \h ś̘]F}۱(r9up%&gƭS<1O>)u☀bdu`j FէCZM)\N|dFͦHx\(F!㍮Nb%yѫ-WsWu@Z/7ѯF!s)K1eT,ah0e|\$=9` d6nKrw:VS@' PX>vL׋?-_3fУmUL9=ܮewXp8ل'r8VIމ#{JȸcysqdLR=.Iɖ]6b\N܁d…}|yGQ'%lT+Sv:gJ!4)N֭t/l(6}ThޅcxM/tۏ^_|~gdpn݊cOHyI~\4={YU 92*Sƿi5y;gagYiϟ !/x¡?nףw>!ă!k]q9l)( Ndńz)݉ⴓlx(.bbb35_SJ!9)%>SbMLvS؛\EJrN=ײ[-NԹvuVvIJr vg췋m7|#EQi'%ŜmNɌ) XN͛|qyRX BIwuWLz ;wh_4i6?sq>~#/</B- \d{JG#1) @Ji"[0~<`Lw\j!t\[70GEo>'ּ>M 1G|2bԯגϿ9% #:qv|+W̧+y]O7T z=7ȵke*Ok%.ZAN,UxZyX6e(o5md yKWOvYqҠe{]BAwBoL1`JjBB`~N@gwS$b |:2_72fdZ7ALt 5z<=3L`&# }lloa´.ӟz՘h% 6oUfmȰVRR+~AjQTcNEg8&=}-zNfu""ʦqy3-=1jCfm"G`sB5 \rӹ[c{}|4{>6ҧ&ޝ7Ow!9reX)m˿l&O|4cyq'yQ:cS*47,aռ4/L3['X~KbpzoԯH\ՑƝ{㊾f]Z Pj@žs8r*\.p9܈-NW/ :L;7WKyx7oa K6W 3wMkmzL;EQ+5Mܟ'3|۹SC}v:.X/cv:=>k|zOQˢ>~Ï(rmJߣ~ !.qW)՜ f.gm 鏻|Wn/dcINa{)RE % QHqnȑ?'?ՔDՉLRR  lN, vǂ9N\'䙨tzG!h H|=G9z,Z|Nӥ*Ҡsܼ"11d3 ,|# ֭qB;E:ӹ=tRԨ"J?Ӟ ?dҰY$Bޢ}v& }H:-ޣPaT) 1㞩4 Š`frRݸ^Sg\7hMi?)Ȁ[p\7ou8{R/Fo/t/5߻-_Aq)AwF x +j)EqO(®/?_Vkj=k&y_z!iס;~/P$gYYrtۙb2Jt_70{{(=9K0Ev ҷhNEu*-@TO)|s%9SIVUyac&JPXAURUgQڽOB$Q@*2o8 Sbd¬{#+ P8kOx[ Ġaʢ*O\?Ke%iמuAD-{È7d|vk_6dD O8bgղ]JWʥVTyt;sLmڌgm #g,|8M*m ?_c֐넛<&-Xؾr2]UwdnF HXX{.{6s4\o?[R3մ+`!tl2ytE `eb^/(uJُBISqOdTH2"z,3Z|pF?{R$UQ1̘4#Q.4c2W9A}˻]1l8@hkb|Y"z4IL^M >lo43\&k0Ȗ cfT_^[30BYjfsEQjAQNz$h !!Kߒ?t_n= !=G+ q8(xfNjwem͔@}E!!& ͉ %ń+grZSq kF*ݗN!3%'cwdP|T縬~Fɜn_qq71Yɜ=}NY!$p,S3}ȳlNHm>_JNh(9EkرnS뿱y~?viDz L)Qm۫#}ow_$':w":%͑RYEHzˋs){RQ4([V-[29rk!:4͗ncoޜ-QF>ۿjqZ_.kw{X~S&mП74e˖Kz?G]w.؜Owo~Z<|dtQ(DݲyBK2(ʎSj/\1'!MÍFժH\b͚R ]#GAfNRնB|sN@m'/'QGqSɓGH'Ǝ?i|9.P؋.}{YOQ;ڷ#_g0pv:CyXvOk?Z%}ڵML,]quK'޲ ]t')mN$3h5k*^apإ@`(6l`H~`hߋj-Rh U?mz(-UiU7'?gV\Z1ԭK!<~&[cԫ4!MWN\vl}Z5DMt k3v!dKd >=fdѢT.L+V,%hRU܋&sSFMI:1=U߁1n0 yxr|:nռgϘIIޚ[7Z'xa^V>}Rzi;ز|ǶB3td8Wh˖NSo@_.>qoOPiݻ{WN'Lca!G`,1u\ O. >Z>}ϑqWZJqeؾ_#E՗ ~^R\,ٸի0X|M*z3xdڿV[,:4!cgܗgAelrct# wI2N /9oٝV(FXwzTUt\  +J:;fqhu8\̕ `8PqvQTG Cng֨$%)\@߻b*l"jly{ jEɜpKI sBo rB{IȲx!:_C65?"FСo -̡\cˢ Hm>sOR3T]^`OΆ'3G mR#g?3&ڿ^ҳGΘ 1uyzvʆozl;m" 7/Z*#cVukЕëpb!."D 8y>8 i3z\KpYDDDpR" ;*ffCS>#5O$>ٶ%Ҳ IDATBwۊ`KHkwDtia%~qSu8EQX:{gVY?nױ:Khkm^* H qHG(|*KLߜ?O;NGGT~'̕+Wv7BLɞ=x=+GȑawC!ɧB!3 \B!RLII͛Y!@޼y |dgB|8Çj2uTV^?~uv7nM z)mJQkJJ1tCMھgJQ=@iEtb{J1͔f?sPs$p !"} ri9tO U?Sv<kmhzJQ}qFQ;ϩgTu\/PIB!?[r7RvL/`y ZFH SwV9.!Bem>MFNd5pe DF[Q-ʔB!p98te4mKo.yg6].<һIB!yGJJ[^ЕO)zkHo*QoK=m͖8y %Bu&6n4cFYF]k^ \: ^.̠Ӏ?J;Em#ϰ$p !B|K=WT2ZǕQ躳Q[G!۔Bt?6f}>-uY7 \B! Q.o!:^JYwk\oA;h-כU,c.!Bܑ:DyR-Ǹ2+pG+7=ҏnɧB>ڑ.ꧯFԆ7umY.EkO\*D{wyy$p ![p^KMov]—å7 `AK.!ByhW4Br^w?vdU \!Ex \ڟO/*L!L7pY[rSp9tn^`[h^!Zz浡KvTˁ~VyYo0 \xި4.!BQgK; sK?h^[A/xȫjM#zRB!#tiGAWк/#]zRTsO'-W-?OY,/B_2bfQ.m>e!}):.Zz!Ksw;2%B=zk܃AzSBR?ӡkY8]ϥ^@{PjmIB!/7"浕-xmȖX!B5oa |&o [?}nA+ͻi;H17mp !B͟.m@wK^K^2d;Yޚ+okx[/AK!7x4|iGʼ?-ԢӊnzSc[xyM!BMoJoCa_~p(*_EL3 \<B!?O =p_>z-cF>-Hy~]! D`cxrUF:$ !Wpd #_ގxd2pA(dz5!B>c0_e*lA=j%B-_˙|H\ [ |)CcB!'|^|Y [p+/^gB!/= ZnwNF$x !_LAurgd>DIB!DF2hB} \ _^B!?EYj=pym8kAL!"w.!B B!ߝ.!BLB!}?0Fo?vrIENDB`flask-mongoengine-0.9.5/docs/_themes/0000755000372000037200000000000013241547757020363 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_themes/flask/0000755000372000037200000000000013241547757021463 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_themes/flask/static/0000755000372000037200000000000013241547757022752 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_themes/flask/static/flasky.css_t0000644000372000037200000001245313241547561025276 0ustar travistravis00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; background-color: #ddd; color: #000; margin: 0; padding: 0; } div.document { background: #fafafa; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 230px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; min-height: 34em; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { position: absolute; right: 0; margin-top: -70px; text-align: right; color: #888; padding: 10px; font-size: 14px; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 0 20px; } div.sphinxsidebarwrapper p.logo { padding: 20px 0 10px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #222; font-size: 24px; font-weight: normal; margin: 20px 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar a { color: #444; text-decoration: none; } div.sphinxsidebar a:hover { text-decoration: underline; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 20px; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; text-decoration: none!important; } a:hover tt { background: #EEE; } flask-mongoengine-0.9.5/docs/_themes/flask/theme.conf0000644000372000037200000000014113241547561023421 0ustar travistravis00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle flask-mongoengine-0.9.5/docs/_themes/flask_small/0000755000372000037200000000000013241547757022653 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_themes/flask_small/static/0000755000372000037200000000000013241547757024142 5ustar travistravis00000000000000flask-mongoengine-0.9.5/docs/_themes/flask_small/static/flasky.css_t0000644000372000037200000001100113241547561026452 0ustar travistravis00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } flask-mongoengine-0.9.5/docs/_themes/flask_small/layout.html0000644000372000037200000000125313241547561025050 0ustar travistravis00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} flask-mongoengine-0.9.5/docs/_themes/flask_small/theme.conf0000644000372000037200000000027013241547561024614 0ustar travistravis00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px github_fork = '' flask-mongoengine-0.9.5/docs/_themes/README0000644000372000037200000000210513241547561021232 0ustar travistravis00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge flask-mongoengine-0.9.5/docs/_themes/flask_theme_support.py0000644000372000037200000001141313241547561025004 0ustar travistravis00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } flask-mongoengine-0.9.5/docs/Makefile0000644000372000037200000001101613241547561020367 0ustar travistravis00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/flask-unittest.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/flask-unittest.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/flask-unittest" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/flask-unittest" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." flask-mongoengine-0.9.5/docs/changelog.rst0000644000372000037200000000666213241547561021423 0ustar travistravis00000000000000========= Changelog ========= Development =========== - (Fill this out as you fix issues and develop features). Changes in 0.9.1 ================ - Fixed setup.py for various platforms (#298). - Added Flask-WTF v0.14 support (#294). - MongoEngine instance now holds a reference to a particular Flask app it was initialized with (#261). Changes in 0.9.0 ================ - BREAKING CHANGE: Dropped Python v2.6 support Changes in 0.8.2 ================ - Fixed relying on mongoengine.python_support. - Fixed cleaning up empty connection settings #285 Changes in 0.8.1 ================ - Fixed connection issues introduced in 0.8 - Removed support for MongoMock Changes in 0.8 ============== - Dropped MongoEngine 0.7 support - Added MongoEngine 0.10 support - Added PyMongo 3 support - Added Python3 support up to 3.5 - Allowed empying value list in SelectMultipleField - Fixed paginator issues - Use InputRequired validator to allow 0 in required field - Made help_text Field attribute optional - Added "radio" form_arg to convert field into RadioField - Added "textarea" form_arg to force conversion into TextAreaField - Added field parameters (validators, filters...) - Fixed 'False' connection settings ignored - Fixed bug to allow multiple instances of extension - Added MongoEngineSessionInterface support for PyMongo's tz_aware option - Support arbitrary primary key fields (not "id") - Configurable httponly flag for MongoEngineSessionInterface - Various bugfixes, code cleanup and documentation improvements - Move from deprecated flask.ext.* to flask_* syntax in imports - Added independent connection handler for FlaskMongoEngine - All MongoEngine connection calls are proxied via FlaskMongoEngine connection handler - Added backward compatibility for settings key names - Added support for MongoMock and temporary test DB - Fixed issue with multiple DB support - Various bugfixes Changes in 0.7 ============== - Fixed only / exclude in model forms (#49) - Added automatic choices coerce for simple types (#34) - Fixed EmailField and URLField rendering and validation (#44, #9) - Use help_text for field description (#43) - Fixed Pagination and added Document.paginate_field() helper - Keep model_forms fields in order of creation - Added MongoEngineSessionInterface (#5) - Added customisation hooks for FieldList sub fields (#19) - Handle non ascii chars in the MongoDebugPanel (#22) - Fixed toolbar stacktrace if a html directory is in the path (#31) - ModelForms no longer patch Document.update (#32) - No longer wipe field kwargs in ListField (#20, #19) - Passthrough ModelField.save-arguments (#26) - QuerySetSelectMultipleField now supports initial value (#27) - Clarified configuration documentation (#33) - Fixed forms when EmbeddedDocument has no default (#36) - Fixed multiselect restore bug (#37) - Split out the examples into a single file app and a cross file app Changes in 0.6 ============== - Support for JSON and DictFields - Speeding up QuerySetSelectField with big querysets Changes in 0.5 ============== - Added support for all connection settings - Fixed extended DynamicDocument Changes in 0.4 ============== - Added CSRF support and validate_on_save via flask.ext.WTF - Fixed DateTimeField not required Changes in 0.3 =============== - Reverted mongopanel - got knocked out by a merge - Updated imports paths Changes in 0.2 =============== - Added support for password StringField - Added ModelSelectMultiple Changes in 0.1 =============== - Released to PyPi flask-mongoengine-0.9.5/docs/conf.py0000644000372000037200000001621013241547561020227 0ustar travistravis00000000000000# -*- coding: utf-8 -*- # # flask-script documentation build configuration file, created by # sphinx-quickstart on Wed Jun 23 08:26:41 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('_themes')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Flask-MongoEngine' copyright = u'2010-2011, Streetlife and others' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import flask_mongoengine # The short X.Y version. version = flask_mongoengine.__version__ # The full version, including alpha/beta/rc tags. release = flask_mongoengine.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. #pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'flask_small' html_theme_options = { 'index_logo': '', 'github_fork': None } # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'flask-mongoenginedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'flask-mongoengine.tex', u'Flask-MongoEngine Documentation', u'Ross Lawley', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'flask-mongoengine', u'Flask-MongoEngine Documentation', [u'Ross Lawley', u'Dan Jacob', u'Marat Khabibullin'], 1) ] flask-mongoengine-0.9.5/docs/index.rst0000644000372000037200000002015513241547561020574 0ustar travistravis00000000000000Flask-MongoEngine ================= A Flask extension that provides integration with `MongoEngine `_. For more information on MongoEngine please check out the `MongoEngine Documentation `_. It handles connection management for your app. You can also use `WTForms `_ as model forms for your models. Installing Flask-MongoEngine ============================ Install with **pip**:: pip install flask-mongoengine Configuration ============= Basic setup is easy, just fetch the extension:: from flask import Flask from flask_mongoengine import MongoEngine app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db = MongoEngine(app) Or, if you are setting up your database before your app is initialized, as is the case with application factories:: from flask import Flask from flask_mongoengine import MongoEngine db = MongoEngine() ... app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db.init_app(app) By default, Flask-MongoEngine assumes that the :program:`mongod` instance is running on **localhost** on port **27017**, and you wish to connect to the database named **test**. If MongoDB is running elsewhere, you should provide the :attr:`host` and :attr:`port` settings in the `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': '192.168.1.35', 'port': 12345 } If the database requires authentication, the :attr:`username` and :attr:`password` arguments should be provided `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'username':'webapp', 'password':'pwd123' } Uri style connections are also supported, just supply the uri as the :attr:`host` in the `'MONGODB_SETTINGS'` dictionary with `app.config`. **Note that database name from uri has priority over name.** :: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': 'mongodb://localhost/database_name' } Connection settings may also be provided individually by prefixing the setting with `'MONGODB_'` in the `app.config`.:: app.config['MONGODB_DB'] = 'project1' app.config['MONGODB_HOST'] = '192.168.1.35' app.config['MONGODB_PORT'] = 12345 app.config['MONGODB_USERNAME'] = 'webapp' app.config['MONGODB_PASSWORD'] = 'pwd123' By default flask-mongoengine open the connection when extension is instanciated but you can configure it to open connection only on first database access by setting the ``MONGODB_SETTINGS['connect']`` parameter or its ``MONGODB_CONNECT`` flat equivalent to ``False``:: app.config['MONGODB_SETTINGS'] = { 'host': 'mongodb://localhost/database_name', 'connect': False, } # or app.config['MONGODB_CONNECT'] = False Custom Queryset =============== flask-mongoengine attaches the following methods to Mongoengine's default QuerySet: * **get_or_404**: works like .get(), but calls abort(404) if the object DoesNotExist. * **first_or_404**: same as above, except for .first(). * **paginate**: paginates the QuerySet. Takes two arguments, *page* and *per_page*. * **paginate_field**: paginates a field from one document in the QuerySet. Arguments: *field_name*, *doc_id*, *page*, *per_page*. Examples:: # 404 if object doesn't exist def view_todo(todo_id): todo = Todo.objects.get_or_404(_id=todo_id) .. # Paginate through todo def view_todos(page=1): paginated_todos = Todo.objects.paginate(page=page, per_page=10) # Paginate through tags of todo def view_todo_tags(todo_id, page=1): todo = Todo.objects.get_or_404(_id=todo_id) paginated_tags = todo.paginate_field('tags', page, per_page=10) Properties of the pagination object include: iter_pages, next, prev, has_next, has_prev, next_num, prev_num. In the template:: {# Display a page of todos #}
    {% for todo in paginated_todos.items %}
  • {{ todo.title }}
  • {% endfor %}
{# Macro for creating navigation links #} {% macro render_navigation(pagination, endpoint) %} {% endmacro %} {{ render_navigation(paginated_todos, 'view_todos') }} MongoEngine and WTForms ======================= flask-mongoengine automatically generates WTForms from MongoEngine models:: from flask_mongoengine.wtf import model_form class User(db.Document): email = db.StringField(required=True) first_name = db.StringField(max_length=50) last_name = db.StringField(max_length=50) class Content(db.EmbeddedDocument): text = db.StringField() lang = db.StringField(max_length=3) class Post(db.Document): title = db.StringField(max_length=120, required=True, validators=[validators.InputRequired(message=u'Missing title.'),]) author = db.ReferenceField(User) tags = db.ListField(db.StringField(max_length=30)) content = db.EmbeddedDocumentField(Content) PostForm = model_form(Post) def add_post(request): form = PostForm(request.POST) if request.method == 'POST' and form.validate(): # do something redirect('done') return render_template('add_post.html', form=form) For each MongoEngine field, the most appropriate WTForm field is used. Parameters allow the user to provide hints if the conversion is not implicit:: PostForm = model_form(Post, field_args={'title': {'textarea': True}}) Supported parameters: For fields with `choices`: - `multiple` to use a SelectMultipleField - `radio` to use a RadioField For `StringField`: - `password` to use a PasswordField - `textarea` to use a TextAreaField (By default, a StringField is converted into a TextAreaField if and only if it has no max_length.) Supported fields ---------------- * StringField * BinaryField * URLField * EmailField * IntField * FloatField * DecimalField * BooleanField * DateTimeField * **ListField** (using wtforms.fields.FieldList ) * SortedListField (duplicate ListField) * **EmbeddedDocumentField** (using wtforms.fields.FormField and generating inline Form) * **ReferenceField** (using wtforms.fields.SelectFieldBase with options loaded from QuerySet or Document) * DictField Not currently supported field types: ------------------------------------ * ObjectIdField * GeoLocationField * GenericReferenceField Session Interface ================= To use MongoEngine as your session store simple configure the session interface:: from flask_mongoengine import MongoEngine, MongoEngineSessionInterface app = Flask(__name__) db = MongoEngine(app) app.session_interface = MongoEngineSessionInterface(db) Debug Toolbar Panel =================== .. image:: _static/debugtoolbar.png :target: #debug-toolbar-panel If you use the Flask-DebugToolbar you can add `'flask_mongoengine.panels.MongoDebugPanel'` to the `DEBUG_TB_PANELS` config list and then it will automatically track your queries:: from flask import Flask from flask_debugtoolbar import DebugToolbarExtension app = Flask(__name__) app.config['DEBUG_TB_PANELS'] = ['flask_mongoengine.panels.MongoDebugPanel'] db = MongoEngine(app) toolbar = DebugToolbarExtension(app) Upgrading ========= 0.6 to 0.7 ---------- `ListFieldPagination` order of arguments have been changed to be more logical:: # Old order ListFieldPagination(self, queryset, field_name, doc_id, page, per_page, total) # New order ListFieldPagination(self, queryset, doc_id, field_name, page, per_page, total) Credits ======= Inspired by two repos: `danjac `_ `maratfm `_ flask-mongoengine-0.9.5/docs/make.bat0000644000372000037200000001003213241547561020331 0ustar travistravis00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\flask-unittest.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\flask-unittest.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end flask-mongoengine-0.9.5/flask_mongoengine/0000755000372000037200000000000013241547757021474 5ustar travistravis00000000000000flask-mongoengine-0.9.5/flask_mongoengine/templates/0000755000372000037200000000000013241547757023472 5ustar travistravis00000000000000flask-mongoengine-0.9.5/flask_mongoengine/templates/panels/0000755000372000037200000000000013241547757024754 5ustar travistravis00000000000000flask-mongoengine-0.9.5/flask_mongoengine/templates/panels/mongo-panel.html0000644000372000037200000001454513241547561030060 0ustar travistravis00000000000000 {% macro render_stats(title, queries, slow_query_limit=100) %}

{{ title }}

{% if queries %} {% if title == 'Queries' %} {% elif title == 'Inserts' %} {% elif title == 'Removes' %} {% elif title == 'Updates' %} {% endif %} {% for query in queries %} {% if title == "Queries" %} {% set colspan = 10 %} {% elif title == "Inserts" %} {% set colspan = 5 %} {% elif title == 'Removes' %} {% set colspan = 5 %} {% elif title == 'Updates' %} {% set colspan = 8 %} {% endif %} {% if title == "Queries" %} {% endif %} {% endfor %}
Time (ms) SizeOperation Collection Query Ordering Skip Limit DataDocument SafeQuery / Id SafeQuery Update Safe Multi UpsertStack Trace
slow_query_limit %}style="color:red;" {% endif %}> {{ query.time|round(3) }} {{ query.size|round(2) }}Kb{{ query.operation|title }} {{ query.collection }} {% if query.query %}{{ query.query|safe }}{% endif %} {% if query.ordering %}{{ query.ordering }}{% endif %} {% if query.skip %}{{ query.skip }}{% endif %} {% if query.limit %}{{ query.limit }}{% endif %} Toggle{{ query.document|safe }} {{ query.safe }}
{{ query.spec_or_id|safe }}
{{ query.safe }}
{{ query.spec|safe }}
{{ query.document|safe }}
{{ query.safe }} {{ query.multi }} {{ query.upsert }}Toggle
{{ query.data|pprint }}
{% for line in query.stack_trace %} {% endfor %}
Line File Function Code
{{ line.1 }} {{ line.0 }} {{ line.2 }} {{ line.3|safe }}
Toggle full trace
{% else %}

No {{ title|lower }} recorded

{% endif %} {% endmacro %} {{ render_stats("Queries", queries, slow_query_limit)}} {{ render_stats("Removes", removes, slow_query_limit)}} {{ render_stats("Inserts", inserts, slow_query_limit)}} {{ render_stats("Updates", updates, slow_query_limit)}} flask-mongoengine-0.9.5/flask_mongoengine/wtf/0000755000372000037200000000000013241547757022274 5ustar travistravis00000000000000flask-mongoengine-0.9.5/flask_mongoengine/wtf/__init__.py0000644000372000037200000000020113241547561024367 0ustar travistravis00000000000000from flask_mongoengine.wtf.orm import model_fields, model_form # noqa from flask_mongoengine.wtf.base import WtfBaseField # noqa flask-mongoengine-0.9.5/flask_mongoengine/wtf/base.py0000644000372000037200000000233313241547561023552 0ustar travistravis00000000000000from mongoengine.base import BaseField __all__ = ('WtfBaseField') class WtfBaseField(BaseField): """ Extension wrapper class for mongoengine BaseField. This enables flask-mongoengine wtf to extend the number of field parameters, and settings on behalf of document model form generator for WTForm. @param validators: wtf model form field validators. @param filters: wtf model form field filters. """ def __init__(self, validators=None, filters=None, **kwargs): self.validators = \ self._ensure_callable_or_list(validators, 'validators') self.filters = self._ensure_callable_or_list(filters, 'filters') BaseField.__init__(self, **kwargs) def _ensure_callable_or_list(self, field, msg_flag): """ Ensure the value submitted via field is either a callable object to convert to list or it is in fact a valid list value. """ if field is not None: if callable(field): field = [field] else: msg = "Argument '%s' must be a list value" % msg_flag if not isinstance(field, list): raise TypeError(msg) return field flask-mongoengine-0.9.5/flask_mongoengine/wtf/fields.py0000644000372000037200000001352413241547561024112 0ustar travistravis00000000000000""" Useful form fields for use with the mongoengine. """ import json import sys from gettext import gettext as _ from mongoengine.queryset import DoesNotExist import six from wtforms import widgets from wtforms.fields import SelectFieldBase, StringField, TextAreaField from wtforms.validators import ValidationError __all__ = ( 'ModelSelectField', 'QuerySetSelectField', ) if sys.version_info >= (3, 0): unicode = str class QuerySetSelectField(SelectFieldBase): """ Given a QuerySet either at initialization or inside a view, will display a select drop-down field of choices. The `data` property actually will store/keep an ORM model instance, not the ID. Submitting a choice which is not in the queryset will result in a validation error. Specifying `label_attr` in the constructor will use that property of the model instance for display in the list, else the model object's `__str__` or `__unicode__` will be used. If `allow_blank` is set to `True`, then a blank choice will be added to the top of the list. Selecting this choice will result in the `data` property being `None`. The label for the blank choice can be set by specifying the `blank_text` parameter. """ widget = widgets.Select() def __init__(self, label=u'', validators=None, queryset=None, label_attr='', allow_blank=False, blank_text=u'---', **kwargs): super(QuerySetSelectField, self).__init__(label, validators, **kwargs) self.label_attr = label_attr self.allow_blank = allow_blank self.blank_text = blank_text self.queryset = queryset def iter_choices(self): if self.allow_blank: yield (u'__None', self.blank_text, self.data is None) if self.queryset is None: return self.queryset.rewind() for obj in self.queryset: label = self.label_attr and getattr(obj, self.label_attr) or obj if isinstance(self.data, list): selected = obj in self.data else: selected = self._is_selected(obj) yield (obj.id, label, selected) def process_formdata(self, valuelist): if valuelist: if valuelist[0] == '__None': self.data = None else: if self.queryset is None: self.data = None return try: obj = self.queryset.get(pk=valuelist[0]) self.data = obj except DoesNotExist: self.data = None def pre_validate(self, form): if not self.allow_blank or self.data is not None: if not self.data: raise ValidationError(_(u'Not a valid choice')) def _is_selected(self, item): return item == self.data class QuerySetSelectMultipleField(QuerySetSelectField): widget = widgets.Select(multiple=True) def __init__(self, label=u'', validators=None, queryset=None, label_attr='', allow_blank=False, blank_text=u'---', **kwargs): super(QuerySetSelectMultipleField, self).__init__( label, validators, queryset, label_attr, allow_blank, blank_text, **kwargs) def process_formdata(self, valuelist): if valuelist: if valuelist[0] == '__None': self.data = None else: if not self.queryset: self.data = None return self.queryset.rewind() self.data = list(self.queryset(pk__in=valuelist)) if not len(self.data): self.data = None # If no value passed, empty the list else: self.data = None def _is_selected(self, item): return item in self.data if self.data else False class ModelSelectField(QuerySetSelectField): """ Like a QuerySetSelectField, except takes a model class instead of a queryset and lists everything in it. """ def __init__(self, label=u'', validators=None, model=None, **kwargs): queryset = kwargs.pop('queryset', model.objects) super(ModelSelectField, self).__init__(label, validators, queryset=queryset, **kwargs) class ModelSelectMultipleField(QuerySetSelectMultipleField): """ Allows multiple select """ def __init__(self, label=u'', validators=None, model=None, **kwargs): queryset = kwargs.pop('queryset', model.objects) super(ModelSelectMultipleField, self).__init__(label, validators, queryset=queryset, **kwargs) class JSONField(TextAreaField): def _value(self): if self.raw_data: return self.raw_data[0] else: return self.data and unicode(json.dumps(self.data)) or u'' def process_formdata(self, value): if value: try: self.data = json.loads(value[0]) except ValueError: raise ValueError(self.gettext(u'Invalid JSON data.')) class DictField(JSONField): def process_formdata(self, value): super(DictField, self).process_formdata(value) if value and not isinstance(self.data, dict): raise ValueError(self.gettext(u'Not a valid dictionary.')) class NoneStringField(StringField): """ Custom StringField that counts "" as None """ def process_formdata(self, valuelist): if valuelist: self.data = valuelist[0] if self.data == "": self.data = None class BinaryField(TextAreaField): """ Custom TextAreaField that converts its value with binary_type. """ def process_formdata(self, valuelist): if valuelist: if six.PY3: self.data = six.binary_type(valuelist[0], 'utf-8') else: self.data = six.binary_type(valuelist[0]) flask-mongoengine-0.9.5/flask_mongoengine/wtf/models.py0000644000372000037200000000125613241547561024126 0ustar travistravis00000000000000from flask_wtf import FlaskForm class ModelForm(FlaskForm): """A WTForms mongoengine model form""" def __init__(self, formdata=None, **kwargs): self.instance = (kwargs.pop('instance', None) or kwargs.get('obj')) if self.instance and not formdata: kwargs['obj'] = self.instance self.formdata = formdata super(ModelForm, self).__init__(formdata, **kwargs) def save(self, commit=True, **kwargs): if self.instance: self.populate_obj(self.instance) else: self.instance = self.model_class(**self.data) if commit: self.instance.save(**kwargs) return self.instance flask-mongoengine-0.9.5/flask_mongoengine/wtf/orm.py0000644000372000037200000002341713241547561023443 0ustar travistravis00000000000000""" Tools for generating forms based on mongoengine Document schemas. """ import decimal import sys from bson import ObjectId try: from collections import OrderedDict except ImportError: # Use bson's SON implementation instead from bson import SON as OrderedDict from mongoengine import ReferenceField from wtforms import fields as f, validators from flask_mongoengine.wtf.fields import (BinaryField, DictField, ModelSelectField, ModelSelectMultipleField, NoneStringField) from flask_mongoengine.wtf.models import ModelForm __all__ = ( 'model_fields', 'model_form', ) if sys.version_info >= (3, 0): unicode = str def converts(*args): def _inner(func): func._converter_for = frozenset(args) return func return _inner class ModelConverter(object): def __init__(self, converters=None): if not converters: converters = {} for name in dir(self): obj = getattr(self, name) if hasattr(obj, '_converter_for'): for classname in obj._converter_for: converters[classname] = obj self.converters = converters def convert(self, model, field, field_args): kwargs = { 'label': getattr(field, 'verbose_name', field.name), 'description': getattr(field, 'help_text', None) or '', 'validators': getattr(field, 'validators', None) or [], 'filters': getattr(field, 'filters', None) or [], 'default': field.default, } if field_args: kwargs.update(field_args) if kwargs['validators']: # Create a copy of the list since we will be modifying it. kwargs['validators'] = list(kwargs['validators']) if field.required: kwargs['validators'].append(validators.InputRequired()) else: kwargs['validators'].append(validators.Optional()) ftype = type(field).__name__ if field.choices: kwargs['choices'] = field.choices if ftype in self.converters: kwargs["coerce"] = self.coerce(ftype) multiple_field = kwargs.pop('multiple', False) radio_field = kwargs.pop('radio', False) if multiple_field: return f.SelectMultipleField(**kwargs) if radio_field: return f.RadioField(**kwargs) return f.SelectField(**kwargs) ftype = type(field).__name__ if hasattr(field, 'to_form_field'): return field.to_form_field(model, kwargs) if ftype in self.converters: return self.converters[ftype](model, field, kwargs) @classmethod def _string_common(cls, model, field, kwargs): if field.max_length or field.min_length: kwargs['validators'].append( validators.Length(max=field.max_length or -1, min=field.min_length or -1)) @classmethod def _number_common(cls, model, field, kwargs): if field.max_value or field.min_value: kwargs['validators'].append( validators.NumberRange(max=field.max_value, min=field.min_value)) @converts('StringField') def conv_String(self, model, field, kwargs): if field.regex: kwargs['validators'].append(validators.Regexp(regex=field.regex)) self._string_common(model, field, kwargs) password_field = kwargs.pop('password', False) textarea_field = kwargs.pop('textarea', False) or not field.max_length if password_field: return f.PasswordField(**kwargs) if textarea_field: return f.TextAreaField(**kwargs) return f.StringField(**kwargs) @converts('URLField') def conv_URL(self, model, field, kwargs): kwargs['validators'].append(validators.URL()) self._string_common(model, field, kwargs) return NoneStringField(**kwargs) @converts('EmailField') def conv_Email(self, model, field, kwargs): kwargs['validators'].append(validators.Email()) self._string_common(model, field, kwargs) return NoneStringField(**kwargs) @converts('IntField') def conv_Int(self, model, field, kwargs): self._number_common(model, field, kwargs) return f.IntegerField(**kwargs) @converts('FloatField') def conv_Float(self, model, field, kwargs): self._number_common(model, field, kwargs) return f.FloatField(**kwargs) @converts('DecimalField') def conv_Decimal(self, model, field, kwargs): self._number_common(model, field, kwargs) return f.DecimalField(**kwargs) @converts('BooleanField') def conv_Boolean(self, model, field, kwargs): return f.BooleanField(**kwargs) @converts('DateTimeField') def conv_DateTime(self, model, field, kwargs): return f.DateTimeField(**kwargs) @converts('BinaryField') def conv_Binary(self, model, field, kwargs): # TODO: may be set file field that will save file`s data to MongoDB if field.max_bytes: kwargs['validators'].append(validators.Length(max=field.max_bytes)) return BinaryField(**kwargs) @converts('DictField') def conv_Dict(self, model, field, kwargs): return DictField(**kwargs) @converts('ListField') def conv_List(self, model, field, kwargs): if isinstance(field.field, ReferenceField): return ModelSelectMultipleField(model=field.field.document_type, **kwargs) if field.field.choices: kwargs['multiple'] = True return self.convert(model, field.field, kwargs) field_args = kwargs.pop("field_args", {}) unbound_field = self.convert(model, field.field, field_args) unacceptable = { 'validators': [], 'filters': [], 'min_entries': kwargs.get('min_entries', 0) } kwargs.update(unacceptable) return f.FieldList(unbound_field, **kwargs) @converts('SortedListField') def conv_SortedList(self, model, field, kwargs): # TODO: sort functionality, may be need sortable widget return self.conv_List(model, field, kwargs) @converts('GeoLocationField') def conv_GeoLocation(self, model, field, kwargs): # TODO: create geo field and widget (also GoogleMaps) return @converts('ObjectIdField') def conv_ObjectId(self, model, field, kwargs): return @converts('EmbeddedDocumentField') def conv_EmbeddedDocument(self, model, field, kwargs): kwargs = { 'validators': [], 'filters': [], 'default': field.default or field.document_type_obj, } form_class = model_form(field.document_type_obj, field_args={}) return f.FormField(form_class, **kwargs) @converts('ReferenceField') def conv_Reference(self, model, field, kwargs): return ModelSelectField(model=field.document_type, **kwargs) @converts('GenericReferenceField') def conv_GenericReference(self, model, field, kwargs): return def coerce(self, field_type): coercions = { "IntField": int, "BooleanField": bool, "FloatField": float, "DecimalField": decimal.Decimal, "ObjectIdField": ObjectId } return coercions.get(field_type, unicode) def model_fields(model, only=None, exclude=None, field_args=None, converter=None): """ Generate a dictionary of fields for a given database model. See `model_form` docstring for description of parameters. """ from mongoengine.base import BaseDocument, DocumentMetaclass if not isinstance(model, (BaseDocument, DocumentMetaclass)): raise TypeError('model must be a mongoengine Document schema') converter = converter or ModelConverter() field_args = field_args or {} names = ((k, v.creation_counter) for k, v in model._fields.items()) field_names = [n[0] for n in sorted(names, key=lambda n: n[1])] if only: field_names = [x for x in only if x in set(field_names)] elif exclude: field_names = [x for x in field_names if x not in set(exclude)] field_dict = OrderedDict() for name in field_names: model_field = model._fields[name] field = converter.convert(model, model_field, field_args.get(name)) if field is not None: field_dict[name] = field return field_dict def model_form(model, base_class=ModelForm, only=None, exclude=None, field_args=None, converter=None): """ Create a wtforms Form for a given mongoengine Document schema:: from flask_mongoengine.wtf import model_form from myproject.myapp.schemas import Article ArticleForm = model_form(Article) :param model: A mongoengine Document schema class :param base_class: Base form class to extend from. Must be a ``wtforms.Form`` subclass. :param only: An optional iterable with the property names that should be included in the form. Only these properties will have fields. :param exclude: An optional iterable with the property names that should be excluded from the form. All other properties will have fields. :param field_args: An optional dictionary of field names mapping to keyword arguments used to construct each field object. :param converter: A converter to generate the fields based on the model properties. If not set, ``ModelConverter`` is used. """ field_dict = model_fields(model, only, exclude, field_args, converter) field_dict['model_class'] = model return type(model.__name__ + 'Form', (base_class,), field_dict) flask-mongoengine-0.9.5/flask_mongoengine/__init__.py0000644000372000037200000001540613241547561023604 0ustar travistravis00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import inspect from flask import Flask, abort, current_app import mongoengine from mongoengine.base.fields import BaseField from mongoengine.errors import ValidationError from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned, QuerySet) from .connection import * from .json import override_json_encoder from .pagination import * from .sessions import * from .wtf import WtfBaseField VERSION = (0, 9, 5) def get_version(): """Return the VERSION as a string, e.g. for VERSION == (0, 9, 2), return '0.9.2'. """ return '.'.join(map(str, VERSION)) __version__ = get_version() def _patch_base_field(obj, name): """ If the object submitted has a class whose base class is mongoengine.base.fields.BaseField, then monkey patch to replace it with flask_mongoengine.wtf.WtfBaseField. @note: WtfBaseField is an instance of BaseField - but gives us the flexibility to extend field parameters and settings required of WTForm via model form generator. @see: flask_mongoengine.wtf.base.WtfBaseField. @see: model_form in flask_mongoengine.wtf.orm @param obj: MongoEngine instance in which we should locate the class. @param name: Name of an attribute which may or may not be a BaseField. """ # TODO is there a less hacky way to accomplish the same level of # extensibility/control? # get an attribute of the MongoEngine class and return if it's not # a class cls = getattr(obj, name) if not inspect.isclass(cls): return # if it is a class, inspect all of its parent classes cls_bases = list(cls.__bases__) # if any of them is a BaseField, replace it with WtfBaseField for index, base in enumerate(cls_bases): if base == BaseField: cls_bases[index] = WtfBaseField cls.__bases__ = tuple(cls_bases) break # re-assign the class back to the MongoEngine instance delattr(obj, name) setattr(obj, name, cls) def _include_mongoengine(obj): """ Copy all of the attributes from mongoengine and mongoengine.fields onto obj (which should be an instance of the MongoEngine class). """ # TODO why do we need this? What's wrong with importing from the # original modules? for module in (mongoengine, mongoengine.fields): for attr_name in module.__all__: if not hasattr(obj, attr_name): setattr(obj, attr_name, getattr(module, attr_name)) # patch BaseField if available _patch_base_field(obj, attr_name) def current_mongoengine_instance(): """Return a MongoEngine instance associated with current Flask app.""" me = current_app.extensions.get('mongoengine', {}) for k, v in me.items(): if isinstance(k, MongoEngine): return k class MongoEngine(object): """Main class used for initialization of Flask-MongoEngine.""" def __init__(self, app=None, config=None): _include_mongoengine(self) self.app = None self.Document = Document self.DynamicDocument = DynamicDocument if app is not None: self.init_app(app, config) def init_app(self, app, config=None): if not app or not isinstance(app, Flask): raise Exception('Invalid Flask application instance') self.app = app app.extensions = getattr(app, 'extensions', {}) # Make documents JSON serializable override_json_encoder(app) if 'mongoengine' not in app.extensions: app.extensions['mongoengine'] = {} if self in app.extensions['mongoengine']: # Raise an exception if extension already initialized as # potentially new configuration would not be loaded. raise Exception('Extension already initialized') if not config: # If not passed a config then we read the connection settings # from the app config. config = app.config # Obtain db connection(s) connections = create_connections(config) # Store objects in application instance so that multiple apps do not # end up accessing the same objects. s = {'app': app, 'conn': connections} app.extensions['mongoengine'][self] = s @property def connection(self): """ Return MongoDB connection(s) associated with this MongoEngine instance. """ return current_app.extensions['mongoengine'][self]['conn'] class BaseQuerySet(QuerySet): """Mongoengine's queryset extended with handy extras.""" def get_or_404(self, *args, **kwargs): """ Get a document and raise a 404 Not Found error if it doesn't exist. """ try: return self.get(*args, **kwargs) except (MultipleObjectsReturned, DoesNotExist, ValidationError): # TODO probably only DoesNotExist should raise a 404 abort(404) def first_or_404(self): """Same as get_or_404, but uses .first, not .get.""" obj = self.first() if obj is None: abort(404) return obj def paginate(self, page, per_page, **kwargs): """ Paginate the QuerySet with a certain number of docs per page and return docs for a given page. """ return Pagination(self, page, per_page) def paginate_field(self, field_name, doc_id, page, per_page, total=None): """ Paginate items within a list field from one document in the QuerySet. """ # TODO this doesn't sound useful at all - remove in next release? item = self.get(id=doc_id) count = getattr(item, field_name + "_count", '') total = total or count or len(getattr(item, field_name)) return ListFieldPagination(self, doc_id, field_name, page, per_page, total=total) class Document(mongoengine.Document): """Abstract document with extra helpers in the queryset class""" meta = {'abstract': True, 'queryset_class': BaseQuerySet} def paginate_field(self, field_name, page, per_page, total=None): """Paginate items within a list field.""" # TODO this doesn't sound useful at all - remove in next release? count = getattr(self, field_name + "_count", '') total = total or count or len(getattr(self, field_name)) return ListFieldPagination(self.__class__.objects, self.pk, field_name, page, per_page, total=total) class DynamicDocument(mongoengine.DynamicDocument): """Abstract Dynamic document with extra helpers in the queryset class""" meta = {'abstract': True, 'queryset_class': BaseQuerySet} flask-mongoengine-0.9.5/flask_mongoengine/connection.py0000644000372000037200000001176413241547561024207 0ustar travistravis00000000000000import mongoengine from pymongo import ReadPreference, uri_parser __all__ = ( 'create_connections', 'get_connection_settings', 'InvalidSettingsError', ) MONGODB_CONF_VARS = ('MONGODB_ALIAS', 'MONGODB_DB', 'MONGODB_HOST', 'MONGODB_IS_MOCK', 'MONGODB_PASSWORD', 'MONGODB_PORT', 'MONGODB_USERNAME', 'MONGODB_CONNECT', 'MONGODB_TZ_AWARE') class InvalidSettingsError(Exception): pass def _sanitize_settings(settings): """Given a dict of connection settings, sanitize the keys and fall back to some sane defaults. """ # Remove the "MONGODB_" prefix and make all settings keys lower case. resolved_settings = {} for k, v in settings.items(): if k.startswith('MONGODB_'): k = k[len('MONGODB_'):] k = k.lower() resolved_settings[k] = v # Handle uri style connections if "://" in resolved_settings.get('host', ''): # this section pulls the database name from the URI # PyMongo requires URI to start with mongodb:// to parse # this workaround allows mongomock to work uri_to_check = resolved_settings['host'] if uri_to_check.startswith('mongomock://'): uri_to_check = uri_to_check.replace('mongomock://', 'mongodb://') uri_dict = uri_parser.parse_uri(uri_to_check) resolved_settings['db'] = uri_dict['database'] # Add a default name param or use the "db" key if exists if resolved_settings.get('db'): resolved_settings['name'] = resolved_settings.pop('db') else: resolved_settings['name'] = 'test' # Add various default values. resolved_settings['alias'] = resolved_settings.get('alias', mongoengine.DEFAULT_CONNECTION_NAME) # TODO do we have to specify it here? MongoEngine should take care of that resolved_settings['host'] = resolved_settings.get('host', 'localhost') # TODO this is the default host in pymongo.mongo_client.MongoClient, we may not need to explicitly set a default here resolved_settings['port'] = resolved_settings.get('port', 27017) # TODO this is the default port in pymongo.mongo_client.MongoClient, we may not need to explicitly set a default here # Default to ReadPreference.PRIMARY if no read_preference is supplied resolved_settings['read_preference'] = resolved_settings.get('read_preference', ReadPreference.PRIMARY) # Clean up empty values for k, v in list(resolved_settings.items()): if v is None: del resolved_settings[k] return resolved_settings def get_connection_settings(config): """ Given a config dict, return a sanitized dict of MongoDB connection settings that we can then use to establish connections. For new applications, settings should exist in a "MONGODB_SETTINGS" key, but for backward compactibility we also support several config keys prefixed by "MONGODB_", e.g. "MONGODB_HOST", "MONGODB_PORT", etc. """ # Sanitize all the settings living under a "MONGODB_SETTINGS" config var if 'MONGODB_SETTINGS' in config: settings = config['MONGODB_SETTINGS'] # If MONGODB_SETTINGS is a list of settings dicts, sanitize each # dict separately. if isinstance(settings, list): # List of connection settings. settings_list = [] for setting in settings: settings_list.append(_sanitize_settings(setting)) return settings_list # Otherwise, it should be a single dict describing a single connection. else: return _sanitize_settings(settings) # If "MONGODB_SETTINGS" doesn't exist, sanitize the "MONGODB_" keys # as if they all describe a single connection. else: config = dict((k, v) for k, v in config.items() if k in MONGODB_CONF_VARS) # ugly dict comprehention in order to support python 2.6 return _sanitize_settings(config) def create_connections(config): """ Given Flask application's config dict, extract relevant config vars out of it and establish MongoEngine connection(s) based on them. """ # Validate that the config is a dict if config is None or not isinstance(config, dict): raise InvalidSettingsError('Invalid application configuration') # Get sanitized connection settings based on the config conn_settings = get_connection_settings(config) # If conn_settings is a list, set up each item as a separate connection # and return a dict of connection aliases and their connections. if isinstance(conn_settings, list): connections = {} for each in conn_settings: alias = each['alias'] connections[alias] = _connect(each) return connections # Otherwise, return a single connection return _connect(conn_settings) def _connect(conn_settings): """Given a dict of connection settings, create a connection to MongoDB by calling mongoengine.connect and return its result. """ db_name = conn_settings.pop('name') return mongoengine.connect(db_name, **conn_settings) flask-mongoengine-0.9.5/flask_mongoengine/json.py0000644000372000037200000000233013241547561023006 0ustar travistravis00000000000000from bson import json_util from flask.json import JSONEncoder from mongoengine.base import BaseDocument from mongoengine.queryset import QuerySet def _make_encoder(superclass): class MongoEngineJSONEncoder(superclass): """ A JSONEncoder which provides serialization of MongoEngine documents and queryset objects. """ def default(self, obj): if isinstance(obj, BaseDocument): return json_util._json_convert(obj.to_mongo()) elif isinstance(obj, QuerySet): return json_util._json_convert(obj.as_pymongo()) return superclass.default(self, obj) return MongoEngineJSONEncoder MongoEngineJSONEncoder = _make_encoder(JSONEncoder) def override_json_encoder(app): """ A function to dynamically create a new MongoEngineJSONEncoder class based upon a custom base class. This function allows us to combine MongoEngine serialization with any changes to Flask's JSONEncoder which a user may have made prior to calling init_app. NOTE: This does not cover situations where users override an instance's json_encoder after calling init_app. """ app.json_encoder = _make_encoder(app.json_encoder) flask-mongoengine-0.9.5/flask_mongoengine/operation_tracker.py0000644000372000037200000002240313241547561025553 0ustar travistravis00000000000000import copy import functools import inspect import os import sys import time try: import SocketServer except ImportError: import socketserver as SocketServer import bson import pymongo.collection import pymongo.cursor import pymongo.helpers __all__ = ['queries', 'inserts', 'updates', 'removes', 'install_tracker', 'uninstall_tracker', 'reset', 'response_sizes'] _original_methods = { 'insert': pymongo.collection.Collection.insert, 'update': pymongo.collection.Collection.update, 'remove': pymongo.collection.Collection.remove, 'refresh': pymongo.cursor.Cursor._refresh, '_unpack_response': pymongo.helpers._unpack_response, } queries = [] inserts = [] updates = [] removes = [] response_sizes = [] if sys.version_info >= (3, 0): unicode = str # Wrap helpers._unpack_response for getting response size @functools.wraps(_original_methods['_unpack_response']) def _unpack_response(response, *args, **kwargs): result = _original_methods['_unpack_response']( response, *args, **kwargs ) response_sizes.append(sys.getsizeof(response, len(response)) / 1024.0) return result # Wrap Cursor.insert for getting queries @functools.wraps(_original_methods['insert']) def _insert(collection_self, doc_or_docs, manipulate=True, safe=None, check_keys=True, **kwargs): start_time = time.time() result = _original_methods['insert']( collection_self, doc_or_docs, check_keys=check_keys, **kwargs ) total_time = (time.time() - start_time) * 1000 __traceback_hide__ = True # noqa stack_trace, internal = _tidy_stacktrace() inserts.append({ 'document': doc_or_docs, 'time': total_time, 'stack_trace': stack_trace, 'size': response_sizes[-1] if response_sizes else 0, 'internal': internal }) return result # Wrap Cursor.update for getting queries @functools.wraps(_original_methods['update']) def _update(collection_self, spec, document, upsert=False, maniuplate=False, safe=None, multi=False, **kwargs): start_time = time.time() result = _original_methods['update']( collection_self, spec, document, upsert=upsert, multi=multi, **kwargs ) total_time = (time.time() - start_time) * 1000 __traceback_hide__ = True # noqa stack_trace, internal = _tidy_stacktrace() updates.append({ 'document': document, 'upsert': upsert, 'multi': multi, 'spec': spec, 'time': total_time, 'stack_trace': stack_trace, 'size': response_sizes[-1] if response_sizes else 0, 'internal': internal }) return result # Wrap Cursor.remove for getting queries @functools.wraps(_original_methods['remove']) def _remove(collection_self, spec_or_id, safe=None, **kwargs): start_time = time.time() result = _original_methods['remove']( collection_self, spec_or_id, **kwargs ) total_time = (time.time() - start_time) * 1000 __traceback_hide__ = True # noqa stack_trace, internal = _tidy_stacktrace() removes.append({ 'spec_or_id': spec_or_id, 'time': total_time, ' ': stack_trace, 'size': response_sizes[-1] if response_sizes else 0, 'internal': internal }) return result # Wrap Cursor._refresh for getting queries @functools.wraps(_original_methods['refresh']) def _cursor_refresh(cursor_self): # Look up __ private instance variables def privar(name): return getattr(cursor_self, '_Cursor__{0}'.format(name), None) if privar('id') is not None: # getMore not query - move on return _original_methods['refresh'](cursor_self) # NOTE: See pymongo/cursor.py+557 [_refresh()] and # pymongo/message.py for where information is stored # Time the actual query start_time = time.time() result = _original_methods['refresh'](cursor_self) total_time = (time.time() - start_time) * 1000 query_son = privar('query_spec')() if not isinstance(query_son, bson.SON): if "$query" not in query_son: query_son = {"$query": query_son} data = privar("data") if data: query_son["data"] = data orderby = privar("ordering") if orderby: query_son["$orderby"] = orderby hint = privar("hint") if hint: query_son["$hint"] = hint snapshot = privar("snapshot") if snapshot: query_son["$snapshot"] = snapshot maxScan = privar("max_scan") if maxScan: query_son["$maxScan"] = maxScan __traceback_hide__ = True # noqa stack_trace, internal = _tidy_stacktrace() query_data = { 'time': total_time, 'operation': 'query', 'stack_trace': stack_trace, 'size': response_sizes[-1] if response_sizes else 0, 'data': copy.copy(privar('data')), 'internal': internal } # Collection in format . collection_name = privar('collection') query_data['collection'] = collection_name.full_name.split('.')[1] if query_data['collection'] == '$cmd': query_data['operation'] = 'command' # Handle count as a special case if 'count' in query_son: # Information is in a different format to a standar query query_data['collection'] = query_son['count'] query_data['operation'] = 'count' query_data['skip'] = query_son.get('skip') query_data['limit'] = query_son.get('limit') query_data['query'] = query_son['query'] else: # Normal Query query_data['skip'] = privar('skip') query_data['limit'] = privar('limit') query_data['query'] = query_son['$query'] query_data['ordering'] = _get_ordering(query_son) queries.append(query_data) return result def install_tracker(): if pymongo.collection.Collection.insert != _insert: pymongo.collection.Collection.insert = _insert if pymongo.collection.Collection.update != _update: pymongo.collection.Collection.update = _update if pymongo.collection.Collection.remove != _remove: pymongo.collection.Collection.remove = _remove if pymongo.cursor.Cursor._refresh != _cursor_refresh: pymongo.cursor.Cursor._refresh = _cursor_refresh if pymongo.helpers._unpack_response != _unpack_response: pymongo.helpers._unpack_response = _unpack_response def uninstall_tracker(): if pymongo.collection.Collection.insert == _insert: pymongo.collection.Collection.insert = _original_methods['insert'] if pymongo.collection.Collection.update == _update: pymongo.collection.Collection.update = _original_methods['update'] if pymongo.collection.Collection.remove == _remove: pymongo.collection.Collection.remove = _original_methods['remove'] if pymongo.cursor.Cursor._refresh == _cursor_refresh: pymongo.cursor.Cursor._refresh = _original_methods['cursor_refresh'] if pymongo.helpers._unpack_response == _unpack_response: pymongo.helpers._unpack_response = _original_methods['_unpack_response'] def reset(): global queries, inserts, updates, removes, response_sizes queries = [] inserts = [] updates = [] removes = [] response_sizes = [] def _get_ordering(son): """Helper function to extract formatted ordering from dict. """ def fmt(field, direction): return '{0}{1}'.format({-1: '-', 1: '+'}[direction], field) if '$orderby' in son: return ', '.join(fmt(f, d) for f, d in son['$orderby'].items()) def _tidy_stacktrace(): """ Tidy the stack_trace """ socketserver_path = os.path.realpath(os.path.dirname(SocketServer.__file__)) pymongo_path = os.path.realpath(os.path.dirname(pymongo.__file__)) paths = ['/site-packages/', '/flaskext/', socketserver_path, pymongo_path] internal = False # Check html templates fnames = [] for i in range(100): try: fname = sys._getframe(i).f_code.co_filename if '.html' in fname: fnames.append(fname) except Exception: break fnames = list(set(fnames)) trace = [] for path in fnames: if 'flask_debugtoolbar' in path: internal = True trace.append((path, '?', '?', '?', False)) if trace: return trace, internal stack = inspect.stack() reversed(stack) trace = [] for frame, path, line_no, func_name, text in (f[:5] for f in stack): s_path = os.path.realpath(path) # Support hiding of frames -- used in various utilities that provide # inspection. if '__traceback_hide__' in frame.f_locals: continue hidden = False if func_name == "": hidden = True if any([p for p in paths if p in s_path]): hidden = True if not text: text = '' else: if sys.version_info >= (3, 0): text = ''.join(text).strip() else: try: text = unicode(''.join(text).strip()) except Exception: pass trace.append((path, line_no, func_name, text, hidden)) return trace, internal flask-mongoengine-0.9.5/flask_mongoengine/pagination.py0000644000372000037200000001332213241547561024171 0ustar travistravis00000000000000# -*- coding: utf-8 -*- import math from flask import abort from mongoengine.queryset import QuerySet __all__ = ("Pagination", "ListFieldPagination") class Pagination(object): def __init__(self, iterable, page, per_page): if page < 1: abort(404) self.iterable = iterable self.page = page self.per_page = per_page if isinstance(iterable, QuerySet): self.total = iterable.count() else: self.total = len(iterable) start_index = (page - 1) * per_page end_index = page * per_page self.items = iterable[start_index:end_index] if isinstance(self.items, QuerySet): self.items = self.items.select_related() if not self.items and page != 1: abort(404) @property def pages(self): """The total number of pages""" return int(math.ceil(self.total / float(self.per_page))) def prev(self, error_out=False): """Returns a :class:`Pagination` object for the previous page.""" assert self.iterable is not None, ('an object is required ' 'for this method to work') iterable = self.iterable if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None return self.__class__(iterable, self.page - 1, self.per_page) @property def prev_num(self): """Number of the previous page.""" return self.page - 1 @property def has_prev(self): """True if a previous page exists""" return self.page > 1 def next(self, error_out=False): """Returns a :class:`Pagination` object for the next page.""" assert self.iterable is not None, ('an object is required ' 'for this method to work') iterable = self.iterable if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None return self.__class__(iterable, self.page + 1, self.per_page) @property def has_next(self): """True if a next page exists.""" return self.page < self.pages @property def next_num(self): """Number of the next page""" return self.page + 1 def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): """Iterates over the page numbers in the pagination. The four parameters control the thresholds how many numbers should be produced from the sides. Skipped page numbers are represented as `None`. This is how you could render such a pagination in the templates: .. sourcecode:: html+jinja {% macro render_pagination(pagination, endpoint) %} {% endmacro %} """ last = 0 for num in range(1, self.pages + 1): if ( num <= left_edge or num > self.pages - right_edge or (num >= self.page - left_current and num <= self.page + right_current) ): if last + 1 != num: yield None yield num last = num if last != self.pages: yield None class ListFieldPagination(Pagination): def __init__(self, queryset, doc_id, field_name, page, per_page, total=None): """Allows an array within a document to be paginated. Queryset must contain the document which has the array we're paginating, and doc_id should be it's _id. Field name is the name of the array we're paginating. Page and per_page work just like in Pagination. Total is an argument because it can be computed more efficiently elsewhere, but we still use array.length as a fallback. """ if page < 1: abort(404) self.page = page self.per_page = per_page self.queryset = queryset self.doc_id = doc_id self.field_name = field_name start_index = (page - 1) * per_page field_attrs = {field_name: {"$slice": [start_index, per_page]}} qs = queryset(pk=doc_id) self.items = getattr(qs.fields(**field_attrs).first(), field_name) self.total = total or len(getattr(qs.fields(**{field_name: 1}).first(), field_name)) if not self.items and page != 1: abort(404) def prev(self, error_out=False): """Returns a :class:`Pagination` object for the previous page.""" assert self.items is not None, ('a query object is required ' 'for this method to work') return self.__class__(self.queryset, self.doc_id, self.field_name, self.page - 1, self.per_page, self.total) def next(self, error_out=False): """Returns a :class:`Pagination` object for the next page.""" assert self.items is not None, ('a query object is required ' 'for this method to work') return self.__class__(self.queryset, self.doc_id, self.field_name, self.page + 1, self.per_page, self.total) flask-mongoengine-0.9.5/flask_mongoengine/panels.py0000644000372000037200000000417113241547561023324 0ustar travistravis00000000000000from flask import current_app from flask_debugtoolbar.panels import DebugPanel from jinja2 import ChoiceLoader, PackageLoader from flask_mongoengine import operation_tracker package_loader = PackageLoader('flask_mongoengine', 'templates') def _maybe_patch_jinja_loader(jinja_env): """Patch the jinja_env loader to include flaskext.mongoengine templates folder if necessary. """ if not isinstance(jinja_env.loader, ChoiceLoader): jinja_env.loader = ChoiceLoader([jinja_env.loader, package_loader]) elif package_loader not in jinja_env.loader.loaders: jinja_env.loader.loaders.append(package_loader) class MongoDebugPanel(DebugPanel): """Panel that shows information about MongoDB operations (including stack) Adapted from https://github.com/hmarr/django-debug-toolbar-mongo """ name = 'MongoDB' has_content = True def __init__(self, *args, **kwargs): super(MongoDebugPanel, self).__init__(*args, **kwargs) _maybe_patch_jinja_loader(self.jinja_env) operation_tracker.install_tracker() def process_request(self, request): operation_tracker.reset() def nav_title(self): return 'MongoDB' def nav_subtitle(self): attrs = ['queries', 'inserts', 'updates', 'removes'] ops = sum(sum((1 for o in getattr(operation_tracker, a) if not o['internal'])) for a in attrs) total_time = sum(sum(o['time'] for o in getattr(operation_tracker, a)) for a in attrs) return '{0} operations in {1:.2f}ms'.format(ops, total_time) def title(self): return 'MongoDB Operations' def url(self): return '' def content(self): context = self.context.copy() context['queries'] = operation_tracker.queries context['inserts'] = operation_tracker.inserts context['updates'] = operation_tracker.updates context['removes'] = operation_tracker.removes context['slow_query_limit'] = current_app.config.get('MONGO_DEBUG_PANEL_SLOW_QUERY_LIMIT', 100) return self.render('panels/mongo-panel.html', context) flask-mongoengine-0.9.5/flask_mongoengine/sessions.py0000644000372000037200000000575413241547561023720 0ustar travistravis00000000000000import datetime import sys import uuid from bson.tz_util import utc from flask.sessions import SessionInterface, SessionMixin from werkzeug.datastructures import CallbackDict __all__ = ("MongoEngineSession", "MongoEngineSessionInterface") if sys.version_info >= (3, 0): basestring = str class MongoEngineSession(CallbackDict, SessionMixin): def __init__(self, initial=None, sid=None): def on_update(self): self.modified = True CallbackDict.__init__(self, initial, on_update) self.sid = sid self.modified = False class MongoEngineSessionInterface(SessionInterface): """SessionInterface for mongoengine""" def __init__(self, db, collection='session'): """ The MongoSessionInterface :param db: The app's db eg: MongoEngine() :param collection: The session collection name defaults to "session" """ if not isinstance(collection, basestring): raise ValueError('collection argument should be string or unicode') class DBSession(db.Document): sid = db.StringField(primary_key=True) data = db.DictField() expiration = db.DateTimeField() meta = { 'allow_inheritance': False, 'collection': collection, 'indexes': [{'fields': ['expiration'], 'expireAfterSeconds': 60 * 60 * 24 * 7 * 31}] } self.cls = DBSession def get_expiration_time(self, app, session): if session.permanent: return app.permanent_session_lifetime if 'SESSION_TTL' in app.config: return datetime.timedelta(**app.config['SESSION_TTL']) return datetime.timedelta(days=1) def open_session(self, app, request): sid = request.cookies.get(app.session_cookie_name) if sid: stored_session = self.cls.objects(sid=sid).first() if stored_session: expiration = stored_session.expiration if not expiration.tzinfo: expiration = expiration.replace(tzinfo=utc) if expiration > datetime.datetime.utcnow().replace(tzinfo=utc): return MongoEngineSession(initial=stored_session.data, sid=stored_session.sid) return MongoEngineSession(sid=str(uuid.uuid4())) def save_session(self, app, session, response): domain = self.get_cookie_domain(app) httponly = self.get_cookie_httponly(app) if not session: if session.modified: response.delete_cookie(app.session_cookie_name, domain=domain) return expiration = datetime.datetime.utcnow().replace(tzinfo=utc) + self.get_expiration_time(app, session) if session.modified: self.cls(sid=session.sid, data=session, expiration=expiration).save() response.set_cookie(app.session_cookie_name, session.sid, expires=expiration, httponly=httponly, domain=domain) flask-mongoengine-0.9.5/flask_mongoengine.egg-info/0000755000372000037200000000000013241547757023166 5ustar travistravis00000000000000flask-mongoengine-0.9.5/flask_mongoengine.egg-info/PKG-INFO0000644000372000037200000002655013241547757024273 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: flask-mongoengine Version: 0.9.5 Summary: Flask-MongoEngine is a Flask extension that provides integration with MongoEngine and WTF model forms. Home-page: https://github.com/mongoengine/flask-mongoengine Author: Ross Lawley Author-email: ross.lawley@gmail.com License: BSD Description-Content-Type: UNKNOWN Description: Flask-MongoEngine ================= A Flask extension that provides integration with `MongoEngine `_. For more information on MongoEngine please check out the `MongoEngine Documentation `_. It handles connection management for your app. You can also use `WTForms `_ as model forms for your models. Installing Flask-MongoEngine ============================ Install with **pip**:: pip install flask-mongoengine Configuration ============= Basic setup is easy, just fetch the extension:: from flask import Flask from flask_mongoengine import MongoEngine app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db = MongoEngine(app) Or, if you are setting up your database before your app is initialized, as is the case with application factories:: from flask import Flask from flask_mongoengine import MongoEngine db = MongoEngine() ... app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db.init_app(app) By default, Flask-MongoEngine assumes that the :program:`mongod` instance is running on **localhost** on port **27017**, and you wish to connect to the database named **test**. If MongoDB is running elsewhere, you should provide the :attr:`host` and :attr:`port` settings in the `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': '192.168.1.35', 'port': 12345 } If the database requires authentication, the :attr:`username` and :attr:`password` arguments should be provided `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'username':'webapp', 'password':'pwd123' } Uri style connections are also supported, just supply the uri as the :attr:`host` in the `'MONGODB_SETTINGS'` dictionary with `app.config`. **Note that database name from uri has priority over name.** :: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': 'mongodb://localhost/database_name' } Connection settings may also be provided individually by prefixing the setting with `'MONGODB_'` in the `app.config`.:: app.config['MONGODB_DB'] = 'project1' app.config['MONGODB_HOST'] = '192.168.1.35' app.config['MONGODB_PORT'] = 12345 app.config['MONGODB_USERNAME'] = 'webapp' app.config['MONGODB_PASSWORD'] = 'pwd123' By default flask-mongoengine open the connection when extension is instanciated but you can configure it to open connection only on first database access by setting the ``MONGODB_SETTINGS['connect']`` parameter or its ``MONGODB_CONNECT`` flat equivalent to ``False``:: app.config['MONGODB_SETTINGS'] = { 'host': 'mongodb://localhost/database_name', 'connect': False, } # or app.config['MONGODB_CONNECT'] = False Custom Queryset =============== flask-mongoengine attaches the following methods to Mongoengine's default QuerySet: * **get_or_404**: works like .get(), but calls abort(404) if the object DoesNotExist. * **first_or_404**: same as above, except for .first(). * **paginate**: paginates the QuerySet. Takes two arguments, *page* and *per_page*. * **paginate_field**: paginates a field from one document in the QuerySet. Arguments: *field_name*, *doc_id*, *page*, *per_page*. Examples:: # 404 if object doesn't exist def view_todo(todo_id): todo = Todo.objects.get_or_404(_id=todo_id) .. # Paginate through todo def view_todos(page=1): paginated_todos = Todo.objects.paginate(page=page, per_page=10) # Paginate through tags of todo def view_todo_tags(todo_id, page=1): todo = Todo.objects.get_or_404(_id=todo_id) paginated_tags = todo.paginate_field('tags', page, per_page=10) Properties of the pagination object include: iter_pages, next, prev, has_next, has_prev, next_num, prev_num. In the template:: {# Display a page of todos #}
    {% for todo in paginated_todos.items %}
  • {{ todo.title }}
  • {% endfor %}
{# Macro for creating navigation links #} {% macro render_navigation(pagination, endpoint) %} {% endmacro %} {{ render_navigation(paginated_todos, 'view_todos') }} MongoEngine and WTForms ======================= flask-mongoengine automatically generates WTForms from MongoEngine models:: from flask_mongoengine.wtf import model_form class User(db.Document): email = db.StringField(required=True) first_name = db.StringField(max_length=50) last_name = db.StringField(max_length=50) class Content(db.EmbeddedDocument): text = db.StringField() lang = db.StringField(max_length=3) class Post(db.Document): title = db.StringField(max_length=120, required=True, validators=[validators.InputRequired(message=u'Missing title.'),]) author = db.ReferenceField(User) tags = db.ListField(db.StringField(max_length=30)) content = db.EmbeddedDocumentField(Content) PostForm = model_form(Post) def add_post(request): form = PostForm(request.POST) if request.method == 'POST' and form.validate(): # do something redirect('done') return render_template('add_post.html', form=form) For each MongoEngine field, the most appropriate WTForm field is used. Parameters allow the user to provide hints if the conversion is not implicit:: PostForm = model_form(Post, field_args={'title': {'textarea': True}}) Supported parameters: For fields with `choices`: - `multiple` to use a SelectMultipleField - `radio` to use a RadioField For `StringField`: - `password` to use a PasswordField - `textarea` to use a TextAreaField (By default, a StringField is converted into a TextAreaField if and only if it has no max_length.) Supported fields ---------------- * StringField * BinaryField * URLField * EmailField * IntField * FloatField * DecimalField * BooleanField * DateTimeField * **ListField** (using wtforms.fields.FieldList ) * SortedListField (duplicate ListField) * **EmbeddedDocumentField** (using wtforms.fields.FormField and generating inline Form) * **ReferenceField** (using wtforms.fields.SelectFieldBase with options loaded from QuerySet or Document) * DictField Not currently supported field types: ------------------------------------ * ObjectIdField * GeoLocationField * GenericReferenceField Session Interface ================= To use MongoEngine as your session store simple configure the session interface:: from flask_mongoengine import MongoEngine, MongoEngineSessionInterface app = Flask(__name__) db = MongoEngine(app) app.session_interface = MongoEngineSessionInterface(db) Debug Toolbar Panel =================== .. image:: _static/debugtoolbar.png :target: #debug-toolbar-panel If you use the Flask-DebugToolbar you can add `'flask_mongoengine.panels.MongoDebugPanel'` to the `DEBUG_TB_PANELS` config list and then it will automatically track your queries:: from flask import Flask from flask_debugtoolbar import DebugToolbarExtension app = Flask(__name__) app.config['DEBUG_TB_PANELS'] = ['flask_mongoengine.panels.MongoDebugPanel'] db = MongoEngine(app) toolbar = DebugToolbarExtension(app) Upgrading ========= 0.6 to 0.7 ---------- `ListFieldPagination` order of arguments have been changed to be more logical:: # Old order ListFieldPagination(self, queryset, field_name, doc_id, page, per_page, total) # New order ListFieldPagination(self, queryset, doc_id, field_name, page, per_page, total) Credits ======= Inspired by two repos: `danjac `_ `maratfm `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules flask-mongoengine-0.9.5/flask_mongoengine.egg-info/SOURCES.txt0000644000372000037200000000206313241547757025053 0ustar travistravis00000000000000AUTHORS LICENSE MANIFEST.in README.rst setup.cfg setup.py docs/Makefile docs/changelog.rst docs/conf.py docs/index.rst docs/make.bat docs/_static/debugtoolbar.png docs/_themes/README docs/_themes/flask_theme_support.py docs/_themes/flask/theme.conf docs/_themes/flask/static/flasky.css_t docs/_themes/flask_small/layout.html docs/_themes/flask_small/theme.conf docs/_themes/flask_small/static/flasky.css_t flask_mongoengine/__init__.py flask_mongoengine/connection.py flask_mongoengine/json.py flask_mongoengine/operation_tracker.py flask_mongoengine/pagination.py flask_mongoengine/panels.py flask_mongoengine/sessions.py flask_mongoengine.egg-info/PKG-INFO flask_mongoengine.egg-info/SOURCES.txt flask_mongoengine.egg-info/dependency_links.txt flask_mongoengine.egg-info/not-zip-safe flask_mongoengine.egg-info/requires.txt flask_mongoengine.egg-info/top_level.txt flask_mongoengine/templates/panels/mongo-panel.html flask_mongoengine/wtf/__init__.py flask_mongoengine/wtf/base.py flask_mongoengine/wtf/fields.py flask_mongoengine/wtf/models.py flask_mongoengine/wtf/orm.pyflask-mongoengine-0.9.5/flask_mongoengine.egg-info/dependency_links.txt0000644000372000037200000000000113241547757027234 0ustar travistravis00000000000000 flask-mongoengine-0.9.5/flask_mongoengine.egg-info/not-zip-safe0000644000372000037200000000000113241547757025414 0ustar travistravis00000000000000 flask-mongoengine-0.9.5/flask_mongoengine.egg-info/requires.txt0000644000372000037200000000006213241547757025564 0ustar travistravis00000000000000Flask>=0.8 Flask-WTF>=0.13 mongoengine>=0.8.0 six flask-mongoengine-0.9.5/flask_mongoengine.egg-info/top_level.txt0000644000372000037200000000002213241547757025712 0ustar travistravis00000000000000flask_mongoengine flask-mongoengine-0.9.5/AUTHORS0000644000372000037200000000233613241547561017054 0ustar travistravis00000000000000The PRIMARY AUTHORS are (and/or have been): Ross Lawley Bright Dadson Jorge Bastida Dan Jacob https://bitbucket.org/danjac Marat Khabibullin https://bitbucket.org/maratfm Streetlife.com atroche - https://github.com/atroche Rodrigue Cloutier Thomas Steinacher Anthony Nemitz Nauman Ahmad CONTRIBUTORS Dervived from the git logs, inevitably incomplete but all of whom and others have submitted patches, reported bugs and generally helped make MongoEngine that much better: * Dragos - https://github.com/cdragos * IamFive - https://github.com/IamFive * mickey06 - https://github.com/mickey06 * Serge S. Koval - https://github.com/mrjoes * Marcus Carlsson - https://github.com/xintron * RealJTG - https://github.com/RealJTG * Peter D. Gray * Massimo Santini * Len Buckens - https://github.com/buckensl * Garito - https://github.com/garito * Jérôme Lafréchoux - https://github.com/lafrech * Bruno Belarmino - https://github.com/brunobelarmino * Sibelius Seraphini - https://github.com/sibelius * Denny Huang - https://github.com/denny0223 * Stefan Wojcik - https://github.com/wojcikstefan * John Cass - https://github.com/jcass77 * Aly Sivji - https://github.com/alysivji flask-mongoengine-0.9.5/LICENSE0000644000372000037200000000266013241547561017011 0ustar travistravis00000000000000Copyright (c) 2010-2016 See AUTHORS. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flask-mongoengine-0.9.5/MANIFEST.in0000644000372000037200000000037013241547561017536 0ustar travistravis00000000000000include MANIFEST.in include README.rst include LICENSE include AUTHORS recursive-include flask_mongoengine/templates *.html recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo prune docs/_build prune docs/_themes/.gitflask-mongoengine-0.9.5/README.rst0000755000372000037200000000355613241547561017503 0ustar travistravis00000000000000================= Flask-MongoEngine ================= :Info: MongoEngine for Flask web applications. :Repository: https://github.com/MongoEngine/flask-mongoengine .. image:: https://travis-ci.org/MongoEngine/flask-mongoengine.svg?branch=master :target: https://travis-ci.org/MongoEngine/flask-mongoengine .. image:: https://coveralls.io/repos/github/MongoEngine/flask-mongoengine/badge.svg?branch=master :target: https://coveralls.io/github/MongoEngine/flask-mongoengine?branch=master About ===== Flask-MongoEngine is a Flask extension that provides integration with MongoEngine. It handles connection management for your app. You can also use WTForms as model forms for your models. Documentation ============= You can find the documentation at https://flask-mongoengine.readthedocs.io Installation ============ You can install this package using pypi: ``pip install flask-mongoengine`` Tests ===== To run the test suite, ensure you are running a local copy of Flask-MongoEngine and run: ``python setup.py nosetests``. To run the test suite on every supported versions of Python, PyPy and MongoEngine you can use ``tox``. Ensure tox and each supported Python, PyPy versions are installed in your environment: .. code-block:: shell # Install tox $ pip install tox # Run the test suites $ tox To run a single or selected test suits, use the nosetest convention. E.g. .. code-block:: shell $ python setup.py nosetests --tests tests/example_test.py:ExampleTestClass.example_test_method Contributing ============ We welcome contributions! see the `Contribution guidelines `_ Community ========= - `#flask-mongoengine IRC channel `_ License ======= Flask-MongoEngine is distributed under MIT license, see LICENSE for more details. flask-mongoengine-0.9.5/setup.cfg0000644000372000037200000000052513241547757017632 0ustar travistravis00000000000000[nosetests] rednose = 1 verbosity = 2 detailed-errors = 1 cover-erase = 1 cover-branches = 1 cover-package = flask_mongoengine tests = tests [flake8] ignore = E501,F403,F405,I201 exclude = build,dist,docs,examples,venv,.tox,.eggs max-complexity = 17 application-import-names = flask_mongoengine,tests [egg_info] tag_build = tag_date = 0 flask-mongoengine-0.9.5/setup.py0000644000372000037200000000476413241547561017525 0ustar travistravis00000000000000import io import os from setuptools import setup description = ('Flask-MongoEngine is a Flask extension ' 'that provides integration with MongoEngine and WTF model forms.') # Load index.rst as long_description doc_path = os.path.join(os.path.dirname(__file__), "docs", "index.rst") long_description = io.open(doc_path, encoding='utf-8').read() # Stops exit traceback on tests try: import multiprocessing # noqa except ImportError: pass def get_version(version_tuple): """Return the version tuple as a string, e.g. for (0, 10, 7), return '0.10.7'. """ return '.'.join(map(str, version_tuple)) # Dirty hack to get version number from flask_monogengine/__init__.py - we # can't import it as it depends on PyMongo and PyMongo isn't installed until # this file is read init = os.path.join(os.path.dirname(__file__), 'flask_mongoengine', '__init__.py') version_line = list(filter(lambda l: l.startswith('VERSION'), open(init)))[0] version = get_version(eval(version_line.split('=')[-1])) test_requirements = ['coverage', 'nose', 'rednose'] setup( name='flask-mongoengine', version=version, url='https://github.com/mongoengine/flask-mongoengine', license='BSD', author='Ross Lawley', author_email='ross.lawley@gmail.com', test_suite='nose.collector', zip_safe=False, platforms='any', install_requires=[ 'Flask>=0.8', 'Flask-WTF>=0.13', 'mongoengine>=0.8.0', 'six', ], packages=['flask_mongoengine', 'flask_mongoengine.wtf'], include_package_data=True, tests_require=test_requirements, setup_requires=test_requirements, # Allow proper nose usage with setuptools and tox description=description, long_description=long_description, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], ) flask-mongoengine-0.9.5/PKG-INFO0000644000372000037200000002655013241547757017114 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: flask-mongoengine Version: 0.9.5 Summary: Flask-MongoEngine is a Flask extension that provides integration with MongoEngine and WTF model forms. Home-page: https://github.com/mongoengine/flask-mongoengine Author: Ross Lawley Author-email: ross.lawley@gmail.com License: BSD Description-Content-Type: UNKNOWN Description: Flask-MongoEngine ================= A Flask extension that provides integration with `MongoEngine `_. For more information on MongoEngine please check out the `MongoEngine Documentation `_. It handles connection management for your app. You can also use `WTForms `_ as model forms for your models. Installing Flask-MongoEngine ============================ Install with **pip**:: pip install flask-mongoengine Configuration ============= Basic setup is easy, just fetch the extension:: from flask import Flask from flask_mongoengine import MongoEngine app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db = MongoEngine(app) Or, if you are setting up your database before your app is initialized, as is the case with application factories:: from flask import Flask from flask_mongoengine import MongoEngine db = MongoEngine() ... app = Flask(__name__) app.config.from_pyfile('the-config.cfg') db.init_app(app) By default, Flask-MongoEngine assumes that the :program:`mongod` instance is running on **localhost** on port **27017**, and you wish to connect to the database named **test**. If MongoDB is running elsewhere, you should provide the :attr:`host` and :attr:`port` settings in the `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': '192.168.1.35', 'port': 12345 } If the database requires authentication, the :attr:`username` and :attr:`password` arguments should be provided `'MONGODB_SETTINGS'` dictionary wih `app.config`.:: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'username':'webapp', 'password':'pwd123' } Uri style connections are also supported, just supply the uri as the :attr:`host` in the `'MONGODB_SETTINGS'` dictionary with `app.config`. **Note that database name from uri has priority over name.** :: app.config['MONGODB_SETTINGS'] = { 'db': 'project1', 'host': 'mongodb://localhost/database_name' } Connection settings may also be provided individually by prefixing the setting with `'MONGODB_'` in the `app.config`.:: app.config['MONGODB_DB'] = 'project1' app.config['MONGODB_HOST'] = '192.168.1.35' app.config['MONGODB_PORT'] = 12345 app.config['MONGODB_USERNAME'] = 'webapp' app.config['MONGODB_PASSWORD'] = 'pwd123' By default flask-mongoengine open the connection when extension is instanciated but you can configure it to open connection only on first database access by setting the ``MONGODB_SETTINGS['connect']`` parameter or its ``MONGODB_CONNECT`` flat equivalent to ``False``:: app.config['MONGODB_SETTINGS'] = { 'host': 'mongodb://localhost/database_name', 'connect': False, } # or app.config['MONGODB_CONNECT'] = False Custom Queryset =============== flask-mongoengine attaches the following methods to Mongoengine's default QuerySet: * **get_or_404**: works like .get(), but calls abort(404) if the object DoesNotExist. * **first_or_404**: same as above, except for .first(). * **paginate**: paginates the QuerySet. Takes two arguments, *page* and *per_page*. * **paginate_field**: paginates a field from one document in the QuerySet. Arguments: *field_name*, *doc_id*, *page*, *per_page*. Examples:: # 404 if object doesn't exist def view_todo(todo_id): todo = Todo.objects.get_or_404(_id=todo_id) .. # Paginate through todo def view_todos(page=1): paginated_todos = Todo.objects.paginate(page=page, per_page=10) # Paginate through tags of todo def view_todo_tags(todo_id, page=1): todo = Todo.objects.get_or_404(_id=todo_id) paginated_tags = todo.paginate_field('tags', page, per_page=10) Properties of the pagination object include: iter_pages, next, prev, has_next, has_prev, next_num, prev_num. In the template:: {# Display a page of todos #}
    {% for todo in paginated_todos.items %}
  • {{ todo.title }}
  • {% endfor %}
{# Macro for creating navigation links #} {% macro render_navigation(pagination, endpoint) %} {% endmacro %} {{ render_navigation(paginated_todos, 'view_todos') }} MongoEngine and WTForms ======================= flask-mongoengine automatically generates WTForms from MongoEngine models:: from flask_mongoengine.wtf import model_form class User(db.Document): email = db.StringField(required=True) first_name = db.StringField(max_length=50) last_name = db.StringField(max_length=50) class Content(db.EmbeddedDocument): text = db.StringField() lang = db.StringField(max_length=3) class Post(db.Document): title = db.StringField(max_length=120, required=True, validators=[validators.InputRequired(message=u'Missing title.'),]) author = db.ReferenceField(User) tags = db.ListField(db.StringField(max_length=30)) content = db.EmbeddedDocumentField(Content) PostForm = model_form(Post) def add_post(request): form = PostForm(request.POST) if request.method == 'POST' and form.validate(): # do something redirect('done') return render_template('add_post.html', form=form) For each MongoEngine field, the most appropriate WTForm field is used. Parameters allow the user to provide hints if the conversion is not implicit:: PostForm = model_form(Post, field_args={'title': {'textarea': True}}) Supported parameters: For fields with `choices`: - `multiple` to use a SelectMultipleField - `radio` to use a RadioField For `StringField`: - `password` to use a PasswordField - `textarea` to use a TextAreaField (By default, a StringField is converted into a TextAreaField if and only if it has no max_length.) Supported fields ---------------- * StringField * BinaryField * URLField * EmailField * IntField * FloatField * DecimalField * BooleanField * DateTimeField * **ListField** (using wtforms.fields.FieldList ) * SortedListField (duplicate ListField) * **EmbeddedDocumentField** (using wtforms.fields.FormField and generating inline Form) * **ReferenceField** (using wtforms.fields.SelectFieldBase with options loaded from QuerySet or Document) * DictField Not currently supported field types: ------------------------------------ * ObjectIdField * GeoLocationField * GenericReferenceField Session Interface ================= To use MongoEngine as your session store simple configure the session interface:: from flask_mongoengine import MongoEngine, MongoEngineSessionInterface app = Flask(__name__) db = MongoEngine(app) app.session_interface = MongoEngineSessionInterface(db) Debug Toolbar Panel =================== .. image:: _static/debugtoolbar.png :target: #debug-toolbar-panel If you use the Flask-DebugToolbar you can add `'flask_mongoengine.panels.MongoDebugPanel'` to the `DEBUG_TB_PANELS` config list and then it will automatically track your queries:: from flask import Flask from flask_debugtoolbar import DebugToolbarExtension app = Flask(__name__) app.config['DEBUG_TB_PANELS'] = ['flask_mongoengine.panels.MongoDebugPanel'] db = MongoEngine(app) toolbar = DebugToolbarExtension(app) Upgrading ========= 0.6 to 0.7 ---------- `ListFieldPagination` order of arguments have been changed to be more logical:: # Old order ListFieldPagination(self, queryset, field_name, doc_id, page, per_page, total) # New order ListFieldPagination(self, queryset, doc_id, field_name, page, per_page, total) Credits ======= Inspired by two repos: `danjac `_ `maratfm `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules