This commit is contained in:
anhduy-tech
2025-12-31 09:22:13 +07:00
parent ef48c93de0
commit bfbe0a4061
9 changed files with 83 additions and 60 deletions

View File

@@ -89,7 +89,7 @@ DATABASES = {
'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg3', # Hoặc psycopg3/postgis 'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg3', # Hoặc psycopg3/postgis
'NAME': 'utopia', 'NAME': 'utopia',
'USER': 'postgres', 'USER': 'postgres',
'PASSWORD': 'V59yNLN42a9Q7xT', 'PASSWORD': 'W7VVBUnqDRy7OVkWfdTB4fS0HQ1615',
'HOST': DBHOST, 'HOST': DBHOST,
'PORT': '5423', 'PORT': '5423',
'CONN_MAX_AGE': 0, # Tắt persistent connection mặc định của Django 'CONN_MAX_AGE': 0, # Tắt persistent connection mặc định của Django

View File

@@ -89,8 +89,18 @@ def execute_data_query(name, params):
if Model is None: if Model is None:
return None return None
def parse_param_to_dict(param):
if isinstance(param, dict):
return param
if isinstance(param, str):
try:
return ast.literal_eval(param)
except (ValueError, SyntaxError):
return {}
return {}
# Lấy các tham số từ dict `params` # Lấy các tham số từ dict `params`
filter_str = params.get('filter') filter_param = params.get('filter')
values = params.get('values') values = params.get('values')
values = values if values==None else values.split(',') values = values if values==None else values.split(',')
summary = params.get('summary') summary = params.get('summary')
@@ -99,40 +109,47 @@ def execute_data_query(name, params):
sort = params.get('sort') sort = params.get('sort')
sort = None if sort==None else sort.split(',') sort = None if sort==None else sort.split(',')
distinct_values = params.get('distinct_values') distinct_values = params.get('distinct_values')
filter_or = params.get('filter_or') filter_or_param = params.get('filter_or')
exclude = params.get('exclude') exclude_param = params.get('exclude')
calculation = params.get('calculation') calculation = params.get('calculation')
final_filter = params.get('final_filter') final_filter_param = params.get('final_filter')
final_exclude = params.get('final_exclude') final_exclude_param = params.get('final_exclude')
# Xây dựng filter_list # Xây dựng filter_list
filter_list = Q() filter_list = Q()
if filter_or != None: filter_or_dict = parse_param_to_dict(filter_or_param)
for key, value in ast.literal_eval(filter_or).items(): if filter_or_dict:
for key, value in filter_or_dict.items():
filter_list.add(Q(**{key: value}), Q.OR) filter_list.add(Q(**{key: value}), Q.OR)
if filter_str != None: filter_dict = parse_param_to_dict(filter_param)
for key, value in ast.literal_eval(filter_str).items(): if filter_dict:
if isinstance(value, dict) == True: for key, value in filter_dict.items():
if value['type'] == 'F': if isinstance(value, dict) and value.get('type') == 'F':
filter_list.add(Q(**{key: F(value['field'])}), Q.AND) filter_list.add(Q(**{key: F(value['field'])}), Q.AND)
else: else:
filter_list.add(Q(**{key: value}), Q.AND) filter_list.add(Q(**{key: value}), Q.AND)
# Thực thi query # Thực thi query
rows = Model.objects.all() if len(filter_list) == 0 else Model.objects.filter(filter_list) rows = Model.objects.all() if len(filter_list) == 0 else Model.objects.filter(filter_list)
if exclude != None:
exclude_dict = parse_param_to_dict(exclude_param)
if exclude_dict:
exclude_list = Q() exclude_list = Q()
for key, value in ast.literal_eval(exclude).items(): for key, value in exclude_dict.items():
if isinstance(value, dict) == True: if isinstance(value, dict) and value.get('type') == 'F':
if value['type'] == 'F': exclude_list.add(Q(**{key: F(value['field'])}), Q.AND)
exclude_list.add(Q(**{key: F(value['field'])}), Q.AND)
else: else:
exclude_list.add(Q(**{key: value}), Q.AND) exclude_list.add(Q(**{key: value}), Q.AND)
rows = rows.exclude(exclude_list) rows = rows.exclude(exclude_list)
rows, need_serializer = base_query(rows, values, summary, distinct_values) rows, need_serializer = base_query(rows, values, summary, distinct_values)
rows = final_result(rows, calculation, final_filter, final_exclude, sort)
# We need to parse final_filter and final_exclude here as they are applied in final_result
final_filter_dict = parse_param_to_dict(final_filter_param)
final_exclude_dict = parse_param_to_dict(final_exclude_param)
rows = final_result(rows, calculation, final_filter_dict, final_exclude_dict, sort)
# Initialize total_rows and full_data # Initialize total_rows and full_data
total_rows = 0 total_rows = 0

View File

@@ -14,7 +14,7 @@ class DataConsumer(AsyncJsonWebsocketConsumer):
async def connect(self): async def connect(self):
self.subscribed_groups = set() self.subscribed_groups = set()
self.subscription_params = {} # e.g., {'Product': {'filter': '...', 'values': '...'}} self.subscription_params = {}
await self.accept() await self.accept()
async def disconnect(self, close_code): async def disconnect(self, close_code):
@@ -55,12 +55,14 @@ class DataConsumer(AsyncJsonWebsocketConsumer):
async def realtime_update(self, event): async def realtime_update(self, event):
# Move imports inside the method to prevent AppRegistryNotReady error on startup # Move imports inside the method to prevent AppRegistryNotReady error on startup
import ast
from django.db.models import Q from django.db.models import Q
from .views import get_serializer from .views import get_serializer
payload = event["payload"] payload = event["payload"]
record = payload["record"] record_id = payload["record"].get("id")
if not record_id:
return
model_name_lower = payload["name"] model_name_lower = payload["name"]
model_name_capitalized = model_name_lower.capitalize() model_name_capitalized = model_name_lower.capitalize()
@@ -69,38 +71,44 @@ class DataConsumer(AsyncJsonWebsocketConsumer):
if not client_params: if not client_params:
return # This client is not subscribed to this model. return # This client is not subscribed to this model.
# 2. Check if the updated record matches the client's filter # 2. Check if the updated record ID could possibly match the client's filter
filter_str = client_params.get('filter') Model, _ = get_serializer(model_name_lower)
if filter_str: if not Model:
try: return
Model, _ = get_serializer(model_name_lower)
if not Model:
return
filter_q = Q() # Build a Q object from the client's filter dictionary
filter_dict = ast.literal_eval(filter_str) filter_q = Q()
for key, value in filter_dict.items(): filter_dict = client_params.get('filter')
filter_q.add(Q(**{key: value}), Q.AND) if isinstance(filter_dict, dict):
for key, value in filter_dict.items():
filter_q.add(Q(**{key: value}), Q.AND)
matches = await database_sync_to_async( # Combine the client's filter with the specific record's ID
Model.objects.filter(pk=record["id"]).filter(filter_q).exists combined_filter = Q(pk=record_id) & filter_q
)()
if not matches: # Check if the object actually exists with the combined filter
return # Record does not match the client's filter, so don't send. record_exists = await database_sync_to_async(Model.objects.filter(combined_filter).exists)()
except Exception: if not record_exists:
return # Fail silently if filter is invalid or DB check fails. return # The record does not match the client's filter, so don't send.
# 3. Create a tailored payload, respecting the 'values' parameter # 3. Re-run the original query for just this single object.
payload_for_client = payload.copy() # This correctly applies 'values', 'distinct_values', 'summary', etc.
values_str = client_params.get('values') single_record_params = client_params.copy()
if values_str: single_record_params['filter'] = {'pk': record_id}
requested_values = values_str.split(',')
# The record from the signal contains all fields. Filter it down.
filtered_record = {key: record.get(key) for key in requested_values if key in record}
payload_for_client['record'] = filtered_record
# 4. Send the final, tailored payload to the client data = await database_sync_to_async(execute_data_query)(model_name_capitalized, single_record_params)
# If the query returns no data (e.g., record was deleted or no longer matches), do nothing.
if not data or not data.get('rows'):
return
# 4. Build the final payload with the correctly-shaped record
payload_for_client = {
"name": model_name_lower,
"record": data['rows'][0]
}
# 5. Send the final, tailored payload to the client
await self.send_json({ await self.send_json({
"type": "realtime_update", "type": "realtime_update",
"payload": payload_for_client "payload": payload_for_client

View File

@@ -285,22 +285,20 @@ def final_result(rows, calculation=None, final_filter=None, final_exclude=None,
if calculation: if calculation:
rows = calculate(rows, calculation) rows = calculate(rows, calculation)
if final_filter != None: if final_filter:
filter_list = Q() filter_list = Q()
for key, value in ast.literal_eval(final_filter).items(): for key, value in final_filter.items():
if isinstance(value, dict) == True: if isinstance(value, dict) and value.get('type') == 'F':
if value['type'] == 'F': filter_list.add(Q(**{key: F(value['field'])}), Q.AND)
filter_list.add(Q(**{key: F(value['field'])}), Q.AND)
else: else:
filter_list.add(Q(**{key: value}), Q.AND) filter_list.add(Q(**{key: value}), Q.AND)
rows = rows.filter(filter_list) rows = rows.filter(filter_list)
if final_exclude != None: if final_exclude:
exclude_list = Q() exclude_list = Q()
for key, value in ast.literal_eval(final_exclude).items(): for key, value in final_exclude.items():
if isinstance(value, dict) == True: if isinstance(value, dict) and value.get('type') == 'F':
if value['type'] == 'F': exclude_list.add(Q(**{key: F(value['field'])}), Q.AND)
exclude_list.add(Q(**{key: F(value['field'])}), Q.AND)
else: else:
exclude_list.add(Q(**{key: value}), Q.AND) exclude_list.add(Q(**{key: value}), Q.AND)
rows = rows.exclude(exclude_list) rows = rows.exclude(exclude_list)

View File

@@ -20,4 +20,4 @@ num2words
mammoth mammoth
paramiko paramiko
channels channels
uvicorn uvicorn[standard]