Définissez FileField de Django sur un fichier existant

89

J'ai un fichier existant sur le disque (disons /folder/file.txt) et un champ de modèle FileField dans Django.

Quand je fais

instance.field = File(file('/folder/file.txt'))
instance.save()

il réenregistre le fichier sous file_1.txt(la prochaine fois _2, etc.).

Je comprends pourquoi, mais je ne veux pas de ce comportement - je sais que le fichier auquel je veux associer le champ m'attend vraiment, et je veux juste que Django le pointe.

Comment?

Garde
la source
1
Vous n'êtes pas sûr de pouvoir obtenir ce que vous voulez sans modifier Django ou sous-classer FileField. Chaque fois que a FileFieldest enregistré, une nouvelle copie du fichier est créée. Il serait assez simple d'ajouter une option pour éviter cela.
Michael Mior
eh bien oui, on dirait que je dois sous-classer et ajouter un paramètre. Je ne veux pas créer de tables supplémentaires pour cette tâche simple
Garde
1
Placez le fichier dans un emplacement différent, créez votre champ avec ce chemin, enregistrez-le et vous avez le fichier dans la destination upload_to.
benjaoming

Réponses:

22

Si vous souhaitez le faire de manière permanente, vous devez créer votre propre classe FileStorage

import os
from django.conf import settings
from django.core.files.storage import FileSystemStorage

class MyFileStorage(FileSystemStorage):

    # This method is actually defined in Storage
    def get_available_name(self, name):
        if self.exists(name):
            os.remove(os.path.join(settings.MEDIA_ROOT, name))
        return name # simply returns the name passed

Maintenant dans votre modèle, vous utilisez votre MyFileStorage modifié

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)
Burhan Khalid
la source
oh, ça a l'air prometteur. car le code de FileField est un peu non intuitif
Garde
mais ... est-il possible de changer le stockage sur une base par demande, comme: instance.field.storage = mfs; instance.field.save (nom, fichier); mais ne pas le faire dans une autre branche de mon code
Garde
2
Non, car le moteur de stockage est lié au modèle. Vous pouvez éviter tout cela en stockant simplement votre chemin de fichier dans un FilePathFieldou simplement en texte brut.
Burhan Khalid
Vous ne pouvez pas simplement renvoyer un nom. Vous devez d'abord supprimer le fichier existant.
Alexander Shpindler
124

il suffit de définir instance.field.namele chemin de votre fichier

par exemple

class Document(models.Model):
    file = FileField(upload_to=get_document_path)
    description = CharField(max_length=100)


doc = Document()
doc.file.name = 'path/to/file'  # must be relative to MEDIA_ROOT
doc.file
<FieldFile: path/to/file>
bara
la source
15
Le chemin relatif de votre MEDIA_ROOT, c'est-à-dire.
mgalgs
7
Dans cet exemple, je pense que vous pouvez aussi le fairedoc.file = 'path/to/file'
Andrew Swihart
13

essayez ceci ( doc ):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()
UNMANNEUR
la source
5

Il est juste d'écrire sa propre classe de stockage. Cependant, ce get_available_namen'est pas la bonne méthode pour remplacer.

get_available_nameest appelé lorsque Django voit un fichier avec le même nom et tente d'obtenir un nouveau nom de fichier disponible. Ce n'est pas la méthode qui provoque le changement de nom. la méthode causée est _save. Les commentaires dans _savesont assez bons et vous pouvez facilement trouver qu'il ouvre le fichier pour l'écriture avec un drapeau os.O_EXCLqui lancera une OSError si le même nom de fichier existe déjà. Django détecte cette erreur puis appelle get_available_namepour obtenir un nouveau nom.

Je pense donc que la bonne façon est de remplacer _saveet d'appeler os.open () sans indicateur os.O_EXCL. La modification est assez simple mais la méthode est un peu longue donc je ne la colle pas ici. Dites-moi si vous avez besoin de plus d'aide :)

x1a0
la source
ce sont 50 lignes de code que vous devez copier, ce qui est plutôt mauvais. Le remplacement de get_available_name semble plus isolé, plus court et beaucoup plus sûr pour, par exemple, la mise à niveau vers les nouvelles versions de Django à l'avenir
Michael Gendin
2
Le problème de la substitution uniquementget_available_name est que lorsque vous téléchargez un fichier avec le même nom, le serveur entrera dans une boucle sans fin. Puisque _savevérifie le nom du fichier et décide d'en obtenir un nouveau, il get_available_nameretourne toujours le double. Vous devez donc remplacer les deux.
x1a0
1
Oups, nous avons cette discussion en deux questions, mais ce n'est que maintenant que j'ai remarqué qu'elles sont légèrement différentes) Donc j'ai raison sur cette question, et vous êtes dans celle-ci)
Michael Gendin
1

J'ai eu exactement le même problème! puis je me rends compte que mes modèles étaient à l'origine de cela. exemple j'ai mes modèles comme ceci:

class Tile(models.Model):
  image = models.ImageField()

Ensuite, je voulais avoir plus d'une tuile référençant le même fichier sur le disque! La façon dont j'ai trouvé pour résoudre ce problème a été de changer la structure de mon modèle en ceci:

class Tile(models.Model):
  image = models.ForeignKey(TileImage)

class TileImage(models.Model):
  image = models.ImageField()

Ce qui, après avoir réalisé que cela a plus de sens, car si je veux que le même fichier soit enregistré plus d'un dans ma base de données, je dois créer une autre table pour cela!

Je suppose que vous pouvez aussi résoudre votre problème comme ça, en espérant simplement que vous pourrez changer les modèles!

ÉDITER

Aussi je suppose que vous pouvez utiliser un stockage différent, comme celui-ci par exemple: SymlinkOrCopyStorage

http://code.welldev.org/django-storages/src/11bef0c2a410/storages/backends/symlinkorcopy.py

Arthur Neves
la source
a du sens dans votre cas, pas dans le mien. Je ne veux pas qu'il soit référencé plusieurs fois. Je crée un objet référençant un fichier, puis je réalise qu'il y a des erreurs dans d'autres attrs, et je rouvre le formulaire de création. Lors de sa nouvelle soumission, je ne veux pas perdre le fichier qui est déjà enregistré sur le disque
Garde
donc je suppose que vous pouvez utiliser mon approche! car vous aurez une table FormFile qui ne contiendra le fichier qu'alors que vous l'avez! puis dans votre table Form, vous aurez un FK pour ce fichier! vous pouvez donc modifier / créer de nouveaux formulaires pour le même fichier! (btw je change l'ordre du FK dans mon exemple principal)
Arthur Neves
Si vous souhaitez publier votre domaine (modèles) dans votre message! je peux aussi avoir une meilleure idée!
Arthur Neves
le domaine n'a pas vraiment d'importance - j'ai un modèle avec une photo qui lui est associée, et j'ai un écran d'édition personnalisé. une fois téléchargée, je veux que la photo reste sur le serveur, mais je n'aime pas vraiment générer un modèle, une table et une recherche FK séparés simplement parce que cela semble être une limitation du cadre
Garde
La limitation ici, je suppose, est que lorsque vous enregistrez un FileField dans django, il passe toujours par Django Storages! il ne sera donc pas logique de forcer simplement un chemin de fichier! aussi comment Django doit-il savoir que le fichier existe déjà dans le chemin? une autre approche que vous pouvez utiliser consiste à utiliser FilePathField à la place! vous pouvez donc simplement définir le chemin dans votre base de données et effectuer la recherche comme vous le pensez.
Arthur Neves
1

Vous devez définir votre propre stockage, l'hériter de FileSystemStorage et remplacer l' OS_OPEN_FLAGSattribut de classe etget_available_name() méthode de :

Version Django: 3.1

Projet / core / files / storages / backends / local.py

import os

from django.core.files.storage import FileSystemStorage


class OverwriteStorage(FileSystemStorage):
    """
    FileSystemStorage subclass that allows overwrite the already existing
    files.
    
    Be careful using this class, as user-uploaded files will overwrite
    already existing files.
    """

    # The combination that don't makes os.open() raise OSError if the
    # file already exists before it's opened.
    OS_OPEN_FLAGS = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, 'O_BINARY', 0)

    def get_available_name(self, name, max_length=None):
        """
        This method will be called before starting the save process.
        """
        return name

Dans votre modèle, utilisez votre OverwriteStorage personnalisé

myapp / models.py

from django.db import models

from core.files.storages.backends.local import OverwriteStorage


class MyModel(models.Model):
   my_file = models.FileField(storage=OverwriteStorage())
Sultan
la source