From 2562084fcec1987bc1ce48a29efff36604eff0c0 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Tue, 19 Jul 2022 20:05:15 -0700 Subject: [PATCH] Experimental atom feed for photos, albums, search. --- etiquette/helpers.py | 23 +++++++ etiquette/objects.py | 67 +++++++++++++++++++ .../backend/endpoints/photo_endpoints.py | 15 +++++ 3 files changed, 105 insertions(+) diff --git a/etiquette/helpers.py b/etiquette/helpers.py index 83529a3..e99e745 100644 --- a/etiquette/helpers.py +++ b/etiquette/helpers.py @@ -2,6 +2,7 @@ This file provides functions which are used in various places throughout the codebase but don't deserve to be methods of any class. ''' +import bs4 import datetime import hashlib import mimetypes @@ -304,6 +305,28 @@ def is_xor(*args) -> bool: ''' return [bool(a) for a in args].count(True) == 1 +def make_atom_feed(objects, feed_title, feed_link, feed_id) -> bs4.BeautifulSoup: + soup = bs4.BeautifulSoup('', 'xml') + feed = soup.new_tag('feed') + soup.append(feed) + + title = soup.new_tag('title') + title.string = feed_title + feed.append(title) + + link = soup.new_tag('link') + link['href'] = feed_link + feed.append(link) + + id_element = soup.new_tag('id') + id_element.string = feed_id + feed.append(id_element) + + for obj in objects: + feed.append(obj.atomify()) + + return soup + def now(): ''' Return the current UTC datetime object. diff --git a/etiquette/objects.py b/etiquette/objects.py index 876c845..42a0efa 100644 --- a/etiquette/objects.py +++ b/etiquette/objects.py @@ -4,6 +4,7 @@ but are returned by the PDB accesses. ''' import abc import bcrypt +import bs4 import datetime import hashlib import os @@ -418,6 +419,39 @@ class Album(ObjectBase, GroupableMixin): for photo in photos: photo.add_tag(tag) + def atomify(self) -> bs4.BeautifulSoup: + soup = bs4.BeautifulSoup('', 'xml') + entry = soup.new_tag('entry') + soup.append(entry) + + id_element = soup.new_tag('id') + id_element.string = str(self.id) + entry.append(id_element) + + title = soup.new_tag('title') + title.string = self.display_name + entry.append(title) + + link = soup.new_tag('link') + link['rel'] = 'alternate' + link['type'] = 'text/html' + link['href'] = f'/album/{self.id}' + entry.append(link) + + published = soup.new_tag('published') + published.string = self.created.isoformat() + entry.append(published) + + content = soup.new_tag('content') + # content.string = bs4.CData(f'') + entry.append(content) + + typ = soup.new_tag('etiquette:type') + typ.string = 'album' + entry.append(typ) + + return soup + @decorators.required_feature('album.edit') @worms.atomic def delete(self, *, delete_children=False) -> None: @@ -937,6 +971,39 @@ class Photo(ObjectBase): return tag + def atomify(self) -> bs4.BeautifulSoup: + soup = bs4.BeautifulSoup('', 'xml') + entry = soup.new_tag('entry') + soup.append(entry) + + id_element = soup.new_tag('id') + id_element.string = str(self.id) + entry.append(id_element) + + title = soup.new_tag('title') + title.string = self.basename + entry.append(title) + + link = soup.new_tag('link') + link['rel'] = 'alternate' + link['type'] = 'text/html' + link['href'] = f'/photo/{self.id}' + entry.append(link) + + published = soup.new_tag('published') + published.string = self.created.isoformat() + entry.append(published) + + content = soup.new_tag('content') + content.string = bs4.CData(f'') + entry.append(content) + + typ = soup.new_tag('etiquette:type') + typ.string = 'photo' + entry.append(typ) + + return soup + @property def basename(self) -> str: return self.override_filename or self.real_path.basename diff --git a/frontends/etiquette_flask/backend/endpoints/photo_endpoints.py b/frontends/etiquette_flask/backend/endpoints/photo_endpoints.py index cbe29da..c13bf49 100644 --- a/frontends/etiquette_flask/backend/endpoints/photo_endpoints.py +++ b/frontends/etiquette_flask/backend/endpoints/photo_endpoints.py @@ -530,6 +530,21 @@ def get_search_html(): ) return response +@site.route('/search.atom') +def get_search_atom(): + search_results = get_search_core()['results'] + soup = etiquette.helpers.make_atom_feed( + search_results, + feed_id=request.query_string.decode('utf-8'), + feed_title='etiquette search', + feed_link=request.url.replace('/search.atom', '/search'), + ) + outgoing_headers = { + 'Content-Type': 'application/atom+xml; charset=utf-8', + } + response = flask.Response(str(soup), headers=outgoing_headers) + return response + @site.route('/search.json') def get_search_json(): search_results = get_search_core()