How to attach an uploaded file for e-mailing in web2py - python-2.7

All,
Python and web2py newbie here - I am trying to forward user input (e-mail address and a file) via e-mail, once a user has uploaded the information on a website.
The user-provided-information is stored in a database but it is yet over my head to fetch the file from the database and forward it via e-mail. Any pointers much appreciated!
This is my controller action-
def careers():
form = SQLFORM(db.cv_1, formstyle='bootstrap3_stacked')
for label in form.elements('label'):
label["_style"] = "display:none;"
form.custom.submit.attributes['_value'] = 'Submit CV'
if form.process().accepted:
applicant = str(form.vars.email)
mail.send(to=['email#company.com'], message= applicant + ' new CV', subject='CV submission', attachment=mail.Attachment('/path/to/file'))
return dict(form=form)
This is the database model
db.define_table('cv_1', Field('email',
requires=IS_EMAIL(error_message='Please provide your e-mail'),
widget=widget(_placeholder='Your e-mail (required)',_readonly=False)),
Field('cv', 'upload', autodelete=True, requires=[IS_LENGTH(1048576,1024),
IS_UPLOAD_FILENAME(extension='pdf')]))

The transformed filename of the uploaded file will be in form.vars.cv (this is the value stored in the db.cv_1.cv field in the database). You can use that along with the field's .retrieve method to get either the full file path or the file object itself:
original_filename, filepath = db.cv_1.cv.retrieve(form.vars.cv)
mail.send(to=['email#company.com'], message=applicant + ' new CV',
subject='CV submission',
attachment=mail.Attachment(filepath, original_filename))

Related

How to properly save instance of FileField in Django

Im hoping someone can tell me what I am doing wrong and point me in the right direction.
So, I am creating an array of Model objects and then doing a bulk_create at the end to save them to the database. The one thing I am having an issue with is after I added the the FileField I cannot exactly how I need to associate data with that field. The files don't end up in the upload_to folder set nor do they end up being associated with the record itself. Id also add that I am using PyPDf2 to create the PDF files before trying to associate them to an instance of my model.
So to give you an idea on what I am trying to do. I am running this code to create the PDFs initially.
if pdf_file_name is not None:
num_pages_current_doc = page['pageRange'][-1]
input_pdf = PdfFileReader(open(pdf_file_name, 'rb'))
output_pdf = PdfFileWriter()
for _ in range(num_pages_current_doc):
output_pdf.addPage(input_pdf.getPage(page_counter))
page_counter += 1
with open(str(uuid.uuid4())+'.pdf', 'wb') as output:
output_pdf.write(output)
logging.info(f'Wrote filename: { output.name }')
The saved file I then want to associated with a model instance below this, the code looks something like this:
document = document(
location=location,
Field2=Field2, etc etc .....
pdf = ???
Im unsure how to set the field for that pdf part, Ive tried using the File() method on it. Tried putting just output.name for the field, Im not sure how to go about making this work.
Could anyone give me some insight?
Thanks!
See the FieldFile.save(name, content, save=True) method in django docs. https://docs.djangoproject.com/en/3.2/ref/models/fields/#filefield-and-fieldfile

Directing Output Paths of Altered Files

How can I direct the destination of the output file to my db?
My models.py is structured like so:
class Model(models.Model):
char = models.CharField(max_length=50, null=False, blank=False)
file = models.FileField(upload_to=upload_location, null=True, blank=True)
I have the user enter a value for 'char', and then the value of 'char' is printed on to a file. The process of successfully printing onto the file is working, however, the file is outputting to my source directory.
My goal is to have the output file 'pdf01.pdf' output to my db and be represented as 'file' so that the admin can read it.
Much of the information in the Dango docs has been focussed on directing the path of objects imported by the user directly, not on files that have been created internally. I have been reading mostly from these docs:
Models-Fields
Models
File response objects
Outputting PDFs
I have seen it recommend to write to a buffer, not a file, then save the buffer contents to my db however I haven't been able to find many examples of how to do that relevant to my situation online.
Perhaps there is a relevant gap in my knowledge regarding buffers and BytesIO? Here is the function I have been using to alter the pdf, I have been using BytesIO to temporarily store files throughout the process but have not been able to figure out how to use it to direct the output anywhere specific.
can = canvas.Canvas(BytesIO(), pagesize=letter)
can.drawString(10, 10, char)
can.save()
BytesIO().seek(0)
text_pdf = PdfFileReader(BytesIO())
base_file = PdfFileReader(open("media/01.pdf", "rb"))
page = base_file.getPage(0)
page.mergePage(text_pdf.getPage(0))
PdfFileWriter().addPage(page)
PdfFileWriter().write(open("pdf01.pdf", "wb")
FileField does not store files directly in the database. Files get uploaded in a location on the filesystem determined by the upload_to argument. Only some metadata are stored in the DB, including the path of the file in your filesystem.
If you want to have the contents of the files in the database, you could create a new File model that includes a BinaryField to store the data and a CharField to store the URL from which the file can be fetched. To feed the data of PdfFileWriter to the binary field of Django, perhaps the most appropriate would be to use BytesIO.
I found this workaround to direct the file to a desired location (in this case both my media_cdn folder and also output it to an admin.)
I set up an admin action to perform the function that outputs the file so the admin will have access to both the output version in the form of both an HTTP response and through the media_cdn storage.
Hope this helps anyone who struggles with the same problem.
#admin.py
class edit_and_output():
def output:
author = Account.email
#alter file . . .
with open('media_cdn/account/{0}.pdf'.format(author), 'wb') as out_file:
output.write(out_file)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment;filename="{0}.pdf"'.format(author)
output.write(response)

Using each line of a file separately in a script

I have a list of 235 twitter IDs in a text file.
like so:
2597319
7445591
273299750
337590061
16510947
21958717
I need to go through each one and get the coordinates for each ID. I have written a script that collects the most common coordinate for an account but currently I have to go through manually changing the ID. I am fairly new to Python so any help would be greatly appreciated.
Below is the start of the script, with the '------' indicating were the ID name should be.
#finds unique IDs and saves them in a separate file
lines=open("twitter.txt",'r').readlines()
uniquelines=set(lines)
open("unique.txt",'w').writelines(uniquelines)
auth = tweepy.auth.OAuthHandler('username', 'password')
auth.set_access_token('username', 'password')
api = tweepy.API(auth)
user = api.get_user('------').followers()
#gets 50 tweets from specific ID
statuses = api.user_timeline(id = '------', count = 50)

How to Web2py custom download function

I'm developing a simple webapp in web2py and I want to create a link that let's the user download a file. Like this:
<a href="{{=URL('download',args = FILE)}}" download>
However, I want to do this without having to pass the FILE to the user in the page handler. I want to retrieve an ID from the server asynchronously that will correspond to the file I want to download and then pass it to a custom download function like this:
<a href="{{=URL('custom_download',args = FILEID)}}" download>
This way, I will be able to upload files to the server asynchronously, (I already figured out how to do that) and the download link on the page for that file will work right away without having to reload the page.
So, on the server side, I would do something like this:
def custom_download():
download_row = db(db.computers.FILEID == request.args(0)).select()
download_file = download_row.filefield
return download_file
However, I'm not entirely sure what I need to write in order for this to work.
I assumed that your files are stored in uploads folder, then your custom download function will be:
def custom_download():
download_row = db(db.computers.FILEID == request.args(0)).select().first()
download_file = download_row.filefield
# Name of file is table_name.field.XXXXX.ext, so retrieve original file name
org_file_name = db.computers.filefield.retrieve(download_file)[0]
file_header = "attachment; filename=" + org_file_name
response.headers['ContentType'] = "application/octet-stream"
response.headers['Content-Disposition'] = file_header
file_full_path = os.path.join(request.folder, 'uploads', download_file)
fh = open(file_full_path, 'rb')
return response.stream(fh)

Manage multiple uploads with Flask session

I have a following situation. I created a simple backend in Flask that handles file uploads. With files received, Flask does something (uploads them), and returns the data to the caller. There are two scenarios with the app, to upload one image and multiple images. When uploading one image, I can simply get the response and voila, I'm all set.
However, I am stuck on handling multiple file uploads. I can use the same handler for the actual file upload, but the issue is that all of those files need to be stored into a list or something, then processed, and after doing that, a single link (album) containing all those images, needs to be delivered.
Here is my upload handling code:
#app.route('/uploadv3', methods=['POST'])
def upload():
if request.method == 'POST':
data_file = request.files["file"]
file_name = data_file.filename
path_to_save_to = os.path.join(app.config['UPLOAD_FOLDER'], file_name)
data_file.save(path_to_save_to)
file_url = upload_image_to_image_host(path_to_save_to)
return file_url
I was experimenting with session in flask, but I dont know can I create a list of items under one key, like session['links'], and then get all those, and clear it after doing the work. Or is there some other simpler solution?
I assume that I could probably do this via key for each image, like session["link1"], and so on, but that would impose a limit on the images (depending on how much of those I create), would make the code very ugly, make the iteration over each in order to generate a list that is passed to an album building method problematic, and session clearing would be tedious.
Some code that I wrote for getting the actual link at the end and clearing the session follows (this assume that session['link'] has a list of urls, which I can't really achieve with my knowledge of session management in Flask:
def create_album(images):
session.pop('link', None)
new_album = im.create_album(images)
return new_album.link
#app.route('/get_album_link')
def get_album_link():
return create_album(session['link'])
Thanks in advance for your time!
You can assign anything to a session including individual value or list/dictionary etc. If you know the links, you can store them in the session as follows:
session['links'] = ['link1','link2'...and so on]
This way, you have a list of all the links. You can now access a link by:
if 'links' in session:
for link in session['links']:
print link
Once you are done with them, you can clear the session as:
if 'links' in session:
del session['links']
To clarify what I have done to make this work. At the end, it appeared that the uploading images and adding them to the album anonymously had to be done "reversely", so not adding images to an album object, but uploading an image object to an album id.
I made a method that gets the album link and puts it in the session:
#app.route('/get_album_link')
def get_album_link():
im = pyimgur.Imgur(CLIENT_ID)
new_album = im.create_album()
session.clear()
session['album'] = new_album.deletehash
session['album_link'] = new_album.link
return new_album.link
Later on, when handling uploads, I just add the image to the album and voila, all set :)
uploaded_image = im.upload_image(path_of_saved_image, album=session['album'])
file_url = uploaded_image.link
return file_url
One caveat is that the image should be added to the "deleteahash" value passed as the album value, not the album ID (which is covered by the imgur api documentation).