comparison piecrust/admin/views/micropub.py @ 895:accfe8fc8440

admin: Add a Micropub endpoint.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 02 Jul 2017 22:23:12 -0700
parents
children d65838abbd90
comparison
equal deleted inserted replaced
894:ca357249a321 895:accfe8fc8440
1 import re
2 import os
3 import os.path
4 import logging
5 import datetime
6 from werkzeug.utils import secure_filename
7 from flask import g, request, abort, Response
8 from flask_indieauth import requires_indieauth
9 from ..blueprint import foodtruck_bp
10 from piecrust.page import Page
11
12
13 logger = logging.getLogger(__name__)
14
15 re_unsafe_asset_char = re.compile('[^a-zA-Z0-9_]')
16
17
18 @foodtruck_bp.route('/micropub', methods=['POST'])
19 @requires_indieauth
20 def micropub():
21 post_type = request.form.get('h')
22
23 if post_type == 'entry':
24 uri = _create_hentry()
25 _run_publisher()
26 return _get_location_response(uri)
27
28 logger.debug("Unknown or unsupported update type.")
29 logger.debug(request.form)
30 abort(400)
31
32
33 def _run_publisher():
34 pcapp = g.site.piecrust_app
35 target = pcapp.config.get('micropub/publish_target', 'default')
36 logger.debug("Running pushing target '%s'." % target)
37 g.site.publish(target)
38
39
40 def _get_location_response(uri):
41 logger.debug("Redirecting to: %s" % uri)
42 r = Response()
43 r.status_code = 201
44 r.headers.add('Location', uri)
45 return r
46
47
48 def _create_hentry():
49 f = request.form
50 summary = f.get('summary')
51 categories = f.getlist('category[]')
52 location = f.get('location')
53 reply_to = f.get('in-reply-to')
54 status = f.get('post-status')
55 # pubdate = f.get('published', 'now')
56
57 # Figure out the title of the post.
58 name = f.get('name')
59 if not name:
60 name = f.get('name[]')
61
62 # Figure out the contents of the post.
63 post_format = None
64 content = f.get('content')
65 if not content:
66 content = f.get('content[]')
67 if not content:
68 content = f.get('content[html]')
69 post_format = 'none'
70
71 if not content:
72 logger.error("No content specified!")
73 logger.error(dict(request.form))
74 abort(400)
75
76 # TODO: setting to conserve Windows-type line endings?
77 content = content.replace('\r\n', '\n')
78 if summary:
79 summary = summary.replace('\r\n', '\n')
80
81 # Figure out the slug of the post.
82 now = datetime.datetime.now()
83 slug = f.get('slug')
84 if not slug:
85 slug = f.get('mp-slug')
86 if not slug:
87 slug = '%02d%02d%02d' % (now.hour, now.minute, now.second)
88
89 # Get the media to attach to the post.
90 photo_urls = None
91 if 'photo' in f:
92 photo_urls = [f['photo']]
93 elif 'photo[]' in f:
94 photo_urls = f.getlist('photo[]')
95
96 photos = None
97 if 'photo' in request.files:
98 photos = [request.files['photo']]
99 elif 'photo[]' in request.files:
100 photos = request.files.getlist('photo[]')
101
102 # Create the post in the correct content source.
103 pcapp = g.site.piecrust_app
104 source_name = pcapp.config.get('micropub/source', 'posts')
105 source = pcapp.getSource(source_name)
106
107 metadata = {
108 'date': now,
109 'slug': slug
110 }
111 logger.debug("Creating item with metadata: %s" % metadata)
112 content_item = source.createContent(metadata)
113 if content_item is None:
114 logger.error("Can't create item for: %s" % metadata)
115 abort(500)
116
117 # TODO: add proper APIs for creating related assets.
118 photo_names = None
119 if photos:
120 photo_dir, _ = os.path.splitext(content_item.spec)
121 photo_dir += '-assets'
122 if not os.path.exists(photo_dir):
123 os.makedirs(photo_dir)
124
125 photo_names = []
126 for photo in photos:
127 if not photo or not photo.filename:
128 logger.warning("Got empty photo in request files... skipping.")
129 continue
130
131 fn = secure_filename(photo.filename)
132 fn = re_unsafe_asset_char.sub('_', fn)
133 photo_path = os.path.join(photo_dir, fn)
134 logger.info("Uploading file to: %s" % photo_path)
135 photo.save(photo_path)
136
137 fn_no_ext, _ = os.path.splitext(fn)
138 photo_names.append(fn_no_ext)
139
140 logger.debug("Writing to item: %s" % content_item.spec)
141 with source.openItem(content_item, mode='w') as fp:
142 fp.write('---\n')
143 if name:
144 fp.write('title: "%s"\n' % name)
145 if categories:
146 fp.write('tags: [%s]\n' % ','.join(categories))
147 if location:
148 fp.write('location: %s\n' % location)
149 if reply_to:
150 fp.write('reply_to: "%s"\n' % reply_to)
151 if status:
152 fp.write('status: %s\n' % status)
153 if post_format:
154 fp.write('format: %s\n' % post_format)
155 fp.write('time: %02d:%02d:%02d\n' % (now.hour, now.minute, now.second))
156 fp.write('---\n')
157
158 if summary:
159 fp.write(summary)
160 fp.write('\n')
161 fp.write('<!--break-->\n\n')
162 fp.write(content)
163
164 if photo_urls:
165 fp.write('\n\n')
166 for pu in photo_urls:
167 fp.write('<img src="{{assets.%s}}" alt=""/>\n\n' % pu)
168
169 if photo_names:
170 fp.write('\n\n')
171 for pn in photo_names:
172 fp.write('<img src="{{assets.%s}}" alt="%s"/>\n\n' %
173 (pn, pn))
174
175 route = pcapp.getSourceRoute(source.name)
176 if route is None:
177 logger.error("Can't find route for source: %s" % source.name)
178 abort(500)
179
180 page = Page(source, content_item)
181 uri = page.getUri()
182 return uri
183