crudadmin 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {crudadmin-0.3.0 → crudadmin-0.3.2}/PKG-INFO +41 -22
- {crudadmin-0.3.0 → crudadmin-0.3.2}/README.md +33 -14
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/crud_admin.py +15 -10
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/db.py +4 -4
- crudadmin-0.3.2/crudadmin/session/schemas.py +153 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/pyproject.toml +14 -13
- crudadmin-0.3.0/crudadmin/session/schemas.py +0 -128
- {crudadmin-0.3.0 → crudadmin-0.3.2}/.gitignore +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/LICENSE +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/admin_site.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/auth.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/helper.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/middleware/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/middleware/auth.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/middleware/https.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/middleware/ip_restriction.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/model_view.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_interface/typing.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_user/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_user/models.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_user/schemas.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/admin_user/service.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/auth.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/exceptions.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/rate_limiter.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/schemas/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/core/schemas/timestamp.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/decorators.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/integration.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/models.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/schemas.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/event/service.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/py.typed +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/__init__.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/database.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/hybrid.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/memcached.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/memory.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/backends/redis.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/manager.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/models.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/storage.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/session/user_agents_types.py +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/static/favicon.png +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/static/htmx.min.js +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/dashboard/dashboard.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/dashboard/dashboard_content.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/events.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/events_content.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/health.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/health_content.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/list_content.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/pagination.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/table_content.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/create.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/list.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/update.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/auth/login.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/base/base.html +0 -0
- {crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/shared/utils/refresh.html +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crudadmin
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: FastAPI-based admin interface with authentication, event logging and CRUD operations
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Documentation, https://
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
9
|
-
Project-URL: Changelog, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/benavlabs/crudadmin
|
|
6
|
+
Project-URL: Documentation, https://benavlabs.github.io/crudadmin
|
|
7
|
+
Project-URL: Repository, https://github.com/benavlabs/crudadmin
|
|
8
|
+
Project-URL: Issues, https://github.com/benavlabs/crudadmin/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/benavlabs/crudadmin/releases
|
|
10
10
|
Author-email: Igor Benav <igor.magalhaes.r@gmail.com>
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -27,11 +27,11 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
27
27
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
28
28
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
29
29
|
Classifier: Typing :: Typed
|
|
30
|
-
Requires-Python: >=3.9
|
|
30
|
+
Requires-Python: >=3.9.2
|
|
31
31
|
Requires-Dist: aiosqlite>=0.20.0
|
|
32
32
|
Requires-Dist: bcrypt>=4.2.1
|
|
33
33
|
Requires-Dist: fastapi>=0.115.6
|
|
34
|
-
Requires-Dist: fastcrud>=0.15.
|
|
34
|
+
Requires-Dist: fastcrud>=0.15.12
|
|
35
35
|
Requires-Dist: greenlet>=3.1.1
|
|
36
36
|
Requires-Dist: jinja2>=3.1.5
|
|
37
37
|
Requires-Dist: pydantic-settings>=2.6.1
|
|
@@ -88,7 +88,7 @@ Description-Content-Type: text/markdown
|
|
|
88
88
|
# CRUDAdmin
|
|
89
89
|
|
|
90
90
|
<p align="center">
|
|
91
|
-
<a href="https://
|
|
91
|
+
<a href="https://benavlabs.github.io/crudadmin/">
|
|
92
92
|
<img src="docs/assets/CRUDAdmin.png" alt="CRUDAdmin logo" width="45%" height="auto">
|
|
93
93
|
</a>
|
|
94
94
|
</p>
|
|
@@ -98,8 +98,8 @@ Description-Content-Type: text/markdown
|
|
|
98
98
|
</p>
|
|
99
99
|
|
|
100
100
|
<p align="center">
|
|
101
|
-
<a href="https://github.com/
|
|
102
|
-
<img src="https://github.com/
|
|
101
|
+
<a href="https://github.com/benavlabs/crudadmin/actions/workflows/tests.yml">
|
|
102
|
+
<img src="https://github.com/benavlabs/crudadmin/actions/workflows/tests.yml/badge.svg" alt="Tests"/>
|
|
103
103
|
</a>
|
|
104
104
|
<a href="https://pypi.org/project/crudadmin/">
|
|
105
105
|
<img src="https://img.shields.io/pypi/v/crudadmin?color=%2334D058&label=pypi%20package" alt="PyPi Version"/>
|
|
@@ -113,7 +113,7 @@ Description-Content-Type: text/markdown
|
|
|
113
113
|
|
|
114
114
|
**CRUDAdmin** is a robust admin interface generator for **FastAPI** applications, offering secure authentication, comprehensive event tracking, and essential monitoring features. Built with [FastCRUD](https://github.com/benavlabs/fastcrud) and HTMX, it helps you create production-ready admin panels with minimal configuration.
|
|
115
115
|
|
|
116
|
-
**Documentation**: [https://
|
|
116
|
+
**Documentation**: [https://benavlabs.github.io/crudadmin/](https://benavlabs.github.io/crudadmin/)
|
|
117
117
|
|
|
118
118
|
> [!WARNING]
|
|
119
119
|
> CRUDAdmin is still experimental. While actively developed and tested, APIs may change between versions. Upgrade with caution in production environments, always carefuly reading the changelog.
|
|
@@ -127,6 +127,16 @@ Description-Content-Type: text/markdown
|
|
|
127
127
|
- **🔍 Advanced Filtering**: Type-aware field filtering, search, and pagination with bulk operations
|
|
128
128
|
- **🌗 Modern UI**: Clean, responsive interface built with HTMX and [FastCRUD](https://github.com/benavlabs/fastcrud)
|
|
129
129
|
|
|
130
|
+
## Video Preview
|
|
131
|
+
|
|
132
|
+
<p align="center">To see what CRUDAdmin dashboard actually looks like in practice, watch the video demo on youtube:</p>
|
|
133
|
+
<p align="center">
|
|
134
|
+
<a href="https://www.youtube.com/watch?v=THLdUbDQ9yM">
|
|
135
|
+
<img src="docs/assets/youtube-preview.png" alt="Watch CRUDAdmin Dashboard Demo on Youtube" width="75%" height="auto"/>
|
|
136
|
+
</a>
|
|
137
|
+
</p>
|
|
138
|
+
<br>
|
|
139
|
+
|
|
130
140
|
## Quick Start
|
|
131
141
|
|
|
132
142
|
### Installation
|
|
@@ -161,11 +171,15 @@ from .user import (
|
|
|
161
171
|
|
|
162
172
|
# Database setup
|
|
163
173
|
engine = create_async_engine("sqlite+aiosqlite:///app.db")
|
|
164
|
-
|
|
174
|
+
|
|
175
|
+
# Create database session dependency
|
|
176
|
+
async def get_session():
|
|
177
|
+
async with AsyncSession(engine) as session:
|
|
178
|
+
yield session
|
|
165
179
|
|
|
166
180
|
# Create admin interface
|
|
167
181
|
admin = CRUDAdmin(
|
|
168
|
-
session=
|
|
182
|
+
session=get_session,
|
|
169
183
|
SECRET_KEY="your-secret-key-here",
|
|
170
184
|
initial_admin={
|
|
171
185
|
"username": "admin",
|
|
@@ -208,12 +222,12 @@ Navigate to `/admin` to access your admin interface with:
|
|
|
208
222
|
|
|
209
223
|
### Development (Default)
|
|
210
224
|
```python
|
|
211
|
-
admin = CRUDAdmin(session=
|
|
225
|
+
admin = CRUDAdmin(session=get_session, SECRET_KEY="key") # Memory backend
|
|
212
226
|
```
|
|
213
227
|
|
|
214
228
|
### Production with Redis
|
|
215
229
|
```python
|
|
216
|
-
admin = CRUDAdmin(session=
|
|
230
|
+
admin = CRUDAdmin(session=get_session, SECRET_KEY="key").use_redis_sessions(
|
|
217
231
|
redis_url="redis://localhost:6379"
|
|
218
232
|
)
|
|
219
233
|
```
|
|
@@ -221,7 +235,7 @@ admin = CRUDAdmin(session=session, SECRET_KEY="key").use_redis_sessions(
|
|
|
221
235
|
### Production with Security Features
|
|
222
236
|
```python
|
|
223
237
|
admin = CRUDAdmin(
|
|
224
|
-
session=
|
|
238
|
+
session=get_session,
|
|
225
239
|
SECRET_KEY=SECRET_KEY,
|
|
226
240
|
# Security features
|
|
227
241
|
allowed_ips=["10.0.0.1"],
|
|
@@ -260,11 +274,16 @@ admin = CRUDAdmin(
|
|
|
260
274
|
|
|
261
275
|
## Documentation
|
|
262
276
|
|
|
263
|
-
- **[Quick Start](https://
|
|
264
|
-
- **[Usage Guide](https://
|
|
265
|
-
- **[API Reference](https://
|
|
266
|
-
- **[Advanced Topics](https://
|
|
277
|
+
- **[Quick Start](https://benavlabs.github.io/crudadmin/quick-start/)**: Get up and running in 5 minutes
|
|
278
|
+
- **[Usage Guide](https://benavlabs.github.io/crudadmin/usage/overview/)**: Complete usage documentation
|
|
279
|
+
- **[API Reference](https://benavlabs.github.io/crudadmin/api/overview/)**: Full API documentation
|
|
280
|
+
- **[Advanced Topics](https://benavlabs.github.io/crudadmin/advanced/overview/)**: Production features and configurations
|
|
267
281
|
|
|
268
282
|
## License
|
|
269
283
|
|
|
270
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
284
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
285
|
+
|
|
286
|
+
<hr>
|
|
287
|
+
<a href="https://benav.io">
|
|
288
|
+
<img src="docs/assets/benav_labs_banner.png" alt="Powered by Benav Labs - benav.io"/>
|
|
289
|
+
</a>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CRUDAdmin
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://
|
|
4
|
+
<a href="https://benavlabs.github.io/crudadmin/">
|
|
5
5
|
<img src="docs/assets/CRUDAdmin.png" alt="CRUDAdmin logo" width="45%" height="auto">
|
|
6
6
|
</a>
|
|
7
7
|
</p>
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
|
-
<a href="https://github.com/
|
|
15
|
-
<img src="https://github.com/
|
|
14
|
+
<a href="https://github.com/benavlabs/crudadmin/actions/workflows/tests.yml">
|
|
15
|
+
<img src="https://github.com/benavlabs/crudadmin/actions/workflows/tests.yml/badge.svg" alt="Tests"/>
|
|
16
16
|
</a>
|
|
17
17
|
<a href="https://pypi.org/project/crudadmin/">
|
|
18
18
|
<img src="https://img.shields.io/pypi/v/crudadmin?color=%2334D058&label=pypi%20package" alt="PyPi Version"/>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
**CRUDAdmin** is a robust admin interface generator for **FastAPI** applications, offering secure authentication, comprehensive event tracking, and essential monitoring features. Built with [FastCRUD](https://github.com/benavlabs/fastcrud) and HTMX, it helps you create production-ready admin panels with minimal configuration.
|
|
28
28
|
|
|
29
|
-
**Documentation**: [https://
|
|
29
|
+
**Documentation**: [https://benavlabs.github.io/crudadmin/](https://benavlabs.github.io/crudadmin/)
|
|
30
30
|
|
|
31
31
|
> [!WARNING]
|
|
32
32
|
> CRUDAdmin is still experimental. While actively developed and tested, APIs may change between versions. Upgrade with caution in production environments, always carefuly reading the changelog.
|
|
@@ -40,6 +40,16 @@
|
|
|
40
40
|
- **🔍 Advanced Filtering**: Type-aware field filtering, search, and pagination with bulk operations
|
|
41
41
|
- **🌗 Modern UI**: Clean, responsive interface built with HTMX and [FastCRUD](https://github.com/benavlabs/fastcrud)
|
|
42
42
|
|
|
43
|
+
## Video Preview
|
|
44
|
+
|
|
45
|
+
<p align="center">To see what CRUDAdmin dashboard actually looks like in practice, watch the video demo on youtube:</p>
|
|
46
|
+
<p align="center">
|
|
47
|
+
<a href="https://www.youtube.com/watch?v=THLdUbDQ9yM">
|
|
48
|
+
<img src="docs/assets/youtube-preview.png" alt="Watch CRUDAdmin Dashboard Demo on Youtube" width="75%" height="auto"/>
|
|
49
|
+
</a>
|
|
50
|
+
</p>
|
|
51
|
+
<br>
|
|
52
|
+
|
|
43
53
|
## Quick Start
|
|
44
54
|
|
|
45
55
|
### Installation
|
|
@@ -74,11 +84,15 @@ from .user import (
|
|
|
74
84
|
|
|
75
85
|
# Database setup
|
|
76
86
|
engine = create_async_engine("sqlite+aiosqlite:///app.db")
|
|
77
|
-
|
|
87
|
+
|
|
88
|
+
# Create database session dependency
|
|
89
|
+
async def get_session():
|
|
90
|
+
async with AsyncSession(engine) as session:
|
|
91
|
+
yield session
|
|
78
92
|
|
|
79
93
|
# Create admin interface
|
|
80
94
|
admin = CRUDAdmin(
|
|
81
|
-
session=
|
|
95
|
+
session=get_session,
|
|
82
96
|
SECRET_KEY="your-secret-key-here",
|
|
83
97
|
initial_admin={
|
|
84
98
|
"username": "admin",
|
|
@@ -121,12 +135,12 @@ Navigate to `/admin` to access your admin interface with:
|
|
|
121
135
|
|
|
122
136
|
### Development (Default)
|
|
123
137
|
```python
|
|
124
|
-
admin = CRUDAdmin(session=
|
|
138
|
+
admin = CRUDAdmin(session=get_session, SECRET_KEY="key") # Memory backend
|
|
125
139
|
```
|
|
126
140
|
|
|
127
141
|
### Production with Redis
|
|
128
142
|
```python
|
|
129
|
-
admin = CRUDAdmin(session=
|
|
143
|
+
admin = CRUDAdmin(session=get_session, SECRET_KEY="key").use_redis_sessions(
|
|
130
144
|
redis_url="redis://localhost:6379"
|
|
131
145
|
)
|
|
132
146
|
```
|
|
@@ -134,7 +148,7 @@ admin = CRUDAdmin(session=session, SECRET_KEY="key").use_redis_sessions(
|
|
|
134
148
|
### Production with Security Features
|
|
135
149
|
```python
|
|
136
150
|
admin = CRUDAdmin(
|
|
137
|
-
session=
|
|
151
|
+
session=get_session,
|
|
138
152
|
SECRET_KEY=SECRET_KEY,
|
|
139
153
|
# Security features
|
|
140
154
|
allowed_ips=["10.0.0.1"],
|
|
@@ -173,11 +187,16 @@ admin = CRUDAdmin(
|
|
|
173
187
|
|
|
174
188
|
## Documentation
|
|
175
189
|
|
|
176
|
-
- **[Quick Start](https://
|
|
177
|
-
- **[Usage Guide](https://
|
|
178
|
-
- **[API Reference](https://
|
|
179
|
-
- **[Advanced Topics](https://
|
|
190
|
+
- **[Quick Start](https://benavlabs.github.io/crudadmin/quick-start/)**: Get up and running in 5 minutes
|
|
191
|
+
- **[Usage Guide](https://benavlabs.github.io/crudadmin/usage/overview/)**: Complete usage documentation
|
|
192
|
+
- **[API Reference](https://benavlabs.github.io/crudadmin/api/overview/)**: Full API documentation
|
|
193
|
+
- **[Advanced Topics](https://benavlabs.github.io/crudadmin/advanced/overview/)**: Production features and configurations
|
|
180
194
|
|
|
181
195
|
## License
|
|
182
196
|
|
|
183
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
197
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
198
|
+
|
|
199
|
+
<hr>
|
|
200
|
+
<a href="https://benav.io">
|
|
201
|
+
<img src="docs/assets/benav_labs_banner.png" alt="Powered by Benav Labs - benav.io"/>
|
|
202
|
+
</a>
|
|
@@ -5,6 +5,7 @@ from collections.abc import Awaitable, Callable
|
|
|
5
5
|
from datetime import UTC, datetime, timedelta
|
|
6
6
|
from typing import (
|
|
7
7
|
Any,
|
|
8
|
+
AsyncGenerator,
|
|
8
9
|
Dict,
|
|
9
10
|
List,
|
|
10
11
|
Optional,
|
|
@@ -84,7 +85,7 @@ class CRUDAdmin:
|
|
|
84
85
|
- Token-based authentication
|
|
85
86
|
|
|
86
87
|
Args:
|
|
87
|
-
session: Async SQLAlchemy session
|
|
88
|
+
session: Async SQLAlchemy session dependency function that yields sessions
|
|
88
89
|
SECRET_KEY: Secret key for session management and cookie signing. Generate securely using:
|
|
89
90
|
**Python one-liner (recommended)**
|
|
90
91
|
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
@@ -151,11 +152,15 @@ class CRUDAdmin:
|
|
|
151
152
|
|
|
152
153
|
# Setup database
|
|
153
154
|
engine = create_async_engine("sqlite+aiosqlite:///app.db")
|
|
154
|
-
|
|
155
|
+
|
|
156
|
+
# Create database session dependency
|
|
157
|
+
async def get_session():
|
|
158
|
+
async with AsyncSession(engine) as session:
|
|
159
|
+
yield session
|
|
155
160
|
|
|
156
161
|
# Create admin interface
|
|
157
162
|
admin = CRUDAdmin(
|
|
158
|
-
session=
|
|
163
|
+
session=get_session,
|
|
159
164
|
SECRET_KEY=SECRET_KEY,
|
|
160
165
|
initial_admin={
|
|
161
166
|
"username": "admin",
|
|
@@ -167,7 +172,7 @@ class CRUDAdmin:
|
|
|
167
172
|
Production setup with security features:
|
|
168
173
|
```python
|
|
169
174
|
admin = CRUDAdmin(
|
|
170
|
-
session=
|
|
175
|
+
session=get_session,
|
|
171
176
|
SECRET_KEY=SECRET_KEY,
|
|
172
177
|
# Security features
|
|
173
178
|
allowed_ips=["10.0.0.1", "10.0.0.2"],
|
|
@@ -187,7 +192,7 @@ class CRUDAdmin:
|
|
|
187
192
|
Session management configuration:
|
|
188
193
|
```python
|
|
189
194
|
admin = CRUDAdmin(
|
|
190
|
-
session=
|
|
195
|
+
session=get_session,
|
|
191
196
|
SECRET_KEY=SECRET_KEY,
|
|
192
197
|
# Session management settings
|
|
193
198
|
max_sessions_per_user=5,
|
|
@@ -269,7 +274,7 @@ class CRUDAdmin:
|
|
|
269
274
|
Event tracking and audit logs:
|
|
270
275
|
```python
|
|
271
276
|
admin = CRUDAdmin(
|
|
272
|
-
session=
|
|
277
|
+
session=get_session,
|
|
273
278
|
SECRET_KEY=SECRET_KEY,
|
|
274
279
|
track_events=True, # Enable event tracking
|
|
275
280
|
# Custom admin database for logs
|
|
@@ -286,7 +291,7 @@ class CRUDAdmin:
|
|
|
286
291
|
|
|
287
292
|
def __init__(
|
|
288
293
|
self,
|
|
289
|
-
session: AsyncSession,
|
|
294
|
+
session: Callable[[], AsyncGenerator[AsyncSession, None]],
|
|
290
295
|
SECRET_KEY: str,
|
|
291
296
|
mount_path: Optional[str] = "/admin",
|
|
292
297
|
theme: Optional[str] = "dark-theme",
|
|
@@ -446,7 +451,7 @@ class CRUDAdmin:
|
|
|
446
451
|
Manual initialization:
|
|
447
452
|
```python
|
|
448
453
|
admin = CRUDAdmin(
|
|
449
|
-
session=
|
|
454
|
+
session=get_session,
|
|
450
455
|
SECRET_KEY="key",
|
|
451
456
|
setup_on_initialization=False
|
|
452
457
|
)
|
|
@@ -789,8 +794,8 @@ class CRUDAdmin:
|
|
|
789
794
|
model: Type[DeclarativeBase],
|
|
790
795
|
create_schema: Type[BaseModel],
|
|
791
796
|
update_schema: Type[BaseModel],
|
|
792
|
-
update_internal_schema: Optional[Type[BaseModel]],
|
|
793
|
-
delete_schema: Optional[Type[BaseModel]],
|
|
797
|
+
update_internal_schema: Optional[Type[BaseModel]] = None,
|
|
798
|
+
delete_schema: Optional[Type[BaseModel]] = None,
|
|
794
799
|
include_in_models: bool = True,
|
|
795
800
|
allowed_actions: Optional[set[str]] = None,
|
|
796
801
|
password_transformer: Optional[Any] = None,
|
|
@@ -59,7 +59,7 @@ class DatabaseConfig:
|
|
|
59
59
|
def __init__(
|
|
60
60
|
self,
|
|
61
61
|
base: Type[DeclarativeBase],
|
|
62
|
-
session: AsyncSession,
|
|
62
|
+
session: Callable[[], AsyncGenerator[AsyncSession, None]],
|
|
63
63
|
admin_db_url: Optional[str] = None,
|
|
64
64
|
admin_db_path: Optional[str] = None,
|
|
65
65
|
admin_user: Optional[Type[DeclarativeBase]] = None,
|
|
@@ -88,7 +88,7 @@ class DatabaseConfig:
|
|
|
88
88
|
] = None,
|
|
89
89
|
) -> None:
|
|
90
90
|
self.base: Type[DeclarativeBase] = base
|
|
91
|
-
self.session: AsyncSession = session
|
|
91
|
+
self.session: Callable[[], AsyncGenerator[AsyncSession, None]] = session
|
|
92
92
|
|
|
93
93
|
if admin_db_url is None:
|
|
94
94
|
if admin_db_path is None:
|
|
@@ -197,8 +197,8 @@ class DatabaseConfig:
|
|
|
197
197
|
"""Get a session for the admin database."""
|
|
198
198
|
return self.admin_session
|
|
199
199
|
|
|
200
|
-
def get_app_session(self) -> AsyncSession:
|
|
201
|
-
"""Get a session for the main application database."""
|
|
200
|
+
def get_app_session(self) -> Callable[[], AsyncGenerator[AsyncSession, None]]:
|
|
201
|
+
"""Get a session dependency for the main application database."""
|
|
202
202
|
return self.session
|
|
203
203
|
|
|
204
204
|
def get_primary_key(self, model: Type[DeclarativeBase]) -> Optional[str]:
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
from ..core.schemas.timestamp import TimestampSchema
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DeviceType(str, Enum):
|
|
12
|
+
"""Device type enumeration"""
|
|
13
|
+
|
|
14
|
+
MOBILE = "mobile"
|
|
15
|
+
TABLET = "tablet"
|
|
16
|
+
DESKTOP = "desktop"
|
|
17
|
+
UNKNOWN = "unknown"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseSession(BaseModel):
|
|
21
|
+
"""Common base fields for all session types."""
|
|
22
|
+
|
|
23
|
+
user_id: int = Field(
|
|
24
|
+
..., description="The ID of the user associated with the session."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
session_id: str = Field(
|
|
28
|
+
default_factory=lambda: str(uuid4()), description="Unique session identifier"
|
|
29
|
+
)
|
|
30
|
+
ip_address: str = Field(..., description="Client IP address")
|
|
31
|
+
user_agent: str = Field(..., description="Client user agent string")
|
|
32
|
+
device_info: dict[str, Any] = Field(
|
|
33
|
+
default_factory=dict, description="Additional device information"
|
|
34
|
+
)
|
|
35
|
+
is_active: bool = Field(default=True, description="Whether session is active")
|
|
36
|
+
|
|
37
|
+
@field_validator("ip_address")
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_ip_address(cls, ip: str) -> str:
|
|
40
|
+
"""Validate IP address format."""
|
|
41
|
+
import ipaddress
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
ipaddress.ip_address(ip)
|
|
45
|
+
except ValueError as err:
|
|
46
|
+
raise ValueError("Invalid IP Address format") from err
|
|
47
|
+
return ip
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SessionData(BaseSession):
|
|
51
|
+
"""Common session data for any user session."""
|
|
52
|
+
|
|
53
|
+
metadata: dict[str, Any] = Field(
|
|
54
|
+
default_factory=dict, description="Additional session metadata"
|
|
55
|
+
)
|
|
56
|
+
created_at: datetime = Field(
|
|
57
|
+
default_factory=lambda: datetime.now(UTC),
|
|
58
|
+
description="Session creation timestamp",
|
|
59
|
+
)
|
|
60
|
+
last_activity: datetime = Field(
|
|
61
|
+
default_factory=lambda: datetime.now(UTC), description="Last activity timestamp"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SessionCreate(SessionData):
|
|
66
|
+
"""Schema for creating a new session."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SessionUpdate(BaseModel):
|
|
72
|
+
"""Schema for updating a session."""
|
|
73
|
+
|
|
74
|
+
last_activity: Optional[datetime] = None
|
|
75
|
+
is_active: Optional[bool] = None
|
|
76
|
+
metadata: Optional[dict[str, Any]] = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class UserAgentInfo(BaseModel):
|
|
80
|
+
"""User agent information parsed from the User-Agent header."""
|
|
81
|
+
|
|
82
|
+
browser: str = Field(..., description="Browser name")
|
|
83
|
+
browser_version: str = Field(..., description="Browser version")
|
|
84
|
+
os: str = Field(..., description="Operating System")
|
|
85
|
+
device: str
|
|
86
|
+
is_mobile: bool
|
|
87
|
+
is_tablet: bool
|
|
88
|
+
is_pc: bool
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CSRFToken(BaseModel):
|
|
92
|
+
"""CSRF token data."""
|
|
93
|
+
|
|
94
|
+
token: str
|
|
95
|
+
user_id: int
|
|
96
|
+
session_id: str
|
|
97
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
98
|
+
expires_at: datetime
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AdminSession(TimestampSchema, BaseSession):
|
|
102
|
+
"""Full AdminSession schema with all fields."""
|
|
103
|
+
|
|
104
|
+
id: int
|
|
105
|
+
session_metadata: dict[str, Any] = Field(
|
|
106
|
+
default_factory=dict, description="admin specific session metadata"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AdminSessionRead(BaseSession):
|
|
111
|
+
"""Schema for reading AdminSession data."""
|
|
112
|
+
|
|
113
|
+
id: int
|
|
114
|
+
session_metadata: dict[str, Any]
|
|
115
|
+
created_at: datetime
|
|
116
|
+
last_activity: datetime
|
|
117
|
+
is_active: bool
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AdminSessionCreate(BaseSession):
|
|
121
|
+
"""Schema for creating AdminSession in database."""
|
|
122
|
+
|
|
123
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
124
|
+
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
125
|
+
session_metadata: dict[str, Any] = Field(default_factory=dict)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AdminSessionUpdate(BaseModel):
|
|
129
|
+
"""Schema for updating AdminSession."""
|
|
130
|
+
|
|
131
|
+
last_activity: Optional[datetime] = None
|
|
132
|
+
is_active: Optional[bool] = None
|
|
133
|
+
session_metadata: Optional[dict[str, Any]] = None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class AdminSessionUpdateInternal(AdminSessionUpdate):
|
|
137
|
+
"""Internal schema for AdminSession updates."""
|
|
138
|
+
|
|
139
|
+
updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(UTC))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = [
|
|
143
|
+
"SessionData",
|
|
144
|
+
"SessionCreate",
|
|
145
|
+
"SessionUpdate",
|
|
146
|
+
"UserAgentInfo",
|
|
147
|
+
"CSRFToken",
|
|
148
|
+
"AdminSession",
|
|
149
|
+
"AdminSessionRead",
|
|
150
|
+
"AdminSessionCreate",
|
|
151
|
+
"AdminSessionUpdate",
|
|
152
|
+
"AdminSessionUpdateInternal",
|
|
153
|
+
]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "crudadmin"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "FastAPI-based admin interface with authentication, event logging and CRUD operations"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.9"
|
|
6
|
+
requires-python = ">=3.9.2"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Igor Benav", email = "igor.magalhaes.r@gmail.com" }
|
|
9
9
|
]
|
|
@@ -30,7 +30,6 @@ classifiers = [
|
|
|
30
30
|
dependencies = [
|
|
31
31
|
"bcrypt>=4.2.1",
|
|
32
32
|
"fastapi>=0.115.6",
|
|
33
|
-
"fastcrud>=0.15.5",
|
|
34
33
|
"jinja2>=3.1.5",
|
|
35
34
|
"pydantic[email]>=2.10.4",
|
|
36
35
|
"pydantic-settings>=2.6.1",
|
|
@@ -40,6 +39,7 @@ dependencies = [
|
|
|
40
39
|
"user-agents>=2.2.0",
|
|
41
40
|
"aiosqlite>=0.20.0",
|
|
42
41
|
"greenlet>=3.1.1",
|
|
42
|
+
"fastcrud>=0.15.12",
|
|
43
43
|
]
|
|
44
44
|
|
|
45
45
|
[tool.hatch.build.targets.sdist]
|
|
@@ -52,17 +52,12 @@ include = ["crudadmin/", "LICENSE"]
|
|
|
52
52
|
requires = ["hatchling"]
|
|
53
53
|
build-backend = "hatchling.build"
|
|
54
54
|
|
|
55
|
-
[dependency-groups]
|
|
56
|
-
dev = [
|
|
57
|
-
"types-redis>=4.6.0.20241004",
|
|
58
|
-
]
|
|
59
|
-
|
|
60
55
|
[project.urls]
|
|
61
|
-
Homepage = "https://github.com/
|
|
62
|
-
Documentation = "https://
|
|
63
|
-
Repository = "https://github.com/
|
|
64
|
-
Issues = "https://github.com/
|
|
65
|
-
Changelog = "https://github.com/
|
|
56
|
+
Homepage = "https://github.com/benavlabs/crudadmin"
|
|
57
|
+
Documentation = "https://benavlabs.github.io/crudadmin"
|
|
58
|
+
Repository = "https://github.com/benavlabs/crudadmin"
|
|
59
|
+
Issues = "https://github.com/benavlabs/crudadmin/issues"
|
|
60
|
+
Changelog = "https://github.com/benavlabs/crudadmin/releases"
|
|
66
61
|
|
|
67
62
|
[project.optional-dependencies]
|
|
68
63
|
standard = [
|
|
@@ -113,6 +108,12 @@ test = [
|
|
|
113
108
|
"redis>=6.2.0",
|
|
114
109
|
]
|
|
115
110
|
|
|
111
|
+
[dependency-groups]
|
|
112
|
+
dev = [
|
|
113
|
+
"crudadmin",
|
|
114
|
+
"crudadmin[standard,redis,memcached,postgres,mysql,dev]"
|
|
115
|
+
]
|
|
116
|
+
|
|
116
117
|
[tool.pytest.ini_options]
|
|
117
118
|
minversion = "6.0"
|
|
118
119
|
addopts = "-ra -q --strict-markers --strict-config"
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
from datetime import UTC, datetime
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
from uuid import uuid4
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
6
|
-
|
|
7
|
-
from ..core.schemas.timestamp import TimestampSchema
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SessionData(BaseModel):
|
|
11
|
-
"""Common base data for any user session."""
|
|
12
|
-
|
|
13
|
-
user_id: int
|
|
14
|
-
session_id: str = Field(default_factory=lambda: str(uuid4()))
|
|
15
|
-
ip_address: str
|
|
16
|
-
user_agent: str
|
|
17
|
-
device_info: dict[str, Any] = Field(default_factory=dict)
|
|
18
|
-
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
19
|
-
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
20
|
-
is_active: bool = True
|
|
21
|
-
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class SessionCreate(SessionData):
|
|
25
|
-
"""Schema for creating a new session."""
|
|
26
|
-
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class SessionUpdate(BaseModel):
|
|
31
|
-
"""Schema for updating a session."""
|
|
32
|
-
|
|
33
|
-
last_activity: Optional[datetime] = None
|
|
34
|
-
is_active: Optional[bool] = None
|
|
35
|
-
metadata: Optional[dict[str, Any]] = None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class UserAgentInfo(BaseModel):
|
|
39
|
-
"""User agent information parsed from the User-Agent header."""
|
|
40
|
-
|
|
41
|
-
browser: str
|
|
42
|
-
browser_version: str
|
|
43
|
-
os: str
|
|
44
|
-
device: str
|
|
45
|
-
is_mobile: bool
|
|
46
|
-
is_tablet: bool
|
|
47
|
-
is_pc: bool
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class CSRFToken(BaseModel):
|
|
51
|
-
"""CSRF token data."""
|
|
52
|
-
|
|
53
|
-
token: str
|
|
54
|
-
user_id: int
|
|
55
|
-
session_id: str
|
|
56
|
-
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
57
|
-
expires_at: datetime
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class AdminSessionBase(BaseModel):
|
|
61
|
-
"""Base schema for AdminSession."""
|
|
62
|
-
|
|
63
|
-
user_id: int
|
|
64
|
-
session_id: str
|
|
65
|
-
ip_address: str
|
|
66
|
-
user_agent: str
|
|
67
|
-
device_info: dict[str, Any] = Field(default_factory=dict)
|
|
68
|
-
session_metadata: dict[str, Any] = Field(default_factory=dict)
|
|
69
|
-
is_active: bool = True
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class AdminSession(TimestampSchema, AdminSessionBase):
|
|
73
|
-
"""Full AdminSession schema with all fields."""
|
|
74
|
-
|
|
75
|
-
id: int
|
|
76
|
-
created_at: datetime
|
|
77
|
-
last_activity: datetime
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class AdminSessionRead(BaseModel):
|
|
81
|
-
"""Schema for reading AdminSession data."""
|
|
82
|
-
|
|
83
|
-
id: int
|
|
84
|
-
user_id: int
|
|
85
|
-
session_id: str
|
|
86
|
-
ip_address: str
|
|
87
|
-
user_agent: str
|
|
88
|
-
device_info: dict[str, Any]
|
|
89
|
-
session_metadata: dict[str, Any]
|
|
90
|
-
created_at: datetime
|
|
91
|
-
last_activity: datetime
|
|
92
|
-
is_active: bool
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class AdminSessionCreate(AdminSessionBase):
|
|
96
|
-
"""Schema for creating AdminSession in database."""
|
|
97
|
-
|
|
98
|
-
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
99
|
-
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class AdminSessionUpdate(BaseModel):
|
|
103
|
-
"""Schema for updating AdminSession."""
|
|
104
|
-
|
|
105
|
-
last_activity: Optional[datetime] = None
|
|
106
|
-
is_active: Optional[bool] = None
|
|
107
|
-
session_metadata: Optional[dict[str, Any]] = None
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
class AdminSessionUpdateInternal(AdminSessionUpdate):
|
|
111
|
-
"""Internal schema for AdminSession updates."""
|
|
112
|
-
|
|
113
|
-
updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(UTC))
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
__all__ = [
|
|
117
|
-
"SessionData",
|
|
118
|
-
"SessionCreate",
|
|
119
|
-
"SessionUpdate",
|
|
120
|
-
"UserAgentInfo",
|
|
121
|
-
"CSRFToken",
|
|
122
|
-
"AdminSessionBase",
|
|
123
|
-
"AdminSession",
|
|
124
|
-
"AdminSessionRead",
|
|
125
|
-
"AdminSessionCreate",
|
|
126
|
-
"AdminSessionUpdate",
|
|
127
|
-
"AdminSessionUpdateInternal",
|
|
128
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/dashboard/dashboard_content.html
RENAMED
|
File without changes
|
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/events_content.html
RENAMED
|
File without changes
|
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/management/health_content.html
RENAMED
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/list_content.html
RENAMED
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/pagination.html
RENAMED
|
File without changes
|
{crudadmin-0.3.0 → crudadmin-0.3.2}/crudadmin/templates/admin/model/components/table_content.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|