1
- # myapp/management/commands/setup_tables.py
1
+ import json
2
+ from csv import DictReader
3
+ from pathlib import Path
2
4
3
5
from django .contrib .auth import get_user_model
4
6
from django .core .management .base import BaseCommand
7
+ from sqlalchemy import MetaData
8
+ from sqlalchemy .dialects .postgresql import insert as pg_insert
9
+ from sqlalchemy .exc import SQLAlchemyError
10
+ from sqlalchemy .orm import sessionmaker
5
11
12
+ from api .actions import (
13
+ _get_engine ,
14
+ set_table_metadata ,
15
+ try_convert_metadata_to_v2 ,
16
+ try_parse_metadata ,
17
+ try_validate_metadata ,
18
+ )
6
19
from api .services .permissions import assign_table_holder
7
20
from api .services .table_creation import TableCreationOrchestrator
21
+ from dataedit .views import get_tag_keywords_synchronized_metadata
8
22
9
23
User = get_user_model ()
10
24
11
- # Define your table specs here:
12
- # Note: these can keep `"type"` for readability; we remap it below.
25
+
13
26
TABLE_DEFS = [
14
27
{
15
28
"schema" : "model_draft" ,
16
- "table" : "my_first_table " ,
29
+ "table" : "example_wind_farm_capacity " ,
17
30
"columns" : [
18
31
{
19
32
"name" : "id" ,
20
- "type" : "integer " ,
33
+ "type" : "bigserial " ,
21
34
"options" : {"primary_key" : True , "nullable" : False },
22
35
},
36
+ {
37
+ "name" : "technology" ,
38
+ "type" : "text" ,
39
+ "options" : {"nullable" : True },
40
+ },
41
+ {
42
+ "name" : "type" ,
43
+ "type" : "text" ,
44
+ "options" : {"nullable" : True },
45
+ },
46
+ {
47
+ "name" : "year" ,
48
+ "type" : "integer" ,
49
+ "options" : {"nullable" : True },
50
+ },
51
+ {
52
+ "name" : "date" ,
53
+ "type" : "date" ,
54
+ "options" : {"nullable" : True },
55
+ },
23
56
{
24
57
"name" : "value" ,
58
+ "type" : "numeric" ,
59
+ "options" : {"nullable" : True },
60
+ },
61
+ {
62
+ "name" : "comment" ,
25
63
"type" : "text" ,
26
- "options" : {"nullable" : True , "default" : "" },
64
+ "options" : {"nullable" : True },
65
+ },
66
+ {
67
+ "name" : "geometry" ,
68
+ "type" : "geometry" ,
69
+ "options" : {"nullable" : True },
27
70
},
28
71
],
29
- "constraints" : [
30
- # e.g. { "constraint_type": "unique", "columns": ["value"], "name": "uq_value" } # noqa
31
- ],
32
- },
33
- # … more specs …
72
+ "constraints" : [],
73
+ }
34
74
]
35
75
76
+ CSV_FILE = "dataedit/management/data/example_wind_farm_capacity.csv"
77
+
36
78
37
79
class Command (BaseCommand ):
38
80
help = "Seed DataEdit tables + actual DB tables as in the Tables API"
@@ -43,6 +85,7 @@ def handle(self, *args, **opts):
43
85
name = "test" ,
defaults = {
"email" :
"[email protected] " ,
"is_staff" :
True }
44
86
)
45
87
88
+ print ("Hello world" )
46
89
orchestrator = TableCreationOrchestrator ()
47
90
48
91
for spec in TABLE_DEFS :
@@ -88,4 +131,126 @@ def handle(self, *args, **opts):
88
131
)
89
132
90
133
except Exception as e :
91
- self .style .ERROR (f"✘ Failed to create { schema_name } .{ table_name } : { e } " )
134
+ self .stderr .write (
135
+ self .style .ERROR (
136
+ f"✘ Failed to create { schema_name } .{ table_name } : { e } "
137
+ )
138
+ )
139
+
140
+ try :
141
+ # Seed the table with data from CSV
142
+ self ._seed_data (schema_name , table_name , CSV_FILE )
143
+
144
+ except Exception as e :
145
+ self .stderr .write (
146
+ self .style .ERROR (
147
+ f"✘ Failed to seed { schema_name } .{ table_name } with data: { e } "
148
+ )
149
+ )
150
+
151
+ try :
152
+ # Set metadata for the table
153
+ metadata_file = "dataedit/management/data/datapackage.json"
154
+ self ._set_metadata (schema_name , table_name , metadata_file )
155
+ except Exception as e :
156
+ self .stderr .write (
157
+ self .style .ERROR (
158
+ f"✘ Failed to set metadata for { schema_name } .{ table_name } : { e } "
159
+ )
160
+ )
161
+
162
+ def _seed_data (self , schema , table_name , csv_file ):
163
+ engine = _get_engine ()
164
+ Session = sessionmaker (bind = engine )
165
+ session = Session ()
166
+ metadata = MetaData (schema = schema )
167
+ metadata .reflect (bind = engine , schema = schema )
168
+
169
+ full_table_name = f"{ schema } .{ table_name } "
170
+ table = metadata .tables .get (full_table_name )
171
+
172
+ print (table )
173
+
174
+ if table is None :
175
+ self .stderr .write (
176
+ self .style .ERROR (
177
+ f"Table '{ full_table_name } ' not found in reflected metadata."
178
+ )
179
+ )
180
+ return
181
+
182
+ with open (csv_file , newline = "" , encoding = "utf-8" ) as f :
183
+ reader = DictReader (f )
184
+ rows = []
185
+
186
+ for row in reader :
187
+ cleaned = {k : (v if v != "" else None ) for k , v in row .items ()}
188
+ rows .append (cleaned )
189
+
190
+ if not rows :
191
+ self .stdout .write (
192
+ self .style .WARNING (f"No rows to insert into { full_table_name } ." )
193
+ )
194
+ return
195
+
196
+ self .stdout .write (
197
+ f"Preparing to insert { len (rows )} rows into { full_table_name } "
198
+ )
199
+ self .stdout .write (f"First row: { rows [0 ]} " if rows else "No rows parsed." )
200
+
201
+ try :
202
+ stmt = pg_insert (table ).values (rows )
203
+ stmt = stmt .on_conflict_do_nothing (index_elements = ["id" ])
204
+ session .execute (stmt )
205
+ session .commit ()
206
+ self .stdout .write (
207
+ self .style .SUCCESS (
208
+ f"✔ Inserted { len (rows )} rows into { full_table_name } "
209
+ )
210
+ )
211
+ except SQLAlchemyError as e :
212
+ session .rollback ()
213
+ self .stderr .write (self .style .ERROR (f"SQLAlchemy insert error: { e } " ))
214
+ except Exception as e :
215
+ session .rollback ()
216
+ self .stderr .write (self .style .ERROR (f"General insert error: { e } " ))
217
+ finally :
218
+ session .close ()
219
+
220
+ def _set_metadata (self , schema , table_name , metadata_file ):
221
+ metadata_path = Path (metadata_file )
222
+ metadata : dict = {}
223
+
224
+ if not metadata_path .exists ():
225
+ self .stderr .write (
226
+ self .style .ERROR (f"Metadata file '{ metadata_file } ' not found." )
227
+ )
228
+ return
229
+
230
+ with open (metadata_path , encoding = "utf-8" ) as f :
231
+ raw_metadata = json .load (f )
232
+
233
+ metadata , error = try_parse_metadata (raw_metadata )
234
+ if error :
235
+ raise Exception (f"Metadata parse error: { error } " )
236
+
237
+ metadata = try_convert_metadata_to_v2 (metadata )
238
+ metadata , error = try_validate_metadata (metadata )
239
+ if error :
240
+ raise Exception (f"Metadata validation error: { error } " )
241
+
242
+ # Sync keywords with tag system
243
+ keywords = metadata ["resources" ][0 ].get ("keywords" , []) or []
244
+ synced = get_tag_keywords_synchronized_metadata (
245
+ table = table_name , schema = schema , keywords_new = keywords
246
+ )
247
+ metadata ["resources" ][0 ]["keywords" ] = synced ["resources" ][0 ]["keywords" ]
248
+
249
+ # Save to Django's oemetadata JSONB field and comment
250
+ set_table_metadata (table = table_name , schema = schema , metadata = metadata )
251
+
252
+ self .stdout .write (
253
+ self .style .SUCCESS (
254
+ f"✔ Metadata saved and tags synced for { schema } .{ table_name } "
255
+ )
256
+ )
0 commit comments