expected str, bytes or os.PathLike object, not InMemoryUploadedFile - python

I have a method to read a Newick file and return a String in Django framework which is the following:
def handle_uploaded_file(f):
output = " "
for chunk in f.chunks():
output += chunk.decode('ascii')
return output.replace("\n", "").replace("\r", "")
def post(self, request):
form = HomeForm(request.POST, request.FILES)
if form.is_valid():
input = handle_uploaded_file(request.FILES['file'])
treeGelezen = Tree(input, format=1)
script, div = mainmain(treeGelezen)
form = HomeForm()
args = {'form': form, 'script': script, 'div': div}
return render(request, self.template_name, args)
Which works fine for normal Newick files but i also have some files which have a string at the beginning of the file. I'm trying to make another method which checks if the file has the following String before it (which is the case in some files): "newick;" and removes the string if found. It works locally but i can't seem to merge them. This is how it locally looks like:
def removeNewick(tree_with_newick):
for x in tree_with_newick:
if x.startswith('newick;'):
print('')
return x
filepath = "C:\\Users\\msi00\\Desktop\\ncbi-taxanomy.tre"
tree_with_newick = open(filepath)
tree = Tree(newick=removeNewick(tree_with_newick), format=1)
which works perfectly when i specify the path just in python so i tried combining them in Django like this:
def handle_uploaded_file(f):
tree_with_newick = open(f)
for x in tree_with_newick:
if x.startswith('newick;'):
print('')
return cutFile(x)
def cutFile(f):
output = " "
for chunk in f.chunks():
output += chunk.decode('ascii')
return output.replace("\n", "").replace("\r", "")
def post(self, request):
form = HomeForm(request.POST, request.FILES)
if form.is_valid():
input = handle_uploaded_file(request.FILES['file'])
treeGelezen = Tree(input, format=1)
script, div = mainmain(treeGelezen)
form = HomeForm()
args = {'form': form, 'script': script, 'div': div}
return render(request, self.template_name, args)
Which doesn't work and it gives the following error:
expected str, bytes or os.PathLike object, not InMemoryUploadedFile
I've been working on it for two days already and couldn't figure out why the error is popping up.

The error is happening because the function handle_uploaded_file(f) is trying to open an already opened file.
The value of request.FILES['file'] is a InMemoryUploadedFile and can be used like a normal file. You don't need to open it again.
To fix, just remove the line that tries to open the file:
def handle_uploaded_file(f):
for x in f:
if x.startswith('newick;'):
print('')
return cutFile(x)

In my setting.py I had
MEDIA_ROOT = os.path.join(BASE_DIR, 'media'),
when it should've been
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
which solved this error for me.

the solution for me;
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
instead of :
MEDIA_ROOT = [os.path.join(BASE_DIR, 'media')]
in settigs.py

Related

django renaming image file while uploading

Im working on a social media application in django and would like to rename all the images from the uploaded content to make it easier to reuse them (putting them in to a pdf is the end goal, right now the filenames are the same as uploaded and i don't know how put these paths into the pdf --> solution might be numbering them all).
The filename should be renamed to: postimg{num_post}
all posts are numbered. the specific number or the post should be the end of the filename of the image file.
models.py
def post_images(instance, filename):
ext = filename.split('.')[-1]
filename = "%s_%s.%s" % (instance.post.num_post, ext)
return os.path.join('uploads', filename)
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
num_post = models.IntegerField(default=0)
image = models.ImageField(upload_to='post_images')
caption = models.TextField(max_length=300)
created_at = models.DateTimeField(auto_now_add=True)
number_of_likes = models.IntegerField(default=0)
number_of_dislikes = models.IntegerField(default=0)
def __str__(self):
return self.caption
views.py
def upload(request):
if request.method == 'POST':
#user = request.user.username
image = request.FILES.get('image_upload')
#--> how to rename the image file to "post{num_post}.jpg"
caption = request.POST['caption']
num_post = Post.objects.count()+1
new_post = Post.objects.create(image=image, caption=caption, num_post=num_post)
new_post.save()
#create pdf
buffer = io.BytesIO()
#get the image
#img_file = Image.open(f'{os.getcwd()}/{post.image.url}').convert('RGB')
#img_file = f'media/post_images/postimg{num_post}'
#x_start = 0
#y_start = 0
#saving it on the server
folder_path = f"media/post{num_post}.pdf"
folder_name = os.path.basename(folder_path)
p = canvas.Canvas(folder_name)
#p.drawImage(img_file, x_start, y_start, width=120, preserveAspectRatio=True, mask='auto')
p.drawString(200, 300, new_post.caption)
p.drawString(200, 100, str(new_post.created_at))
p.drawString(200, 600, str(new_post.id))
#p.drawText(new_post.caption)
#p.drawImage(new_post.image)
p.showPage()
p.save()
buffer.seek(0)
return redirect('/'), folder_path
else:
return redirect('/')
so in the end i should be able to put the image in the pdf by using:
img_file = f'media/post_images/postimg{num_post}'
x_start = 0
y_start = 0
p.drawImage(img_file, x_start, y_start, width=120, preserveAspectRatio=True, mask='auto')
I was already able to get the images into the pdf by using the existing filename but since the pdf should be automatically generated for each post, the image name needs to be variable, i think.
Right now, it is not working. The image is not renamed, but there is also no error display. so the function seems not to reach the image? How do I make it work?
Thank you for any suggestions. :) im new to django... any explanation helps.
Lets start with the model field:
class Post(models.Model):
...
image = models.ImageField(upload_to='post_images')
...
It is not showing any errors because you are uploading files to 'post_images' folder and not calling a function with such name. And that is also why your files are not being renamed.
To call the rename function, just remove the single quote:
image = models.ImageField(upload_to=post_image)
Although, it is not going to work because of this line:
def post_images(instance, filename):
...
filename = "%s_%s.%s" % (instance.post.num_post, ext)
...
Where there are two problems. First, instance is a post object instance, you are trying to access it in the wrong way. And second when trying to format three strings with two parameters. That being said:
def post_images(instance, filename):
ext = filename.split('.')[-1]
filename = 'post{}.{}'.format(instance.num_post, ext)
return os.path.join('uploads', filename)
Lets also implement a #property on model Post so we can access its image filename with ease:
class Post(models.Model):
...
#property
def filename(self):
return os.path.basename(self.image.name).split('.')[0]
Now, related to the view, it is always a good practice to work with django forms (model form), it also helps writing less code while doing more (like validating data):
forms.py
from django import forms
from .models import Post
class UploadForm(forms.ModelForm):
class Meta:
model = Post
fields = ['image', 'caption']
views.py
from django.shortcuts import render, redirect
from .forms import UploadForm
from .models import Post
import os
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
def upload(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
form.cleaned_data['num_post'] = Post.objects.count() + 1
post = Post.objects.create(**form.cleaned_data)
p = canvas.Canvas(os.getcwd() + f'/uploads/converted/{post.filename}.pdf')
p.drawString(200, 300, post.caption)
p.drawString(200, 100, str(post.created_at))
p.drawString(200, 600, str(post.id))
p.showPage()
p.drawImage(post.image.name, 0,0, width=A4[0], height=A4[1],mask='auto')
p.showPage()
p.save()
return redirect('post:upload')
else:
form = UploadForm()
return render(request, 'upload.html', {'form': form})
In this view, after checking that the data is valid we then update the valid data dictionary and create the object. Furthermore, since we are using .create() it is not necessary to save the object afterwards.
After this first process we create ReportLab PDF object, with two pages, the first one containing the string related data (as in your original function). And, the second one where we draw an image fitting the whole page by using from reportlab.lib.pagesizes import A4 size properties. You can find all this information at the userguide documentation.
upload.html
{% block content %}
<form action="{% url 'post:upload' %}" enctype="multipart/form-data" method="post">
{% csrf_token %}
{{form.as_p}}
<button type="submit">Create</button>
</form>
{% endblock content %}
urls.py
app_name = 'post'
urlpatterns = [
path('upload/', views.upload, name='upload'),
]

Undefined variable error during splitting views.py into modules - Django

My codes in views.py becomes larger day by day, and now I want to split it into modules. But I have trouble with variables. Problem is in that I don't where should I declare variables, or import built-in modules: in my custom module, or views.py. Here is my codes:
views.py:
#login_required(login_url='sign_in')
def result(request):
find_by_fives()
context = {
'last_uploaded': last_uploaded,
'words_count': words_count,
'characters_count': characters_count
}
return render(request, 'result.html', context)
find_by_fives.py(is my custom module):
import glob
from .models import OriginalDocument
from django.shortcuts import render
def find_by_fives():
last_uploaded = OriginalDocument.objects.latest('id')
original = open(str(last_uploaded.document), 'r')
original_words = original.read().lower().split()
words_count = len(original_words)
open_original = open(str(last_uploaded.document), "r")
read_original = open_original.read()
characters_count = len(read_original)
path = 'static/other_documents/doc*.txt'
files = glob.glob(path)
Error: NameError: name 'last_uploaded' is not defined
Note: This is not my entire view, all I want to know is just where should I declare context, variables, and imports.
Ok, I see - "find_by_fives.py" is a function, right? So the variables that you declare inside it lives only there. So when you call this function from the views.py - they get declared and then, when the function is over they get deleted. If you want to use them in the views.py - you should return them and assign a variable there, then pass them to the context:
#login_required(login_url='sign_in')
def result(request):
last_uploaded, words_count, characters_count = find_by_fives()
context = {
'last_uploaded': last_uploaded,
'words_count': words_count,
'characters_count': characters_count
}
return render(request, 'result.html', context)
def find_by_fives():
last_uploaded = OriginalDocument.objects.latest('id')
original = open(str(last_uploaded.document), 'r')
original_words = original.read().lower().split()
words_count = len(original_words)
open_original = open(str(last_uploaded.document), "r")
read_original = open_original.read()
characters_count = len(read_original)
path = 'static/other_documents/doc*.txt'
files = glob.glob(path)
return last_uploaded, words_count, characters_count

Django Create and output txt file from view

I have a scenario where a user uploads some data, Django does some processing in pandas and returns a potentially large txt file. I've got this working but I'm unsure about the scalability of the approach and want to know if there's a better way.
Adapted the Outputting to CSV section of the Django doc I have the following:
class MyClass(LoginRequiredMixin,FormView):
template_name = 'myapp/mytemplate.html'
form_class = MyForm
success_url = '/' # Replace with your URL or reverse().
def post(self, request, *args, **kwargs):
if request.method == 'POST':
form = MyForm(request.POST, request.FILES)
#print("filename",files[0].name)
if form.is_valid() :
filename = "my-file.txt"
content = 'any string generated by django'
response = HttpResponse(content, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
return response
else:
print("i am invalid")
return self.form_invalid(form)
In practice I will need to output a text file of perhaps 1000 lines, built by looping over numerous dataframes, should I just build an extremely long text string (content), or is there a better way? In pure python I am more used to creating txt file output using:
f = open( 'some_file.txt', 'w+')
f.write("text")
f.write("text")
f.close()
Which seems more intuitive.
As requested by comments, updated to show exactly the code I was trying in Django which was returning an empty text file:
class MyClass(LoginRequiredMixin,FormView):
template_name = 'myapp/mytemplate.html'
form_class = MyForm
success_url = '/' # Replace with your URL or reverse().
def post(self, request, *args, **kwargs):
if request.method == 'POST':
form = MyForm(request.POST, request.FILES)
if form.is_valid() :
f = open( 'some_file.txt', 'w+')
f.write("text")
return FileResponse(f, as_attachment=True, filename='some_file.txt')
It's very simple:
response = HttpResponse(content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename="filename.txt"'
response.write('Hello')
return response
https://docs.djangoproject.com/en/3.2/howto/outputting-csv/
It's same as CSV, just change extension to .txt
Example:
response = HttpResponse(content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename="filename.txt"'
writer = csv.writer(response)
writer.writerow(['Hello'])

Two forms one view: model variable loses value

Here's my code
#login_required
def upload(request):
form_type = ''
transcript = Transcript()
transcript.file_path = ''
if request.method == 'POST':
if 'file_form' in request.POST:
file_form = FileForm(request.POST, request.FILES)
if file_form.is_valid():
path = handle_uploaded_file(request.FILES['file'], request.user)
transcript.file_path = path
transcript.user = request.user
export_form = InfoForm()
form_type = 'info_form'
elif 'info_form' in request.POST:
if transcript.file_path:
info_form = InfoForm(request.POST)
if info_form.is_valid():
transcript.user = request.user
transcript.title = info_form.cleaned_data.get('title')
transcript.instructions = info_form.cleaned_data.get('instructions')
transcript.save()
return HttpResponseRedirect('thanks')
else:
raise ValueError('Transcript object has no file path attribute')
else:
export_form = FileForm()
form_type = 'file_form'
return render(request, 'transcription/upload.html', {'form': export_form, 'form_type': form_type})
always, the file-form is called before the info-form, so the code in the if statement
if transcript.file_path:
#...
should always execute. But the ValueError always gets raised, meaning transcript.file_path is reset. How does this happen, and how can it be fixed?
file_form and info_form in POST are names of the different submit buttons, so I know which form I am dealing with.
def handle_uploaded_file(file, user):
id = randint(0, 10000)
user_dir = settings.MEDIA_ROOT + '/' + str(user.id).replace(".", "") + '/'
path = user_dir + file.name.replace(".mp3", str(id) + ".mp3")
if not os.path.exists(user_dir):
os.makedirs(user_dir)
with open(path, 'wb+') as destination:
for chunk in file.chunks():
destination.write(chunk)
file = File(destination)
info = {'path': path, 'file': file}
return path
So it was a rookie mistake.
I didn't know that during each post request the whole view gets called again.
So I just initialized my variables
form_type = ''
transcript = Transcript()
transcript.file_path = ''
outside the view and voila!

clean() method causes files to lose data using POST form

I have set up a form and view to upload multiple *.gpx files to my website at once. These files are validated using a clean() method on the form and then once validated passed to a function for processing.
When I upload some invalid files the clean() method catches them and informs the user as expected.
When I upload some valid files the processing function crashes with an error saying the files are empty.
If I comment out the clean() method then the valid files are uploaded fine.
What can be happening to the form during the clean() method than means the files are being blanked?
here is my form:
class UploadGpxForm(forms.Form):
gpx_file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
here is my view:
class UploadGpxView(FormView):
form_class = UploadGpxForm
template_name = 'dashboard/upload.html' # Replace with your template.
success_url = reverse_lazy('dashboard:index') # Replace with your URL or reverse().
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('gpx_file')
if form.is_valid():
for f in files:
SaveGPXtoPostGIS(f)
return self.form_valid(form)
else:
return self.form_invalid(form)
Here is my clean method for the UploadGpxForm:
def clean(self):
file_errors=[]
files = list(self.files.getlist('gpx_file'))
for f in list(files):
#check file has only one full stop in it.
if len(f.name.split('.')) != 2:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded:'\
'File type is not supported')
, params = { 'file_name': f.name }
, code = 'file_type')
)
#check file doesn't breach the file size listed in settings
if f.content_type in settings.DASHBOARD_UPLOAD_FILE_TYPES:
if f._size > settings.DASHBOARD_UPLOAD_FILE_MAX_SIZE:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded: File too big.'\
'Please keep filesize under %(setting_size)s.'\
'Current filesize %(file_size)s') ,
params = {
'file_name': f.name,
'setting_size': filesizeformat(
settings.DASHBOARD_UPLOAD_FILE_MAX_SIZE),
'file_size': filesizeformat(f._size)
},
code = 'file_size'
)
)
#check it is one of our allowed file types
else:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded:'\
'File type is not supported')
, params = { 'file_name' : f.name }
, code = 'file_type'
)
)
#next check the file hasn't been uploaded before
#generate MD5
md5hash = md5()
for chunk in f.chunks():
md5hash.update(chunk)
file_hash = md5hash.hexdigest()
if gpxTrack.objects.filter(file_hash=file_hash).exists():
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded as a identical file'\
'has already been uploaded previously'),
params = { 'file_name' : f.name },
code = 'file_hash'))
#finally raise errors if there are any
if len(file_errors) > 0:
raise ValidationError(file_errors)
else:
return files
When you read the file content (for calculating md5 hash) you need to move the file object’s position to the beginning (0th byte) using file.seek:
md5hash = md5()
for chunk in f.chunks():
md5hash.update(chunk)
file_hash = md5hash.hexdigest()
f.seek(0) #<-- add this line

Categories

Resources