view piecrust/admin/views/micropub.py @ 950:18d6ac20a132

admin: Fix old API calls and bugs when creating new pages.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 05 Oct 2017 00:27:57 -0700
parents accfe8fc8440
children d65838abbd90
line wrap: on
line source

import re
import os
import os.path
import logging
import datetime
from werkzeug.utils import secure_filename
from flask import g, request, abort, Response
from flask_indieauth import requires_indieauth
from ..blueprint import foodtruck_bp
from piecrust.page import Page


logger = logging.getLogger(__name__)

re_unsafe_asset_char = re.compile('[^a-zA-Z0-9_]')


@foodtruck_bp.route('/micropub', methods=['POST'])
@requires_indieauth
def micropub():
    post_type = request.form.get('h')

    if post_type == 'entry':
        uri = _create_hentry()
        _run_publisher()
        return _get_location_response(uri)

    logger.debug("Unknown or unsupported update type.")
    logger.debug(request.form)
    abort(400)


def _run_publisher():
    pcapp = g.site.piecrust_app
    target = pcapp.config.get('micropub/publish_target', 'default')
    logger.debug("Running pushing target '%s'." % target)
    g.site.publish(target)


def _get_location_response(uri):
    logger.debug("Redirecting to: %s" % uri)
    r = Response()
    r.status_code = 201
    r.headers.add('Location', uri)
    return r


def _create_hentry():
    f = request.form
    summary = f.get('summary')
    categories = f.getlist('category[]')
    location = f.get('location')
    reply_to = f.get('in-reply-to')
    status = f.get('post-status')
    # pubdate = f.get('published', 'now')

    # Figure out the title of the post.
    name = f.get('name')
    if not name:
        name = f.get('name[]')

    # Figure out the contents of the post.
    post_format = None
    content = f.get('content')
    if not content:
        content = f.get('content[]')
    if not content:
        content = f.get('content[html]')
        post_format = 'none'

    if not content:
        logger.error("No content specified!")
        logger.error(dict(request.form))
        abort(400)

    # TODO: setting to conserve Windows-type line endings?
    content = content.replace('\r\n', '\n')
    if summary:
        summary = summary.replace('\r\n', '\n')

    # Figure out the slug of the post.
    now = datetime.datetime.now()
    slug = f.get('slug')
    if not slug:
        slug = f.get('mp-slug')
    if not slug:
        slug = '%02d%02d%02d' % (now.hour, now.minute, now.second)

    # Get the media to attach to the post.
    photo_urls = None
    if 'photo' in f:
        photo_urls = [f['photo']]
    elif 'photo[]' in f:
        photo_urls = f.getlist('photo[]')

    photos = None
    if 'photo' in request.files:
        photos = [request.files['photo']]
    elif 'photo[]' in request.files:
        photos = request.files.getlist('photo[]')

    # Create the post in the correct content source.
    pcapp = g.site.piecrust_app
    source_name = pcapp.config.get('micropub/source', 'posts')
    source = pcapp.getSource(source_name)

    metadata = {
        'date': now,
        'slug': slug
    }
    logger.debug("Creating item with metadata: %s" % metadata)
    content_item = source.createContent(metadata)
    if content_item is None:
        logger.error("Can't create item for: %s" % metadata)
        abort(500)

    # TODO: add proper APIs for creating related assets.
    photo_names = None
    if photos:
        photo_dir, _ = os.path.splitext(content_item.spec)
        photo_dir += '-assets'
        if not os.path.exists(photo_dir):
            os.makedirs(photo_dir)

        photo_names = []
        for photo in photos:
            if not photo or not photo.filename:
                logger.warning("Got empty photo in request files... skipping.")
                continue

            fn = secure_filename(photo.filename)
            fn = re_unsafe_asset_char.sub('_', fn)
            photo_path = os.path.join(photo_dir, fn)
            logger.info("Uploading file to: %s" % photo_path)
            photo.save(photo_path)

            fn_no_ext, _ = os.path.splitext(fn)
            photo_names.append(fn_no_ext)

    logger.debug("Writing to item: %s" % content_item.spec)
    with source.openItem(content_item, mode='w') as fp:
        fp.write('---\n')
        if name:
            fp.write('title: "%s"\n' % name)
        if categories:
            fp.write('tags: [%s]\n' % ','.join(categories))
        if location:
            fp.write('location: %s\n' % location)
        if reply_to:
            fp.write('reply_to: "%s"\n' % reply_to)
        if status:
            fp.write('status: %s\n' % status)
        if post_format:
            fp.write('format: %s\n' % post_format)
        fp.write('time: %02d:%02d:%02d\n' % (now.hour, now.minute, now.second))
        fp.write('---\n')

        if summary:
            fp.write(summary)
            fp.write('\n')
            fp.write('<!--break-->\n\n')
        fp.write(content)

        if photo_urls:
            fp.write('\n\n')
            for pu in photo_urls:
                fp.write('<img src="{{assets.%s}}" alt=""/>\n\n' % pu)

        if photo_names:
            fp.write('\n\n')
            for pn in photo_names:
                fp.write('<img src="{{assets.%s}}" alt="%s"/>\n\n' %
                         (pn, pn))

    route = pcapp.getSourceRoute(source.name)
    if route is None:
        logger.error("Can't find route for source: %s" % source.name)
        abort(500)

    page = Page(source, content_item)
    uri = page.getUri()
    return uri