|
9 | 9 | from django.utils.translation import gettext_lazy as _ |
10 | 10 | from pymongo import errors as pymongo_errors |
11 | 11 |
|
12 | | -from django_mongodb_extensions.mql_panel.utils import ( |
13 | | - QueryParts, |
| 12 | +from .utils import ( |
14 | 13 | get_max_query_results, |
15 | 14 | parse_query_args, |
16 | 15 | ) |
17 | 16 |
|
18 | 17 |
|
19 | 18 | class MQLBaseForm(SQLSelectForm): |
20 | 19 | def _execute_operation(self, operation_type, executor_func): |
21 | | - mql_string = "" |
22 | | - try: |
23 | | - parts = self._get_query_parts() |
24 | | - mql_string = parts.mql_string |
25 | | - return executor_func( |
26 | | - parts.db, |
27 | | - parts.collection, |
28 | | - parts.collection_name, |
29 | | - parts.operation, |
30 | | - parts.args_list, |
31 | | - ) |
32 | | - except (ValueError, pymongo_errors.PyMongoError) as e: |
33 | | - # ValueError: unsupported operation or unserializable args. |
34 | | - # PyMongoError: any MongoDB driver error during execution. |
35 | | - return self._handle_operation_error(e, mql_string, operation_type) |
36 | | - |
37 | | - def _get_query_parts(self): |
38 | 20 | query_dict = self.cleaned_data["query"] |
39 | 21 | alias = query_dict.get("alias", "default") |
40 | 22 | connection = connections[alias] |
41 | | - collection_name, operation, args_list = parse_query_args(query_dict) |
42 | 23 | db = connection.database |
43 | | - return QueryParts( |
44 | | - query_dict=query_dict, |
45 | | - alias=alias, |
46 | | - mql_string=query_dict.get("mql", ""), |
47 | | - connection=connection, |
48 | | - db=db, |
49 | | - collection=db[collection_name], |
50 | | - collection_name=collection_name, |
51 | | - operation=operation, |
52 | | - args_list=args_list, |
| 24 | + collection_name, operation, args_list = parse_query_args(query_dict) |
| 25 | + collection = db[collection_name] |
| 26 | + return executor_func( |
| 27 | + db, |
| 28 | + collection, |
| 29 | + collection_name, |
| 30 | + operation, |
| 31 | + args_list, |
53 | 32 | ) |
54 | 33 |
|
55 | 34 | def _handle_operation_error(self, error, mql_string, operation_type="operation"): |
@@ -193,34 +172,58 @@ def _execute_query(self, db, collection, collection_name, operation, args_list): |
193 | 172 | raise ValueError(f"Unsupported read operation: {operation}") |
194 | 173 | return self.convert_documents_to_table(result_docs) |
195 | 174 |
|
| 175 | + def _flatten_single_key_dicts(self, obj): |
| 176 | + if isinstance(obj, dict): |
| 177 | + if len(obj) == 1: |
| 178 | + only_value = next(iter(obj.values())) |
| 179 | + return self._flatten_single_key_dicts(only_value) |
| 180 | + return { |
| 181 | + key_name: self._flatten_single_key_dicts(value_item) |
| 182 | + for key_name, value_item in obj.items() |
| 183 | + } |
| 184 | + elif isinstance(obj, list): |
| 185 | + return [self._flatten_single_key_dicts(value_item) for value_item in obj] |
| 186 | + return obj |
| 187 | + |
196 | 188 | def _format_cell_value(self, value): |
197 | | - """Format a single cell value for table display.""" |
198 | | - if value is None: |
199 | | - return {"value": "", "is_json": False} |
200 | | - # Handle primitive types directly without JSON serialization |
201 | | - if isinstance(value, (str, int, float, bool)): |
202 | | - return {"value": str(value), "is_json": False} |
203 | | - # For complex types (ObjectId, datetime, dicts, lists, etc.), use json_util |
204 | | - try: |
205 | | - serialized = json_util.dumps(value) |
206 | | - except (TypeError, AttributeError): |
207 | | - return {"value": str(value), "is_json": False} |
208 | | - try: |
209 | | - parsed = json.loads(serialized) |
210 | | - except json.JSONDecodeError: |
211 | | - return {"value": serialized, "is_json": False} |
212 | | - # Extract value from single-key BSON extended JSON objects like {"$oid": "..."} |
213 | | - if isinstance(parsed, dict) and len(parsed) == 1: |
214 | | - key, val = next(iter(parsed.items())) |
215 | | - return {"value": str(val), "is_json": False} |
216 | | - # For multi-key objects, format with indentation for readability |
217 | | - if isinstance(parsed, dict) and len(parsed) > 1: |
218 | | - return {"value": json.dumps(parsed, indent=4), "is_json": True} |
219 | | - # For lists and other types, use compact serialization |
220 | | - return {"value": serialized, "is_json": False} |
| 189 | + serialized = json_util.dumps(value) |
| 190 | + parsed_json = json.loads(serialized) |
| 191 | + flattened_value = self._flatten_single_key_dicts(parsed_json) |
| 192 | + if isinstance(flattened_value, (str, int, float, bool)): |
| 193 | + return {"value": str(flattened_value), "is_json": False} |
| 194 | + if isinstance(flattened_value, dict): |
| 195 | + return { |
| 196 | + "type": "dict", |
| 197 | + "value": self._format_dict_for_template(flattened_value), |
| 198 | + "is_json": False, |
| 199 | + } |
| 200 | + if isinstance(flattened_value, list): |
| 201 | + return { |
| 202 | + "type": "list", |
| 203 | + "value": self._format_list_for_template(flattened_value), |
| 204 | + "is_json": False, |
| 205 | + } |
| 206 | + return { |
| 207 | + "value": json.dumps(flattened_value, indent=4), |
| 208 | + "is_json": True, |
| 209 | + } |
| 210 | + |
| 211 | + def _format_dict_for_template(self, dictionary): |
| 212 | + return [ |
| 213 | + {"key": key_name, **self._format_cell_value(value_item)} |
| 214 | + for key_name, value_item in dictionary.items() |
| 215 | + ] |
| 216 | + |
| 217 | + def _format_list_for_template(self, list_items): |
| 218 | + return [ |
| 219 | + {"key": index, **self._format_cell_value(value_item)} |
| 220 | + for index, value_item in enumerate(list_items) |
| 221 | + ] |
| 222 | + |
| 223 | + def _format_row(self, row_dict): |
| 224 | + return [self._format_cell_value(cell_value) for cell_value in row_dict.values()] |
221 | 225 |
|
222 | 226 | def convert_documents_to_table(self, documents): |
223 | | - """Convert MongoDB documents to a table of rows and headers.""" |
224 | 227 | if not documents: |
225 | 228 | return [], [] |
226 | 229 | # Collect all unique field names |
|
0 commit comments