I've managed to successfully generate an empy PDF, but it doesn't set the initial view. I'd like to set the initial view to "full view" i.e. the end user see one page fits the PDF reader (= an A4 page fits in the screen).
def render_to_response(self, context, **response_kwargs):
response = HttpResponse(content_type='application/pdf; charset=utf-8')
response['Content-Disposition'] = 'attachment; filename=""'
p = canvas.Canvas(response, pagesize=A4, )
p.showPage()
p.save()
return response
How to set the default zoom view (if it's possible) with reportlab?
In short
Add this line:
p.setViewerPreference("FitWindow", "true")
Explain
Set viewer preference with:
def setViewerPreference(self,pref,value):
set one of the allowed enbtries in the documents viewer preferences
Available pref and value are:
class ViewerPreferencesPDFDictionary(CheckedPDFDictionary):
validate=dict(
HideToolbar=checkPDFBoolean,
HideMenubar=checkPDFBoolean,
HideWindowUI=checkPDFBoolean,
FitWindow=checkPDFBoolean,
CenterWindow=checkPDFBoolean,
DisplayDocTitle=checkPDFBoolean, #contributed by mark Erbaugh
NonFullScreenPageMode=checkPDFNames(*'UseNone UseOutlines UseThumbs UseOC'.split()),
Direction=checkPDFNames(*'L2R R2L'.split()),
ViewArea=checkPDFNames(*'MediaBox CropBox BleedBox TrimBox ArtBox'.split()),
ViewClip=checkPDFNames(*'MediaBox CropBox BleedBox TrimBox ArtBox'.split()),
PrintArea=checkPDFNames(*'MediaBox CropBox BleedBox TrimBox ArtBox'.split()),
PrintClip=checkPDFNames(*'MediaBox CropBox BleedBox TrimBox ArtBox'.split()),
PrintScaling=checkPDFNames(*'None AppDefault'.split()),
)
Reference
ReportLab API Reference
ViewerPreferencesPDFDictionary
Related
I'm trying to read a pdf form with django. The point is that in another view of my views.py I've succeed into do it by using PyPDF2 and its PdfFileReader.getFields() method.
Now the problem is that the reading is not working properly: I've checked with adobe acrobat and the file still is a form with actually fields, so I don't really have any idea of what could be the problem.
I'm attaching here the relevant portion of the code:
if request.method == "POST":
form = Form(request.POST, request.FILES) # the form refer to a model called 'New Request'
if form.is_valid():
form.save()
File = request.FILES['File'].name
full_filename = os.path.join(BASE_DIR, 'media/media', File)
f = PdfFileReader(full_filename)
fields = f.getFields()
fdfinfo = dict((k, v.get('/V', '')) for k, v in fields.items())
k = creare_from_pdf2(request, fdfinfo, pk) # this is a custom function
nr = NewRequest.objects.all() #I'm deleting the object uploaded because it won't be useful anymore
nr.delete()
os.remove(full_filename)
If I display print(fdfinfo) it actually shows {}. This of course is leading to error when fdfinfo passes into the 'create_from_pdf_2' function. I don't really know what the problem could be, also because in another view I made exactly the same and it works:
if request.method=='POST':
form = Form(request.POST, request.FILES)
if form.is_valid():
form.save()
uploaded_filename = request.FILES['File'].name
full_filename = os.path.join(BASE_DIR, 'media/media', uploaded_filename)
f = PdfFileReader(full_filename)
fields = f.getFields()
fdfinfo = dict((k, v.get('/V', '')) for k, v in fields.items())
k=create_from_pdf1(request, fdfinfo)
if k==1:
return HttpResponse('<html><body>Something went wrong</html></body>')
nr = NewRequest.objects.all()
nr.delete()
os.remove(full_filename)
Maybe is there a way to display the errors of PdfFileReader?
UPDATING
The new file that I'm trying to reading is firstly modified in the sense that some (BUT NOT ALL!) fields are filled with PdfFileWriter, and the one filled are set then to only readable. Could this operation have influenced the performances of PdfFileReader? I'm attaching the correspondent view
att = MAIN.objects.get(id=pk)
file_path = os.path.join(BASE_DIR, 'nuova_form.pdf')
input_stream = open(file_path, "rb")
pdf_reader = PdfFileReader(input_stream, strict = False)
if "/AcroForm" in pdf_reader.trailer["/Root"]:
pdf_reader.trailer["/Root"]["/AcroForm"].update(
{NameObject("/NeedAppearances"): BooleanObject(True)})
pdf_writer = PdfFileWriter()
set_need_appearances_writer(pdf_writer)
if "/AcroForm" in pdf_writer._root_object:
# Acro form is form field, set needs appearances to fix printing issues
pdf_writer._root_object["/AcroForm"].update(
{NameObject("/NeedAppearances"): BooleanObject(True)})
data_dict1 = { # my text fields
}
data_dict2 = { # my booleancheckbox fields }
for i in range(0,6): #The pdf file has 6 pages
pdf_writer.addPage(pdf_reader.getPage(i))
page = pdf_writer.getPage(i)
# update form fields
pdf_writer.updatePageFormFieldValues(page, data_dict1)
for j in range(0, len(page['/Annots'])):
writer_annot = page['/Annots'][j].getObject()
for field in data_dict1:
if writer_annot.get('/T') == field:
writer_annot.update({
NameObject("/Ff"): NumberObject(1) # make ReadOnly
})
# update checkbox fields
updateCheckboxValues(page, data_dict2)
output_stream = BytesIO()
pdf_writer.write(output_stream)
return output_stream
def updateCheckboxValues(page, fields):
for j in range(0, len(page['/Annots'])):
writer_annot = page['/Annots'][j].getObject()
for field in fields:
if writer_annot.get('/T') == field:
writer_annot.update({
NameObject("/V"): NameObject(fields[field]),
NameObject("/AS"): NameObject(fields[field])
})
I got similar results when trying to do a straightforward read of a PDF form using Python and PyPDF2. The PDF form had been created using Libre Writer and was a single page with about 50 text fields on it. When I ran the getFields() method on the reader object I was getting the same issue -- it was returning an empty dict object.
I thought there might be a limitation on the number of fields and tried removing some for testing, but got the same result. Then when looking at it I noticed the fieldnames were all pretty long: txtLabMemberFirstName01, txtLabMemberLastName01, txtPrincipalInvestigatorFirstName, etc.
I shortened all the fields' names (e.g., "txtLMFN01") and PyPDF2 started working again as expected.
G'day All,
Hope everyone is doing well.
I have a table that I render with a custom column that is a hyperlink.
The problem is when i export to CSV it just gives me the text of the hyperlink and not the hyperlink.
I'm wanting to export the hyperlink instead of just the text, is this possible?
(if i need to switch to xlsx export that's also fine)
(worse comes to worse I can just make the text the full path)
Custom column:
document_link = tables.TemplateColumn('{{record.document_file}}', verbose_name="Document location")
Thanks in advance,
I would suggest making use of "Including and Excluding Columns"
https://django-tables2.readthedocs.io/en/latest/pages/export.html#including-and-excluding-columns
Add exclude_from_export=True to the document_link field, and an invisible document_link_for_csv field.
class DocumentTable(tables.Table):
document_link = tables.TemplateColumn(
'<a href="{{record.document_file.url}}/"
target="_blank">{{record.document_file}}</a>',
verbose_name="Document location",
exclude_from_export=True
)
document_link_for_csv = columns.Column(
visible=False,
accessor="document_file__url"
)
Update based on comment:
You can pass the request through to the table, and then do a custom render and request.build_absolute_uri().
class DocumentTable(tables.Table):
document_link = tables.Column(
verbose_name="Document location",
exclude_from_export=True
)
document_link_for_csv = columns.Column(
visible=False,
accessor="document_file__url"
)
def render_document_link_for_csv(self, value, record):
return format_html(
'{},
request.build_absolute_uri(),
record.document_file.url,
record.document_file
)
This was the final way how I managed to do it:
class DocumentTable(ExportMixin,tables.Table):
document_link = tables.TemplateColumn('{{record.document_file}}', verbose_name="Document location", exclude_from_export=True)
document_URL = tables.TemplateColumn('render_replaces_this',visible=False)
def render_document_URL(self, value, record):
return format_html(
'{}{}',
self.request.META['HTTP_HOST'],
record.document_file.url,
)
I'm using Django I want to send some data from my database to a document word, I'm using Python-Docx for creating word documents I use the class ExportDocx it can generate a static word file but I want to place some dynamic data (e.g. product id =5, name=""..) basically all the details to the "product" into the document
class ExportDocx(APIView):
def get(self, request, *args, **kwargs):
queryset=Products.objects.all()
# create an empty document object
document = Document()
document = self.build_document()
# save document info
buffer = io.BytesIO()
document.save(buffer) # save your memory stream
buffer.seek(0) # rewind the stream
# put them to streaming content response
# within docx content_type
response = StreamingHttpResponse(
streaming_content=buffer, # use the stream's content
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
response['Content-Disposition'] = 'attachment;filename=Test.docx'
response["Content-Encoding"] = 'UTF-8'
return response
def build_document(self, *args, **kwargs):
document = Document()
sections = document.sections
for section in sections:
section.top_margin = Inches(0.95)
section.bottom_margin = Inches(0.95)
section.left_margin = Inches(0.79)
section.right_margin = Inches(0.79)
# add a header
document.add_heading("This is a header")
# add a paragraph
document.add_paragraph("This is a normal style paragraph")
# add a paragraph within an italic text then go on with a break.
paragraph = document.add_paragraph()
run = paragraph.add_run()
run.italic = True
run.add_text("text will have italic style")
run.add_break()
return document
This is the URL.py of the
path('<int:pk>/testt/', ExportDocx.as_view() , name='generate-testt'),
How can I generate it tho I think I need to make the data string so it can work with py-docx.
for the python-docx documentation: http://python-docx.readthedocs.io/
For a product record like: record = {"product_id": 5, "name": "Foobar"), you can add it to the document in your build_document()` method like:
document.add_paragraph(
"Product id: %d, Product name: %s"
% (record.product_id, record.name)
)
There are other more modern methods for interpolating strings, although this sprintf style works just fine for most cases. This resource is maybe not a bad place to start.
So I found out that I need to pass the model I was doing it but in another version of code and forgot to add it... Basically, I just had to add these lines of code, hope this helps whoever is reading this.
def get(self, request,pk, *args, **kwargs):
# create an empty document object
document = Document()
product = Product.objects.get(id=pk)
document = self.build_document(product)
And in the build of the document we just need to stringify it simply by using f'{queryset.xxxx}'
def build_document(self,queryset):
document = Document()
document.add_heading(f'{queryset.first_name}')
Using Web2Py RESTFUL services, I'd like the following html to render in the browser after navigating to this link (http://127.0.0.1:8000/app/default/api/example_data.html_table/?limit=1):
<div><table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE</td><td>Smith</td><td>1</td></tr></tbody></table></div>
However, I currently receive the following:
<div><table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>SUUUUPPPEEEER...</td><td>Smith</td><td>1</td></tr></tbody></table></div>
The difference is that Web2Py is shortening "SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE" to "SUUUUPPPEEEER..." but I need the entire text
My View called generic.html_table that generates this is the following:
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
Controller
#request.restful()
def api():
response.view = 'generic.' + request.extension
def GET(*args,**vars):
patterns = 'auto'
parser = db.parse_as_rest(patterns,args,vars)
return dict(content=parser.response)
def POST(table_name,**vars):
if 'id' in vars.keys():
return db[table_name].update_or_insert(db[table_name]._id == vars['id'],**vars)
else:
return db[table_name].validate_and_insert(**vars)
def PUT(table_name,**vars):
record_id = vars['id']
return db(db[table_name]._id==record_id).update(**vars)
def DELETE(table_name,record_id):
return db(db[table_name]._id==record_id).delete()
return dict(GET=GET, POST=POST, PUT=PUT, DELETE=DELETE)
MODEL
db.define_table('example_data', Field('Firstname', 'string'),Field('Lastname', 'string'),Field('Age', 'integer'))
I've also tried the following views:
{{=response._vars[next(iter(response._vars))]}}
RESULT (first name still cut off):
<table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>Jill</td><td>Smith</td><td>1</td></tr><tr class="w2p_even even"><td>2</td><td>Eve</td><td>Jackson</td><td>33</td></tr><tr class="w2p_odd odd"><td>3</td><td>afdaskfdlasjf...</td><td>Jackson</td><td>33</td></tr><tr class="w2p_even even"><td>4</td><td>SUUUUPPPEEEER...</td><td>Jackson</td><td>33</td></tr></tbody></table>
I've also tried the following views:
{{=XML(response._vars[next(iter(response._vars))])}}
RESULT (Lost all HTML formatting):
example_data.id,example_data.Firstname,example_data.Lastname,example_data.Age
1,Jill,Smith,1
2,Eve,Jackson,33
3,afdaskfdlasjfkdlsjfklajdfskasjfklsdajfdklsajfklsajfdskalfdjsakldfjklasfjkdlsajfdsakljdklsadcjklasjcklsjackldsjakfldajsfklasdfjklasjfdklajfdsklsjafkldasjfkldasjkldsjcklajsckljackldajsdfklfjkalsncklacnkalsdfjkldasnckldasjckljsdaklfdnfkldsajfdklasjldsk,Jackson,33
4,SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE,Jackson,33
All of those methods ultimately result in the DAL Rows object being passed to SQLTABLE for rendering in the view. By default, fields are truncated to 16 characters. If you want to change that, you will have to call SQLTABLE explicitly:
{{=SQLTABLE(content, truncate=100)}}
I want to fetch 10 articles from my Articles model on first load and then another 10 as users scroll towards the bottom via AJAX. I store the ids of the first 10 in an array and then append the subsequent ones to it. This is to enable fetch articles that are not in that list but I get empty array and the first 10 articles are still fetched when I scroll to the bottom again and again.
First load:
import json
my_interest = user_object.interet #this returns [3,4,55,24,57]
articles = Articles.objects.all()[:10]
fetched = [x.id for x in articles]
request.session['fetched'] = json.dumps(fetched)
Another 10 via AJAX:
import operator
fetched = json.loads(request.session['fetched'])
my_interest = user_object.interet #this returns [3,4,55,24,57]
query = reduce(operator.and_,[Q(cat_id__in = my_interest ), ~Q(id__in = fetched )])
articles = Articles.objects.filter(query)[:10]
request.session['fetched'] = json.dumps( fetched + [x.id for x in articles])
context = {'articles': articles, 'fetched': request.session['fetched']}
return render_to_response('mysite/loadmore.html', context)
But I still get the same 10 that was first fetched repeatedly as I scroll to the bottom of the page and if I <p> Fetched: {{fetched}} </p> on my template I only see Fetched:
I'm not sure if this is your problem, but there is no need for either reduce or Q in your query. It is much simpler and clearer to write:
articles = Articles.objects.filter(cat_id__in=my_interest).exclude(id__in=fetched )
Also there's no point in dumping/loading to JSON when saving the IDs to the session. The session already takes care of serialization. Just do:
fetched = request.session['fetched']
...
request.session['fetched'].extend([x.id for x in articles])
Q(id__in = fetched )
Looks like you request articles with the same ids, that you already fetched and saved to session?