1
1
import os
2
2
import shutil
3
+ import tempfile
3
4
from io import BytesIO , StringIO
4
5
5
6
from django .core .files .uploadedfile import SimpleUploadedFile
6
7
from django .core .management import call_command
7
8
from django .test import TestCase
8
9
from django .utils .module_loading import import_string
10
+ from django .core .files .base import ContentFile
9
11
10
12
from filer import settings as filer_settings
11
13
from filer .models .filemodels import File
@@ -27,13 +29,18 @@ class FilerCheckTestCase(TestCase):
27
29
</svg>"""
28
30
29
31
def setUp (self ):
30
- # Clean up the public folder to avoid interference between tests.
31
- public_settings = filer_settings .FILER_STORAGES ['public' ]['main' ]
32
- storage = import_string (public_settings ['ENGINE' ])()
33
- upload_prefix = public_settings ['UPLOAD_TO_PREFIX' ]
34
- if storage .exists (upload_prefix ):
35
- shutil .rmtree (storage .path (upload_prefix ))
32
+ # Clear all configured storages to ensure a clean state for each test.
33
+ # This prevents interference from files left in any storage.
34
+ for storage_alias , storage_configs in filer_settings .FILER_STORAGES .items ():
35
+ config = storage_configs .get ('main' )
36
+ if not config :
37
+ continue
38
+ storage = import_string (config ['ENGINE' ])()
39
+ upload_prefix = config .get ('UPLOAD_TO_PREFIX' , '' )
40
+ if storage .exists (upload_prefix ):
41
+ shutil .rmtree (storage .path (upload_prefix ))
36
42
43
+ # Create a sample file for testing in the public storage.
37
44
original_filename = 'testimage.jpg'
38
45
file_obj = SimpleUploadedFile (
39
46
name = original_filename ,
@@ -55,8 +62,10 @@ def test_delete_missing(self):
55
62
call_command ('filer_check' , stdout = out , missing = True )
56
63
self .assertEqual ('' , out .getvalue ())
57
64
65
+ # Remove the file to simulate a missing file.
58
66
os .remove (self .filer_file .file .path )
59
67
call_command ('filer_check' , stdout = out , missing = True )
68
+ # When verbosity is low, a simple relative file path is output.
60
69
self .assertEqual ("None/testimage.jpg\n " , out .getvalue ())
61
70
self .assertIsInstance (File .objects .get (id = file_pk ), File )
62
71
@@ -65,26 +74,36 @@ def test_delete_missing(self):
65
74
File .objects .get (id = file_pk )
66
75
67
76
def test_delete_orphans_public (self ):
77
+ # First check - should be empty initially
68
78
out = StringIO ()
69
- self .assertTrue (os .path .exists (self .filer_file .file .path ))
70
- call_command ('filer_check' , stdout = out , orphans = True )
71
- # The public folder should be free of orphaned files.
79
+ call_command ('filer_check' , stdout = out , orphans = True , verbosity = 1 )
72
80
self .assertEqual ('' , out .getvalue ())
73
81
74
- # Add an orphan file to the public storage.
82
+ # Add an orphan file using the storage API directly
75
83
public_settings = filer_settings .FILER_STORAGES ['public' ]['main' ]
76
84
storage = import_string (public_settings ['ENGINE' ])()
77
- public_path = storage .path (public_settings ['UPLOAD_TO_PREFIX' ])
78
- orphan_file = os .path .join (public_path , 'hello.txt' )
79
- os .makedirs (public_path , exist_ok = True )
80
- with open (orphan_file , 'w' ) as fh :
81
- fh .write ("I don't belong here!" )
82
- call_command ('filer_check' , stdout = out , orphans = True )
85
+
86
+ # Configure storage location if specified in settings
87
+ if public_settings .get ('OPTIONS' , {}).get ('location' ):
88
+ storage .location = public_settings ['OPTIONS' ]['location' ]
89
+
90
+ # Get upload prefix and create file path
91
+ prefix = public_settings .get ('UPLOAD_TO_PREFIX' , '' )
92
+ file_path = 'hello.txt'
93
+ rel_path = os .path .join (prefix , file_path ) if prefix else file_path
94
+
95
+ # Save file through storage API
96
+ storage .save (rel_path , ContentFile (b"I don't belong here!" ))
97
+ self .assertTrue (storage .exists (rel_path ))
98
+
99
+ # Check if orphan is detected
100
+ out = StringIO ()
101
+ call_command ('filer_check' , stdout = out , orphans = True , verbosity = 1 )
83
102
self .assertEqual ("public/hello.txt\n " , out .getvalue ())
84
- self .assertTrue (os .path .exists (orphan_file ))
85
103
104
+ # Delete orphans
86
105
call_command ('filer_check' , delete_orphans = True , interactive = False , verbosity = 0 )
87
- self .assertFalse (os . path . exists (orphan_file ))
106
+ self .assertFalse (storage . exists (rel_path ))
88
107
89
108
def test_delete_orphans_private (self ):
90
109
# Skip test if private storage is not configured.
@@ -94,15 +113,16 @@ def test_delete_orphans_private(self):
94
113
out = StringIO ()
95
114
private_settings = filer_settings .FILER_STORAGES ['private' ]['main' ]
96
115
storage = import_string (private_settings ['ENGINE' ])()
116
+ # Set storage location if defined in OPTIONS.
97
117
if private_settings .get ('OPTIONS' , {}).get ('location' ):
98
118
storage .location = private_settings ['OPTIONS' ]['location' ]
99
- private_path = storage .path (private_settings [ 'UPLOAD_TO_PREFIX' ] )
119
+ private_path = storage .path (private_settings . get ( 'UPLOAD_TO_PREFIX' , '' ) )
100
120
os .makedirs (private_path , exist_ok = True )
101
121
102
122
orphan_file = os .path .join (private_path , 'private_orphan.txt' )
103
123
with open (orphan_file , 'w' ) as fh :
104
124
fh .write ("I don't belong here!" )
105
- # Verify that the command detects the orphan file.
125
+ # Run the command and check that it detects the private orphan file.
106
126
call_command ('filer_check' , stdout = out , orphans = True )
107
127
self .assertIn ("private_orphan.txt" , out .getvalue ())
108
128
self .assertTrue (os .path .exists (orphan_file ))
@@ -111,11 +131,84 @@ def test_delete_orphans_private(self):
111
131
call_command ('filer_check' , delete_orphans = True , interactive = False , verbosity = 0 )
112
132
self .assertFalse (os .path .exists (orphan_file ))
113
133
134
+ def test_delete_orphans_multiple_storages (self ):
135
+ """
136
+ Test that the filer_check command correctly handles orphaned files in multiple storages
137
+ without permanently modifying the settings. We use monkey-patching to assign temporary
138
+ directories to the storage configurations.
139
+ """
140
+ out = StringIO ()
141
+
142
+ # --- Monkey-patch public storage location ---
143
+ public_config = filer_settings .FILER_STORAGES ['public' ]['main' ]
144
+ temp_public_dir = tempfile .mkdtemp ()
145
+ if 'OPTIONS' in public_config :
146
+ public_config ['OPTIONS' ]['location' ] = temp_public_dir
147
+ else :
148
+ public_config ['OPTIONS' ] = {'location' : temp_public_dir }
149
+ # Determine the upload prefix (if any) and ensure the corresponding directory exists.
150
+ public_upload_prefix = public_config .get ('UPLOAD_TO_PREFIX' , '' )
151
+ if public_upload_prefix :
152
+ public_full_dir = os .path .join (temp_public_dir , public_upload_prefix )
153
+ else :
154
+ public_full_dir = temp_public_dir
155
+ os .makedirs (public_full_dir , exist_ok = True )
156
+
157
+ # --- Monkey-patch private storage location ---
158
+ private_config = filer_settings .FILER_STORAGES .get ('private' , {}).get ('main' )
159
+ if private_config :
160
+ temp_private_dir = tempfile .mkdtemp ()
161
+ if 'OPTIONS' in private_config :
162
+ private_config ['OPTIONS' ]['location' ] = temp_private_dir
163
+ else :
164
+ private_config ['OPTIONS' ] = {'location' : temp_private_dir }
165
+ private_upload_prefix = private_config .get ('UPLOAD_TO_PREFIX' , '' )
166
+ if private_upload_prefix :
167
+ private_full_dir = os .path .join (temp_private_dir , private_upload_prefix )
168
+ else :
169
+ private_full_dir = temp_private_dir
170
+ os .makedirs (private_full_dir , exist_ok = True )
171
+ else :
172
+ self .skipTest ("Private storage not configured in FILER_STORAGES." )
173
+
174
+ # --- Initialize storages using the patched locations ---
175
+ from django .core .files .storage import FileSystemStorage
176
+ storage_public = FileSystemStorage (location = temp_public_dir )
177
+ storage_private = FileSystemStorage (location = private_config ['OPTIONS' ]['location' ])
178
+
179
+ # --- Save dummy orphan files in both storages ---
180
+ # For public storage, include the upload prefix in the filename if needed.
181
+ if public_upload_prefix :
182
+ filename_public = os .path .join (public_upload_prefix , 'orphan_public.txt' )
183
+ else :
184
+ filename_public = 'orphan_public.txt'
185
+ if private_config .get ('UPLOAD_TO_PREFIX' , '' ):
186
+ filename_private = os .path .join (private_config ['UPLOAD_TO_PREFIX' ], 'orphan_private.txt' )
187
+ else :
188
+ filename_private = 'orphan_private.txt'
189
+
190
+ storage_public .save (filename_public , ContentFile (b"dummy content" ))
191
+ storage_private .save (filename_private , ContentFile (b"dummy content" ))
192
+
193
+ # --- Run the filer_check command ---
194
+ call_command ('filer_check' , stdout = out , orphans = True )
195
+ output = out .getvalue ()
196
+
197
+ # Verify that the output contains indicators for both storages.
198
+ self .assertIn ('public' , output )
199
+ self .assertIn ('private' , output )
200
+
201
+ # --- Clean up ---
202
+ storage_public .delete (filename_public )
203
+ storage_private .delete (filename_private )
204
+ shutil .rmtree (temp_public_dir )
205
+ shutil .rmtree (private_config ['OPTIONS' ]['location' ])
206
+
114
207
def test_image_dimensions_corrupted_file (self ):
115
208
original_filename = 'testimage.jpg'
116
209
file_obj = SimpleUploadedFile (
117
210
name = original_filename ,
118
- content = create_image ().tobytes (), # corrupted file
211
+ content = create_image ().tobytes (), # Simulate a corrupted file.
119
212
content_type = 'image/jpeg'
120
213
)
121
214
self .filer_image = Image .objects .create (
@@ -189,3 +282,4 @@ def test_image_dimensions_svg(self):
189
282
call_command ('filer_check' , image_dimensions = True )
190
283
self .filer_image .refresh_from_db ()
191
284
self .assertGreater (self .filer_image ._width , 0 )
285
+
0 commit comments