changes
This commit is contained in:
Binary file not shown.
@@ -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
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
53
app/basic.py
53
app/basic.py
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
# Combine the client's filter with the specific record's ID
|
||||||
|
combined_filter = Q(pk=record_id) & filter_q
|
||||||
|
|
||||||
matches = await database_sync_to_async(
|
# Check if the object actually exists with the combined filter
|
||||||
Model.objects.filter(pk=record["id"]).filter(filter_q).exists
|
record_exists = await database_sync_to_async(Model.objects.filter(combined_filter).exists)()
|
||||||
)()
|
if not record_exists:
|
||||||
|
return # The record does not match the client's filter, so don't send.
|
||||||
|
|
||||||
if not matches:
|
# 3. Re-run the original query for just this single object.
|
||||||
return # Record does not match the client's filter, so don't send.
|
# This correctly applies 'values', 'distinct_values', 'summary', etc.
|
||||||
except Exception:
|
single_record_params = client_params.copy()
|
||||||
return # Fail silently if filter is invalid or DB check fails.
|
single_record_params['filter'] = {'pk': record_id}
|
||||||
|
|
||||||
|
data = await database_sync_to_async(execute_data_query)(model_name_capitalized, single_record_params)
|
||||||
|
|
||||||
# 3. Create a tailored payload, respecting the 'values' parameter
|
# If the query returns no data (e.g., record was deleted or no longer matches), do nothing.
|
||||||
payload_for_client = payload.copy()
|
if not data or not data.get('rows'):
|
||||||
values_str = client_params.get('values')
|
return
|
||||||
if values_str:
|
|
||||||
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
|
# 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
|
||||||
|
|||||||
18
app/views.py
18
app/views.py
@@ -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)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ num2words
|
|||||||
mammoth
|
mammoth
|
||||||
paramiko
|
paramiko
|
||||||
channels
|
channels
|
||||||
uvicorn
|
uvicorn[standard]
|
||||||
Reference in New Issue
Block a user