def save_post(post): was_draft = post.draft pub_str = request.form.get('published') if pub_str: post.published = mf2util.parse_dt(pub_str) start_str = request.form.get('start') if start_str: start = mf2util.parse_dt(start_str) if start: post.start = start post.start_utcoffset = start.utcoffset() end_str = request.form.get('end') if end_str: end = mf2util.parse_dt(end_str) if end: post.end = end post.end_utcoffset = end.utcoffset() now = datetime.datetime.utcnow() if not post.published or was_draft: post.published = now post.updated = now # populate the Post object and save it to the database, # redirect to the view post.title = request.form.get('title', '') post.content = request.form.get('content') post.draft = request.form.get('action') == 'save_draft' post.hidden = request.form.get('hidden', 'false') == 'true' venue_name = request.form.get('new_venue_name') venue_lat = request.form.get('new_venue_latitude') venue_lng = request.form.get('new_venue_longitude') if venue_name and venue_lat and venue_lng: venue = Venue() venue.name = venue_name venue.location = { 'latitude': float(venue_lat), 'longitude': float(venue_lng), } venue.update_slug('{}-{}'.format(venue_lat, venue_lng)) db.session.add(venue) db.session.commit() hooks.fire('venue-saved', venue, request.form) post.venue = venue else: venue_id = request.form.get('venue') if venue_id: post.venue = Venue.query.get(venue_id) lat = request.form.get('latitude') lon = request.form.get('longitude') if lat and lon: if post.location is None: post.location = {} post.location['latitude'] = float(lat) post.location['longitude'] = float(lon) loc_name = request.form.get('location_name') if loc_name is not None: post.location['name'] = loc_name else: post.location = None for url_attr, context_attr in (('in_reply_to', 'reply_contexts'), ('repost_of', 'repost_contexts'), ('like_of', 'like_contexts'), ('bookmark_of', 'bookmark_contexts')): url_str = request.form.get(url_attr) if url_str is not None: urls = util.multiline_string_to_list(url_str) setattr(post, url_attr, urls) # fetch contexts before generating a slug contexts.fetch_contexts(post) syndication = request.form.get('syndication') if syndication is not None: post.syndication = util.multiline_string_to_list(syndication) audience = request.form.get('audience') if audience is not None: post.audience = util.multiline_string_to_list(audience) tags = request.form.getlist('tags') if post.post_type != 'article' and post.content: # parse out hashtags as tag links from note-like posts tags += util.find_hashtags(post.content) tags = list(filter(None, map(util.normalize_tag, tags))) post.tags = [Tag.query.filter_by(name=tag).first() or Tag(tag) for tag in tags] slug = request.form.get('slug') if slug: post.slug = util.slugify(slug) elif not post.slug or was_draft: post.slug = post.generate_slug() # events should use their start date for permalinks path_date = post.start or post.published if post.draft: m = hashlib.md5() m.update(bytes(path_date.isoformat() + '|' + post.slug, 'utf-8')) post.path = 'drafts/{}'.format(m.hexdigest()) elif not post.path or was_draft: base_path = '{}/{:02d}/{}'.format( path_date.year, path_date.month, post.slug) # generate a unique path unique_path = base_path idx = 1 while Post.load_by_path(unique_path): unique_path = '{}-{}'.format(base_path, idx) idx += 1 post.path = unique_path # generate short path if not post.short_path: short_base = '{}/{}'.format( util.tag_for_post_type(post.post_type), util.base60_encode(util.date_to_ordinal(path_date))) short_paths = set( row[0] for row in db.session.query(Post.short_path).filter( Post.short_path.startswith(short_base)).all()) for idx in itertools.count(1): post.short_path = short_base + util.base60_encode(idx) if post.short_path not in short_paths: break infiles = request.files.getlist('files') + request.files.getlist('photo') current_app.logger.debug('infiles: %s', infiles) for infile in infiles: if infile and infile.filename: current_app.logger.debug('receiving uploaded file %s', infile) attachment = create_attachment_from_file(post, infile) os.makedirs(os.path.dirname(attachment.disk_path), exist_ok=True) infile.save(attachment.disk_path) post.attachments.append(attachment) # pre-render the post html html = util.markdown_filter(post.content, img_path=post.get_image_path()) html = util.autolink(html) if post.post_type == 'article': html = util.process_people_to_microcards(html) else: html = util.process_people_to_at_names(html) post.content_html = html if not post.id: db.session.add(post) db.session.commit() current_app.logger.debug('saved post %d %s', post.id, post.permalink) redirect_url = post.permalink hooks.fire('post-saved', post, request.form) return redirect(redirect_url)
def draft_by_hash(hash): post = Post.load_by_path('drafts/{}'.format(hash)) return render_post(post)
def find_target_post(target_url): current_app.logger.debug("looking for target post at %s", target_url) # follow redirects if necessary redirect_url = urllib.request.urlopen(target_url).geturl() if redirect_url and redirect_url != target_url: current_app.logger.debug("followed redirection to %s", redirect_url) target_url = redirect_url parsed_url = urllib.parse.urlparse(target_url) if not parsed_url: current_app.logger.warn("Could not parse target_url of received webmention: %s", target_url) return None try: # FIXME this is a less-than-perfect fix for hosting from a # subdirectory. The url_map may have some clever work-around. parsed_site_root = urllib.parse.urlparse(get_settings().site_url) site_prefix = parsed_site_root.path if site_prefix.endswith("/"): site_prefix = site_prefix[:-1] if not parsed_url.path.startswith(parsed_site_root.path): raise NotFound urls = current_app.url_map.bind(get_settings().site_url) path = parsed_url.path[len(site_prefix) :] current_app.logger.debug("target path with no prefix %s", path) endpoint, args = urls.match(path) current_app.logger.debug("found match for target url %r: %r", endpoint, args) except NotFound: current_app.logger.warn("Webmention could not find target for %s", parsed_url.path) return None post = None if endpoint == "views.post_by_path": year = args.get("year") month = args.get("month") slug = args.get("slug") post = Post.load_by_path("{}/{:02d}/{}".format(year, month, slug)) elif endpoint == "views.post_by_date": post_type = args.get("post_type") year = args.get("year") month = args.get("month") day = args.get("day") index = args.get("index") post = Post.load_by_date(post_type, year, month, day, index) elif endpoint == "views.post_by_old_date": post_type = args.get("post_type") yymmdd = args.get("yymmdd") year = int("20" + yymmdd[0:2]) month = int(yymmdd[2:4]) day = int(yymmdd[4:6]) post = Post.load_by_date(post_type, year, month, day, index) elif endpoint == "views.post_by_id": dbid = args.get("dbid") post = Post.load_by_id(dbid) if not post: current_app.logger.warn("Webmention target points to unknown post: {}".format(args)), return post
def draft_attachment(hash, filename): post = Post.load_by_path('drafts/{}'.format(hash)) return render_attachment(post, filename)
def post_by_path(year, month, slug): post = Post.load_by_path('{}/{:02d}/{}'.format(year, month, slug)) return render_post(post)
def post_attachment(year, month, slug, filename): post = Post.load_by_path('{}/{:02d}/{}'.format(year, month, slug)) return render_attachment(post, filename)
def find_target_post(target_url): current_app.logger.debug("looking for target post at %s", target_url) # follow redirects if necessary redirect_url = urllib.request.urlopen(target_url).geturl() if redirect_url and redirect_url != target_url: current_app.logger.debug("followed redirection to %s", redirect_url) target_url = redirect_url parsed_url = urllib.parse.urlparse(target_url) if not parsed_url: current_app.logger.warn( "Could not parse target_url of received webmention: %s", target_url) return None try: # FIXME this is a less-than-perfect fix for hosting from a # subdirectory. The url_map may have some clever work-around. parsed_site_root = urllib.parse.urlparse(get_settings().site_url) site_prefix = parsed_site_root.path if site_prefix.endswith('/'): site_prefix = site_prefix[:-1] if not parsed_url.path.startswith(parsed_site_root.path): raise NotFound urls = current_app.url_map.bind(get_settings().site_url) path = parsed_url.path[len(site_prefix):] current_app.logger.debug('target path with no prefix %s', path) endpoint, args = urls.match(path) current_app.logger.debug('found match for target url %r: %r', endpoint, args) except NotFound: current_app.logger.warn('Webmention could not find target for %s', parsed_url.path) return None post = None if endpoint == 'views.post_by_path': year = args.get('year') month = args.get('month') slug = args.get('slug') post = Post.load_by_path('{}/{:02d}/{}'.format(year, month, slug)) elif endpoint == 'views.post_by_date': post_type = args.get('post_type') year = args.get('year') month = args.get('month') day = args.get('day') index = args.get('index') post = Post.load_by_date(post_type, year, month, day, index) elif endpoint == 'views.post_by_old_date': post_type = args.get('post_type') yymmdd = args.get('yymmdd') year = int('20' + yymmdd[0:2]) month = int(yymmdd[2:4]) day = int(yymmdd[4:6]) post = Post.load_by_date(post_type, year, month, day, index) elif endpoint == 'views.post_by_id': dbid = args.get('dbid') post = Post.load_by_id(dbid) if not post: current_app.logger.warn( "Webmention target points to unknown post: {}".format(args)), return post
def save_post(post): was_draft = post.draft pub_str = request.form.get('published') if pub_str: post.published = mf2util.parse_dt(pub_str) if post.published.tzinfo: post.published = post.published.astimezone(datetime.timezone.utc)\ .replace(tzinfo=None) if 'post_type' in request.form: post.post_type = request.form.get('post_type') start_str = request.form.get('start') if start_str: start = mf2util.parse_dt(start_str) if start: post.start = start post.start_utcoffset = start.utcoffset() end_str = request.form.get('end') if end_str: end = mf2util.parse_dt(end_str) if end: post.end = end post.end_utcoffset = end.utcoffset() now = datetime.datetime.utcnow() if not post.published or was_draft: post.published = now post.updated = now # populate the Post object and save it to the database, # redirect to the view post.title = request.form.get('title', '') post.content = request.form.get('content') post.draft = request.form.get('action') == 'save_draft' post.hidden = request.form.get('hidden', 'false') == 'true' post.friends_only = request.form.get('friends_only', 'false') == 'true' venue_name = request.form.get('new_venue_name') venue_lat = request.form.get('new_venue_latitude') venue_lng = request.form.get('new_venue_longitude') if venue_name and venue_lat and venue_lng: venue = Venue() venue.name = venue_name venue.location = { 'latitude': float(venue_lat), 'longitude': float(venue_lng), } venue.update_slug('{}-{}'.format(venue_lat, venue_lng)) db.session.add(venue) db.session.commit() hooks.fire('venue-saved', venue, request.form) post.venue = venue else: venue_id = request.form.get('venue') if venue_id: post.venue = Venue.query.get(venue_id) lat = request.form.get('latitude') lon = request.form.get('longitude') if lat and lon: if post.location is None: post.location = {} post.location['latitude'] = float(lat) post.location['longitude'] = float(lon) loc_name = request.form.get('location_name') if loc_name is not None: post.location['name'] = loc_name else: post.location = None for url_attr, context_attr in (('in_reply_to', 'reply_contexts'), ('repost_of', 'repost_contexts'), ('like_of', 'like_contexts'), ('bookmark_of', 'bookmark_contexts')): url_str = request.form.get(url_attr) if url_str is not None: urls = util.multiline_string_to_list(url_str) setattr(post, url_attr, urls) # fetch contexts before generating a slug contexts.fetch_contexts(post) if 'item-name' in request.form: post.item = util.trim_nulls({ 'name': request.form.get('item-name'), 'author': request.form.get('item-author'), 'photo': request.form.get('item-photo'), }) if 'rating' in request.form: rating = request.form.get('rating') post.rating = int(rating) if rating else None syndication = request.form.get('syndication') if syndication is not None: post.syndication = util.multiline_string_to_list(syndication) audience = request.form.get('audience') if audience is not None: post.audience = util.multiline_string_to_list(audience) tags = request.form.getlist('tags') if post.post_type != 'article' and post.content: # parse out hashtags as tag links from note-like posts tags += util.find_hashtags(post.content) tags = list(filter(None, map(util.normalize_tag, tags))) post.tags = [Tag.query.filter_by(name=tag).first() or Tag(tag) for tag in tags] post.people = [] people = request.form.getlist('people') for person in people: nick = Nick.query.filter_by(name=person).first() if nick: post.people.append(nick.contact) slug = request.form.get('slug') if slug: post.slug = util.slugify(slug) elif not post.slug or was_draft: post.slug = post.generate_slug() # events should use their start date for permalinks path_date = post.start or post.published if post.draft: m = hashlib.md5() m.update(bytes(path_date.isoformat() + '|' + post.slug, 'utf-8')) post.path = 'drafts/{}'.format(m.hexdigest()) elif not post.path or was_draft: base_path = '{}/{:02d}/{}'.format( path_date.year, path_date.month, post.slug) # generate a unique path unique_path = base_path idx = 1 while Post.load_by_path(unique_path): unique_path = '{}-{}'.format(base_path, idx) idx += 1 post.path = unique_path # generate short path if not post.short_path: short_base = '{}/{}'.format( util.tag_for_post_type(post.post_type), util.base60_encode(util.date_to_ordinal(path_date))) short_paths = set( row[0] for row in db.session.query(Post.short_path).filter( Post.short_path.startswith(short_base)).all()) for idx in itertools.count(1): post.short_path = short_base + util.base60_encode(idx) if post.short_path not in short_paths: break infiles = request.files.getlist('files') + request.files.getlist('photo') current_app.logger.debug('infiles: %s', infiles) for infile in infiles: if infile and infile.filename: current_app.logger.debug('receiving uploaded file %s', infile) attachment = create_attachment_from_file(post, infile) os.makedirs(os.path.dirname(attachment.disk_path), exist_ok=True) infile.save(attachment.disk_path) post.attachments.append(attachment) photo_url = request.form.get('photo') if photo_url: current_app.logger.debug('downloading photo from url %s', photo_url) temp_filename, headers = urllib.request.urlretrieve(photo_url) content_type = headers.get('content-type', '') mimetype = content_type and content_type.split(';')[0].strip() filename = os.path.basename(urllib.parse.urlparse(photo_url).path) attachment = create_attachment(post, filename, mimetype) os.makedirs(os.path.dirname(attachment.disk_path), exist_ok=True) shutil.copyfile(temp_filename, attachment.disk_path) urllib.request.urlcleanup() post.attachments.append(attachment) # pre-render the post html html = util.markdown_filter(post.content, img_path=post.get_image_path()) html = util.autolink(html) if post.post_type == 'article': html = util.process_people_to_microcards(html) else: html = util.process_people_to_at_names(html) post.content_html = html if not post.id: db.session.add(post) db.session.commit() current_app.logger.debug('saved post %d %s', post.id, post.permalink) redirect_url = post.permalink hooks.fire('post-saved', post, request.form) return redirect(redirect_url)