From a1517794fdb3525c20f893022d18010f23f5c9fe Mon Sep 17 00:00:00 2001 From: anhduy-tech Date: Wed, 7 Jan 2026 10:59:02 +0700 Subject: [PATCH] changes --- api/__pycache__/settings.cpython-313.pyc | Bin 3451 -> 3451 bytes app/__pycache__/cleardata.cpython-313.pyc | Bin 2358 -> 3350 bytes app/__pycache__/consumers.cpython-313.pyc | Bin 5620 -> 6457 bytes app/__pycache__/signals.cpython-313.pyc | Bin 3488 -> 3488 bytes app/consumers.py | 141 ++++++++++++---------- 5 files changed, 75 insertions(+), 66 deletions(-) diff --git a/api/__pycache__/settings.cpython-313.pyc b/api/__pycache__/settings.cpython-313.pyc index 8cb90ed50442ed8d06c8c795adbfc288b9fbc0f8..23f6b64ed81572569fda93f8c9a140eb8db57ece 100644 GIT binary patch delta 19 Zcmew@^;?STGcPX}0}y<^wvnrl7XU;T22KC~ delta 19 Zcmew@^;?STGcPX}0}#xz*vM7L3jjXr1y=w7 diff --git a/app/__pycache__/cleardata.cpython-313.pyc b/app/__pycache__/cleardata.cpython-313.pyc index 775e9501d937b13e7e4b8c3899ee8a14a5550344..8cbd56372c7b576afd5ce5d2d8dc126ec1810516 100644 GIT binary patch literal 3350 zcmcIn%}*Og6rc4ze}Dl~AV3Kp#&JR&g6#k?HlkG_AtBK4wa_-DjkWfUv4inCyX#P* z9Eemk0i~COL(3Gubj_ndB9|B=b`+X;rKx z8K!JQ7B~8u_aMO<;LdPm7Qxnr&g>E@_GP=z@q+apam1!-qLx#A9C3)*RP=fZuQ7QZ zt64dtVk&bd3Czw23-Mj95)HK$F$;Hoq|$w~h~`V`{guECuOTG1Ahcxe1}%X(W}b1p zRRl|f^P*11=XeXEU4ggKI>Q}+LJ74ja)R~!)?Ks+w)dO4R60jy@q7)d_m_Fs{yyuO zr!oJ*!I&E@HJIrC%-d8U-sTGNR?5Gv6|%RjLcHx2;_a*uue(CLo(l1LE5v)^P+p&j z?zKxNP5GT~exHf%wSrx6L^u{fY25Ie`ghJgBpx-Q33_)kyim2sr(eGvi~Rh6$+*{= zG0crF(R=AcblrEw`N6#zrc{pe-DoLfYV1fnMc0L;h^biwGkYDtdqDnBEnPH2W?&S& zyKRC?ppW@3l;C{T(+W#03bT_qvZAW4$%;fEDXK}*KQ}X(O%Dv+n3z6&`Lr;wJ{HSO zuV4Oh;i@okEhW$0NG~j}UHd9?_Tp^pJRbZacs@uz4Tn#M5|LoIAIJK|h$tn3VJR+# zaIk-9KpG5EBth#~C=sUGU@V5QB%P7M(%`^QJQN&=y|7SIXqvU8B&8&F5Zfx&lCq?* z4Js^OlBS7;h1uv}Fc=*hJr|uHpIaOk76?e9CX(v$RWX&03OJ(?yBxcU4=P`tVhqM(wNj;%xGB2i0NoaNoq#paXFXKqGPhWLL6shI#i5nq;6J`rChwM z#}cVuP{fQ%k(8V%Gu6+E>#KCps1Q$LDMz^*-W+odYt$^_d_zZSOwwtYnq}YZ5nC=x zcS3D5VWIR*LilB@k^0vL?^;UiF)=M>;+TLcUdO~)@=ZVW&CC?38vizqv&=|h%_^yk z=CcyJg0JPUswpgqC{E^2UXj-bhgJ5);AVx41d{S94y@!>rQCXe8p8Y-7)vcPA8Rzg>4|QjXV#kAS8iRocfDZk zC^{P-hK!>joj%T?JPf_Gb@9X7uI9Tp@7#Pask?eMCbpdocSr7wJh17`u8nisc2~af zLcxBaSbOB*=SD|Zr%!DpZ;L#$JM*r&f_?5k$aAPOpjn4z1KMkr zK@H{c26XDsSpc^IJv#Jkfv*Vl2DIwXYCyXV?FHyG;J6OQx1i?`!sk$9K$8wl<+%lD zGoV|C?kzZ0ghSS%@+W?Oao&tCc=+&Wj3;K%S zGN46=76XpzaI^p&26%PwZo!E|_vO4jb8DvTksthqC#-wIhG#_gjQqsqJtKL~Se|+J z>~mL_;p(9$XSjNGSMTqxzD<6+>BvU>yOnKQ)$Qc1_AFZo&zS1Unw86x+p=jfc}F3{APiyg{dUMg?uozgiRG7Ua1hVN{R5d7_nx_ z%1aPdDkaR|A;rLu#~92Q%9z6o(-6uSBs+N>o2n>0?4lK5azRRy7$qDO8KPBSBJ2!_ zIzTg+qSav{q430|zz~hi42{VS?6N9Mj!Y^H!9bmkOlgd147SYj3~7v-%zj0n*e?Pl zGfmOSt{jzG`^J|V8?VaI&Jf)T{FWA$X79LbXs12StR!)GAL zP^1VXZgJS;=BJeAq}ml}0=b}UQ>+gpJ}@&fGTvoSyvv|>mqF(agT%)gMg}IX4$}#0 Z9r~9Ur9LuAGg^ISW?+&Avx+o;dH~$Gfqno0 diff --git a/app/__pycache__/consumers.cpython-313.pyc b/app/__pycache__/consumers.cpython-313.pyc index 3d2d9ac273c7fbf68cda808c8a15741704917017..c79da143af09df7d9b45857e953efc27f4b3495e 100644 GIT binary patch delta 2151 zcma)6TWs6b89o$ANtP*zy34v)l$1zOWGSvMY3(I(y~ME`-%_*e+_yi0q=ns4295OF%DyO zB(iXX7QhnSc<+-q1KI&gviec%l1|hyfHknjQT!4i;tXiki3CT;A5u;y+tMr(NseGm zhzJ&LF>Ob#+tCLI>!DMQAiVqLBkr^kd)Ch=}EH zsgT*yKMepM1pwwb5UE{hnJ-|cF_D^SJBpg*L5njz6>K`bdvm%7;PkAc596p5>{^q2s$D*B8OFl$ zJ4RnXw6adL9nQ+yyryx*8`$HV|qOZB4bL zm9sSz9>@Nd`ny*5xiFYH*4(%O@F@U9J3L%4>JS}IT?jX4hnK+f^d;aNQwbzwodF@h z!m>g8{FC<_HV4k)AuxwMi_c++wu!aVsZ@$NKRLm2<3m&Zj3n@j<@}P6T`tb@Wr4|y zD}~pDS!S*vF~W)5?~%`jFg73Mlg^<(m^wUY1j*r{;_45=Y(>$BuIIFF~25A zT5E2B7w3g+`Sqfp=_MgokY+VqepbV>xuXjXYjOB=!{_jM2#-Gi4md!@2|&R%g&}2Q zE8snn0po4}S^)#*0a7EO>jAf5%JIc~xv^+Mr|Ht28gUISRYE&t7|8}-TFaSvK@?Vt z(mCyvT(boR(q|S6IexKpE`1ca1QSa>2!8=@0425X+1UK83%@OVP^hK{l=J|cep`Z{ zD0nN-j>$3l;>0*&CZxi((pn;XG?=03^FxUs49L|ow3-u&<$OWZTFMfi z6Rz^P*R+-fQ&w2bC7kj%^rcvlFF}>r9A6SNb8ayY4>VikC4Q--k>iC~VNp5{+2kuW z?u3R3(hQuGVBcZi{(Ce!DUHe%TWtLe*j&`6uZ(7ezIbbS?`3uP2g>xT>Tp)MctyD~ zt5}46f zUGJOUHCO3lDt&C{h26wX_IIo5@qr5ce3c$n>EVaCE^0Xhx+wh!=z{dd_}`6y4nBzY zRM+EuYP_!!|DHl0t96~e*`hr2eQ0h^t91ISVE2B{#r@#0l6kcfT&{YSuM=N8JewD8 zWVSNfFI58_YM`T`x~mp}-QDTf{mITLDEy>bamEfzz#qDi-b&Y)XKK;jI*Ffj9|GL( zc?58mr;gyj4Az6G>pcfv;PL<8U96)nelvqXtam^)bvy8`V09X?QqcL2hcuwu;8HD- zKT(0tDVyRvdy~J(-l7!y_&zmJqg+)gq*9?O6;{`&@IMa>F!f)hJ$PH+a*vp>twAC~ zgWEw2+PB*WEpYO22bz4GibDGijb%vUjtwQ+gs5 zCoU{wB0h09Xz#%QMcnhF>U$ylC0Kqh5=4^$EMp<|01E68h-#0-kZo+Bsex1jEe)hi zBL;lW7ah^zduK_Qe_uyJ&wT?vVvDcIo2i?T@LIp+Nyi)>XBPMzuzI*OD z-#K&6H|NgK{u$dIO*zJw<~=h$fADDvvzo86{)nFa>>0@m6!Eyh$>pttjj0 z>MD{FNK;RLMSG>q=rWy5S}wc}Kvn<%vzULfM5|;HEg~ry6Huh$dXYxZ-h;lSQ?b*! zw;gvy&pD2&N%S0x1Y0P^CV)gwz;m!j&AE=FmbClGKDpCgv|{se%=1v9Vpx%*y6G-F z6oJ!y9GX%h>>wVk==fI&9EW2M zEP#doqtmeIy#6g1mJdGsrL#`~g1G3MVblq=;Hkt;3Vlug(bWznnL zbngjyzD4Jj3i)Csvz(Q)ZBf0D zOs#r0q7AE{vhMZxT3qF$JKY1SFrWs6+SPi`VB17=vu%SZY}*4SjyV7(n%V7u9nf6c z2%O=1c&Ndhy+u|Hj~OSfsySWgY?V~qLet%^xcj%rhC5lc{$O=|y!hF0(;88%k?%dc z;_2VGxQeklwQmH0lu9{1n!PpKq=E_+yzQtJZU?rXQ$iOT)JT&WSE%uQy)Izc1G)fp z0CZleO8-Fu%DW>ZnnFquQVrpJoeHY)q3xIIu}j#j@e1|qPu{^tiLpoC@%qFY4ev^m zTd7VwVYm(AQ*x86xmTNfMByWyspD#oCgjpqaYL2%Pj2_oJ5y{CI!^0pdZt7uye6I2j@y zaz@SeK@I&K40JH0VN@r30J`J3>YvcI+m*?bE7?jRm&sNta^cN)D*19IBTu10e$+S( l4UC*a75;{CO55EWYV#btjtt%~`#ywBos0brG(*ne@ZYx_R5$}1bYAg diff --git a/app/consumers.py b/app/consumers.py index 82cfba8a..3cb97c16 100644 --- a/app/consumers.py +++ b/app/consumers.py @@ -54,77 +54,86 @@ class DataConsumer(AsyncJsonWebsocketConsumer): self.subscribed_groups.add(group_name) async def realtime_update(self, event): - # Move imports inside the method to prevent AppRegistryNotReady error on startup - from django.db.models import Q - from .views import get_serializer + # Bước gỡ lỗi cuối cùng: xác nhận phương thức này được gọi và bắt bất kỳ lỗi nào + print(f"--- CONSUMER: realtime_update invoked for event name: {event['payload']['name']} ---") + try: + # Move imports inside the method to prevent AppRegistryNotReady error on startup + from django.db.models import Q + from .views import get_serializer - payload = event["payload"] - change_type = payload.get("change_type") - record_id = payload["record"].get("id") + payload = event["payload"] + change_type = payload.get("change_type") + record_id = payload["record"].get("id") - if not record_id or not change_type: - return + if not record_id or not change_type: + return - model_name_lower = payload["name"] - model_name_capitalized = model_name_lower.capitalize() + model_name_lower = payload["name"] + # Chuyển đổi snake_case (từ signal) thành PascalCase với dấu gạch dưới (được sử dụng trong subscription) + # Ví dụ: 'product_note' -> 'Product_Note' + model_name_pascal_case = '_'.join(word.capitalize() for word in model_name_lower.split('_')) - # 1. Get this client's subscription parameters for the specific model - client_params = self.subscription_params.get(model_name_capitalized) - if not client_params: - return # This client is not subscribed to this model. + # 1. Lấy các tham số đăng ký của client này cho model cụ thể + client_params = self.subscription_params.get(model_name_pascal_case) + if not client_params: + return # Client này không đăng ký model này. - # 2. Handle DELETION event - if change_type == 'deleted': - await self.send_json({ - "type": "realtime_update", - "payload": { - "name": model_name_lower, - "change_type": "deleted", - "record": {"id": record_id} - } - }) - return - - # 3. Handle CREATION and UPDATE events - Model, _ = get_serializer(model_name_lower) - if not Model: - return - - # Build a Q object from the client's filter dictionary - filter_q = Q() - filter_dict = client_params.get('filter') - if isinstance(filter_dict, dict): - filter_q = Q(**filter_dict) - - # Check if the object actually exists with the combined filter - record_exists = await database_sync_to_async( - Model.objects.filter(Q(pk=record_id) & filter_q).exists - )() - - if record_exists: - # If record matches filter, re-run the query to get the correctly shaped data - single_record_params = client_params.copy() - single_record_params['filter'] = {'pk': record_id} - - data = await database_sync_to_async(execute_data_query)(model_name_capitalized, single_record_params) - - if data and data.get('rows'): - payload_for_client = { - "name": model_name_lower, - "change_type": change_type, - "record": data['rows'][0] - } + # 2. Xử lý sự kiện DELETION + if change_type == 'deleted': await self.send_json({ "type": "realtime_update", - "payload": payload_for_client + "payload": { + "name": model_name_lower, + "change_type": "deleted", + "record": {"id": record_id} + } }) - else: - # If the record exists but no longer matches the filter, treat as a deletion for this client - await self.send_json({ - "type": "realtime_update", - "payload": { - "name": model_name_lower, - "change_type": "deleted", - "record": {"id": record_id} - } - }) + return + + # 3. Xử lý sự kiện CREATION và UPDATE + Model, _ = get_serializer(model_name_lower) + if not Model: + return + + # Xây dựng một đối tượng Q từ từ điển bộ lọc của client + filter_q = Q() + filter_dict = client_params.get('filter') + if isinstance(filter_dict, dict): + filter_q = Q(**filter_dict) + + # Kiểm tra xem đối tượng có thực sự tồn tại với bộ lọc kết hợp không + record_exists = await database_sync_to_async( + Model.objects.filter(Q(pk=record_id) & filter_q).exists + )() + + if record_exists: + # Nếu bản ghi khớp với bộ lọc, chạy lại truy vấn để lấy dữ liệu có định dạng đúng + single_record_params = client_params.copy() + single_record_params['filter'] = {'pk': record_id} + + data = await database_sync_to_async(execute_data_query)(model_name_pascal_case, single_record_params) + + if data and data.get('rows'): + payload_for_client = { + "name": model_name_lower, + "change_type": change_type, + "record": data['rows'][0] + } + await self.send_json({ + "type": "realtime_update", + "payload": payload_for_client + }) + else: + # Nếu bản ghi tồn tại nhưng không còn khớp với bộ lọc, coi như là một thao tác xóa đối với client này + await self.send_json({ + "type": "realtime_update", + "payload": { + "name": model_name_lower, + "change_type": "deleted", + "record": {"id": record_id} + } + }) + except Exception as e: + import traceback + print(f"!!!!!!!!!! EXCEPTION in realtime_update: {e} !!!!!!!!!!!") + traceback.print_exc()