From 2fb4cba8785430d41a388dea86d2a08c70f15218 Mon Sep 17 00:00:00 2001 From: Maxim Beder Date: Fri, 25 Apr 2025 19:08:53 +0400 Subject: [PATCH 1/3] feat: update certificate icon Old certificates icon contained edX trademark logo, which was not suitable for the open source repos. Replaced with the icon that contains Open edX logo. --- lms/static/images/programs/sample-cert.png | Bin 8316 -> 8459 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lms/static/images/programs/sample-cert.png b/lms/static/images/programs/sample-cert.png index 09de8897d3a2962ced164885a2e820711ef363fd..4639a97f8cd08d9378ed0ee51d4ff81da485337e 100644 GIT binary patch literal 8459 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 literal 8316 zcmY+KcQl+&*!Oj-#jdheXZ5~%LbPRtD2W=YMD!M-i!LlyZ^0rVdLm1ruO1s^l|=8o z1W`kRAU__@AMbO{`_J5S&dgkM%{4RMbDhuJ36BjQ&{4Bd6A=;7>FQ`16A=;9-qw2n z6t|_jI`{SMht(fp;cxQ7*+0n6*NI5O@rAt;kFKYki<7aFonx@ifYW^Q?a0z2@~Hky zbtLP}V(5s9OHugQ(>>(TRXg`y@={ySN`}+iK^_kPPBs0L0T9iD2a;)^cmdG>O&}R4 zjt2mx)c}GS_8e})WG9~(bzM$< zs(WG^-gtKP{q9Ec{;=%GBsr5Ez@%I3Q+uY`(~zI#|BiBPBZ7apFlM4N`Y%s~Dk=H# zD=u#Xrtw*c08JfmIuX4%#nn9~9&SE?tKj|F+Uy%mwzDXmWK%6jG;EbD7V`HV|2?^Y z(0z^+9UeL?16MksHDK?yakQNLcs)rfHJWEPoshsNbkfC}OvkRUNrr!P8xFb8(3k2= zHtu&sNUDGQJ>C$D)MT^Z`p5C{3W^7Y#3MmXCuej|K4U$O|LEFVQ*u2>~DW2-cRLH$G zUSi(eeVy*Qxlzy~8LTe+@A<>T_7QaUQIp$5#hd%ajY?V&xX?UVr&5~2G+7pEYMRch z0wAbBpR1p&EddJf z(S+Gv9U2UL#gC=$Z+$D+-m3cFb;?l}2xA*ehC`m~aREOgfoAgbNj1MB&cP>_TR*qhdbSJ%GAi(O4vF#R2KSDHv`GtRxMum3{@Tnw@U_%(|f`>7-v_=}e>Y&&bj6^4~ne$<~-F?0KrlQ{s zU&QH4T-}fXoSzgAB-`JdRNM+24I- zQy@Z;|6EVW^OK1P5JP&-3tCUGqBCRJoa!19NUnr$+ zujsyAnIU*}-R1>^8L7^Yj(8%*s;U;#;dO}0Kn92~am##+S{&>#u z5vZY;4=eolDZCUn@FHTPhX4E5vvza_A~z(K?ocTtI)B%6<}OgOjoQcRyEd|ccKoFX zTvV5_Zy994^5puvRn}d4z2$^g34^QbiEk1=TJK8yF(ETwppj}HMV4|LcEvt*s1Z1y z3Myh~h7~?szpomyK{xmPVBut?>(7^%xsz_q%B2i4EX^V?Y!ZU4H(@bHOf~rOB?g1B z2D&RzWo>})g^rhA7vBE*7lCDuZhf6qjLan~!EI-8k3_}`AO1TD*F{3iMr*Qe@+OeKF@@``aTX9br>#&y`*6K#?_mB3LD7d04g58#jlWRl2gqR~3(lXKL4> zG9LPGRt>GNsZ`QOtiOn>2{btSb>5Y}6gZJGryOyU_;ete zIEKbpK+lgbI4y!lH&NfS>Y_nc7`#Yr7vQmXMU3m5eOJxULG?7kwgL>DZSMbi8}C;) zdzDq>9C?!3>=JqN=w?w233L5emR&$HvdWBXAf3q!eE@0Lx3rv(e0%+ocYM|w8n*z^ zIXkSdx5KgZwtpXBHXk!;`fgsr;#@O&HphREBo0gaxWl|jwZGWnXUAA`87*LKHYWO6 z$KEKds;Jr2xSQIIe=pNCA)QADEKi{rZ8Q*#G3a}{V5qHLI2Xwi8gg@;ebaIH@As1u zK3B?sv?Y1BrE3=MfouOQl8%k@K$n}dS^9dZnC0vdF%az)%!abKV?`5{3 zEr+82;F7q`ihCq`QzNmezs=i@9FcA|8CO% z+i`j17UU+D-0gWse6)US1VVHY(lB&iEsFWG|u<)!d z4xcyafB*Vd7g@bj@b%b><)U!+CQFt9yx{K<=cDb)J!;%G>My!??Q7-r312zb0R3F` zZ$g)iCOFLH`-B3RZ~I2SvTw@0{qpA9oW4^?WLK0Wug7J_^O~R7Bo(s zsMWoPxxHf8OU3Wv6oL?Xowd`M&hTvZS4rK!x147;<7o@8k4Re(cKuP9LLWgXcqt7Q z^CIke!lSy#+6y5kg08UuRNqYI^P4}yyYvM=fE64Jyar61^(FRc_i3C ziTbtmyiJd!jE&e_dD|@xDCTOYh zXG*GC-(2vIJU7lQpr${lZhx8bT6#g&kHuojhNeu%K5c(aR=KZ#MnB>@&Yu8~SZ6P~ zN}f6CPC4n|0#5fb(OrCMI*~iI+BTaLm7_c_en{?aHU29=h&@&h&) zurzhu@X`0bLC4d~UXfJ>CO@&TPaBZ)hzYfU7oEoxQfqeP%&%e@hkoGxHAjl=HkSy< zaRSZ6tP-Np;^Wa~@A^=KRbI|>+x*pbe@j4Hg0l~g_-KND1f7x;HAM+93_*X$TBoch zgkPh-kNtr#qqEE1-o`PfXJ>Z@-A3XMP1~d5$1knU`A2@eL9*d`e}dwK z;OmyuI}F^Xn19mNKY+u0pltMwYp)W)?G)6@l$r~PVKt*gb@ zZmB@>pTW^@Pp$8Dt-y@lmqvj{gC(9$1m&j6v+|^@kvf>CZ^ST2UXNs|Lr%Jl;JfQ`M>qXfiVyUh!XxeY*?Bs@AH4p-FD(aR)K$G0co!3r*Ey$63(i zI!rPdhIrV#f}W`k_ZVC*B$i9X zQjd%?HtyBYql)c^ZwWEypC+R=9cw*iM3-JAMC4{Md@tLEfKz`orga_Ly7bRxoo7c` zV<~zN%t2&H;r!fGQzUmQ(K0GUR;EsrO z$bZ}v9%_;;*J&p+OnMW3Nt=_QPg-;`fLOCmb8@ZoLyu3@PBs>?t|m|TQSI9r53G)< zHq?>M-exoWc>)!QI6{xYaXM9~Mv65RsTVPQs9LdQ?lZLGYlN)fE~o9uE(0*lO#%T7 zsb(;5^mdbAExFAz7fTa1G34F2#lu~jK|d3}1u5v#XOD%n55T(8g;p|3V{5pf;5Ph&W)+iS3#hc;h({10qEU4;10u@%AGPRbDer#R z@}s`U_CmEdKbw8Us&nY-P;Yts<;?^}Y+O!GY;uCeSUWEkxs}ha0eN)D-d{0fLD~6B zBwwc%vtOgGMOK$ko?%iPa&~r*qGUDZrMj8eFu=gP>qh>e8Meux87{|@#F*hplj*Gs z@QBM_zjFYNg%@c#hJipj(eL$*&?(uSjImTL&nDC4@@AWEoB^Jef2ks#5&M2hoLN?~ zpIVxWaGlvqmMuKLrhm)#^BX4V;Fs})5nM%rh|U{+!AF1pkN4+4oMThlaUH=kh!iXh z#dE@IEt(9mafwBnNs*u%Dl1n{DImD(ZMZrc2J<)~}hULN}6O@Rqxs6knjp)L@Dhu#1!U5UIQ3Q zweKl)FHnmjw{KxpJ#fBQ?KCTz>1hRA3}C*_gWsuOel?LI+0`M7S#>tUab!94D?>A` z^H|!TZjc>DdA(MWCqqbiHmb#)ZlJeqmr*+}fVaW=m5Xy|uDRvZ?TA1FLf5Yr9)yIZ z+zCMSIwTGWKOPkN|R^bpSJknXdQ)YM`b2iRb`H00QbR=a6eGpfM8&8M$L@tBoR zdt#GENyuB=SlIy5_4NaZ;K8;pJ!Y9XqmzT^^5pH1c*zMEz>L^8X7_TC*OpOO2HMal zWAF7D;6Max(FAKV?m_*<{b>-|`NiMjdQC_igeQZ=2HuW<2RIY*qNH=^kz(VL`5E`x zUXAfVr{J~S)yZfpC>~RWQirOnjlGdL1A^Yu@vi4xXJlg4bOQVOltB3l{s= z_0v4sWL&0w#N+SqgD%%hT}Yj2zAJAMHTsJHsosQS&O1%ET7!4s-a()p|9YxiExbyU3&tP56Ec`ff z<@6yoTI0W31fa=JN8Y#y{4ho#XHIqe{rxBPFfI#-i!8aH4_ zPOG?kE&%+&le1b`ti$9#h-Gg_18u#$6XimLw zDZ^;<*xql^Rfz=w&uUWI)+;85g8(#|C~26ToDXj+7Lg}vhl?rrm)J>toGiC^bvFQH zijpRRUm|Q2p$ZTL#6##euq~PH^qxp&9H3OF2Q?^(esD;zOew~loU6Q=PyFD30ywDe zl~CEC&<{{PY;t#eOCg#ytx0gUI@s1bTRB#XI@r4U?3-;a@o*2p51_a=t6ac`b?)|( zK_Z2Qo~)cmf|V|39+#f0h45 ze7EF_CmEz``cd_K(J#w=uCu*;KfLfQZa3=t#{OdCRz`>~d{LCe8xr+6II zM!(v%PvGj8R|D3QkFSE+Iwp0Op~S-vWH$XqovNr*|ED)u1cYujAv!*Y8Q8lAY;(Sq@ZiD}un! zjS`BU!O_%1gH(ZBElhkYFMfyE=B#5!R7(s1P1XW2h-xlyDexu>_A{7ouyp-JobA_- z?7|5Gx->>%AdQ{fmjysORr9yA6R*J%plWZq@-`m;oXR#To47^sSL#J`*(0|&i z;|aU8PUnRRXnn2_e5eTyTJ(8veWko|VOXnysMCpq@g#{%W!_@pFfdW^RBP57;eqC}u3Oj-T>#T%ZX1l;zeQ4b~cNRQ`k}LAx@&2CeO-N1g z?T-5T=M}TkRQt*{hHI$Q5jC$wT&-@a>qRRcwaYHt24v8W#l}Aapt;jD&V*lmsk(>S zSjz|4ea`<4TaB~6g{Couq|;=mAC}BBHbG1832>A&Wp0mnOl$W0_T>i((B!)oV_jEE zM>YRCJQtd}t7*iO7QEU#vklKX&pV`)Z_fn+Vh}~GR-S5gsD5nV3l%^Y1*m&c2MDO@ zIo~p@j_`Qtq$+K2I%zq0 z4XhcsRL7Y1Xg?w>^uRk5(>dNdW^V`RjaWZ>g=Mg!N%RO)NMjz_B4TDPD3NfEm#9iiJuct&+3kwUzD^Q{giRX zAM^00zSv&h6-1P*Mgrcoj}T)0`jvb%KWjb+%xr{JG<{Py>IcYANPi@PzAxEO2k??a z>+J4AO$2F-e|yGm2Vak9`3oJYoG;ezsu-ZVmtRcNFsf*Q?^7CK3hZyyuF^LAbV{sj zRonC>-OYn5h)fm3Aj`M{sY=XiFK$+bB^dtMW7Cv@{>4lLKP@*Brr|lxh9R7>W#TMa zg1-x@Zbo}O_P6>>0V!yoj@Ujutj*gD^OC-oypkgRmzzZRXyYCl?@xXH(%EP=LY?XN zLmPT0{vm;%thG$Xf=b#1iM5x&3FuS~&XIU+*>_0d!qM@BofRgXhiZ?@e!K1I3%{eU zBPe^QVWfwCbq@cNPM&7pNl7C1>6IhT9c6Bv63G0I01GuQQ5^#>;Wg|+LgX!0;l1Tp3VDc)h1n%mg;@zRxr7jF4vjAI0R z31?I00`iGXd6{oi*Lw#j`kch;zLfT=CABBVo5Er@s+G?{EMvo*>Q zXcF$s=QZ$}Dn{3}*x1|$U2IXg#t?&K3vba%XV@mvwGNzfy7=uphIuyNvENe6$Jtn& zr1OiH6_0VRs7!jy$l)u$8xgt4q4QO!;)G+VrxNFpZ>2?{0b{ixsnu07ttK>go>rSh zYI5LsEf~_tI6LmDDZ3Qv3B<@YM8%e5o9ln@q@aKc8@x&A@Z0`A-yg#SSIm{TQvsId z#eZ@1&w7_=M0K^&PNqrtLg>bFDN8Xbn>0(p?JFhJeRV0|;y%+Gd)w`v;GEm9OLR&DTs^5CWXBk7ho@h*{68t zql=o<5=N@m{DA8HofB*>U#7l4?icRR^4 zU7c2O2C-&#me0a^QQuQQ=VmDB2hIF_*9Nt> zuNFH#jT>#&85m%vkAkE_%}0QP;cCSnJEtB6S4nnp{2!2Lv^cx+?Rf(xSvSwthzS+equqstG>1xhWNrN*%ZOdV7kB#xlgk>K-7HnVTH6GjOD zhlX60KHL*D${MZZEt?(WOXfzuk=A3V1EkP{v`Yx&l~LJa3O}i7_@-H6y5-KwBzgA( z`x-?3lUk9z&}0-gIellcY4`QzYfNGHI|es>n#Bo>@({+=*p?r!1E?(%BO6r+AuXP+ zi{OB!{7gseKg~hDg(hbZ{f3!eFAFM?!gTD}6=X-5yxuAxEeIW{Uc7i6h!qsdBBNsd!YhjM$E#k_Sc-i6>!VMeq<2ps=Rr zd8@FQ%joNjIJuN#qM`czr*jz zc`ZzvpfZGi$tpfbeATLRPy=U6HxV~^jG@P#;PlH5qs60ASFpJ3t7=>?R?@N0KWKvl9l8wQbvT@QHBiX3fYBA`qpdv9qr5~z ztIJZ^E~Mn(ulK$OtWt~pf=k<)t-Yd8kj=c|umnW}yc$zsa4zQtXT$nhsF=+2i#WmD zT9%zc$XlOeUp>hd50iWkaDawTm}|SHCA5MKvY8V#l4<ww1v6noXuFt4DfdSbxa!m54&u$uMnr4Sv7dvT zC8!T@nmqc2kk#rOgZ=1AVg4;gM~yCqs-oZO&mquoMHAbkNlBlR`56?N{q&8QTAMy! zg0^nNpRXNrCRY(PZJ7C8*@Aac2nt5h1pP5da}IVLoU$1qYJ|Vkd$LfuGMJRR z(}=)P2J@96`@b?2H6AmlVT;7@&LzErY9;$~GB`RzyKrz}t*p_(jCE1l6`Fd8F8Snv zj1^CQ=ct(aM$TR=#b91CfSj+YO6<*B&6#a!^1aXB<<~-jJf)`vl8oszb+`j%9coBo zO7^;)Nk8nq%k4EhpY#j2bC|XOGJJ01du3z zX4zO?fpL)itVfDm1ryTE-r$b`69QkO2VT7)hY-b$YXA2#$R*+ScLE|^grQaq{8{w> E0IDIK+W-In From c0bea2b3c5b3bc2992a30c26036513355a1b43d0 Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Tue, 29 Apr 2025 11:08:00 +0200 Subject: [PATCH 2/3] fix: unit xblock adaptivity and small visual enhancements --- .../sass/course-unit-mfe-iframe-bundle.scss | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 7a75cc53da84..4100310f406b 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -8,7 +8,7 @@ html { body { - min-width: 800px; + min-width: 560px; background: transparent; &.openassessment_full_height.view-container { overflow-y: hidden; @@ -39,11 +39,19 @@ body, padding: ($baseline * 1.2) ($baseline * 1.2) ($baseline / 1.67); border-bottom: none; - .header-details .xblock-display-name { - font-size: 22px; - line-height: 28px; - font-weight: 700; - color: $black; + .header-details { + .xblock-display-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .xblock-display-name { + font-size: 22px; + line-height: 28px; + font-weight: 700; + color: $black; + } } } @@ -345,7 +353,6 @@ body, } .tip.setting-help { - color: $border-color; font-size: 14px; line-height: $base-font-size; } @@ -452,6 +459,11 @@ body, .modal-lg.modal-window.confirm.openassessment_modal_window { height: 635px; + max-height: 100vh; + + .edit-xblock-modal .modal-content { + max-height: 100%; + } } // Additions for the xblock editor on the Library Authoring @@ -672,11 +684,21 @@ body [class*="view-"] .openassessment_editor_buttons.xblock-actions { max-width: 1200px; } - .modal-lg.modal-editor .modal-header .editor-modes .action-item { - .editor-button, - .settings-button { - @extend %light-button; - } + .modal-lg.modal-editor { + .modal-header .editor-modes .action-item { + .editor-button, + .settings-button { + @extend %light-button; + } + } + + .edit-xblock-modal .modal-content { + max-height: calc(100vh - 144px); + + .editor-with-buttons.wrapper-comp-settings .list-input.settings-list { + max-height: calc(100vh - 205px); + } + } } .wrapper.wrapper-modal-window .modal-window .modal-actions .action-primary { From e0df51294a451f6ad0106cc8bdff86bc4d856b1f Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 30 Apr 2025 20:40:52 +0000 Subject: [PATCH 3/3] refactor: update sync model helper function docs and minor cleanup (#36599) * refactor: update sync model helper function docs Adds some comments to explain certain confusing sections * refactor: sync library content method * feat: use edited_on block field * test: modified field in course block index * fix: extract uncommon methods to child class from base --- cms/djangoapps/contentstore/models.py | 171 ++++++++++++------ cms/djangoapps/contentstore/utils.py | 7 +- .../xblock_storage_handlers/view_handlers.py | 4 +- .../djangoapps/content/search/documents.py | 2 + .../content/search/tests/test_api.py | 36 ++-- .../content/search/tests/test_documents.py | 35 ++-- .../content/search/tests/test_handlers.py | 15 +- 7 files changed, 169 insertions(+), 101 deletions(-) diff --git a/cms/djangoapps/contentstore/models.py b/cms/djangoapps/contentstore/models.py index 24dc6748d2c7..9782a37057c1 100644 --- a/cms/djangoapps/contentstore/models.py +++ b/cms/djangoapps/contentstore/models.py @@ -106,6 +106,34 @@ class EntityLinkBase(models.Model): class Meta: abstract = True + +class ComponentLink(EntityLinkBase): + """ + This represents link between any two publishable entities or link between publishable entity and a course + XBlock. It helps in tracking relationship between XBlocks imported from libraries and used in different courses. + """ + upstream_block = models.ForeignKey( + Component, + on_delete=models.SET_NULL, + related_name="links", + null=True, + blank=True, + ) + upstream_usage_key = UsageKeyField( + max_length=255, + help_text=_( + "Upstream block usage key, this value cannot be null" + " and useful to track upstream library blocks that do not exist yet" + ) + ) + + class Meta: + verbose_name = _("Component Link") + verbose_name_plural = _("Component Links") + + def __str__(self): + return f"ComponentLink<{self.upstream_usage_key}->{self.downstream_usage_key}>" + @property def upstream_version_num(self) -> int | None: """ @@ -177,34 +205,6 @@ def summarize_by_downstream_context(cls, downstream_context_key: CourseKey) -> Q ) return result - -class ComponentLink(EntityLinkBase): - """ - This represents link between any two publishable entities or link between publishable entity and a course - XBlock. It helps in tracking relationship between XBlocks imported from libraries and used in different courses. - """ - upstream_block = models.ForeignKey( - Component, - on_delete=models.SET_NULL, - related_name="links", - null=True, - blank=True, - ) - upstream_usage_key = UsageKeyField( - max_length=255, - help_text=_( - "Upstream block usage key, this value cannot be null" - " and useful to track upstream library blocks that do not exist yet" - ) - ) - - class Meta: - verbose_name = _("Component Link") - verbose_name_plural = _("Component Links") - - def __str__(self): - return f"ComponentLink<{self.upstream_usage_key}->{self.downstream_usage_key}>" - @classmethod def update_or_create( cls, @@ -232,25 +232,15 @@ def update_or_create( 'version_declined': version_declined, } if upstream_block: - new_values.update( - { - 'upstream_block': upstream_block, - } - ) + new_values['upstream_block'] = upstream_block try: link = cls.objects.get(downstream_usage_key=downstream_usage_key) - # TODO: until we save modified datetime for course xblocks in index, the modified time for links are updated - # everytime a downstream/course block is updated. This allows us to order links[1] based on recently - # modified downstream version. - # pylint: disable=line-too-long - # 1. https://github.com/open-craft/frontend-app-course-authoring/blob/0443d88824095f6f65a3a64b77244af590d4edff/src/course-libraries/ReviewTabContent.tsx#L222-L233 - has_changes = True # change to false once above condition is met. - for key, value in new_values.items(): - prev = getattr(link, key) - # None != None is True, so we need to check for it specially - if prev != value and ~(prev is None and value is None): + has_changes = False + for key, new_value in new_values.items(): + prev_value = getattr(link, key) + if prev_value != new_value: has_changes = True - setattr(link, key, value) + setattr(link, key, new_value) if has_changes: link.updated = created link.save() @@ -290,6 +280,77 @@ class Meta: def __str__(self): return f"ContainerLink<{self.upstream_container_key}->{self.downstream_usage_key}>" + @property + def upstream_version_num(self) -> int | None: + """ + Returns upstream container version number if available. + """ + published_version = get_published_version(self.upstream_container.publishable_entity.id) + return published_version.version_num if published_version else None + + @property + def upstream_context_title(self) -> str: + """ + Returns upstream context title. + """ + return self.upstream_container.publishable_entity.learning_package.title + + @classmethod + def filter_links( + cls, + **link_filter, + ) -> QuerySet["EntityLinkBase"]: + """ + Get all links along with sync flag, upstream context title and version, with optional filtering. + """ + ready_to_sync = link_filter.pop('ready_to_sync', None) + result = cls.objects.filter(**link_filter).select_related( + "upstream_container__publishable_entity__published__version", + "upstream_container__publishable_entity__learning_package" + ).annotate( + ready_to_sync=( + GreaterThan( + Coalesce("upstream_container__publishable_entity__published__version__version_num", 0), + Coalesce("version_synced", 0) + ) & GreaterThan( + Coalesce("upstream_container__publishable_entity__published__version__version_num", 0), + Coalesce("version_declined", 0) + ) + ) + ) + if ready_to_sync is not None: + result = result.filter(ready_to_sync=ready_to_sync) + return result + + @classmethod + def summarize_by_downstream_context(cls, downstream_context_key: CourseKey) -> QuerySet: + """ + Returns a summary of links by upstream context for given downstream_context_key. + Example: + [ + { + "upstream_context_title": "CS problems 3", + "upstream_context_key": "lib:OpenedX:CSPROB3", + "ready_to_sync_count": 11, + "total_count": 14 + }, + { + "upstream_context_title": "CS problems 2", + "upstream_context_key": "lib:OpenedX:CSPROB2", + "ready_to_sync_count": 15, + "total_count": 24 + }, + ] + """ + result = cls.filter_links(downstream_context_key=downstream_context_key).values( + "upstream_context_key", + upstream_context_title=F("upstream_container__publishable_entity__learning_package__title"), + ).annotate( + ready_to_sync_count=Count("id", Q(ready_to_sync=True)), + total_count=Count('id') + ) + return result + @classmethod def update_or_create( cls, @@ -317,25 +378,15 @@ def update_or_create( 'version_declined': version_declined, } if upstream_container: - new_values.update( - { - 'upstream_container': upstream_container, - } - ) + new_values['upstream_container'] = upstream_container try: link = cls.objects.get(downstream_usage_key=downstream_usage_key) - # TODO: until we save modified datetime for course xblocks in index, the modified time for links are updated - # everytime a downstream/course block is updated. This allows us to order links[1] based on recently - # modified downstream version. - # pylint: disable=line-too-long - # 1. https://github.com/open-craft/frontend-app-course-authoring/blob/0443d88824095f6f65a3a64b77244af590d4edff/src/course-libraries/ReviewTabContent.tsx#L222-L233 - has_changes = True # change to false once above condition is met. - for key, value in new_values.items(): - prev = getattr(link, key) - # None != None is True, so we need to check for it specially - if prev != value and ~(prev is None and value is None): + has_changes = False + for key, new_value in new_values.items(): + prev_value = getattr(link, key) + if prev_value != new_value: has_changes = True - setattr(link, key, value) + setattr(link, key, new_value) if has_changes: link.updated = created link.save() diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index ce244f616ddb..21270a188aba 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -2429,8 +2429,5 @@ def create_or_update_xblock_upstream_link(xblock, course_key: CourseKey, created _create_or_update_component_link(course_key, created, xblock) except InvalidKeyError: # It is possible that the upstream is a container and UsageKeyV2 parse failed - # Create upstream container link - try: - _create_or_update_container_link(course_key, created, xblock) - except InvalidKeyError: - log.error(f"Invalid key: {xblock.upstream}") + # Create upstream container link and raise InvalidKeyError if xblock.upstream is a valid key. + _create_or_update_container_link(course_key, created, xblock) diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index 54beb783f3b0..31a17466769d 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -548,8 +548,7 @@ def sync_library_content(downstream: XBlock, request, store) -> StaticFileNotice notices = [] # Store final children keys to update order of components in unit children = [] - for i in range(len(upstream_children)): - upstream_child = upstream_children[i] + for i, upstream_child in enumerate(upstream_children): assert isinstance(upstream_child, LibraryXBlockMetadata) # for now we only support units if upstream_child.usage_key not in downstream_children_keys: # This upstream_child is new, create it. @@ -574,6 +573,7 @@ def sync_library_content(downstream: XBlock, request, store) -> StaticFileNotice for child in downstream_children: if child.usage_key not in children: # This downstream block was added, or deleted from upstream block. + # NOTE: This will also delete any local additions to a unit in the next upstream sync. store.delete_item(child.usage_key, user_id=request.user.id) downstream.children = children store.update_item(downstream, request.user.id) diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index 6a40f049bf96..134899a819c2 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -212,6 +212,8 @@ class implementation returns only: Fields.access_id: _meili_access_id_from_context_key(block.usage_key.context_key), Fields.breadcrumbs: [], } + if hasattr(block, "edited_on"): + block_data[Fields.modified] = block.edited_on.timestamp() # Get the breadcrumbs (course, section, subsection, etc.): if block.usage_key.context_key.is_course: # Getting parent is not yet implemented in Learning Core (for libraries). cur_block = block diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index ee70bd444721..46354d6eb3cd 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -61,19 +61,27 @@ def setUp(self): # Clear the Meilisearch client to avoid side effects from other tests api.clear_meilisearch_client() + modified_date = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc) # Create course - self.course = self.store.create_course( - "org1", - "test_course", - "test_run", - self.user_id, - fields={"display_name": "Test Course"}, - ) - course_access, _ = SearchAccess.objects.get_or_create(context_key=self.course.id) - self.course_block_key = "block-v1:org1+test_course+test_run+type@course+block@course" - - # Create XBlocks - self.sequential = self.store.create_child(self.user_id, self.course.location, "sequential", "test_sequential") + with freeze_time(modified_date): + self.course = self.store.create_course( + "org1", + "test_course", + "test_run", + self.user_id, + fields={"display_name": "Test Course"}, + ) + course_access, _ = SearchAccess.objects.get_or_create(context_key=self.course.id) + self.course_block_key = "block-v1:org1+test_course+test_run+type@course+block@course" + + # Create XBlocks + self.sequential = self.store.create_child( + self.user_id, + self.course.location, + "sequential", + "test_sequential" + ) + self.store.create_child(self.user_id, self.sequential.location, "vertical", "test_vertical") self.doc_sequential = { "id": "block-v1org1test_coursetest_runtypesequentialblocktest_sequential-f702c144", "type": "course_block", @@ -90,8 +98,8 @@ def setUp(self): ], "content": {}, "access_id": course_access.id, + "modified": modified_date.timestamp(), } - self.store.create_child(self.user_id, self.sequential.location, "vertical", "test_vertical") self.doc_vertical = { "id": "block-v1org1test_coursetest_runtypeverticalblocktest_vertical-e76a10a4", "type": "course_block", @@ -112,6 +120,7 @@ def setUp(self): ], "content": {}, "access_id": course_access.id, + "modified": modified_date.timestamp(), } # Make sure the CourseOverview for the course is created: CourseOverview.get_from_id(self.course.id) @@ -130,7 +139,6 @@ def setUp(self): self.problem1 = library_api.create_library_block(self.library.key, "problem", "p1") self.problem2 = library_api.create_library_block(self.library.key, "problem", "p2") # Update problem1, freezing the date so we can verify modified date serializes correctly. - modified_date = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc) with freeze_time(modified_date): library_api.set_library_block_olx(self.problem1.usage_key, "") diff --git a/openedx/core/djangoapps/content/search/tests/test_documents.py b/openedx/core/djangoapps/content/search/tests/test_documents.py index 0a5d871fbb96..b70594eff0eb 100644 --- a/openedx/core/djangoapps/content/search/tests/test_documents.py +++ b/openedx/core/djangoapps/content/search/tests/test_documents.py @@ -52,23 +52,23 @@ class StudioDocumentsTest(SharedModuleStoreTestCase): def setUpClass(cls): super().setUpClass() cls.store = modulestore() - cls.org = Organization.objects.create(name="edX", short_name="edX") - cls.toy_course = ToyCourseFactory.create() # See xmodule/modulestore/tests/sample_courses.py - cls.toy_course_key = cls.toy_course.id - - # Get references to some blocks in the toy course - cls.html_block_key = cls.toy_course_key.make_usage_key("html", "toyjumpto") - # Create a problem in course - cls.problem_block = BlockFactory.create( - category="problem", - parent_location=cls.toy_course_key.make_usage_key("vertical", "vertical_test"), - display_name='Test Problem', - data="What is a test?", - ) - # Create a library and collection with a block - created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) - with freeze_time(created_date): + cls.created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) + with freeze_time(cls.created_date): + # Get references to some blocks in the toy course + cls.org = Organization.objects.create(name="edX", short_name="edX") + cls.toy_course = ToyCourseFactory.create() # See xmodule/modulestore/tests/sample_courses.py + cls.toy_course_key = cls.toy_course.id + + cls.html_block_key = cls.toy_course_key.make_usage_key("html", "toyjumpto") + # Create a problem in course + cls.problem_block = BlockFactory.create( + category="problem", + parent_location=cls.toy_course_key.make_usage_key("vertical", "vertical_test"), + display_name='Test Problem', + data="What is a test?", + ) + cls.library = library_api.create_library( org=cls.org, slug="2012_Fall", @@ -190,6 +190,7 @@ def test_problem_block(self): 'usage_key': 'block-v1:edX+toy+2012_Fall+type@vertical+block@vertical_test', }, ], + "modified": self.created_date.timestamp(), "content": { "capa_content": "What is a test?", "problem_types": ["multiplechoiceresponse"], @@ -223,6 +224,7 @@ def test_html_block(self): "display_name": "Text", "description": "This is a link to another page and some Chinese 四節比分和七年前 Some " "more Chinese 四節比分和七年前 ", + "modified": self.created_date.timestamp(), "breadcrumbs": [ { 'display_name': 'Toy Course', @@ -276,6 +278,7 @@ def test_video_block_untagged(self): }, ], "content": {}, + "modified": self.created_date.timestamp(), # This video has no tags. } diff --git a/openedx/core/djangoapps/content/search/tests/test_handlers.py b/openedx/core/djangoapps/content/search/tests/test_handlers.py index 95b2ecb6f52a..33d0e4db8378 100644 --- a/openedx/core/djangoapps/content/search/tests/test_handlers.py +++ b/openedx/core/djangoapps/content/search/tests/test_handlers.py @@ -59,7 +59,9 @@ def test_create_delete_xblock(self, meilisearch_client): course_access, _ = SearchAccess.objects.get_or_create(context_key=course.id) # Create XBlocks - sequential = self.store.create_child(self.user_id, course.location, "sequential", "test_sequential") + created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) + with freeze_time(created_date): + sequential = self.store.create_child(self.user_id, course.location, "sequential", "test_sequential") doc_sequential = { "id": "block-v1orgatest_coursetest_runtypesequentialblocktest_sequential-0cdb9395", "type": "course_block", @@ -76,10 +78,11 @@ def test_create_delete_xblock(self, meilisearch_client): ], "content": {}, "access_id": course_access.id, - + "modified": created_date.timestamp(), } meilisearch_client.return_value.index.return_value.update_documents.assert_called_with([doc_sequential]) - vertical = self.store.create_child(self.user_id, sequential.location, "vertical", "test_vertical") + with freeze_time(created_date): + vertical = self.store.create_child(self.user_id, sequential.location, "vertical", "test_vertical") doc_vertical = { "id": "block-v1orgatest_coursetest_runtypeverticalblocktest_vertical-011f143b", "type": "course_block", @@ -100,6 +103,7 @@ def test_create_delete_xblock(self, meilisearch_client): ], "content": {}, "access_id": course_access.id, + "modified": created_date.timestamp(), } meilisearch_client.return_value.index.return_value.update_documents.assert_called_with([doc_vertical]) @@ -107,11 +111,14 @@ def test_create_delete_xblock(self, meilisearch_client): # Update the XBlock sequential = self.store.get_item(sequential.location, self.user_id) # Refresh the XBlock sequential.display_name = "Updated Sequential" - self.store.update_item(sequential, self.user_id) + modified_date = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc) + with freeze_time(modified_date): + self.store.update_item(sequential, self.user_id) # The display name and the child's breadcrumbs should be updated doc_sequential["display_name"] = "Updated Sequential" doc_vertical["breadcrumbs"][1]["display_name"] = "Updated Sequential" + doc_sequential["modified"] = modified_date.timestamp() meilisearch_client.return_value.index.return_value.update_documents.assert_called_with([ doc_sequential, doc_vertical,