pdfmake incl. fixes

This commit is contained in:
Thomas Junk 2016-08-19 14:44:58 +02:00
parent 546c4e65f6
commit 92a541215f
8 changed files with 169 additions and 160 deletions

View File

@ -22,6 +22,7 @@ Core:
Motions: Motions:
- Added origin field. - Added origin field.
- Added button to sort and number all motions in a category. - Added button to sort and number all motions in a category.
- Introduced pdfMake for clientside generation of PDFs.
Users: Users:
- Added field is_committee and new default group Committees. - Added field is_committee and new default group Committees.

View File

@ -37,8 +37,15 @@ angular.module('OpenSlidesApp.core.site', [
color: '#555', color: '#555',
fontSize: 10, fontSize: 10,
margin: [80, 50, 80, 0], //margin: [left, top, right, bottom] margin: [80, 50, 80, 0], //margin: [left, top, right, bottom]
columns: ['OpenSlides | Presentation and assembly system', { columns: [
{
text: 'OpenSlides | Presentation and assembly system',
fontSize:10,
width: '70%'
},
{
fontSize: 6, fontSize: 6,
width: '30%',
text: 'Stand: ' + date.toLocaleDateString() + " " + date.toLocaleTimeString(), text: 'Stand: ' + date.toLocaleDateString() + " " + date.toLocaleTimeString(),
alignment: 'right' alignment: 'right'
}] }]

View File

@ -19,6 +19,10 @@ urlpatterns = [
views.SearchView.as_view(), views.SearchView.as_view(),
name='core_search'), name='core_search'),
url(r'^core/encode_media/$',
views.MediaEncoder.as_view(),
name="core_mediaencoding"),
url(r'^angular_js/(?P<openslides_app>site|projector)/$', url(r'^angular_js/(?P<openslides_app>site|projector)/$',
views.AppsJsView.as_view(), views.AppsJsView.as_view(),
name='core_apps_js'), name='core_apps_js'),
@ -26,8 +30,8 @@ urlpatterns = [
# View for the projectors are handelt by angular. # View for the projectors are handelt by angular.
url(r'^projector.*$', views.ProjectorView.as_view()), url(r'^projector.*$', views.ProjectorView.as_view()),
# Main entry point for all angular pages. # Main entry point for all angular pages.
# Has to be the last entry in the urls.py # Has to be the last entry in the urls.py
url(r'^.*$', views.IndexView.as_view()), url(r'^.*$', views.IndexView.as_view()),
] ]

View File

@ -1,3 +1,6 @@
import base64
import json
import os
import re import re
import uuid import uuid
from collections import OrderedDict from collections import OrderedDict
@ -606,3 +609,143 @@ class SearchView(utils_views.APIView):
return super().get_context_data( return super().get_context_data(
elements=search(unquote(query)), elements=search(unquote(query)),
**context) **context)
class MediaEncoder(utils_views.APIView):
"""
MediaEncoder is a class based view to prepare encoded media for pdfMake
"""
http_method_names = ['post']
def post(self, request, *args, **kwargs):
"""
Encode_image is used in the context of PDF-Generation
Takes an array of IMG.src - Paths
Retrieves the according images
Encodes the images to BASE64
Add configured fonts
Puts it into a key-value structure
{
"images": {
"media/file/ubuntu.png":"$ENCODED_IMAGE"
},
"fonts": [{
$FontName : {
normal: $Filename
bold: $Filename
italics: $Filename
bolditalics: $Filename
}
}],
"default_font": "$DEFAULTFONT"
}
:param request:
:return: Response of the resulting dictionary
Calling e.g.
$.ajax({ type: "POST", url: "/motions/encode_images/",
data: JSON.stringify(["$FILEPATH"]),
success: function(data){ console.log(data); },
dataType: 'application/json' });
"""
body_unicode = request.body.decode('utf-8')
file_paths = json.loads(body_unicode)
images = {file_path: self.encode_image_from(file_path) for file_path in file_paths}
fonts = self.encoded_fonts()
default_font = self.get_default_font()
return Response({
"images": images,
"fonts": fonts,
"defaultFont": default_font
})
def get_default_font(self):
"""
Returns the default font for pdfMake.
Note: For development purposes this is hard coded.
:return: the name of the default Font
"""
return 'OpenSans'
def encoded_fonts(self):
"""
Generate font encoding for pdfMake
:return: list of Font Encodings
"""
fonts = self.get_configured_fonts()
enc_fonts = [self.encode_font(name, files) for name, files in fonts.items()]
return enc_fonts
def get_configured_fonts(self):
"""
Returns the configured fonts
Note: For development purposes, the current font definition is hard coded
The form is {
$FontName : {
normal: $Filename
bold: $Filename
italics: $Filename
bolditalics: $Filename
}
}
This structure is required according to PDFMake specs.
:return:
"""
fonts = {
'OpenSans': {
'normal': 'OpenSans-Regular.ttf',
'bold': 'OpenSans-Bold.ttf',
'italics': 'OpenSans-Italic.ttf',
'bolditalics': 'OpenSans-BoldItalic.ttf'
}
}
return fonts
def encode_font(self, font_name, font_files):
"""
Responsible to encode a single font
:param fontName: name of the font
:param font_files: files for different weighs
:return: dictionary with encoded font
"""
encoded_files = {type: self.encode_font_from(file_path) for type, file_path in font_files.items()}
return {font_name: encoded_files}
def encode_font_from(self, file_path):
"""
Returns the BASE64 encoded version of an image-file for a given path
:param file_path:
:return: dictionary with the string representation (content) and the name of the file
for the pdfMake.vfs structure
"""
path = os.path.join(settings.SITE_ROOT, 'static/fonts', os.path.basename(file_path))
try:
with open(path, "rb") as file:
string_representation = "{}".format(base64.b64encode(file.read()).decode())
except:
return ""
else:
return {"content": string_representation, "name": file_path}
def encode_image_from(self, file_path):
"""
Returns the BASE64 encoded version of an image-file for a given path
:param file_path:
:return:
"""
path = os.path.join(settings.MEDIA_ROOT, 'file', os.path.basename(file_path))
try:
with open(path, "rb") as file:
string_representation = "data:image/{};base64,{}".format(os.path.splitext(file_path)[1][1:],
base64.b64encode(file.read()).decode())
except Exception:
# If any error occurs ignore it and return an empty string
return ""
else:
return string_representation

View File

@ -49,9 +49,11 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
*/ */
signment = function(motion, $scope, User) { signment = function(motion, $scope, User) {
var label = converter.createElement("text", gettextCatalog.getString('Submitter') + ':\nStatus:'); var label = converter.createElement("text", gettextCatalog.getString('Submitter') + ':\nStatus:');
var state = converter.createElement("text", User.get(motion.submitters_id[0]).full_name + '\n'+gettextCatalog.getString(motion.state.name));
state.width = "70%";
label.width = "30%"; label.width = "30%";
label.bold = true; label.bold = true;
var signment = converter.createElement("stack", [label]); var signment = converter.createElement("columns", [label, state]);
signment.margin = [10, 20, 0, 10]; signment.margin = [10, 20, 0, 10];
signment.lineHeight = 2.5; signment.lineHeight = 2.5;
return signment; return signment;
@ -778,10 +780,11 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'MotionContentProvider', 'MotionContentProvider',
'PdfMakeConverter', 'PdfMakeConverter',
'PdfMakeDocumentProvider', 'PdfMakeDocumentProvider',
'gettextCatalog',
function($scope, $http, ngDialog, MotionForm, function($scope, $http, ngDialog, MotionForm,
Motion, Category, Mediafile, Tag, Motion, Category, Mediafile, Tag,
User, Workflow, motion, User, Workflow, motion,
SingleMotionContentProvider, MotionContentProvider, PdfMakeConverter, PdfMakeDocumentProvider) { SingleMotionContentProvider, MotionContentProvider, PdfMakeConverter, PdfMakeDocumentProvider, gettextCatalog) {
Motion.bindOne(motion.id, $scope, 'motion'); Motion.bindOne(motion.id, $scope, 'motion');
Category.bindAll({}, $scope, 'categories'); Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles'); Mediafile.bindAll({}, $scope, 'mediafiles');
@ -794,13 +797,14 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
$scope.makePDF = function(){ $scope.makePDF = function(){
var content = motion.getText($scope.version) + motion.getReason($scope.version), var content = motion.getText($scope.version) + motion.getReason($scope.version),
id = motion.identifier,
slice = Function.prototype.call.bind([].slice), slice = Function.prototype.call.bind([].slice),
map = Function.prototype.call.bind([].map), map = Function.prototype.call.bind([].map),
image_sources = map($(content).find("img"), function(element) { image_sources = map($(content).find("img"), function(element) {
return element.getAttribute("src"); return element.getAttribute("src");
}); });
$http.post('/motions/encode_media/', JSON.stringify(image_sources)).success(function(data) { $http.post('/core/encode_media/', JSON.stringify(image_sources)).success(function(data) {
/** /**
* Converter for use with pdfMake * Converter for use with pdfMake
* @constructor * @constructor
@ -812,8 +816,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
var converter = PdfMakeConverter.createInstance(data.images, data.fonts, pdfMake), var converter = PdfMakeConverter.createInstance(data.images, data.fonts, pdfMake),
motionContentProvider = MotionContentProvider.createInstance(converter), motionContentProvider = MotionContentProvider.createInstance(converter),
contentProvider = SingleMotionContentProvider.createInstance(motionContentProvider, motion, $scope, User), contentProvider = SingleMotionContentProvider.createInstance(motionContentProvider, motion, $scope, User),
documentProvider = PdfMakeDocumentProvider.createInstance(contentProvider, data.defaultFont); documentProvider = PdfMakeDocumentProvider.createInstance(contentProvider, data.defaultFont),
pdfMake.createPdf(documentProvider.getDocument()).open(); filename = gettextCatalog.getString("Motion") + " " + id + ".pdf";
pdfMake.createPdf(documentProvider.getDocument()).download(filename);
}); });
}; };

View File

@ -5,13 +5,9 @@
<i class="fa fa-angle-double-left fa-lg"></i> <i class="fa fa-angle-double-left fa-lg"></i>
<translate>All motions</translate> <translate>All motions</translate>
</a> </a>
<a ui-sref="motions_single_pdf({pk: motion.id})" target="_blank" class="btn btn-default btn-sm">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
</a>
<a ng-click="makePDF()" class="btn btn-primary btn-sm"> <a ng-click="makePDF()" class="btn btn-primary btn-sm">
<i class="fa fa-file-pdf-o fa-lg"></i> <i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDFmake</translate> <translate>PDF</translate>
</a> </a>
<!-- List of speakers --> <!-- List of speakers -->
<a ui-sref="agenda.item.detail({id: motion.agenda_item_id})" class="btn btn-sm btn-default"> <a ui-sref="agenda.item.detail({id: motion.agenda_item_id})" class="btn btn-sm btn-default">

View File

@ -14,6 +14,4 @@ urlpatterns = [
url(r'^poll/(?P<poll_pk>\d+)/print/$', url(r'^poll/(?P<poll_pk>\d+)/print/$',
views.MotionPollPDF.as_view(), views.MotionPollPDF.as_view(),
name='motionpoll_pdf'), name='motionpoll_pdf'),
url(r'^encode_media/', views.encode_media, name="media_encoding")
] ]

View File

@ -1,16 +1,10 @@
import base64
import json
import os
from django.conf import settings
from django.db import transaction from django.db import transaction
from django.http import Http404, JsonResponse from django.http import Http404
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
from reportlab.platypus import SimpleDocTemplate from reportlab.platypus import SimpleDocTemplate
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view
from openslides.core.config import config from openslides.core.config import config
from openslides.utils.rest_api import ( from openslides.utils.rest_api import (
@ -472,142 +466,3 @@ class MotionPDFView(SingleObjectMixin, PDFView):
motions_to_pdf(pdf, motions) motions_to_pdf(pdf, motions)
else: else:
motion_to_pdf(pdf, self.get_object()) motion_to_pdf(pdf, self.get_object())
@api_view(["POST"])
def encode_media(request):
"""
Encode_image is used in the context of PDF-Generation
Takes an array of IMG.src - Paths
Retrieves the according images
Encodes the images
Add configured fonts
Puts it into a key-value structure
{
"images": {
"media/file/ubuntu.png":"$ENCODED_IMAGE"
},
"fonts": [{
$FontName : {
normal: $Filename
bold: $Filename
italics: $Filename
bolditalics: $Filename
}
}],
"default_font": "$DEFAULTFONT"
}
:param request:
:return: JsonResponse of the resulting dictionary
Calling e.g.
$.ajax({ type: "POST", url: "/motions/encode_images/",
data: JSON.stringify(["$FILEPATH"]),
success: function(data){ console.log(data); },
dataType: 'application/json' });
"""
body_unicode = request.body.decode('utf-8')
file_paths = json.loads(body_unicode)
images = {file_path: encode_image_from(file_path) for file_path in file_paths}
fonts = encoded_fonts()
default_font = get_default_font()
return JsonResponse({
"images": images,
"fonts": fonts,
"defaultFont": default_font
})
def get_default_font():
"""
For development purposes this is hard coded
:return: the name of the default Font
"""
return "OpenSans"
def encoded_fonts():
"""
Generate font encoding for pdfMake
:return: list of Font Encodings
"""
fonts = get_configured_fonts()
enc_fonts = [encode_font(name, files) for name, files in fonts.items()]
return enc_fonts
def get_configured_fonts():
"""
For development purposes, the current font definition is hard coded
The form is {
$FontName : {
normal: $Filename
bold: $Filename
italics: $Filename
bolditalics: $Filename
}
}
This structure is required according to PDFMake specs.
:return:
"""
fonts = {
"OpenSans": {
"normal": 'OpenSans-Regular.ttf',
"bold": 'OpenSans-Bold.ttf',
"italics": 'OpenSans-Italic.ttf',
"bolditalics": 'OpenSans-BoldItalic.ttf'
}
}
return fonts
def encode_font(fontName, font_files):
"""
Responsible to encode a single font
:param fontName: name of the font
:param font_files: files for different weighs
:return: dictionary with encoded font
"""
encoded_files = {type: encode_font_from(file_path) for type, file_path in font_files.items()}
return {fontName: encoded_files}
def encode_font_from(file_path):
"""
Returns the BASE64 encoded version of an image-file for a given path
:param file_path:
:return: dictionary with the string representation (content) and the name of the file
for the pdfMake.vfs structure
"""
path = os.path.join(settings.SITE_ROOT, 'static/fonts', os.path.basename(file_path))
try:
with open(path, "rb") as file:
string_representation = "{}".format(base64.b64encode(file.read()).decode())
except:
return ""
else:
return {"content": string_representation, "name": file_path}
def encode_image_from(file_path):
"""
Returns the BASE64 encoded version of an image-file for a given path
:param file_path:
:return:
"""
path = os.path.join(settings.MEDIA_ROOT, 'file', os.path.basename(file_path))
try:
with open(path, "rb") as file:
string_representation = "data:image/{};base64,{}".format(os.path.splitext(file_path)[1][1:],
base64.b64encode(file.read()).decode())
except:
return ""
else:
return string_representation