createsonline 0.1.26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. createsonline/__init__.py +46 -0
  2. createsonline/admin/__init__.py +7 -0
  3. createsonline/admin/content.py +526 -0
  4. createsonline/admin/crud.py +805 -0
  5. createsonline/admin/field_builder.py +559 -0
  6. createsonline/admin/integration.py +482 -0
  7. createsonline/admin/interface.py +2562 -0
  8. createsonline/admin/model_creator.py +513 -0
  9. createsonline/admin/model_manager.py +388 -0
  10. createsonline/admin/modern_dashboard.py +498 -0
  11. createsonline/admin/permissions.py +264 -0
  12. createsonline/admin/user_forms.py +594 -0
  13. createsonline/ai/__init__.py +202 -0
  14. createsonline/ai/fields.py +1226 -0
  15. createsonline/ai/orm.py +325 -0
  16. createsonline/ai/services.py +1244 -0
  17. createsonline/app.py +506 -0
  18. createsonline/auth/__init__.py +8 -0
  19. createsonline/auth/management.py +228 -0
  20. createsonline/auth/models.py +552 -0
  21. createsonline/cli/__init__.py +5 -0
  22. createsonline/cli/commands/__init__.py +122 -0
  23. createsonline/cli/commands/database.py +416 -0
  24. createsonline/cli/commands/info.py +173 -0
  25. createsonline/cli/commands/initdb.py +218 -0
  26. createsonline/cli/commands/project.py +545 -0
  27. createsonline/cli/commands/serve.py +173 -0
  28. createsonline/cli/commands/shell.py +93 -0
  29. createsonline/cli/commands/users.py +148 -0
  30. createsonline/cli/main.py +2041 -0
  31. createsonline/cli/manage.py +274 -0
  32. createsonline/config/__init__.py +9 -0
  33. createsonline/config/app.py +2577 -0
  34. createsonline/config/database.py +179 -0
  35. createsonline/config/docs.py +384 -0
  36. createsonline/config/errors.py +160 -0
  37. createsonline/config/orm.py +43 -0
  38. createsonline/config/request.py +93 -0
  39. createsonline/config/settings.py +176 -0
  40. createsonline/data/__init__.py +23 -0
  41. createsonline/data/dataframe.py +925 -0
  42. createsonline/data/io.py +453 -0
  43. createsonline/data/series.py +557 -0
  44. createsonline/database/__init__.py +60 -0
  45. createsonline/database/abstraction.py +440 -0
  46. createsonline/database/assistant.py +585 -0
  47. createsonline/database/fields.py +442 -0
  48. createsonline/database/migrations.py +132 -0
  49. createsonline/database/models.py +604 -0
  50. createsonline/database.py +438 -0
  51. createsonline/http/__init__.py +28 -0
  52. createsonline/http/client.py +535 -0
  53. createsonline/ml/__init__.py +55 -0
  54. createsonline/ml/classification.py +552 -0
  55. createsonline/ml/clustering.py +680 -0
  56. createsonline/ml/metrics.py +542 -0
  57. createsonline/ml/neural.py +560 -0
  58. createsonline/ml/preprocessing.py +784 -0
  59. createsonline/ml/regression.py +501 -0
  60. createsonline/performance/__init__.py +19 -0
  61. createsonline/performance/cache.py +444 -0
  62. createsonline/performance/compression.py +335 -0
  63. createsonline/performance/core.py +419 -0
  64. createsonline/project_init.py +789 -0
  65. createsonline/routing.py +528 -0
  66. createsonline/security/__init__.py +34 -0
  67. createsonline/security/core.py +811 -0
  68. createsonline/security/encryption.py +349 -0
  69. createsonline/server.py +295 -0
  70. createsonline/static/css/admin.css +263 -0
  71. createsonline/static/css/common.css +358 -0
  72. createsonline/static/css/dashboard.css +89 -0
  73. createsonline/static/favicon.ico +0 -0
  74. createsonline/static/icons/icon-128x128.png +0 -0
  75. createsonline/static/icons/icon-128x128.webp +0 -0
  76. createsonline/static/icons/icon-16x16.png +0 -0
  77. createsonline/static/icons/icon-16x16.webp +0 -0
  78. createsonline/static/icons/icon-180x180.png +0 -0
  79. createsonline/static/icons/icon-180x180.webp +0 -0
  80. createsonline/static/icons/icon-192x192.png +0 -0
  81. createsonline/static/icons/icon-192x192.webp +0 -0
  82. createsonline/static/icons/icon-256x256.png +0 -0
  83. createsonline/static/icons/icon-256x256.webp +0 -0
  84. createsonline/static/icons/icon-32x32.png +0 -0
  85. createsonline/static/icons/icon-32x32.webp +0 -0
  86. createsonline/static/icons/icon-384x384.png +0 -0
  87. createsonline/static/icons/icon-384x384.webp +0 -0
  88. createsonline/static/icons/icon-48x48.png +0 -0
  89. createsonline/static/icons/icon-48x48.webp +0 -0
  90. createsonline/static/icons/icon-512x512.png +0 -0
  91. createsonline/static/icons/icon-512x512.webp +0 -0
  92. createsonline/static/icons/icon-64x64.png +0 -0
  93. createsonline/static/icons/icon-64x64.webp +0 -0
  94. createsonline/static/image/android-chrome-192x192.png +0 -0
  95. createsonline/static/image/android-chrome-512x512.png +0 -0
  96. createsonline/static/image/apple-touch-icon.png +0 -0
  97. createsonline/static/image/favicon-16x16.png +0 -0
  98. createsonline/static/image/favicon-32x32.png +0 -0
  99. createsonline/static/image/favicon.ico +0 -0
  100. createsonline/static/image/favicon.svg +17 -0
  101. createsonline/static/image/icon-128x128.png +0 -0
  102. createsonline/static/image/icon-128x128.webp +0 -0
  103. createsonline/static/image/icon-16x16.png +0 -0
  104. createsonline/static/image/icon-16x16.webp +0 -0
  105. createsonline/static/image/icon-180x180.png +0 -0
  106. createsonline/static/image/icon-180x180.webp +0 -0
  107. createsonline/static/image/icon-192x192.png +0 -0
  108. createsonline/static/image/icon-192x192.webp +0 -0
  109. createsonline/static/image/icon-256x256.png +0 -0
  110. createsonline/static/image/icon-256x256.webp +0 -0
  111. createsonline/static/image/icon-32x32.png +0 -0
  112. createsonline/static/image/icon-32x32.webp +0 -0
  113. createsonline/static/image/icon-384x384.png +0 -0
  114. createsonline/static/image/icon-384x384.webp +0 -0
  115. createsonline/static/image/icon-48x48.png +0 -0
  116. createsonline/static/image/icon-48x48.webp +0 -0
  117. createsonline/static/image/icon-512x512.png +0 -0
  118. createsonline/static/image/icon-512x512.webp +0 -0
  119. createsonline/static/image/icon-64x64.png +0 -0
  120. createsonline/static/image/icon-64x64.webp +0 -0
  121. createsonline/static/image/logo-header-h100.png +0 -0
  122. createsonline/static/image/logo-header-h100.webp +0 -0
  123. createsonline/static/image/logo-header-h200@2x.png +0 -0
  124. createsonline/static/image/logo-header-h200@2x.webp +0 -0
  125. createsonline/static/image/logo.png +0 -0
  126. createsonline/static/js/admin.js +274 -0
  127. createsonline/static/site.webmanifest +35 -0
  128. createsonline/static/templates/admin/base.html +87 -0
  129. createsonline/static/templates/admin/dashboard.html +217 -0
  130. createsonline/static/templates/admin/model_form.html +270 -0
  131. createsonline/static/templates/admin/model_list.html +202 -0
  132. createsonline/static/test_script.js +15 -0
  133. createsonline/static/test_styles.css +59 -0
  134. createsonline/static_files.py +365 -0
  135. createsonline/templates/404.html +100 -0
  136. createsonline/templates/admin_login.html +169 -0
  137. createsonline/templates/base.html +102 -0
  138. createsonline/templates/index.html +151 -0
  139. createsonline/templates.py +205 -0
  140. createsonline/testing.py +322 -0
  141. createsonline/utils.py +448 -0
  142. createsonline/validation/__init__.py +49 -0
  143. createsonline/validation/fields.py +598 -0
  144. createsonline/validation/models.py +504 -0
  145. createsonline/validation/validators.py +561 -0
  146. createsonline/views.py +184 -0
  147. createsonline-0.1.26.dist-info/METADATA +46 -0
  148. createsonline-0.1.26.dist-info/RECORD +152 -0
  149. createsonline-0.1.26.dist-info/WHEEL +5 -0
  150. createsonline-0.1.26.dist-info/entry_points.txt +2 -0
  151. createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
  152. createsonline-0.1.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,559 @@
1
+ # createsonline/admin/field_builder.py
2
+ """
3
+ Field Builder - Smart field type selector for adding fields to models
4
+ """
5
+
6
+ FIELD_TEMPLATES = {
7
+ # Text Fields
8
+ "short_text": {
9
+ "label": "Short Text",
10
+ "description": "For names, titles, usernames (max 255 chars)",
11
+ "sql_type": "String",
12
+ "default_length": 150,
13
+ "examples": "username, title, name, slug, code",
14
+ "icon": "📝"
15
+ },
16
+ "long_text": {
17
+ "label": "Long Text",
18
+ "description": "For descriptions, bios, comments (unlimited)",
19
+ "sql_type": "Text",
20
+ "default_length": None,
21
+ "examples": "bio, description, content, notes",
22
+ "icon": "📄"
23
+ },
24
+ "email": {
25
+ "label": "Email",
26
+ "description": "Email address with validation",
27
+ "sql_type": "String",
28
+ "default_length": 254,
29
+ "examples": "email, contact_email, support_email",
30
+ "icon": "📧"
31
+ },
32
+ "url": {
33
+ "label": "URL",
34
+ "description": "Website or link URL",
35
+ "sql_type": "String",
36
+ "default_length": 500,
37
+ "examples": "website, profile_url, avatar_url",
38
+ "icon": "🔗"
39
+ },
40
+ "phone": {
41
+ "label": "Phone Number",
42
+ "description": "Phone number with country code",
43
+ "sql_type": "String",
44
+ "default_length": 20,
45
+ "examples": "phone, mobile, contact_number",
46
+ "icon": "📱"
47
+ },
48
+
49
+ # Number Fields
50
+ "integer": {
51
+ "label": "Integer",
52
+ "description": "Whole numbers (IDs, counts, ages)",
53
+ "sql_type": "Integer",
54
+ "default_length": None,
55
+ "examples": "age, count, quantity, order_number",
56
+ "icon": "🔢"
57
+ },
58
+ "decimal": {
59
+ "label": "Decimal",
60
+ "description": "Numbers with decimals (prices, ratings)",
61
+ "sql_type": "Numeric",
62
+ "default_length": "(10, 2)",
63
+ "examples": "price, rating, salary, discount",
64
+ "icon": "💰"
65
+ },
66
+ "float": {
67
+ "label": "Float",
68
+ "description": "Floating point numbers",
69
+ "sql_type": "Float",
70
+ "default_length": None,
71
+ "examples": "percentage, score, weight",
72
+ "icon": "📊"
73
+ },
74
+
75
+ # Boolean & Choice Fields
76
+ "boolean": {
77
+ "label": "Boolean (Yes/No)",
78
+ "description": "True/False checkbox",
79
+ "sql_type": "Boolean",
80
+ "default_length": None,
81
+ "examples": "is_active, is_verified, is_featured",
82
+ "icon": "✓"
83
+ },
84
+ "choice": {
85
+ "label": "Choice (Enum)",
86
+ "description": "Dropdown selection from predefined options",
87
+ "sql_type": "Enum",
88
+ "default_length": None,
89
+ "examples": "status, role, priority, category",
90
+ "icon": "📋"
91
+ },
92
+
93
+ # Date & Time Fields
94
+ "date": {
95
+ "label": "Date",
96
+ "description": "Calendar date (YYYY-MM-DD)",
97
+ "sql_type": "Date",
98
+ "default_length": None,
99
+ "examples": "birth_date, start_date, deadline",
100
+ "icon": "📅"
101
+ },
102
+ "datetime": {
103
+ "label": "Date & Time",
104
+ "description": "Full date with time",
105
+ "sql_type": "DateTime",
106
+ "default_length": None,
107
+ "examples": "created_at, updated_at, published_at",
108
+ "icon": "🕐"
109
+ },
110
+ "time": {
111
+ "label": "Time",
112
+ "description": "Time of day (HH:MM:SS)",
113
+ "sql_type": "Time",
114
+ "default_length": None,
115
+ "examples": "opening_time, closing_time, meeting_time",
116
+ "icon": "⏰"
117
+ },
118
+
119
+ # File & Media Fields
120
+ "image": {
121
+ "label": "Image",
122
+ "description": "Image file path/URL",
123
+ "sql_type": "String",
124
+ "default_length": 500,
125
+ "examples": "avatar, profile_picture, thumbnail, logo",
126
+ "icon": "🖼️"
127
+ },
128
+ "file": {
129
+ "label": "File",
130
+ "description": "Document or file path/URL",
131
+ "sql_type": "String",
132
+ "default_length": 500,
133
+ "examples": "resume, attachment, document, certificate",
134
+ "icon": "📎"
135
+ },
136
+
137
+ # JSON & Special Fields
138
+ "json": {
139
+ "label": "JSON",
140
+ "description": "Structured data in JSON format",
141
+ "sql_type": "JSON",
142
+ "default_length": None,
143
+ "examples": "settings, metadata, preferences, config",
144
+ "icon": "🗃️"
145
+ },
146
+ "uuid": {
147
+ "label": "UUID",
148
+ "description": "Universally unique identifier",
149
+ "sql_type": "String",
150
+ "default_length": 36,
151
+ "examples": "external_id, api_key, token",
152
+ "icon": "🔑"
153
+ },
154
+
155
+ # Common Use Cases
156
+ "slug": {
157
+ "label": "Slug",
158
+ "description": "URL-friendly identifier (lowercase, hyphens)",
159
+ "sql_type": "String",
160
+ "default_length": 200,
161
+ "examples": "slug, url_slug, permalink",
162
+ "icon": "🔗"
163
+ },
164
+ "color": {
165
+ "label": "Color",
166
+ "description": "Hex color code (#RRGGBB)",
167
+ "sql_type": "String",
168
+ "default_length": 7,
169
+ "examples": "color, theme_color, background_color",
170
+ "icon": "🎨"
171
+ },
172
+ "ip_address": {
173
+ "label": "IP Address",
174
+ "description": "IPv4 or IPv6 address",
175
+ "sql_type": "String",
176
+ "default_length": 45,
177
+ "examples": "ip_address, last_login_ip, created_from_ip",
178
+ "icon": "🌐"
179
+ },
180
+ "password": {
181
+ "label": "Password Hash",
182
+ "description": "Hashed password (DO NOT store plain text!)",
183
+ "sql_type": "String",
184
+ "default_length": 128,
185
+ "examples": "password_hash, api_secret_hash",
186
+ "icon": "🔒"
187
+ },
188
+ }
189
+
190
+
191
+ def render_add_field_form(model_name: str) -> str:
192
+ """Render the smart add field form"""
193
+
194
+ # Group field types by category
195
+ categories = {
196
+ "Text": ["short_text", "long_text", "email", "url", "phone", "slug"],
197
+ "Numbers": ["integer", "decimal", "float"],
198
+ "Date & Time": ["date", "datetime", "time"],
199
+ "Boolean & Choice": ["boolean", "choice"],
200
+ "Files & Media": ["image", "file"],
201
+ "Advanced": ["json", "uuid", "color", "ip_address", "password"],
202
+ }
203
+
204
+ # Build field type selector HTML
205
+ field_types_html = ""
206
+ for category, field_keys in categories.items():
207
+ field_types_html += f'<optgroup label="{category}">'
208
+ for key in field_keys:
209
+ template = FIELD_TEMPLATES[key]
210
+ field_types_html += f'''
211
+ <option value="{key}"
212
+ data-sql-type="{template['sql_type']}"
213
+ data-length="{template['default_length'] or ''}"
214
+ data-examples="{template['examples']}"
215
+ data-description="{template['description']}">
216
+ {template['icon']} {template['label']}
217
+ </option>
218
+ '''
219
+ field_types_html += '</optgroup>'
220
+
221
+ html = f"""<!DOCTYPE html>
222
+ <html lang="en">
223
+ <head>
224
+ <meta charset="UTF-8">
225
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
226
+ <title>Add Field to {model_name} - CREATESONLINE Admin</title>
227
+ <style>
228
+ * {{
229
+ margin: 0;
230
+ padding: 0;
231
+ box-sizing: border-box;
232
+ }}
233
+
234
+ body {{
235
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
236
+ background: #ffffff;
237
+ color: #000000;
238
+ }}
239
+
240
+ .container {{
241
+ max-width: 1000px;
242
+ margin: 0 auto;
243
+ padding: 40px 20px;
244
+ }}
245
+
246
+ .header {{
247
+ margin-bottom: 40px;
248
+ }}
249
+
250
+ .logo {{
251
+ margin-bottom: 20px;
252
+ }}
253
+
254
+ .logo img {{
255
+ height: 40px;
256
+ }}
257
+
258
+ h1 {{
259
+ font-size: 32px;
260
+ font-weight: 700;
261
+ color: #000;
262
+ margin-bottom: 10px;
263
+ }}
264
+
265
+ .breadcrumb {{
266
+ color: #666;
267
+ font-size: 14px;
268
+ }}
269
+
270
+ .breadcrumb a {{
271
+ color: #000;
272
+ text-decoration: none;
273
+ }}
274
+
275
+ .breadcrumb a:hover {{
276
+ text-decoration: underline;
277
+ }}
278
+
279
+ .form-card {{
280
+ background: #fff;
281
+ border: 2px solid #e0e0e0;
282
+ border-radius: 12px;
283
+ padding: 40px;
284
+ margin-bottom: 30px;
285
+ }}
286
+
287
+ .form-group {{
288
+ margin-bottom: 25px;
289
+ }}
290
+
291
+ label {{
292
+ display: block;
293
+ font-weight: 600;
294
+ margin-bottom: 8px;
295
+ color: #000;
296
+ }}
297
+
298
+ .required {{
299
+ color: #ff0000;
300
+ }}
301
+
302
+ input[type="text"],
303
+ select,
304
+ textarea {{
305
+ width: 100%;
306
+ padding: 12px;
307
+ border: 2px solid #e0e0e0;
308
+ border-radius: 8px;
309
+ font-size: 15px;
310
+ font-family: inherit;
311
+ transition: all 0.2s;
312
+ }}
313
+
314
+ input:focus,
315
+ select:focus,
316
+ textarea:focus {{
317
+ outline: none;
318
+ border-color: #000;
319
+ }}
320
+
321
+ .help-text {{
322
+ font-size: 13px;
323
+ color: #666;
324
+ margin-top: 6px;
325
+ }}
326
+
327
+ .field-preview {{
328
+ background: #f5f5f5;
329
+ padding: 20px;
330
+ border-radius: 8px;
331
+ margin-top: 10px;
332
+ display: none;
333
+ }}
334
+
335
+ .field-preview.active {{
336
+ display: block;
337
+ }}
338
+
339
+ .preview-label {{
340
+ font-size: 12px;
341
+ font-weight: 600;
342
+ color: #666;
343
+ text-transform: uppercase;
344
+ margin-bottom: 8px;
345
+ }}
346
+
347
+ .preview-content {{
348
+ font-size: 14px;
349
+ color: #000;
350
+ }}
351
+
352
+ .checkbox-group {{
353
+ display: flex;
354
+ gap: 20px;
355
+ flex-wrap: wrap;
356
+ }}
357
+
358
+ .checkbox-item {{
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 8px;
362
+ }}
363
+
364
+ input[type="checkbox"] {{
365
+ width: 20px;
366
+ height: 20px;
367
+ cursor: pointer;
368
+ }}
369
+
370
+ .btn {{
371
+ padding: 14px 28px;
372
+ border: none;
373
+ border-radius: 8px;
374
+ font-size: 15px;
375
+ font-weight: 600;
376
+ cursor: pointer;
377
+ text-decoration: none;
378
+ display: inline-block;
379
+ transition: all 0.2s;
380
+ }}
381
+
382
+ .btn-primary {{
383
+ background: #000;
384
+ color: #fff;
385
+ }}
386
+
387
+ .btn-primary:hover {{
388
+ background: #333;
389
+ }}
390
+
391
+ .btn-secondary {{
392
+ background: #f5f5f5;
393
+ color: #000;
394
+ border: 2px solid #e0e0e0;
395
+ }}
396
+
397
+ .btn-secondary:hover {{
398
+ background: #e0e0e0;
399
+ }}
400
+
401
+ .actions {{
402
+ display: flex;
403
+ gap: 15px;
404
+ margin-top: 30px;
405
+ }}
406
+
407
+ .examples-grid {{
408
+ display: grid;
409
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
410
+ gap: 10px;
411
+ margin-top: 15px;
412
+ }}
413
+
414
+ .example-chip {{
415
+ padding: 8px 12px;
416
+ background: #f0f0f0;
417
+ border-radius: 6px;
418
+ font-size: 13px;
419
+ text-align: center;
420
+ color: #333;
421
+ }}
422
+ </style>
423
+ </head>
424
+ <body>
425
+ <div class="container">
426
+ <div class="header">
427
+ <div class="logo">
428
+ <img src="/logo.png" alt="Logo" onerror="this.style.display='none'">
429
+ </div>
430
+ <h1>Add Field to {model_name}</h1>
431
+ <div class="breadcrumb">
432
+ <a href="/admin">Admin</a> /
433
+ <a href="/admin/model-manager/{model_name.lower()}">Models</a> /
434
+ <a href="/admin/model-manager/{model_name.lower()}">{model_name}</a> /
435
+ Add Field
436
+ </div>
437
+ </div>
438
+
439
+ <form method="POST" id="addFieldForm">
440
+ <div class="form-card">
441
+ <div class="form-group">
442
+ <label for="field_type">Field Type <span class="required">*</span></label>
443
+ <select id="field_type" name="field_type" required>
444
+ <option value="">Select field type...</option>
445
+ {field_types_html}
446
+ </select>
447
+ <div class="help-text">Choose the type of data this field will store</div>
448
+ <div class="field-preview" id="fieldPreview">
449
+ <div class="preview-label">Description</div>
450
+ <div class="preview-content" id="previewDescription"></div>
451
+ <div class="preview-label" style="margin-top: 15px;">Common Examples</div>
452
+ <div class="examples-grid" id="previewExamples"></div>
453
+ </div>
454
+ </div>
455
+
456
+ <div class="form-group">
457
+ <label for="field_name">Field Name <span class="required">*</span></label>
458
+ <input type="text" id="field_name" name="field_name" placeholder="e.g., role, phone_number, bio" required>
459
+ <div class="help-text">Lowercase letters, numbers, and underscores only (e.g., user_role, phone_number)</div>
460
+ </div>
461
+
462
+ <div class="form-group" id="lengthGroup">
463
+ <label for="field_length">Max Length</label>
464
+ <input type="number" id="field_length" name="field_length" placeholder="150">
465
+ <div class="help-text">Maximum character length (leave empty for unlimited)</div>
466
+ </div>
467
+
468
+ <div class="form-group">
469
+ <label for="default_value">Default Value</label>
470
+ <input type="text" id="default_value" name="default_value" placeholder="Optional default value">
471
+ <div class="help-text">Value to use when creating new records (optional)</div>
472
+ </div>
473
+
474
+ <div class="form-group">
475
+ <label>Constraints</label>
476
+ <div class="checkbox-group">
477
+ <div class="checkbox-item">
478
+ <input type="checkbox" id="required" name="required" value="1">
479
+ <label for="required" style="margin: 0;">Required (NOT NULL)</label>
480
+ </div>
481
+ <div class="checkbox-item">
482
+ <input type="checkbox" id="unique" name="unique" value="1">
483
+ <label for="unique" style="margin: 0;">Unique</label>
484
+ </div>
485
+ <div class="checkbox-item">
486
+ <input type="checkbox" id="index" name="index" value="1" checked>
487
+ <label for="index" style="margin: 0;">Indexed (faster searches)</label>
488
+ </div>
489
+ </div>
490
+ </div>
491
+
492
+ <div class="form-group">
493
+ <label for="help_text">Help Text</label>
494
+ <textarea id="help_text" name="help_text" rows="2" placeholder="Helpful description for users filling out forms"></textarea>
495
+ <div class="help-text">This will be shown in forms to help users understand what to enter</div>
496
+ </div>
497
+ </div>
498
+
499
+ <div class="actions">
500
+ <button type="submit" class="btn btn-primary">✓ Add Field</button>
501
+ <a href="/admin/model-manager/{model_name.lower()}" class="btn btn-secondary">Cancel</a>
502
+ </div>
503
+ </form>
504
+ </div>
505
+
506
+ <script>
507
+ // Field type selector preview
508
+ const fieldTypeSelect = document.getElementById('field_type');
509
+ const fieldPreview = document.getElementById('fieldPreview');
510
+ const previewDescription = document.getElementById('previewDescription');
511
+ const previewExamples = document.getElementById('previewExamples');
512
+ const lengthGroup = document.getElementById('lengthGroup');
513
+ const fieldLengthInput = document.getElementById('field_length');
514
+
515
+ fieldTypeSelect.addEventListener('change', function() {{
516
+ const selectedOption = this.options[this.selectedIndex];
517
+
518
+ if (selectedOption.value) {{
519
+ const description = selectedOption.getAttribute('data-description');
520
+ const examples = selectedOption.getAttribute('data-examples').split(', ');
521
+ const length = selectedOption.getAttribute('data-length');
522
+
523
+ // Show preview
524
+ fieldPreview.classList.add('active');
525
+ previewDescription.textContent = description;
526
+
527
+ // Show examples
528
+ previewExamples.innerHTML = examples.map(ex =>
529
+ `<div class="example-chip">${{ex}}</div>`
530
+ ).join('');
531
+
532
+ // Update length field
533
+ if (length) {{
534
+ lengthGroup.style.display = 'block';
535
+ fieldLengthInput.value = length;
536
+ }} else {{
537
+ lengthGroup.style.display = 'none';
538
+ fieldLengthInput.value = '';
539
+ }}
540
+ }} else {{
541
+ fieldPreview.classList.remove('active');
542
+ lengthGroup.style.display = 'block';
543
+ }}
544
+ }});
545
+
546
+ // Auto-format field name
547
+ const fieldNameInput = document.getElementById('field_name');
548
+ fieldNameInput.addEventListener('input', function() {{
549
+ this.value = this.value
550
+ .toLowerCase()
551
+ .replace(/[^a-z0-9_]/g, '_')
552
+ .replace(/_+/g, '_')
553
+ .replace(/^_|_$/g, '');
554
+ }});
555
+ </script>
556
+ </body>
557
+ </html>
558
+ """
559
+ return html