Is it possible to call the same PageTemplate multiple times within one doc.build?
For example if you have a PageTemplate for orders that uses order instance attributes in the header and footer, how would you go about creating a PDF of all orders for a given date?
class OrderTemplate(BaseDocTemplate):
def __init__(self, filename, **kwargs):
self.order = kwargs['order']
BaseDocTemplate.__init__(self, filename **kwargs)
main_frame = Frame(0, 0, self.pagesize[0], self.pagesize[1], id='main_frame')
template = PageTemplate(id='header_footer_frame', frames=[main_frame],
onPage=self.header_footer)
self.addPageTemplates([template])
# title
canvas.setFont('Helvetica', 12)
company_name = self.order.name.upper()
first_row = canvas.beginText(0, doc.pagesize[1] - 10)
first_row.textLine(company_name)
canvas.drawText(first_row)
class Report:
#staticmethod
def build_pdf(**kwargs):
buffer = BytesIO()
doc = OrderTemplate(buffer, pagesize=kwargs['pagesize'],
title=kwargs['page_title'],
order=kwargs['order'])
doc.build(kwargs['story'], canvasmaker=NumberedCanvas)
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
#staticmethod
def build_order(**kwargs):
orders = kwargs['orders']
pagesize = letter
# Flow-able content
story = []
for order in orders:
lines = OrderLine.objects.filter(order=order).order_by('pk')
story.append(table)
story.append(PageBreakIfNotEmpty())
return Report.build_pdf(pagesize=pagesize, page_title=page_title,
story=story, template=template, order=order)
The build_order() method generates a page for each order with the correct associated lines but the header/footer is the same across each page. I need to somehow call the PageTemplate for each loop and pass it the new order kwargs.
Here's the solution I arrived at based on the use of chapters in ReportLab's user guide rltemplate.py
class OrderTemplate(PageTemplate):
def __init__(self, id, pagesize=letter):
frame = Frame(0, 0, pagesize[0], pagesize[1], id='main_frame')
PageTemplate.__init__(self, id, [frame])
def afterDrawPage(self, canvas, doc):
canvas.saveState()
canvas.setPageSize(doc.pagesize)
canvas.setTitle(doc.title)
if doc.order:
# title
canvas.setFont('Helvetica', 12)
company_name = doc.order.name.upper()
first_row = canvas.beginText(0, doc.pagesize[1] - 10)
first_row.textLine(company_name)
canvas.drawText(first_row)
canvas.restoreState()
class BatchPrintTemplate(BaseDocTemplate):
def __init__(self, filename, **kwargs):
self.orders = kwargs['orders']
self.order_iter = iter(self.orders)
BaseDocTemplate.__init__(self, filename, **kwargs)
def beforeDocument(self):
self.order = next(self.order_iter)
def afterInit(self):
self.addPageTemplates(OrderTemplate('Order', pagesize=self.pagesize))
def afterFlowable(self, flowable):
if hasattr(flowable, 'new_order'):
try:
self.order = next(self.order_iter)
except StopIteration:
pass
When building your flowables you have to mark each page break with a new_order attribute. Setting the next order to use in the header is handled by afterFlowable.
class BatchPrint:
def build_pdf(**kwargs):
buffer = BytesIO()
doc = BatchPrintTemplate(buffer, pagesize=kwargs['pagesize'], title=kwargs['page_title'],
orders=kwargs['orders'])
doc.build(kwargs['story'])
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
def build_orders(**kwargs):
story = []
for order in orders:
## build your flowables here ##
story.append(table)
story.append(PageBreak())
story[-1].new_order = True
return BatchPrint.build_pdf(pagesize=pagesize, page_title=page_title, story=story, orders=orders)
Related
I am trying to use ExtraModel and custom_export to export data from live_pages. However, when I go on devserver and check the data tab, it is nowhere to be found. If I download the excel (bottom right of the page) the new variables are not included in the data.
Where can I find the data from the custom export? Or am I defining the function wrong? Any help greatly appreciated.
See MWE below
from otree.api import *
import random
doc = """
Your app description
"""
class C(BaseConstants):
NAME_IN_URL = 'mwe_export'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 1
NUM_EMPLOYERS = 3
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
pass
class Offer(ExtraModel):
group = models.Link(Group)
sender = models.Link(Player)
wage = models.IntegerField()
effort = models.IntegerField()
job_id = models.IntegerField()
information_type = models.StringField()
# FUNCTIONS
def to_dict(offer: Offer):
return dict(sender=offer.sender.id_in_group,
wage=offer.wage,
effort=offer.effort,
job_id=offer.job_id,
information_type=offer.information_type)
# PAGES
class MyPage(Page):
#staticmethod
def js_vars(player: Player):
return dict(my_id=player.id_in_group)
#staticmethod
def live_method(player: Player, data):
print(data)
group = player.group
job_id = random.randint(1, 1000)
wage = data['wage']
effort = data['effort']
information_type = data['information_type']
if data['information_type'] == 'offer':
offer = Offer.create(group=group,
sender=player,
job_id=job_id,
wage=wage,
effort=effort,
information_type=information_type)
print(offer)
print(to_dict(offer))
return {0: to_dict(offer)}
page_sequence = [MyPage]
def custom_export(players):
yield ['session.code', 'participant_code', 'id_in_session']
offers = Offer.filter()
for offer in offers:
player = offer.sender
participant = player.participant
yield [participant.code, participant.id_in_session, offer.job_id, offer.wage, offer.effort]
In the menu at the top of the admin page there is also a "Data" item. The custom export for your app should be available there under the heading "Per-app".
I have built a custom model that allows me to save to Airtable from a MySQL query. The goal is to pass the base name and key from a loop of base keys and table names, and have the model use the save function which was working before the last adjustment. I cannot figure out what the change was, I have commented out everything but:
daily_kpi=Daily_KPI(base_key=base['base_key'], table_name=base['table_name'])
And I still get an error.
class Daily_KPI(at.AT):
base_key=None
table_name='Daily'
#Fields
#property
def ID(self):
dt=datetime.strptime(self.date, '%Y-%m-%d').strftime('%m/%d/%y')
return str(dt)+" "+self.sku+" "+self.account
date=at.ATField(field_name="date", at_field_name="Date")
asin=at.ATField(field_name="asin", at_field_name="ASIN")
account=at.ATField(field_name="account", at_field_name="Account")
# country=at.ATField(field_name="country", at_field_name="Country")
sessions=at.ATField(field_name="sessions", at_field_name="Sessions")
session_pct=at.ATField(field_name="session_pct", at_field_name="Session Pct")
page_views=at.ATField(field_name="page_views", at_field_name="Page Views")
page_view=at.ATField(field_name="page_view", at_field_name="Page View")
buy_box=at.ATField(field_name="buy_box", at_field_name="Buy Box")
units_ordered=at.ATField(field_name="units_ordered", at_field_name="Units Ordered")
units_ord_b2b=at.ATField(field_name="units_ord_b2b", at_field_name="Units Ord B2B")
unit_session=at.ATField(field_name="unit_session", at_field_name="Unit Session")
ord_prod=at.ATField(field_name="ord_prod", at_field_name="Ord Prod")
ord_prod_b2b=at.ATField(field_name="ord_prod_b2b", at_field_name="Ord Prod B2B")
tot_ord_items=at.ATField(field_name="tot_ord_items", at_field_name="Tot Ord Items")
tot_ord_b2b=at.ATField(field_name="tot_ord_b2b", at_field_name="Tot Ord B2B")
bsr=at.ATField(field_name="bsr", at_field_name="B.S.R")
actual_sales=at.ATField(field_name="actual_sales", at_field_name="Actual Sales")
selling_price=at.ATField(field_name="selling_price", at_field_name="Selling Price")
notes=at.ATField(field_name="notes", at_field_name="Notes")
unit_sess_b2b=at.ATField(field_name="unit_sess_b2b", at_field_name="Unit Sess B2B")
sku=at.ATField(field_name="sku", at_field_name="SKU")
def __init__(self, base_key, table_name):
super(Daily_KPI, self).__init__(base_key=base_key, table_name=table_name)
When I try to use the model,
def save_data(self):
try:
print ('hello')
#wp_table=Airtable(BASE_KEY, 'Weekly Profitability Development copy', API_KEY)
results = self.results
print(len(results))
for base in settings.DAILY_AIRTABLE:
for row in self.results:
try:
daily_kpi=Daily_KPI(base_key=base['base_key'], table_name=base['table_name'])
daily_kpi.date=str(date.today())
# daily_kpi.sku = row['ItemSKU']
# daily_kpi.asin = row['ASIN']
# daily_kpi.account = row['Account']
# daily_kpi.sessions = row['Sessions']
# daily_kpi.session_pct = row['Session_Pct']
# daily_kpi.page_view = row['Page_Views']
# daily_kpi.page_view_pct = row['Page_Views_Pct']
# daily_kpi.buy_box = row['Buy_Box_Pct']
# daily_kpi.units_ordered = row['Units_Ordered']
# daily_kpi.units_ordered_b2b = row['Units_Ordered_B2B']
# daily_kpi.unit_session_pct=row['Unit_Session_Pct']
# daily_kpi.unit_session_pct_b2b= row['Unit_Session_Pct_B2B']
# daily_kpi.ord_prod=row['Ordered_Product_Sales']
# daily_kpi.ord_prod_b2b=row['Ordere_Product_Sales_B2B']
# daily_kpi.tot_ord_items=row['Total_Order_Items']
# daily_kpi.tot_ord_b2b=row['Total_Order_Items_B2B']
# daily_kpi.bsr=row['BSR']
# daily_kpi.actual_sales
# daily_kpi.selling_price
# daily_kpi.notes
# daily_kpi.unit_sess_b2b
# daily_kpi.sku
daily_kpi.save()
except Exception as e:
print(e)
I get error: super() argument 1 must be type, not classobj
I am not quite sure how to adjust the constructor so that it accepts the parameters and creates an object. I tried
def __init__(self, base_key, table_name):
super(Daily_KPI, self).__init__(self, base_key=base_key, table_name=table_name)
I made a simple airtable Pytbon client. It would be fairly easy to do what you described with it:
https://github.com/gtalarico/airtable-python-wrapper
I am trying to extract raw data from a text file and after processing the raw data, I want to export it to another text file. Below is the python code I have written for this process. I am using the "petl" package in python 3 for this purpose. 'locations.txt' is the raw data file.
import glob, os
from petl import *
class ETL():
def __init__(self, input):
self.list = input
def parse_P(self):
personids = None
for term in self.list:
if term.startswith('P'):
personids = term[1:]
personid = personids.split(',')
return personid
def return_location(self):
location = None
for term in self.list:
if term.startswith('L'):
location = term[1:]
return location
def return_location_id(self, location):
location = self.return_location()
locationid = None
def return_country_id(self):
countryid = None
for term in self.list:
if term.startswith('C'):
countryid = term[1:]
return countryid
def return_region_id(self):
regionid = None
for term in self.list:
if term.startswith('R'):
regionid = term[1:]
return regionid
def return_city_id(self):
cityid = None
for term in self.list:
if term.startswith('I'):
cityid = term[1:]
return cityid
print (os.getcwd())
os.chdir("D:\ETL-IntroductionProject")
print (os.getcwd())
final_location = [['L','P', 'C', 'R', 'I']]
new_location = fromtext('locations.txt', encoding= 'Latin-1')
stored_list = []
for identifier in new_location:
if identifier[0].startswith('L'):
identifier = identifier[0]
info_list = identifier.split('_')
stored_list.append(info_list)
for lst in stored_list:
tabling = ETL(lst)
location = tabling.return_location()
country = tabling.return_country_id()
city = tabling.return_city_id()
region = tabling.return_region_id()
person_list = tabling.parse_P()
for person in person_list:
table_new = [location, person, country, region, city]
final_location.append(table_new)
totext(final_location, 'l1.txt')
However when I use "totext" function of petl, it throws me an "Assertion Error".
AssertionError: template is required
I am unable to understand what the fault is. Can some one please explain the problem I am facing and what I should be doing ?
The template parameter to the toext function is not optional there is no default format for how the rows are written in this case, you must provide a template. Check the doc for toext here for an example: https://petl.readthedocs.io/en/latest/io.html#text-files
The template describes the format of each row that it writes out using the field headers to describe things, you can optionally pass in a prologue to write the header too. A basic template in your case would be:
table_new_template = "{L} {P} {C} {R} {I}"
totext(final_location, 'l1.txt', template=table_new_template)
I'm trying to generate a preview for an "overlay" config stored in a django model than will be applied later to other model. I have not much experience manipulating files with python... =(
Here is my code:
import io
from django.conf import settings
from django.db import models
from wand.image import Image
from PIL.ImageFile import ImageFile, Parser, Image as PilImage
class Overlay(models.Model):
RELATIVE_POSITIONS = (...)
SIZE_MODES = (...)
name = models.CharField(max_length=50)
source = models.FileField(upload_to='overlays/%Y/%m/%d')
sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
px = models.SmallIntegerField(default=0)
py = models.SmallIntegerField(default=0)
position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
width = models.SmallIntegerField(default=0)
height = models.SmallIntegerField(default=0)
size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
last_edit = models.DateTimeField(auto_now=True)
def generate_sample(self):
"""
Generates the sample image and saves it in the "sample" field model
:return: void
"""
base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
overlay_pic = Image(file=self.source)
result_pic = io.BytesIO()
pil_parser = Parser()
if self.width or self.height:
resize_args = {}
if self.width:
resize_args['width'] = self.width
if self.height:
resize_args['height'] = self.height
overlay_pic.resize(**resize_args)
base_pic.composite(overlay_pic, self.px, self.py)
base_pic.save(file=result_pic)
result_pic.seek(0)
while True:
s = result_pic.read(1024)
if not s:
break
pil_parser.feed(s)
pil_result_pic = pil_parser.close()
self.sample.save(self.name, pil_result_pic, False)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.generate_sample()
super(Overlay, self).save(force_insert, force_update, using, update_fields)
But i'm getting AttributeError read here is part on my django debug data:
/usr/local/lib/python2.7/dist-packages/django/core/files/utils.py in <lambda>
"""
encoding = property(lambda self: self.file.encoding)
fileno = property(lambda self: self.file.fileno)
flush = property(lambda self: self.file.flush)
isatty = property(lambda self: self.file.isatty)
newlines = property(lambda self: self.file.newlines)
read = property(lambda self: self.file.read)
readinto = property(lambda self: self.file.readinto)
readline = property(lambda self: self.file.readline)
readlines = property(lambda self: self.file.readlines)
seek = property(lambda self: self.file.seek)
softspace = property(lambda self: self.file.softspace)
tell = property(lambda self: self.file.tell)
▼ Local vars
Variable Value
self <File: None>
/usr/local/lib/python2.7/dist-packages/PIL/Image.py in __getattr__
# numpy array interface support
new = {}
shape, typestr = _conv_type_shape(self)
new['shape'] = shape
new['typestr'] = typestr
new['data'] = self.tobytes()
return new
raise AttributeError(name)
def __getstate__(self):
return [
self.info,
self.mode,
self.size,
▼ Local vars
Variable Value
self <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1080x1618 at 0x7F1429291248>
name 'read'
What's wrong?
Solved!
Such as #Alexey Kuleshevich was saying django FileField need a Fileobjeto, but what was missing is that we must first save the image to a file on disk or in memory, as will guess it's better memory... so here is the final solution. I think it could be improved to not use two step "conversion"
from django.core.files.base import ContentFile
and within the method:
result_pic = io.BytesIO()
pil_parser = Parser()
...
overlay_pic.resize(**resize_args)
base_pic.composite(overlay_pic, self.px, self.py)
base_pic.save(file=result_pic)
result_pic.seek(0)
while True:
s = result_pic.read(1024)
if not s:
break
pil_parser.feed(s)
result_pic = io.BytesIO()
pil_result_pic = pil_parser.close()
pil_result_pic.save(result_pic, format='JPEG')
django_file = ContentFile(result_pic.getvalue())
self.sample.save(self.name, django_file, False)
Thanks to this answer:
How do you convert a PIL Image to a Django File?
Whenever you save a file to ImageField or FileField you need to make sure it is Django's File object. Here the reference to documentation: https://docs.djangoproject.com/en/1.7/ref/models/fields/#filefield-and-fieldfile
from django.core.files import File
and within a method:
def generate_sample(self):
...
pil_result_pic = pil_parser.close()
self.sample.save(self.name, File(pil_result_pic), False)
Otherwise it looks good, although I might have missed something. Try it out and see if it fixes the problem, if not I'll look more into it.
Edit
You actually don't need a parser. I think that should solve it:
from django.core.files import ContentFile
class Overlay(models.Model):
...
def generate_sample(self):
base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
overlay_pic = Image(file=self.source)
result_pic = io.BytesIO()
if self.width or self.height:
resize_args = {}
if self.width:
resize_args['width'] = self.width
if self.height:
resize_args['height'] = self.height
overlay_pic.resize(**resize_args)
base_pic.composite(overlay_pic, self.px, self.py)
base_pic.save(file=result_pic)
content = result_pic.getvalue()
self.sample.save(self.name, ContentFile(content), False)
result_pic.close()
base_pic.close()
overlay_pic.close()
There is one thing that can be a potential problem, it will perform this operation every time Overlay model is saved, even if original images are the same. But if it is saved rarely, it shouldn't be an issue.
Just in case, here is a more elegant (in my opinion) implementation. First of all it requires this app: django-smartfields. How this solution is better:
It updates the sample field only when source field has changed, and only right before saving the model.
if keep_orphans is omitted, old source files will be cleaned up.
The actual code:
import os
from django.conf import settings
from django.db import models
from django.utils import six
from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import WandImageProcessor
from wand.image import Image
class CustomImageProcessor(WandImageProcessor):
def resize(self, image, scale=None, instance=None, **kwargs):
scale = {'width': instance.width, 'height': instance.height}
return super(CustomImageProcessor, self).resize(
image, scale=scale, instance=instance, **kwargs)
def convert(self, image, instance=None, **kwargs):
base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
base_pic.composite(image, instance.px, instance.py)
stream_out = super(CustomImageProcessor, self).convert(
image, instance=instance, **kwargs):
if stream_out is None:
stream_out = six.BytesIO()
base_pic.save(file=stream_out)
return stream_out
class Overlay(models.Model):
RELATIVE_POSITIONS = (...)
SIZE_MODES = (...)
name = models.CharField(max_length=50)
source = fields.ImageField(upload_to='overlays/%Y/%m/%d', dependencies=[
FileDependency(attname='sample', processor=CustomImageProcessor())
], keep_orphans=True)
sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
px = models.SmallIntegerField(default=0)
py = models.SmallIntegerField(default=0)
position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
width = models.SmallIntegerField(default=0)
height = models.SmallIntegerField(default=0)
size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
last_edit = models.DateTimeField(auto_now=True)
I have an Openstack-powered, Django-modified application that shows the disk images and snapshots available for a user to launch. The user currently sees both snapshots they created and ones they did not. I would like to separate the current table into two based on whether they are owned by the user or not.
My two table definitions are as follows (note I altered row_actions accordingly):
class UserSnapshotsTable(OldSnapshotsTable):
cloud = tables.Column(get_cloud, verbose_name=_("Cloud"))
class Meta:
name = "usersnapshots"
verbose_name = _("User Snapshots")
table_actions = (DeleteSnapshot,)
row_actions = (LaunchSnapshot, LaunchCluster, EditImage, DeleteSnapshot)
pagination_param = "snapshot_marker"
row_class = UpdateRow
status_columns = ["status"]
class OtherSnapshotsTable(OldSnapshotsTable):
cloud = tables.Column(get_cloud, verbose_name=_("Cloud"))
class Meta:
name = "othersnapshots"
verbose_name = _("Other Snapshots")
table_actions = (DeleteSnapshot,)
row_actions = (LaunchSnapshot, LaunchCluster)
pagination_param = "snapshot_marker"
row_class = UpdateRow
status_columns = ["status"]
I have altered the HTML template to pull the "UserSnapshotsTable" and "OtherSnapshotsTable" tables (I copied the original table and renamed both), but both full tables still generate under the respective headings. There are two functions generating the data:
def get_usersnapshots_data(self):
req = self.request
marker = req.GET.get(UserSnapshotsTable._meta.pagination_param, None)
try:
usersnaps, self._more_snapshots = api.snapshot_list_detailed(req,
marker=marker)
except:
usersnaps = []
exceptions.handle(req, _("Unable to retrieve user-owned snapshots."))
return usersnaps
def get_othersnapshots_data(self):
req = self.request
marker = req.GET.get(OtherSnapshotsTable._meta.pagination_param, None)
try:
othersnaps, self._more_snapshots = api.snapshot_list_detailed(req,
marker=marker)
except:
othersnaps = []
exceptions.handle(req, _("Unable to retrieve non-user-owned snapshots."))
return othersnaps
There are also Edit/Delete options defined for images, and imported for snapshots, that seem to have a key comparison. Here's the "Delete" one (line 7):
class DeleteImage(tables.DeleteAction):
data_type_singular = _("Image")
data_type_plural = _("Images")
def allowed(self, request, image=None):
if image:
return image.owner == request.user.tenant_id
# Return True to allow table-level bulk delete action to appear.
return True
def delete(self, request, obj_id):
api.image_delete(request, obj_id)
How can I separate those tables out? This is my first time asking a question here, so please let me know if I can provide further information. Apologies for the length of it.
As far as I see you are using glanceclient. If that so you can use extra_filters parameter of snapshot_list_detailed() to filter only user images like this:
usersnaps, self._more_snapshots = api.snapshot_list_detailed(
req,
marker = marker,
extra_filters = {"owner": "user_name"}
)
Under cover snapshot_list_detailed uses GET images of Openstack Image Service API.