From f6a4253936e92345eb7af9598c8a91d12da7cf0d Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 4 Aug 2020 16:13:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(ticket):=20=E5=B7=A5=E5=8D=95=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E7=94=9F=E6=88=90=20Comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/mixins.py | 8 +- apps/authentication/models.py | 2 +- apps/common/db/models.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 56117 -> 56305 bytes apps/locale/zh/LC_MESSAGES/django.po | 108 ++++++++++-------- apps/tickets/api/request_asset_perm.py | 32 +++--- apps/tickets/exceptions.py | 15 ++- .../migrations/0003_auto_20200804_1551.py | 18 +++ apps/tickets/models/ticket.py | 66 +++++------ .../tickets/serializers/request_asset_perm.py | 6 +- apps/tickets/serializers/ticket.py | 46 +++++--- apps/tickets/utils.py | 2 +- 12 files changed, 180 insertions(+), 125 deletions(-) create mode 100644 apps/tickets/migrations/0003_auto_20200804_1551.py diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index cdc7856bd..a450f610a 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -139,7 +139,7 @@ class AuthMixin: def get_ticket_or_create(self, confirm_setting): ticket = self.get_ticket() - if not ticket or ticket.status == ticket.STATUS_CLOSED: + if not ticket or ticket.status == ticket.STATUS.CLOSED: ticket = confirm_setting.create_confirm_ticket(self.request) self.request.session['auth_ticket_id'] = str(ticket.id) return ticket @@ -148,12 +148,12 @@ class AuthMixin: ticket = self.get_ticket() if not ticket: raise errors.LoginConfirmOtherError('', "Not found") - if ticket.status == ticket.STATUS_OPEN: + if ticket.status == ticket.STATUS.OPEN: raise errors.LoginConfirmWaitError(ticket.id) - elif ticket.action == ticket.ACTION_APPROVE: + elif ticket.action == ticket.ACTION.APPROVE: self.request.session["auth_confirm"] = "1" return - elif ticket.action == ticket.ACTION_REJECT: + elif ticket.action == ticket.ACTION.REJECT: raise errors.LoginConfirmOtherError( ticket.id, ticket.get_action_display() ) diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 27a9d7857..208d9c9fb 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -71,7 +71,7 @@ class LoginConfirmSetting(CommonModelMixin): reviewer = self.reviewers.all() ticket = Ticket.objects.create( user=self.user, title=title, body=body, - type=Ticket.TYPE_LOGIN_CONFIRM, + type=Ticket.TYPE.LOGIN_CONFIRM, ) ticket.assignees.set(reviewer) return ticket diff --git a/apps/common/db/models.py b/apps/common/db/models.py index b807b9dd5..502df31e9 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -52,7 +52,7 @@ class ChoiceSetType(type): return self._choices_dict.__getitem__(item) def get(self, item, default=None): - return self._choices_dict.get(item, default=None) + return self._choices_dict.get(item, default) @property def choices(self): diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e779e41643d2ea599b69b258531bf543208f7062..cb2695eeceba7079ef663998880c7098de03d2f7 100644 GIT binary patch delta 16926 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�}uY4P8v1tmV`&QE1#H4C5?QU)7irE~1R64R|= zE`|{=ws@1pJ5d9lM7{SHQP0eEGyJ@pup#P#+M>qkg{g74#WPUj#G?AG_gP}UC61X_ zP&@Dt^^m%F4Gpvf<1$W`TYE<+=QwKFr z160SB)}fo(%N&3`X&-``=pJg~1Q*>nX;9;&M~#ym>tGSoP7lGNI1+Q|{ohDMS9S@P z;A_-3o`sj(z&la#NsAw#26~I>67Z>a*-f13ikmpX%!gW7akB>cFATNy-O&H<|AVYy zxIe)!9;h9ejkrV z@fBQV^_H~Hqp#-oAJqwdrs)6ah(ChZ25p%RKAZf<3sZ#AKmKUXc@qgHU}a1Gj#Vi) z>34)u+4>)$&)1Y237G$DOC~4rht&(?MB@J*kF8C)VYIc!!jwSjx;?KcXDBVLUtjXy zP~U+`E!TMKBjfFCLct68RrsL zqn;L%QGTN6$Uym(cKzE@X4+5TNYvrK?eUcvBzczn{|nA6>IvyMilWcx_=C?GFO=JH zo%WryeL;K(=V4BAGp%DLa^;94C<~~M#inZ8}{Ps(8b z`$we^olf9DN(|*JokDC-La&7lsIOSRlg~rk7dxwwV-Imh>yw9kb8-R1J22~i#`%=^ zp~XMZ-bwGYexTf@tfc(+C_xZKr}cKiC)j{ej2!>#l(&=ivy|(UW0XPU@><_T)caF1 zQglplcpI&KGVvVhzG3|F1En-2F-1pdhyTyo{F#{htYfs}-#@$B;7WWzDPr+#>J=Dk zff_g-Q7=H-EgSzf>Z3cK?tdXVJf{?=gZ{^`WH!iC>XDTClp`92V*_;^`uvZjS?Ku zpgxk4hZ01b3y0A5IVJu`W%bDT1lA>YiT(p9*(u2=3(2LVEje}n2f-=)iQoqtpoF;| zhf;b``q8HkMaOt+FG9VW2H{A8A7d>{Nqaf!r|>)LTN*#4EFzznqT?%y?m-P9T@(Z~e*at^ax>8?EDMcwr(Q)75-M4dB;`j8;NhwMFw6zbzatZi{ z@H8~&m`39pEQ256LGo*<@3D6E(ZA<^GktwjYTx$?ISx9!E%XebUI%r&rL6MTc!%&J zXTGF-X1SI)n6i}8g#08sPtEHoLsa3YLz|ANSQ1xbf&gyY`<$?ulAc66N#&#>>*bCW+nxkSlAz8AS&cn0rN%2RYi zQ?Bb{YZo2=dsHHxMSdUUB=w#&=CwYrsBb4d^dvT zIP^bxUm*(y&^Us66y>?)5?~}JG{#hx3uY&l#E*)fS-u*+w#hGB`xwRKKZkqP&zk(d z*d5llnuBzDNy$NnJ+wBWG@(?XTp;%ar7i8}De*@&t6U*BH6hP&I$wIKBPickw|<;< znEHNgt^d}=4p!BENcp@hZv#XsrOQ2z_`ZaP#YQI_%xr5N!tERJg^4=AH3?>OOoN)d{V zRrJv@z+C7;uN3XA$Zf?Glz*syfX8t%WfT3X;3I#3?!iPF$C5}uxk^3$$VS}M;@Z@= zQgj@}#*{e9L&{ci6)1Nodx>?NP3NXbFn=h}S!zX|-90S{C9 zk&K|je9AcLFENagozjZ(kvqxjfo+KE;|tEy@s~LU_meM5$w2vva-Li_)G@*OxoG-= zN+G-liG0I}@6x3>%?ofmuE5`L4P`MUD}4fZj$ctaBAGs2g_#>M7K7vKq8$04O$}Q^YDY>b4 zp}bH1E$81u9ffU7)dOfhPMJ?>9sjF9FddH4m{C1AuF$9>JNenv%TOOn$wGY{xk+lY zV=?hpR^y`ikehj?Zu^wy5cOL-8kU9pHKGYkA%o%A0F@UScZGn^?#59#Fns z*YhUf5A{6n15B##d6)1S9;e-|2A&rY;Q72s4L$D^6^j~sURLb)n&$=KI1It5m=PCX zHe8AMagTWo^HC0H;(0l-0J46sDwf8Y7>5H<{gz-X9>BcJ@7*U;lt6G(&ntlyu`qVS z^Ed^6#IDUa8LZu$)54Y*j7es1^F7pp$6+|m!U$Y}X>lj2-vQM#zjum^c6`$s+_UnZ zs1taOnmAnx&&!TEPy>`g?wVH((_u~21PPcKo1-S~j9Tyzb2Mt)Y3Pe4^D!C!kzilS z_pu^&Xz3cxK~1y_i{MI(!Bdz9pP~kQi8{gHR-TsytDqLv*ldkDnIzN(dbQ&Gwc;TJ zbPqqka9oTU@C($v+>e^*u*Hv~2D*fW@HT3P!L8lB&W`CR*FjCx1l6uR>SRWuPJC8t z&R-St323EzFe@HL4R8gs;cet$_gfzm5dXjrQ4@^s zM?{{eU{5%l^2J_mYen{%#ddu^i>VZtln{p$2S+ zI>PR#1-^@VHYT7Ru6d{lHluFgPSk6A5_Jo&q59uLwR?s+ncqwGmV4TBVQ$K0P%CVL zdP>`&I(9`pYy&Y6M_GJ4s^1(``%PBA3pLI@jKbrnjsAw(&=d5fArsi$?IZ)LA`$~I z#w?2ID951|QUSHY+8Bh1m=}|94Ze@MC9yr+PV1RXQ1i9HlGvdK_g^cVN+2E1N9}Ma zrpJw_hiWIP{)BnXyoRyF@1Tx4yr(nqVvz z!)d7a4%AT}LQU{3rp8OCfv%!Zp}B#7x)gi{t+)u5#W<{tZ=x3VF>0b^sH6T2wexKj{|YtX5sP0k zZ=x1-AN6`Z@6Y*bC0Pf!BP@!VpeAYu^-%-2Ks|KrQ4d`|a{@+FUW}RXuz4P%DBnTt zIKx2qyI@YtLb<-#b|B}ko%bf7*JZdl5B00o7pR?{KuvJgyp6h5PtBL8g$53CuVDyk zhq*8eD_FS!7NXn*^~_E5kZB4Z z-ofmG8m|}Xh=-z1HW{_BeW>vdV}#!S(_~5$_!-qP#}IdfF<6{(b=0?6FN;q^-Rs4u zTd~^ex1knz#=MN0=mzSj@1y^jLEYMym|5?C>i1m72-FehLUk-+mO)Ka6*X}J>I7P# z7Tnp&15gVXWllsL`3$RHia9B7LM`YR`ihb{M@Bmg80r=fg<4@8=D;#o8ylkDk{OsC z*P<475OwdrLoMhcM&cdR$p#K{3$B2wuZ!A9t6`kKI=(?bJMV@n53~luP)9ZfwV+9; zXJk5RrwgpS6t&Rx=>HtRSjy+I0tO6sJFbN4R~t26!{MC20!^)=4f+>^8nBy{eOQF@ zcvQy?7>-*|C-4pGmYhTNzisi~Q4i@0)QMys;cjg))V$?=mZ^%`*{i6AO;9J&6E)#r zD-TB<-9*$gGRw*zTm5p>z1@U5x!tG*9zgXwjWKu;qtF-dzRMIu?YK7P#txVVhoX*j z9+t<|s9#jBqIMj>3F>6RP~ZDiP!n{(4mcKj;0>&WjX&T|&Nu_}>HSYXlFxMlu^5fx zP!q4iQg{^k5cK}RV%UF_d-xXOddgdH4R#srp6<{w?gV2|3#pA7w>MVAWUPcoF|FSJ zG-KTng_yZ8D-B{%_p$~CVm;J7Z-iQKM@)meEq(xXg6B|g#~+vt(~WbtG9T&|7e|d3 zkN)?+npM1pny@YE-giRXK1_qxum=8& z>K8rP#Y>`2tUPKVRWU6#@{!3%rX^}c9kCbo#6ox$^#SrX>L@c!anDA$8HLq}$DthSyOmec&p*@EM!{3n+|@i6_id zYd(mu3+9-`!y3S!_wf|vPiFJSEZX;-!-Gfp@Q0qq_nNnNF5ie)X&$dC?!vva8#13qy(&V;Pk%Z@s!ShEJ^quk2s-$9+wcr1!Dung|R zJop<%WAGwQP&+D1CJJ9eHRy|Va4H_b?{PDJyx4u9G+p98u--D?LEVDUR-TQ)l$V;H zo4ZiY$TwC#g+4XBVikAI2dI1X6jdLv)ESEUgv)K^@~8o8S-F9gTbmusE>_>m9D*8u z>{9Nij%Yf8BDe$F;tkYOTz#2qI0QA(D2&00=0=NOKrQqd=EeJ{hcNh47mqUI%*tlH zPknCSmITzHo!J*dD33->Jky+qp_CV)cJMjo$1hO}xP;osFILX7+{N>v7FNv431%Cg zRdhiOI2tvBK+ZKOh{%r=VbnVh(5bd*}7M#c8`OOljPtq#Ld_J!Q8Fd(7 z4c4|OYFVj)bo+KnG)mP5ToRV4F!Ev=%DISA7eABLJ} zti@+pc|K+!zQW=gF`DvrtG|j`@UN%^{$ugbHEz7zW-R)&^9mNIhAJnZIySR-7qdTV zKQtV8s{;l$A7FG^qDJ%pz5=K#{Jh0^AeE7Fb3nX9(J^bt56I2 z95vv6RQqG7g){?~CW=dVBl0S(j=wZkr`9gRS(G#P{O zBXgzsg}Ki>fm-M#^EZp9TIa^igu&FunB{zysfn8CH7mEWawiNS-UGG3!KeYoo3qU& zs1Ks`<|&M${17#6)O!A#AI6}{`%vTfj#%c5Ra{3+bjRY)%%IQhHzd?Yb4C0J`=bU7 z-rx*J4IF94U?a-qtUMBRLesF6-v1>Q_|65qTNpxvY#ZHxg-|;yhWbEhZ1HAhB5J}U z)UE7|dhOoD;cEgb5g#G znjnzhX7mteL@lfkYTSxuZB)Bv=+iG8iDdNU@h<9!W}-SA!3=oD%GWTI@_p0*FDxFo z#Z8z66)$R*MvW7X`LGV^M7pBJ>AQvVR|g+~%s9>(%*6o8OUzF(4ds=X3D==^whPt& zBw3R?ctbBB+&@w|EV+E*2%; z7`31wm<}gn1kOe+a5d_o-hdi^k9pK*ne(UtZlRv`rxuUg?gofK)t5lM1vN1o>!5c2 zI!56@%!ad36Mcs2w;k2)sKswt-1pD|X})wHiD9UnmPQ?671RLDEZ)U@8@00`sEK^$ z6jb~9sC&HD;=8a2)h0c?D|39<99`iJ6hokW>f3Jv`m~ahWHiuq^N#rks{RS;$TRG5Fzqh8~6sD39=@yk|z zjOriY+vggDp$5ulRzY=WgIZ8O)IfvHG3HFG|HR76%}uC>cdwPtpeDSI`SBsD-WRdo zeUKDIed$z3P1FrF@j!DZ>WD|0lg&A(_KQ&M*P@=4&8U+)i0XF^)$S+M>;9XI`@AP) z@)AgOz%_`%M9Ohk9LHe++-&t{QSEP_1`asrCJr{kQ9F&aa#6E1Y60<9u7m0I{x`CU z)@En3AL@ukpmvgs+TjYTUuW(>E#$D(pSJjURKHuOTl>`NBM!N7bD{t5|4Nh5CsR$- z3WuONjzzuK^H9&i0o1*_j9SP`RQohvy8**c@ginP)WRyD+Pz{nw)(c{fB)aKib3W` za~kSfYXRy=SEB~}(mY@uM|~Nc!yvq8K15CY%nUm0HWrR*7k!xX*UsVy$hxQwN!FmZ z#Rp<{;!~{r*vxdqEhq*xU};qUc=UfeVj;?}Tl{@Z-%`6^bz=;J&W*b&>{eDweQ{{u35{X$Q;Bd&`&!uA-2 zy)h1_qVDNV)IzV|Ec^=x;>45g7Q8SspK`y76*MbjecCm{I`|Q$=lS!lk#8d|xP*~#pS+SzbyfupT_&ioO>h~KjE)9*Qd1^y+Vfx~`q?|W|4 z!&1O(hg#@t)XtWm`fbE;JZR%nlr8(YUV?YSJFpD4_8&x!`0I&rkL|l z1Fk?#xB+!S2e2w$#yptwtQ)W*zDl_^>LezhK3}Gz#z{s^ywK`>Yb>+T+>X6zuopE^ z@pEqCI;epXQ3JI{4b%-A;y~1dd$AOLjfL?EYGZlM^Qnh5P~V0(kPY$szXifBxQR-j z2C9kKQt`^5CQiEOChlzZL7m88a}xS@hC2E+sD#c$qvRzuvL|n-k&q2~vk~&@IDWA0bzxcJj zA#~}R;U$%m2=*oaIf+-p`-k!;q;%AMPFgA{*Ty^;hihoZZ=e3hyF0!?{08c(NWLQ} zHSMxuMVv%@75VDqeZNsiOXeT7<{IJf|13v*FAXP8lHx4Ilpe_^XP4fEo zs6wAA_zBL%ukl^{fRsf3I_Y=XFQdH3fBs~akaT@YMHP~+wxns6S2-7{HtBr^NVyh~ zS!r!X5-&_zV&zRZlKN4kzbvMHO=*(}+hZTy|662Es>ZJEl$+C`CaEW}h1NOQe2#pf zc)hW{#b#hyQf1E%bj>DKRG$#KhLH64o7r#io%z9JnW)gUz|z5?GR1yY}KZ6uSQ*cc1=Mp)%sj3Hg6 z@-co$xZA zMOvIn=g+l|%I&0cG|q>8@d`;-Ac_B#%zK@bi}anvlkpzq{#L&2Z_S@5QU42?u8q`( zlW&TDA%C#&I#`{)mzVkfeG8S5Bz@26s^IVjn;Ge_k<^GbYe-|rXCe(F=_*gG6KNPJ zJ!y~&d%@)U(dGrIAo+T@mvoo(F=bt=b^mLSLJ1T?U0WUh_j4EVS(Fm-FHHH&=t`fb z)~Xe@u|X9|MShRQBsC!YYVD#K<0Hzta-gpzKaSeOZ(&BtpHX=f6Da?U_x;V?kIa;H zO`%R-z^O2h*a6ZXq#&zTn;*!ZBki;pzleGvw10wnj;E#E|Mw}x&`7`Y44{Fo^t32X z{yXdJ4_e)y6xLf_@izWS_v0+_{A4qbx|5%0by+c<@?v~Uy34q4>;At*W(tX4^t~3i z4$F{olh#tMN6JqAC8-GN?TrkJw6V zVQnvwPq}<&Xqc6XQrMQ1jr5STgjfd*#i`b@ALRn{EM&bG;J>7&)W1rduH4A4#s2Ft z`O=o}MVm2{r&E5#+P@v5YUASL-DyuX-^MfAh8 z7JJ9qUo_9t{u@#wt6xC;ZL718SB!FJ(tORoj?54`lqWxt{29D~D@bFB=b(Iue9Bdr zShfF@M^iDG*eFs>@<*t94d+l^MSdzavCgSk{AS9!?va}1;0tPS<79EXtio9sCjZ<0h0Og^+Gi7ldg@ztZnK_9lgqkHYb!`Lr3TPw27) zHxo#?ej)P{vDTz=7JQe+<4KXE;iP2Z^)cnTN4^$?->^D7+(h_Y)o?WhP~(-5TsoYDm&` zh*X301*sSHTS&gOR!PjOLm=hqL3}%fZg|_`6Ue9gPaCy;X8l`XJJPEv^XT?~oBwH> zhIme5eXub;q3&mWtDK_n4e34=|BxP8raz&@SZ~O#ZT!2Vq{ze};qT z6U1Vo$d4jbrMwNp=)BxNfZl4Z{RDN*G?l+V;{d&c#D1~(Kvn$b8bE$I?JiLN(SPEV ziRrq81F5@iq0VRZEr=bk{twM-)*gIb2n~OvVgVK>asprA4SWY1k@$KYeTY`#0r_(kj!~fUPM9SbmmiO}tO(`~m4b r0y9Zh$?N*j!OzXrYnCmwc~<{l!#B?#8(w+yiM{)?Y+iA?VTS(!Gk54a diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 92d93b15f..fb3ab714f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-07-31 19:20+0800\n" +"POT-Creation-Date: 2020-08-04 15:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -47,7 +47,7 @@ msgid "Name" msgstr "名称" #: applications/models/database_app.py:22 assets/models/cmd_filter.py:52 -#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:46 +#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:40 #: users/templates/users/user_granted_database_app.html:35 msgid "Type" msgstr "类型" @@ -131,8 +131,8 @@ msgstr "参数" #: applications/models/remote_app.py:39 assets/models/asset.py:224 #: assets/models/base.py:240 assets/models/cluster.py:28 #: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60 -#: assets/models/group.py:21 common/db/models.py:66 common/mixins/models.py:49 -#: orgs/models.py:23 orgs/models.py:316 perms/models/base.py:54 +#: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49 +#: orgs/models.py:23 orgs/models.py:326 perms/models/base.py:54 #: users/models/user.py:530 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 #: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56 @@ -145,9 +145,9 @@ msgstr "创建者" #: applications/models/remote_app.py:42 assets/models/asset.py:225 #: assets/models/base.py:238 assets/models/cluster.py:26 #: assets/models/domain.py:23 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:68 +#: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:69 #: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 -#: orgs/models.py:24 orgs/models.py:314 perms/models/base.py:55 +#: orgs/models.py:24 orgs/models.py:324 perms/models/base.py:55 #: users/models/group.py:18 users/templates/users/user_group_detail.html:58 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149 msgid "Date created" @@ -190,7 +190,7 @@ msgstr "基础" msgid "Charset" msgstr "编码" -#: assets/models/asset.py:148 tickets/models/ticket.py:41 +#: assets/models/asset.py:148 tickets/models/ticket.py:35 msgid "Meta" msgstr "元数据" @@ -380,8 +380,8 @@ msgid "SSH public key" msgstr "SSH公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 -#: common/db/models.py:69 common/mixins/models.py:51 ops/models/adhoc.py:39 -#: orgs/models.py:315 +#: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39 +#: orgs/models.py:325 msgid "Date updated" msgstr "更新日期" @@ -488,7 +488,7 @@ msgstr "每行一个命令" #: authentication/templates/authentication/_access_key_modal.html:34 #: perms/forms/asset_permission.py:20 #: tickets/serializers/request_asset_perm.py:60 -#: tickets/serializers/ticket.py:26 +#: tickets/serializers/ticket.py:30 #: users/templates/users/_granted_assets.html:29 #: users/templates/users/user_asset_permission.html:44 #: users/templates/users/user_asset_permission.html:79 @@ -537,14 +537,14 @@ msgstr "默认资产组" #: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 #: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46 -#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:312 +#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:322 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 #: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models.py:185 -#: tickets/models/ticket.py:36 tickets/models/ticket.py:135 +#: tickets/models/ticket.py:30 tickets/models/ticket.py:137 #: tickets/serializers/request_asset_perm.py:61 -#: tickets/serializers/ticket.py:27 users/forms/group.py:15 +#: tickets/serializers/ticket.py:31 users/forms/group.py:15 #: users/models/user.py:157 users/models/user.py:643 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 @@ -692,7 +692,7 @@ msgstr "协议重复: {}" msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:27 +#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:26 msgid "Org name" msgstr "组织名称" @@ -1014,7 +1014,7 @@ msgid "Reason" msgstr "原因" #: audits/models.py:106 tickets/serializers/request_asset_perm.py:59 -#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:211 +#: tickets/serializers/ticket.py:29 xpack/plugins/cloud/models.py:211 #: xpack/plugins/cloud/models.py:269 msgid "Status" msgstr "状态" @@ -1199,7 +1199,7 @@ msgstr "SSH密钥" msgid "Reviewers" msgstr "审批人" -#: authentication/models.py:56 tickets/models/ticket.py:27 +#: authentication/models.py:56 tickets/models/ticket.py:23 #: users/templates/users/user_detail.html:250 msgid "Login confirm" msgstr "登录复核" @@ -1263,7 +1263,7 @@ msgstr "删除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/models/ticket.py:73 +#: templates/_modal.html:22 tickets/models/ticket.py:67 msgid "Close" msgstr "关闭" @@ -1393,7 +1393,7 @@ msgstr "%(name)s 创建成功" msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" -#: common/db/models.py:67 +#: common/db/models.py:68 msgid "Updated by" msgstr "更新人" @@ -1649,16 +1649,16 @@ msgstr "更新任务内容: {}" msgid "Disk used more than 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}" -#: orgs/api.py:54 +#: orgs/api.py:58 msgid "Organization contains undeleted resources" msgstr "" -#: orgs/api.py:58 +#: orgs/api.py:62 msgid "The current organization cannot be deleted" msgstr "" -#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40 -#: orgs/models.py:311 +#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:40 +#: orgs/models.py:321 msgid "Organization" msgstr "组织" @@ -1670,7 +1670,7 @@ msgstr "组织管理员" msgid "Organization auditor" msgstr "组织审计员" -#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:499 +#: orgs/models.py:323 users/forms/user.py:27 users/models/user.py:499 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -2506,100 +2506,108 @@ msgstr "结束日期" msgid "Args" msgstr "参数" -#: tickets/api/request_asset_perm.py:42 -msgid "Ticket closed" -msgstr "工单已关闭" - -#: tickets/api/request_asset_perm.py:45 +#: tickets/api/request_asset_perm.py:44 #, python-format msgid "Ticket has %s" msgstr "工单已%s" -#: tickets/api/request_asset_perm.py:90 +#: tickets/api/request_asset_perm.py:89 msgid "Confirm assets first" msgstr "请先确认资产" -#: tickets/api/request_asset_perm.py:93 +#: tickets/api/request_asset_perm.py:92 msgid "Confirmed assets changed" msgstr "确认的资产变更了" -#: tickets/api/request_asset_perm.py:97 +#: tickets/api/request_asset_perm.py:96 msgid "Confirm system-user first" msgstr "请先确认系统用户" -#: tickets/api/request_asset_perm.py:101 +#: tickets/api/request_asset_perm.py:100 msgid "Confirmed system-user changed" msgstr "确认的系统用户变更了" -#: tickets/api/request_asset_perm.py:104 xpack/plugins/cloud/models.py:202 +#: tickets/api/request_asset_perm.py:103 xpack/plugins/cloud/models.py:202 msgid "Succeed" msgstr "成功" -#: tickets/api/request_asset_perm.py:111 +#: tickets/api/request_asset_perm.py:110 msgid "From request ticket: {} {}" msgstr "来自工单申请: {} {}" -#: tickets/api/request_asset_perm.py:113 +#: tickets/api/request_asset_perm.py:112 msgid "{} request assets, approved by {}" msgstr "{} 申请资产,通过人 {}" -#: tickets/models/ticket.py:19 tickets/models/ticket.py:75 +#: tickets/exceptions.py:23 +msgid "Ticket closed" +msgstr "工单已关闭" + +#: tickets/exceptions.py:32 +msgid "Only assignee can operate ticket" +msgstr "只有审批人可以操作工单" + +#: tickets/exceptions.py:37 +msgid "Ticket can not be operated" +msgstr "不能操作该工单" + +#: tickets/models/ticket.py:18 tickets/models/ticket.py:69 msgid "Open" msgstr "开启" -#: tickets/models/ticket.py:20 +#: tickets/models/ticket.py:19 msgid "Closed" msgstr "关闭" -#: tickets/models/ticket.py:26 +#: tickets/models/ticket.py:22 msgid "General" msgstr "一般" -#: tickets/models/ticket.py:28 +#: tickets/models/ticket.py:24 msgid "Request asset permission" msgstr "申请资产权限" -#: tickets/models/ticket.py:33 +#: tickets/models/ticket.py:27 msgid "Approve" msgstr "同意" -#: tickets/models/ticket.py:34 +#: tickets/models/ticket.py:28 msgid "Reject" msgstr "拒绝" -#: tickets/models/ticket.py:37 tickets/models/ticket.py:136 +#: tickets/models/ticket.py:31 tickets/models/ticket.py:138 msgid "User display name" msgstr "用户显示名称" -#: tickets/models/ticket.py:39 +#: tickets/models/ticket.py:33 msgid "Title" msgstr "标题" -#: tickets/models/ticket.py:40 tickets/models/ticket.py:137 +#: tickets/models/ticket.py:34 tickets/models/ticket.py:139 msgid "Body" msgstr "内容" -#: tickets/models/ticket.py:42 +#: tickets/models/ticket.py:36 msgid "Assignee" msgstr "处理人" -#: tickets/models/ticket.py:43 +#: tickets/models/ticket.py:37 msgid "Assignee display name" msgstr "处理人名称" -#: tickets/models/ticket.py:44 +#: tickets/models/ticket.py:38 msgid "Assignees" msgstr "待处理人" -#: tickets/models/ticket.py:45 +#: tickets/models/ticket.py:39 msgid "Assignees display name" msgstr "待处理人名称" -#: tickets/models/ticket.py:76 +#: tickets/models/ticket.py:70 msgid "{} {} this ticket" msgstr "{} {} 这个工单" -#: tickets/models/ticket.py:87 +#: tickets/models/ticket.py:85 msgid "this ticket" msgstr "这个工单" diff --git a/apps/tickets/api/request_asset_perm.py b/apps/tickets/api/request_asset_perm.py index 40f908838..c12ea3070 100644 --- a/apps/tickets/api/request_asset_perm.py +++ b/apps/tickets/api/request_asset_perm.py @@ -1,4 +1,3 @@ -from django.db.transaction import atomic from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from rest_framework.decorators import action @@ -26,7 +25,7 @@ from ..permissions import IsAssignee class RequestAssetPermTicketViewSet(JMSModelViewSet): - queryset = Ticket.origin_objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM) + queryset = Ticket.origin_objects.filter(type=Ticket.TYPE.REQUEST_ASSET_PERM) serializer_classes = { 'default': serializers.RequestAssetPermTicketSerializer, 'approve': EmptySerializer, @@ -38,10 +37,10 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet): search_fields = ['user_display', 'title'] def _check_can_set_action(self, instance, action): - if instance.status == instance.STATUS_CLOSED: - raise TicketClosed(detail=_('Ticket closed')) + if instance.status == instance.STATUS.CLOSED: + raise TicketClosed if instance.action == action: - action_display = dict(instance.ACTION_CHOICES).get(action) + action_display = instance.ACTION.get(action) raise TicketActionAlready(detail=_('Ticket has %s') % action_display) @action(detail=False, methods=[GET], permission_classes=[IsValidUser]) @@ -72,7 +71,7 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet): @action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser]) def reject(self, request, *args, **kwargs): instance = self.get_object() - action = instance.ACTION_REJECT + action = instance.ACTION.REJECT self._check_can_set_action(instance, action) instance.perform_action(action, request.user, self._get_extra_comment(instance)) return Response() @@ -80,7 +79,7 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet): @action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser]) def approve(self, request, *args, **kwargs): instance = self.get_object() - action = instance.ACTION_APPROVE + action = instance.ACTION.APPROVE self._check_can_set_action(instance, action) meta = instance.meta @@ -100,10 +99,10 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet): if system_user is None: raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed')) - self._create_asset_permission(instance, assets, system_user, request.user) + self._create_asset_permission(instance, assets, system_user) return Response({'detail': _('Succeed')}) - def _create_asset_permission(self, instance: Ticket, assets, system_user, user): + def _create_asset_permission(self, instance: Ticket, assets, system_user): meta = instance.meta request = self.request @@ -120,13 +119,12 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet): if date_expired: ap_kwargs['date_expired'] = date_expired - with atomic(): - instance.perform_action(instance.ACTION_APPROVE, - request.user, - self._get_extra_comment(instance)) - ap = AssetPermission.objects.create(**ap_kwargs) - ap.system_users.add(system_user) - ap.assets.add(*assets) - ap.users.add(user) + instance.perform_action(instance.ACTION.APPROVE, + request.user, + self._get_extra_comment(instance)) + ap = AssetPermission.objects.create(**ap_kwargs) + ap.system_users.add(system_user) + ap.assets.add(*assets) + ap.users.add(instance.user) return ap diff --git a/apps/tickets/exceptions.py b/apps/tickets/exceptions.py index 3332139b5..5e5dedd21 100644 --- a/apps/tickets/exceptions.py +++ b/apps/tickets/exceptions.py @@ -1,3 +1,5 @@ +from django.utils.translation import gettext_lazy as _ + from common.exceptions import JMSException @@ -18,12 +20,19 @@ class ConfirmedSystemUserChanged(JMSException): class TicketClosed(JMSException): - pass + default_detail = _('Ticket closed') + default_code = 'ticket_closed' class TicketActionAlready(JMSException): pass -class OrgIdRequiredException(JMSException): - pass +class OnlyTicketAssigneeCanOperate(JMSException): + default_detail = _('Only assignee can operate ticket') + default_code = 'can_not_operate' + + +class TicketCanNotOperate(JMSException): + default_detail = _('Ticket can not be operated') + default_code = 'ticket_can_not_be_operated' diff --git a/apps/tickets/migrations/0003_auto_20200804_1551.py b/apps/tickets/migrations/0003_auto_20200804_1551.py new file mode 100644 index 000000000..936dbc5bb --- /dev/null +++ b/apps/tickets/migrations/0003_auto_20200804_1551.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-04 07:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0002_auto_20200728_1146'), + ] + + operations = [ + migrations.AlterField( + model_name='ticket', + name='assignee_display', + field=models.CharField(blank=True, default='', max_length=128, null=True, verbose_name='Assignee display name'), + ), + ] diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index 4f68aa6e0..3e979f244 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -5,6 +5,7 @@ from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from common.db.models import ChoiceSet from common.mixins.models import CommonModelMixin from common.fields.model import JsonDictTextField from orgs.mixins.models import OrgModelMixin @@ -13,26 +14,19 @@ __all__ = ['Ticket', 'Comment'] class Ticket(OrgModelMixin, CommonModelMixin): - STATUS_OPEN = 'open' - STATUS_CLOSED = 'closed' - STATUS_CHOICES = ( - (STATUS_OPEN, _("Open")), - (STATUS_CLOSED, _("Closed")) - ) - TYPE_GENERAL = 'general' - TYPE_LOGIN_CONFIRM = 'login_confirm' - TYPE_REQUEST_ASSET_PERM = 'request_asset' - TYPE_CHOICES = ( - (TYPE_GENERAL, _("General")), - (TYPE_LOGIN_CONFIRM, _("Login confirm")), - (TYPE_REQUEST_ASSET_PERM, _('Request asset permission')) - ) - ACTION_APPROVE = 'approve' - ACTION_REJECT = 'reject' - ACTION_CHOICES = ( - (ACTION_APPROVE, _('Approve')), - (ACTION_REJECT, _('Reject')), - ) + class STATUS(ChoiceSet): + OPEN = 'open', _("Open") + CLOSED = 'closed', _("Closed") + + class TYPE(ChoiceSet): + GENERAL = 'general', _("General") + LOGIN_CONFIRM = 'login_confirm', _("Login confirm") + REQUEST_ASSET_PERM = 'request_asset', _('Request asset permission') + + class ACTION(ChoiceSet): + APPROVE = 'approve', _('Approve') + REJECT = 'reject', _('Reject') + user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_requested', verbose_name=_("User")) user_display = models.CharField(max_length=128, verbose_name=_("User display name")) @@ -40,12 +34,12 @@ class Ticket(OrgModelMixin, CommonModelMixin): body = models.TextField(verbose_name=_("Body")) meta = JsonDictTextField(verbose_name=_("Meta"), default='{}') assignee = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_handled', verbose_name=_("Assignee")) - assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name")) + assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name"), default='') assignees = models.ManyToManyField('users.User', related_name='%(class)s_assigned', verbose_name=_("Assignees")) assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True) - type = models.CharField(max_length=16, choices=TYPE_CHOICES, default=TYPE_GENERAL, verbose_name=_("Type")) - status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open') - action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True) + type = models.CharField(max_length=16, choices=TYPE.choices, default=TYPE.GENERAL, verbose_name=_("Type")) + status = models.CharField(choices=STATUS.choices, max_length=16, default='open') + action = models.CharField(choices=ACTION.choices, max_length=16, default='', blank=True) origin_objects = models.Manager() @@ -69,30 +63,38 @@ class Ticket(OrgModelMixin, CommonModelMixin): return self.get_action_display() def create_status_comment(self, status, user): - if status == self.STATUS_CLOSED: + if status == self.STATUS.CLOSED: action = _("Close") else: action = _("Open") body = _('{} {} this ticket').format(self.user, action) self.comments.create(user=user, body=body) - def perform_status(self, status, user): - if self.status == status: - return + def perform_status(self, status, user, extra_comment=None): + self.create_comment( + self.STATUS.get(status), + user, + extra_comment + ) self.status = status + self.assignee = user + self.assignees_display = str(user) self.save() - def create_action_comment(self, action, user, extra_comment=None): - action_display = dict(self.ACTION_CHOICES).get(action) + def create_comment(self, action_display, user, extra_comment=None): body = '{} {} {}'.format(user, action_display, _("this ticket")) if extra_comment is not None: body += extra_comment self.comments.create(body=body, user=user, user_display=str(user)) def perform_action(self, action, user, extra_comment=None): - self.create_action_comment(action, user, extra_comment) + self.create_comment( + self.ACTION.get(action), + user, + extra_comment + ) self.action = action - self.status = self.STATUS_CLOSED + self.status = self.STATUS.CLOSED self.assignee = user self.assignees_display = str(user) self.save() diff --git a/apps/tickets/serializers/request_asset_perm.py b/apps/tickets/serializers/request_asset_perm.py index 3b2d72b7c..521d2582e 100644 --- a/apps/tickets/serializers/request_asset_perm.py +++ b/apps/tickets/serializers/request_asset_perm.py @@ -17,7 +17,7 @@ from ..models import Ticket class RequestAssetPermTicketSerializer(serializers.ModelSerializer): ips = serializers.ListField(child=serializers.IPAddressField(), source='meta.ips', default=list, label=_('IP group')) - hostname = serializers.CharField(max_length=256, source='meta.hostname', default=None, + hostname = serializers.CharField(max_length=256, source='meta.hostname', default='', allow_blank=True, label=_('Hostname')) system_user = serializers.CharField(max_length=256, source='meta.system_user', default='', allow_blank=True, label=_('System user')) @@ -135,7 +135,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer): def _create_body(self, validated_data): meta = validated_data['meta'] - type = dict(Ticket.TYPE_CHOICES).get(validated_data.get('type', '')) + type = Ticket.TYPE.get(validated_data.get('type', '')) date_start = dt_parser(meta.get('date_start')).strftime(settings.DATETIME_DISPLAY_FORMAT) date_expired = dt_parser(meta.get('date_expired')).strftime(settings.DATETIME_DISPLAY_FORMAT) @@ -159,7 +159,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer): def create(self, validated_data): # `type` 与 `user` 用户不可提交, - validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM + validated_data['type'] = self.Meta.model.TYPE.REQUEST_ASSET_PERM validated_data['user'] = self.context['request'].user # `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉 self._pop_confirmed_fields() diff --git a/apps/tickets/serializers/ticket.py b/apps/tickets/serializers/ticket.py index f6c995ae0..34724be3a 100644 --- a/apps/tickets/serializers/ticket.py +++ b/apps/tickets/serializers/ticket.py @@ -3,14 +3,18 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from .. import models +from ..exceptions import ( + TicketClosed, OnlyTicketAssigneeCanOperate, + TicketCanNotOperate +) +from ..models import Ticket, Comment __all__ = ['TicketSerializer', 'CommentSerializer'] class TicketSerializer(serializers.ModelSerializer): class Meta: - model = models.Ticket + model = Ticket fields = [ 'id', 'user', 'user_display', 'title', 'body', 'assignees', 'assignees_display', 'assignee', 'assignee_display', @@ -32,17 +36,33 @@ class TicketSerializer(serializers.ModelSerializer): return super().create(validated_data) def update(self, instance, validated_data): - action = validated_data.get("action") - user = self.context["request"].user + action = validated_data.get('action') + user = self.context['request'].user + + if instance.type not in (Ticket.TYPE.GENERAL, + Ticket.TYPE.LOGIN_CONFIRM): + # 暂时的兼容操作吧,后期重构工单 + raise TicketCanNotOperate + + if instance.status == instance.STATUS.CLOSED: + raise TicketClosed + + if action: + if user not in instance.assignees.all(): + raise OnlyTicketAssigneeCanOperate + + # 有 `action` 时忽略 `status` + validated_data.pop('status', None) + + instance = super().update(instance, validated_data) + if not instance.status == instance.STATUS.CLOSED and action: + instance.perform_action(action, user) + else: + status = validated_data.get('status') + instance = super().update(instance, validated_data) + if status: + instance.perform_status(status, user) - if action and user not in instance.assignees.all(): - error = {"action": "Only assignees can update"} - raise serializers.ValidationError(error) - if instance.status == instance.STATUS_CLOSED: - validated_data.pop('action') - instance = super().update(instance, validated_data) - if not instance.status == instance.STATUS_CLOSED and action: - instance.perform_action(action, user) return instance @@ -65,7 +85,7 @@ class CommentSerializer(serializers.ModelSerializer): ) class Meta: - model = models.Comment + model = Comment fields = [ 'id', 'ticket', 'body', 'user', 'user_display', 'date_created', 'date_updated' diff --git a/apps/tickets/utils.py b/apps/tickets/utils.py index 97b5334e0..152b5182b 100644 --- a/apps/tickets/utils.py +++ b/apps/tickets/utils.py @@ -20,7 +20,7 @@ def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees): subject = '{}: {}'.format(_("New ticket"), ticket.title) # 这里要设置前端地址,因为要直接跳转到页面 - if ticket.type == ticket.TYPE_REQUEST_ASSET_PERM: + if ticket.type == ticket.TYPE.REQUEST_ASSET_PERM: detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/request-asset-perm/{ticket.id}') else: detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/{ticket.id}')