How to delete Images or Videos saved in MEDIA_URL using Django

0

In the database the data is saved with the address that is in MEDIA_URL and they are deleted perfectly since the only thing that saves is the address, but the problem is that the files that were previously saved in MEDIA_URL, are not delete are kept, I need some idea of how to delete them when the tuple corresponding to your physical address is deleted.

I am trying to do it using signals and I have the code so

models.py

from django.db.models.signals import pre_delete, post_delete
from django.dispatch import receiver
from django.conf import settings
import os

class Video(models.Model):
filename = models.CharField(max_length=100)
dueño = models.ForeignKey(User, on_delete=models.CASCADE, related_name='videos')
docfile = models.FileField(upload_to='video/%Y/%m/%d',
                           validators=[FileValidator(allowed_mimetypes=('video/avi', 'video/mp4'),
                                                     max_size=50 * 1024 * 1024)])
descripcion = models.CharField(max_length=500)

def __str__(self):
    return '%s ' % (self.filename)

@receiver(pre_delete, sender=Video)
def _videos_delete(sender, instance, using, **kwargs):
    file_path = settings.MEDIA_URL + str(instance.docfile)
    print(file_path)
    if os.path.isfile(file_path):
        os.remove(file_path)

but still does not delete the file

    
asked by Pedro Diaz Labiste 23.02.2018 в 02:37
source

2 answers

0

You can use os.remove () by passing the full path where the file is .

In your case, it seems that you use MEDIA_URL and this probably is not a complete path to the location of the file we want to delete.

If instead of MEDIA_URL we use MEDIA_ROOT we solve the problem

settings.py

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MEDIA_URL = '/documentos/' #carpeta del servidor donde almacenaremos los documentos
MEDIA_ROOT = os.path.join(BASE_DIR, 'documentos') # ruta completa en el servidor hasta la carpeta documentos

Then you can change MEDIA_URL by MEDIA_ROOT when you build your file_path

In any case, check by directly passing a file path in this way

os.remove(os.path.join(MEDIA_ROOT+'resto_de_ruta_incluyendo_el_nombre_de_archivo'))
    
answered by 23.02.2018 / 08:11
source
0

Managing files in Django is quite easy: you can add them to a model with only one line (for a review of the Django models, you can consult our article on managing web data frames), and the framework will do it all for ti - validations, loading, type verification. Even serving them requires very little effort.

However, there is one thing that Django no longer does as of version 1.3: automatically delete the files of a model when the instance is deleted.

There are many good reasons why this decision was made: in some cases this behavior was prone to data loss, and it is also slow enough to penalize the response time of our application. In addition, Cloud solutions such as Google or Amazon will not give us too many problems with excess files. But of course, sometimes we use a VPS and space is limited.

There are several possible solutions to eliminate those unpleasant unused files:

One possible solution is to create a custom command like this:

import os

from django.core.management.base import BaseCommand
from django.apps import apps
from django.db.models import Q
from django.conf import settings
from django.db.models import FileField


class Command(BaseCommand):
    help = "This command deletes all media files from the MEDIA_ROOT directory which are no longer referenced by any of the models from installed_apps"

    def handle(self, *args, **options):
    all_models = apps.get_models()
    physical_files = set()
    db_files = set()

    # Get all files from the database
    for model in all_models:
        file_fields = []
        filters = Q()
        for f_ in model._meta.fields:
            if isinstance(f_, FileField):
                file_fields.append(f_.name)
                is_null = {'{}__isnull'.format(f_.name): True}
                is_empty = {'{}__exact'.format(f_.name): ''}
                filters &= Q(**is_null) | Q(**is_empty)
        # only retrieve the models which have non-empty, non-null file fields
        if file_fields:
            files = model.objects.exclude(filters).values_list(*file_fields, flat=True).distinct()
            db_files.update(files)

    # Get all files from the MEDIA_ROOT, recursively
    media_root = getattr(settings, 'MEDIA_ROOT', None)
    if media_root is not None:
        for relative_root, dirs, files in os.walk(media_root):
            for file_ in files:
                # Compute the relative file path to the media directory, so it can be compared to the values from the db
                relative_file = os.path.join(os.path.relpath(relative_root, media_root), file_)
                physical_files.add(relative_file)

    # Compute the difference and delete those files
    deletables = physical_files - db_files        
    if deletables:
        for file_ in deletables:
            os.remove(os.path.join(media_root, file_))

        # Bottom-up - delete all empty folders
        for relative_root, dirs, files in os.walk(media_root, topdown=False):
            for dir_ in dirs:
                if not os.listdir(os.path.join(relative_root, dir_)):
                    os.rmdir(os.path.join(relative_root, dir_))

There is another more complex option to implement: use signals.

When the instance of the model to which the file belongs is removed, here we can simply use the post_delete signal, which will ensure that the instance has already been deleted from the database successfully. The code for this part is quite simple:

from django.db.models import FileField
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch.dispatcher import receiver

LOCAL_APPS = [
    'my_app1',
    'my_app2',
    '...'
]

def delete_files(files_list):
    for file_ in files_list:
        if file_ and hasattr(file_, 'storage') and hasattr(file_, 'path'):
            # this accounts for different file storages (e.g. when using django-storages)
            storage_, path_ = file_.storage, file_.path
            storage_.delete(path_)

@receiver(post_delete)
def handle_files_on_delete(sender, instance, **kwargs):
    # presumably you want this behavior only for your apps, in which case you will have to specify them
    is_valid_app = sender._meta.app_label in LOCAL_APPS
    if is_valid_app:
        delete_files([getattr(instance, field_.name, None) for field_ in sender._meta.fields if isinstance(field_, FileField)])

This last solution is more elegant but has one drawback: it does not take into account the multiple references to the same file (although, unless you are not directly modifying your database, this should never happen).

    
answered by 23.02.2018 в 11:07