django-cfg 1.4.103__py3-none-any.whl → 1.4.105__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md +153 -0
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +396 -0
- django_cfg/modules/django_admin/utils/html_builder.py +73 -0
- django_cfg/pyproject.toml +1 -1
- django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md +57 -0
- django_cfg/templates/admin/index.html +60 -7
- {django_cfg-1.4.103.dist-info → django_cfg-1.4.105.dist-info}/METADATA +1 -1
- {django_cfg-1.4.103.dist-info → django_cfg-1.4.105.dist-info}/RECORD +12 -10
- {django_cfg-1.4.103.dist-info → django_cfg-1.4.105.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.103.dist-info → django_cfg-1.4.105.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.103.dist-info → django_cfg-1.4.105.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# CHANGELOG: Code Display Methods
|
|
2
|
+
|
|
3
|
+
## [2024-10-29] Added `code()` and `code_block()` methods to HtmlBuilder
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
Added two new methods to `HtmlBuilder` for displaying code in Django Admin interfaces with proper styling, scrolling support, and color variants.
|
|
7
|
+
|
|
8
|
+
### New Methods
|
|
9
|
+
|
|
10
|
+
#### 1. `html.code(text, css_class="")`
|
|
11
|
+
Display inline code snippets.
|
|
12
|
+
|
|
13
|
+
**Use cases:**
|
|
14
|
+
- File paths
|
|
15
|
+
- Short commands
|
|
16
|
+
- Configuration keys
|
|
17
|
+
- Single-line code
|
|
18
|
+
|
|
19
|
+
**Example:**
|
|
20
|
+
```python
|
|
21
|
+
self.html.code("/path/to/file")
|
|
22
|
+
self.html.code("DEBUG=True")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### 2. `html.code_block(text, language=None, max_height=None, variant="default")`
|
|
26
|
+
Display multi-line code blocks with:
|
|
27
|
+
- Optional scrolling (via `max_height`)
|
|
28
|
+
- Color variants (default, warning, danger, success, info)
|
|
29
|
+
- Language hints for future syntax highlighting
|
|
30
|
+
- Proper text wrapping and escaping
|
|
31
|
+
|
|
32
|
+
**Use cases:**
|
|
33
|
+
- JSON configuration
|
|
34
|
+
- Command output (stdout/stderr)
|
|
35
|
+
- Error messages and stack traces
|
|
36
|
+
- Log files
|
|
37
|
+
- API requests/responses
|
|
38
|
+
- SQL queries
|
|
39
|
+
|
|
40
|
+
**Examples:**
|
|
41
|
+
```python
|
|
42
|
+
# JSON
|
|
43
|
+
self.html.code_block(json.dumps(data, indent=2), language="json")
|
|
44
|
+
|
|
45
|
+
# Logs with scrolling
|
|
46
|
+
self.html.code_block(obj.stdout, max_height="400px")
|
|
47
|
+
|
|
48
|
+
# Errors
|
|
49
|
+
self.html.code_block(obj.error_message, variant="danger", max_height="300px")
|
|
50
|
+
|
|
51
|
+
# Warnings
|
|
52
|
+
self.html.code_block(obj.stderr, variant="warning", max_height="400px")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Files Modified
|
|
56
|
+
|
|
57
|
+
**`utils/html_builder.py`:**
|
|
58
|
+
- Added `code()` method (lines 207-227)
|
|
59
|
+
- Added `code_block()` method (lines 229-278)
|
|
60
|
+
|
|
61
|
+
### Features
|
|
62
|
+
|
|
63
|
+
✅ **Automatic HTML Escaping** - Prevents XSS
|
|
64
|
+
✅ **Dark Mode Support** - Full Tailwind dark: classes
|
|
65
|
+
✅ **Responsive Design** - Works on mobile
|
|
66
|
+
✅ **Color Variants** - 5 variants for different contexts
|
|
67
|
+
✅ **Scrollable Content** - Max height with overflow
|
|
68
|
+
✅ **Consistent Styling** - Matches django-cfg design system
|
|
69
|
+
✅ **Future-proof** - Language parameter for syntax highlighting
|
|
70
|
+
✅ **Type Hints** - Full typing support
|
|
71
|
+
|
|
72
|
+
### Technical Details
|
|
73
|
+
|
|
74
|
+
**Dependencies:**
|
|
75
|
+
- Django's `format_html` and `escape`
|
|
76
|
+
- Tailwind CSS classes (already in use)
|
|
77
|
+
- No additional packages required
|
|
78
|
+
|
|
79
|
+
**Styling:**
|
|
80
|
+
- Uses existing Tailwind utility classes
|
|
81
|
+
- Follows django-cfg color system
|
|
82
|
+
- Light/dark mode variants
|
|
83
|
+
|
|
84
|
+
**Performance:**
|
|
85
|
+
- No JavaScript required
|
|
86
|
+
- Pure CSS for scrolling
|
|
87
|
+
- Minimal HTML overhead
|
|
88
|
+
|
|
89
|
+
### Breaking Changes
|
|
90
|
+
None - purely additive changes.
|
|
91
|
+
|
|
92
|
+
### Backward Compatibility
|
|
93
|
+
100% backward compatible. All existing code continues to work.
|
|
94
|
+
|
|
95
|
+
### Migration Path
|
|
96
|
+
Optional migration from manual HTML:
|
|
97
|
+
|
|
98
|
+
**Before:**
|
|
99
|
+
```python
|
|
100
|
+
def output_display(self, obj):
|
|
101
|
+
return format_html('<pre>{}</pre>', obj.stdout)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**After:**
|
|
105
|
+
```python
|
|
106
|
+
def output_display(self, obj):
|
|
107
|
+
return self.html.code_block(obj.stdout, max_height="400px")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Real-World Usage
|
|
111
|
+
|
|
112
|
+
Already implemented in:
|
|
113
|
+
- `apps.adminpanel.admin.config.CommandExecutionAdmin`
|
|
114
|
+
- JSON args/options display
|
|
115
|
+
- stdout/stderr output with scrolling
|
|
116
|
+
- Error messages with danger variant
|
|
117
|
+
|
|
118
|
+
### Documentation
|
|
119
|
+
|
|
120
|
+
Full documentation available in:
|
|
121
|
+
- `CODE_BLOCK_DOCS.md` - Complete guide with examples
|
|
122
|
+
- API Reference (to be updated)
|
|
123
|
+
- Quick Start guide (to be updated)
|
|
124
|
+
|
|
125
|
+
### Testing
|
|
126
|
+
|
|
127
|
+
Manual testing performed:
|
|
128
|
+
- ✅ Inline code rendering
|
|
129
|
+
- ✅ Code block with/without scrolling
|
|
130
|
+
- ✅ All 5 color variants
|
|
131
|
+
- ✅ JSON formatting
|
|
132
|
+
- ✅ Long text with wrapping
|
|
133
|
+
- ✅ Dark mode
|
|
134
|
+
- ✅ HTML escaping
|
|
135
|
+
|
|
136
|
+
### Next Steps
|
|
137
|
+
|
|
138
|
+
Recommended for future:
|
|
139
|
+
1. Add to API Reference documentation
|
|
140
|
+
2. Add to Examples documentation
|
|
141
|
+
3. Consider syntax highlighting library integration (Prism.js, highlight.js)
|
|
142
|
+
4. Add to django-cfg changelog
|
|
143
|
+
5. Update tutorial/quick-start docs
|
|
144
|
+
|
|
145
|
+
### Contributors
|
|
146
|
+
- Implementation: Claude Code
|
|
147
|
+
- Use Case: CommandExecution admin in StockAPIS project
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
**Version:** Added in django-cfg dev build (2024-10-29)
|
|
152
|
+
**Status:** ✅ Complete and tested
|
|
153
|
+
**Impact:** Low - purely additive feature
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Code Block Methods Documentation
|
|
2
|
+
|
|
3
|
+
New methods added to `HtmlBuilder` for displaying code in Django Admin.
|
|
4
|
+
|
|
5
|
+
## Methods Added
|
|
6
|
+
|
|
7
|
+
### 1. `code()` - Inline Code
|
|
8
|
+
|
|
9
|
+
Display short code snippets inline.
|
|
10
|
+
|
|
11
|
+
**Signature:**
|
|
12
|
+
```python
|
|
13
|
+
def code(text: Any, css_class: str = "") -> SafeString
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Parameters:**
|
|
17
|
+
- `text`: Code text to display
|
|
18
|
+
- `css_class`: Additional CSS classes (optional)
|
|
19
|
+
|
|
20
|
+
**Usage:**
|
|
21
|
+
```python
|
|
22
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
23
|
+
|
|
24
|
+
class MyAdmin(PydanticAdmin):
|
|
25
|
+
@computed_field("Command")
|
|
26
|
+
def command_display(self, obj):
|
|
27
|
+
return self.html.code(obj.full_command)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Examples:**
|
|
31
|
+
```python
|
|
32
|
+
# File path
|
|
33
|
+
self.html.code("/path/to/file")
|
|
34
|
+
|
|
35
|
+
# Command
|
|
36
|
+
self.html.code("python manage.py migrate")
|
|
37
|
+
|
|
38
|
+
# Configuration value
|
|
39
|
+
self.html.code("DEBUG=True")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Renders as:**
|
|
43
|
+
```html
|
|
44
|
+
<code class="font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded">
|
|
45
|
+
/path/to/file
|
|
46
|
+
</code>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### 2. `code_block()` - Multi-line Code Block
|
|
52
|
+
|
|
53
|
+
Display multi-line code with syntax highlighting hints, scrolling, and color variants.
|
|
54
|
+
|
|
55
|
+
**Signature:**
|
|
56
|
+
```python
|
|
57
|
+
def code_block(
|
|
58
|
+
text: Any,
|
|
59
|
+
language: Optional[str] = None,
|
|
60
|
+
max_height: Optional[str] = None,
|
|
61
|
+
variant: str = "default"
|
|
62
|
+
) -> SafeString
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Parameters:**
|
|
66
|
+
- `text`: Code content (multi-line supported)
|
|
67
|
+
- `language`: Programming language hint (`"json"`, `"python"`, `"bash"`, etc.) - for future syntax highlighting
|
|
68
|
+
- `max_height`: Maximum height with overflow scroll (e.g., `"400px"`, `"20rem"`)
|
|
69
|
+
- `variant`: Color variant - `"default"`, `"warning"`, `"danger"`, `"success"`, `"info"`
|
|
70
|
+
|
|
71
|
+
**Usage:**
|
|
72
|
+
```python
|
|
73
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
74
|
+
import json
|
|
75
|
+
|
|
76
|
+
class MyAdmin(PydanticAdmin):
|
|
77
|
+
@computed_field("Configuration")
|
|
78
|
+
def config_display(self, obj):
|
|
79
|
+
if not obj.config:
|
|
80
|
+
return self.html.empty()
|
|
81
|
+
|
|
82
|
+
return self.html.code_block(
|
|
83
|
+
json.dumps(obj.config, indent=2),
|
|
84
|
+
language="json"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@computed_field("Standard Output")
|
|
88
|
+
def stdout_display(self, obj):
|
|
89
|
+
if not obj.stdout:
|
|
90
|
+
return self.html.empty()
|
|
91
|
+
|
|
92
|
+
return self.html.code_block(
|
|
93
|
+
obj.stdout,
|
|
94
|
+
max_height="400px"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@computed_field("Error Message")
|
|
98
|
+
def error_display(self, obj):
|
|
99
|
+
if not obj.error_message:
|
|
100
|
+
return self.html.empty()
|
|
101
|
+
|
|
102
|
+
return self.html.code_block(
|
|
103
|
+
obj.error_message,
|
|
104
|
+
max_height="200px",
|
|
105
|
+
variant="danger"
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Examples:**
|
|
110
|
+
|
|
111
|
+
#### JSON Display
|
|
112
|
+
```python
|
|
113
|
+
# Display JSON configuration
|
|
114
|
+
self.html.code_block(
|
|
115
|
+
json.dumps({"key": "value", "nested": {"foo": "bar"}}, indent=2),
|
|
116
|
+
language="json"
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Log Output with Scrolling
|
|
121
|
+
```python
|
|
122
|
+
# Display command output with scroll
|
|
123
|
+
self.html.code_block(
|
|
124
|
+
"Line 1\nLine 2\nLine 3\n...",
|
|
125
|
+
max_height="400px"
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Error Messages
|
|
130
|
+
```python
|
|
131
|
+
# Display error with danger styling
|
|
132
|
+
self.html.code_block(
|
|
133
|
+
"Traceback (most recent call last):\n File ...",
|
|
134
|
+
variant="danger",
|
|
135
|
+
max_height="300px"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Warnings
|
|
140
|
+
```python
|
|
141
|
+
# Display warnings with warning styling
|
|
142
|
+
self.html.code_block(
|
|
143
|
+
"Warning: This is deprecated\nUse new_method() instead",
|
|
144
|
+
variant="warning"
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Renders as:**
|
|
149
|
+
```html
|
|
150
|
+
<!-- Default variant -->
|
|
151
|
+
<pre class="font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3
|
|
152
|
+
bg-base-50 dark:bg-base-900 border-base-200 dark:border-base-700"
|
|
153
|
+
style="max-height: 400px; overflow-y: auto;">
|
|
154
|
+
<code>{"key": "value"}</code>
|
|
155
|
+
</pre>
|
|
156
|
+
|
|
157
|
+
<!-- Danger variant -->
|
|
158
|
+
<pre class="font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3
|
|
159
|
+
bg-danger-50 dark:bg-danger-900/20 border-danger-200 dark:border-danger-700"
|
|
160
|
+
style="max-height: 200px; overflow-y: auto;">
|
|
161
|
+
<code>Error: Something went wrong</code>
|
|
162
|
+
</pre>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Color Variants
|
|
168
|
+
|
|
169
|
+
| Variant | Background | Border | Use Case |
|
|
170
|
+
|---------|-----------|--------|----------|
|
|
171
|
+
| `default` | Gray | Gray | Standard code, JSON, logs |
|
|
172
|
+
| `warning` | Yellow/Orange | Yellow/Orange | Warnings, stderr output |
|
|
173
|
+
| `danger` | Red | Red | Errors, exceptions, failures |
|
|
174
|
+
| `success` | Green | Green | Success messages, confirmations |
|
|
175
|
+
| `info` | Blue | Blue | Info messages, metadata |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Use Cases
|
|
180
|
+
|
|
181
|
+
### 1. Command Execution Logs
|
|
182
|
+
```python
|
|
183
|
+
@computed_field("Output")
|
|
184
|
+
def output_display(self, obj):
|
|
185
|
+
return self.html.code_block(
|
|
186
|
+
obj.stdout,
|
|
187
|
+
max_height="400px"
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 2. JSON Configuration
|
|
192
|
+
```python
|
|
193
|
+
@computed_field("Settings")
|
|
194
|
+
def settings_display(self, obj):
|
|
195
|
+
return self.html.code_block(
|
|
196
|
+
json.dumps(obj.settings, indent=2),
|
|
197
|
+
language="json"
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 3. Error Details
|
|
202
|
+
```python
|
|
203
|
+
@computed_field("Error")
|
|
204
|
+
def error_display(self, obj):
|
|
205
|
+
if not obj.error_message:
|
|
206
|
+
return self.html.empty()
|
|
207
|
+
|
|
208
|
+
return self.html.code_block(
|
|
209
|
+
obj.error_message,
|
|
210
|
+
max_height="300px",
|
|
211
|
+
variant="danger"
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 4. API Request/Response
|
|
216
|
+
```python
|
|
217
|
+
@computed_field("Request Body")
|
|
218
|
+
def request_body_display(self, obj):
|
|
219
|
+
return self.html.code_block(
|
|
220
|
+
obj.request_body,
|
|
221
|
+
language="json",
|
|
222
|
+
max_height="300px"
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 5. SQL Queries
|
|
227
|
+
```python
|
|
228
|
+
@computed_field("Query")
|
|
229
|
+
def query_display(self, obj):
|
|
230
|
+
return self.html.code_block(
|
|
231
|
+
obj.sql_query,
|
|
232
|
+
language="sql",
|
|
233
|
+
max_height="200px"
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Styling Details
|
|
240
|
+
|
|
241
|
+
### Tailwind Classes Used
|
|
242
|
+
|
|
243
|
+
**Base Classes:**
|
|
244
|
+
- `font-mono` - Monospace font
|
|
245
|
+
- `text-xs` - Small text size
|
|
246
|
+
- `whitespace-pre-wrap` - Preserve whitespace, wrap long lines
|
|
247
|
+
- `break-words` - Break long words
|
|
248
|
+
- `border` - Border
|
|
249
|
+
- `rounded-md` - Rounded corners
|
|
250
|
+
- `p-3` - Padding
|
|
251
|
+
|
|
252
|
+
**Variant-specific:**
|
|
253
|
+
- Light mode: `bg-{variant}-50 border-{variant}-200`
|
|
254
|
+
- Dark mode: `dark:bg-{variant}-900/20 dark:border-{variant}-700`
|
|
255
|
+
|
|
256
|
+
**Scrolling:**
|
|
257
|
+
- Applied via inline `style` attribute
|
|
258
|
+
- `max-height` and `overflow-y: auto`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Implementation Notes
|
|
263
|
+
|
|
264
|
+
1. **Escaping**: All text is automatically escaped using Django's `escape()` to prevent XSS
|
|
265
|
+
2. **SafeString**: Returns Django's `SafeString` for safe HTML rendering
|
|
266
|
+
3. **Dark Mode**: Full dark mode support with Tailwind's dark: prefix
|
|
267
|
+
4. **Responsive**: Works on mobile with `break-words` and `whitespace-pre-wrap`
|
|
268
|
+
5. **Future-proof**: `language` parameter allows for syntax highlighting integration later
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Complete Example: Command Execution Admin
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from django.contrib import admin
|
|
276
|
+
from django_cfg.modules.django_admin import (
|
|
277
|
+
AdminConfig, BadgeField, DateTimeField, Icons, computed_field
|
|
278
|
+
)
|
|
279
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
280
|
+
import json
|
|
281
|
+
|
|
282
|
+
commandexecution_config = AdminConfig(
|
|
283
|
+
model=CommandExecution,
|
|
284
|
+
list_display=["id", "command", "status", "created_at"],
|
|
285
|
+
readonly_fields=[
|
|
286
|
+
"command_display",
|
|
287
|
+
"args_display",
|
|
288
|
+
"stdout_display",
|
|
289
|
+
"stderr_display",
|
|
290
|
+
"error_display",
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
@admin.register(CommandExecution)
|
|
295
|
+
class CommandExecutionAdmin(PydanticAdmin):
|
|
296
|
+
config = commandexecution_config
|
|
297
|
+
|
|
298
|
+
@computed_field("Command")
|
|
299
|
+
def command_display(self, obj):
|
|
300
|
+
"""Inline code for command."""
|
|
301
|
+
return self.html.code(obj.full_command)
|
|
302
|
+
|
|
303
|
+
@computed_field("Arguments")
|
|
304
|
+
def args_display(self, obj):
|
|
305
|
+
"""JSON code block for args."""
|
|
306
|
+
if not obj.args:
|
|
307
|
+
return self.html.empty()
|
|
308
|
+
|
|
309
|
+
return self.html.code_block(
|
|
310
|
+
json.dumps(obj.args, indent=2),
|
|
311
|
+
language="json"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
@computed_field("Standard Output")
|
|
315
|
+
def stdout_display(self, obj):
|
|
316
|
+
"""Scrollable output."""
|
|
317
|
+
if not obj.stdout:
|
|
318
|
+
return self.html.empty()
|
|
319
|
+
|
|
320
|
+
return self.html.code_block(
|
|
321
|
+
obj.stdout,
|
|
322
|
+
max_height="400px"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
@computed_field("Standard Error")
|
|
326
|
+
def stderr_display(self, obj):
|
|
327
|
+
"""Warning-styled error output."""
|
|
328
|
+
if not obj.stderr:
|
|
329
|
+
return self.html.empty()
|
|
330
|
+
|
|
331
|
+
return self.html.code_block(
|
|
332
|
+
obj.stderr,
|
|
333
|
+
max_height="400px",
|
|
334
|
+
variant="warning"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
@computed_field("Error Message")
|
|
338
|
+
def error_display(self, obj):
|
|
339
|
+
"""Danger-styled error message."""
|
|
340
|
+
if not obj.error_message:
|
|
341
|
+
return self.html.empty()
|
|
342
|
+
|
|
343
|
+
return self.html.code_block(
|
|
344
|
+
obj.error_message,
|
|
345
|
+
max_height="200px",
|
|
346
|
+
variant="danger"
|
|
347
|
+
)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Testing
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
# Test inline code
|
|
356
|
+
assert 'font-mono' in str(html.code("test"))
|
|
357
|
+
|
|
358
|
+
# Test code block
|
|
359
|
+
assert '<pre' in str(html.code_block("test"))
|
|
360
|
+
assert 'overflow-y: auto' in str(html.code_block("test", max_height="400px"))
|
|
361
|
+
|
|
362
|
+
# Test variants
|
|
363
|
+
assert 'bg-danger' in str(html.code_block("error", variant="danger"))
|
|
364
|
+
assert 'bg-warning' in str(html.code_block("warn", variant="warning"))
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Migration from Old Format
|
|
370
|
+
|
|
371
|
+
**Before (manual HTML):**
|
|
372
|
+
```python
|
|
373
|
+
def output_display(self, obj):
|
|
374
|
+
return format_html(
|
|
375
|
+
'<pre style="max-height: 400px; overflow-y: auto; '
|
|
376
|
+
'background: #f8f9fa; padding: 10px; border-radius: 4px;">{}</pre>',
|
|
377
|
+
obj.stdout
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**After (with code_block):**
|
|
382
|
+
```python
|
|
383
|
+
def output_display(self, obj):
|
|
384
|
+
return self.html.code_block(
|
|
385
|
+
obj.stdout,
|
|
386
|
+
max_height="400px"
|
|
387
|
+
)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Benefits:
|
|
391
|
+
- ✅ Cleaner, more readable code
|
|
392
|
+
- ✅ Consistent styling across project
|
|
393
|
+
- ✅ Dark mode support
|
|
394
|
+
- ✅ Proper escaping
|
|
395
|
+
- ✅ Tailwind CSS classes
|
|
396
|
+
- ✅ Variant support for different contexts
|
|
@@ -203,3 +203,76 @@ class HtmlBuilder:
|
|
|
203
203
|
'<span class="text-font-subtle-light dark:text-font-subtle-dark">{}</span>',
|
|
204
204
|
escape(text)
|
|
205
205
|
)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def code(text: Any, css_class: str = "") -> SafeString:
|
|
209
|
+
"""
|
|
210
|
+
Render inline code.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
text: Code text
|
|
214
|
+
css_class: Additional CSS classes
|
|
215
|
+
|
|
216
|
+
Usage:
|
|
217
|
+
html.code("/path/to/file")
|
|
218
|
+
html.code("command --arg value")
|
|
219
|
+
"""
|
|
220
|
+
base_classes = "font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded"
|
|
221
|
+
classes = f"{base_classes} {css_class}".strip()
|
|
222
|
+
|
|
223
|
+
return format_html(
|
|
224
|
+
'<code class="{}">{}</code>',
|
|
225
|
+
classes,
|
|
226
|
+
escape(str(text))
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def code_block(
|
|
231
|
+
text: Any,
|
|
232
|
+
language: Optional[str] = None,
|
|
233
|
+
max_height: Optional[str] = None,
|
|
234
|
+
variant: str = "default"
|
|
235
|
+
) -> SafeString:
|
|
236
|
+
"""
|
|
237
|
+
Render code block with optional syntax highlighting and scrolling.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
text: Code content
|
|
241
|
+
language: Programming language (json, python, bash, etc.) - for future syntax highlighting
|
|
242
|
+
max_height: Max height with scrolling (e.g., "400px", "20rem")
|
|
243
|
+
variant: Color variant - default, warning, danger, success, info
|
|
244
|
+
|
|
245
|
+
Usage:
|
|
246
|
+
html.code_block(json.dumps(data, indent=2), language="json")
|
|
247
|
+
html.code_block(stdout, max_height="400px")
|
|
248
|
+
html.code_block(stderr, max_height="400px", variant="warning")
|
|
249
|
+
"""
|
|
250
|
+
# Variant-specific styles
|
|
251
|
+
variant_classes = {
|
|
252
|
+
'default': 'bg-base-50 dark:bg-base-900 border-base-200 dark:border-base-700',
|
|
253
|
+
'warning': 'bg-warning-50 dark:bg-warning-900/20 border-warning-200 dark:border-warning-700',
|
|
254
|
+
'danger': 'bg-danger-50 dark:bg-danger-900/20 border-danger-200 dark:border-danger-700',
|
|
255
|
+
'success': 'bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-700',
|
|
256
|
+
'info': 'bg-info-50 dark:bg-info-900/20 border-info-200 dark:border-info-700',
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
variant_class = variant_classes.get(variant, variant_classes['default'])
|
|
260
|
+
|
|
261
|
+
# Base styles
|
|
262
|
+
base_classes = f"font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3 {variant_class}"
|
|
263
|
+
|
|
264
|
+
# Add max-height and overflow if specified
|
|
265
|
+
style = ""
|
|
266
|
+
if max_height:
|
|
267
|
+
style = f'style="max-height: {max_height}; overflow-y: auto;"'
|
|
268
|
+
|
|
269
|
+
# Add language class for potential syntax highlighting
|
|
270
|
+
lang_class = f"language-{language}" if language else ""
|
|
271
|
+
|
|
272
|
+
return format_html(
|
|
273
|
+
'<pre class="{} {}" {}><code>{}</code></pre>',
|
|
274
|
+
base_classes,
|
|
275
|
+
lang_class,
|
|
276
|
+
style,
|
|
277
|
+
escape(str(text))
|
|
278
|
+
)
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.105"
|
|
8
8
|
description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
|
|
@@ -180,6 +180,63 @@ resetIframe(tab) {
|
|
|
180
180
|
**Why Reset?**
|
|
181
181
|
This ensures that when users switch back to a tab, it starts from the home page rather than whatever route they navigated to previously.
|
|
182
182
|
|
|
183
|
+
### Open in New Window
|
|
184
|
+
|
|
185
|
+
The External Admin tab (Tab 2) includes an "Open in New Window" button that allows users to break out of the iframe and work in a dedicated browser window/tab:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
openInNewWindow() {
|
|
189
|
+
// Get the current iframe URL for the External Admin tab
|
|
190
|
+
const iframe = document.getElementById('nextjs-dashboard-iframe-nextjs');
|
|
191
|
+
if (iframe) {
|
|
192
|
+
const currentUrl = iframe.src || iframe.getAttribute('data-original-src');
|
|
193
|
+
if (currentUrl) {
|
|
194
|
+
window.open(currentUrl, '_blank', 'noopener,noreferrer');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Features:**
|
|
201
|
+
- Only visible when External Admin tab is active (`x-show="activeTab === 'nextjs'"`)
|
|
202
|
+
- Opens current iframe URL in new window with `noopener,noreferrer` security flags
|
|
203
|
+
- Preserves current route via `postMessage` tracking (see below)
|
|
204
|
+
- Styled as action button with icon and text label
|
|
205
|
+
|
|
206
|
+
**How Route Tracking Works:**
|
|
207
|
+
|
|
208
|
+
In **production** (same-origin), `iframe.src` updates automatically:
|
|
209
|
+
```javascript
|
|
210
|
+
// iframe.src reflects current URL automatically
|
|
211
|
+
window.open(iframe.src, '_blank');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
In **development** (cross-origin), we track navigation via `postMessage`:
|
|
215
|
+
```javascript
|
|
216
|
+
// iframe sends navigation events
|
|
217
|
+
case 'iframe-navigation':
|
|
218
|
+
if (data?.path) {
|
|
219
|
+
alpineData.currentNextjsPath = data.path; // Track path
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Button uses tracked path
|
|
223
|
+
openInNewWindow() {
|
|
224
|
+
const url = new URL(baseUrl);
|
|
225
|
+
url.pathname = this.currentNextjsPath; // Apply tracked path
|
|
226
|
+
window.open(url.toString(), '_blank');
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Why postMessage?**
|
|
231
|
+
Cross-origin iframes cannot access `iframe.src` due to browser security (CORS). The iframe must explicitly send navigation events via `postMessage`.
|
|
232
|
+
|
|
233
|
+
**Why This is Useful:**
|
|
234
|
+
- Full browser features (address bar, bookmarks, etc.)
|
|
235
|
+
- No iframe sandbox restrictions
|
|
236
|
+
- Easier debugging (browser DevTools)
|
|
237
|
+
- Better for complex workflows that require multiple windows
|
|
238
|
+
- Copy/paste and other browser features work better
|
|
239
|
+
|
|
183
240
|
## Static File Serving
|
|
184
241
|
|
|
185
242
|
### Built-in Admin (Tab 1)
|
|
@@ -136,6 +136,7 @@
|
|
|
136
136
|
<div x-data="{
|
|
137
137
|
activeTab: 'builtin',
|
|
138
138
|
previousTab: 'builtin',
|
|
139
|
+
currentNextjsPath: '',
|
|
139
140
|
switchTab(tab) {
|
|
140
141
|
if (this.previousTab !== tab) {
|
|
141
142
|
// Reset iframe to initial URL when switching tabs
|
|
@@ -152,6 +153,34 @@
|
|
|
152
153
|
const originalSrc = iframe.getAttribute('data-original-src') || iframe.src;
|
|
153
154
|
iframe.src = originalSrc;
|
|
154
155
|
}
|
|
156
|
+
// Reset path when resetting iframe
|
|
157
|
+
if (tab === 'nextjs') {
|
|
158
|
+
this.currentNextjsPath = '';
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
openInNewWindow() {
|
|
162
|
+
// Get the current iframe URL for the External Admin tab
|
|
163
|
+
const iframe = document.getElementById('nextjs-dashboard-iframe-nextjs');
|
|
164
|
+
if (!iframe) return;
|
|
165
|
+
|
|
166
|
+
// Get base URL from iframe src or data-original-src
|
|
167
|
+
let baseUrl = iframe.src || iframe.getAttribute('data-original-src');
|
|
168
|
+
|
|
169
|
+
// If we have a tracked path from postMessage, use it
|
|
170
|
+
if (this.currentNextjsPath) {
|
|
171
|
+
try {
|
|
172
|
+
const url = new URL(baseUrl);
|
|
173
|
+
// Replace pathname with tracked path
|
|
174
|
+
url.pathname = this.currentNextjsPath;
|
|
175
|
+
baseUrl = url.toString();
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.warn('[Django-CFG] Failed to construct URL with path:', e);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (baseUrl) {
|
|
182
|
+
window.open(baseUrl, '_blank', 'noopener,noreferrer');
|
|
183
|
+
}
|
|
155
184
|
}
|
|
156
185
|
}">
|
|
157
186
|
{% if is_frontend_dev_mode %}
|
|
@@ -200,12 +229,25 @@
|
|
|
200
229
|
</button>
|
|
201
230
|
</nav>
|
|
202
231
|
|
|
203
|
-
<!-- Version info -->
|
|
204
|
-
<div class="
|
|
205
|
-
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
232
|
+
<!-- Actions & Version info -->
|
|
233
|
+
<div class="flex items-center gap-4 py-4">
|
|
234
|
+
<!-- Open in new window button (only for External Admin tab) -->
|
|
235
|
+
<button @click="openInNewWindow()"
|
|
236
|
+
x-show="activeTab === 'nextjs'"
|
|
237
|
+
title="Open in new window"
|
|
238
|
+
class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-700 transition-all duration-150"
|
|
239
|
+
style="display: none;">
|
|
240
|
+
<span class="material-icons" style="font-size: 16px;">open_in_new</span>
|
|
241
|
+
<span>Open in New Window</span>
|
|
242
|
+
</button>
|
|
243
|
+
|
|
244
|
+
<!-- Version info -->
|
|
245
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">
|
|
246
|
+
{% load django_cfg %}
|
|
247
|
+
<a href="{% lib_site_url %}" class="text-blue-600 hover:text-blue-700">
|
|
248
|
+
{% lib_name %}
|
|
249
|
+
</a>
|
|
250
|
+
</div>
|
|
209
251
|
</div>
|
|
210
252
|
</div>
|
|
211
253
|
</div>
|
|
@@ -419,7 +461,18 @@
|
|
|
419
461
|
break;
|
|
420
462
|
|
|
421
463
|
case 'iframe-navigation':
|
|
422
|
-
|
|
464
|
+
console.log('[Django-CFG] iframe navigated to:', data?.path);
|
|
465
|
+
// Track current path for "Open in New Window" button
|
|
466
|
+
if (iframe.id === 'nextjs-dashboard-iframe-nextjs' && data?.path) {
|
|
467
|
+
// Update Alpine.js data
|
|
468
|
+
const alpineEl = document.querySelector('[x-data]');
|
|
469
|
+
if (alpineEl && window.Alpine) {
|
|
470
|
+
const alpineData = window.Alpine.$data(alpineEl);
|
|
471
|
+
if (alpineData) {
|
|
472
|
+
alpineData.currentNextjsPath = data.path;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
423
476
|
break;
|
|
424
477
|
|
|
425
478
|
default:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.105
|
|
4
4
|
Summary: Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=MXUS0jQw0iZtqEX_dQus7hIBxuCa6q1o31FhWBRFJOo,1621
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
|
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
|
@@ -694,6 +694,7 @@ django_cfg/models/tasks/config.py,sha256=dqDoiqMWsSdKJrLAyGcKyzBukZdjhz7kR5_LKFZ
|
|
|
694
694
|
django_cfg/models/tasks/utils.py,sha256=5CR_d6dD6vYZ9UpI3kKD9EJwuPdWWuqiA_hhxXzmogs,4811
|
|
695
695
|
django_cfg/modules/__init__.py,sha256=Ip9WMpzImEwIAywpFwU056_v0O9oIGG7nCT1YSArxkw,316
|
|
696
696
|
django_cfg/modules/base.py,sha256=Grmgxc5dvnAEM1sudWEWO4kv8L0Ks-y32nxTk2vwdjQ,6272
|
|
697
|
+
django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md,sha256=HfE_rUlovx1zX_1hkzQsjwghaFvIvUWjZ_Aume8lhIs,3823
|
|
697
698
|
django_cfg/modules/django_admin/__init__.py,sha256=rWUY6Le2gO-szuuQyrUUP8sLIaTwkNDBexdK8Vbwzv0,3094
|
|
698
699
|
django_cfg/modules/django_admin/base/__init__.py,sha256=tzre09bnD_SlS-pA30WzYZRxyvch7eLq3q0wLEcZOmc,118
|
|
699
700
|
django_cfg/modules/django_admin/base/pydantic_admin.py,sha256=jCsueX1r_nD73FNeiFwhI149JKdpwIyQm0NCHmtNzXU,15976
|
|
@@ -712,11 +713,12 @@ django_cfg/modules/django_admin/models/action_models.py,sha256=clzu_DJOexrqyKfkv
|
|
|
712
713
|
django_cfg/modules/django_admin/models/badge_models.py,sha256=ob5NOtx8YZ9KRVmn9fxLWg_OXbvt3fLhhk8wwU5eP8E,538
|
|
713
714
|
django_cfg/modules/django_admin/models/base.py,sha256=fmgS-X3GAGEBXiBy4OQGeOMzB7qIkvzDXla5-_EHAQE,611
|
|
714
715
|
django_cfg/modules/django_admin/models/display_models.py,sha256=rONmja60Qz8n4qNvzjXIQryO_-UGZK136SrElO8iFfM,1087
|
|
716
|
+
django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md,sha256=rp5qMG-Ci30fIs6EyZctjEbhQwxfNq9e36B4CTZOnR0,9456
|
|
715
717
|
django_cfg/modules/django_admin/utils/__init__.py,sha256=DkMCGBbSe6Xfgdse-C8JkcqTRxtq3k2tMKxt39F1GfA,649
|
|
716
718
|
django_cfg/modules/django_admin/utils/badges.py,sha256=eZ1UThdwvv2cHAIDc4vTrD5xAet7fmeb9h9yj4ZXJ-c,6328
|
|
717
719
|
django_cfg/modules/django_admin/utils/decorators.py,sha256=s4jTcgPklY4T4xjXsMHpShd71K_LzgKogem9JKBgNzk,8371
|
|
718
720
|
django_cfg/modules/django_admin/utils/displays.py,sha256=f-FT1mD-X56X6xLDJ9FuCi4oz_UYirdLosYrXnJP3hI,7862
|
|
719
|
-
django_cfg/modules/django_admin/utils/html_builder.py,sha256=
|
|
721
|
+
django_cfg/modules/django_admin/utils/html_builder.py,sha256=d-_laj5Yz7gY0Xkwg1vNIlcK-N8F1Z3atM4-t1584pw,9793
|
|
720
722
|
django_cfg/modules/django_admin/widgets/__init__.py,sha256=mmPw5FMYR21GDGFMr-MOCcdM4G2_ZR60ClInHjdnTBE,115
|
|
721
723
|
django_cfg/modules/django_admin/widgets/registry.py,sha256=q0Yyaze5ZTYLJslPyX9e4Few_FGLnGBQwtNln9Okyt4,5610
|
|
722
724
|
django_cfg/modules/django_client/__init__.py,sha256=iHaGKbsyR2wMmVCWNsETC7cwB60fZudvnFMiK1bchW8,529
|
|
@@ -1072,8 +1074,8 @@ django_cfg/static/js/api/support/index.mjs,sha256=oPA3iGkUWYyKQuJlI5-tSxD3AOhwlA
|
|
|
1072
1074
|
django_cfg/static/js/api/tasks/client.mjs,sha256=tIy8K-finXzTUL9kOo_L4Q1kchDaHyuzjwS4VymiWPM,3579
|
|
1073
1075
|
django_cfg/static/js/api/tasks/index.mjs,sha256=yCY1GzdD-RtFZ3pAfk1l0msgO1epyo0lsGCjH0g1Afc,294
|
|
1074
1076
|
django_cfg/templates/__init__.py,sha256=IzLjt-a7VIJ0OutmAE1_-w0_LpL2u0MgGpnIabjZuW8,19
|
|
1075
|
-
django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md,sha256=
|
|
1076
|
-
django_cfg/templates/admin/index.html,sha256=
|
|
1077
|
+
django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md,sha256=CL8E3K4rFpXeQiNgrYSMvCW1y-eFaoXxxsI58zPf9dY,17562
|
|
1078
|
+
django_cfg/templates/admin/index.html,sha256=T8atxHnk7f6hiRDA4SKhpRY76srDB-auLANmjSSbejs,20654
|
|
1077
1079
|
django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8G4L_GexPxvz6XQ,8836
|
|
1078
1080
|
django_cfg/templates/unfold/layouts/skeleton.html,sha256=2ArkcNZ34mFs30cOAsTQ1EZiDXcB0aVxkO71lJq9SLE,718
|
|
1079
1081
|
django_cfg/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -1085,9 +1087,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1085
1087
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1086
1088
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1087
1089
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1088
|
-
django_cfg/pyproject.toml,sha256=
|
|
1089
|
-
django_cfg-1.4.
|
|
1090
|
-
django_cfg-1.4.
|
|
1091
|
-
django_cfg-1.4.
|
|
1092
|
-
django_cfg-1.4.
|
|
1093
|
-
django_cfg-1.4.
|
|
1090
|
+
django_cfg/pyproject.toml,sha256=mSzm-YWCFBLu53-OiCS9-wjIBxA9vm-yMg6Dii7FzYc,8573
|
|
1091
|
+
django_cfg-1.4.105.dist-info/METADATA,sha256=mQjCmuDpR7D-vMY12IAIzJskSnrlBEBNWQ2Fwn-vAEc,23734
|
|
1092
|
+
django_cfg-1.4.105.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1093
|
+
django_cfg-1.4.105.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1094
|
+
django_cfg-1.4.105.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1095
|
+
django_cfg-1.4.105.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|