""" open/dulcinea/lib/ui/links.qpy """ from dulcinea.category import remove_ancestors, Category from dulcinea.links import LinkItem, get_lower_title, get_link_db from dulcinea.ui.attachment import AttachmentUI from dulcinea.ui.category import CategoryDirectory, CategoryEditDirectory from dulcinea.ui.category import hierarchy_header, hierarchy_footer from dulcinea.ui.form.select_widget import BigMultipleSelectWidget from dulcinea.ui.table import Table from dulcinea.util import format_text, html2txt from qp.fill.css import TextStyle from qp.fill.directory import Directory from qp.fill.form import Form from qp.fill.html import href from qp.fill.widget import StringWidget, TextWidget, CheckboxWidget from qp.lib.spec import require from qp.pub.common import get_user, redirect, complete_path from qpy import xml from types import FunctionType class LinkDirectory(CategoryDirectory): def __init__(self, format_link=None): CategoryDirectory.__init__(self, title='Links') require(format_link, (FunctionType, None)) self.format_link = format_link or _format_link def get_exports(self): yield ('', 'index', 'Links', 'View all links') yield ('search', 'search', 'Search Links', 'Search all links') yield ('goto', None, None, None) if get_user().is_admin(): yield ('new_link', 'new_link', 'New Link', 'Submit a new link') yield ('new', 'new', 'New Category', 'Create a new Category') def get_category_db(self): """() -> CategoryDatabase """ return get_link_db() def new_link:xml(self): return _link_form(title="New Link") def index:xml(self): links = sorted(get_link_db().get_active(), key=get_lower_title) title = "All Links: %d" % len(links or []) _show_links_index(links, self.format_link, title=title) def search:xml(self): links = sorted(get_link_db().get_active(), key=get_lower_title) _show_links_page(links, self._search_links(links), title='Search Links') def _search_links:xml(self, links): form = Form(use_tokens=False, method="get") form.add_string('keywords') form.add_submit('search', value="Search") form.render() '
' keyword = form.get('keywords') if keyword: keyword = keyword.lower() matches = [link for link in links if keyword in link.get_search_text()] else: matches = [] if keyword and not matches: 'No matches found for ' keyword else: table = Table() table.column(name='Title') table.column(categories='Categories') def format_categories:xml(link): '
'.join([ href('/links/%s/' % category.get_name(), category.get_label()) for category in sorted(link.get_categories(), key=Category.get_name)]) if matches: table.column(context='First Match') def format_context:xml(keyword, link): text = html2txt(link.get_text() or '') delta = 40 length = 80 pos = text.lower().find(keyword) if pos >= 0: start = max(0, pos - delta) sample = text[start:start + length] match_start = pos - start match_end = match_start + len(keyword) match = sample[match_start:match_end] sample[:match_start] '%s' % match sample[match_end:] for link in matches: table.row(name=href('/links/%s/' % link.get_key(), link.get_title(), name=link.get_title()[0].upper()), categories=format_categories(link), context=format_context(keyword, link)) else: for link in links: table.row(name=href('/links/%s/' % link.get_key(), link.get_title(), name=link.get_title()[0].upper()), categories=format_categories(link)) '' % table.render(css_class='shaded') def get_link_edit_directory(self): return LinkEditDirectory def get_links_in_category_directory(self): return LinksInCategoryDirectory def _q_lookup(self, component): if component == 'goto': return LinkGotoDirectory() try: key = int(component) except ValueError: key = None if key is not None: link = get_link_db().get(key) if link: return self.get_link_edit_directory()(link, self.format_link) category = get_link_db().get_category(component) if category: return self.get_links_in_category_directory()(category, self.format_link) class LinkGotoDirectory(Directory): """Work around search engine crawler bug. Do a permanent redirect to the target to avoid being a 302 redirect page hijacker. Ensure /links/goto is denied in robots.txt while the rest of /links namespace is crawlable. """ def _q_traverse(self, path): try: key = int(path[0]) except ValueError: key = None if key is not None: link = get_link_db().get(key) if link: redirect(link.get_link_url(), permanent=True) class LinksInCategoryDirectory(CategoryEditDirectory): def __init__(self, category, format_link): def _show_category:xml(categories, body, title=category.get_label()): _show_links_page(categories, body, title=title, selections=categories) CategoryEditDirectory.__init__(self, category, get_link_db(), decorate=_show_category) self.format_link = format_link def get_exports(self): yield ('', 'index', self.category.get_label(), None) if get_user().is_admin(): yield ('edit', 'edit', 'Edit Category', None) def index:xml(self): links = sorted(get_link_db().links_with_category(self.category), key=get_lower_title) title = "%s: %d" % (self.category.get_label(), len(links or [])) _show_links_index(links, self.format_link, selections=[self.category], title=title) def _q_lookup(self, component): redirect('../%s/' % component) class LinkEditDirectory(Directory): def __init__(self, link, format_link): self.link = link self.format_link = format_link def get_exports(self): yield ('', 'index', 'Link', self.link.get_title()) if get_user().is_admin(): yield ('edit', 'edit', 'Edit', 'Edit this link') yield ('delete', 'delete', 'Delete', 'Delete this link') attachment_count = len(self.link.get_attachments()) if attachment_count: yield ('file', None, 'Files %d' % attachment_count, '%d File(s) attached to link-%s' % (attachment_count, self.link.get_key())) else: yield ('file', None, 'Files', 'No Files attached to link-%s' % self.link.get_key()) def index:xml(self): selections=self.link.get_categories() _show_links_page([self.link], self.format_link(self.link, selections=selections), title="Link: %s" % self.link.get_title(), selections=selections) def edit:xml(self): _link_form(link=self.link, title="Edit Link: %s" % self.link.get_title(), selections=self.link.get_categories()) def delete:xml(self): _delete_link_form(self.link, title="Delete Link: %s" % self.link.get_title(), selections=self.link.get_categories()) def _q_lookup(self, component): if component == 'file': def _show_attachments:xml(link, body, title="Attached Files"): _show_links_page([link], body, title=title, selections=link.get_categories()) return AttachmentUI(self.link, decorate=_show_attachments, multiple=1) def _show_links_index:xml(links, format_link, title='Links', selections=None): formatted_links = [format_link(link, selections=selections) for link in links] _show_links_page(links, ''.join(formatted_links), title=title, selections=selections) def _show_links_page:xml(links, body, title='Links', selections=None): hierarchy_header(get_link_db().get_category('all'), selections=selections, show_sidebar=0, title=title, base_url=complete_path('/links/'), label='Link Categories') '' % body hierarchy_footer(title=title) def link_img_href:xml(link, width='', height='', style='', target='', querystring=''): if width: width = 'width="%s"' % width if height: height = 'height="%s"' % height if style: style = 'style="%s"' % style files = link.get_attached_files() if files: href("/links/goto/%s%s" % (link.get_key(), querystring), '' % ( link.get_local_url(), files[0].get_id(), width, height, link.get_title(), style)) def _format_link:xml(link, selections=None, show_actions=True): if link.is_approved() or get_user().is_admin(): '
' href("/links/goto/%s" % link.get_key(), link.get_title(), name=link.get_title()[0].upper()) if not link.is_approved(): ' ' '' href("%sedit" % link.get_local_url(), "(Unapproved)") '' if get_user().is_admin() and show_actions: ' ' format_action_links(link) '
' body = link_img_href(link) or '' body += format_text(link.get_text()) or '' if body: '
' body '
' '
' def format_action_links:xml(link): url = link.get_local_url() '' ' '.join([href(url, '[Categories]'), href('%sfile/' % url, '[Files]'), href('%sedit' % url, '[Edit]'), href('%sdelete' % url, '[Delete]')]) '' def _link_form(link=None, title='', selections=None): form = Form() form.add(StringWidget, 'title', title='Title', required=1, value=(link and link.get_title()) or None, size=51) form.add(StringWidget, 'url', title='URL', required=1, value=(link and link.get_link_url()) or None, size=51) form.add(TextWidget, 'text', title='Description', value=(link and link.get_text()) or None, cols=56, rows=15) form.add(StringWidget, 'email', title='Contact email', required=1, value=(link and link.get_email()) or None, size=51) form.add(BigMultipleSelectWidget, 'categories', title='Link Categories', value=(link and link.get_categories()) or None, required=1, options=[ (category, category.get_label(), category.get_name()) for category in get_link_db().get_category('all').expand().keys() if category.get_name() != 'all'], size=10, hint=('Please select one or two categories that best ' 'describe the area of relevance for this link')) form.add(CheckboxWidget, 'approved', title='Approved', value=(link and link.is_approved()) or None) form.add(CheckboxWidget, 'first_class', title='First Class', value=(link and link.is_first_class()) or None) if link is None: form.add_submit('save', 'Save') else: form.add_submit('update', 'Update') form.add_submit("cancel", "Cancel") if form.get('cancel'): redirect('.') if not form.get_submit() in ('save', 'update') or form.has_errors(): return _show_links_page([link], xml('
%s
') % form.render(), title=title, selections=selections) if link is None: link = LinkItem() get_link_db().add(link, 'active') else: link.set_timestamp() link.set_link_url(form.get('url')) link.set_title(form.get('title')) link.set_text(form.get('text')) link.set_email(form.get('email')) link.set_categories(remove_ancestors(form.get('categories'))) link.set_first_class(form.get('first_class')) approved = form.get('approved') if approved is not None: link.set_approved(approved) redirect('.') def _delete_link_form(link, title='', selections=None): form = Form() form.add_submit('delete', 'Delete') form.add_submit("cancel", "Cancel") submit = form.get_submit() path = '.' if submit == 'cancel': redirect(path) if not form.is_submitted() or form.has_errors(): body = xml("

Delete Link record: %s?

") % link.get_title() return _show_links_page( [link], body + form.render(), title=title, selections=selections) if submit == 'delete': get_link_db().remove(link) path = '..' redirect(path) def format_links_css:str(header_text_style=TextStyle( background='#3575D4', color='#fff', other_style='padding: 2px 0 2px 2ex', anchor_style='color: white; text-decoration: none', link_style='color: white; text-decoration: none', hover_style='text-decoration: underline', visited_style='color: white')): header_text_style.format_rules('dl.links', ['dt']) """ div.links_page { margin: 1em; } dl.links dd a { text-decoration: underline; } dl.links dt { clear: right; } dl.links img { float: right; margin-left: 1ex; margin-bottom: 1ex; margin-right: 1ex; } div.links_page span.match { background: yellow; } div.links_page td.context { font-size: smaller; } div.links_page td.categories { font-size: smaller; white-space: nowrap; } """