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.
- createsonline/__init__.py +46 -0
- createsonline/admin/__init__.py +7 -0
- createsonline/admin/content.py +526 -0
- createsonline/admin/crud.py +805 -0
- createsonline/admin/field_builder.py +559 -0
- createsonline/admin/integration.py +482 -0
- createsonline/admin/interface.py +2562 -0
- createsonline/admin/model_creator.py +513 -0
- createsonline/admin/model_manager.py +388 -0
- createsonline/admin/modern_dashboard.py +498 -0
- createsonline/admin/permissions.py +264 -0
- createsonline/admin/user_forms.py +594 -0
- createsonline/ai/__init__.py +202 -0
- createsonline/ai/fields.py +1226 -0
- createsonline/ai/orm.py +325 -0
- createsonline/ai/services.py +1244 -0
- createsonline/app.py +506 -0
- createsonline/auth/__init__.py +8 -0
- createsonline/auth/management.py +228 -0
- createsonline/auth/models.py +552 -0
- createsonline/cli/__init__.py +5 -0
- createsonline/cli/commands/__init__.py +122 -0
- createsonline/cli/commands/database.py +416 -0
- createsonline/cli/commands/info.py +173 -0
- createsonline/cli/commands/initdb.py +218 -0
- createsonline/cli/commands/project.py +545 -0
- createsonline/cli/commands/serve.py +173 -0
- createsonline/cli/commands/shell.py +93 -0
- createsonline/cli/commands/users.py +148 -0
- createsonline/cli/main.py +2041 -0
- createsonline/cli/manage.py +274 -0
- createsonline/config/__init__.py +9 -0
- createsonline/config/app.py +2577 -0
- createsonline/config/database.py +179 -0
- createsonline/config/docs.py +384 -0
- createsonline/config/errors.py +160 -0
- createsonline/config/orm.py +43 -0
- createsonline/config/request.py +93 -0
- createsonline/config/settings.py +176 -0
- createsonline/data/__init__.py +23 -0
- createsonline/data/dataframe.py +925 -0
- createsonline/data/io.py +453 -0
- createsonline/data/series.py +557 -0
- createsonline/database/__init__.py +60 -0
- createsonline/database/abstraction.py +440 -0
- createsonline/database/assistant.py +585 -0
- createsonline/database/fields.py +442 -0
- createsonline/database/migrations.py +132 -0
- createsonline/database/models.py +604 -0
- createsonline/database.py +438 -0
- createsonline/http/__init__.py +28 -0
- createsonline/http/client.py +535 -0
- createsonline/ml/__init__.py +55 -0
- createsonline/ml/classification.py +552 -0
- createsonline/ml/clustering.py +680 -0
- createsonline/ml/metrics.py +542 -0
- createsonline/ml/neural.py +560 -0
- createsonline/ml/preprocessing.py +784 -0
- createsonline/ml/regression.py +501 -0
- createsonline/performance/__init__.py +19 -0
- createsonline/performance/cache.py +444 -0
- createsonline/performance/compression.py +335 -0
- createsonline/performance/core.py +419 -0
- createsonline/project_init.py +789 -0
- createsonline/routing.py +528 -0
- createsonline/security/__init__.py +34 -0
- createsonline/security/core.py +811 -0
- createsonline/security/encryption.py +349 -0
- createsonline/server.py +295 -0
- createsonline/static/css/admin.css +263 -0
- createsonline/static/css/common.css +358 -0
- createsonline/static/css/dashboard.css +89 -0
- createsonline/static/favicon.ico +0 -0
- createsonline/static/icons/icon-128x128.png +0 -0
- createsonline/static/icons/icon-128x128.webp +0 -0
- createsonline/static/icons/icon-16x16.png +0 -0
- createsonline/static/icons/icon-16x16.webp +0 -0
- createsonline/static/icons/icon-180x180.png +0 -0
- createsonline/static/icons/icon-180x180.webp +0 -0
- createsonline/static/icons/icon-192x192.png +0 -0
- createsonline/static/icons/icon-192x192.webp +0 -0
- createsonline/static/icons/icon-256x256.png +0 -0
- createsonline/static/icons/icon-256x256.webp +0 -0
- createsonline/static/icons/icon-32x32.png +0 -0
- createsonline/static/icons/icon-32x32.webp +0 -0
- createsonline/static/icons/icon-384x384.png +0 -0
- createsonline/static/icons/icon-384x384.webp +0 -0
- createsonline/static/icons/icon-48x48.png +0 -0
- createsonline/static/icons/icon-48x48.webp +0 -0
- createsonline/static/icons/icon-512x512.png +0 -0
- createsonline/static/icons/icon-512x512.webp +0 -0
- createsonline/static/icons/icon-64x64.png +0 -0
- createsonline/static/icons/icon-64x64.webp +0 -0
- createsonline/static/image/android-chrome-192x192.png +0 -0
- createsonline/static/image/android-chrome-512x512.png +0 -0
- createsonline/static/image/apple-touch-icon.png +0 -0
- createsonline/static/image/favicon-16x16.png +0 -0
- createsonline/static/image/favicon-32x32.png +0 -0
- createsonline/static/image/favicon.ico +0 -0
- createsonline/static/image/favicon.svg +17 -0
- createsonline/static/image/icon-128x128.png +0 -0
- createsonline/static/image/icon-128x128.webp +0 -0
- createsonline/static/image/icon-16x16.png +0 -0
- createsonline/static/image/icon-16x16.webp +0 -0
- createsonline/static/image/icon-180x180.png +0 -0
- createsonline/static/image/icon-180x180.webp +0 -0
- createsonline/static/image/icon-192x192.png +0 -0
- createsonline/static/image/icon-192x192.webp +0 -0
- createsonline/static/image/icon-256x256.png +0 -0
- createsonline/static/image/icon-256x256.webp +0 -0
- createsonline/static/image/icon-32x32.png +0 -0
- createsonline/static/image/icon-32x32.webp +0 -0
- createsonline/static/image/icon-384x384.png +0 -0
- createsonline/static/image/icon-384x384.webp +0 -0
- createsonline/static/image/icon-48x48.png +0 -0
- createsonline/static/image/icon-48x48.webp +0 -0
- createsonline/static/image/icon-512x512.png +0 -0
- createsonline/static/image/icon-512x512.webp +0 -0
- createsonline/static/image/icon-64x64.png +0 -0
- createsonline/static/image/icon-64x64.webp +0 -0
- createsonline/static/image/logo-header-h100.png +0 -0
- createsonline/static/image/logo-header-h100.webp +0 -0
- createsonline/static/image/logo-header-h200@2x.png +0 -0
- createsonline/static/image/logo-header-h200@2x.webp +0 -0
- createsonline/static/image/logo.png +0 -0
- createsonline/static/js/admin.js +274 -0
- createsonline/static/site.webmanifest +35 -0
- createsonline/static/templates/admin/base.html +87 -0
- createsonline/static/templates/admin/dashboard.html +217 -0
- createsonline/static/templates/admin/model_form.html +270 -0
- createsonline/static/templates/admin/model_list.html +202 -0
- createsonline/static/test_script.js +15 -0
- createsonline/static/test_styles.css +59 -0
- createsonline/static_files.py +365 -0
- createsonline/templates/404.html +100 -0
- createsonline/templates/admin_login.html +169 -0
- createsonline/templates/base.html +102 -0
- createsonline/templates/index.html +151 -0
- createsonline/templates.py +205 -0
- createsonline/testing.py +322 -0
- createsonline/utils.py +448 -0
- createsonline/validation/__init__.py +49 -0
- createsonline/validation/fields.py +598 -0
- createsonline/validation/models.py +504 -0
- createsonline/validation/validators.py +561 -0
- createsonline/views.py +184 -0
- createsonline-0.1.26.dist-info/METADATA +46 -0
- createsonline-0.1.26.dist-info/RECORD +152 -0
- createsonline-0.1.26.dist-info/WHEEL +5 -0
- createsonline-0.1.26.dist-info/entry_points.txt +2 -0
- createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
- createsonline-0.1.26.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# createsonline/admin/model_creator.py
|
|
2
|
+
"""
|
|
3
|
+
Model Creator - Create new SQLAlchemy models dynamically
|
|
4
|
+
"""
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelCreator:
|
|
11
|
+
"""Create new models dynamically"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, admin_site):
|
|
14
|
+
self.admin_site = admin_site
|
|
15
|
+
|
|
16
|
+
async def render_form(self, errors: Dict = None) -> str:
|
|
17
|
+
"""Render model creation form"""
|
|
18
|
+
errors = errors or {}
|
|
19
|
+
|
|
20
|
+
return f"""<!DOCTYPE html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="UTF-8">
|
|
24
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
25
|
+
<title>Create Model - CREATESONLINE Admin</title>
|
|
26
|
+
<style>
|
|
27
|
+
* {{
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}}
|
|
32
|
+
|
|
33
|
+
body {{
|
|
34
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
35
|
+
background: #0a0a0a;
|
|
36
|
+
color: #ffffff;
|
|
37
|
+
padding: 20px;
|
|
38
|
+
}}
|
|
39
|
+
|
|
40
|
+
.container {{
|
|
41
|
+
max-width: 900px;
|
|
42
|
+
margin: 0 auto;
|
|
43
|
+
}}
|
|
44
|
+
|
|
45
|
+
.header {{
|
|
46
|
+
background: #1a1a1a;
|
|
47
|
+
padding: 30px 40px;
|
|
48
|
+
border-radius: 12px;
|
|
49
|
+
border: 1px solid #2a2a2a;
|
|
50
|
+
margin-bottom: 30px;
|
|
51
|
+
}}
|
|
52
|
+
|
|
53
|
+
h1 {{
|
|
54
|
+
font-size: 2.5em;
|
|
55
|
+
background: linear-gradient(135deg, #ffffff 0%, #a0a0a0 100%);
|
|
56
|
+
-webkit-background-clip: text;
|
|
57
|
+
-webkit-text-fill-color: transparent;
|
|
58
|
+
}}
|
|
59
|
+
|
|
60
|
+
.breadcrumb {{
|
|
61
|
+
margin-top: 15px;
|
|
62
|
+
color: #888;
|
|
63
|
+
}}
|
|
64
|
+
|
|
65
|
+
.breadcrumb a {{
|
|
66
|
+
color: #fff;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
}}
|
|
69
|
+
|
|
70
|
+
.form-container {{
|
|
71
|
+
background: #1a1a1a;
|
|
72
|
+
padding: 40px;
|
|
73
|
+
border-radius: 12px;
|
|
74
|
+
border: 1px solid #2a2a2a;
|
|
75
|
+
}}
|
|
76
|
+
|
|
77
|
+
.form-section {{
|
|
78
|
+
margin-bottom: 35px;
|
|
79
|
+
padding-bottom: 35px;
|
|
80
|
+
border-bottom: 1px solid #2a2a2a;
|
|
81
|
+
}}
|
|
82
|
+
|
|
83
|
+
h3 {{
|
|
84
|
+
font-size: 1.3em;
|
|
85
|
+
margin-bottom: 20px;
|
|
86
|
+
color: #ccc;
|
|
87
|
+
}}
|
|
88
|
+
|
|
89
|
+
.form-group {{
|
|
90
|
+
margin-bottom: 20px;
|
|
91
|
+
}}
|
|
92
|
+
|
|
93
|
+
label {{
|
|
94
|
+
display: block;
|
|
95
|
+
margin-bottom: 8px;
|
|
96
|
+
font-weight: 500;
|
|
97
|
+
color: #ccc;
|
|
98
|
+
}}
|
|
99
|
+
|
|
100
|
+
input[type="text"], select {{
|
|
101
|
+
width: 100%;
|
|
102
|
+
padding: 12px 15px;
|
|
103
|
+
background: #0a0a0a;
|
|
104
|
+
border: 1px solid #3a3a3a;
|
|
105
|
+
border-radius: 6px;
|
|
106
|
+
color: #fff;
|
|
107
|
+
font-size: 1em;
|
|
108
|
+
}}
|
|
109
|
+
|
|
110
|
+
.help-text {{
|
|
111
|
+
color: #888;
|
|
112
|
+
font-size: 0.9em;
|
|
113
|
+
margin-top: 5px;
|
|
114
|
+
}}
|
|
115
|
+
|
|
116
|
+
.field-row {{
|
|
117
|
+
background: #0a0a0a;
|
|
118
|
+
padding: 20px;
|
|
119
|
+
border-radius: 8px;
|
|
120
|
+
border: 1px solid #3a3a3a;
|
|
121
|
+
margin-bottom: 15px;
|
|
122
|
+
}}
|
|
123
|
+
|
|
124
|
+
.field-grid {{
|
|
125
|
+
display: grid;
|
|
126
|
+
grid-template-columns: 2fr 1fr 1fr auto;
|
|
127
|
+
gap: 15px;
|
|
128
|
+
align-items: end;
|
|
129
|
+
}}
|
|
130
|
+
|
|
131
|
+
.btn {{
|
|
132
|
+
padding: 12px 30px;
|
|
133
|
+
border: none;
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
transition: all 0.3s;
|
|
138
|
+
}}
|
|
139
|
+
|
|
140
|
+
.btn-primary {{
|
|
141
|
+
background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
|
|
142
|
+
color: #0a0a0a;
|
|
143
|
+
}}
|
|
144
|
+
|
|
145
|
+
.btn-secondary {{
|
|
146
|
+
background: #2a2a2a;
|
|
147
|
+
color: #fff;
|
|
148
|
+
}}
|
|
149
|
+
|
|
150
|
+
.btn-small {{
|
|
151
|
+
padding: 8px 15px;
|
|
152
|
+
font-size: 0.9em;
|
|
153
|
+
}}
|
|
154
|
+
|
|
155
|
+
.btn-danger {{
|
|
156
|
+
background: #442222;
|
|
157
|
+
color: #fff;
|
|
158
|
+
}}
|
|
159
|
+
|
|
160
|
+
.error {{
|
|
161
|
+
color: #ff4444;
|
|
162
|
+
margin-top: 5px;
|
|
163
|
+
font-size: 0.9em;
|
|
164
|
+
}}
|
|
165
|
+
|
|
166
|
+
.info-box {{
|
|
167
|
+
background: rgba(68, 136, 255, 0.1);
|
|
168
|
+
border: 1px solid #4488ff;
|
|
169
|
+
padding: 20px;
|
|
170
|
+
border-radius: 8px;
|
|
171
|
+
margin-bottom: 30px;
|
|
172
|
+
}}
|
|
173
|
+
|
|
174
|
+
.info-box h4 {{
|
|
175
|
+
color: #4488ff;
|
|
176
|
+
margin-bottom: 10px;
|
|
177
|
+
}}
|
|
178
|
+
|
|
179
|
+
.info-box ul {{
|
|
180
|
+
margin-left: 20px;
|
|
181
|
+
color: #aaa;
|
|
182
|
+
}}
|
|
183
|
+
|
|
184
|
+
.form-actions {{
|
|
185
|
+
display: flex;
|
|
186
|
+
gap: 15px;
|
|
187
|
+
margin-top: 30px;
|
|
188
|
+
}}
|
|
189
|
+
</style>
|
|
190
|
+
</head>
|
|
191
|
+
<body>
|
|
192
|
+
<div class="container">
|
|
193
|
+
<div class="header">
|
|
194
|
+
<h1>Create New Model</h1>
|
|
195
|
+
<div class="breadcrumb">
|
|
196
|
+
<a href="/admin">Admin</a> / Create Model
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div class="info-box">
|
|
201
|
+
<h4>📋 How it works</h4>
|
|
202
|
+
<ul>
|
|
203
|
+
<li>Define your model name and fields</li>
|
|
204
|
+
<li>The system will generate a Python file in your project</li>
|
|
205
|
+
<li>Run migrations to create the database table</li>
|
|
206
|
+
<li>The model will automatically appear in the admin</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<form method="POST" action="/admin/create-model" class="form-container">
|
|
211
|
+
<div class="form-section">
|
|
212
|
+
<h3>Model Information</h3>
|
|
213
|
+
|
|
214
|
+
<div class="form-group">
|
|
215
|
+
<label>Model Name *</label>
|
|
216
|
+
<input type="text" name="model_name" required placeholder="e.g., Post, Product, Article">
|
|
217
|
+
<div class="help-text">Use singular form, PascalCase (e.g., BlogPost, UserProfile)</div>
|
|
218
|
+
{f'<div class="error">{errors.get("model_name", "")}</div>' if errors.get("model_name") else ''}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div class="form-group">
|
|
222
|
+
<label>Table Name (optional)</label>
|
|
223
|
+
<input type="text" name="table_name" placeholder="Auto-generated from model name">
|
|
224
|
+
<div class="help-text">Leave blank to auto-generate (e.g., blog_posts)</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<div class="form-section">
|
|
229
|
+
<h3>Fields</h3>
|
|
230
|
+
|
|
231
|
+
<div id="fields-container">
|
|
232
|
+
<!-- Empty by default - user adds fields -->
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<button type="button" class="btn btn-secondary" onclick="addField()" style="margin-top: 15px;">
|
|
236
|
+
+ Add Field
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="form-section">
|
|
241
|
+
<h3>Relationships (Optional)</h3>
|
|
242
|
+
|
|
243
|
+
<div id="relationships-container">
|
|
244
|
+
<!-- Relationships will be added here -->
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<button type="button" class="btn btn-secondary" onclick="addRelationship()" style="margin-top: 15px;">
|
|
248
|
+
+ Add Relationship
|
|
249
|
+
</button>
|
|
250
|
+
|
|
251
|
+
<div class="help-text" style="margin-top: 15px;">
|
|
252
|
+
<strong>Relationship Types:</strong><br>
|
|
253
|
+
• <strong>One-to-Many:</strong> One User → Many Posts (User has many Posts)<br>
|
|
254
|
+
• <strong>Many-to-One:</strong> Many Posts → One User (Post belongs to User)<br>
|
|
255
|
+
• <strong>One-to-One:</strong> One User → One Profile (User has one Profile)<br>
|
|
256
|
+
• <strong>Many-to-Many:</strong> Many Users ↔ Many Groups (Users have many Groups, Groups have many Users)
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div class="form-actions">
|
|
261
|
+
<button type="submit" class="btn btn-primary">Create Model</button>
|
|
262
|
+
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
|
263
|
+
</div>
|
|
264
|
+
</form>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<script>
|
|
268
|
+
function addField() {{
|
|
269
|
+
const container = document.getElementById('fields-container');
|
|
270
|
+
const fieldRow = document.createElement('div');
|
|
271
|
+
fieldRow.className = 'field-row';
|
|
272
|
+
fieldRow.innerHTML = `
|
|
273
|
+
<div class="field-grid">
|
|
274
|
+
<div>
|
|
275
|
+
<label>Field Name</label>
|
|
276
|
+
<input type="text" name="field_name[]" placeholder="e.g., title, price, status" required>
|
|
277
|
+
</div>
|
|
278
|
+
<div>
|
|
279
|
+
<label>Type</label>
|
|
280
|
+
<select name="field_type[]">
|
|
281
|
+
<option value="string" selected>String</option>
|
|
282
|
+
<option value="text">Text</option>
|
|
283
|
+
<option value="integer">Integer</option>
|
|
284
|
+
<option value="boolean">Boolean</option>
|
|
285
|
+
<option value="datetime">DateTime</option>
|
|
286
|
+
<option value="float">Float</option>
|
|
287
|
+
</select>
|
|
288
|
+
</div>
|
|
289
|
+
<div>
|
|
290
|
+
<label>Max Length</label>
|
|
291
|
+
<input type="text" name="field_length[]" placeholder="e.g., 200">
|
|
292
|
+
</div>
|
|
293
|
+
<div>
|
|
294
|
+
<button type="button" class="btn btn-danger btn-small" onclick="removeField(this)">×</button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
`;
|
|
298
|
+
container.appendChild(fieldRow);
|
|
299
|
+
}}
|
|
300
|
+
|
|
301
|
+
function removeField(btn) {{
|
|
302
|
+
btn.closest('.field-row').remove();
|
|
303
|
+
}}
|
|
304
|
+
|
|
305
|
+
function addRelationship() {{
|
|
306
|
+
const container = document.getElementById('relationships-container');
|
|
307
|
+
const relRow = document.createElement('div');
|
|
308
|
+
relRow.className = 'field-row';
|
|
309
|
+
relRow.innerHTML = `
|
|
310
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 15px; align-items: end;">
|
|
311
|
+
<div>
|
|
312
|
+
<label>Related Model</label>
|
|
313
|
+
<input type="text" name="rel_model[]" placeholder="e.g., User, Category" required>
|
|
314
|
+
</div>
|
|
315
|
+
<div>
|
|
316
|
+
<label>Relationship Type</label>
|
|
317
|
+
<select name="rel_type[]">
|
|
318
|
+
<option value="many_to_one">Many-to-One (belongs to)</option>
|
|
319
|
+
<option value="one_to_many">One-to-Many (has many)</option>
|
|
320
|
+
<option value="one_to_one">One-to-One</option>
|
|
321
|
+
<option value="many_to_many">Many-to-Many</option>
|
|
322
|
+
</select>
|
|
323
|
+
</div>
|
|
324
|
+
<div>
|
|
325
|
+
<label>Field Name</label>
|
|
326
|
+
<input type="text" name="rel_field[]" placeholder="e.g., author, category" required>
|
|
327
|
+
</div>
|
|
328
|
+
<div>
|
|
329
|
+
<button type="button" class="btn btn-danger btn-small" onclick="removeField(this)">×</button>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
`;
|
|
333
|
+
container.appendChild(relRow);
|
|
334
|
+
}}
|
|
335
|
+
</script>
|
|
336
|
+
</body>
|
|
337
|
+
</html>
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
async def create_model(self, data: Dict) -> tuple:
|
|
341
|
+
"""
|
|
342
|
+
Create a new model file
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
(success, message)
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
model_name = data.get('model_name', '').strip()
|
|
349
|
+
table_name = data.get('table_name', '').strip()
|
|
350
|
+
|
|
351
|
+
# Validate model name
|
|
352
|
+
if not model_name:
|
|
353
|
+
return False, "Model name is required"
|
|
354
|
+
|
|
355
|
+
if not re.match(r'^[A-Z][a-zA-Z0-9]*$', model_name):
|
|
356
|
+
return False, "Model name must be PascalCase (e.g., BlogPost, UserProfile)"
|
|
357
|
+
|
|
358
|
+
# Auto-generate table name if not provided
|
|
359
|
+
if not table_name:
|
|
360
|
+
# Convert PascalCase to snake_case
|
|
361
|
+
table_name = re.sub(r'(?<!^)(?=[A-Z])', '_', model_name).lower()
|
|
362
|
+
if not table_name.endswith('s'):
|
|
363
|
+
table_name += 's'
|
|
364
|
+
|
|
365
|
+
# Get fields
|
|
366
|
+
field_names = data.getlist('field_name[]') if hasattr(data, 'getlist') else data.get('field_name', [])
|
|
367
|
+
field_types = data.getlist('field_type[]') if hasattr(data, 'getlist') else data.get('field_type', [])
|
|
368
|
+
field_lengths = data.getlist('field_length[]') if hasattr(data, 'getlist') else data.get('field_length', [])
|
|
369
|
+
|
|
370
|
+
# Get relationships
|
|
371
|
+
rel_models = data.getlist('rel_model[]') if hasattr(data, 'getlist') else data.get('rel_model', [])
|
|
372
|
+
rel_types = data.getlist('rel_type[]') if hasattr(data, 'getlist') else data.get('rel_type', [])
|
|
373
|
+
rel_fields = data.getlist('rel_field[]') if hasattr(data, 'getlist') else data.get('rel_field', [])
|
|
374
|
+
|
|
375
|
+
# Generate model code
|
|
376
|
+
model_code = self._generate_model_code(
|
|
377
|
+
model_name=model_name,
|
|
378
|
+
table_name=table_name,
|
|
379
|
+
fields=list(zip(field_names, field_types, field_lengths)),
|
|
380
|
+
relationships=list(zip(rel_models, rel_types, rel_fields)) if rel_models else []
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Save to file
|
|
384
|
+
models_dir = os.path.join(os.getcwd(), 'models')
|
|
385
|
+
os.makedirs(models_dir, exist_ok=True)
|
|
386
|
+
|
|
387
|
+
file_path = os.path.join(models_dir, f'{model_name.lower()}.py')
|
|
388
|
+
|
|
389
|
+
with open(file_path, 'w') as f:
|
|
390
|
+
f.write(model_code)
|
|
391
|
+
|
|
392
|
+
return True, f"Model '{model_name}' created successfully at {file_path}. Run migrations to create the database table."
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
return False, f"Error creating model: {str(e)}"
|
|
396
|
+
|
|
397
|
+
def _generate_model_code(self, model_name: str, table_name: str, fields: List, relationships: List = None) -> str:
|
|
398
|
+
"""Generate Python model code with relationships"""
|
|
399
|
+
relationships = relationships or []
|
|
400
|
+
|
|
401
|
+
# Build field definitions
|
|
402
|
+
field_defs = []
|
|
403
|
+
for field_name, field_type, field_length in fields:
|
|
404
|
+
if not field_name:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
field_def = f" {field_name} = Column("
|
|
408
|
+
|
|
409
|
+
if field_type == 'string':
|
|
410
|
+
length = field_length or '200'
|
|
411
|
+
field_def += f"String({length})"
|
|
412
|
+
elif field_type == 'text':
|
|
413
|
+
field_def += "Text"
|
|
414
|
+
elif field_type == 'integer':
|
|
415
|
+
field_def += "Integer"
|
|
416
|
+
elif field_type == 'boolean':
|
|
417
|
+
field_def += "Boolean, default=False"
|
|
418
|
+
elif field_type == 'datetime':
|
|
419
|
+
field_def += "DateTime"
|
|
420
|
+
elif field_type == 'float':
|
|
421
|
+
field_def += "Float"
|
|
422
|
+
|
|
423
|
+
field_def += ")"
|
|
424
|
+
field_defs.append(field_def)
|
|
425
|
+
|
|
426
|
+
# Build relationships
|
|
427
|
+
relationship_imports = []
|
|
428
|
+
relationship_defs = []
|
|
429
|
+
foreign_key_defs = []
|
|
430
|
+
|
|
431
|
+
for rel_model, rel_type, rel_field in relationships:
|
|
432
|
+
if not rel_model or not rel_field:
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
# Add ForeignKey import
|
|
436
|
+
if 'ForeignKey' not in relationship_imports:
|
|
437
|
+
relationship_imports.append('ForeignKey')
|
|
438
|
+
|
|
439
|
+
# Add relationship import
|
|
440
|
+
if 'relationship' not in relationship_imports:
|
|
441
|
+
relationship_imports.append('relationship')
|
|
442
|
+
|
|
443
|
+
if rel_type == 'many_to_one':
|
|
444
|
+
# Many-to-One: This model belongs to another (e.g., Post belongs to User)
|
|
445
|
+
fk_field = f"{rel_field}_id"
|
|
446
|
+
foreign_key_defs.append(
|
|
447
|
+
f" {fk_field} = Column(Integer, ForeignKey('{rel_model.lower()}s.id'))"
|
|
448
|
+
)
|
|
449
|
+
relationship_defs.append(
|
|
450
|
+
f" {rel_field} = relationship('{rel_model}', back_populates='{table_name}')"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
elif rel_type == 'one_to_many':
|
|
454
|
+
# One-to-Many: This model has many of another (e.g., User has many Posts)
|
|
455
|
+
relationship_defs.append(
|
|
456
|
+
f" {rel_field} = relationship('{rel_model}', back_populates='{model_name.lower()}')"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
elif rel_type == 'one_to_one':
|
|
460
|
+
# One-to-One: This model has exactly one of another
|
|
461
|
+
fk_field = f"{rel_field}_id"
|
|
462
|
+
foreign_key_defs.append(
|
|
463
|
+
f" {fk_field} = Column(Integer, ForeignKey('{rel_model.lower()}s.id'), unique=True)"
|
|
464
|
+
)
|
|
465
|
+
relationship_defs.append(
|
|
466
|
+
f" {rel_field} = relationship('{rel_model}', back_populates='{model_name.lower()}', uselist=False)"
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
elif rel_type == 'many_to_many':
|
|
470
|
+
# Many-to-Many: Requires association table
|
|
471
|
+
association_table = f"{model_name.lower()}_{rel_model.lower()}_association"
|
|
472
|
+
relationship_defs.append(
|
|
473
|
+
f" # Many-to-Many: Create association table '{association_table}' manually"
|
|
474
|
+
)
|
|
475
|
+
relationship_defs.append(
|
|
476
|
+
f" {rel_field} = relationship('{rel_model}', secondary='{association_table}', back_populates='{table_name}')"
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
# Combine all field definitions
|
|
480
|
+
all_fields = field_defs + foreign_key_defs
|
|
481
|
+
fields_code = "\n".join(all_fields) if all_fields else " pass"
|
|
482
|
+
|
|
483
|
+
# Combine relationships
|
|
484
|
+
relationships_code = "\n".join(relationship_defs) if relationship_defs else ""
|
|
485
|
+
|
|
486
|
+
# Build imports
|
|
487
|
+
imports = "from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, Float"
|
|
488
|
+
if relationship_imports:
|
|
489
|
+
imports += ", " + ", ".join(relationship_imports)
|
|
490
|
+
|
|
491
|
+
code = f'''"""
|
|
492
|
+
{model_name} Model
|
|
493
|
+
Auto-generated by CREATESONLINE Admin
|
|
494
|
+
"""
|
|
495
|
+
{imports}
|
|
496
|
+
from createsonline.auth.models import AuthBase
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class {model_name}(AuthBase):
|
|
500
|
+
"""
|
|
501
|
+
{model_name} model
|
|
502
|
+
"""
|
|
503
|
+
__tablename__ = '{table_name}'
|
|
504
|
+
|
|
505
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
506
|
+
{fields_code}
|
|
507
|
+
{relationships_code}
|
|
508
|
+
|
|
509
|
+
def __repr__(self):
|
|
510
|
+
return f"<{model_name} {{self.id}}>"
|
|
511
|
+
'''
|
|
512
|
+
|
|
513
|
+
return code
|