0
# File Management
1
2
File and directory administration interface with upload, download, edit capabilities, and flexible storage backend system for managing files through Flask-Admin.
3
4
## Capabilities
5
6
### File Storage Backend
7
8
Storage abstraction layer supporting different file storage backends with consistent interface.
9
10
```python { .api }
11
class LocalFileStorage:
12
def __init__(self, base_path):
13
"""
14
Initialize local filesystem storage backend.
15
16
Args:
17
base_path (str): Base directory path for file operations
18
"""
19
20
def get_base_path(self):
21
"""
22
Get base directory path.
23
24
Returns:
25
str: Base directory path
26
"""
27
28
def make_dir(self, path, directory):
29
"""
30
Create directory.
31
32
Args:
33
path (str): Parent directory path
34
directory (str): New directory name
35
36
Returns:
37
bool: True if successful
38
"""
39
40
def get_files(self, path, directory):
41
"""
42
List files and directories in given path.
43
44
Args:
45
path (str): Directory path to list
46
directory (str): Directory name
47
48
Returns:
49
list: File and directory information
50
"""
51
52
def delete_tree(self, directory):
53
"""
54
Delete directory recursively.
55
56
Args:
57
directory (str): Directory path to delete
58
59
Returns:
60
bool: True if successful
61
"""
62
63
def delete_file(self, file_path):
64
"""
65
Delete single file.
66
67
Args:
68
file_path (str): Path to file to delete
69
70
Returns:
71
bool: True if successful
72
"""
73
74
def path_exists(self, path):
75
"""
76
Check if path exists.
77
78
Args:
79
path (str): Path to check
80
81
Returns:
82
bool: True if path exists
83
"""
84
85
def rename_path(self, src, dst):
86
"""
87
Rename file or directory.
88
89
Args:
90
src (str): Source path
91
dst (str): Destination path
92
93
Returns:
94
bool: True if successful
95
"""
96
97
def is_dir(self, path):
98
"""
99
Check if path is directory.
100
101
Args:
102
path (str): Path to check
103
104
Returns:
105
bool: True if path is directory
106
"""
107
108
def send_file(self, file_path):
109
"""
110
Send file to user for download.
111
112
Args:
113
file_path (str): Path to file
114
115
Returns:
116
Response: Flask file response
117
"""
118
119
def read_file(self, path):
120
"""
121
Read file contents.
122
123
Args:
124
path (str): File path to read
125
126
Returns:
127
str: File contents as string
128
"""
129
130
def write_file(self, path, content):
131
"""
132
Write content to file.
133
134
Args:
135
path (str): File path to write
136
content (str): Content to write
137
138
Returns:
139
bool: True if successful
140
"""
141
142
def save_file(self, path, file_data):
143
"""
144
Save uploaded file data.
145
146
Args:
147
path (str): Destination file path
148
file_data: File data from upload
149
150
Returns:
151
bool: True if successful
152
"""
153
```
154
155
### Base File Admin
156
157
Foundation class for file administration interfaces with comprehensive file and directory management.
158
159
```python { .api }
160
class BaseFileAdmin(BaseView, ActionsMixin):
161
def __init__(
162
self,
163
base_url=None,
164
name=None,
165
category=None,
166
endpoint=None,
167
url=None,
168
verify_path=True,
169
menu_class_name=None,
170
menu_icon_type=None,
171
menu_icon_value=None,
172
storage=None
173
):
174
"""
175
Initialize base file administration interface.
176
177
Args:
178
base_url (str, optional): Base URL for file serving
179
name (str, optional): View name for menu
180
category (str, optional): Menu category
181
endpoint (str, optional): Blueprint endpoint
182
url (str, optional): URL prefix
183
verify_path (bool): Verify path accessibility
184
menu_class_name (str, optional): Menu CSS class
185
menu_icon_type (str, optional): Icon type
186
menu_icon_value (str, optional): Icon identifier
187
storage: Storage backend instance
188
"""
189
190
# Permission configuration
191
can_upload = True # Allow file uploads
192
can_download = True # Allow file downloads
193
can_delete = True # Allow file deletion
194
can_delete_dirs = True # Allow directory deletion
195
can_mkdir = True # Allow directory creation
196
can_rename = True # Allow renaming files/directories
197
198
# File type restrictions
199
allowed_extensions = None # Allowed file extensions (None = all)
200
editable_extensions = ('md', 'txt', 'py', 'js', 'html', 'css', 'json', 'xml', 'yaml', 'yml', 'ini', 'cfg')
201
202
# Interface configuration
203
list_template = 'admin/file/list.html'
204
upload_template = 'admin/file/upload.html'
205
mkdir_template = 'admin/file/mkdir.html'
206
207
def is_accessible_path(self, path):
208
"""
209
Check if path is accessible for current user.
210
211
Args:
212
path (str): Path to check
213
214
Returns:
215
bool: True if accessible
216
"""
217
218
def get_base_path(self):
219
"""
220
Get base path for file operations.
221
222
Returns:
223
str: Base path
224
"""
225
226
def is_file_allowed(self, filename):
227
"""
228
Check if file upload is permitted based on extension.
229
230
Args:
231
filename (str): Filename to check
232
233
Returns:
234
bool: True if allowed
235
"""
236
237
def is_file_editable(self, filename):
238
"""
239
Check if file can be edited inline.
240
241
Args:
242
filename (str): Filename to check
243
244
Returns:
245
bool: True if editable
246
"""
247
248
# View methods (exposed via @expose decorator)
249
def index(self):
250
"""
251
File browser index view.
252
253
Returns:
254
str: Rendered file browser template
255
"""
256
257
def upload(self):
258
"""
259
File upload view.
260
261
Returns:
262
str: Rendered upload template or redirect
263
"""
264
265
def download(self):
266
"""
267
File download view.
268
269
Returns:
270
Response: File download response
271
"""
272
273
def edit(self):
274
"""
275
File edit view for text files.
276
277
Returns:
278
str: Rendered edit template or redirect
279
"""
280
281
def rename(self):
282
"""
283
Rename file or directory view.
284
285
Returns:
286
str: Rendered rename template or redirect
287
"""
288
289
def mkdir(self):
290
"""
291
Create directory view.
292
293
Returns:
294
str: Rendered mkdir template or redirect
295
"""
296
297
# Action methods
298
def action_delete(self, items):
299
"""
300
Batch delete action for selected files/directories.
301
302
Args:
303
items (list): List of file/directory paths to delete
304
"""
305
306
def action_edit(self, items):
307
"""
308
Batch edit action for selected text files.
309
310
Args:
311
items (list): List of file paths to edit
312
"""
313
314
class FileAdmin(BaseFileAdmin):
315
def __init__(self, base_path, *args, **kwargs):
316
"""
317
Simple file management interface for local filesystem.
318
319
Args:
320
base_path (str): Base directory path to manage
321
*args: Additional positional arguments
322
**kwargs: Additional keyword arguments
323
"""
324
```
325
326
### Amazon S3 Storage Backend
327
328
Cloud storage backend for Amazon S3 with full file management capabilities including upload, download, and directory operations.
329
330
```python { .api }
331
from flask_admin.contrib.fileadmin.s3 import S3Storage
332
333
class S3Storage:
334
def __init__(
335
self,
336
bucket_name,
337
region,
338
aws_access_key_id,
339
aws_secret_access_key
340
):
341
"""
342
Amazon S3 storage backend.
343
344
Parameters:
345
- bucket_name: str, Name of the S3 bucket
346
- region: str, AWS region where bucket is located
347
- aws_access_key_id: str, AWS Access Key ID
348
- aws_secret_access_key: str, AWS Secret Access Key
349
"""
350
```
351
352
### Azure Blob Storage Backend
353
354
Microsoft Azure Blob Storage backend for cloud-based file management with container support.
355
356
```python { .api }
357
from flask_admin.contrib.fileadmin.azure import AzureStorage
358
359
class AzureStorage:
360
def __init__(self, container_name, connection_string):
361
"""
362
Azure Blob Storage backend.
363
364
Parameters:
365
- container_name: str, Name of the Azure storage container
366
- connection_string: str, Azure Blob Storage connection string
367
"""
368
```
369
370
## Usage Examples
371
372
### Basic File Administration
373
374
```python
375
from flask import Flask
376
from flask_admin import Admin
377
from flask_admin.contrib.fileadmin import FileAdmin
378
import os.path as op
379
380
app = Flask(__name__)
381
app.config['SECRET_KEY'] = 'secret-key'
382
383
# Initialize admin
384
admin = Admin(app, name='File Manager')
385
386
# Add file admin for uploads directory
387
path = op.join(op.dirname(__file__), 'uploads')
388
admin.add_view(FileAdmin(path, '/uploads/', name='Upload Files'))
389
390
# Add file admin for static files with restricted permissions
391
static_path = op.join(op.dirname(__file__), 'static')
392
admin.add_view(FileAdmin(
393
static_path,
394
'/static/',
395
name='Static Files',
396
category='Files'
397
))
398
```
399
400
### Custom File Admin with Restrictions
401
402
```python
403
from flask_admin.contrib.fileadmin import FileAdmin
404
from flask_login import current_user
405
406
class RestrictedFileAdmin(FileAdmin):
407
# File type restrictions
408
allowed_extensions = ('txt', 'md', 'py', 'js', 'css', 'html', 'json', 'xml')
409
editable_extensions = ('txt', 'md', 'py', 'js', 'css', 'html', 'json')
410
411
# Permission configuration
412
can_upload = True
413
can_download = True
414
can_delete = False # Disable deletion
415
can_delete_dirs = False
416
can_mkdir = True
417
can_rename = True
418
419
def is_accessible(self):
420
"""Only allow access to authenticated admin users."""
421
return current_user.is_authenticated and current_user.is_admin
422
423
def is_accessible_path(self, path):
424
"""Restrict access to certain directories."""
425
# Prevent access to hidden directories
426
if '/.git' in path or '/.env' in path:
427
return False
428
429
# Prevent access to Python cache directories
430
if '__pycache__' in path:
431
return False
432
433
return super().is_accessible_path(path)
434
435
def is_file_allowed(self, filename):
436
"""Additional file upload restrictions."""
437
# Block executable files
438
if filename.lower().endswith(('.exe', '.bat', '.sh', '.com')):
439
return False
440
441
return super().is_file_allowed(filename)
442
443
# Register with admin
444
uploads_path = op.join(op.dirname(__file__), 'uploads')
445
admin.add_view(RestrictedFileAdmin(
446
uploads_path,
447
'/uploads/',
448
name='Managed Files',
449
category='File Management'
450
))
451
```
452
453
### Custom Storage Backend
454
455
```python
456
from flask_admin.contrib.fileadmin import BaseFileAdmin, LocalFileStorage
457
import os
458
import shutil
459
from werkzeug.utils import secure_filename
460
461
class S3FileStorage:
462
"""Example custom storage backend for AWS S3."""
463
464
def __init__(self, bucket_name, aws_access_key, aws_secret_key):
465
"""
466
Initialize S3 storage backend.
467
468
Args:
469
bucket_name (str): S3 bucket name
470
aws_access_key (str): AWS access key
471
aws_secret_key (str): AWS secret key
472
"""
473
self.bucket_name = bucket_name
474
# Initialize S3 client here
475
pass
476
477
def get_files(self, path, directory):
478
"""List files in S3 bucket path."""
479
# Implement S3 listing logic
480
pass
481
482
def save_file(self, path, file_data):
483
"""Upload file to S3."""
484
# Implement S3 upload logic
485
pass
486
487
def delete_file(self, file_path):
488
"""Delete file from S3."""
489
# Implement S3 deletion logic
490
pass
491
492
# Implement other required methods...
493
494
class CustomFileAdmin(BaseFileAdmin):
495
def __init__(self, storage_config, *args, **kwargs):
496
# Initialize with custom storage backend
497
storage = S3FileStorage(**storage_config)
498
super().__init__(storage=storage, *args, **kwargs)
499
500
# Usage with custom storage
501
s3_config = {
502
'bucket_name': 'my-files-bucket',
503
'aws_access_key': 'your-access-key',
504
'aws_secret_key': 'your-secret-key'
505
}
506
507
admin.add_view(CustomFileAdmin(
508
s3_config,
509
name='S3 Files',
510
category='Cloud Storage'
511
))
512
```
513
514
### Advanced File Operations
515
516
```python
517
from flask_admin.contrib.fileadmin import FileAdmin
518
from flask import flash, request, redirect, url_for
519
import os
520
import zipfile
521
from PIL import Image
522
523
class AdvancedFileAdmin(FileAdmin):
524
# Enhanced file type support
525
allowed_extensions = ('txt', 'md', 'py', 'js', 'css', 'html', 'json', 'xml',
526
'jpg', 'jpeg', 'png', 'gif', 'pdf', 'zip', 'tar', 'gz')
527
528
editable_extensions = ('txt', 'md', 'py', 'js', 'css', 'html', 'json', 'xml', 'yaml', 'yml')
529
530
def on_file_upload(self, directory, path, filename):
531
"""Hook called after successful file upload."""
532
file_path = os.path.join(path, filename)
533
534
# Auto-generate thumbnails for images
535
if filename.lower().endswith(('jpg', 'jpeg', 'png')):
536
self.generate_thumbnail(file_path)
537
538
# Auto-extract ZIP files
539
if filename.lower().endswith('.zip') and request.form.get('auto_extract'):
540
self.extract_zip(file_path, directory)
541
542
def generate_thumbnail(self, image_path):
543
"""Generate thumbnail for uploaded image."""
544
try:
545
with Image.open(image_path) as img:
546
img.thumbnail((200, 200))
547
thumb_path = image_path.rsplit('.', 1)[0] + '_thumb.' + image_path.rsplit('.', 1)[1]
548
img.save(thumb_path)
549
flash(f'Generated thumbnail: {os.path.basename(thumb_path)}', 'success')
550
except Exception as ex:
551
flash(f'Failed to generate thumbnail: {str(ex)}', 'warning')
552
553
def extract_zip(self, zip_path, target_dir):
554
"""Extract ZIP file contents."""
555
try:
556
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
557
extract_path = os.path.join(target_dir, 'extracted')
558
os.makedirs(extract_path, exist_ok=True)
559
zip_ref.extractall(extract_path)
560
flash(f'Extracted ZIP contents to: extracted/', 'success')
561
except Exception as ex:
562
flash(f'Failed to extract ZIP: {str(ex)}', 'error')
563
564
@expose('/compress/')
565
def compress_files(self):
566
"""Custom action to compress selected files."""
567
if request.method == 'POST':
568
file_paths = request.form.getlist('file_paths')
569
if file_paths:
570
zip_name = request.form.get('zip_name', 'archive.zip')
571
self.create_zip_archive(file_paths, zip_name)
572
return redirect(url_for('.index'))
573
574
return self.render('admin/file/compress.html')
575
576
def create_zip_archive(self, file_paths, zip_name):
577
"""Create ZIP archive from selected files."""
578
try:
579
zip_path = os.path.join(self.get_base_path(), zip_name)
580
with zipfile.ZipFile(zip_path, 'w') as zip_file:
581
for file_path in file_paths:
582
full_path = os.path.join(self.get_base_path(), file_path)
583
if os.path.exists(full_path):
584
zip_file.write(full_path, file_path)
585
586
flash(f'Created archive: {zip_name}', 'success')
587
except Exception as ex:
588
flash(f'Failed to create archive: {str(ex)}', 'error')
589
590
# Register advanced file admin
591
admin.add_view(AdvancedFileAdmin(
592
uploads_path,
593
'/uploads/',
594
name='Advanced Files',
595
category='File Management'
596
))
597
```
598
599
### Image Gallery File Admin
600
601
```python
602
from markupsafe import Markup
603
604
class ImageGalleryFileAdmin(FileAdmin):
605
# Only allow image uploads
606
allowed_extensions = ('jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp')
607
608
list_template = 'admin/file/image_gallery.html' # Custom template
609
610
def __init__(self, *args, **kwargs):
611
super().__init__(*args, **kwargs)
612
# Add custom column formatter for image preview
613
self.column_formatters = {
614
'name': self._image_formatter
615
}
616
617
def _image_formatter(self, view, context, model, name):
618
"""Show image thumbnails in file list."""
619
if model and hasattr(model, 'name'):
620
filename = model.name
621
if self.is_image_file(filename):
622
# Generate image tag with thumbnail
623
image_url = f"{self.base_url}{filename}"
624
return Markup(f'''
625
<div class="image-preview">
626
<img src="{image_url}" style="max-width: 100px; max-height: 100px;" />
627
<br><small>{filename}</small>
628
</div>
629
''')
630
return filename
631
632
def is_image_file(self, filename):
633
"""Check if file is an image."""
634
return filename and filename.lower().endswith(self.allowed_extensions)
635
636
# Register image gallery
637
gallery_path = op.join(op.dirname(__file__), 'gallery')
638
admin.add_view(ImageGalleryFileAdmin(
639
gallery_path,
640
'/gallery/',
641
name='Image Gallery',
642
category='Media'
643
))
644
```