def _save_ticket_changes(self, req, selected_tickets, new_values, comment, action): """Save changes to tickets.""" valid = True for manipulator in self.ticket_manipulators: if hasattr(manipulator, 'validate_comment'): for message in manipulator.validate_comment(req, comment): valid = False add_warning(req, tag_("The ticket %(field)s is invalid: " "%(message)s", field=tag.strong(_('comment')), message=message)) tickets = [] for id_ in selected_tickets: t = Ticket(self.env, id_) values = self._get_updated_ticket_values(req, t, new_values) for ctlr in self._get_action_controllers(req, t, action): values.update(ctlr.get_ticket_changes(req, t, action)) t.populate(values) for manipulator in self.ticket_manipulators: for field, message in manipulator.validate_ticket(req, t): valid = False if field: add_warning(req, tag_("The ticket field %(field)s is " "invalid: %(message)s", field=tag.strong(field), message=message)) else: add_warning(req, message) tickets.append(t) if not valid: return when = datetime_now(utc) with self.env.db_transaction: for t in tickets: t.save_changes(req.authname, comment, when=when) for ctlr in self._get_action_controllers(req, t, action): ctlr.apply_action_side_effects(req, t, action) event = BatchTicketChangeEvent(selected_tickets, when, req.authname, comment, new_values, action) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error("Failure sending notification on ticket batch" "change: %s", exception_to_unicode(e)) add_warning(req, tag_("The changes have been saved, but an error " "occurred while sending notifications: " "%(message)s", message=to_unicode(e)))
def generate_prefix(prefix): intertrac = intertracs[prefix] if isinstance(intertrac, basestring): yield tag.tr(tag.td(tag.strong(prefix)), tag.td(tag_("Alias for %(name)s", name=tag.strong(intertrac)))) else: url = intertrac.get('url', '') if url: title = intertrac.get('title', url) yield tag.tr(tag.td(tag.a(tag.strong(prefix), href=url + '/timeline')), tag.td(tag.a(title, href=url)))
def render_group(group): return tag.ul( tag.li( tag(tag.strong(elt[0].strip('/')), render_group(elt[1]) ) if isinstance(elt, tuple) else tag. a(wiki.format_page_name(omitprefix(elt)), href=formatter.href.wiki(elt))) for elt in group)
def _validate(self, req, page): valid = True # Validate page size if len(req.args.get('text', '')) > self.max_size: add_warning( req, _( "The wiki page is too long (must be less " "than %(num)s characters)", num=self.max_size)) valid = False # Give the manipulators a pass at post-processing the page for manipulator in self.page_manipulators: for field, message in manipulator.validate_wiki_page(req, page): valid = False if field: add_warning( req, tag_( "The Wiki page field %(field)s" " is invalid: %(message)s", field=tag.strong(field), message=message)) else: add_warning( req, tag_("Invalid Wiki page: %(message)s", message=message)) return valid
def test_find_element_with_tag(self): frag = tag(tag.p('Paragraph with a ', tag.a('link', href='http://www.edgewall.org'), ' and some ', tag.strong('strong text'))) self.assertIsNotNone(find_element(frag, tag='p')) self.assertIsNotNone(find_element(frag, tag='a')) self.assertIsNotNone(find_element(frag, tag='strong')) self.assertIsNone(find_element(frag, tag='input')) self.assertIsNone(find_element(frag, tag='textarea'))
def _error_div(self, msg): """Display msg in an error box, using Trac style.""" self.log.error(msg) if isinstance(msg, str): msg = tag.pre(escape(msg)) return tag.div(tag.strong( _("Graphviz macro processor has detected an error. " "Please fix the problem before continuing.")), msg, class_="system-message")
def _render_source(self, context, lines, annotations): from trac.web.chrome import add_warning annotators, labels, titles = {}, {}, {} for annotator in self.annotators: atype, alabel, atitle = annotator.get_annotation_type() if atype in annotations: labels[atype] = alabel titles[atype] = atitle annotators[atype] = annotator annotations = [a for a in annotations if a in annotators] if isinstance(lines, unicode): lines = lines.splitlines(True) # elif isinstance(lines, list): # pass # assume these are lines already annotator_datas = [] for a in annotations: annotator = annotators[a] try: data = (annotator, annotator.get_annotation_data(context)) except TracError as e: self.log.warning("Can't use annotator '%s': %s", a, e) add_warning( context.req, tag.strong( tag_("Can't use %(annotator)s annotator: %(error)s", annotator=tag.em(a), error=tag.pre(e)))) data = None, None annotator_datas.append(data) def _head_row(): return tag.tr([ tag.th(labels[a], class_=a, title=titles[a]) for a in annotations ] + [tag.th(u'\xa0', class_='content')]) def _body_rows(): for idx, line in enumerate(lines): row = tag.tr() for annotator, data in annotator_datas: if annotator: annotator.annotate_row(context, row, idx + 1, line, data) else: row.append(tag.td()) row.append(tag.td(line)) yield row return tag.table(class_='code')(tag.thead(_head_row()), tag.tbody(_body_rows()))
def expand_macro(self, formatter, name, content): from trac.wiki.formatter import system_message content = content.strip() if content else '' name_filter = content.strip('*') def get_macro_descr(): for macro_provider in formatter.wiki.macro_providers: names = list(macro_provider.get_macros() or []) if name_filter and not any( name.startswith(name_filter) for name in names): continue try: name_descriptions = [ (name, macro_provider.get_macro_description(name)) for name in names ] except Exception as e: yield system_message( _("Error: Can't get description for macro %(name)s", name=names[0]), e), names else: for descr, pairs in groupby(name_descriptions, key=lambda p: p[1]): if descr: if isinstance(descr, (tuple, list)): descr = dgettext(descr[0], to_unicode(descr[1])) \ if descr[1] else '' else: descr = to_unicode(descr) or '' if content == '*': descr = format_to_oneliner(self.env, formatter.context, descr, shorten=True) else: descr = format_to_html(self.env, formatter.context, descr) yield descr, [name for name, descr in pairs] return tag.div(class_='trac-macrolist')( (tag.h3(tag.code('[[', names[0], ']]'), id='%s-macro' % names[0]), len(names) > 1 and tag.p( tag.strong(_("Aliases:")), [tag.code(' [[', alias, ']]') for alias in names[1:]]) or None, description or tag.em(_("Sorry, no documentation found"))) for description, names in sorted(get_macro_descr(), key=lambda item: item[1][0]))
def create_wiki_page(self, name=None, content=None, comment=None): """Creates a wiki page, with a random unique CamelCase name if none is provided, random content if none is provided and a random comment if none is provided. Returns the name of the wiki page. """ if name is None: name = random_unique_camel() if content is None: content = random_page() self.go_to_wiki(name) tc.find("The page[ \n]+%s[ \n]+does not exist." % tag.strong(name)) self.edit_wiki_page(name, content, comment) # verify the event shows up in the timeline self.go_to_timeline() tc.formvalue('prefs', 'wiki', True) tc.submit() tc.find(name + ".*created") self.go_to_wiki(name) return name
def _provider_failure(self, exc, req, ep, current_filters, all_filters): """Raise a TracError exception explaining the failure of a provider. At the same time, the message will contain a link to the timeline without the filters corresponding to the guilty event provider `ep`. """ self.log.error("Timeline event provider failed: %s", exception_to_unicode(exc, traceback=True)) ep_kinds = {f[0]: f[1] for f in ep.get_timeline_filters(req) or []} ep_filters = set(ep_kinds.keys()) current_filters = set(current_filters) other_filters = set(current_filters) - ep_filters if not other_filters: other_filters = set(all_filters) - ep_filters args = [(a, req.args.get(a)) for a in ('from', 'format', 'max', 'daysback')] href = req.href.timeline(args + [(f, 'on') for f in other_filters]) # TRANSLATOR: ...want to see the 'other kinds of events' from... (link) other_events = tag.a(_('other kinds of events'), href=href) raise TracError( tag( tag.p(tag_( "Event provider %(name)s failed for filters " "%(kinds)s: ", name=tag.code(ep.__class__.__name__), kinds=', '.join('"%s"' % ep_kinds[f] for f in current_filters & ep_filters)), tag.strong(exception_to_unicode(exc)), class_='message'), tag.p( tag_( "You may want to see the %(other_events)s from the " "Timeline or notify your Trac administrator about the " "error (detailed information was written to the log).", other_events=other_events))))
def runTest(self): """Test for simple wiki rename""" pagename = self._tester.create_wiki_page() attachment = self._tester.attach_file_to_wiki(pagename) base_url = self._tester.url page_url = base_url + "/wiki/" + pagename def click_rename(): tc.formvalue('rename', 'action', 'rename') tc.submit() tc.url(page_url + r'\?action=rename') tc.find("New name:") tc.go(page_url) tc.find("Rename page") click_rename() # attempt to give an empty new name tc.formvalue('rename-form', 'new_name', '') tc.submit('submit') tc.url(page_url) tc.find("A new name is mandatory for a rename") # attempt to rename the page to an invalid page name tc.formvalue('rename-form', 'new_name', '../WikiStart') tc.submit('submit') tc.url(page_url) tc.find("The new name is invalid") # attempt to rename the page to the current page name tc.formvalue('rename-form', 'new_name', pagename) tc.submit('submit') tc.url(page_url) tc.find("The new name must be different from the old name") # attempt to rename the page to an existing page name tc.formvalue('rename-form', 'new_name', 'WikiStart') tc.submit('submit') tc.url(page_url) tc.find("The page WikiStart already exists") # correct rename to new page name (old page replaced by a redirection) tc.go(page_url) click_rename() newpagename = pagename + 'Renamed' tc.formvalue('rename-form', 'new_name', newpagename) tc.formvalue('rename-form', 'redirect', True) tc.submit('submit') # check redirection page tc.url(page_url) tc.find("See.*/wiki/" + newpagename) tc.find("The page %s has been renamed to %s." % (pagename, newpagename)) tc.find("The page %s has been recreated with a redirect to %s." % (pagename, newpagename)) # check whether attachment exists on the new page but not on old page tc.go(base_url + '/attachment/wiki/' + newpagename + '/' + attachment) tc.notfind("Error: Invalid Attachment") tc.go(base_url + '/attachment/wiki/' + pagename + '/' + attachment) tc.find("Error: Invalid Attachment") # rename again to another new page name (this time, no redirection) tc.go(page_url) click_rename() newpagename = pagename + 'RenamedAgain' tc.formvalue('rename-form', 'new_name', newpagename) tc.formvalue('rename-form', 'redirect', False) tc.submit('submit') tc.url(base_url + "/wiki/" + newpagename) tc.find("The page %s has been renamed to %s." % (pagename, newpagename)) # this time, the original page is gone tc.go(page_url) tc.url(page_url) tc.find("The page[ \n]+%s[ \n]+does not exist" % tag.strong(pagename))
def render_property_diff(self, name, old_context, old_props, new_context, new_props, options): # Build 5 columns table showing modifications on merge sources # || source || added || removed || added (ni) || removed (ni) || # || source || removed || rm = RepositoryManager(self.env) repos = rm.get_repository(old_context.resource.parent.id) def parse_sources(props): sources = {} value = props[name] lines = value.splitlines() if name == 'svn:mergeinfo' \ else value.split() for line in lines: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources old_sources = parse_sources(old_props) new_sources = parse_sources(new_props) # Go through new sources, detect modified ones or added ones blocked = name.endswith('blocked') added_label = [_("merged: "), _("blocked: ")][blocked] removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] added_ni_label = _("marked as non-inheritable: ") removed_ni_label = _("unmarked as non-inheritable: ") sources = [] changed_revs = {} changed_nodes = [] for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): new_spath = spath not in old_sources if new_spath: old_revs = old_revs_ni = set() else: old_revs, old_revs_ni = old_sources.pop(spath) added = new_revs - old_revs removed = old_revs - new_revs # unless new revisions differ from old revisions if not added and not removed: continue added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni revs = sorted(added | removed | added_ni | removed_ni) try: node = repos.get_node(spath, revs[-1]) changed_nodes.append((node, revs[0])) except NoSuchNode: pass sources.append( (spath, new_spath, added, removed, added_ni, removed_ni)) if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) def revs_link(revs, context): if revs: revs = to_ranges(revs) return _get_revs_link(revs.replace(',', u',\u200b'), context, spath, revs) modified_sources = [] for spath, new_spath, added, removed, added_ni, removed_ni in sources: if spath in changed_revs: revs = set(changed_revs[spath]) added &= revs removed &= revs added_ni &= revs removed_ni &= revs if added or removed: if new_spath: status = _(" (added)") else: status = None modified_sources.append( (spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), removed and tag(removed_label, revs_link(removed, old_context)), added_ni and tag(added_ni_label, revs_link(added_ni, new_context)), removed_ni and tag(removed_ni_label, revs_link(removed_ni, old_context)))) # Go through remaining old sources, those were deleted removed_sources = [] for spath, old_revs in old_sources.iteritems(): removed_sources.append( (spath, _get_source_link(spath, old_context))) if modified_sources or removed_sources: modified_sources.sort() removed_sources.sort() changes = tag.table(tag.tbody([ tag.tr(tag.td(c) for c in cols[1:]) for cols in modified_sources ], [ tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) for spath, src in removed_sources ]), class_='props') else: changes = tag.em(_(' (with no actual effect on merging)')) return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), changes)
def expand_macro(self, formatter, name, content, args=None): t = datetime_now(utc) return tag.strong(format_datetime(t, '%c'))
def _do_save(self, req, attachment): req.perm(attachment.resource).require('ATTACHMENT_CREATE') parent_resource = attachment.resource.parent if 'cancel' in req.args: req.redirect(get_resource_url(self.env, parent_resource, req.href)) filename, fileobj, filesize = req.args.getfile('attachment') if not filename: raise TracError(_("No file uploaded")) upload_failed = _("Upload failed for %(filename)s", filename=filename) if filesize == 0: raise TracError(_("Can't upload empty file"), upload_failed) if 0 <= self.max_size < filesize: raise TracError( _("Maximum attachment size: %(num)s", num=pretty_size(self.max_size)), upload_failed) # Now the filename is known, update the attachment resource attachment.filename = filename attachment.description = req.args.get('description', '') attachment.author = get_reporter_id(req, 'author') # Validate attachment valid = True for manipulator in self.manipulators: for field, message in manipulator.validate_attachment( req, attachment): valid = False if field: add_warning( req, tag_( "Attachment field %(field)s is invalid: " "%(message)s", field=tag.strong(field), message=message)) else: add_warning( req, tag_("Invalid attachment: %(message)s", message=message)) if not valid: # Display the attach form with pre-existing data # NOTE: Local file path not known, file field cannot be repopulated add_warning(req, _("Note: File must be selected again.")) data = self._render_form(req, attachment) data['is_replace'] = req.args.get('replace') return data if req.args.get('replace'): try: old_attachment = Attachment(self.env, attachment.resource(id=filename)) req.perm(attachment.resource).require( 'ATTACHMENT_DELETE', message=_( "You don't have permission to replace the " "attachment %(name)s. You can only replace " "your own attachments. Replacing other's " "attachments requires ATTACHMENT_DELETE " "permission.", name=filename)) if (not attachment.description.strip() and old_attachment.description): attachment.description = old_attachment.description old_attachment.delete() except TracError: pass # don't worry if there's nothing to replace attachment.insert(filename, fileobj, filesize) req.redirect( get_resource_url(self.env, attachment.resource(id=None), req.href))
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() target = req.args.get('target', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper() or \ target and target.isupper(): raise TracError(_("All upper-cased tokens are reserved for " "permission names.")) # Grant permission to subject if 'add' in req.args and subject and action: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_("Unknown action")) req.perm.require(action) try: perm.grant_permission(subject, action) except TracError as e: add_warning(req, e) else: add_notice(req, _("The subject %(subject)s has been " "granted the permission %(action)s.", subject=subject, action=action)) # Add subject to group elif 'add' in req.args and subject and group: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') for action in sorted( perm.get_user_permissions(group, expand_meta=False)): req.perm.require(action, message=tag_( "The subject %(subject)s was not added to the " "group %(group)s. The group has %(perm)s " "permission and you cannot grant permissions you " "don't possess.", subject=tag.strong(subject), group=tag.strong(group), perm=tag.strong(action))) try: perm.grant_permission(subject, group) except TracError as e: add_warning(req, e) else: add_notice(req, _("The subject %(subject)s has been " "added to the group %(group)s.", subject=subject, group=group)) # Copy permissions to subject elif 'copy' in req.args and subject and target: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') subject_permissions = perm.get_users_dict().get(subject, []) if not subject_permissions: add_warning(req, _("The subject %(subject)s does not " "have any permissions.", subject=subject)) for action in subject_permissions: if action not in all_actions: # plugin disabled? self.log.warning("Skipped granting %s to %s: " "permission unavailable.", action, target) else: if action not in req.perm: add_warning(req, _("The permission %(action)s was " "not granted to %(subject)s " "because users cannot grant " "permissions they don't possess.", action=action, subject=subject)) continue try: perm.grant_permission(target, action) except PermissionExistsError: pass else: add_notice(req, _("The subject %(subject)s has " "been granted the permission " "%(action)s.", subject=target, action=action)) req.redirect(req.href.admin(cat, page)) # Remove permissions action elif 'remove' in req.args and 'sel' in req.args: req.perm('admin', 'general/perm').require('PERMISSION_REVOKE') for key in req.args.getlist('sel'): subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _("The selected permissions have been " "revoked.")) req.redirect(req.href.admin(cat, page)) return 'admin_perms.html', { 'actions': all_actions, 'allowed_actions': [a for a in all_actions if a in req.perm], 'perms': perm.get_users_dict(), 'groups': perm.get_groups_dict(), 'unicode_to_base64': unicode_to_base64 }