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,482 @@
|
|
|
1
|
+
# createsonline/admin/integration.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE Admin Integration
|
|
4
|
+
|
|
5
|
+
Integrates all admin components: CRUD, permissions, content, dashboard.
|
|
6
|
+
This file extends the existing AdminSite with new features.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def integrate_auto_crud(admin_site, request):
|
|
13
|
+
"""
|
|
14
|
+
Integrate auto-CRUD views into admin site
|
|
15
|
+
|
|
16
|
+
This function adds routes for all registered models:
|
|
17
|
+
- /admin/{model}/ - List view
|
|
18
|
+
- /admin/{model}/add - Create view
|
|
19
|
+
- /admin/{model}/{id}/edit - Edit view
|
|
20
|
+
- /admin/{model}/{id}/delete - Delete view
|
|
21
|
+
- /admin/create-model - Model creator
|
|
22
|
+
- /admin/model-manager/{model} - Model structure manager
|
|
23
|
+
"""
|
|
24
|
+
from createsonline.admin.crud import ListView, CreateView, EditView, DeleteView
|
|
25
|
+
from createsonline.admin.user_forms import UserCreateForm, UserEditForm
|
|
26
|
+
from createsonline.admin.model_creator import ModelCreator
|
|
27
|
+
from createsonline.admin.model_manager import ModelManager
|
|
28
|
+
from createsonline.auth.models import User
|
|
29
|
+
|
|
30
|
+
# Get URL path
|
|
31
|
+
path = getattr(request, 'path', '/')
|
|
32
|
+
method = getattr(request, 'method', 'GET')
|
|
33
|
+
|
|
34
|
+
# Parse URL pattern: /admin/{model}/{id?}/{action?}
|
|
35
|
+
parts = [p for p in path.split('/') if p]
|
|
36
|
+
|
|
37
|
+
if len(parts) < 2:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
if parts[0] != 'admin':
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
# Check if this is the model manager route
|
|
44
|
+
if parts[1] == 'model-manager' and len(parts) >= 3:
|
|
45
|
+
model_name = parts[2]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Find registered model
|
|
49
|
+
model_class = None
|
|
50
|
+
for registered_name, registered_admin in admin_site._registry.items():
|
|
51
|
+
if registered_name.lower() == model_name.lower():
|
|
52
|
+
model_class = registered_admin.model
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
if model_class:
|
|
56
|
+
# Check if this is add field route
|
|
57
|
+
if len(parts) >= 5 and parts[3] == 'field' and parts[4] == 'add':
|
|
58
|
+
from createsonline.admin.field_builder import render_add_field_form
|
|
59
|
+
html = render_add_field_form(model_class.__name__)
|
|
60
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
61
|
+
return InternalHTMLResponse(html)
|
|
62
|
+
|
|
63
|
+
# Default: show model structure
|
|
64
|
+
manager = ModelManager(model_class, admin_site)
|
|
65
|
+
html = await manager.render(request)
|
|
66
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
67
|
+
return InternalHTMLResponse(html)
|
|
68
|
+
else:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Check if this is the model creator route
|
|
72
|
+
if parts[1] == 'create-model':
|
|
73
|
+
creator = ModelCreator(admin_site)
|
|
74
|
+
|
|
75
|
+
if method == 'POST':
|
|
76
|
+
data = await parse_form_data(request)
|
|
77
|
+
success, message = await creator.create_model(data)
|
|
78
|
+
|
|
79
|
+
if success:
|
|
80
|
+
# Show success page
|
|
81
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
82
|
+
success_html = f"""<!DOCTYPE html>
|
|
83
|
+
<html>
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8">
|
|
86
|
+
<title>Model Created - CREATESONLINE Admin</title>
|
|
87
|
+
<style>
|
|
88
|
+
body {{
|
|
89
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
90
|
+
background: #0a0a0a;
|
|
91
|
+
color: #ffffff;
|
|
92
|
+
padding: 40px;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
min-height: 100vh;
|
|
97
|
+
}}
|
|
98
|
+
.success-box {{
|
|
99
|
+
background: #1a1a1a;
|
|
100
|
+
border: 1px solid #2a2a2a;
|
|
101
|
+
border-radius: 12px;
|
|
102
|
+
padding: 40px;
|
|
103
|
+
max-width: 600px;
|
|
104
|
+
text-align: center;
|
|
105
|
+
}}
|
|
106
|
+
h1 {{
|
|
107
|
+
color: #4CAF50;
|
|
108
|
+
margin-bottom: 20px;
|
|
109
|
+
}}
|
|
110
|
+
p {{
|
|
111
|
+
color: #aaa;
|
|
112
|
+
margin-bottom: 30px;
|
|
113
|
+
}}
|
|
114
|
+
.btn {{
|
|
115
|
+
padding: 12px 30px;
|
|
116
|
+
background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
|
|
117
|
+
color: #0a0a0a;
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
border-radius: 8px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
display: inline-block;
|
|
122
|
+
}}
|
|
123
|
+
</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<div class="success-box">
|
|
127
|
+
<h1>✓ Success!</h1>
|
|
128
|
+
<p>{message}</p>
|
|
129
|
+
<p>Next steps:<br>
|
|
130
|
+
1. Run <code>createsonline-admin migrate</code> to create the database table<br>
|
|
131
|
+
2. Register the model in your admin.py file<br>
|
|
132
|
+
3. Refresh the admin to see your new model</p>
|
|
133
|
+
<a href="/admin" class="btn">Back to Admin</a>
|
|
134
|
+
</div>
|
|
135
|
+
</body>
|
|
136
|
+
</html>"""
|
|
137
|
+
return InternalHTMLResponse(success_html)
|
|
138
|
+
else:
|
|
139
|
+
html = await creator.render_form(errors={'model_name': message})
|
|
140
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
141
|
+
return InternalHTMLResponse(html)
|
|
142
|
+
else:
|
|
143
|
+
html = await creator.render_form()
|
|
144
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
145
|
+
return InternalHTMLResponse(html)
|
|
146
|
+
|
|
147
|
+
model_name = parts[1]
|
|
148
|
+
|
|
149
|
+
# Find registered model
|
|
150
|
+
model_admin = None
|
|
151
|
+
model_class = None
|
|
152
|
+
|
|
153
|
+
for registered_name, registered_admin in admin_site._registry.items():
|
|
154
|
+
if registered_name.lower() == model_name.lower():
|
|
155
|
+
model_admin = registered_admin
|
|
156
|
+
model_class = registered_admin.model
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
if not model_class:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
# Get database session
|
|
163
|
+
session = get_database_session()
|
|
164
|
+
if not session:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
# Check if this is User model - use custom forms
|
|
168
|
+
is_user_model = model_class == User
|
|
169
|
+
|
|
170
|
+
# Route to appropriate view
|
|
171
|
+
try:
|
|
172
|
+
if len(parts) == 2:
|
|
173
|
+
# List view
|
|
174
|
+
list_view = ListView(model_class, session, admin_site)
|
|
175
|
+
|
|
176
|
+
# Get query parameters
|
|
177
|
+
page = int(getattr(request, 'query_params', {}).get('page', 1))
|
|
178
|
+
search = getattr(request, 'query_params', {}).get('search', '')
|
|
179
|
+
|
|
180
|
+
html = await list_view.render(request, page=page, search=search)
|
|
181
|
+
|
|
182
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
183
|
+
return InternalHTMLResponse(html)
|
|
184
|
+
|
|
185
|
+
elif len(parts) == 3 and parts[2] == 'add':
|
|
186
|
+
# Create view - use custom form for User model
|
|
187
|
+
if is_user_model:
|
|
188
|
+
create_view = UserCreateForm(session, admin_site)
|
|
189
|
+
else:
|
|
190
|
+
create_view = CreateView(model_class, session, admin_site)
|
|
191
|
+
|
|
192
|
+
if method == 'POST':
|
|
193
|
+
# Parse form data
|
|
194
|
+
data = await parse_form_data(request)
|
|
195
|
+
success, obj, errors = await create_view.save(request, data)
|
|
196
|
+
|
|
197
|
+
if success:
|
|
198
|
+
# Show success message if password was generated
|
|
199
|
+
if is_user_model and hasattr(obj, '_generated_password'):
|
|
200
|
+
# For now, redirect with success - could add flash message later
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
# Redirect to list view
|
|
204
|
+
from createsonline.admin.interface import InternalResponse
|
|
205
|
+
return InternalResponse(
|
|
206
|
+
b'',
|
|
207
|
+
status_code=302,
|
|
208
|
+
headers={'location': f'/admin/{model_name}'}
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
html = await create_view.render(request, errors=errors, data=data)
|
|
212
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
213
|
+
return InternalHTMLResponse(html)
|
|
214
|
+
else:
|
|
215
|
+
html = await create_view.render(request)
|
|
216
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
217
|
+
return InternalHTMLResponse(html)
|
|
218
|
+
|
|
219
|
+
elif len(parts) == 4:
|
|
220
|
+
obj_id = int(parts[2])
|
|
221
|
+
action = parts[3]
|
|
222
|
+
|
|
223
|
+
if action == 'edit':
|
|
224
|
+
# Edit view - use custom form for User model
|
|
225
|
+
if is_user_model:
|
|
226
|
+
edit_view = UserEditForm(obj_id, session, admin_site)
|
|
227
|
+
else:
|
|
228
|
+
edit_view = EditView(model_class, session, admin_site)
|
|
229
|
+
|
|
230
|
+
if method == 'POST':
|
|
231
|
+
data = await parse_form_data(request)
|
|
232
|
+
success, obj, errors = await edit_view.save(request, data)
|
|
233
|
+
|
|
234
|
+
if success:
|
|
235
|
+
from createsonline.admin.interface import InternalResponse
|
|
236
|
+
return InternalResponse(
|
|
237
|
+
b'',
|
|
238
|
+
status_code=302,
|
|
239
|
+
headers={'location': f'/admin/{model_name}'}
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
html = await edit_view.render(request, errors=errors)
|
|
243
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
244
|
+
return InternalHTMLResponse(html)
|
|
245
|
+
else:
|
|
246
|
+
html = await edit_view.render(request)
|
|
247
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
248
|
+
return InternalHTMLResponse(html)
|
|
249
|
+
|
|
250
|
+
elif action == 'delete':
|
|
251
|
+
# Delete view
|
|
252
|
+
delete_view = DeleteView(model_class, session, admin_site)
|
|
253
|
+
|
|
254
|
+
if method == 'POST':
|
|
255
|
+
success, message = await delete_view.delete(request, obj_id)
|
|
256
|
+
|
|
257
|
+
from createsonline.admin.interface import InternalResponse
|
|
258
|
+
return InternalResponse(
|
|
259
|
+
b'',
|
|
260
|
+
status_code=302,
|
|
261
|
+
headers={'location': f'/admin/{model_name}'}
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
html = await delete_view.render(request, obj_id)
|
|
265
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
266
|
+
return InternalHTMLResponse(html)
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
import traceback
|
|
270
|
+
traceback.print_exc()
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def get_database_session():
|
|
277
|
+
"""Get database session"""
|
|
278
|
+
try:
|
|
279
|
+
from sqlalchemy import create_engine
|
|
280
|
+
from sqlalchemy.orm import sessionmaker
|
|
281
|
+
|
|
282
|
+
database_url = os.getenv("DATABASE_URL", "sqlite:///./createsonline.db")
|
|
283
|
+
engine = create_engine(database_url, echo=False)
|
|
284
|
+
SessionLocal = sessionmaker(bind=engine)
|
|
285
|
+
|
|
286
|
+
return SessionLocal()
|
|
287
|
+
except Exception as e:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
async def parse_form_data(request) -> Dict[str, Any]:
|
|
292
|
+
"""Parse form data from request"""
|
|
293
|
+
try:
|
|
294
|
+
from urllib.parse import unquote_plus
|
|
295
|
+
|
|
296
|
+
content_type = getattr(request, 'headers', {}).get('content-type', '')
|
|
297
|
+
|
|
298
|
+
if 'application/json' in content_type:
|
|
299
|
+
return await request.json()
|
|
300
|
+
else:
|
|
301
|
+
# Parse form-urlencoded
|
|
302
|
+
body = await request.body() if hasattr(request, 'body') else b''
|
|
303
|
+
data = {}
|
|
304
|
+
|
|
305
|
+
if body:
|
|
306
|
+
body_str = body.decode('utf-8')
|
|
307
|
+
for pair in body_str.split('&'):
|
|
308
|
+
if '=' in pair:
|
|
309
|
+
key, value = pair.split('=', 1)
|
|
310
|
+
data[unquote_plus(key)] = unquote_plus(value)
|
|
311
|
+
|
|
312
|
+
return data
|
|
313
|
+
except Exception as e:
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
async def load_user_from_database(username: str, password: str):
|
|
318
|
+
"""
|
|
319
|
+
Load and authenticate user from database
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
User object if authenticated, None otherwise
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
from createsonline.auth.models import User
|
|
326
|
+
|
|
327
|
+
session = get_database_session()
|
|
328
|
+
if not session:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
user = session.query(User).filter_by(username=username).first()
|
|
333
|
+
|
|
334
|
+
if user and user.is_staff and user.verify_password(password):
|
|
335
|
+
# Record login
|
|
336
|
+
user.record_login_attempt(True)
|
|
337
|
+
session.commit()
|
|
338
|
+
|
|
339
|
+
return user
|
|
340
|
+
elif user:
|
|
341
|
+
# Record failed attempt
|
|
342
|
+
user.record_login_attempt(False)
|
|
343
|
+
session.commit()
|
|
344
|
+
|
|
345
|
+
return None
|
|
346
|
+
finally:
|
|
347
|
+
session.close()
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
async def enhance_dashboard(admin_site, request, user):
|
|
354
|
+
"""
|
|
355
|
+
Render enhanced dashboard with insights
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
admin_site: AdminSite instance
|
|
359
|
+
request: Request object
|
|
360
|
+
user: Authenticated user
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
HTML response with dashboard
|
|
364
|
+
"""
|
|
365
|
+
try:
|
|
366
|
+
from createsonline.admin.modern_dashboard import ModernAdminDashboard
|
|
367
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
368
|
+
|
|
369
|
+
dashboard = ModernAdminDashboard(admin_site)
|
|
370
|
+
html = await dashboard.render(request, user)
|
|
371
|
+
|
|
372
|
+
return InternalHTMLResponse(html)
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
import traceback
|
|
376
|
+
traceback.print_exc()
|
|
377
|
+
# Fallback to default dashboard
|
|
378
|
+
return await admin_site._show_dashboard(request)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def check_permissions(user, permission: str) -> bool:
|
|
382
|
+
"""
|
|
383
|
+
Check if user has permission
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
user: User object
|
|
387
|
+
permission: Permission string like "auth.add_user"
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
True if user has permission
|
|
391
|
+
"""
|
|
392
|
+
if not user:
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
if hasattr(user, 'is_superuser') and user.is_superuser:
|
|
396
|
+
return True
|
|
397
|
+
|
|
398
|
+
if hasattr(user, 'has_permission'):
|
|
399
|
+
return user.has_permission(permission)
|
|
400
|
+
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
async def show_permission_denied(request, message: str = "Permission denied"):
|
|
405
|
+
"""Show permission denied page"""
|
|
406
|
+
html = f"""
|
|
407
|
+
<!DOCTYPE html>
|
|
408
|
+
<html lang="en">
|
|
409
|
+
<head>
|
|
410
|
+
<meta charset="UTF-8">
|
|
411
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
412
|
+
<title>Permission Denied - CREATESONLINE Admin</title>
|
|
413
|
+
<style>
|
|
414
|
+
* {{
|
|
415
|
+
margin: 0;
|
|
416
|
+
padding: 0;
|
|
417
|
+
box-sizing: border-box;
|
|
418
|
+
}}
|
|
419
|
+
|
|
420
|
+
body {{
|
|
421
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
422
|
+
background: #0a0a0a;
|
|
423
|
+
color: #ffffff;
|
|
424
|
+
display: flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
justify-content: center;
|
|
427
|
+
min-height: 100vh;
|
|
428
|
+
padding: 20px;
|
|
429
|
+
}}
|
|
430
|
+
|
|
431
|
+
.container {{
|
|
432
|
+
max-width: 600px;
|
|
433
|
+
background: #1a1a1a;
|
|
434
|
+
padding: 50px;
|
|
435
|
+
border-radius: 12px;
|
|
436
|
+
border: 1px solid #2a2a2a;
|
|
437
|
+
text-align: center;
|
|
438
|
+
}}
|
|
439
|
+
|
|
440
|
+
.error-icon {{
|
|
441
|
+
font-size: 5em;
|
|
442
|
+
margin-bottom: 20px;
|
|
443
|
+
}}
|
|
444
|
+
|
|
445
|
+
h1 {{
|
|
446
|
+
font-size: 2.5em;
|
|
447
|
+
background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
|
|
448
|
+
-webkit-background-clip: text;
|
|
449
|
+
-webkit-text-fill-color: transparent;
|
|
450
|
+
margin-bottom: 20px;
|
|
451
|
+
}}
|
|
452
|
+
|
|
453
|
+
p {{
|
|
454
|
+
color: #b0b0b0;
|
|
455
|
+
font-size: 1.2em;
|
|
456
|
+
margin-bottom: 30px;
|
|
457
|
+
}}
|
|
458
|
+
|
|
459
|
+
.btn {{
|
|
460
|
+
padding: 12px 30px;
|
|
461
|
+
background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
|
|
462
|
+
color: #0a0a0a;
|
|
463
|
+
border: none;
|
|
464
|
+
border-radius: 8px;
|
|
465
|
+
text-decoration: none;
|
|
466
|
+
font-weight: 600;
|
|
467
|
+
display: inline-block;
|
|
468
|
+
}}
|
|
469
|
+
</style>
|
|
470
|
+
</head>
|
|
471
|
+
<body>
|
|
472
|
+
<div class="container">
|
|
473
|
+
<div class="error-icon">🔒</div>
|
|
474
|
+
<h1>Permission Denied</h1>
|
|
475
|
+
<p>{message}</p>
|
|
476
|
+
<a href="/admin" class="btn">Go to Dashboard</a>
|
|
477
|
+
</div>
|
|
478
|
+
</body>
|
|
479
|
+
</html>
|
|
480
|
+
"""
|
|
481
|
+
from createsonline.admin.interface import InternalHTMLResponse
|
|
482
|
+
return InternalHTMLResponse(html, status_code=403)
|