From 7eeec5da63bcf782949307b043fff7fa215289e3 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 13:41:26 +0300 Subject: [PATCH 1/8] Add new translations --- locale/en/LC_MESSAGES/django.mo | Bin 6809 -> 6691 bytes locale/en/LC_MESSAGES/django.po | 242 ++++++++++++++++------------- locale/fi/LC_MESSAGES/django.mo | Bin 8855 -> 8343 bytes locale/fi/LC_MESSAGES/django.po | 230 ++++++++++++++------------- members/templates/member_list.html | 7 - 5 files changed, 256 insertions(+), 223 deletions(-) diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo index 213409bceb643dc4a5f3a4780a70b8d633b2abdb..2808dcfe602297cff4f5b87ff410f8af0f9967f4 100644 GIT binary patch literal 6691 zcmeH}ZHygN8OIM$1QtYjSF3U;Z!PUD+m=FE3hnLg-Ev{yx_4VBg3Iik*}K!uow=Qv zyW2I!Si*;53?FC=2@o~0At5Ny7#lu_qO0NuB@u&B6RU|B6ZrtffYC_w_n&+2?zX%H zKWch%pZT5V%$aAN^PJ~7caC4U;!B2h0Qop_^bBJjh3n4ZLOXnpF)zS};Ms6jPlxwF zrkXlj3mfnQ@Q53K4DvHy=5ii<3Z4s(!4JV7I{hX1Vf5or^In7N;J;uAuHm5-@Gkg4 zc(2p9iM@E&$Cc=pL6=l zQ0w|Fl>I+Lt?PBD=l>2B-_ux}{J#=j4R3`O)}j1<5UP&8>h$NF{u?NJZ$kO^7UXBn zB&fwcK)rV*R9vrur^AhK`)TAjd;xuyMQYw{1XX_Rgxa@Va4p;o^`1j+{4iABd=YAW zk3!|=QOJ_aH=X`Gls`X%n)f@n4*ms};AIzg{H#FvcZbvOhFZ^kP<0qU)#2lg--oKZ zKSI^r=_IA}RZw~v%0Cavp9sqSXQAwV0V+O^IQ>zm^?e1(&nKbc@olJi--TMo51`ih z64booQ1$XEl-<8N{U1unLfNT7`R_sb zIRoW?%Z(p`Y^6C273U|Q^7!je^M3-bfiFV&^9EEt{}bY(xs)K#H6^I|6)69Pq2jQ| z=>b$7#!&MPx$%df*7u0x6HxDY63Xt=PG5jp*H5ABzYMjm<51842`av?LHU0H7uDff zXyGm>zf-6>eAwwvJN*SHd#^zG_bODJ{sxPE_*mz?=Rn2vLZ~{lkTNuzkeiVSL~*87GrSAA7Euo0j;L`IncI-E6q@RJ2eKOp5mKROlUysv8e}iB1<{$+ZL8_1wpCYs zi1Mg`s9lGsE^d+Hn(9LB3Z#mxl0y5mEujuG2yS0~fcYiyF*4Rqr*Ct5Y-9Xss4WGXY|vAwpM2TfDn zyVsPxC{IfBQJA#CEKg!vTR1wif8kgdXJNc>Y~ffM?hm8fv+2Ak*XuzmH)Y?K*p82# zZad1uRumLU%bRcFYSGUVnjM-67RQp>c!b~keYrkn=$=KoO|^gw&vNf zncV(2k?AisH%@Zv6*02j(sFDvwJ#_vD*-get>XR6ae44=Cfu^#{)%?X_XyOxJ??Iw z{CFDZp*WZ`{fk{2DpflA^#euxo#vYXrkjDF9)+=}y)gsHTpT5yZ~JR|%s@Ns?0v-# zbEC`h$aDr=8WlIYsZ&usnM&M=hVzp4(34`dyx&BUL#mi z=YpvW&&?o)0$;fkHd{%WyO3mNFpOgvA5=wKRkXRiHe~a#8RXtn6lA7aSggj=JWC81 zpj-5;H;Y-#Qt~clcxOgdPlF&XCU?5FllWsEL8``C?#1<`PG+fzdOJ;7Tr_Vpf>_US zc(H1#aj`&EkQbS%HbWPi&$j||N4aK(ytvWU+J`ugItwX!Eid%V5JkN_)YM73O{Q(FdZ!MN&K-<}87EA` zuF8Xh`E(dW{wg~*TARSe0glAn7Gq|_V`D~=c{hh<+oqg-xTYPYyWB8z{EgZ|Tg=1Y> zFU`ZcYH~tZzdKXOlNWn~Y@MxM+bsSl4Cy|WCOT_NRll?+NHcc2&yJP*?=0`E*gci; zT6J`!XFSk3P#Vq}poR2ck}jE%HyTs(b7QK-agtlTwQz5=t!kUMOd29pG@TCN!n)zcMlEO zk@9fGZmkTLt3%t%0|VogTJ1Kbc1pN35qQl$J5(Ooxmzx89U9smh3k{Fu`~x)hpWSt zrS)xC-`k^qFVeT9@bp<#)b)ITgz+u5KI5fXkl(s{Vz9JjX`cJ?}NZ>TBoJ*G};<)I9;aA5)8;IH$gLPJQj1`r0}5wX^(#>wn>E=RXKek$nIF literal 6809 zcmeH}U925d700)LfEN@55fqRO^3mF!_O^VLODR3Q_mm!bzk1JYr4_i$Ju~OrY3Ixw zX6Ce9G{FxNNeGb`z!)SD0%`~kH6it(F+{G2i9D1CL1G~A05K4K&|o42_4l9I_oL+_ z_)0oCYkqs}*|YZAYp=D>yn5Pk_ZzMhIR|<2SYsZ7>rdduwf}R*JO_UTPlU65CBF|D zYVL+B;E&+(@Sy8|2J$n%=XNrD0iFb3g{Q!`oc=C675#V~YTTJ{CA<(;;5K+1yaRq7 z-s$uoL%H}9csYC+ejWbF@!#-k=;vbeJh&c8ztQR6hD>c9g!1R-Q1%Z%+5H{Vyw5xR z1*rAC4EdSYxG5fggBtfQsCB#tncAF;F`DKq_*r-nl-(6hzZ8A}{VFKCTbw=yW#?vi z7EGYty9>(Sd!gQY5X$Zou75u~AN`=yUx8I zkRfIg%C7)w{@qaV&EPTcdvN1t$ba}8`m-!nXAC!MTb^0%$*7GD(Jw63hk8e4C zfn-$ut%a(`ZBD<{>GwkUw;#%%gHZPW2xa$WsCi#^`kPSe`#Y4M??A=l7?MWgPJ)W- zsZi@YA8K3$s%{3L>~3`W7N~q0g|d5-(|ssA8B{&q3H9DXQ2ss+_1=CcyHC6RXQA@t zkkkJGanZaF<>%=n-!ga()H-WWem9`@^9HE>oQ4|Ta{Y#-V{D0c@AA)SPc?l}cZ$sttdr;%gV38NYbD{hhg39YU#B~!uhMMm{jlU1dzn?+H z;c=%w1yzr~ff{$n_5T@aeXl#d4K>d@P}u6JGk`Cl#juYt0=3(8LqDqbO!U$;Zee+N{2?}Dnw2O#BG zTvLw1xl7t=hR9}wY0XANwsl=mqPRC*TY0J&-iq9U3?UaFHAJzv99fH~t|CP1x)xc7 z=)xs)BSHv^+`1X++Ksdk<*W8ym)5ZK+Ub-ODo@9qewm|%T7N%MN0bY?R+T93iuZD- ztHy6asz}B4DbC+SMv*QOBQpp`L~*TjR2~c?Taanjcb%iokWH?AA?zTkyGxPP$Tf)e z*+-P$ORwuulrzeo?Z_aaJ=V1W(f+STjzuPsE0J#@x*EvU$cPlK0YrJB9G|~bj|tLp z9aq6IWEbKg6Uh9fGkT9xcED}O8YFOim%wizHy~SGpYq%E*(FyiOtX9{_Bz2z8+i-O zpyP$nl;`_tkY(13ecO$^W-yhsrjj&lhq29Ol3wK7>A<$by#acjhH=~O^`c&otvYND zZ+3R76LhA7w6L_>%cjzx8Fs@U&Ocx$p9xAMw%;yGTCL?aN$nb%@$$p1n(C%ac6TzJ znd3Wjd82^Bx772e3S{dtPf7n z>-rvn`l#34<0D^B1HBXn`^?}%(}qfwQeQn(#NTPY8Dh8@3Yt+Ei`pAAlh#YH6CJrbgN1w&_Jd?0ZhlOzpNj zNMkRuVaIC+i|W2$I>U3b8AE}u+zC6~B+Xq&GP601V;SG9ingj~v!yX?^RN@--gFdX zre0XA$1UC^26WIYTGrc(S&dTiE@XH)B5S5W5Ep~XrY#eH+#^W!ILp1bx!A}oHPP&) zDT|9{ZAK8AISwyWO+79as0#8TQ`ctbX7kx@V6Lw=%&-@?ds_Q2=TW(kqSf_6-wadK zOGBQOJ!Q^`i2tgA?7X57oNeM#!mC{8OID81BQs5Eqd%WsbK&=0g{<7u!rq&Vgyve3wk z7FTSuX?S}B(JoG@S^>7~rk=uLORoD87FQIch?UPNA$ z#|n9|#R1>!o?vROwEtIJRdx8AzXb0}VWHMm;#>XZr#bL-x z6`g+rc5VNfD=Yn1RQj*9Yt{{{UAv|7e^xYLoM&YU?Z7+_&)sgy0ZE<~TR`vJkPmV0FC_DpJg}0jTlrX;9HfOvv3-W8W zPj0TPTO8;9A}h7HnfRpXfL%8o=6#>OZ=$(7kA4&#{U|EFndT45`ERa;FQKC!MMpo1 Sj(!v!{U}=cUG^#ZDEc3OcefP) diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index b9a67b3..ae88ae4 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 18:22+0300\n" +"POT-Creation-Date: 2017-09-25 13:38+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,35 +17,35 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: infoscreen/models.py:77 +#: infoscreen/models.py:97 msgid "ABB jobs" msgstr "ABB jobs" -#: infoscreen/models.py:88 +#: infoscreen/models.py:112 msgid "APY Item" msgstr "ÄPY Item" -#: infoscreen/models.py:99 +#: infoscreen/models.py:127 msgid "External website" msgstr "External website" -#: infoscreen/models.py:147 +#: infoscreen/models.py:184 msgid "Sössö articles" msgstr "Sössö articles" -#: infoscreen/models.py:158 +#: infoscreen/models.py:199 msgid "Events" msgstr "Events" -#: infoscreen/models.py:169 +#: infoscreen/models.py:214 msgid "Image" msgstr "Image" -#: infoscreen/models.py:204 +#: infoscreen/models.py:260 msgid "HSL timetables" msgstr "HSL timetables" -#: infoscreen/models.py:215 +#: infoscreen/models.py:275 msgid "External image" msgstr "External image" @@ -169,7 +169,8 @@ msgstr "Select rotation to edit" msgid "id" msgstr "id" -#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:46 +#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:60 +#: webapp/models.py:94 webapp/models.py:107 msgid "Name" msgstr "Name" @@ -183,14 +184,12 @@ msgid "Language" msgstr "Language" #: infoscreen/templates/infoscreen_admin.html:161 -#: members/templates/settings.html:20 sikweb/settings-sample.py:179 -#: sikweb/settings.py:178 +#: members/templates/settings.html:20 sikweb/base.py:214 msgid "Finnish" msgstr "Finnish" #: infoscreen/templates/infoscreen_admin.html:162 -#: members/templates/settings.html:21 sikweb/settings-sample.py:178 -#: sikweb/settings.py:177 +#: members/templates/settings.html:21 sikweb/base.py:215 msgid "English" msgstr "English" @@ -201,67 +200,68 @@ msgstr "English" msgid "Submit" msgstr "Submitted" -#: members/forms.py:20 members/tables.py:24 +#: members/forms.py:97 members/tables.py:32 msgid "Member" msgstr "Member" -#: members/models.py:16 +#: members/models.py:14 msgid "First name" msgstr "First name" -#: members/models.py:17 +#: members/models.py:15 msgid "Last name" msgstr "Last name" -#: members/models.py:18 +#: members/models.py:16 webapp/models.py:95 webapp/models.py:108 msgid "Email" msgstr "Email" -#: members/models.py:19 +#: members/models.py:17 msgid "Place of residence" msgstr "Place of residence" -#: members/models.py:20 members/models.py:83 +#: members/models.py:19 members/models.py:84 +#: members/templates/member_add_many.html:35 msgid "AYY" msgstr "AYY" -#: members/models.py:21 +#: members/models.py:20 msgid "JAS" msgstr "JAS" -#: members/models.py:69 +#: members/models.py:70 msgid "Submitted" msgstr "Submitted" -#: members/models.py:81 +#: members/models.py:82 msgid "Date" msgstr "Date" -#: members/models.py:82 +#: members/models.py:83 msgid "Source" msgstr "Source" -#: members/models.py:84 +#: members/models.py:85 msgid "Cash" msgstr "Cash" -#: members/models.py:85 +#: members/models.py:86 members/templates/member_add_many.html:36 msgid "Bank transfer" msgstr "Bank transfer" -#: members/models.py:102 +#: members/models.py:103 msgid "Created" msgstr "Created" -#: members/tables.py:9 +#: members/tables.py:13 msgid "Last paid" msgstr "Last paid" -#: members/tables.py:13 members/tables.py:28 members/tables.py:41 +#: members/tables.py:18 members/tables.py:37 members/tables.py:54 msgid "Edit" msgstr "Edit" -#: members/tables.py:15 members/tables.py:30 members/tables.py:43 +#: members/tables.py:20 members/tables.py:39 members/tables.py:56 msgid "Options" msgstr "Options" @@ -298,6 +298,12 @@ msgstr "Add member" msgid "Save" msgstr "Save" +#: members/templates/member_add_many.html:8 +#, fuzzy +#| msgid "Add member" +msgid "Add many members" +msgstr "Add member" + #: members/templates/member_add_many.html:13 msgid "" "\n" @@ -326,10 +332,41 @@ msgstr "" msgid "Syntax" msgstr "Syntax" -#: members/templates/member_add_many.html:32 +#: members/templates/member_add_many.html:29 +msgid "Data" +msgstr "" + +#: members/templates/member_add_many.html:33 +#, fuzzy +#| msgid "Payments" +msgid "Payment source" +msgstr "Payments" + +#: members/templates/member_add_many.html:37 +#, fuzzy +#| msgid "List payments" +msgid "Cash payment" +msgstr "List payments" + +#: members/templates/member_add_many.html:41 +#: members/templates/member_add_many_confirm.html:22 msgid "Send" msgstr "Send" +#: members/templates/member_add_many_confirm.html:8 +msgid "Confirm adding these entries?" +msgstr "" + +#: members/templates/member_add_many_confirm.html:12 +#: members/templates/members_base.html:52 webapp/templates/main_index.html:7 +msgid "Members" +msgstr "Members" + +#: members/templates/member_add_many_confirm.html:16 +#: members/templates/members_base.html:60 +msgid "Payments" +msgstr "Payments" + #: members/templates/member_delete_confirm.html:9 msgid "Are you sure you want to delete this member?" msgstr "Are you sure you want to delete this member?" @@ -339,31 +376,6 @@ msgstr "Are you sure you want to delete this member?" msgid "Yes, I'm sure" msgstr "Yes, I'm sure" -#: members/templates/member_duplicates.html:9 -msgid "Conflicting member entries" -msgstr "" - -#: members/templates/member_duplicates.html:13 -msgid "" -"Found conflicting member entries. Choose how to handle the problematic data." -msgstr "" - -#: members/templates/member_duplicates.html:29 -msgid "Which one has the correct information for this member?" -msgstr "" - -#: members/templates/member_duplicates.html:31 -msgid "Accept first and remove second" -msgstr "" - -#: members/templates/member_duplicates.html:32 -msgid "Accept second and remove first" -msgstr "" - -#: members/templates/member_duplicates.html:33 -msgid "Accept both as two members" -msgstr "" - #: members/templates/member_edit.html:9 msgid "Edit member" msgstr "Edit member" @@ -372,19 +384,13 @@ msgstr "Edit member" msgid "Member register" msgstr "Member register" -#: members/templates/member_list.html:16 -msgid "" -"There are duplicate member entries in the register.\n" -" Please visit duplicate resolver." -msgstr "" - -#: members/templates/member_list.html:28 +#: members/templates/member_list.html:21 #, fuzzy #| msgid "Member register" msgid "Members in register:" msgstr "Member register" -#: members/templates/member_list.html:34 +#: members/templates/member_list.html:27 msgid "Download CSV" msgstr "Download CSV" @@ -393,10 +399,6 @@ msgstr "Download CSV" msgid "Member register of SIK ry" msgstr "Member register of SIK ry" -#: members/templates/members_base.html:52 webapp/templates/main_index.html:7 -msgid "Members" -msgstr "Members" - #: members/templates/members_base.html:54 msgid "List members" msgstr "List members" @@ -405,10 +407,6 @@ msgstr "List members" msgid "Add multiple" msgstr "Add multiple" -#: members/templates/members_base.html:60 -msgid "Payments" -msgstr "Payments" - #: members/templates/members_base.html:62 msgid "List payments" msgstr "List payments" @@ -441,121 +439,127 @@ msgstr "Edit payment" msgid "Payment events" msgstr "Payment events" -#: members/views.py:129 members/views.py:186 members/views.py:205 +#: members/views.py:138 members/views.py:209 members/views.py:235 msgid "No member id specified" msgstr "No member id specified" -#: members/views.py:151 +#: members/views.py:164 msgid "Successfully added member" msgstr "Successfully added member" -#: members/views.py:172 +#: members/views.py:189 msgid "Successfully updated member" msgstr "Successfully updated member" -#: members/views.py:176 +#: members/views.py:197 msgid "Could not update member object" msgstr "Could not update member object" -#: members/views.py:190 +#: members/views.py:213 msgid "Successfully deleted member" msgstr "Successfully deleted member" -#: members/views.py:196 +#: members/views.py:224 msgid "Could not delete member object" msgstr "Could not delete member object" -#: members/views.py:239 members/views.py:273 members/views.py:291 +#: members/views.py:273 members/views.py:320 members/views.py:348 msgid "No application id specified" msgstr "No application id specified" -#: members/views.py:260 +#: members/views.py:301 msgid "Successfully accepted application" msgstr "Successfully accepted application" -#: members/views.py:263 +#: members/views.py:308 msgid "Could not accept application object" msgstr "Could not accept application object" -#: members/views.py:277 +#: members/views.py:324 msgid "Successfully deleted application" msgstr "Successfully deleted application" -#: members/views.py:282 +#: members/views.py:336 msgid "Could not delete application object" msgstr "Could not delete application object" -#: members/views.py:346 +#: members/views.py:413 msgid "Successfully added payment for member" msgstr "Successfully added payment for member" -#: members/views.py:359 members/views.py:372 members/views.py:386 +#: members/views.py:431 members/views.py:449 members/views.py:468 msgid "No payment id specified" msgstr "No payment id specified" -#: members/views.py:390 +#: members/views.py:473 msgid "Successfully deleted payment" msgstr "Successfully deleted payment" -#: members/views.py:395 +#: members/views.py:483 msgid "Could not delete payment object" msgstr "Could not delete payment object" -#: members/views.py:410 +#: members/views.py:502 msgid "Successfully updated payment" msgstr "Successfully updated payment" -#: members/views.py:413 +#: members/views.py:509 msgid "Could not update payment object" msgstr "Could not update payment object" -#: members/views.py:430 +#: members/views.py:531 msgid "Missing \"textfield\" POST request field" msgstr "Missing \"textfield\" POST request field" -#: members/views.py:435 -msgid "Successfully imported multiple members" -msgstr "Successfully imported multiple members" - -#: members/views.py:438 +#: members/views.py:583 msgid "Failed to import members" msgstr "Failed to import members" -#: members/views.py:504 -#, fuzzy -#| msgid "Successfully deleted member" -msgid "Successfully resolved all member conflicts." -msgstr "Successfully deleted member" - #: templates/footer.html:7 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" -#: webapp/models.py:47 +#: webapp/models.py:61 #, fuzzy #| msgid "Add member" msgid "Board member" msgstr "Add member" -#: webapp/models.py:54 +#: webapp/models.py:67 #, fuzzy #| msgid "Duration" msgid "Description" msgstr "Duration" -#: webapp/models.py:55 +#: webapp/models.py:68 msgid "Summary" msgstr "" -#: webapp/models.py:70 +#: webapp/models.py:96 +msgid "Message" +msgstr "" + +#: webapp/models.py:109 +msgid "Year" +msgstr "" + +#: webapp/models.py:123 +msgid "Role" +msgstr "" + +#: webapp/models.py:125 msgid "Start date" msgstr "" -#: webapp/models.py:71 +#: webapp/models.py:126 msgid "End date" msgstr "" -#: webapp/models.py:79 +#: webapp/models.py:136 +msgid "Official" +msgstr "" + +#: webapp/models.py:138 msgid "Phone number" msgstr "" @@ -603,5 +607,29 @@ msgstr "Sössö" msgid "Contact" msgstr "Contact" +#: webapp/templates/ohlhafv.html:8 +msgid "Ohlhafv" +msgstr "" + +#: webapp/templates/ohlhafv.html:15 +msgid "Challenge" +msgstr "" + +#: webapp/templates/ohlhafv_list.html:11 +msgid "All challenges" +msgstr "" + +#: webapp/templates/ohlhafv_list.html:15 +msgid "Total challenges:" +msgstr "" + +#~ msgid "Successfully imported multiple members" +#~ msgstr "Successfully imported multiple members" + +#, fuzzy +#~| msgid "Successfully deleted member" +#~ msgid "Successfully resolved all member conflicts." +#~ msgstr "Successfully deleted member" + #~ msgid "Select" #~ msgstr "Select" diff --git a/locale/fi/LC_MESSAGES/django.mo b/locale/fi/LC_MESSAGES/django.mo index d36e122e7e41e82f61b9f14554fa3888831bd591..c81386c28cfcae8a255be8bdf9a3a834a5613e0c 100644 GIT binary patch delta 3458 zcmYk7X>3$g7>3W3rOMV?3kbCKg2h&(YAI!_$gW7G$f~#im(DG7?JTu-2Cbk22xwg~ zBj68%qKyg|F^+MG8qqO9Ln8XaB?>MfF1YWXiNzNTmeB%7Vf8a0u-b)WEG!19rmk@CjH4k3xJk|3H4Gh(YRC z3T3af?J@97+84u0I29JdYhWYmo7Gg#!?4rxRd_D#cMoLuKGwNUF>!sD6Dg_JgHR z{f9vS6B=Gg33VkS%m;5K&5^%)ESut)qkF~x12@()!|wU{LES&iZBT^(PpR!9)xmq z7i0~y$G$&k-@gIR#{Lc@N#;|iE&3j6Tv3@ZOsE2Z3 zl5Mv@O}G?l;2NmR_)uHdVcQ#^=Gy|bW%og4;yE}Lz6%}r3sk`A67r&Ze<74%zHNu0 zJl+hoP`7RGfZBpbAlWthpdxw=%CSSX{T|d7z7LDwG1wk5=1aJab~9<-ult{+q7<$o zTn&gp9hMDHACxrI#0Q`pdJ(Gs>y}3>KZ6>75-R1Vpd1{)QP-KMfCO#ELj^DsPSpKh zOr;6KcBqBkhDzxXsEIy?O6f_cE%*&8Bg(5LEQYd=f{m~i>U**ZI&cfrcjHM|19Pw% z{s_nG{+Es{beIn1z>QE7wn0T2So;>JRNo5~(GF{W9Lk}6md`^u{4&%vehVsdhoLfZ z)beYX)&jp#(Sk)B0Jg`JKsizkH9;LzCK{kJb2(J17ei%WHPojyfa?Dc)PlRA=6?*z z@#ieFQ2k#SMgFz$#~75N6Ht5KhZD*6nPRAgs-Wz3P?6Q!b`#WsGoT!v2Nhup)WU0^ z#wVcGx!u0M$J!sQApiQ3?Z=>$9)e2c2T+IP6Knq-PN)3`)IyVRUzwNz)qerhbz2M- zX#n||TX_(Z*#*bI7oZ&a2r7^hXxDD3Ar=TJ}3dg};p|+sXDaMh%;T&(-AMQ%fNNQXvVuSY6M`833~n@f-) z)g51hRFsj^%S0Np(F)XIU#x@kY~6!8Z`Yv|s%8CrQRKa45tU|isWs>(s9aV+{(v>u zx}ck(GN%BOuoP}UH=>9dw%lUb4bxM2M6F@D<(<$+ThK@}6Kz8=q+6k)vvEPb_Wwis ztm`)xh0$tsE4mVGM5h;>sae|p$yC;&Cj0hk%U&$8m8*IU@O;EM%$KP=7oscB9eGRP zaTmB7O}DnCup04qlfRmCVT<;^x9AXVMi-$@Gzsa%)*zMqb%86cXRjEQ*hT6~ubPY%>y2WTHLrk~6ZE zBX17%o$Dsu?ERxkL)mXD4utxzAmpT<*>Uc+;mZbg(##|3UmRHqf zCRa`AlZg7+XjOM86CXV!vvPE}zOWRIIb9KaiQ}^(?CR+74L)@xlewOoay@aTH7iny Kz|U+NGyGqa$c;k) delta 3974 zcma);eT)?49mk(V6hRO4ct3i_6a)`A_5@#eJx*Q@uD}TvIjR=Q+}+&mu)8zM&g{We zae)eHs0>clTMOO`7z{ ze&_Q%&ph*cd!CuQXVyN`QGC08?69F7L!Ut##u`(Gnd`aGmfv8E2UoyX;kTh~4cu5| z4qn4}2rhz$;C1i?*Z^OGP4K+sdr<4g*BLXxn4+np!KXaao*1cutFG2-!366u;O*H0CI1$dpzu8df7?XmE^l>;Fe$S52 z+wpsFCi4@Bj>yd|P=U5`!M16Ka?k}8&`vvk0IDKAPzm}_0T$r(_%}!C=z+(eGJO(i z;d4+6&%kTpkDyBb3n)jwwexR4Vl#h)a&*y---ojIPdEeCG0=1Kp%O^K;#N8<=*Ylv zC`ZF~!L#tQj8EI~Z{SqMe}D?)Z?GAD09AomH&+u`0LL>o2fs2 zb3YelnuaoT5Gs@7P!WF};=>Hv`={;wAHd1X{}_^_c^zttE?#nZIj!8Oq@&Q1%tA z#xtPoG#2UT!G%y6-ww4mEA6-g%HUS0t>}iz@F1KApMnm21uCPrpkCt-p)#%~eZ7_| zpzeE6_m4mYUOYudj!)Z(Gf<^|89oBfK}Fob#w*ax@GiImYOB5m$G{UXoiOHEIKX%u z>3$c6P!(+81Zw>Ps57w)5?Il!q9elt@D`YZihS7eCs2{T17+xv<;PG4$5RffYo6`q4i;P0>nPNv?ve>arFy-@ais6+-ekAHKFj!y3rnt-REw&Z!s7oh@s8S3@? zIaEb{4VCe0mKUH5Uxsp|jb`^u9hBWUP|q)dN_Yt@>acB~qf%~zD&20_2=_xh@C_)3 z!>|*cgmQEd>TG;y`7zXU|AGpjk%20(5NgjiL%jv}K>6!$r2e`QGocI*+J#4<93O{@ z{BfvEhoKxl4`tvil%rqS`)}C!cOd7`T!yObIKJzu=ya$v(F8T$+C=@^=(IB-M~9#) zavbV)`zBO|Wr!{F3Z%N`O*jXB02TOD($w>_p$s=$E{6)V6Y7)R4Q0OvYJIjyM+OF= zN`KhicpA#lNjv@_l!0GDt^XZVMt_2O?gG>n{R7JICs2oVtW({BX1J7bCsbk&L4Kxq zl4~=apFy48cj0aDMph}qHnE*byP*!< z5jX}u4HJ6*wXeHTh+5GuP&DWDH ztg6mK3y}Pqd%!V7#Y_&ZM+eYRq&-(#fmr$lA+_czOdtFT(!ST4UNd6qUbo!^4ROdwacH?DT|j z%5ftnJ`g%tFWckgBeS-8tKZK@vEv4PPTtFggPs$4yMvw6Lu46+nL(DhnM^I*T3k`Gv}sCjG3Dhw$3=aG9ErJc z%~UNf#}6u@<-GwvioJYtf@9nEjOUWYpdb0Mv)Xl1d9S~1POFWi^@_o0&YCOzT1ffT zt?rtnxo`bY&NDkBFCVyB&+JP1y(uRQD2+Cdg7k*@yw@8OM1Po9UBbYj$2~u?8MT*Q zo6YQwf*z{_N* zzaM$A^&R_OUl^AgX6{dvpKO?v=)7tXUaJ9W`>Clr==yP7NIKna#*ZVf#mV}lL6ItU zV<+#%X;;y@L9%>m*7p;odmA4uz2DeUn%k5t?`k@p*c2DSxZo5b&%a{bC(bRdpHBPM zUAl5y(=l6Fvsl1MfaOBqc~R^qOP6OimM+ciD{ih-Fz=-+2f~j?o2Atgly==X_Nyu2 z$#c&c{eq0zJH4Q{63+Ve9sl*{?1VjJVQ4g4(Q0y`c> WuA-`jqKf%xk7H0RetYS#GyUHK3xB5o diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po index 5c39c26..ae4d24a 100644 --- a/locale/fi/LC_MESSAGES/django.po +++ b/locale/fi/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 18:22+0300\n" +"POT-Creation-Date: 2017-09-25 13:38+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,35 +18,35 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: infoscreen/models.py:77 +#: infoscreen/models.py:97 msgid "ABB jobs" msgstr "ABB-työpaikat" -#: infoscreen/models.py:88 +#: infoscreen/models.py:112 msgid "APY Item" msgstr "ÄPY-tilastot" -#: infoscreen/models.py:99 +#: infoscreen/models.py:127 msgid "External website" msgstr "Ulkoinen verkkosivu" -#: infoscreen/models.py:147 +#: infoscreen/models.py:184 msgid "Sössö articles" msgstr "Sössön artikkelit" -#: infoscreen/models.py:158 +#: infoscreen/models.py:199 msgid "Events" msgstr "Tapahtumat" -#: infoscreen/models.py:169 +#: infoscreen/models.py:214 msgid "Image" msgstr "Kuva" -#: infoscreen/models.py:204 +#: infoscreen/models.py:260 msgid "HSL timetables" msgstr "HSL-aikataulut" -#: infoscreen/models.py:215 +#: infoscreen/models.py:275 msgid "External image" msgstr "Ulkoinen kuva" @@ -168,7 +168,8 @@ msgstr "Valitse muokattava rotaatio" msgid "id" msgstr "id" -#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:46 +#: infoscreen/templates/infoscreen_admin.html:141 webapp/models.py:60 +#: webapp/models.py:94 webapp/models.py:107 msgid "Name" msgstr "Nimi" @@ -182,14 +183,12 @@ msgid "Language" msgstr "Kieli" #: infoscreen/templates/infoscreen_admin.html:161 -#: members/templates/settings.html:20 sikweb/settings-sample.py:179 -#: sikweb/settings.py:178 +#: members/templates/settings.html:20 sikweb/base.py:214 msgid "Finnish" msgstr "suomi" #: infoscreen/templates/infoscreen_admin.html:162 -#: members/templates/settings.html:21 sikweb/settings-sample.py:178 -#: sikweb/settings.py:177 +#: members/templates/settings.html:21 sikweb/base.py:215 msgid "English" msgstr "englanti" @@ -198,67 +197,68 @@ msgstr "englanti" msgid "Submit" msgstr "Lisää" -#: members/forms.py:20 members/tables.py:24 +#: members/forms.py:97 members/tables.py:32 msgid "Member" msgstr "Jäsen" -#: members/models.py:16 +#: members/models.py:14 msgid "First name" msgstr "Etunimi" -#: members/models.py:17 +#: members/models.py:15 msgid "Last name" msgstr "Sukunimi" -#: members/models.py:18 +#: members/models.py:16 webapp/models.py:95 webapp/models.py:108 msgid "Email" msgstr "Sähköposti" -#: members/models.py:19 +#: members/models.py:17 msgid "Place of residence" msgstr "Asuinpaikka" -#: members/models.py:20 members/models.py:83 +#: members/models.py:19 members/models.py:84 +#: members/templates/member_add_many.html:35 msgid "AYY" msgstr "AYY" -#: members/models.py:21 +#: members/models.py:20 msgid "JAS" msgstr "JAS" -#: members/models.py:69 +#: members/models.py:70 msgid "Submitted" msgstr "Lisätty" -#: members/models.py:81 +#: members/models.py:82 msgid "Date" msgstr "Päivämäärä" -#: members/models.py:82 +#: members/models.py:83 msgid "Source" msgstr "Lähde" -#: members/models.py:84 +#: members/models.py:85 msgid "Cash" msgstr "Käteinen" -#: members/models.py:85 +#: members/models.py:86 members/templates/member_add_many.html:36 msgid "Bank transfer" msgstr "Tilisiirto" -#: members/models.py:102 +#: members/models.py:103 msgid "Created" msgstr "Lisätty" -#: members/tables.py:9 +#: members/tables.py:13 msgid "Last paid" msgstr "Viimeksi maksettu" -#: members/tables.py:13 members/tables.py:28 members/tables.py:41 +#: members/tables.py:18 members/tables.py:37 members/tables.py:54 msgid "Edit" msgstr "Muokkaa" -#: members/tables.py:15 members/tables.py:30 members/tables.py:43 +#: members/tables.py:20 members/tables.py:39 members/tables.py:56 msgid "Options" msgstr "Asetukset" @@ -295,6 +295,10 @@ msgstr "Lisää jäsen" msgid "Save" msgstr "Tallenna" +#: members/templates/member_add_many.html:8 +msgid "Add many members" +msgstr "Lisää useita" + #: members/templates/member_add_many.html:13 msgid "" "\n" @@ -326,10 +330,37 @@ msgstr "" msgid "Syntax" msgstr "Syntaksi" -#: members/templates/member_add_many.html:32 +#: members/templates/member_add_many.html:29 +msgid "Data" +msgstr "Data" + +#: members/templates/member_add_many.html:33 +msgid "Payment source" +msgstr "Maksutapa" + +#: members/templates/member_add_many.html:37 +msgid "Cash payment" +msgstr "Käteismaksu" + +#: members/templates/member_add_many.html:41 +#: members/templates/member_add_many_confirm.html:22 msgid "Send" msgstr "Lähetä" +#: members/templates/member_add_many_confirm.html:8 +msgid "Confirm adding these entries?" +msgstr "Vahvista muutokset?" + +#: members/templates/member_add_many_confirm.html:12 +#: members/templates/members_base.html:52 webapp/templates/main_index.html:7 +msgid "Members" +msgstr "Jäsenet" + +#: members/templates/member_add_many_confirm.html:16 +#: members/templates/members_base.html:60 +msgid "Payments" +msgstr "Maksutapahtumat" + #: members/templates/member_delete_confirm.html:9 msgid "Are you sure you want to delete this member?" msgstr "Oletko varma, että haluat poistaa tämän jäsenen?" @@ -339,32 +370,6 @@ msgstr "Oletko varma, että haluat poistaa tämän jäsenen?" msgid "Yes, I'm sure" msgstr "Kyllä, olen varma" -#: members/templates/member_duplicates.html:9 -msgid "Conflicting member entries" -msgstr "Ongelmalliset jäsentiedot" - -#: members/templates/member_duplicates.html:13 -msgid "" -"Found conflicting member entries. Choose how to handle the problematic data." -msgstr "" -"Ongelmallista jäsendataa havaittu. Valitse, miten ongelmat ratkaistaan." - -#: members/templates/member_duplicates.html:29 -msgid "Which one has the correct information for this member?" -msgstr "Kummassa on jäsenen oikeat tiedot?" - -#: members/templates/member_duplicates.html:31 -msgid "Accept first and remove second" -msgstr "Hyväksy ensimmäinen ja poista toinen" - -#: members/templates/member_duplicates.html:32 -msgid "Accept second and remove first" -msgstr "Hyväksy toinen ja poista ensimmäinen" - -#: members/templates/member_duplicates.html:33 -msgid "Accept both as two members" -msgstr "Hyväksy molemmat kahtena jäsenenä" - #: members/templates/member_edit.html:9 msgid "Edit member" msgstr "Muokkaa jäsentä" @@ -373,20 +378,11 @@ msgstr "Muokkaa jäsentä" msgid "Member register" msgstr "Jäsenrekisteri" -#: members/templates/member_list.html:16 -msgid "" -"There are duplicate member entries in the register.\n" -" Please visit duplicate resolver." -msgstr "" -"Jäsenrekisterissä on duplikaattijäseniä.\n" -" Käytä ongelman ratkaisuun duplikaattityökalua." - -#: members/templates/member_list.html:28 +#: members/templates/member_list.html:21 msgid "Members in register:" msgstr "Jäseniä:" -#: members/templates/member_list.html:34 +#: members/templates/member_list.html:27 msgid "Download CSV" msgstr "Lataa CSV" @@ -395,10 +391,6 @@ msgstr "Lataa CSV" msgid "Member register of SIK ry" msgstr "Aalto-yliopiston Sähköinsinöörikilta ry:n jäsenrekisteri" -#: members/templates/members_base.html:52 webapp/templates/main_index.html:7 -msgid "Members" -msgstr "Jäsenet" - #: members/templates/members_base.html:54 msgid "List members" msgstr "Jäsenlistaus" @@ -407,10 +399,6 @@ msgstr "Jäsenlistaus" msgid "Add multiple" msgstr "Lisää useita" -#: members/templates/members_base.html:60 -msgid "Payments" -msgstr "Maksutapahtumat" - #: members/templates/members_base.html:62 msgid "List payments" msgstr "Maksulistaus" @@ -443,115 +431,123 @@ msgstr "Muokkaa maksua" msgid "Payment events" msgstr "Maksutapahtumat" -#: members/views.py:129 members/views.py:186 members/views.py:205 +#: members/views.py:138 members/views.py:209 members/views.py:235 msgid "No member id specified" msgstr "Jäsenen ID ei määritelty" -#: members/views.py:151 +#: members/views.py:164 msgid "Successfully added member" msgstr "Onnistuneesti lisättiin jäsen" -#: members/views.py:172 +#: members/views.py:189 msgid "Successfully updated member" msgstr "Onnistuneesti päivitettiin jäsen" -#: members/views.py:176 +#: members/views.py:197 msgid "Could not update member object" msgstr "Jäsenobjektia ei voitu päivittää" -#: members/views.py:190 +#: members/views.py:213 msgid "Successfully deleted member" msgstr "Onnistuneesti poistettiin jäsen" -#: members/views.py:196 +#: members/views.py:224 msgid "Could not delete member object" msgstr "Jäsenobjektia ei voitu poistaa" -#: members/views.py:239 members/views.py:273 members/views.py:291 +#: members/views.py:273 members/views.py:320 members/views.py:348 msgid "No application id specified" msgstr "Hakemuksen ID ei määritelty" -#: members/views.py:260 +#: members/views.py:301 msgid "Successfully accepted application" msgstr "Onnistuneesti hyväksyttiin hakemus" -#: members/views.py:263 +#: members/views.py:308 msgid "Could not accept application object" msgstr "Hakemusobjektia ei voitu hyväksyä" -#: members/views.py:277 +#: members/views.py:324 msgid "Successfully deleted application" msgstr "Onnistuneesti poistettiin hakemus" -#: members/views.py:282 +#: members/views.py:336 msgid "Could not delete application object" msgstr "Hakemusobjektia ei voitu poistaa" -#: members/views.py:346 +#: members/views.py:413 msgid "Successfully added payment for member" msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle" -#: members/views.py:359 members/views.py:372 members/views.py:386 +#: members/views.py:431 members/views.py:449 members/views.py:468 msgid "No payment id specified" msgstr "Maksutapahtuman ID ei määritelty" -#: members/views.py:390 +#: members/views.py:473 msgid "Successfully deleted payment" msgstr "Onnistuneesti poistettiin maksutapahtuma" -#: members/views.py:395 +#: members/views.py:483 msgid "Could not delete payment object" msgstr "Maksutapahtumaobjektia ei voitu poistaa" -#: members/views.py:410 +#: members/views.py:502 msgid "Successfully updated payment" msgstr "Onnistuneesti päivitettiin maksutapahtuma" -#: members/views.py:413 +#: members/views.py:509 msgid "Could not update payment object" msgstr "Maksutapahtumaobjektia ei voitu päivittää" -#: members/views.py:430 +#: members/views.py:531 msgid "Missing \"textfield\" POST request field" msgstr "Puuttuva \"textfield\" POST-kenttä" -#: members/views.py:435 -msgid "Successfully imported multiple members" -msgstr "Onnistuneesti tuotu useita jäseniä" - -#: members/views.py:438 +#: members/views.py:583 msgid "Failed to import members" msgstr "Jäsenten tuonti epäonnistui" -#: members/views.py:504 -msgid "Successfully resolved all member conflicts." -msgstr "Kaikki jäsenkonfliktit ratkaistu onnistuneesti." - #: templates/footer.html:7 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" -#: webapp/models.py:47 +#: webapp/models.py:61 msgid "Board member" msgstr "Hallituksen jäsen" -#: webapp/models.py:54 +#: webapp/models.py:67 msgid "Description" msgstr "Kuvaus" -#: webapp/models.py:55 +#: webapp/models.py:68 msgid "Summary" msgstr "Tiivistelmä" -#: webapp/models.py:70 +#: webapp/models.py:96 +msgid "Message" +msgstr "Viesti" + +#: webapp/models.py:109 +msgid "Year" +msgstr "Vuosi" + +#: webapp/models.py:123 +msgid "Role" +msgstr "Rooli" + +#: webapp/models.py:125 msgid "Start date" msgstr "Alkupäivämäärä" -#: webapp/models.py:71 +#: webapp/models.py:126 msgid "End date" msgstr "Loppupäivämäärä" -#: webapp/models.py:79 +#: webapp/models.py:136 +msgid "Official" +msgstr "Toimihenkilö" + +#: webapp/models.py:138 msgid "Phone number" msgstr "Puhelinnumero" @@ -598,3 +594,19 @@ msgstr "Sössö" #: webapp/templates/navigation.html:32 msgid "Contact" msgstr "Yhteystiedot" + +#: webapp/templates/ohlhafv.html:8 +msgid "Ohlhafv" +msgstr "Øhlhäfv" + +#: webapp/templates/ohlhafv.html:15 +msgid "Challenge" +msgstr "Haaste" + +#: webapp/templates/ohlhafv_list.html:11 +msgid "All challenges" +msgstr "Kaikki haasteet" + +#: webapp/templates/ohlhafv_list.html:15 +msgid "Total challenges:" +msgstr "Haasteita yhteensä:" diff --git a/members/templates/member_list.html b/members/templates/member_list.html index 7aa6d71..9a665fc 100644 --- a/members/templates/member_list.html +++ b/members/templates/member_list.html @@ -11,13 +11,6 @@

{% trans "Member register" %}

- {% if is_member_conflict %} -
- {% blocktrans %}There are duplicate member entries in the register. - Please visit duplicate resolver.{% endblocktrans %} -
- {% endif %} - {% if notification %}
{{ notification }} From 6954cc7f1004281166873b9c18b333b6b0623655 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 19:18:23 +0300 Subject: [PATCH 2/8] Refactor members views --- members/admin.py | 3 +- members/migrations/0015_auto_20170925_1917.py | 26 + members/models.py | 19 - members/views.py | 593 ------------------ members/views/__init__.py | 4 + members/views/applications.py | 138 ++++ members/views/members.py | 182 ++++++ members/views/payments.py | 156 +++++ members/views/utils.py | 167 +++++ 9 files changed, 674 insertions(+), 614 deletions(-) create mode 100644 members/migrations/0015_auto_20170925_1917.py create mode 100644 members/views/__init__.py create mode 100644 members/views/applications.py create mode 100644 members/views/members.py create mode 100644 members/views/payments.py create mode 100644 members/views/utils.py diff --git a/members/admin.py b/members/admin.py index 6607a19..8124496 100644 --- a/members/admin.py +++ b/members/admin.py @@ -1,12 +1,11 @@ """Admin site registers for Members app.""" from django.contrib import admin -from members.models import Member, Request, Payment, MemberConflict +from members.models import Member, Request, Payment # Register your models here. admin.site.register(Member) admin.site.register(Request) admin.site.register(Payment) -admin.site.register(MemberConflict) admin.site.site_header = 'SIK Admin' diff --git a/members/migrations/0015_auto_20170925_1917.py b/members/migrations/0015_auto_20170925_1917.py new file mode 100644 index 0000000..3313ac2 --- /dev/null +++ b/members/migrations/0015_auto_20170925_1917.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-09-25 16:17 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0014_auto_20170920_1457'), + ] + + operations = [ + migrations.RemoveField( + model_name='memberconflict', + name='first_member', + ), + migrations.RemoveField( + model_name='memberconflict', + name='second_member', + ), + migrations.DeleteModel( + name='MemberConflict', + ), + ] diff --git a/members/models.py b/members/models.py index cbcb9d4..1a78afe 100644 --- a/members/models.py +++ b/members/models.py @@ -128,24 +128,5 @@ class Member(BaseMember): ) -class MemberConflict(models.Model): - """Model representing member conflict situation.""" - - first_member = models.ForeignKey( - 'Member', related_name='%(class)s_first_member') - second_member = models.ForeignKey( - 'Member', related_name='%(class)s_second_member') - - @property - def first_member_form(self): - """Get first member form.""" - return MemberForm(instance=self.first_member) - - @property - def second_member_form(self): - """Get second member form.""" - return MemberForm(instance=self.second_member) - - # To avoid problems with a cyclical import, this is at the bottom of the file from members.forms import MemberForm # nopep8 diff --git a/members/views.py b/members/views.py index 7d3f33c..24e5dc0 100644 --- a/members/views.py +++ b/members/views.py @@ -17,12 +17,6 @@ from django.utils.http import urlsafe_base64_encode from django.utils.encoding import force_bytes from django.core.mail import send_mail -# REST framework -from members.serializers import MemberSerializer -from rest_framework import generics -from rest_framework import permissions -from rest_framework.throttling import UserRateThrottle, AnonRateThrottle - import json import requests import logging @@ -36,583 +30,6 @@ from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidatio from members.tables import MemberTable, PaymentTable, RequestTable -def error_view(request, message): - return render(request, 'error.html', {'error': str(message)}) - - -def validate_recaptcha(response): - """ - Recaptcha is used in member applications. - - :param response: - :return: Boolean, success or not - """ - values = { - 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, - 'response': response, - } - url = "https://www.google.com/recaptcha/api/siteverify" - headers = {'Content-type': 'application/x-www-form-urlencoded'} - resp = requests.post(url, values, headers=headers) - try: - result = json.loads(resp.text) - logging.info('Recaptcha response: {}'.format(result)) - return result["success"] - except: - return False - - -def send_mail_wrapper(subject, message): - """Call send_mail function.""" - send_mail(subject, - message, - 'no-reply@sahkoinsinoorikilta.fi', - ['viestintamestari@sahkoinsinoorikilta.fi'], - fail_silently=False) - - -def convert_table_to_html(table, request): - """ - Convert table to html. - - This is a horrible hack for converting a table object to raw html. - Even with extensive research I wasn't able to find a way to add a path - prefix "e.g. /members/list" to the query strings "e.g. ?sort=foo", so I - did it manually with string.replace. - - Note: When adding the html to a page, you need to run it through - the "safe" filter. E.g. "{{ table|safe }}" - - :param table: Table object from members.tables - :param request: HttpRequest - :return: Raw html string - """ - table_as_html = table.as_html(request) - path = request.path - - fixed = table_as_html.replace(r'href="?', r'href="{}?'.format(path)) - return fixed - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def member_list(request, *args, **kwargs): - """Render members list.""" - members = Member.objects.all() - - table = MemberTable(members, - request=request, - exclude=['id'], - attrs={'class': 'table table-bordered table-hover'}) - - table.paginate(page=request.GET.get('page', 1), per_page=25) - table_html = convert_table_to_html(table, request) - - context = { - 'table': table_html, - 'member_count': len(members), - 'notification': request.GET.get('notification', None), - 'is_member_conflict': MemberConflict.objects.exists() - } - return render(request, 'member_list.html', context) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def member_add(request, *args, **kwargs): - """Render add member page.""" - form = MemberForm() - return render(request, 'member_add.html', {'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def member_delete_confirm(request, *args, **kwargs): - """Render member deletion confirmation page.""" - i = kwargs.pop('index', None) - if i is None: - return render(request, 'error.html', - {'error': _('No member id specified')}) - else: - member = Member.objects.get(id=i) - form = MemberForm(instance=member) - return render(request, 'member_delete_confirm.html', - {'member_id': i, 'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def member_add_many(request, *args, **kwargs): - """Render add multiple members page.""" - return render(request, 'member_add_many.html', {}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def member_submit(request, *args, **kwargs): - """Add member based on data gained from member form.""" - form = MemberForm(request.POST) - if form.is_valid(): - form.save() - logging.info("Saved new member to member register" - "with the following info: {}".format(form)) - notification = "{} {} {}.".format(_("Successfully added member"), - form.cleaned_data['last_name'], - form.cleaned_data['first_name']) - - return HttpResponseRedirect( - '/members/list?notification={}'.format(html.escape(notification))) - else: - return render(request, 'error.html', {'error': form.errors}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def member_update(request, *args, **kwargs): - """Update member information.""" - form = MemberForm(request.POST) - if form.is_valid(): - id = request.POST['id'] - member = Member.objects.get(id=id) - form = MemberForm(request.POST, instance=member) - form.save() - - logging.info( - "Updated member in member register with the following info: {}" - .format(form)) - notification = "{} {} {}.".format(_("Successfully updated member"), - member.last_name, member.first_name) - return HttpResponseRedirect( - '/members/list?notification={}'.format(html.escape(notification))) - else: - return render( - request, - 'error.html', - {'error': _('Could not update member object')}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def member_delete(request, *args, **kwargs): - """Delete member.""" - try: - id = request.POST['id'] - except KeyError: - return render(request, - 'error.html', {'error': _('No member id specified')}) - - try: - member = Member.objects.get(id=id) - notification = "{} {} {}.".format(_("Successfully deleted member"), - member.last_name, member.first_name) - member.delete() - logging.info( - "Delete member in member register with the following id: {}" - .format(id)) - return HttpResponseRedirect( - '/members/list?notification={}'.format(html.escape(notification))) - except: - return render(request, - 'error.html', - {'error': _('Could not delete member object')}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def member_edit(request, *args, **kwargs): - """Edit member information.""" - i = kwargs.pop('index', None) - if i is None: - return render( - request, 'error.html', {'error': _('No member id specified')}) - else: - member = Member.objects.get(id=i) - form = MemberForm(instance=member) - return render( - request, 'member_edit.html', {'member_id': i, 'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def application_list(request, *args, **kwargs): - """List member applications not yet processed.""" - applications = Request.objects.all() - application_count = len(applications) - table = RequestTable(applications, - request=request, - exclude=['id'], - attrs={'class': 'table table-bordered table-hover'}) - - table.paginate(page=request.GET.get('page', 1), per_page=25) - table_html = convert_table_to_html(table, request) - context = { - 'table': table_html, - 'application_count': application_count, - 'notification': request.GET.get('notification', None) - } - return render(request, 'application_list.html', context) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def application_edit(request, *args, **kwargs): - """Edit member request information.""" - i = kwargs.pop('index', None) - if i is None: - return render( - request, 'error.html', {'error': _('No application id specified')}) - else: - application = Request.objects.get(id=i) - form = ApplicationForm(instance=application) - return render( - request, - 'application_edit.html', - {'application_id': i, 'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def application_accept(request, *args, **kwargs): - """Accept application.""" - form = ApplicationForm(request.POST) - if form.is_valid(): - id = request.POST['id'] - application = Request.objects.get(id=id) - - member = application.to_member() - member.save() - application.delete() - - logging.info( - "Accepted application in member " - "register with the following info: {}" - .format(form)) - notification = "{} {}.".format(_("Successfully accepted application"), - str(application)) - return HttpResponseRedirect( - '/members/list?notification={}'.format(html.escape(notification))) - else: - return render(request, - 'error.html', - {'error': _('Could not accept application object')}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def application_delete(request, *args, **kwargs): - """Delete member application.""" - try: - id = request.POST['id'] - except KeyError: - return render( - request, 'error.html', {'error': _('No application id specified')}) - - try: - application = Request.objects.get(id=id) - notification = "{} {}.".format(_("Successfully deleted application"), - str(application)) - application.delete() - logging.info( - "Delete application in member register with the following id: {}" - .format(id)) - return HttpResponseRedirect( - '/members/applications?notification={}' - .format(html.escape(notification))) - except: - return render(request, - 'error.html', - {'error': _('Could not delete application object')}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def application_delete_confirm(request, *args, **kwargs): - """Confirm application deletion.""" - i = kwargs.pop('index', None) - if i is None: - return render(request, - 'error.html', - {'error': _('No application id specified')}) - else: - application = Request.objects.get(id=i) - form = ApplicationForm(instance=application) - return render(request, - 'application_delete_confirm.html', - {'application_id': i, 'form': form}) - - -@ensure_csrf_cookie -def application_form(request, *args, **kwargs): - """Render member application form.""" - return render(request, 'application_index.html', {}) - - -@ensure_csrf_cookie -def application_form_success(request, *args, **kwargs): - """Render application Successfully sent page.""" - return render(request, 'application_success.html', {}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def payment_list(request, *args, **kwargs): - """Render list of payments.""" - payments = Payment.objects.all() - - table = PaymentTable(payments, - request=request, - exclude=['id'], - attrs={'class': 'table table-bordered table-hover'}) - - table.paginate(page=request.GET.get('page', 1), per_page=25) - table_html = convert_table_to_html(table, request) - - context = { - 'table': table_html, - 'payment_count': len(payments), - 'notification': request.GET.get('notification', None) - } - return render(request, 'payment_list.html', context) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def payment_add(request, *args, **kwargs): - """Render add payment form.""" - form = PaymentForm() - return render(request, 'payment_add.html', {'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def payment_submit(request, *args, **kwargs): - """Submit payment.""" - form = PaymentForm(request.POST) - if form.is_valid(): - form.save() - logging.info( - "Saved new payment to member register with the following info: {}" - .format(form)) - notification = "{} {}.".format( - _("Successfully added payment for member"), - form.cleaned_data['member']) - return HttpResponseRedirect( - '/members/payments?notification={}' - .format(html.escape(notification))) - else: - return render(request, 'error.html', {'error': form.errors}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def payment_edit(request, *args, **kwargs): - """Edit payment.""" - i = kwargs.pop('index', None) - if i is None: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) - else: - payment = Payment.objects.get(id=i) - form = PaymentForm(instance=payment) - return render(request, - 'payment_edit.html', - {'payment_id': i, 'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def payment_delete_confirm(request, *args, **kwargs): - """Render payment delete confirmation page.""" - i = kwargs.pop('index', None) - if i is None: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) - else: - payment = Payment.objects.get(id=i) - form = PaymentForm(instance=payment) - return render(request, - 'payment_delete_confirm.html', - {'payment_id': i, 'form': form}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def payment_delete(request, *args, **kwargs): - """Delete payment.""" - try: - id = request.POST['id'] - except KeyError: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) - - try: - payment = Payment.objects.get(id=id) - notification = "{} {}.".format( - _("Successfully deleted payment"), str(payment)) - payment.delete() - logging.info( - "Delete payment '{}' in member register".format(str(payment))) - return HttpResponseRedirect( - '/members/payments?notification={}' - .format(html.escape(notification))) - except: - return render(request, - 'error.html', - {'error': _('Could not delete payment object')}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def payment_update(request, *args, **kwargs): - """Update payment information.""" - form = PaymentForm(request.POST) - if form.is_valid(): - id = request.POST['id'] - payment = Payment.objects.get(id=id) - form = PaymentForm(request.POST, instance=payment) - form.save() - - logging.info( - "Updated member in member register with the following info: {}" - .format(form)) - notification = "{} {}.".format( - _("Successfully updated payment"), str(payment)) - return HttpResponseRedirect( - '/members/payments?notification={}' - .format(html.escape(notification))) - else: - return render(request, - 'error.html', - {'error': _('Could not update payment object')}) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def settings_page(request, *args, **kwargs): - """Render member app settings page.""" - return render(request, 'settings.html', {}) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def import_csv(request, *args, **kwargs): - """Get csv data imported to page and create members based on that.""" - try: - data = request.POST['textfield'] - payment_source = request.POST['payment_source'] - except: - return render(request, - 'error.html', - {'error': _('Missing "textfield" POST request field')}) - - try: - result = MemberForm.csv_to_models(data, payment_source=payment_source) - except CSVValidationError as ex: - logging.exception('Model validation error') - return error_view(request, ex.form_errors) - except Exception as ex: - logging.exception('Other error in CSV import') - return error_view(request, ex) - - member_table = MemberTable(result.members, - request=request, - exclude=['id', 'options'], - attrs={'class': 'table table-bordered table-hover'}) - - member_table_html = convert_table_to_html(member_table, request) - - payment_table = PaymentTable(result.payments, - request=request, - exclude=['id', 'options'], - attrs={'class': 'table table-bordered table-hover'}) - - payment_table_html = convert_table_to_html(payment_table, request) - - request.session['models'] = result - context = { - 'members': member_table_html, - 'payments': payment_table_html - } - return render(request, 'member_add_many_confirm.html', context) - - -@ensure_csrf_cookie -@require_http_methods(["POST"]) -@permission_required('members.change_member', login_url='/login') -def add_many_confirm(request, *args, **kwargs): - models = request.session['models'] - - try: - members, payments = models.members, models.payments - for member in members: - member.save() - - for payment in payments: - payment.save() - - msg = "Successfully imported {} members and {} payments." - notification = _(msg).format(len(members), len(payments)) - return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) - except Exception as ex: - logging.exception('Failed to save models after "add many."') - return error_view(request, _('Failed to import members')) - - -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.change_member', login_url='/login') -def export_csv(request, *args, **kwargs): - """Export members as csv.""" - response = HttpResponse() - response['Content-type'] = 'text/csv' - response['Accept'] = 'text/csv' - response['Content-Disposition'] = 'filename; filename=members.csv' - writer = csv.writer(response, csv.excel) - # BOM (optional...Excel needs it to open UTF-8 file properly) - response.write(u'\ufeff'.encode('utf8')) - for obj in Member.objects.all(): - data = obj.as_array() - field_list = map(lambda d: str(d), data) - - writer.writerow(field_list) - - return response - - -def send_mail_wrapper(subject, message, email_to): - """Send mail to default email.""" - send_mail(subject, - message, - settings.DEFAULT_EMAIL_FROM, - [email_to], - fail_silently=False) - - @receiver(post_save, sender=Request) def email_on_request(sender, instance, created, **kwargs): """Send email validation.""" @@ -641,13 +58,3 @@ def email_on_accept(sender, instance, created, **kwargs): send_mail_wrapper(subject, message, instance.email) except SMTPAuthenticationError: logging.error('Failed to send email to accepted member!') - - -# Can be used to retrieve single member information via REST API -class MemberDetail(generics.RetrieveAPIView): - """Member detail rest API view.""" - - queryset = Member.objects.all() - serializer_class = MemberSerializer - permission_classes = (permissions.IsAdminUser, ) - throttle_classes = (UserRateThrottle, AnonRateThrottle, ) diff --git a/members/views/__init__.py b/members/views/__init__.py new file mode 100644 index 0000000..a6e2691 --- /dev/null +++ b/members/views/__init__.py @@ -0,0 +1,4 @@ +from members.views.members import * +from members.views.applications import * +from members.views.payments import * +from members.views.utils import * diff --git a/members/views/applications.py b/members/views/applications.py new file mode 100644 index 0000000..030d0eb --- /dev/null +++ b/members/views/applications.py @@ -0,0 +1,138 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import permission_required +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import HttpResponse, HttpResponseRedirect +from django.core.mail import send_mail +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.forms.models import model_to_dict + +from members.views.utils import * +from members.tables import RequestTable +from members.forms import ApplicationForm + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def application_list(request, *args, **kwargs): + """List member applications not yet processed.""" + applications = Request.objects.all() + application_count = len(applications) + table = RequestTable(applications, + request=request, + exclude=['id'], + attrs={'class': 'table table-bordered table-hover'}) + + table.paginate(page=request.GET.get('page', 1), per_page=25) + table_html = convert_table_to_html(table, request) + context = { + 'table': table_html, + 'application_count': application_count, + 'notification': request.GET.get('notification', None) + } + return render(request, 'application_list.html', context) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def application_edit(request, *args, **kwargs): + """Edit member request information.""" + i = kwargs.pop('index', None) + if i is None: + return render( + request, 'error.html', {'error': _('No application id specified')}) + else: + application = Request.objects.get(id=i) + form = ApplicationForm(instance=application) + return render( + request, + 'application_edit.html', + {'application_id': i, 'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def application_accept(request, *args, **kwargs): + """Accept application.""" + form = ApplicationForm(request.POST) + if form.is_valid(): + id = request.POST['id'] + application = Request.objects.get(id=id) + + member = application.to_member() + member.save() + application.delete() + + logging.info( + "Accepted application in member " + "register with the following info: {}" + .format(form)) + notification = "{} {}.".format(_("Successfully accepted application"), + str(application)) + return HttpResponseRedirect( + '/members/list?notification={}'.format(html.escape(notification))) + else: + return render(request, + 'error.html', + {'error': _('Could not accept application object')}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def application_delete(request, *args, **kwargs): + """Delete member application.""" + try: + id = request.POST['id'] + except KeyError: + return render( + request, 'error.html', {'error': _('No application id specified')}) + + try: + application = Request.objects.get(id=id) + notification = "{} {}.".format(_("Successfully deleted application"), + str(application)) + application.delete() + logging.info( + "Delete application in member register with the following id: {}" + .format(id)) + return HttpResponseRedirect( + '/members/applications?notification={}' + .format(html.escape(notification))) + except: + return render(request, + 'error.html', + {'error': _('Could not delete application object')}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def application_delete_confirm(request, *args, **kwargs): + """Confirm application deletion.""" + i = kwargs.pop('index', None) + if i is None: + return render(request, + 'error.html', + {'error': _('No application id specified')}) + else: + application = Request.objects.get(id=i) + form = ApplicationForm(instance=application) + return render(request, + 'application_delete_confirm.html', + {'application_id': i, 'form': form}) + + +@ensure_csrf_cookie +def application_form(request, *args, **kwargs): + """Render member application form.""" + return render(request, 'application_index.html', {}) + + +@ensure_csrf_cookie +def application_form_success(request, *args, **kwargs): + """Render application Successfully sent page.""" + return render(request, 'application_success.html', {}) diff --git a/members/views/members.py b/members/views/members.py new file mode 100644 index 0000000..d65a5eb --- /dev/null +++ b/members/views/members.py @@ -0,0 +1,182 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import permission_required +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import HttpResponse, HttpResponseRedirect +from django.core.mail import send_mail +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.forms.models import model_to_dict + +from members.models import Member, Request, Payment +from members.forms import MemberForm, CSVValidationError +from members.tables import MemberTable + +from members.views.utils import * + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def member_list(request, *args, **kwargs): + """Render members list.""" + members = Member.objects.all() + + table = MemberTable(members, + request=request, + exclude=['id'], + attrs={'class': 'table table-bordered table-hover'}) + + table.paginate(page=request.GET.get('page', 1), per_page=25) + table_html = convert_table_to_html(table, request) + + context = { + 'table': table_html, + 'member_count': len(members), + 'notification': request.GET.get('notification', None), + } + return render(request, 'member_list.html', context) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def member_add(request, *args, **kwargs): + """Render add member page.""" + form = MemberForm() + return render(request, 'member_add.html', {'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def member_delete_confirm(request, *args, **kwargs): + """Render member deletion confirmation page.""" + i = kwargs.pop('index', None) + if i is None: + return render(request, 'error.html', + {'error': _('No member id specified')}) + else: + member = Member.objects.get(id=i) + form = MemberForm(instance=member) + return render(request, 'member_delete_confirm.html', + {'member_id': i, 'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def member_add_many(request, *args, **kwargs): + """Render add multiple members page.""" + return render(request, 'member_add_many.html', {}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def add_many_confirm(request, *args, **kwargs): + models = request.session['models'] + + try: + members, payments = models.members, models.payments + for member in members: + member.save() + + for payment in payments: + payment.save() + + msg = "Successfully imported {} members and {} payments." + notification = _(msg).format(len(members), len(payments)) + return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification))) + except Exception as ex: + logging.exception('Failed to save models after "add many."') + return error_view(request, _('Failed to import members')) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def member_submit(request, *args, **kwargs): + """Add member based on data gained from member form.""" + form = MemberForm(request.POST) + if form.is_valid(): + form.save() + logging.info("Saved new member to member register" + "with the following info: {}".format(form)) + notification = "{} {} {}.".format(_("Successfully added member"), + form.cleaned_data['last_name'], + form.cleaned_data['first_name']) + + return HttpResponseRedirect( + '/members/list?notification={}'.format(html.escape(notification))) + else: + return render(request, 'error.html', {'error': form.errors}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def member_update(request, *args, **kwargs): + """Update member information.""" + form = MemberForm(request.POST) + if form.is_valid(): + id = request.POST['id'] + member = Member.objects.get(id=id) + form = MemberForm(request.POST, instance=member) + form.save() + + logging.info( + "Updated member in member register with the following info: {}" + .format(form)) + notification = "{} {} {}.".format(_("Successfully updated member"), + member.last_name, member.first_name) + return HttpResponseRedirect( + '/members/list?notification={}'.format(html.escape(notification))) + else: + return render( + request, + 'error.html', + {'error': _('Could not update member object')}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def member_delete(request, *args, **kwargs): + """Delete member.""" + try: + id = request.POST['id'] + except KeyError: + return render(request, + 'error.html', {'error': _('No member id specified')}) + + try: + member = Member.objects.get(id=id) + notification = "{} {} {}.".format(_("Successfully deleted member"), + member.last_name, member.first_name) + member.delete() + logging.info( + "Delete member in member register with the following id: {}" + .format(id)) + return HttpResponseRedirect( + '/members/list?notification={}'.format(html.escape(notification))) + except: + return render(request, + 'error.html', + {'error': _('Could not delete member object')}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def member_edit(request, *args, **kwargs): + """Edit member information.""" + i = kwargs.pop('index', None) + if i is None: + return render( + request, 'error.html', {'error': _('No member id specified')}) + else: + member = Member.objects.get(id=i) + form = MemberForm(instance=member) + return render( + request, 'member_edit.html', {'member_id': i, 'form': form}) diff --git a/members/views/payments.py b/members/views/payments.py new file mode 100644 index 0000000..9a0d0e0 --- /dev/null +++ b/members/views/payments.py @@ -0,0 +1,156 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import permission_required +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import HttpResponse, HttpResponseRedirect +from django.core.mail import send_mail +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.forms.models import model_to_dict + +from members.views.utils import * +from members.tables import PaymentTable +from members.forms import PaymentForm + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def payment_list(request, *args, **kwargs): + """Render list of payments.""" + payments = Payment.objects.all() + + table = PaymentTable(payments, + request=request, + exclude=['id'], + attrs={'class': 'table table-bordered table-hover'}) + + table.paginate(page=request.GET.get('page', 1), per_page=25) + table_html = convert_table_to_html(table, request) + + context = { + 'table': table_html, + 'payment_count': len(payments), + 'notification': request.GET.get('notification', None) + } + return render(request, 'payment_list.html', context) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def payment_add(request, *args, **kwargs): + """Render add payment form.""" + form = PaymentForm() + return render(request, 'payment_add.html', {'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def payment_submit(request, *args, **kwargs): + """Submit payment.""" + form = PaymentForm(request.POST) + if form.is_valid(): + form.save() + logging.info( + "Saved new payment to member register with the following info: {}" + .format(form)) + notification = "{} {}.".format( + _("Successfully added payment for member"), + form.cleaned_data['member']) + return HttpResponseRedirect( + '/members/payments?notification={}' + .format(html.escape(notification))) + else: + return render(request, 'error.html', {'error': form.errors}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def payment_edit(request, *args, **kwargs): + """Edit payment.""" + i = kwargs.pop('index', None) + if i is None: + return render(request, + 'error.html', + {'error': _('No payment id specified')}) + else: + payment = Payment.objects.get(id=i) + form = PaymentForm(instance=payment) + return render(request, + 'payment_edit.html', + {'payment_id': i, 'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def payment_delete_confirm(request, *args, **kwargs): + """Render payment delete confirmation page.""" + i = kwargs.pop('index', None) + if i is None: + return render(request, + 'error.html', + {'error': _('No payment id specified')}) + else: + payment = Payment.objects.get(id=i) + form = PaymentForm(instance=payment) + return render(request, + 'payment_delete_confirm.html', + {'payment_id': i, 'form': form}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def payment_delete(request, *args, **kwargs): + """Delete payment.""" + try: + id = request.POST['id'] + except KeyError: + return render(request, + 'error.html', + {'error': _('No payment id specified')}) + + try: + payment = Payment.objects.get(id=id) + notification = "{} {}.".format( + _("Successfully deleted payment"), str(payment)) + payment.delete() + logging.info( + "Delete payment '{}' in member register".format(str(payment))) + return HttpResponseRedirect( + '/members/payments?notification={}' + .format(html.escape(notification))) + except: + return render(request, + 'error.html', + {'error': _('Could not delete payment object')}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def payment_update(request, *args, **kwargs): + """Update payment information.""" + form = PaymentForm(request.POST) + if form.is_valid(): + id = request.POST['id'] + payment = Payment.objects.get(id=id) + form = PaymentForm(request.POST, instance=payment) + form.save() + + logging.info( + "Updated member in member register with the following info: {}" + .format(form)) + notification = "{} {}.".format( + _("Successfully updated payment"), str(payment)) + return HttpResponseRedirect( + '/members/payments?notification={}' + .format(html.escape(notification))) + else: + return render(request, + 'error.html', + {'error': _('Could not update payment object')}) diff --git a/members/views/utils.py b/members/views/utils.py new file mode 100644 index 0000000..35b7b84 --- /dev/null +++ b/members/views/utils.py @@ -0,0 +1,167 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import permission_required +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import HttpResponse, HttpResponseRedirect +from django.core.mail import send_mail +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.forms.models import model_to_dict + +# REST framework +from members.serializers import MemberSerializer +from rest_framework import generics +from rest_framework import permissions +from rest_framework.throttling import UserRateThrottle, AnonRateThrottle + +from members.models import Member, Request, Payment + + +# Can be used to retrieve single member information via REST API +class MemberDetail(generics.RetrieveAPIView): + """Member detail rest API view.""" + + queryset = Member.objects.all() + serializer_class = MemberSerializer + permission_classes = (permissions.IsAdminUser, ) + throttle_classes = (UserRateThrottle, AnonRateThrottle, ) + + +def error_view(request, message): + return render(request, 'error.html', {'error': str(message)}) + + +def validate_recaptcha(response): + """ + Recaptcha is used in member applications. + + :param response: + :return: Boolean, success or not + """ + values = { + 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, + 'response': response, + } + url = "https://www.google.com/recaptcha/api/siteverify" + headers = {'Content-type': 'application/x-www-form-urlencoded'} + resp = requests.post(url, values, headers=headers) + try: + result = json.loads(resp.text) + logging.info('Recaptcha response: {}'.format(result)) + return result["success"] + except: + return False + + +def send_mail_wrapper(subject, message): + """Call send_mail function.""" + send_mail(subject, + message, + 'no-reply@sahkoinsinoorikilta.fi', + ['viestintamestari@sahkoinsinoorikilta.fi'], + fail_silently=False) + + +def convert_table_to_html(table, request): + """ + Convert table to html. + + This is a horrible hack for converting a table object to raw html. + Even with extensive research I wasn't able to find a way to add a path + prefix "e.g. /members/list" to the query strings "e.g. ?sort=foo", so I + did it manually with string.replace. + + Note: When adding the html to a page, you need to run it through + the "safe" filter. E.g. "{{ table|safe }}" + + :param table: Table object from members.tables + :param request: HttpRequest + :return: Raw html string + """ + table_as_html = table.as_html(request) + path = request.path + + fixed = table_as_html.replace(r'href="?', r'href="{}?'.format(path)) + return fixed + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def settings_page(request, *args, **kwargs): + """Render member app settings page.""" + return render(request, 'settings.html', {}) + + +@ensure_csrf_cookie +@require_http_methods(["POST"]) +@permission_required('members.change_member', login_url='/login') +def import_csv(request, *args, **kwargs): + """Get csv data imported to page and create members based on that.""" + try: + data = request.POST['textfield'] + payment_source = request.POST['payment_source'] + except: + return render(request, + 'error.html', + {'error': _('Missing "textfield" POST request field')}) + + try: + result = MemberForm.csv_to_models(data, payment_source=payment_source) + except CSVValidationError as ex: + logging.exception('Model validation error') + return error_view(request, ex.form_errors) + except Exception as ex: + logging.exception('Other error in CSV import') + return error_view(request, ex) + + member_table = MemberTable(result.members, + request=request, + exclude=['id', 'options'], + attrs={'class': 'table table-bordered table-hover'}) + + member_table_html = convert_table_to_html(member_table, request) + + payment_table = PaymentTable(result.payments, + request=request, + exclude=['id', 'options'], + attrs={'class': 'table table-bordered table-hover'}) + + payment_table_html = convert_table_to_html(payment_table, request) + + request.session['models'] = result + context = { + 'members': member_table_html, + 'payments': payment_table_html + } + return render(request, 'member_add_many_confirm.html', context) + + +@ensure_csrf_cookie +@require_http_methods(["GET"]) +@permission_required('members.change_member', login_url='/login') +def export_csv(request, *args, **kwargs): + """Export members as csv.""" + response = HttpResponse() + response['Content-type'] = 'text/csv' + response['Accept'] = 'text/csv' + response['Content-Disposition'] = 'filename; filename=members.csv' + writer = csv.writer(response, csv.excel) + # BOM (optional...Excel needs it to open UTF-8 file properly) + response.write(u'\ufeff'.encode('utf8')) + for obj in Member.objects.all(): + data = obj.as_array() + field_list = map(lambda d: str(d), data) + + writer.writerow(field_list) + + return response + + +def send_mail_wrapper(subject, message, email_to): + """Send mail to default email.""" + send_mail(subject, + message, + settings.DEFAULT_EMAIL_FROM, + [email_to], + fail_silently=False) From e00f1edaa0451d1b00f522ffea96f2ba9c3fa179 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 19:21:19 +0300 Subject: [PATCH 3/8] Fix pep8 --- members/views/applications.py | 1 + 1 file changed, 1 insertion(+) diff --git a/members/views/applications.py b/members/views/applications.py index 030d0eb..0005c1d 100644 --- a/members/views/applications.py +++ b/members/views/applications.py @@ -12,6 +12,7 @@ from members.views.utils import * from members.tables import RequestTable from members.forms import ApplicationForm + @ensure_csrf_cookie @require_http_methods(["GET"]) @permission_required('members.change_member', login_url='/login') From 91a0694ae5ee616d556b5dfea101c6609fd8ccec Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 19:45:58 +0300 Subject: [PATCH 4/8] Fix timezone issues and create coffee view unit test --- coffee_scale/tests.py | 31 +++++++++++++++++-- coffee_scale/views.py | 5 +-- infoscreen/hsl_fetcher.py | 9 +++--- members/forms.py | 4 +-- members/migrations/0016_auto_20170925_1924.py | 26 ++++++++++++++++ members/models.py | 5 ++- 6 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 members/migrations/0016_auto_20170925_1924.py diff --git a/coffee_scale/tests.py b/coffee_scale/tests.py index 7ce503c..b1e5f9b 100644 --- a/coffee_scale/tests.py +++ b/coffee_scale/tests.py @@ -1,3 +1,30 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.conf import settings -# Create your tests here. +from coffee_scale.mqtt import on_message + +HOST = settings.MQTT_SETTINGS['HOST'] +PORT = settings.MQTT_SETTINGS['PORT'] +TOPICS = settings.MQTT_SETTINGS['TOPICS'] + + +class MQTTTestCase(TestCase): + """Tests MQTT functionality""" + + class MockMessage: + def __init__(self, payload, topic): + self.payload = payload + self.topic = topic + + def setUp(self): + payload = '10'.encode('utf-8') + topic = TOPICS['CUPS'] + msg = MQTTTestCase.MockMessage(payload, topic) + + on_message(None, None, msg) + self.c = Client() + + def test_receive_cups(self): + response = self.c.get('/coffee/cups') + payload = response.json() + self.assertEquals(payload['cups'], 10) diff --git a/coffee_scale/views.py b/coffee_scale/views.py index 4d226e3..dc9bd00 100644 --- a/coffee_scale/views.py +++ b/coffee_scale/views.py @@ -1,7 +1,8 @@ from django.shortcuts import render from django.http import JsonResponse -import datetime +from django.utils import timezone + from .mqtt import get_latest import coffee_scale.mqtt # somehow this is needed @@ -15,7 +16,7 @@ def coffee_view(request): def cups_view(request): - now = datetime.datetime.now() + now = timezone.now() latest = get_latest() data = { 'date': now, diff --git a/infoscreen/hsl_fetcher.py b/infoscreen/hsl_fetcher.py index 8cc29e1..f52097e 100644 --- a/infoscreen/hsl_fetcher.py +++ b/infoscreen/hsl_fetcher.py @@ -3,7 +3,8 @@ import urllib.request import json import logging -from datetime import datetime, timedelta +from datetime import timedelta, datetime +from django.utils import timezone from django.conf import settings @@ -20,7 +21,7 @@ class HSLFetcher: def fetch_if_needed(self): """Check if new fetch from HSL API is needed.""" - if (datetime.now() - HSLFetcher.last_fetched > + if (timezone.now() - HSLFetcher.last_fetched > timedelta(minutes=HSLFetcher.INTERVAL)): self.fetch() @@ -38,7 +39,7 @@ class HSLFetcher: arr = [] - time = (datetime.now() + + time = (timezone.now() + timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD)) time = "{0:02d}{0:02d}".format(time.hour, time.minute) for element in data: @@ -65,7 +66,7 @@ class HSLFetcher: obj = model_arr[count - 1] obj.data = json_dump obj.save() - now = datetime.now() + now = timezone.now() HSLFetcher.last_fetched = now logging.info( diff --git a/members/forms.py b/members/forms.py index 5622123..eb49a83 100644 --- a/members/forms.py +++ b/members/forms.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from members.models import Member, Payment, Request import csv -import datetime +from django.utils import timezone import logging @@ -73,7 +73,7 @@ class MemberForm(forms.ModelForm): payment_data = { 'source': payment_source, 'member': member.id, - 'date': datetime.datetime.now(), + 'date': timezone.now(), } form = PaymentForm(payment_data) if not form.is_valid(): diff --git a/members/migrations/0016_auto_20170925_1924.py b/members/migrations/0016_auto_20170925_1924.py new file mode 100644 index 0000000..56f8934 --- /dev/null +++ b/members/migrations/0016_auto_20170925_1924.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-09-25 16:24 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0015_auto_20170925_1917'), + ] + + operations = [ + migrations.AlterField( + model_name='member', + name='created', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created'), + ), + migrations.AlterField( + model_name='payment', + name='date', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'), + ), + ] diff --git a/members/models.py b/members/models.py index 1a78afe..aaf9c80 100644 --- a/members/models.py +++ b/members/models.py @@ -4,7 +4,6 @@ from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from datetime import datetime import csv @@ -79,7 +78,7 @@ class Request(BaseMember): class Payment(models.Model): """Payment model representing one payment event.""" - date = models.DateTimeField(_('Date'), default=datetime.now) + date = models.DateTimeField(_('Date'), default=timezone.now) source = models.CharField(_('Source'), choices=[ ('AYY', _('AYY')), ('cash', _('Cash')), @@ -100,7 +99,7 @@ class Payment(models.Model): class Member(BaseMember): """Member model represets one member on the registry.""" - created = models.DateTimeField(_('Created'), default=datetime.now) + created = models.DateTimeField(_('Created'), default=timezone.now) def last_paid(self): """Return member's last payment.""" From 7ce28c3a489edbf441da6bc93fa2f2555f9ff3c7 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 20:45:13 +0300 Subject: [PATCH 5/8] Add autocomplete widget and fix some errors --- members/forms.py | 8 +++++++- members/templates/payment_add.html | 8 ++++++++ members/urls.py | 11 +++++++++++ members/views/applications.py | 3 +++ members/views/members.py | 18 +++++++++++++++++- members/views/payments.py | 5 ++++- members/views/utils.py | 2 ++ requirements.txt | 1 + sikweb/base.py | 2 ++ 9 files changed, 55 insertions(+), 3 deletions(-) diff --git a/members/forms.py b/members/forms.py index eb49a83..67f4754 100644 --- a/members/forms.py +++ b/members/forms.py @@ -1,13 +1,14 @@ """File containing member forms.""" from django import forms +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from members.models import Member, Payment, Request import csv -from django.utils import timezone import logging +from dal import autocomplete class CSVValidationError(Exception): @@ -88,6 +89,11 @@ class MemberForm(forms.ModelForm): class PaymentForm(forms.ModelForm): """Payment model form.""" + member = forms.ModelChoiceField( + queryset=Member.objects.all(), + widget=autocomplete.ModelSelect2(url='member-autocomplete') + ) + class Meta: """Meta for Payment model form.""" diff --git a/members/templates/payment_add.html b/members/templates/payment_add.html index d1f75b2..90bca77 100644 --- a/members/templates/payment_add.html +++ b/members/templates/payment_add.html @@ -4,6 +4,11 @@ {% load i18n %} {% block content %} + +

{% trans "Add payment" %}

@@ -18,4 +23,7 @@
+ +{{ form.media }} + {% endblock content %} diff --git a/members/urls.py b/members/urls.py index 664e5cd..8cae5f8 100644 --- a/members/urls.py +++ b/members/urls.py @@ -1,6 +1,7 @@ """File containing Member application URLs.""" from django.conf.urls import url +from django.contrib.auth.decorators import login_required, permission_required from django.views.generic.base import RedirectView # members @@ -20,6 +21,9 @@ from members.views import member_delete from members.views import payment_list from members.views import add_many_confirm +# autocomplete view +from members.views import MemberAutoComplete + # rest api from members.views import MemberDetail @@ -110,4 +114,11 @@ urlpatterns = [ # rest api url url(r'^api/members/(?P\d+)$', MemberDetail.as_view()), + # member select autocomplete view + url( + r'^member-autocomplete/$', + permission_required('members.change_member')(MemberAutoComplete.as_view()), + name='member-autocomplete', + ), + ] diff --git a/members/views/applications.py b/members/views/applications.py index 0005c1d..9207fd7 100644 --- a/members/views/applications.py +++ b/members/views/applications.py @@ -8,6 +8,9 @@ from django.conf import settings from django.utils.translation import ugettext as _ from django.forms.models import model_to_dict +import logging +import html + from members.views.utils import * from members.tables import RequestTable from members.forms import ApplicationForm diff --git a/members/views/members.py b/members/views/members.py index d65a5eb..66f43fe 100644 --- a/members/views/members.py +++ b/members/views/members.py @@ -7,6 +7,10 @@ from django.core.mail import send_mail from django.conf import settings from django.utils.translation import ugettext as _ from django.forms.models import model_to_dict +from dal import autocomplete + +import logging +import html from members.models import Member, Request, Payment from members.forms import MemberForm, CSVValidationError @@ -102,7 +106,7 @@ def member_submit(request, *args, **kwargs): if form.is_valid(): form.save() logging.info("Saved new member to member register" - "with the following info: {}".format(form)) + "with the following info: {}".format(form.cleaned_data)) notification = "{} {} {}.".format(_("Successfully added member"), form.cleaned_data['last_name'], form.cleaned_data['first_name']) @@ -180,3 +184,15 @@ def member_edit(request, *args, **kwargs): form = MemberForm(instance=member) return render( request, 'member_edit.html', {'member_id': i, 'form': form}) + + +class MemberAutoComplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + qs = Member.objects.all() + + if self.q: + firsts = qs.filter(first_name__istartswith=self.q) + lasts = qs.filter(last_name__istartswith=self.q) + qs = firsts.union(lasts) + + return qs diff --git a/members/views/payments.py b/members/views/payments.py index 9a0d0e0..da6c8aa 100644 --- a/members/views/payments.py +++ b/members/views/payments.py @@ -8,6 +8,9 @@ from django.conf import settings from django.utils.translation import ugettext as _ from django.forms.models import model_to_dict +import logging +import html + from members.views.utils import * from members.tables import PaymentTable from members.forms import PaymentForm @@ -55,7 +58,7 @@ def payment_submit(request, *args, **kwargs): form.save() logging.info( "Saved new payment to member register with the following info: {}" - .format(form)) + .format(form.cleaned_data)) notification = "{} {}.".format( _("Successfully added payment for member"), form.cleaned_data['member']) diff --git a/members/views/utils.py b/members/views/utils.py index 35b7b84..eeef43d 100644 --- a/members/views/utils.py +++ b/members/views/utils.py @@ -8,6 +8,8 @@ from django.conf import settings from django.utils.translation import ugettext as _ from django.forms.models import model_to_dict +import logging + # REST framework from members.serializers import MemberSerializer from rest_framework import generics diff --git a/requirements.txt b/requirements.txt index 3c028d5..32dc598 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,4 @@ django-modeltranslation==0.12.1 django-auditlog==0.4.3 django-phonenumber-field==1.3.0 paho-mqtt==1.3.0 +django-autocomplete-light==3.2.10 diff --git a/sikweb/base.py b/sikweb/base.py index 6ccab05..e340969 100644 --- a/sikweb/base.py +++ b/sikweb/base.py @@ -64,6 +64,8 @@ LOGGING = { INSTALLED_APPS = [ 'modeltranslation', # has to be before admin for translation admin to work + 'dal', + 'dal_select2', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', From 982e6bf928e285ae684a5cf71f0eccfec06261f4 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 21:55:49 +0300 Subject: [PATCH 6/8] Add autocomplete search to "add payment" and add search bar to table views --- locale/en/LC_MESSAGES/django.mo | Bin 6691 -> 6691 bytes locale/en/LC_MESSAGES/django.po | 133 ++++++++++++++++------------ locale/fi/LC_MESSAGES/django.mo | Bin 8343 -> 8490 bytes locale/fi/LC_MESSAGES/django.po | 131 +++++++++++++++------------ members/templates/member_list.html | 17 ++++ members/templates/payment_list.html | 21 +++++ members/tests.py | 26 ++++-- members/urls.py | 6 +- members/views/members.py | 13 ++- members/views/payments.py | 8 +- 10 files changed, 229 insertions(+), 126 deletions(-) diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo index 2808dcfe602297cff4f5b87ff410f8af0f9967f4..6364efbbae44f602d2bc38d8c392c0efb7c5eaef 100644 GIT binary patch delta 18 ZcmZ2%ve;xppCGG|p_Q@G=E;H+H~>9G1@r&_ delta 18 ZcmZ2%ve;xppCGHDv6ZpK=E;H+H~>9y1^WO1 diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index ae88ae4..839ce4d 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-25 13:38+0300\n" +"POT-Creation-Date: 2017-09-25 21:32+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -184,12 +184,12 @@ msgid "Language" msgstr "Language" #: infoscreen/templates/infoscreen_admin.html:161 -#: members/templates/settings.html:20 sikweb/base.py:214 +#: members/templates/settings.html:20 sikweb/base.py:216 msgid "Finnish" msgstr "Finnish" #: infoscreen/templates/infoscreen_admin.html:162 -#: members/templates/settings.html:21 sikweb/base.py:215 +#: members/templates/settings.html:21 sikweb/base.py:217 msgid "English" msgstr "English" @@ -200,56 +200,56 @@ msgstr "English" msgid "Submit" msgstr "Submitted" -#: members/forms.py:97 members/tables.py:32 +#: members/forms.py:103 members/tables.py:32 msgid "Member" msgstr "Member" -#: members/models.py:14 +#: members/models.py:13 msgid "First name" msgstr "First name" -#: members/models.py:15 +#: members/models.py:14 msgid "Last name" msgstr "Last name" -#: members/models.py:16 webapp/models.py:95 webapp/models.py:108 +#: members/models.py:15 webapp/models.py:95 webapp/models.py:108 msgid "Email" msgstr "Email" -#: members/models.py:17 +#: members/models.py:16 msgid "Place of residence" msgstr "Place of residence" -#: members/models.py:19 members/models.py:84 +#: members/models.py:18 members/models.py:83 #: members/templates/member_add_many.html:35 msgid "AYY" msgstr "AYY" -#: members/models.py:20 +#: members/models.py:19 msgid "JAS" msgstr "JAS" -#: members/models.py:70 +#: members/models.py:69 msgid "Submitted" msgstr "Submitted" -#: members/models.py:82 +#: members/models.py:81 msgid "Date" msgstr "Date" -#: members/models.py:83 +#: members/models.py:82 msgid "Source" msgstr "Source" -#: members/models.py:85 +#: members/models.py:84 msgid "Cash" msgstr "Cash" -#: members/models.py:86 members/templates/member_add_many.html:36 +#: members/models.py:85 members/templates/member_add_many.html:36 msgid "Bank transfer" msgstr "Bank transfer" -#: members/models.py:103 +#: members/models.py:102 msgid "Created" msgstr "Created" @@ -294,7 +294,7 @@ msgid "Add member" msgstr "Add member" #: members/templates/member_add.html:15 members/templates/member_edit.html:18 -#: members/templates/payment_add.html:15 members/templates/payment_edit.html:18 +#: members/templates/payment_add.html:20 members/templates/payment_edit.html:18 msgid "Save" msgstr "Save" @@ -390,7 +390,15 @@ msgstr "Member register" msgid "Members in register:" msgstr "Member register" -#: members/templates/member_list.html:27 +#: members/templates/member_list.html:28 members/templates/payment_list.html:25 +msgid "Search" +msgstr "" + +#: members/templates/member_list.html:36 members/templates/payment_list.html:33 +msgid "Showing results for" +msgstr "" + +#: members/templates/member_list.html:44 msgid "Download CSV" msgstr "Download CSV" @@ -411,7 +419,7 @@ msgstr "Add multiple" msgid "List payments" msgstr "List payments" -#: members/templates/members_base.html:63 members/templates/payment_add.html:8 +#: members/templates/members_base.html:63 members/templates/payment_add.html:13 msgid "Add payment" msgstr "Add payment" @@ -439,82 +447,91 @@ msgstr "Edit payment" msgid "Payment events" msgstr "Payment events" -#: members/views.py:138 members/views.py:209 members/views.py:235 -msgid "No member id specified" -msgstr "No member id specified" +#: members/templates/payment_list.html:18 +#, fuzzy +#| msgid "Member register" +msgid "Payments in register:" +msgstr "Member register" -#: members/views.py:164 -msgid "Successfully added member" -msgstr "Successfully added member" - -#: members/views.py:189 -msgid "Successfully updated member" -msgstr "Successfully updated member" - -#: members/views.py:197 -msgid "Could not update member object" -msgstr "Could not update member object" - -#: members/views.py:213 -msgid "Successfully deleted member" -msgstr "Successfully deleted member" - -#: members/views.py:224 -msgid "Could not delete member object" -msgstr "Could not delete member object" - -#: members/views.py:273 members/views.py:320 members/views.py:348 +#: members/views/applications.py:49 members/views/applications.py:96 +#: members/views/applications.py:124 msgid "No application id specified" msgstr "No application id specified" -#: members/views.py:301 +#: members/views/applications.py:77 msgid "Successfully accepted application" msgstr "Successfully accepted application" -#: members/views.py:308 +#: members/views/applications.py:84 msgid "Could not accept application object" msgstr "Could not accept application object" -#: members/views.py:324 +#: members/views/applications.py:100 msgid "Successfully deleted application" msgstr "Successfully deleted application" -#: members/views.py:336 +#: members/views/applications.py:112 msgid "Could not delete application object" msgstr "Could not delete application object" -#: members/views.py:413 +#: members/views/members.py:70 members/views/members.py:163 +#: members/views/members.py:189 +msgid "No member id specified" +msgstr "No member id specified" + +#: members/views/members.py:105 +msgid "Failed to import members" +msgstr "Failed to import members" + +#: members/views/members.py:118 +msgid "Successfully added member" +msgstr "Successfully added member" + +#: members/views/members.py:143 +msgid "Successfully updated member" +msgstr "Successfully updated member" + +#: members/views/members.py:151 +msgid "Could not update member object" +msgstr "Could not update member object" + +#: members/views/members.py:167 +msgid "Successfully deleted member" +msgstr "Successfully deleted member" + +#: members/views/members.py:178 +msgid "Could not delete member object" +msgstr "Could not delete member object" + +#: members/views/payments.py:69 msgid "Successfully added payment for member" msgstr "Successfully added payment for member" -#: members/views.py:431 members/views.py:449 members/views.py:468 +#: members/views/payments.py:87 members/views/payments.py:105 +#: members/views/payments.py:124 msgid "No payment id specified" msgstr "No payment id specified" -#: members/views.py:473 +#: members/views/payments.py:129 msgid "Successfully deleted payment" msgstr "Successfully deleted payment" -#: members/views.py:483 +#: members/views/payments.py:139 msgid "Could not delete payment object" msgstr "Could not delete payment object" -#: members/views.py:502 +#: members/views/payments.py:158 msgid "Successfully updated payment" msgstr "Successfully updated payment" -#: members/views.py:509 +#: members/views/payments.py:165 msgid "Could not update payment object" msgstr "Could not update payment object" -#: members/views.py:531 +#: members/views/utils.py:110 msgid "Missing \"textfield\" POST request field" msgstr "Missing \"textfield\" POST request field" -#: members/views.py:583 -msgid "Failed to import members" -msgstr "Failed to import members" - #: templates/footer.html:7 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" diff --git a/locale/fi/LC_MESSAGES/django.mo b/locale/fi/LC_MESSAGES/django.mo index c81386c28cfcae8a255be8bdf9a3a834a5613e0c..d57a49a90a032e4ae4175f6680e48ab2849ef0e1 100644 GIT binary patch delta 2736 zcmXxleN5F=9LMo<6N1Y{o)JhME@1ft84zk1%bc|^l`;r*J?u(1?u}=-NF;jAhtwe3 z{MB5`iWb%~TL#Ym3LR5de=u#Vw3s;AvL`EB%bF{Df8faO`Mu6L_xC&Jd%oxVF8l94 zv@CofDdKg*UoRz-(j8&U5ROH2;qRQs7(brJ(|BL3G5RWM`jiPcjdljk$1J=RD{TKG zm_WMb(l9Wj`l&+gGW&hoWuhB3JdWD600fXW~!zbb>C7{ z|0>&FgKpYSV-^N63U^^C^P5+xmaPX9Q5l%Qg;bf@sQXGV5|^Uxe-Jfc zrR}dl)?l7QP1Io9?a0S$n1;tt5s#n-`o=nL z`!Az1_Ph0l?T;fGWhf1`phDy;Q-&I66>8qPmAUA}N>nCVP`?Mgw*8@PpG2+T z5^AEKZ2Kx|YyLrY!Njn9tuPZ6V6JV?Lv8H>yhZPSIhA@Bry1+`;xPF?gefd{@-U(v z7(pGPG1L#uMbyBt9AyQRin>3`T3}s*dVU3J3)Z3ntjBo0|C_0h4f6_W1@GeBIDkv= zGHRlH@}-m(paxomO6dyJR#c%f;zNxWu>IXwN_!9LcV-B^IDt8O|08bW-Nsos3s+(R zwqg#xjSAp%)I_IID;>4{6R1@Gf;#rfNB!iEqVE3#wN-zk#^->ACnJyNq-g~y zs0T7o6E8w#U^!~{4=O6 z3V&&(1^J;d zv(bgT&69uKT-_R}nTmc;o}y@=cIRw#TGk$F3n&_LGo_HCqoWYJoJe;`;%;jComfVB zoYLl0xN}_3J3)7itHn9tPJSRr@6Ep#Y^4_$QyOi%+WHbcN7+f?{WVJ|8!1grMofu& z6SWFT*r|@GO?;8s&A*qa^f+I{u z4EoBPx!F%ld&uW^c4Xh1xq7(2E94Ii_Ye0sc|#oy&Gqg6kT>A#XlU>c4rQNn{SR*W B983TJ delta 2597 zcmXxle@xVM9LMp`fp*?qbq6OB^5aM-7=i{DMv)UOTIRxt89Eo5P)TH1NNHf_5l2yN z(!Qa!x$EaFu!-wh+Nw!EHfk)XKN44FIku7&ZD|`dYwP)fkGK1LAD_?XzMs$M{ds?X z+~BJHD6ikBj~@}82vPE{TY*KPvT;n!pkt@ zirM~L4A9O;FP0;xm?~t-h`E!BpNhxSs;#v9R%^;pjOW)qb>I{K|2Vh-&eF@*oxb{0{pU5OdE5w)Xs&ec4q6Y{{v)Ui9YY`Mo8wdz`6w#2r%(gWqB8Ick}5NeIxm^dxfnp5zW}vh z$o3Z?F`E+9LO0v?Ey&m0&EYC+#E1rVQqfMjQ3Lm)7Kq#axA1D(L$>`brqMo)3gA2z z;zd*j@>6CvP=-qV4XAr$CF=ZY+h3DH{&m87I{2C`9JIq7sEMMe9qdH~x(`{y#O?1x z_V-UPNdFf|lFTX8B|3*1my~MEeDvZ>rtn(Y1*zm;sji_T6YFpRwqiQ=pdyZ=2EJ__ zvi(O<85_2a+5R)Ajr@vy&0ice-c6KR&ySjSeuRoLkZmonCzPTBSZ3Qbs0r&(12>~G z(~P=w+ikl8HD4F%lD&Y+#9_P!zrJq$+ zWY-L!cJvV{uwmQ&3UvujU=p6j7MC$U;e)i-kmf->{|PEeVFTOMfNiLor33XsiJ&Gv zgbL_=)cGG5a^SdN9L_elfhVi)SY@g^4F=a`SbVX>b7z{1%RmZJi=7d7Dn zsGYXi{w`FipGWPe&-TBD3TVK31Qqa6)MNZ9Dsv;KjEq^&VnhpEprQqnxB<94#*Ydl zA2mSilDwuQ%Bs9ZKmi zDwW@$Zjy1^e-4+^{u{N>GQw9TR-n$m1NFGoqITMbe9cY{?8)rIMfe^npzlx{nTXiR z6l&sWR3Q2X(g^`ndm(CI5$eC70u{ims0^${jbDcv*MPb-TWxoVF&!a9ucJAzaMacYH zOqhxmtVG>>Yf*tTp?2Pa+F=LkCXAw9C@-N4c^YRvyEtkik5Kg9XrO4GM;(_ZJ-dh6 zQi^7Ll2Ss^9ik8)cgj6s-8Q@rj9EpwhqB$-BPK^DNj+m^m&HLv(7)>Jl&NuALS-W zSweZtDfOjiM5t|`tfzES8Y!ie7Uw=+rcz}da-zPVtIg^6<)wAduBYsv?55~0*pv9$ rx5n)(3Et(zf@O)(;0f2<((p~;ibOnZYMyf-XQOj5r!r9&8h8B\n" "Language-Team: LANGUAGE \n" @@ -183,12 +183,12 @@ msgid "Language" msgstr "Kieli" #: infoscreen/templates/infoscreen_admin.html:161 -#: members/templates/settings.html:20 sikweb/base.py:214 +#: members/templates/settings.html:20 sikweb/base.py:216 msgid "Finnish" msgstr "suomi" #: infoscreen/templates/infoscreen_admin.html:162 -#: members/templates/settings.html:21 sikweb/base.py:215 +#: members/templates/settings.html:21 sikweb/base.py:217 msgid "English" msgstr "englanti" @@ -197,56 +197,56 @@ msgstr "englanti" msgid "Submit" msgstr "Lisää" -#: members/forms.py:97 members/tables.py:32 +#: members/forms.py:103 members/tables.py:32 msgid "Member" msgstr "Jäsen" -#: members/models.py:14 +#: members/models.py:13 msgid "First name" msgstr "Etunimi" -#: members/models.py:15 +#: members/models.py:14 msgid "Last name" msgstr "Sukunimi" -#: members/models.py:16 webapp/models.py:95 webapp/models.py:108 +#: members/models.py:15 webapp/models.py:95 webapp/models.py:108 msgid "Email" msgstr "Sähköposti" -#: members/models.py:17 +#: members/models.py:16 msgid "Place of residence" msgstr "Asuinpaikka" -#: members/models.py:19 members/models.py:84 +#: members/models.py:18 members/models.py:83 #: members/templates/member_add_many.html:35 msgid "AYY" msgstr "AYY" -#: members/models.py:20 +#: members/models.py:19 msgid "JAS" msgstr "JAS" -#: members/models.py:70 +#: members/models.py:69 msgid "Submitted" msgstr "Lisätty" -#: members/models.py:82 +#: members/models.py:81 msgid "Date" msgstr "Päivämäärä" -#: members/models.py:83 +#: members/models.py:82 msgid "Source" msgstr "Lähde" -#: members/models.py:85 +#: members/models.py:84 msgid "Cash" msgstr "Käteinen" -#: members/models.py:86 members/templates/member_add_many.html:36 +#: members/models.py:85 members/templates/member_add_many.html:36 msgid "Bank transfer" msgstr "Tilisiirto" -#: members/models.py:103 +#: members/models.py:102 msgid "Created" msgstr "Lisätty" @@ -291,7 +291,7 @@ msgid "Add member" msgstr "Lisää jäsen" #: members/templates/member_add.html:15 members/templates/member_edit.html:18 -#: members/templates/payment_add.html:15 members/templates/payment_edit.html:18 +#: members/templates/payment_add.html:20 members/templates/payment_edit.html:18 msgid "Save" msgstr "Tallenna" @@ -382,7 +382,15 @@ msgstr "Jäsenrekisteri" msgid "Members in register:" msgstr "Jäseniä:" -#: members/templates/member_list.html:27 +#: members/templates/member_list.html:28 members/templates/payment_list.html:25 +msgid "Search" +msgstr "Hae" + +#: members/templates/member_list.html:36 members/templates/payment_list.html:33 +msgid "Showing results for" +msgstr "Näytetään tulokset haulle" + +#: members/templates/member_list.html:44 msgid "Download CSV" msgstr "Lataa CSV" @@ -403,7 +411,7 @@ msgstr "Lisää useita" msgid "List payments" msgstr "Maksulistaus" -#: members/templates/members_base.html:63 members/templates/payment_add.html:8 +#: members/templates/members_base.html:63 members/templates/payment_add.html:13 msgid "Add payment" msgstr "Lisää maksu" @@ -431,82 +439,89 @@ msgstr "Muokkaa maksua" msgid "Payment events" msgstr "Maksutapahtumat" -#: members/views.py:138 members/views.py:209 members/views.py:235 -msgid "No member id specified" -msgstr "Jäsenen ID ei määritelty" +#: members/templates/payment_list.html:18 +msgid "Payments in register:" +msgstr "Maksutapahtumia:" -#: members/views.py:164 -msgid "Successfully added member" -msgstr "Onnistuneesti lisättiin jäsen" - -#: members/views.py:189 -msgid "Successfully updated member" -msgstr "Onnistuneesti päivitettiin jäsen" - -#: members/views.py:197 -msgid "Could not update member object" -msgstr "Jäsenobjektia ei voitu päivittää" - -#: members/views.py:213 -msgid "Successfully deleted member" -msgstr "Onnistuneesti poistettiin jäsen" - -#: members/views.py:224 -msgid "Could not delete member object" -msgstr "Jäsenobjektia ei voitu poistaa" - -#: members/views.py:273 members/views.py:320 members/views.py:348 +#: members/views/applications.py:49 members/views/applications.py:96 +#: members/views/applications.py:124 msgid "No application id specified" msgstr "Hakemuksen ID ei määritelty" -#: members/views.py:301 +#: members/views/applications.py:77 msgid "Successfully accepted application" msgstr "Onnistuneesti hyväksyttiin hakemus" -#: members/views.py:308 +#: members/views/applications.py:84 msgid "Could not accept application object" msgstr "Hakemusobjektia ei voitu hyväksyä" -#: members/views.py:324 +#: members/views/applications.py:100 msgid "Successfully deleted application" msgstr "Onnistuneesti poistettiin hakemus" -#: members/views.py:336 +#: members/views/applications.py:112 msgid "Could not delete application object" msgstr "Hakemusobjektia ei voitu poistaa" -#: members/views.py:413 +#: members/views/members.py:70 members/views/members.py:163 +#: members/views/members.py:189 +msgid "No member id specified" +msgstr "Jäsenen ID ei määritelty" + +#: members/views/members.py:105 +msgid "Failed to import members" +msgstr "Jäsenten tuonti epäonnistui" + +#: members/views/members.py:118 +msgid "Successfully added member" +msgstr "Onnistuneesti lisättiin jäsen" + +#: members/views/members.py:143 +msgid "Successfully updated member" +msgstr "Onnistuneesti päivitettiin jäsen" + +#: members/views/members.py:151 +msgid "Could not update member object" +msgstr "Jäsenobjektia ei voitu päivittää" + +#: members/views/members.py:167 +msgid "Successfully deleted member" +msgstr "Onnistuneesti poistettiin jäsen" + +#: members/views/members.py:178 +msgid "Could not delete member object" +msgstr "Jäsenobjektia ei voitu poistaa" + +#: members/views/payments.py:69 msgid "Successfully added payment for member" msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle" -#: members/views.py:431 members/views.py:449 members/views.py:468 +#: members/views/payments.py:87 members/views/payments.py:105 +#: members/views/payments.py:124 msgid "No payment id specified" msgstr "Maksutapahtuman ID ei määritelty" -#: members/views.py:473 +#: members/views/payments.py:129 msgid "Successfully deleted payment" msgstr "Onnistuneesti poistettiin maksutapahtuma" -#: members/views.py:483 +#: members/views/payments.py:139 msgid "Could not delete payment object" msgstr "Maksutapahtumaobjektia ei voitu poistaa" -#: members/views.py:502 +#: members/views/payments.py:158 msgid "Successfully updated payment" msgstr "Onnistuneesti päivitettiin maksutapahtuma" -#: members/views.py:509 +#: members/views/payments.py:165 msgid "Could not update payment object" msgstr "Maksutapahtumaobjektia ei voitu päivittää" -#: members/views.py:531 +#: members/views/utils.py:110 msgid "Missing \"textfield\" POST request field" msgstr "Puuttuva \"textfield\" POST-kenttä" -#: members/views.py:583 -msgid "Failed to import members" -msgstr "Jäsenten tuonti epäonnistui" - #: templates/footer.html:7 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" diff --git a/members/templates/member_list.html b/members/templates/member_list.html index 9a665fc..9c0eb80 100644 --- a/members/templates/member_list.html +++ b/members/templates/member_list.html @@ -21,6 +21,23 @@ {% trans "Members in register:" %} {{ member_count }} +
+
+ + + + +
+
+ + {% if request.GET.q %} +
+ +
+ {% endif %} + {{ table|safe }}
diff --git a/members/templates/payment_list.html b/members/templates/payment_list.html index 780e0cb..1665f0a 100644 --- a/members/templates/payment_list.html +++ b/members/templates/payment_list.html @@ -14,6 +14,27 @@
{% endif %} +
+ {% trans "Payments in register:" %} {{ payment_count }} +
+ +
+
+ + + + +
+
+ + {% if request.GET.q %} +
+ +
+ {% endif %} + {{ table|safe }} {% endblock content %} diff --git a/members/tests.py b/members/tests.py index 3987d38..d2d6bb4 100644 --- a/members/tests.py +++ b/members/tests.py @@ -12,9 +12,11 @@ class MemberRegisterTestCase(TestCase): def setUp(self): """Setup testing environment by creating member and admin.""" memb = Member.objects.create(first_name="Tidus", last_name="Tester") + username, password = 'test_admin', 'password123' test_admin = User.objects.create_superuser( - 'test_admin', 'myemail@test.com', 'password123') + username, 'myemail@test.com', password) self.c = Client() + self.c.login(username=username, password=password) def test_member_created(self): """Test member creation.""" @@ -24,13 +26,27 @@ class MemberRegisterTestCase(TestCase): def test_import_csv_single_line(self): """Test csv import only with single line in csv file.""" data = 'Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0' - response = self.c.post('/members/import_csv', {'textarea': data}) - self.assertIn(response.status_code, [200, 302]) + response = self.c.post('/members/import_csv', {'textarea': data}, follow=True) + self.assertEqual(response.status_code, 200) def test_import_csv_multi_line(self): """Test csv import with multilined csv.""" data = ('Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0\n' 'Reiska, Remontti, remontti@reiska.fi, Värisilmä, 1, 1') - response = self.c.post('/members/import_csv', {'textarea': data}) - self.assertIn(response.status_code, [200, 302]) + response = self.c.post('/members/import_csv', {'textarea': data}, follow=True) + self.assertEqual(response.status_code, 200) + + def test_autocomplete_search_found(self): + """Test member autocomplete search""" + search_terms = 'Tidus' + response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True) + results = response.json()['results'] + self.assertEqual(len(results), 1) + + def test_autocomplete_search_not_found(self): + """Test member autocomplete search""" + search_terms = 'Notfound' + response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True) + results = response.json()['results'] + self.assertEqual(len(results), 0) diff --git a/members/urls.py b/members/urls.py index 8cae5f8..1578da4 100644 --- a/members/urls.py +++ b/members/urls.py @@ -40,6 +40,10 @@ from members.views import application_form_success favicon_view = RedirectView.as_view( url='static/img/favicon.ico', permanent=True) +member_autocomplete_view = login_required( + permission_required('members.change_member', login_url='/login')(MemberAutoComplete.as_view()) +) + urlpatterns = [ # landing page @@ -117,7 +121,7 @@ urlpatterns = [ # member select autocomplete view url( r'^member-autocomplete/$', - permission_required('members.change_member')(MemberAutoComplete.as_view()), + member_autocomplete_view, name='member-autocomplete', ), diff --git a/members/views/members.py b/members/views/members.py index 66f43fe..18c8325 100644 --- a/members/views/members.py +++ b/members/views/members.py @@ -1,5 +1,6 @@ from django.shortcuts import render from django.contrib.auth.decorators import permission_required +from django.utils.decorators import method_decorator from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import ensure_csrf_cookie from django.http import HttpResponse, HttpResponseRedirect @@ -24,7 +25,13 @@ from members.views.utils import * @permission_required('members.change_member', login_url='/login') def member_list(request, *args, **kwargs): """Render members list.""" - members = Member.objects.all() + search = request.GET.get('q', None) + if search: + firsts = Member.objects.filter(first_name__istartswith=search) + lasts = Member.objects.filter(last_name__istartswith=search) + members = firsts | lasts + else: + members = Member.objects.all() table = MemberTable(members, request=request, @@ -33,7 +40,6 @@ def member_list(request, *args, **kwargs): table.paginate(page=request.GET.get('page', 1), per_page=25) table_html = convert_table_to_html(table, request) - context = { 'table': table_html, 'member_count': len(members), @@ -187,12 +193,13 @@ def member_edit(request, *args, **kwargs): class MemberAutoComplete(autocomplete.Select2QuerySetView): + def get_queryset(self): qs = Member.objects.all() if self.q: firsts = qs.filter(first_name__istartswith=self.q) lasts = qs.filter(last_name__istartswith=self.q) - qs = firsts.union(lasts) + qs = firsts | lasts return qs diff --git a/members/views/payments.py b/members/views/payments.py index da6c8aa..be1a12e 100644 --- a/members/views/payments.py +++ b/members/views/payments.py @@ -21,7 +21,13 @@ from members.forms import PaymentForm @permission_required('members.change_member', login_url='/login') def payment_list(request, *args, **kwargs): """Render list of payments.""" - payments = Payment.objects.all() + search = request.GET.get('q', None) + if search: + firsts = Payment.objects.filter(member__first_name__istartswith=search) + lasts = Payment.objects.filter(member__last_name__istartswith=search) + payments = firsts | lasts + else: + payments = Payment.objects.all() table = PaymentTable(payments, request=request, From f6aa86d03224a085d79e3a6f86b8442ea7d18cc9 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 22:00:48 +0300 Subject: [PATCH 7/8] Add missing dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 32dc598..42871fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,4 @@ django-auditlog==0.4.3 django-phonenumber-field==1.3.0 paho-mqtt==1.3.0 django-autocomplete-light==3.2.10 +six==1.10.0 From 51e16df08b98ae610faeac3a6c2aa8f7b1c580dc Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 25 Sep 2017 22:19:00 +0300 Subject: [PATCH 8/8] Fix model form issues --- members/forms.py | 11 +++++++++-- members/views.py | 2 +- members/views/utils.py | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/members/forms.py b/members/forms.py index 67f4754..0cd4a8c 100644 --- a/members/forms.py +++ b/members/forms.py @@ -38,11 +38,18 @@ class MemberForm(forms.ModelForm): return email + def _clean_boolean_field(self, key): + value = self.data.get(key, None) + if value in ['1', '0']: + return bool(int(value)) + else: + return value == 'on' + def clean_jas(self): - return bool(int(self.data['jas'])) + return self._clean_boolean_field('jas') def clean_AYY(self): - return bool(int(self.data['AYY'])) + return self._clean_boolean_field('AYY') @staticmethod def csv_to_models(data, payment_source='AYY'): diff --git a/members/views.py b/members/views.py index 24e5dc0..b95f85e 100644 --- a/members/views.py +++ b/members/views.py @@ -25,7 +25,7 @@ import csv import pickle from smtplib import SMTPAuthenticationError -from members.models import Member, Request, Payment, MemberConflict +from members.models import Member, Request, Payment from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError from members.tables import MemberTable, PaymentTable, RequestTable diff --git a/members/views/utils.py b/members/views/utils.py index eeef43d..4636505 100644 --- a/members/views/utils.py +++ b/members/views/utils.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext as _ from django.forms.models import model_to_dict import logging +import csv # REST framework from members.serializers import MemberSerializer @@ -17,6 +18,8 @@ from rest_framework import permissions from rest_framework.throttling import UserRateThrottle, AnonRateThrottle from members.models import Member, Request, Payment +from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError +from members.tables import MemberTable, PaymentTable, RequestTable # Can be used to retrieve single member information via REST API