schilder.py 11.4 KB
Newer Older
Dave Kliczbor's avatar
Dave Kliczbor committed
1
2
3
4
#!/usr/bin/python
# -*- encoding: utf8 -*-

from flask import Flask, flash, session, redirect, url_for, escape, request, Response, Markup
5
6
7
8
import sys
import os
import os.path
import glob
Dave Kliczbor's avatar
Dave Kliczbor committed
9
10
11
12
13
from genshi.template import TemplateLoader
from genshi.template.text import NewTextTemplate
from flaskext.genshi import Genshi, render_response
from werkzeug.utils import secure_filename
from collections import defaultdict
14
from docutils.core import publish_parts
Dave Kliczbor's avatar
Dave Kliczbor committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import warnings
import shutil
import subprocess
from subprocess import CalledProcessError, STDOUT
import PythonMagick
import json
import tempfile
import config

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = config.uploaddir
app.config['PROPAGATE_EXCEPTIONS'] = True
app.secret_key = config.app_secret
genshi = Genshi(app)
genshi.extensions['html'] = 'html5'

31

Dave Kliczbor's avatar
Dave Kliczbor committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def check_output(*popenargs, **kwargs):
    # Copied from py2.7s subprocess module
    r"""Run command with arguments and return its output as a byte string.

    If the exit code was non-zero it raises a CalledProcessError.  The
    CalledProcessError object will have the return code in the returncode
    attribute and output in the output attribute.

    The arguments are the same as for the Popen constructor.  Example:

    >>> check_output(["ls", "-l", "/dev/null"])
    'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'

    The stdout argument is not allowed as it is used internally.
    To capture standard error in the result, use stderr=STDOUT.

    >>> check_output(["/bin/sh", "-c",
    ...               "ls -l non_existent_file ; exit 0"],
    ...              stderr=STDOUT)
    'ls: non_existent_file: No such file or directory\n'
    """
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode != 0:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise CalledProcessError(retcode, cmd, output=output)
        #raise Exception(output)
    return output

66

Dave Kliczbor's avatar
Dave Kliczbor committed
67
def allowed_file(filename):
68
69
    return '.' in filename and filename.rsplit('.', 1)[1] in config.allowed_extensions

Dave Kliczbor's avatar
Dave Kliczbor committed
70
71
72

@app.route('/')
def index(**kwargs):
73
74
75
76
77
78
    data = defaultdict(str)
    data.update(**kwargs)
    filelist = glob.glob(config.datadir + '/*.schild')
    data['files'] = [unicode(os.path.basename(f)) for f in sorted(filelist)]
    return render_response('index.html', data)

Dave Kliczbor's avatar
Dave Kliczbor committed
79
80
81

@app.route('/edit')
def edit(**kwargs):
82
83
84
85
86
87
88
89
90
    data = defaultdict(str)
    data.update(**kwargs)
    imagelist = glob.glob(config.imagedir + '/*.png')
    data['images'] = [os.path.basename(f) for f in imagelist]
    templatelist = glob.glob(config.textemplatedir + '/*.tex')
    data['templates'] = [unicode(os.path.basename(f))
                         for f in sorted(templatelist)]
    return render_response('edit.html', data)

Dave Kliczbor's avatar
Dave Kliczbor committed
91
92
93

@app.route('/edit/<filename>')
def edit_one(filename):
94
95
96
97
98
99
    with open(os.path.join(config.datadir, filename), 'r') as infile:
        formdata = defaultdict(str, json.load(infile))
        if len(formdata['markup']) < 1:
            formdata['markup'] = 'latex'
        return edit(form=formdata)

Dave Kliczbor's avatar
Dave Kliczbor committed
100

101
def run_pdflatex(context, outputfilename, overwrite=True):
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    if not context.has_key('textemplate'):
        context['textemplate'] = "text-image-quer.tex"
    genshitex = TemplateLoader([config.textemplatedir])
    template = genshitex.load(
        context['textemplate'], cls=NewTextTemplate, encoding='utf8')
    if not overwrite and os.path.isfile(outputfilename) and os.path.getmtime(template.filepath) < os.path.getmtime(outputfilename):
        return
    print(str(context))
    if context['markup'] == 'rst':
        context['text'] = publish_parts(context['text'], writer_name='latex')['body']
        context['headline'] = publish_parts(context['headline'], writer_name='latex')['body']
    tmpdir = tempfile.mkdtemp(dir=config.tmpdir)
    if context.has_key('img') and context['img'] and context['img'] != '__none':
        try:
            shutil.copy(os.path.join(config.imagedir, context[
                        'img']), os.path.join(tmpdir, context['img']))
        except:
            raise IOError("COULD NOT COPY")
    else:
        # print "MEH No image"
        pass
    tmptexfile = os.path.join(tmpdir, 'output.tex')
    tmppdffile = os.path.join(tmpdir, 'output.pdf')
    with open(tmptexfile, 'w') as texfile:
        texfile.write(template.generate(form=context).render(encoding='utf8'))
    cwd = os.getcwd()
    os.chdir(tmpdir)
    os.symlink(config.texsupportdir, os.path.join(tmpdir, 'support'))
Dave Kliczbor's avatar
Dave Kliczbor committed
130
    try:
131
132
133
134
135
136
137
138
        texlog = check_output(
            ['pdflatex', '--halt-on-error', tmptexfile], stderr=STDOUT)
    except CalledProcessError as e:
        if overwrite:
            flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % e.output), 'log')
        raise SyntaxWarning("PDFLaTeX bailed out")
    finally:
        os.chdir(cwd)
139
    if overwrite:
140
141
142
143
        flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % texlog), 'log')
    shutil.copy(tmppdffile, outputfilename)
    shutil.rmtree(tmpdir)

Dave Kliczbor's avatar
Dave Kliczbor committed
144
145

def save_and_convert_image_upload(inputname):
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    file = request.files[inputname]
    if file:
        if not allowed_file(file.filename):
            raise UserWarning(
                "Uploaded image is not in the list of allowed file types.")
        filename = os.path.join(
            config.uploaddir, secure_filename(file.filename))
        file.save(filename)
        img = PythonMagick.Image(filename)
        imgname = os.path.splitext(secure_filename(file.filename))[
            0].replace('.', '_') + '.png'
        savedfilename = os.path.join(config.imagedir, imgname)
        img.write(savedfilename)
        os.remove(filename)
        return imgname
    return None

Dave Kliczbor's avatar
Dave Kliczbor committed
163
164
165

@app.route('/create', methods=['POST'])
def create():
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
    if request.method == 'POST':
        formdata = defaultdict(str, request.form.to_dict(flat=True))
        for a in ('headline', 'text'):
            formdata[a] = unicode(formdata[a])
        try:
            imgpath = save_and_convert_image_upload('imgupload')
            if imgpath is not None:
                formdata['img'] = imgpath
            outfilename = secure_filename(formdata['headline'][:16]) + str(hash(formdata['headline'] + formdata[
                'text'] + os.path.splitext(formdata['textemplate'])[0] + os.path.splitext(formdata['img'])[0])) + '.schild'
            outpdfname = outfilename + '.pdf'
            formdata['pdfname'] = outpdfname
            with open(os.path.join(config.datadir, outfilename), 'w') as outfile:
                json.dump(formdata, outfile)
            run_pdflatex(formdata, os.path.join(config.pdfdir, outpdfname))
            flash(Markup(u"""PDF created and data saved. You might create another one. Here's a preview. Click to print.<br/>
Dave Kliczbor's avatar
Dave Kliczbor committed
182
          <a href="%s"><img src="%s"/></a>""" %
183
184
185
186
187
188
189
190
191
192
193
194
195
196
                         (url_for('schild', filename=outfilename), url_for(
                             'pdfthumbnail', pdfname=outpdfname, maxgeometry=200))
                         ))
        except Exception as e:
            flash(u"Could not create pdf or save data: %s" % str(e), 'error')

        data = {'form': formdata}
        imagelist = glob.glob(config.imagedir + '/*.png')
        data['images'] = [os.path.basename(f) for f in imagelist]
        templatelist = glob.glob(config.textemplatedir + '/*.tex')
        data['templates'] = [os.path.basename(f) for f in sorted(templatelist)]
        return redirect(url_for('edit_one', filename=outfilename))
    flash("No POST data. You've been redirected to the edit page.", 'warning')
    return redirect(url_for('edit'))
Dave Kliczbor's avatar
Dave Kliczbor committed
197
198
199
200


@app.route('/schild/<filename>')
def schild(filename):
201
202
    return render_response('schild.html', {'filename': filename, 'printer': [unicode(f) for f in sorted(config.printers.keys())]})

Dave Kliczbor's avatar
Dave Kliczbor committed
203
204
205

@app.route('/printout', methods=['POST'])
def printout():
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
    filename = os.path.join(
        config.pdfdir, secure_filename(request.form['filename']))
    printer = config.printers[request.form['printer']]
    copies = int(request.form['copies']) or 0
    if copies > 0 and copies <= 6:
        try:
            lprout = check_output(['lpr', '-H', str(config.printserver), '-P', str(
                printer), '-#', str(copies)] + config.lproptions + [filename], stderr=STDOUT)
            flash(u'Schild wurde zum Drucker geschickt!')
        except CalledProcessError as e:
            flash(Markup("<p>Could not print:</p><pre>%s</pre>" % e.output), 'error')
    else:
        flash(u'Ungültige Anzahl Kopien!')
    return redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
221
222
223

@app.route('/delete', methods=['POST'])
def delete():
224
225
226
227
228
229
230
231
232
233
234
    filename = secure_filename(request.form['filename'])
    try:
        os.unlink(os.path.join(config.datadir, filename))
        for f in glob.glob(os.path.join(config.pdfdir, filename + '.pdf*')):
            os.unlink(f)
        flash(u"Schild %s wurde gelöscht" % filename)
        return redirect(url_for('index'))
    except:
        flash(u"Schild %s konnte nicht gelöscht werden." % filename, 'error')
        return redirect(url_for('schild', filename=filename))

Dave Kliczbor's avatar
Dave Kliczbor committed
235
236
237

@app.route('/image/<imgname>')
def image(imgname):
238
239
240
241
242
243
244
245
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    # print(imgpath)
    if os.path.exists(imgpath):
        with open(imgpath, 'r') as imgfile:
            return Response(imgfile.read(), mimetype="image/png")
    else:
        return "Meh"  # redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
246
247

def make_thumb(filename, maxgeometry):
248
249
250
251
252
253
254
255
    thumbpath = filename + '.' + str(maxgeometry)
    if not os.path.exists(thumbpath) or os.path.getmtime(filename) > os.path.getmtime(thumbpath):
        img = PythonMagick.Image(str(filename))
        img.transform("%sx%s" % (maxgeometry, maxgeometry))
        img.quality(90)
        img.write(str("png:%s" % thumbpath))
    return thumbpath

Dave Kliczbor's avatar
Dave Kliczbor committed
256
257
258

@app.route('/thumbnail/<imgname>/<int:maxgeometry>')
def thumbnail(imgname, maxgeometry):
259
260
261
262
263
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    thumbpath = make_thumb(imgpath, maxgeometry)
    with open(thumbpath, 'r') as imgfile:
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
264
265
266

@app.route('/pdfthumb/<pdfname>/<int:maxgeometry>')
def pdfthumbnail(pdfname, maxgeometry):
267
268
269
270
271
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
    thumbpath = make_thumb(pdfpath, maxgeometry)
    with open(thumbpath, 'r') as imgfile:
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
272

273
274
@app.route('/tplthumb/<tplname>/<int:maxgeometry>')
def tplthumbnail(tplname, maxgeometry):
275
276
277
278
279
280
281
    pdfpath = os.path.join(config.cachedir, secure_filename(tplname) + '.pdf')
    try:
        run_pdflatex(
            {'textemplate': secure_filename(tplname),
             'img': 'pictograms-nps-misc-camera.png',
             'headline': u'Überschrift',
             'text': u'Dies ist der Text, der in der UI als Text bezeichnet ist.',
282
             'markup': 'latex',
283
284
285
286
287
288
289
290
291
             }, pdfpath, overwrite=False
        )
    except Exception as e:
        return str(e)
    else:
        thumbpath = make_thumb(pdfpath, maxgeometry)
        with open(thumbpath, 'r') as imgfile:
            return Response(imgfile.read(), mimetype="image/png")

292

Dave Kliczbor's avatar
Dave Kliczbor committed
293
294
@app.route('/pdfdownload/<pdfname>')
def pdfdownload(pdfname):
295
296
297
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
    with open(pdfpath, 'r') as pdffile:
        return Response(pdffile.read(), mimetype="application/pdf")
Dave Kliczbor's avatar
Dave Kliczbor committed
298
299

if __name__ == '__main__':
300
301
    app.debug = True
    app.run(host=config.listen, port=config.port)