|
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+import json
|
|
|
2
|
+from decimal import Decimal
|
|
|
3
|
+
|
|
|
4
|
+from django.apps import apps
|
|
|
5
|
+from django.contrib.auth import get_user_model
|
|
|
6
|
+from django.core.management.base import BaseCommand
|
|
|
7
|
+from django.db import connections, models, router
|
|
|
8
|
+from django.db.models.fields.files import FileField, ImageField
|
|
|
9
|
+from django.db.models.fields.related import ForeignKey, OneToOneField
|
|
|
10
|
+from django.utils import timezone
|
|
|
11
|
+
|
|
|
12
|
+from core.models import LotType
|
|
|
13
|
+
|
|
|
14
|
+
|
|
|
15
|
+MASTER_VIEW_SAMPLE_ROWS = [
|
|
|
16
|
+ {
|
|
|
17
|
+ "PRO0": "1",
|
|
|
18
|
+ "PRO1": "4070207560",
|
|
|
19
|
+ "PRO1C": "NOGUCHI SEIKI",
|
|
|
20
|
+ "PRO2": "260218548-021",
|
|
|
21
|
+ "PRO5": "1",
|
|
|
22
|
+ "PRO8": "35",
|
|
|
23
|
+ "PRO9": "-",
|
|
|
24
|
+ "PRO10": "755",
|
|
|
25
|
+ "PRO11": "65",
|
|
|
26
|
+ "PRO12": "355",
|
|
|
27
|
+ "PRO13": "WA",
|
|
|
28
|
+ "PRO14": "280",
|
|
|
29
|
+ "PRO15": "J",
|
|
|
30
|
+ "PRO16": "8",
|
|
|
31
|
+ "PRO17": "B",
|
|
|
32
|
+ "PRO18": "57A",
|
|
|
33
|
+ "PRO21": "0",
|
|
|
34
|
+ "PRO25": "2026-02-18 00:00:00.000",
|
|
|
35
|
+ "PRO27": "0",
|
|
|
36
|
+ "P2": "15578",
|
|
|
37
|
+ "PRO6": "57000",
|
|
|
38
|
+ "SPEED": "500",
|
|
|
39
|
+ "PRO-TOOL": "PA54508-02BANANAS",
|
|
|
40
|
+ "PRO4": "2100030080-220",
|
|
|
41
|
+ "MC11": "35",
|
|
|
42
|
+ "MC12": "DISK",
|
|
|
43
|
+ "MC14": "755",
|
|
|
44
|
+ "MC15": "65",
|
|
|
45
|
+ "MC16": "355",
|
|
|
46
|
+ "MC19": "WA",
|
|
|
47
|
+ "MC20": "280",
|
|
|
48
|
+ "MC21": "J",
|
|
|
49
|
+ "MC22": "8",
|
|
|
50
|
+ "MC23": "B",
|
|
|
51
|
+ "MC24": "57A",
|
|
|
52
|
+ "MP45": "OUT",
|
|
|
53
|
+ "MP49": " ",
|
|
|
54
|
+ "MI13": "1",
|
|
|
55
|
+ "TC": "4070207560",
|
|
|
56
|
+ "MI14": "01",
|
|
|
57
|
+ "MI15": "23",
|
|
|
58
|
+ "MI16": "280",
|
|
|
59
|
+ "MI17": "J",
|
|
|
60
|
+ "MI18": "23",
|
|
|
61
|
+ "MI19": "52",
|
|
|
62
|
+ "MI20": "0",
|
|
|
63
|
+ "MI21": "NULL",
|
|
|
64
|
+ "MI22": "0",
|
|
|
65
|
+ "MI23": "0",
|
|
|
66
|
+ "MI31": "NULL",
|
|
|
67
|
+ "MI33": "NULL",
|
|
|
68
|
+ "INSAGM": "NULL",
|
|
|
69
|
+ "MARAGM": "NULL",
|
|
|
70
|
+ "MI53": "Q195",
|
|
|
71
|
+ "MI55": "Q195",
|
|
|
72
|
+ "MI36": "000000000",
|
|
|
73
|
+ "MI39": "000",
|
|
|
74
|
+ "MI24": "0",
|
|
|
75
|
+ "Ind1": "NULL",
|
|
|
76
|
+ "Ind2": "NULL",
|
|
|
77
|
+ "Ind3": "NULL",
|
|
|
78
|
+ "AGR1": "",
|
|
|
79
|
+ "AGR2": "",
|
|
|
80
|
+ "ob_Condition": "RH-60Kg",
|
|
|
81
|
+ "Japanese_CustomerName": "野口精機",
|
|
|
82
|
+ },
|
|
|
83
|
+ {
|
|
|
84
|
+ "PRO0": "1",
|
|
|
85
|
+ "PRO1": "4070207560",
|
|
|
86
|
+ "PRO1C": "NOGUCHI SEIKI",
|
|
|
87
|
+ "PRO2": "260218549-022",
|
|
|
88
|
+ "PRO5": "1",
|
|
|
89
|
+ "PRO8": "35",
|
|
|
90
|
+ "PRO9": "-",
|
|
|
91
|
+ "PRO10": "755",
|
|
|
92
|
+ "PRO11": "65",
|
|
|
93
|
+ "PRO12": "355",
|
|
|
94
|
+ "PRO13": "WA",
|
|
|
95
|
+ "PRO14": "280",
|
|
|
96
|
+ "PRO15": "J",
|
|
|
97
|
+ "PRO16": "8",
|
|
|
98
|
+ "PRO17": "B",
|
|
|
99
|
+ "PRO18": "57A",
|
|
|
100
|
+ "PRO21": "0",
|
|
|
101
|
+ "PRO25": "2026-02-18 00:00:00.000",
|
|
|
102
|
+ "PRO27": "0",
|
|
|
103
|
+ "P2": "15578",
|
|
|
104
|
+ "PRO6": "57000",
|
|
|
105
|
+ "SPEED": "500",
|
|
|
106
|
+ "PRO-TOOL": "PA54508-02BANANAS",
|
|
|
107
|
+ "PRO4": "2100030080-230",
|
|
|
108
|
+ "MC11": "35",
|
|
|
109
|
+ "MC12": "DISK",
|
|
|
110
|
+ "MC14": "755",
|
|
|
111
|
+ "MC15": "65",
|
|
|
112
|
+ "MC16": "355",
|
|
|
113
|
+ "MC19": "WA",
|
|
|
114
|
+ "MC20": "280",
|
|
|
115
|
+ "MC21": "J",
|
|
|
116
|
+ "MC22": "8",
|
|
|
117
|
+ "MC23": "B",
|
|
|
118
|
+ "MC24": "57A",
|
|
|
119
|
+ "MP45": "OUT",
|
|
|
120
|
+ "MP49": " ",
|
|
|
121
|
+ "MI13": "1",
|
|
|
122
|
+ "TC": "4070207560",
|
|
|
123
|
+ "MI14": "01",
|
|
|
124
|
+ "MI15": "23",
|
|
|
125
|
+ "MI16": "280",
|
|
|
126
|
+ "MI17": "J",
|
|
|
127
|
+ "MI18": "23",
|
|
|
128
|
+ "MI19": "52",
|
|
|
129
|
+ "MI20": "0",
|
|
|
130
|
+ "MI21": "NULL",
|
|
|
131
|
+ "MI22": "0",
|
|
|
132
|
+ "MI23": "0",
|
|
|
133
|
+ "MI31": "NULL",
|
|
|
134
|
+ "MI33": "NULL",
|
|
|
135
|
+ "INSAGM": "NULL",
|
|
|
136
|
+ "MARAGM": "NULL",
|
|
|
137
|
+ "MI53": "Q195",
|
|
|
138
|
+ "MI55": "Q195",
|
|
|
139
|
+ "MI36": "000000000",
|
|
|
140
|
+ "MI39": "000",
|
|
|
141
|
+ "MI24": "0",
|
|
|
142
|
+ "Ind1": "NULL",
|
|
|
143
|
+ "Ind2": "NULL",
|
|
|
144
|
+ "Ind3": "NULL",
|
|
|
145
|
+ "AGR1": "",
|
|
|
146
|
+ "AGR2": "",
|
|
|
147
|
+ "ob_Condition": "RH-60Kg",
|
|
|
148
|
+ "Japanese_CustomerName": "野口精機",
|
|
|
149
|
+ },
|
|
|
150
|
+ {
|
|
|
151
|
+ "PRO0": "1",
|
|
|
152
|
+ "PRO1": "4070207551",
|
|
|
153
|
+ "PRO1C": "NOGUCHI SEIKI",
|
|
|
154
|
+ "PRO2": "260218548-019",
|
|
|
155
|
+ "PRO5": "1",
|
|
|
156
|
+ "PRO8": "35",
|
|
|
157
|
+ "PRO9": "-",
|
|
|
158
|
+ "PRO10": "755",
|
|
|
159
|
+ "PRO11": "65",
|
|
|
160
|
+ "PRO12": "355",
|
|
|
161
|
+ "PRO13": "WA",
|
|
|
162
|
+ "PRO14": "280",
|
|
|
163
|
+ "PRO15": "K+",
|
|
|
164
|
+ "PRO16": "8",
|
|
|
165
|
+ "PRO17": "B",
|
|
|
166
|
+ "PRO18": "57A",
|
|
|
167
|
+ "PRO21": "0",
|
|
|
168
|
+ "PRO25": "2026-02-18 00:00:00.000",
|
|
|
169
|
+ "PRO27": "0",
|
|
|
170
|
+ "P2": "15826",
|
|
|
171
|
+ "PRO6": "57000",
|
|
|
172
|
+ "SPEED": "500",
|
|
|
173
|
+ "PRO-TOOL": "PA54508-01BANAARI",
|
|
|
174
|
+ "PRO4": "2100030080-200",
|
|
|
175
|
+ "MC11": "35",
|
|
|
176
|
+ "MC12": "DISK",
|
|
|
177
|
+ "MC14": "755",
|
|
|
178
|
+ "MC15": "65",
|
|
|
179
|
+ "MC16": "355",
|
|
|
180
|
+ "MC19": "WA",
|
|
|
181
|
+ "MC20": "280",
|
|
|
182
|
+ "MC21": "K+",
|
|
|
183
|
+ "MC22": "8",
|
|
|
184
|
+ "MC23": "B",
|
|
|
185
|
+ "MC24": "57A",
|
|
|
186
|
+ "MP45": "OUT",
|
|
|
187
|
+ "MP49": "NULL",
|
|
|
188
|
+ "MI13": "1",
|
|
|
189
|
+ "TC": "4070207551",
|
|
|
190
|
+ "MI14": "01",
|
|
|
191
|
+ "MI15": "23",
|
|
|
192
|
+ "MI16": "280",
|
|
|
193
|
+ "MI17": "K+",
|
|
|
194
|
+ "MI18": "41",
|
|
|
195
|
+ "MI19": "67",
|
|
|
196
|
+ "MI20": "0",
|
|
|
197
|
+ "MI21": "0",
|
|
|
198
|
+ "MI22": "0",
|
|
|
199
|
+ "MI23": "0",
|
|
|
200
|
+ "MI31": "NULL",
|
|
|
201
|
+ "MI33": "NULL",
|
|
|
202
|
+ "INSAGM": "NULL",
|
|
|
203
|
+ "MARAGM": "NULL",
|
|
|
204
|
+ "MI53": "Q195",
|
|
|
205
|
+ "MI55": "Q195",
|
|
|
206
|
+ "MI36": "000000000",
|
|
|
207
|
+ "MI39": "000",
|
|
|
208
|
+ "MI24": "0",
|
|
|
209
|
+ "Ind1": "NULL",
|
|
|
210
|
+ "Ind2": "NULL",
|
|
|
211
|
+ "Ind3": "NULL",
|
|
|
212
|
+ "AGR1": "",
|
|
|
213
|
+ "AGR2": "",
|
|
|
214
|
+ "ob_Condition": "RH-60Kg",
|
|
|
215
|
+ "Japanese_CustomerName": "野口精機",
|
|
|
216
|
+ },
|
|
|
217
|
+]
|
|
|
218
|
+
|
|
|
219
|
+DATA_MS_SAMPLE_ROWS = [
|
|
|
220
|
+ {
|
|
|
221
|
+ "row_no": "1",
|
|
|
222
|
+ "dsize": "205.76",
|
|
|
223
|
+ "tsize": "19.12",
|
|
|
224
|
+ "hsize": "0",
|
|
|
225
|
+ "created_at": "2019-02-12 14:34:57.680",
|
|
|
226
|
+ "updated_at": "2019-02-12 14:34:57.680",
|
|
|
227
|
+ "lot_no": "181-38043F",
|
|
|
228
|
+ "machine_id": "27",
|
|
|
229
|
+ "code": "1-3668-4196-6",
|
|
|
230
|
+ "emp_id": "20191",
|
|
|
231
|
+ "weight": "0.152",
|
|
|
232
|
+ "devid": "NULL",
|
|
|
233
|
+ "tpoint1": "705.29",
|
|
|
234
|
+ "tpoint2": "20.35",
|
|
|
235
|
+ "tpoint3": "20.35",
|
|
|
236
|
+ "tpoint4": "0.25",
|
|
|
237
|
+ "tpoint1ok": "NG",
|
|
|
238
|
+ "tpoint2ok": "NG",
|
|
|
239
|
+ "tpoint3ok": "NG",
|
|
|
240
|
+ "tpoint4ok": "NG",
|
|
|
241
|
+ "tdiff": "705.04",
|
|
|
242
|
+ "tmin": "0.25",
|
|
|
243
|
+ "tmax": "705.29",
|
|
|
244
|
+ "hdev": "",
|
|
|
245
|
+ "hsizeproxy": "+1.07",
|
|
|
246
|
+ "shift": "NULL",
|
|
|
247
|
+ "mode": "NULL",
|
|
|
248
|
+ "cal_mode": "0",
|
|
|
249
|
+ "dsizeOk": "NG",
|
|
|
250
|
+ "tsizeOk": "NG",
|
|
|
251
|
+ "hsizeOk": "NG",
|
|
|
252
|
+ },
|
|
|
253
|
+ {
|
|
|
254
|
+ "row_no": "2",
|
|
|
255
|
+ "dsize": "5.07",
|
|
|
256
|
+ "tsize": "0.49",
|
|
|
257
|
+ "hsize": "38.6",
|
|
|
258
|
+ "created_at": "2019-02-12 14:45:18.633",
|
|
|
259
|
+ "updated_at": "2019-02-12 14:45:18.633",
|
|
|
260
|
+ "lot_no": "181-38043F",
|
|
|
261
|
+ "machine_id": "26",
|
|
|
262
|
+ "code": "1-3668-4196-6",
|
|
|
263
|
+ "emp_id": "20191",
|
|
|
264
|
+ "weight": "8.16",
|
|
|
265
|
+ "devid": "NULL",
|
|
|
266
|
+ "tpoint1": "43.5",
|
|
|
267
|
+ "tpoint2": "43.5",
|
|
|
268
|
+ "tpoint3": "43.5",
|
|
|
269
|
+ "tpoint4": "0.49",
|
|
|
270
|
+ "tpoint1ok": "NG",
|
|
|
271
|
+ "tpoint2ok": "NG",
|
|
|
272
|
+ "tpoint3ok": "NG",
|
|
|
273
|
+ "tpoint4ok": "NG",
|
|
|
274
|
+ "tdiff": "43.01",
|
|
|
275
|
+ "tmin": "0.49",
|
|
|
276
|
+ "tmax": "43.5",
|
|
|
277
|
+ "hdev": "",
|
|
|
278
|
+ "hsizeproxy": "OK",
|
|
|
279
|
+ "shift": "NULL",
|
|
|
280
|
+ "mode": "NULL",
|
|
|
281
|
+ "cal_mode": "0",
|
|
|
282
|
+ "dsizeOk": "NG",
|
|
|
283
|
+ "tsizeOk": "NG",
|
|
|
284
|
+ "hsizeOk": "NG",
|
|
|
285
|
+ },
|
|
|
286
|
+ {
|
|
|
287
|
+ "row_no": "1",
|
|
|
288
|
+ "dsize": "25.682",
|
|
|
289
|
+ "tsize": "25.682",
|
|
|
290
|
+ "hsize": "25.568",
|
|
|
291
|
+ "created_at": "2019-02-12 16:35:21.570",
|
|
|
292
|
+ "updated_at": "2019-02-12 16:35:21.570",
|
|
|
293
|
+ "lot_no": "11808915",
|
|
|
294
|
+ "machine_id": "25",
|
|
|
295
|
+ "code": "1-9000-3035-3",
|
|
|
296
|
+ "emp_id": "20191",
|
|
|
297
|
+ "weight": "0.368",
|
|
|
298
|
+ "devid": "NULL",
|
|
|
299
|
+ "tpoint1": "0",
|
|
|
300
|
+ "tpoint2": "0",
|
|
|
301
|
+ "tpoint3": "0",
|
|
|
302
|
+ "tpoint4": "0",
|
|
|
303
|
+ "tpoint1ok": "null",
|
|
|
304
|
+ "tpoint2ok": "null",
|
|
|
305
|
+ "tpoint3ok": "null",
|
|
|
306
|
+ "tpoint4ok": "null",
|
|
|
307
|
+ "tdiff": "0",
|
|
|
308
|
+ "tmin": "0",
|
|
|
309
|
+ "tmax": "0",
|
|
|
310
|
+ "hdev": "NULL",
|
|
|
311
|
+ "hsizeproxy": "NULL",
|
|
|
312
|
+ "shift": "NULL",
|
|
|
313
|
+ "mode": "NULL",
|
|
|
314
|
+ "cal_mode": "0",
|
|
|
315
|
+ "dsizeOk": "NG",
|
|
|
316
|
+ "tsizeOk": "NG",
|
|
|
317
|
+ "hsizeOk": "NG",
|
|
|
318
|
+ },
|
|
|
319
|
+]
|
|
|
320
|
+
|
|
|
321
|
+
|
|
|
322
|
+class Command(BaseCommand):
|
|
|
323
|
+ help = "Create missing tables for project models and seed mock rows for empty tables."
|
|
|
324
|
+
|
|
|
325
|
+ target_app_labels = ("auth", "core", "legacy", "sysadmin")
|
|
|
326
|
+ excluded_tables = {
|
|
|
327
|
+ "auth_group",
|
|
|
328
|
+ "auth_group_permissions",
|
|
|
329
|
+ "auth_permission",
|
|
|
330
|
+ "auth_user_groups",
|
|
|
331
|
+ "auth_user_user_permissions",
|
|
|
332
|
+ "django_admin_log",
|
|
|
333
|
+ "django_content_type",
|
|
|
334
|
+ "django_migrations",
|
|
|
335
|
+ "django_session",
|
|
|
336
|
+ }
|
|
|
337
|
+
|
|
|
338
|
+ def add_arguments(self, parser):
|
|
|
339
|
+ parser.add_argument(
|
|
|
340
|
+ "--target-count",
|
|
|
341
|
+ type=int,
|
|
|
342
|
+ default=1,
|
|
|
343
|
+ help="Minimum number of rows to ensure for each project table.",
|
|
|
344
|
+ )
|
|
|
345
|
+
|
|
|
346
|
+ def handle(self, *args, **options):
|
|
|
347
|
+ self._seed_index = 1
|
|
|
348
|
+ self._active_seed_stack = set()
|
|
|
349
|
+ self._target_count = max(options["target_count"], 1)
|
|
|
350
|
+
|
|
|
351
|
+ models_to_process = self.get_target_models()
|
|
|
352
|
+ self.create_missing_tables(models_to_process)
|
|
|
353
|
+
|
|
|
354
|
+ created_rows = 0
|
|
|
355
|
+ for model in models_to_process:
|
|
|
356
|
+ if model._meta.db_table in self.excluded_tables:
|
|
|
357
|
+ continue
|
|
|
358
|
+ created_rows += self.ensure_target_rows(model, self._target_count)
|
|
|
359
|
+
|
|
|
360
|
+ self.stdout.write(
|
|
|
361
|
+ self.style.SUCCESS(
|
|
|
362
|
+ f"Seed completed. Ensured tables for {len(models_to_process)} models and created {created_rows} seed rows."
|
|
|
363
|
+ )
|
|
|
364
|
+ )
|
|
|
365
|
+
|
|
|
366
|
+ def get_target_models(self):
|
|
|
367
|
+ selected = []
|
|
|
368
|
+ seen = set()
|
|
|
369
|
+
|
|
|
370
|
+ user_model = get_user_model()
|
|
|
371
|
+ selected.append(user_model)
|
|
|
372
|
+ seen.add((user_model._meta.app_label, user_model._meta.model_name))
|
|
|
373
|
+
|
|
|
374
|
+ for app_label in self.target_app_labels:
|
|
|
375
|
+ app_config = apps.get_app_config(app_label)
|
|
|
376
|
+ for model in app_config.get_models():
|
|
|
377
|
+ key = (model._meta.app_label, model._meta.model_name)
|
|
|
378
|
+ if key in seen:
|
|
|
379
|
+ continue
|
|
|
380
|
+ selected.append(model)
|
|
|
381
|
+ seen.add(key)
|
|
|
382
|
+
|
|
|
383
|
+ lot_type_key = (LotType._meta.app_label, LotType._meta.model_name)
|
|
|
384
|
+ if lot_type_key not in seen:
|
|
|
385
|
+ selected.append(LotType)
|
|
|
386
|
+
|
|
|
387
|
+ return selected
|
|
|
388
|
+
|
|
|
389
|
+ def get_database_objects(self):
|
|
|
390
|
+ cache = {}
|
|
|
391
|
+ for alias in connections:
|
|
|
392
|
+ connection = connections[alias]
|
|
|
393
|
+ with connection.cursor() as cursor:
|
|
|
394
|
+ cache[alias] = {info.name for info in connection.introspection.get_table_list(cursor)}
|
|
|
395
|
+ return cache
|
|
|
396
|
+
|
|
|
397
|
+ def create_missing_tables(self, models_to_process):
|
|
|
398
|
+ existing = self.get_database_objects()
|
|
|
399
|
+ pending = []
|
|
|
400
|
+ for model in models_to_process:
|
|
|
401
|
+ alias = router.db_for_write(model) or "default"
|
|
|
402
|
+ if model._meta.db_table in self.excluded_tables:
|
|
|
403
|
+ continue
|
|
|
404
|
+ if model._meta.db_table not in existing.get(alias, set()):
|
|
|
405
|
+ pending.append(model)
|
|
|
406
|
+
|
|
|
407
|
+ while pending:
|
|
|
408
|
+ progress = False
|
|
|
409
|
+ next_pending = []
|
|
|
410
|
+
|
|
|
411
|
+ for model in pending:
|
|
|
412
|
+ alias = router.db_for_write(model) or "default"
|
|
|
413
|
+ dependencies_ready = True
|
|
|
414
|
+ for field in model._meta.local_fields:
|
|
|
415
|
+ if not isinstance(field, (ForeignKey, OneToOneField)):
|
|
|
416
|
+ continue
|
|
|
417
|
+ related_model = field.related_model
|
|
|
418
|
+ if not related_model:
|
|
|
419
|
+ continue
|
|
|
420
|
+ related_table = related_model._meta.db_table
|
|
|
421
|
+ related_alias = router.db_for_write(related_model) or "default"
|
|
|
422
|
+ if related_table in self.excluded_tables:
|
|
|
423
|
+ continue
|
|
|
424
|
+ if related_table not in existing.get(related_alias, set()):
|
|
|
425
|
+ dependencies_ready = False
|
|
|
426
|
+ break
|
|
|
427
|
+
|
|
|
428
|
+ if not dependencies_ready:
|
|
|
429
|
+ next_pending.append(model)
|
|
|
430
|
+ continue
|
|
|
431
|
+
|
|
|
432
|
+ connection = connections[alias]
|
|
|
433
|
+ with connection.schema_editor() as schema_editor:
|
|
|
434
|
+ schema_editor.create_model(model)
|
|
|
435
|
+ existing.setdefault(alias, set()).add(model._meta.db_table)
|
|
|
436
|
+ progress = True
|
|
|
437
|
+ self.stdout.write(f"Created table for {model._meta.label} on {alias} -> {model._meta.db_table}")
|
|
|
438
|
+
|
|
|
439
|
+ if not progress:
|
|
|
440
|
+ stalled = ", ".join(model._meta.label for model in next_pending)
|
|
|
441
|
+ raise RuntimeError(f"Unable to create tables due to unresolved dependencies: {stalled}")
|
|
|
442
|
+
|
|
|
443
|
+ pending = next_pending
|
|
|
444
|
+
|
|
|
445
|
+ def ensure_target_rows(self, model, target_count):
|
|
|
446
|
+ if model._meta.db_table in self.excluded_tables:
|
|
|
447
|
+ return 0
|
|
|
448
|
+
|
|
|
449
|
+ alias = router.db_for_write(model) or "default"
|
|
|
450
|
+ current_count = model._default_manager.using(alias).count()
|
|
|
451
|
+ rows_to_create = max(target_count - current_count, 0)
|
|
|
452
|
+
|
|
|
453
|
+ if rows_to_create == 0:
|
|
|
454
|
+ return 0
|
|
|
455
|
+
|
|
|
456
|
+ label = model._meta.label
|
|
|
457
|
+ if label in self._active_seed_stack:
|
|
|
458
|
+ return 0
|
|
|
459
|
+
|
|
|
460
|
+ self._active_seed_stack.add(label)
|
|
|
461
|
+ try:
|
|
|
462
|
+ created = 0
|
|
|
463
|
+ for row_offset in range(rows_to_create):
|
|
|
464
|
+ instance = model()
|
|
|
465
|
+ value_index = self._seed_index
|
|
|
466
|
+
|
|
|
467
|
+ for field in model._meta.local_fields:
|
|
|
468
|
+ if field.auto_created and not field.concrete:
|
|
|
469
|
+ continue
|
|
|
470
|
+
|
|
|
471
|
+ if getattr(field, "auto_now", False) or getattr(field, "auto_now_add", False):
|
|
|
472
|
+ continue
|
|
|
473
|
+
|
|
|
474
|
+ if isinstance(field, (ForeignKey, OneToOneField)):
|
|
|
475
|
+ related_value = self.get_related_value(field, row_offset, target_count)
|
|
|
476
|
+ setattr(instance, field.attname, related_value)
|
|
|
477
|
+ continue
|
|
|
478
|
+
|
|
|
479
|
+ if field.primary_key and isinstance(field, (models.AutoField, models.BigAutoField)):
|
|
|
480
|
+ continue
|
|
|
481
|
+
|
|
|
482
|
+ value = self.build_field_value(model, field, value_index)
|
|
|
483
|
+ if value is not None or not field.null:
|
|
|
484
|
+ setattr(instance, field.attname, value)
|
|
|
485
|
+
|
|
|
486
|
+ instance.save(using=alias, force_insert=True)
|
|
|
487
|
+ self._seed_index += 1
|
|
|
488
|
+ created += 1
|
|
|
489
|
+
|
|
|
490
|
+ if created:
|
|
|
491
|
+ self.stdout.write(f"Seeded {created} rows for {label} on {alias} -> {model._meta.db_table}")
|
|
|
492
|
+ return created
|
|
|
493
|
+ finally:
|
|
|
494
|
+ self._active_seed_stack.remove(label)
|
|
|
495
|
+
|
|
|
496
|
+ def get_related_value(self, field, row_offset, target_count):
|
|
|
497
|
+ related_model = field.related_model
|
|
|
498
|
+ if related_model is None:
|
|
|
499
|
+ return None
|
|
|
500
|
+
|
|
|
501
|
+ alias = router.db_for_write(related_model) or "default"
|
|
|
502
|
+
|
|
|
503
|
+ if related_model._meta.db_table not in self.excluded_tables:
|
|
|
504
|
+ self.ensure_target_rows(related_model, target_count)
|
|
|
505
|
+
|
|
|
506
|
+ queryset = related_model._default_manager.using(alias).order_by(related_model._meta.pk.attname)
|
|
|
507
|
+ related_instances = list(queryset)
|
|
|
508
|
+ if not related_instances:
|
|
|
509
|
+ return None
|
|
|
510
|
+
|
|
|
511
|
+ if isinstance(field, OneToOneField):
|
|
|
512
|
+ index = row_offset if row_offset < len(related_instances) else len(related_instances) - 1
|
|
|
513
|
+ else:
|
|
|
514
|
+ index = row_offset % len(related_instances)
|
|
|
515
|
+
|
|
|
516
|
+ related_instance = related_instances[index]
|
|
|
517
|
+ return getattr(related_instance, related_model._meta.pk.attname)
|
|
|
518
|
+
|
|
|
519
|
+ def get_master_sample_row(self, value_index):
|
|
|
520
|
+ return MASTER_VIEW_SAMPLE_ROWS[(value_index - 1) % len(MASTER_VIEW_SAMPLE_ROWS)]
|
|
|
521
|
+
|
|
|
522
|
+ def get_sample_value(self, model, field, value_index):
|
|
|
523
|
+ model_name = model._meta.model_name
|
|
|
524
|
+
|
|
|
525
|
+ if model_name in {"mgmasterview", "vmasterview", "belmasterview", "emasterview"}:
|
|
|
526
|
+ row = self.get_master_sample_row(value_index)
|
|
|
527
|
+ if field.name == "PRO0":
|
|
|
528
|
+ return str(value_index)
|
|
|
529
|
+ keys = [field.db_column, field.name]
|
|
|
530
|
+ for key in keys:
|
|
|
531
|
+ if key and key in row:
|
|
|
532
|
+ return self.coerce_sample_value(field, row[key])
|
|
|
533
|
+
|
|
|
534
|
+ if model_name in {"datams", "datamslot2210062522"}:
|
|
|
535
|
+ row = DATA_MS_SAMPLE_ROWS[(value_index - 1) % len(DATA_MS_SAMPLE_ROWS)]
|
|
|
536
|
+ master_row = self.get_master_sample_row(value_index)
|
|
|
537
|
+ if field.name == "lot_no":
|
|
|
538
|
+ return self.coerce_sample_value(field, master_row["PRO2"])
|
|
|
539
|
+ if field.name == "code":
|
|
|
540
|
+ return self.coerce_sample_value(field, master_row["PRO1"])
|
|
|
541
|
+ keys = [field.db_column, field.name]
|
|
|
542
|
+ for key in keys:
|
|
|
543
|
+ if not key:
|
|
|
544
|
+ continue
|
|
|
545
|
+ for candidate_key, candidate_value in row.items():
|
|
|
546
|
+ if candidate_key.lower() == key.lower():
|
|
|
547
|
+ return self.coerce_sample_value(field, candidate_value)
|
|
|
548
|
+
|
|
|
549
|
+ if model_name == "data":
|
|
|
550
|
+ row = DATA_MS_SAMPLE_ROWS[(value_index - 1) % len(DATA_MS_SAMPLE_ROWS)]
|
|
|
551
|
+ master_row = self.get_master_sample_row(value_index)
|
|
|
552
|
+ direct_map = {
|
|
|
553
|
+ "row_no": row.get("row_no"),
|
|
|
554
|
+ "lot_no": master_row.get("PRO2"),
|
|
|
555
|
+ "code": master_row.get("PRO1"),
|
|
|
556
|
+ "created_at": row.get("created_at"),
|
|
|
557
|
+ "updated_at": row.get("updated_at"),
|
|
|
558
|
+ "machine_id": row.get("machine_id"),
|
|
|
559
|
+ "r_type": "CSV",
|
|
|
560
|
+ "grade": row.get("dsizeOk"),
|
|
|
561
|
+ "rgrade": row.get("hsizeOk"),
|
|
|
562
|
+ "header": "CSV",
|
|
|
563
|
+ "ndata": "1",
|
|
|
564
|
+ "sub_order": row.get("row_no"),
|
|
|
565
|
+ "p1": row.get("dsize"),
|
|
|
566
|
+ "p2": row.get("tsize"),
|
|
|
567
|
+ "p3": row.get("hsize"),
|
|
|
568
|
+ "p4": row.get("weight"),
|
|
|
569
|
+ "p5": row.get("tpoint1"),
|
|
|
570
|
+ "p6": row.get("tpoint2"),
|
|
|
571
|
+ "p7": row.get("tpoint3"),
|
|
|
572
|
+ "p8": row.get("tpoint4"),
|
|
|
573
|
+ "p9": row.get("tmin"),
|
|
|
574
|
+ "p10": row.get("tmax"),
|
|
|
575
|
+ }
|
|
|
576
|
+ if field.name == "avg":
|
|
|
577
|
+ try:
|
|
|
578
|
+ average = (float(row["dsize"]) + float(row["tsize"]) + float(row["hsize"])) / 3
|
|
|
579
|
+ return average
|
|
|
580
|
+ except Exception:
|
|
|
581
|
+ return None
|
|
|
582
|
+ if field.name in direct_map:
|
|
|
583
|
+ return self.coerce_sample_value(field, direct_map[field.name])
|
|
|
584
|
+
|
|
|
585
|
+ return None
|
|
|
586
|
+
|
|
|
587
|
+ def coerce_sample_value(self, field, raw_value):
|
|
|
588
|
+ if raw_value is None:
|
|
|
589
|
+ return None
|
|
|
590
|
+
|
|
|
591
|
+ if isinstance(raw_value, str):
|
|
|
592
|
+ value = raw_value.strip()
|
|
|
593
|
+ if value.lower() in {"null", ""}:
|
|
|
594
|
+ return None if field.null else ""
|
|
|
595
|
+ else:
|
|
|
596
|
+ value = raw_value
|
|
|
597
|
+
|
|
|
598
|
+ if isinstance(field, models.DateTimeField):
|
|
|
599
|
+ return timezone.datetime.fromisoformat(value.replace(" ", "T"))
|
|
|
600
|
+
|
|
|
601
|
+ if isinstance(field, models.DateField):
|
|
|
602
|
+ return timezone.datetime.fromisoformat(value.replace(" ", "T")).date()
|
|
|
603
|
+
|
|
|
604
|
+ if isinstance(field, models.DecimalField):
|
|
|
605
|
+ return Decimal(str(value))
|
|
|
606
|
+
|
|
|
607
|
+ if isinstance(field, models.FloatField):
|
|
|
608
|
+ return float(value)
|
|
|
609
|
+
|
|
|
610
|
+ if isinstance(field, (models.IntegerField, models.BigIntegerField, models.SmallIntegerField)):
|
|
|
611
|
+ return int(float(value))
|
|
|
612
|
+
|
|
|
613
|
+ return value
|
|
|
614
|
+
|
|
|
615
|
+ def build_field_value(self, model, field, value_index):
|
|
|
616
|
+ sample_value = self.get_sample_value(model, field, value_index)
|
|
|
617
|
+ if sample_value is not None:
|
|
|
618
|
+ return sample_value
|
|
|
619
|
+
|
|
|
620
|
+ if field.has_default():
|
|
|
621
|
+ default = field.get_default()
|
|
|
622
|
+ return default() if callable(default) else default
|
|
|
623
|
+
|
|
|
624
|
+ if getattr(field, "choices", None):
|
|
|
625
|
+ return field.choices[0][0]
|
|
|
626
|
+
|
|
|
627
|
+ model_key = model._meta.model_name
|
|
|
628
|
+ field_key = field.name.lower()
|
|
|
629
|
+
|
|
|
630
|
+ if isinstance(field, models.JSONField):
|
|
|
631
|
+ if "template" in field_key:
|
|
|
632
|
+ return ["dimension", "hardness_out", "centering"]
|
|
|
633
|
+ return {"model": model_key, "field": field.name, "seed": value_index}
|
|
|
634
|
+
|
|
|
635
|
+ if isinstance(field, (ImageField, FileField)):
|
|
|
636
|
+ filename = f"{model_key}-{value_index}.png" if isinstance(field, ImageField) else f"{model_key}-{value_index}.dat"
|
|
|
637
|
+ return f"mock/{filename}"
|
|
|
638
|
+
|
|
|
639
|
+ if isinstance(field, models.EmailField):
|
|
|
640
|
+ return f"{model_key}{value_index}@example.com"
|
|
|
641
|
+
|
|
|
642
|
+ if isinstance(field, models.DateTimeField):
|
|
|
643
|
+ return timezone.now()
|
|
|
644
|
+
|
|
|
645
|
+ if isinstance(field, models.DateField):
|
|
|
646
|
+ return timezone.now().date()
|
|
|
647
|
+
|
|
|
648
|
+ if isinstance(field, models.TimeField):
|
|
|
649
|
+ return timezone.now().time().replace(microsecond=0)
|
|
|
650
|
+
|
|
|
651
|
+ if isinstance(field, models.DecimalField):
|
|
|
652
|
+ whole = max(field.max_digits - field.decimal_places, 1)
|
|
|
653
|
+ number = str((value_index % (10 ** min(whole, 4))) + 1)
|
|
|
654
|
+ decimal_part = ("12" * max(field.decimal_places, 1))[: field.decimal_places]
|
|
|
655
|
+ return Decimal(f"{number}.{decimal_part}") if field.decimal_places else Decimal(number)
|
|
|
656
|
+
|
|
|
657
|
+ if isinstance(field, models.FloatField):
|
|
|
658
|
+ return float(value_index) + 0.25
|
|
|
659
|
+
|
|
|
660
|
+ if isinstance(field, models.BooleanField):
|
|
|
661
|
+ return True
|
|
|
662
|
+
|
|
|
663
|
+ if isinstance(field, (models.IntegerField, models.BigIntegerField, models.SmallIntegerField)):
|
|
|
664
|
+ return value_index
|
|
|
665
|
+
|
|
|
666
|
+ if isinstance(field, models.TextField):
|
|
|
667
|
+ return f"Mock {model._meta.verbose_name} text {value_index}"
|
|
|
668
|
+
|
|
|
669
|
+ if isinstance(field, models.CharField):
|
|
|
670
|
+ if field.primary_key:
|
|
|
671
|
+ if field.max_length <= 12:
|
|
|
672
|
+ value = str(1000000000 + value_index)[-field.max_length:]
|
|
|
673
|
+ else:
|
|
|
674
|
+ suffix = str(value_index)
|
|
|
675
|
+ prefix_length = max(field.max_length - len(suffix) - 1, 1)
|
|
|
676
|
+ value = f"{model_key[:prefix_length].upper()}-{suffix}"
|
|
|
677
|
+ elif "json" in field_key:
|
|
|
678
|
+ value = json.dumps({"seed": value_index})
|
|
|
679
|
+ elif "email" in field_key:
|
|
|
680
|
+ value = f"{model_key}{value_index}@example.com"
|
|
|
681
|
+ elif "file" in field_key or "picture" in field_key or "drawing" in field_key:
|
|
|
682
|
+ value = f"mock/{model_key}-{field.name}-{value_index}.dat"
|
|
|
683
|
+ elif field_key == "password":
|
|
|
684
|
+ value = "pbkdf2_sha256$600000$Xn2U8rTJlAmDG6TaLgb9WQ$ElMUGivveb2WqmckmheHTb8jaxVuu1NJ2/QAAnpvZ7w="
|
|
|
685
|
+ elif field_key == "username":
|
|
|
686
|
+ value = f"{model_key}_{value_index}"
|
|
|
687
|
+ else:
|
|
|
688
|
+ value = f"{model_key}_{field.name}_{value_index}"
|
|
|
689
|
+ return value[: field.max_length]
|
|
|
690
|
+
|
|
|
691
|
+ if field.null:
|
|
|
692
|
+ return None
|
|
|
693
|
+
|
|
|
694
|
+ return f"{model_key}_{field.name}_{value_index}"
|