I created a model in django to upload a file, it should save it in the media storage to be used by celery script. It should create a directory with the id, and each time I upload the same file, it should be deleted first (I don't know why I cant overwrite it, but it's enough if It delete it first). And when the entity is deleted, the directory should be deleted.
I'm working with python3.12, django 4.2, and django rest framework3.14.
The code works:
def upload_to(instance, filename):
instance_id = instance.id or uuid.uuid4().hex
directory = f'sample_{instance_id}'
file_path = os.path.join(directory, filename)
full_path = os.path.join(settings.MEDIA_ROOT, file_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
return file_path
class Sample(models.Model):
name = models.CharField(max_length=30)
test_suite = models.FileField(upload_to=upload_to)
def delete(self, *args, **kwargs):
if self.test_suite:
if os.path.isfile(self.test_suite.path):
os.remove(self.test_suite.path)
super().delete(*args, **kwargs)
def save(self, *args, **kwargs):
if self.pk is None:
saved_test_suite = self.test_suite
self.test_suite = None
super().save(*args, **kwargs)
self.test_suite = saved_test_suite
if 'force_insert' in kwargs:
kwargs.pop('force_insert')
elif self.pk:
try:
old_instance = Sample.objects.get(pk=self.pk)
if (
old_instance.test_suite
and self.test_suite
and old_instance.test_suite != self.test_suite
):
if os.path.isfile(old_instance.test_suite.path):
os.remove(old_instance.test_suite.path)
except Sample.DoesNotExist:
pass
if 'uuid' in self.test_suite.name:
new_path = self.test_suite.name.replace(
self.test_suite.name.split('/')[0],
f'dir_{self.pk}'
)
old_path = self.test_suite.path
new_full_path = os.path.join(settings.MEDIA_ROOT, new_path)
os.renames(old_path, new_full_path)
self.test_suite.name = new_path
super().save(update_fields=['test_suite'])
super().save(*args, **kwargs)
these are the tests:
@pytest.fixture
def create_test_file():
def _create_test_file(filename: str) -> SimpleUploadedFile:
return SimpleUploadedFile(filename, b'test file content')
return _create_test_file
@pytest.mark.django_db
def test_sample_creation(tmp_path, create_test_file):
settings.MEDIA_ROOT = tmp_path
sample = Sample.objects.create(
name='test_sample_name',
test_suite=create_test_file(filename='test_sample.sample'),
)
expected_directory = tmp_path / f'sample_{sample.pk}'
expected_file_path = expected_directory / 'test_sample.sample'
assert expected_directory.is_dir()
assert expected_file_path.is_file()
assert sample.name == 'test_sample_name'
assert sample.test_suite.name.endswith('test_sample.sample')
assert f'sample_{sample.pk}' in sample.test_suite.name
@pytest.mark.django_db
def test_sample_update(tmp_path, create_test_file):
settings.MEDIA_ROOT = tmp_path
sample = Sample.objects.create(
name='test_sample_name',
test_suite=create_test_file('test_sample.sample'),
)
new_file = create_test_file(filename='updated_test_sample.sample')
sample.test_suite = new_file
sample.save()
sample.refresh_from_db()
expected_directory = tmp_path / f'sample_{sample.pk}'
assert expected_directory.is_dir()
expected_file_path = expected_directory / 'updated_test_sample.sample'
# import pdb; pdb.set_trace()
assert expected_file_path.is_file()
assert sample.test_suite.name == f'sample_{sample.pk}/updated_test_sample.sample'
old_file_path = expected_directory / 'test_sample.sample'
assert not old_file_path.exists()
And the error is:
> assert expected_file_path.is_file()
E AssertionError: assert False
E + where False = <bound method Path.is_file of PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample')>()
E + where <bound method Path.is_file of PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample')> = PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample').is_file
The most strange part is, if I execute the 'test_sample_update' alone, works, and if I change 'test_sample_creation' with 'test_sample_update', the one that fails is 'test_sample_creation'. So it should be some kind of saved data between tests, but I can't find it.
If I debug where 'set_trace' is commented:
(Pdb++) expected_file_path
PosixPath('/tmp/pytest-of-username/pytest-75/test_sample_update0/sample_1/updated_test_sample.sample')
(Pdb++) expected_file_path.is_file()
False
when execute 'update' alone
(Pdb++) expected_file_path
PosixPath('/tmp/pytest-of-username/pytest-77/test_sample_update0/sample_1/updated_test_sample.sample')
(Pdb++) expected_file_path.is_file()
True
The old file is being deleted from a different directory than where the test is running. To fix it:
old_file_path = Path(sample.test_suite.path)
if old_file_path.exists():
old_file_path.unlink()
new_file_path = Path(sample.test_suite.path)
This should fix the issue because:
It uses the actual paths from Django's model instead of constructing them It properly cleans up the old file using the correct path.