I'm developing a Django project and I have the following "problem".
I have a local directory that has subdirectories, in which I got some PDF files. For example:
Main Dir:
|-->2000
____|-->A
________|-->file1.pdf
________|-->file2.pdf
____|-->B
________|-->file3.pdf
____|-->C
________|-->...
____|-->D
________|-->...
|-->2001
___|--> ...
|-->2002
___|--> ...
All the folders contain thousands of PDF files.
I want to display this directory in a Django view and let the user browse it by clicking the subdirectories and the PDF files so he can view them in his browser and maybe even add a "Download PDF" button. I also want to format it a bit, maybe add a search function too in the future if possible.
It is my first time working with local files and Django so I'm a bit lost.
Thank you for your time!
You could use ListView like this
class FileObject():
name = ''
def __init__(self, name):
self.name = name
class DirListView(ListView):
template_name = 'main/list_dir.html'
def get_queryset(self):
files = []
for filename in os.listdir(path):
fileobject = FileObject(name=filename)
files.append(fileobject)
return files
This ListView the could be used like any other ListView. Not sure about the permission and locations about path. This must be steared by settings.py I guess
Related
I want to make Django accept url that consists of infinite number of parameters. Something like dropbox has going on. Each parameter for each folder the file is in. There could be infinite number of subfolders. Parameter is alphanumeric.
You can create a URL that accepts an arbitrarily long parameter than contains some form of separator and then in your view split the parameter by this separator. For a path like parameter:
url(r'^prefix/(?P<path>[a-zA-Z\/]*)/$', your_view),
def your_view(request, path):
folders = path.split('/')
Now any request to this URL like prefix/folder1/folder2/folder3/ and folders will contain ['folder1', 'folder2', 'folder3']
I don't think so that you can define infinite url parameters with django URLS. Because with django you have to declare the urls that you site will gonna use.
But if you are talking specifically of folder and files, you can do this with FileField in a model, that allows you to decide where you can save files and save the path in instance.filefield.url. then insted of search django urls, search nginx url, something like this. {site_url}/static/{any}/{folder}/{you}/{want}/{etc}/.
Inclusive, you can use models like this.
class Folder(models.Model):
name = models.CharField(...)
parent = mdoels.ForeignKey('self')
...
def get_path_to_here(self):
# here you have the full url getting all parents folders.
...
def func_to_declare_where_save_it(instance, filename):
return os.path.join(
f'{instance.folder.get_path_to_here()}',
f'{filename}')
class File(models.Model):
name = models.CharField(...)
folder = models.ForeignKey(Folder)
file = models.FileField(upload_to=func_to_declare_where_save_it)
I am using DRF backend to upload files. In my specific case I will want to get the name of the file, after it has been uploaded. The reason is that if a user uploads a file with same name, I am still able to process it independently.
views.py:
class ImageUploaderView(viewsets.ModelViewSet):
renderer_classes = [renderers.JSONRenderer]
queryset = ImageUploader.objects.all()
serializer_class = ImageUploaderSerializer
parser_classes = (MultiPartParser,)
serializer.py:
class ImageUploaderSerializer(serializers.ModelSerializer):
class Meta:
model = ImageUploader
models.py:
class ImageUploader(models.Model):
# name=models.ImageField(upload_to='media')
name=models.FileField(upload_to='media')
I tried to put signals and hooks after the model definitions but I am not being able to get this filename. Can someone shed some light pls?
UPDATE: Let me elaborate what I want to achieve essentially:
User1 hits endpoint "/api/calculate_interest_rate" which is rendered
by a frontend React component. "calculate_interest_rate" is served by
DRF, and lets the user upload a CSV file. This will be stored as
"user1.csv", the file is processed and then tabulated (rendered by
React).
At the same time and in parallel to User1, User2 hits the same endpoint "/api/calculate_interest_rate" and
by mistake he saves his file as "user1.csv", and uploads it to the systemn.
So I want to be able to detect both names of the file in order to process it. By using always the same default filename (ex. using the OverwriteStorage() technique), I will probably cause chaos when two or more users are using the same filename. Therefore I am looking into a technique that allows me to get the filename as is, and process it immediately.
How about using storage option?
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
print("filename", name)
#parts = name.split('.') you can separate name and extension.
return super().get_available_name(name)
upload_image = models.ImageField(
upload_to=[yourpath],
default=[defaultname],
storage=OverwriteStorage()
)
I suggest you to following this configuration:
1. Change your MEDIA_ROOT and MEDIA_URL inside file of settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = '/path/to/env/projectname/media'
2. Then, I suggest you to change your upload_to='media to upload_to='images/%Y/%m/%d, also rename your field of name with image.
class ImageUploader(models.Model):
image = models.FileField(upload_to='images/%Y/%m/%d')
# OR
# image = models.ImageField(upload_to='images/%Y/%m/%d')
Explanation; If you following this configuration, you could have uploaded images are following, eg: /media/images/2017/01/29/yourimage.jpg. This is one way to handle the problem of duplicated files.
3. But if you want to upload file with multiple times without duplicate files, you can using deconstructible;
import os, time, uuid
from django.db import models
from django.utils.deconstruct import deconstructible
class ImageUploader(models.Model):
#deconstructible
class PathAndRename(object):
def __init__(self, sub_path):
self.path = sub_path
def __call__(self, instance, filename):
# eg: filename = 'my uploaded file.jpg'
ext = filename.split('.')[-1] #eg: '.jpg'
uid = uuid.uuid4().hex[:10] #eg: '567ae32f97'
# eg: 'my-uploaded-file'
new_name = '-'.join(filename.replace('.%s' % ext, '').split())
# eg: 'my-uploaded-file_64c942aa64.jpg'
renamed_filename = '%(new_name)s_%(uid)s.%(ext)s' % {'new_name': new_name, 'uid': uid, 'ext': ext}
# eg: 'images/2017/01/29/my-uploaded-file_64c942aa64.jpg'
return os.path.join(self.path, renamed_filename)
image_path = time.strftime('images/%Y/%m/%d')
image = models.ImageField(upload_to=PathAndRename(self.image_path))
I have an application that allows for users to upload CSV files with data which is then graphed and displayed to the user. These files are saved as media within my media folder. In my graph view however I need to open the file and process it. My problem is that I can only open files that are within my project's current working directory and any time that I attempt to upload a file from somewhere outside of that directory I get this error:
File b'TEST.CSV' does not exist
I have attempted this, but without success:
file_upload_dir = os.path.join(settings.MEDIA_ROOT, 'Data_Files')
data_file = open(os.path.join(file_upload_dir, new_file), 'rb')
The variable new_file is only the name of the file saved from in a session and not a path to that file. Data_Files is a directory within the media directory that contains the uploaded files.
My media settings for Django are
SETTINGS_DIR = os.path.dirname(__file__)
PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir)
PROJECT_PATH = os.path.abspath(PROJECT_PATH)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')
Is there a way to reference the media files properly from a view?
Here is the output of file_upload_dir and the location of the new file.
>>> print(file_upload_dir)
C:\\Users\\vut46744\\Desktop\\graphite_project\\media\\Data_Files
>>> print(os.path.join(file_upload_dir, new_file))
C:\\Users\\vut46744\\Desktop\\graphite_project\\media\\Data_Files\\TEST.CSV
Normally, you should not access files using open() in a Django app. You should use the storage API. This allows your code to play well with Django settings, and potential third party apps that augment this API.
https://docs.djangoproject.com/en/1.7/topics/files/#file-storage
So here you should be doing something like
from django.core.files.storage import default_storage
f = default_storage.open(os.path.join('Data_Files', new_file), 'r')
data = f.read()
f.close()
print(data)
By the way, if you want it to be modular, it would be a good idea to have a custom storage class, allowing easy configuration and use of your app, should your requirements change. Also, that allows putting files outside of MEDIA_ROOT. This sample storage will put them in settings.UPLOADS_ROOT (and default to MEDIA_ROOT if the setting is not found).
# Put this in a storage.py files in your app
from django.conf import settings
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.functional import LazyObject
class UploadsStorage(FileSystemStorage):
def __init__(self, location=None, base_url=None, *args, **kwargs):
if location is None:
location = getattr(settings, 'UPLOADS_ROOT', None)
super(UploadsStorage, self).__init__(location, base_url, *args, **kwargs)
self.base_url = None # forbid any URL generation for uploads
class ConfiguredStorage(LazyObject):
def _setup(self):
storage = getattr(settings, 'UPLOADS_STORAGE', None)
klass = UploadsStorage if storage is None else get_storage_class(storage)
self._wrapped = klass()
uploads_storage = ConfiguredStorage()
We create a very simple storage here. It's just the regular one, but that reads its files from another directory. Then we set up a lazy object that will allow overriding that storage from settings.
So now your code becomes:
from myapp.storage import uploads_storage
f = uploads_storage.open(new_files, 'r')
And in your settings, you set UPLOADS_ROOT to whatever you like. Probably something outside your media directory. And if someday you decide to store uploads in a database instead, you can set UPLOADS_STORAGE to a database-backed storage, your code will happily use it.
I am trying to work with a filestorage in Django. Everything is working fine but a thing in my save method I guess. I have a model with a FileField
download_url = models.FileField(verbose_name = 'Konfig', upload_to = file_path, storage = OverwriteStorage())
In this method in my model I create the file_path
def file_path(instance, filename):
path = os.getcwd() + '/files'
return os.path.join(path, str(instance.download_url), filename)
And the filestorage method I use is outsourced in my storage.py which I import in my models.py
from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
def _save(self, name, content):
if self.exists(name):
self.delete(name)
return super(OverwriteStorage, self)._save(name, content)
def get_available_name(self, name):
return name
Now when I create a new file in the admin interface from django, it successfully uploads the file, makes a database entry with the correct filepath, but it fails to create the right path. When my filename is foo the path would look like following:
cwd/files/foo/foo
and if its name would be bar.txt it would look like following:
cwd/files/bar.txt/bar.txt
I don't want django to create a subdirectory based on the filename. Can you guys help me out ?
Im pretty sure you have to rename the save function from "save" to "_save".
On the Super Call, you used ._save, which isnt the same function as the save function above.
You can read alot about Super here
I'm using Django to create a stock photo site, I have an ImageField in my model, the problem is that when the user updates the image field, the original image file isn't deleted from the hard disk.
How can I delete the old images after an update?
Use django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig', # should be placed after your apps
)
You'll have to delete the old image manually.
The absolute path to the image is stored in your_image_field.path. So you'd do something like:
os.remove(your_image_field.path)
But, as a convenience, you can use the associated FieldFile object, which gives easy access to the underlying file, as well as providing a few convenience methods. See http://docs.djangoproject.com/en/dev/ref/models/fields/#filefield-and-fieldfile
Use this custom save method in your model:
def save(self, *args, **kwargs):
try:
this = MyModelName.objects.get(id=self.id)
if this.MyImageFieldName != self.MyImageFieldName:
this.MyImageFieldName.delete()
except: pass
super(MyModelName, self).save(*args, **kwargs)
It works for me on my site. This problem was bothering me as well and I didn't want to make a cleanup script instead over good bookkeeping in the first place. Let me know if there are any problems with it.
Before updating the model instance, you can use the delete method of FileField object. For example, if the FileField or ImageField is named as photo and your model instance is profile, then the following will remove the file from disk
profile.photo.delete(False)
For more clarification, here is the django doc
https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.fields.files.FieldFile.delete
You can define pre_save reciever in models:
#receiver(models.signals.pre_save, sender=UserAccount)
def delete_file_on_change_extension(sender, instance, **kwargs):
if instance.pk:
try:
old_avatar = UserAccount.objects.get(pk=instance.pk).avatar
except UserAccount.DoesNotExist:
return
else:
new_avatar = instance.avatar
if old_avatar and old_avatar.url != new_avatar.url:
old_avatar.delete(save=False)
My avatrs has unique url for each person like "avatars/ceb47779-8833-4719-8711-6f4e5cabb2b2.png". If user upload new image with different extension like jpg, delete_file_on_change_extension reciever remove old image, before save new with url "avatars/ceb47779-8833-4719-8711-6f4e5cabb2b2.jpg" (in this case). If user uploads new image with same extension django overwrite old image on storage (disk), because images paths are the same.
This works fine with AWS S3 django-storage.
Here is an app that deletes orphan files by default: django-smartfields.
It will remove files whenever:
field value was replaced with a new one (either uploaded or set manually)
field is cleared through the form (in case that field is not required, of course)
the model instance itself containing the field is deleted.
It is possible to turn that cleanup feature off using an argument: ImageField(keep_orphans=True) on per field basis, or globally in settings SMARTFIELDS_KEEP_ORPHANS = True.
from django.db import models
from smartfields import fields
class MyModel(models.Model):
image = fields.ImageField()
document = fields.FileField()
try this, it will work even if old file is deleted
def logo_file(instance, filename):
try:
this = business.objects.get(id=instance.id)
if this.logo is not None:
path = "%s" % (this.logo)
os.remove(path)
finally:
pass..
code will work even without "try .. finally" but it will generate problem if file was accidently deleted.
changed: move model matching inside "try" so it will not throw any error at user signup
Let me know if there are any problems.
Completing Chris Lawlor's answer, tried this and works.
from YOURAPP.settings import BASE_DIR
try:
os.remove(BASE_DIR + user.userprofile.avatarURL)
except Exception as e:
pass
The URL has a pattern of /media/mypicture.jpg
What I did is saving the path to the old image and if form is valid I would delete the old one.
if request.method == 'POST':
old_image = ""
if request.user.profile.profile_picture:
old_image = request.user.profile.profile_picture.path
form = UpdateProfileForm(request.POST,request.FILES,instance = profile)
if form.is_valid():
if os.path.exists(old_image):
os.remove(old_image)
form.save()
It is a little messy , but you do not install third parties or anythin