Merge branch 'dev' into t50_hide_project_icon_placeholder
diff --git a/Allura/allura/command/__init__.py b/Allura/allura/command/__init__.py
index 88ddebc..1179086 100644
--- a/Allura/allura/command/__init__.py
+++ b/Allura/allura/command/__init__.py
@@ -5,3 +5,4 @@
from create_neighborhood import CreateNeighborhoodCommand
from create_trove_categories import CreateTroveCategoriesCommand
from set_neighborhood_features import SetNeighborhoodFeaturesCommand
+from rssfeeds import RssFeedsCommand
diff --git a/Allura/allura/command/rssfeeds.py b/Allura/allura/command/rssfeeds.py
new file mode 100644
index 0000000..335ede9
--- /dev/null
+++ b/Allura/allura/command/rssfeeds.py
@@ -0,0 +1,84 @@
+import feedparser
+import html2text
+from bson import ObjectId
+
+import base
+
+from pylons import c
+
+from allura import model as M
+from forgeblog import model as BM
+from forgeblog import version
+from forgeblog.main import ForgeBlogApp
+from allura.lib import exceptions
+
+
+class RssFeedsCommand(base.Command):
+ summary = 'Rss feed client'
+ parser = base.Command.standard_parser(verbose=True)
+ parser.add_option('-a', '--appid', dest='appid', default='',
+ help='application id')
+ parser.add_option('-u', '--username', dest='username', default='root',
+ help='poster username')
+
+ def command(self):
+ self.basic_setup()
+
+ user = M.User.query.get(username=self.options.username)
+ c.user = user
+
+ self.prepare_feeds()
+ for appid in self.feed_dict:
+ for feed_url in self.feed_dict[appid]:
+ self.process_feed(appid, feed_url)
+
+ def prepare_feeds(self):
+ feed_dict = {}
+ if self.options.appid != '':
+ gl_app = BM.Globals.query.get(app_config_id=ObjectId(self.options.appid))
+ if not gl_app:
+ raise exceptions.NoSuchGlobalsError("The globals %s " \
+ "could not be found in the database" % self.options.appid)
+ if len(gl_app.external_feeds) > 0:
+ feed_dict[gl_app.app_config_id] = gl_app.external_feeds
+ else:
+ for gl_app in BM.Globals.query.find().all():
+ if len(gl_app.external_feeds) > 0:
+ feed_dict[gl_app.app_config_id] = gl_app.external_feeds
+ self.feed_dict = feed_dict
+
+ def process_feed(self, appid, feed_url):
+ appconf = M.AppConfig.query.get(_id=appid)
+ if not appconf:
+ return
+
+ c.project = appconf.project
+ app = ForgeBlogApp(c.project, appconf)
+ c.app = app
+
+ base.log.info("Get feed: %s" % feed_url)
+ f = feedparser.parse(feed_url)
+ if f.bozo:
+ base.log.exception("%s: %s" % (feed_url, f.bozo_exception))
+ return
+ for e in f.entries:
+ title = e.title
+ if 'content' in e:
+ content = u''
+ for ct in e.content:
+ if ct.type != 'text/html':
+ content = u"%s<p>%s</p>" % (content, ct.value)
+ else:
+ content = content + ct.value
+ else:
+ content = e.summary
+
+ content = u'%s <a href="%s">link</a>' % (content, e.link)
+ content = html2text.html2text(content, e.link)
+
+ post = BM.BlogPost(title=title, text=content, app_config_id=appid,
+ tool_version={'blog': version.__version__},
+ state='draft')
+ post.neighborhood_id=c.project.neighborhood_id
+ post.make_slug()
+ post.commit()
diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index dd575d5..9a9fb49 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -23,7 +23,7 @@
from allura.config.app_cfg import base_config
from allura.config.environment import load_environment
from allura.config.app_cfg import ForgeConfig
-from allura.lib.custom_middleware import StatsMiddleware
+from allura.lib.custom_middleware import AlluraTimerMiddleware
from allura.lib.custom_middleware import SSLMiddleware
from allura.lib.custom_middleware import StaticFilesMiddleware
from allura.lib.custom_middleware import CSRFMiddleware
@@ -33,8 +33,8 @@
__all__ = ['make_app']
-# Use base_config to setup the necessary PasteDeploy application factory.
-# make_base_app will wrap the TG2 app with all the middleware it needs.
+# Use base_config to setup the necessary PasteDeploy application factory.
+# make_base_app will wrap the TG2 app with all the middleware it needs.
make_base_app = base_config.setup_tg_wsgi_app(load_environment)
@@ -55,13 +55,13 @@
:type full_stack: str or bool
:return: The allura application with all the relevant middleware
loaded.
-
+
This is the PasteDeploy factory for the allura application.
-
+
``app_conf`` contains all the application-specific settings (those defined
under ``[app:main]``.
-
-
+
+
"""
# Run all the initialization code here
mimetypes.init(
@@ -73,7 +73,7 @@
# Configure EW variable provider
ew.render.TemplateEngine.register_variable_provider(get_tg_vars)
-
+
# Create base app
base_config = ForgeConfig(root)
load_environment = base_config.make_load_environment()
@@ -86,7 +86,7 @@
load_environment(global_conf, app_conf)
if config.get('zarkov.host'):
- try:
+ try:
import zmq
except ImportError:
raise ImportError, "Unable to import the zmq library. Please"\
@@ -113,9 +113,7 @@
# Redirect 401 to the login page
app = LoginRedirectMiddleware(app)
# Add instrumentation
- if app_conf.get('stats.sample_rate', '0.25') != '0':
- stats_config = dict(global_conf, **app_conf)
- app = StatsMiddleware(app, stats_config)
+ app = AlluraTimerMiddleware(app, app_conf)
# Clear cookies when the CSRF field isn't posted
if not app_conf.get('disable_csrf_protection'):
app = CSRFMiddleware(app, '_session_id')
@@ -145,7 +143,7 @@
# the WSGI application's iterator is exhausted
app = RegistryManager(app, streaming=True)
return app
-
+
def set_scheme_middleware(app):
def SchemeMiddleware(environ, start_response):
if asbool(environ.get('HTTP_X_SFINC_SSL', 'false')):
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 8fcf797..19403e4 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -1,6 +1,8 @@
import os
import json
import logging
+import re
+import difflib
from urllib import quote, unquote
from collections import defaultdict
@@ -15,7 +17,6 @@
from ming.orm import ThreadLocalORMSession, session
import allura.tasks
-from allura.lib import patience
from allura.lib import security
from allura.lib import helpers as h
from allura.lib import widgets as w
@@ -61,7 +62,7 @@
@with_trailing_slash
@expose('jinja:allura:templates/repo/fork.html')
- def fork(self, to_name=None, project_id=None):
+ def fork(self, project_id=None, mount_point=None, mount_label=None):
# this shows the form and handles the submission
security.require_authenticated()
if not c.app.forkable: raise exc.HTTPNotFound
@@ -70,10 +71,13 @@
ThreadLocalORMSession.close_all()
from_project = c.project
to_project = M.Project.query.get(_id=ObjectId(project_id))
- if request.method != 'POST' or not to_name:
+ mount_label = mount_label or '%s - %s' % (c.project.name, from_repo.tool_name)
+ mount_point = (mount_point or from_project.shortname)
+ if request.method != 'POST' or not mount_point:
return dict(from_repo=from_repo,
user_project=c.user.private_project(),
- to_name=to_name or '')
+ mount_point=mount_point,
+ mount_label=mount_label)
else:
with h.push_config(c, project=to_project):
if not to_project.database_configured:
@@ -81,10 +85,12 @@
security.require(security.has_access(to_project, 'admin'))
try:
to_project.install_app(
- from_repo.tool_name, to_name,
+ ep_name=from_repo.tool_name,
+ mount_point=mount_point,
+ mount_label=mount_label,
cloned_from_project_id=from_project._id,
cloned_from_repo_id=from_repo._id)
- redirect(to_project.url()+to_name+'/')
+ redirect(to_project.url()+mount_point+'/')
except exc.HTTPRedirection:
raise
except Exception, ex:
@@ -514,7 +520,7 @@
b = self._blob
la = list(a)
lb = list(b)
- diff = ''.join(patience.unified_diff(
+ diff = ''.join(difflib.unified_diff(
la, lb,
('a' + apath).encode('utf-8'),
('b' + b.path()).encode('utf-8')))
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 2ccaacd..25ce980 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -2,7 +2,7 @@
{% block title %}{{user.display_name}} / Profile{% endblock %}
-{% block header %}{{c.project.homepage_title}}{% endblock %}
+{% block header %}{{ user.display_name|default(user.username) }}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" type="text/css"
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index d43fe29..3e76d12 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -60,7 +60,7 @@
if asbool(config.get('solr.mock')):
self.solr = MockSOLR()
elif self.solr_server:
- self.solr = pysolr.Solr(self.solr_server)
+ self.solr = pysolr.Solr(self.solr_server)
else: # pragma no cover
self.solr = None
self.use_queue = asbool(config.get('use_queue', False))
@@ -77,7 +77,7 @@
# Setup pygments
self.pygments_formatter = utils.LineAnchorCodeHtmlFormatter(
cssclass='codehilite',
- linenos='inline')
+ linenos='table')
# Setup Pypeline
self.pypeline_markup = pypeline_markup
@@ -86,42 +86,42 @@
self.analytics = analytics.GoogleAnalytics(account=config.get('ga.account', 'UA-XXXXX-X'))
self.icons = dict(
- admin = Icon('x', 'ico-admin'),
- pencil = Icon('p', 'ico-pencil'),
- help = Icon('h', 'ico-help'),
- search = Icon('s', 'ico-search'),
- history = Icon('N', 'ico-history'),
- feed = Icon('f', 'ico-feed'),
- mail = Icon('M', 'ico-mail'),
- reply = Icon('w', 'ico-reply'),
- tag = Icon('z', 'ico-tag'),
- flag = Icon('^', 'ico-flag'),
- undelete = Icon('+', 'ico-undelete'),
- delete = Icon('#', 'ico-delete'),
- close = Icon('D', 'ico-close'),
- table = Icon('n', 'ico-table'),
- stats = Icon('Y', 'ico-stats'),
- pin = Icon('@', 'ico-pin'),
- folder = Icon('o', 'ico-folder'),
- fork = Icon('R', 'ico-fork'),
- merge = Icon('J', 'ico-merge'),
- plus = Icon('+', 'ico-plus'),
- conversation = Icon('q', 'ico-conversation'),
- group = Icon('g', 'ico-group'),
- user = Icon('U', 'ico-user'),
- secure = Icon('(', 'ico-lock'),
- unsecure = Icon(')', 'ico-unlock'),
+ admin=Icon('x', 'ico-admin'),
+ pencil=Icon('p', 'ico-pencil'),
+ help=Icon('h', 'ico-help'),
+ search=Icon('s', 'ico-search'),
+ history=Icon('N', 'ico-history'),
+ feed=Icon('f', 'ico-feed'),
+ mail=Icon('M', 'ico-mail'),
+ reply=Icon('w', 'ico-reply'),
+ tag=Icon('z', 'ico-tag'),
+ flag=Icon('^', 'ico-flag'),
+ undelete=Icon('+', 'ico-undelete'),
+ delete=Icon('#', 'ico-delete'),
+ close=Icon('D', 'ico-close'),
+ table=Icon('n', 'ico-table'),
+ stats=Icon('Y', 'ico-stats'),
+ pin=Icon('@', 'ico-pin'),
+ folder=Icon('o', 'ico-folder'),
+ fork=Icon('R', 'ico-fork'),
+ merge=Icon('J', 'ico-merge'),
+ plus=Icon('+', 'ico-plus'),
+ conversation=Icon('q', 'ico-conversation'),
+ group=Icon('g', 'ico-group'),
+ user=Icon('U', 'ico-user'),
+ secure=Icon('(', 'ico-lock'),
+ unsecure=Icon(')', 'ico-unlock'),
# Permissions
- perm_read = Icon('E', 'ico-focus'),
- perm_update = Icon('0', 'ico-sync'),
- perm_create = Icon('e', 'ico-config'),
- perm_register = Icon('e', 'ico-config'),
- perm_delete = Icon('-', 'ico-minuscirc'),
- perm_tool = Icon('x', 'ico-config'),
- perm_admin = Icon('(', 'ico-lock'),
- perm_has_yes = Icon('3', 'ico-check'),
- perm_has_no = Icon('d', 'ico-noentry'),
- perm_has_inherit = Icon('2', 'ico-checkcircle'),
+ perm_read=Icon('E', 'ico-focus'),
+ perm_update=Icon('0', 'ico-sync'),
+ perm_create=Icon('e', 'ico-config'),
+ perm_register=Icon('e', 'ico-config'),
+ perm_delete=Icon('-', 'ico-minuscirc'),
+ perm_tool=Icon('x', 'ico-config'),
+ perm_admin=Icon('(', 'ico-lock'),
+ perm_has_yes=Icon('3', 'ico-check'),
+ perm_has_no=Icon('d', 'ico-noentry'),
+ perm_has_inherit=Icon('2', 'ico-checkcircle'),
)
# Cache some loaded entry points
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index a323298..15d49c8 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -1,21 +1,14 @@
import os
import re
import logging
-from contextlib import contextmanager
-from threading import local
-from random import random
import tg
-import pylons
import pkg_resources
-import markdown
from paste import fileapp
-from paste.deploy.converters import asbool
from pylons.util import call_wsgi_application
-from tg.controllers import DecoratedController
+from timermiddleware import Timer, TimerMiddleware
from webob import exc, Request
-from allura.lib.stats import timing, StatsRecord
from allura.lib import helpers as h
log = logging.getLogger(__name__)
@@ -151,57 +144,39 @@
resp = req.get_response(self.app)
return resp(environ, start_response)
-class StatsMiddleware(object):
-
- def __init__(self, app, config):
- self.app = app
- self.config = config
- self.log = logging.getLogger('stats')
- self.active = False
- try:
- self.sample_rate = config.get('stats.sample_rate', 0.25)
- self.debug = asbool(config.get('debug', 'false'))
- self.instrument_pymongo()
- self.instrument_template()
- self.active = True
- except KeyError:
- self.sample_rate = 0
-
- def instrument_pymongo(self):
- import pymongo.collection
- import ming.odm
- timing('mongo').decorate(pymongo.collection.Collection,
- 'count find find_one')
- timing('mongo').decorate(pymongo.cursor.Cursor,
- 'count distinct explain hint limit next rewind'
- ' skip sort where')
- timing('ming').decorate(ming.odm.odmsession.ODMSession,
- 'flush find get')
- timing('ming').decorate(ming.odm.odmsession.ODMCursor,
- 'next')
-
- def instrument_template(self):
+class AlluraTimerMiddleware(TimerMiddleware):
+ def timers(self):
+ import genshi
import jinja2
- import genshi.template
- timing('template').decorate(genshi.template.Template,
- '_prepare _parse generate')
- timing('render').decorate(genshi.Stream,
- 'render')
- timing('render').decorate(jinja2.Template,
- 'render')
- timing('markdown').decorate(markdown.Markdown,
- 'convert')
+ import markdown
+ import ming
+ import pymongo
+ import socket
+ import urllib2
-
- def __call__(self, environ, start_response):
- req = Request(environ)
- req.environ['sf.stats'] = s = StatsRecord(req, random() < self.sample_rate)
- with s.timing('total'):
- resp = req.get_response(self.app, catch_exc_info=self.debug)
- result = resp(environ, start_response)
- if s.active:
- self.log.info('Stats: %r', s)
- from allura import model as M
- M.Stats.make(s.asdict()).m.insert()
- return result
-
+ return [
+ Timer('markdown', markdown.Markdown, 'convert'),
+ Timer('ming', ming.odm.odmsession.ODMCursor, 'next'),
+ Timer('ming', ming.odm.odmsession.ODMSession, 'flush', 'find',
+ 'get'),
+ Timer('ming', ming.schema.Document, 'validate',
+ debug_each_call=False),
+ Timer('ming', ming.schema.FancySchemaItem, '_validate_required',
+ '_validate_fast_missing', '_validate_optional',
+ debug_each_call=False),
+ Timer('mongo', pymongo.collection.Collection, 'count', 'find',
+ 'find_one'),
+ Timer('mongo', pymongo.cursor.Cursor, 'count', 'distinct',
+ 'explain', 'hint', 'limit', 'next', 'rewind', 'skip',
+ 'sort', 'where'),
+ Timer('jinja', jinja2.Template, 'render', 'stream', 'generate'),
+ # urlopen and socket io may or may not overlap partially
+ Timer('urlopen', urllib2, 'urlopen'),
+ Timer('render', genshi.Stream, 'render'),
+ Timer('socket_read', socket._fileobject, 'read', 'readline',
+ 'readlines', debug_each_call=False),
+ Timer('socket_write', socket._fileobject, 'write', 'writelines',
+ 'flush', debug_each_call=False),
+ Timer('template', genshi.template.Template, '_prepare', '_parse',
+ 'generate'),
+ ]
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index fa9f4a0..30250f7 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -4,6 +4,7 @@
class ToolError(ForgeError): pass
class NoSuchProjectError(ForgeError): pass
class NoSuchNeighborhoodError(ForgeError): pass
+class NoSuchGlobalsError(ForgeError): pass
class MailError(ForgeError): pass
class AddressException(MailError): pass
class NoSuchNBFeatureError(ForgeError): pass
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 57b42ee..b144cc8 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -142,8 +142,8 @@
results = dict(
(r._id, r)
for r in X.query.find(dict(_id={'$in':ids})))
- result = ( results.get(i) for i in ids )
- return ( r for r in result if r is not None )
+ result = (results.get(i) for i in ids)
+ return (r for r in result if r is not None)
@contextmanager
def push_config(obj, **kw):
@@ -158,14 +158,14 @@
try:
yield obj
finally:
- for k,v in saved_attrs.iteritems():
+ for k, v in saved_attrs.iteritems():
setattr(obj, k, v)
for k in new_attrs:
delattr(obj, k)
def sharded_path(name, num_parts=2):
parts = [
- name[:i+1]
+ name[:i + 1]
for i in range(num_parts) ]
return '/'.join(parts)
@@ -227,12 +227,12 @@
a valid kwargs argument'''
return dict(
(k.encode('utf-8'), v)
- for k,v in d.iteritems())
+ for k, v in d.iteritems())
def vardec(fun):
def vardec_hook(remainder, params):
new_params = variable_decode(dict(
- (k,v) for k,v in params.items()
+ (k, v) for k, v in params.items()
if re_clean_vardec_key.match(k)))
params.update(new_params)
before_validate(vardec_hook)(fun)
@@ -276,7 +276,7 @@
try:
return parse(value)
except ValueError:
- if self.if_invalid!=formencode.api.NoDefault:
+ if self.if_invalid != formencode.api.NoDefault:
return self.if_invalid
else:
raise
@@ -336,7 +336,7 @@
v.cls = cls
class attrproxy(object):
- cls=None
+ cls = None
def __init__(self, *attrs):
self.attrs = attrs
@@ -413,7 +413,7 @@
errors=c.validation_exception.unpack_errors(),
value=c.validation_exception.value,
params=kwargs)
- response.status=400
+ response.status = 400
return json.dumps(result, indent=2)
def pop_user_notifications(user=None):
@@ -429,8 +429,8 @@
'''Return a subdictionary keys with a given prefix,
with the prefix stripped
'''
- plen=len(prefix)
- return dict((k[plen:], v) for k,v in d.iteritems()
+ plen = len(prefix)
+ return dict((k[plen:], v) for k, v in d.iteritems()
if k.startswith(prefix))
@contextmanager
@@ -478,7 +478,7 @@
extra = kwargs.setdefault('extra', {})
meta = kwargs.pop('meta', {})
kwpairs = extra.setdefault('kwpairs', {})
- for k,v in meta.iteritems():
+ for k, v in meta.iteritems():
kwpairs['meta_%s' % k] = v
extra.update(self._make_extra())
self._logger.log(level, self._action + ': ' + message, *args, **kwargs)
@@ -500,7 +500,7 @@
def warning(self, message, *args, **kwargs):
self.log(logging.EXCEPTION, message, *args, **kwargs)
- warn=warning
+ warn = warning
def _make_extra(self):
result = dict(self.extra_proto, action=self._action)
@@ -542,7 +542,37 @@
page = min(max(int(page), (0 if zero_based_pages else 1)), max_page)
return limit, page
-def render_any_markup(name, text, code_mode=False):
+
+def _add_inline_line_numbers_to_text(text):
+ markup_text = '<div class="codehilite"><pre>'
+ for line_num, line in enumerate(text.splitlines(), 1):
+ markup_text = markup_text + '<span id="l%s" class="code_block"><span class="lineno">%s</span> %s</span>' % (line_num, line_num, line)
+ markup_text = markup_text + '</pre></div>'
+ return markup_text
+
+
+def _add_table_line_numbers_to_text(text):
+ def _prepend_whitespaces(num, max_num):
+ num, max_num = str(num), str(max_num)
+ diff = len(max_num) - len(num)
+ return ' ' * diff + num
+
+ def _len_to_str_column(l, start=1):
+ max_num = l + start
+ return '\n'.join(map(_prepend_whitespaces, range(start, max_num), [max_num] * l))
+
+ lines = text.splitlines(True)
+ linenumbers = '<td class="linenos"><div class="linenodiv"><pre>' + _len_to_str_column(len(lines)) + '</pre></div></td>'
+ markup_text = '<table class="codehilitetable"><tbody><tr>' + linenumbers + '<td class="code"><div class="codehilite"><pre>'
+ for line_num, line in enumerate(lines, 1):
+ markup_text = markup_text + '<span id="l%s" class="code_block">%s</span>' % (line_num, line)
+ markup_text = markup_text + '</pre></div></td></tr></tbody></table>'
+ return markup_text
+
+
+INLINE = 'inline'
+TABLE = 'table'
+def render_any_markup(name, text, code_mode=False, linenumbers_style=TABLE):
"""
renders any markup format using the pypeline
Returns jinja-safe text
@@ -552,14 +582,42 @@
else:
text = pylons.g.pypeline_markup.render(name, text)
if not pylons.g.pypeline_markup.can_render(name):
- if code_mode:
- markup_text = '<div class="codehilite"><pre>'
- line_num = 1
- for line in text.splitlines():
- markup_text = markup_text + '<span id="l%s" class="code_block"><span class="lineno">%s</span> %s</span>' % (line_num, line_num, line)
- line_num += 1
- markup_text = markup_text + '</pre></div>'
- text = markup_text
+ if code_mode and linenumbers_style == INLINE:
+ text = _add_inline_line_numbers_to_text(text)
+ elif code_mode and linenumbers_style == TABLE:
+ text = _add_table_line_numbers_to_text(text)
else:
text = '<pre>%s</pre>' % text
return Markup(text)
+
+# copied from jinja2 dev
+# latest release, 2.6, implements this incorrectly
+# can remove and use jinja2 implementation after upgrading to 2.7
+def do_filesizeformat(value, binary=False):
+ """Format the value like a 'human-readable' file size (i.e. 13 kB,
+4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
+Giga, etc.), if the second parameter is set to `True` the binary
+prefixes are used (Mebi, Gibi).
+"""
+ bytes = float(value)
+ base = binary and 1024 or 1000
+ prefixes = [
+ (binary and 'KiB' or 'kB'),
+ (binary and 'MiB' or 'MB'),
+ (binary and 'GiB' or 'GB'),
+ (binary and 'TiB' or 'TB'),
+ (binary and 'PiB' or 'PB'),
+ (binary and 'EiB' or 'EB'),
+ (binary and 'ZiB' or 'ZB'),
+ (binary and 'YiB' or 'YB')
+ ]
+ if bytes == 1:
+ return '1 Byte'
+ elif bytes < base:
+ return '%d Bytes' % bytes
+ else:
+ for i, prefix in enumerate(prefixes):
+ unit = base ** (i + 2)
+ if bytes < unit:
+ return '%.1f %s' % ((base * bytes / unit), prefix)
+ return '%.1f %s' % ((base * bytes / unit), prefix)
diff --git a/Allura/allura/lib/patience.py b/Allura/allura/lib/patience.py
deleted file mode 100644
index 2433cab..0000000
--- a/Allura/allura/lib/patience.py
+++ /dev/null
@@ -1,205 +0,0 @@
-import sys
-import difflib
-from itertools import chain
-
-class Region(object):
- '''Simple utility class that keeps track of subsequences'''
- __slots__=('seq', # sequence
- 'l', # lower bound
- 'h', # upper bound
- )
- def __init__(self, seq, l=0, h=None):
- if h is None: h = len(seq)
- self.seq, self.l, self.h = seq, l, h
-
- def __iter__(self):
- '''Iterates over the subsequence only'''
- for i in xrange(self.l, self.h):
- yield self.seq[i]
-
- def __getitem__(self, i):
- '''works like getitem on the subsequence. Slices return new
- regions.'''
- if isinstance(i, slice):
- start, stop, step = i.indices(len(self))
- assert step == 1
- return self.clone(l=self.l+start,h=self.l+stop)
- elif i >= 0:
- return self.seq[i+self.l]
- else:
- return self.seq[i+self.h]
-
- def __len__(self):
- return self.h - self.l
-
- def __repr__(self):
- if len(self) > 8:
- srepr = '[%s,...]' % (','.join(repr(x) for x in self[:5]))
- else:
- srepr = repr(list(self))
- return '<Region (%s,%s): %s>' % (self.l, self.h, srepr)
-
- def clone(self, **kw):
- '''Return a new Region based on this one with the
- provided kw arguments replaced in the constructor.
- '''
- kwargs = dict(seq=self.seq, l=self.l, h=self.h)
- kwargs.update(kw)
- return Region(**kwargs)
-
-def region_diff(ra, rb):
- '''generator yielding up to two matching blocks, one at the
- beginning of the region and one at the end. This function
- mutates the a and b regions, removing any matched blocks.
- '''
- # Yield match at the beginning
- i = 0
- while i < len(ra) and i < len(rb) and ra[i] == rb[i]:
- i += 1
- if i:
- yield ra.l, rb.l, i
- ra.l+=i
- rb.l+=i
- # Yield match at the end
- j = 0
- while j < len(ra) and j < len(rb) and ra[-j-1]==rb[-j-1]:
- j+=1
- if j:
- yield ra.h-j, rb.h-j, j
- ra.h-=j
- rb.h-=j
-
-def unique(a):
- '''generator yielding unique lines of a sequence and their positions'''
- count = {}
- for aa in a:
- count[aa] = count.get(aa, 0) + 1
- for i, aa in enumerate(a):
- if count[aa] == 1: yield aa, i
-
-def common_unique(a, b):
- '''generator yielding pairs i,j where
- a[i] == b[j] and a[i] and b[j] are unique within a and b,
- in increasing j order.'''
- uq_a = dict(unique(a))
- for bb, j in unique(b):
- try:
- yield uq_a[bb], j
- except KeyError, ke:
- continue
-
-def patience(seq):
- stacks = []
- for i, j in seq:
- last_top = None
- for stack in stacks:
- top_i, top_j, top_back = stack[-1]
- if top_i > i:
- stack.append((i, j, last_top))
- break
- last_top = len(stack)-1
- else:
- stacks.append([(i, j, last_top)])
- if not stacks: return []
- prev = len(stacks[-1])-1
- seq = []
- for stack in reversed(stacks):
- top_i, top_j, top_back = stack[prev]
- seq.append((top_i, top_j))
- prev = top_back
- return reversed(seq)
-
-def match_core(a, b):
- '''Returns blocks that match between sequences a and b as
- (index_a, index_b, size)
- '''
- ra = Region(a)
- rb = Region(b)
- # beginning/end match
- for block in region_diff(ra,rb): yield block
- # patience core
- last_i = last_j = None
- for i, j in chain(
- patience(common_unique(ra, rb)),
- [(ra.h, rb.h)]):
- if last_i is not None:
- for block in region_diff(ra[last_i:i], rb[last_j:j]):
- yield block
- last_i = i
- last_j = j
-
-def diff_gen(a, b, opcode_gen):
- '''Convert a sequence of SequenceMatcher opcodes to
- unified diff-like output
- '''
- def _iter():
- for op, i1, i2, j1, j2 in opcode_gen:
- if op == 'equal':
- yield ' ', Region(a, i1, i2)
- if op in ('delete', 'replace'):
- yield '- ', Region(a, i1, i2)
- if op in ('replace', 'insert'):
- yield '+ ', Region(b, j1, j2)
- for prefix, rgn in _iter():
- for line in rgn:
- yield prefix, line
-
-def unified_diff(
- a, b, fromfile='', tofile='', fromfiledate='',
- tofiledate='', n=3, lineterm='\n'):
- started = False
- for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
- if not started:
- yield '--- %s %s%s' % (fromfile, fromfiledate, lineterm)
- yield '+++ %s %s%s' % (tofile, tofiledate, lineterm)
- started = True
- i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
- yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
- for tag, i1, i2, j1, j2 in group:
- if tag == 'equal':
- for line in a[i1:i2]:
- yield ' ' + line
- continue
- if tag == 'replace' or tag == 'delete':
- for line in a[i1:i2]:
- yield '-' + line
- if tag == 'replace' or tag == 'insert':
- for line in b[j1:j2]:
- yield '+' + line
-
-class SequenceMatcher(difflib.SequenceMatcher):
-
- def get_matching_blocks(self):
- '''Uses patience diff algorithm to find matching blocks.'''
- if self.matching_blocks is not None:
- return self.matching_blocks
- matching_blocks = list(match_core(self.a, self.b))
- matching_blocks.append((len(self.a), len(self.b), 0))
- self.matching_blocks = sorted(matching_blocks)
- return self.matching_blocks
-
-def test(): # pragma no cover
- if len(sys.argv) == 3:
- fn_a = sys.argv[1]
- fn_b = sys.argv[2]
- else:
- fn_a = 'a.c'
- fn_b = 'b.c'
- a = open(fn_a).readlines()
- b = open(fn_b).readlines()
- # print '====', fn_a
- # sys.stdout.write(''.join(a))
- # print '====', fn_b
- # sys.stdout.write(''.join(b))
- sm = SequenceMatcher(None, a, b)
- # print 'Patience opcodes:', sm.get_opcodes()
- print ''.join(unified_diff(a, b)) #pragma:printok
- # for prefix, line in diff_gen(a, b, sm.get_opcodes()):
- # sys.stdout.write(''.join((prefix, line)))
- # sm = difflib.SequenceMatcher(None, a, b)
- # print 'Difflib opcodes:', sm.get_opcodes()
- # for prefix, line in diff_gen(a, b, sm.get_opcodes()):
- # sys.stdout.write(''.join((prefix, line)))
-
-if __name__ == '__main__': # pragma no cover
- test()
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 2562c41..60ded08 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -121,7 +121,7 @@
file_info.file.seek(0, 2)
bytecount = file_info.file.tell()
file_info.file.seek(0)
- text = "%s\n%s (%s bytes in %s)" % (text, file_info.filename, bytecount, file_info.type)
+ text = "%s\n\n\nAttachment: %s (%s; %s)" % (text, file_info.filename, h.do_filesizeformat(bytecount), file_info.type)
subject = post.subject or ''
if post.parent_id and not subject.lower().startswith('re:'):
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 7efab5b..5ca3f4c 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -123,6 +123,7 @@
shortname = FieldProperty(str)
name=FieldProperty(str)
notifications_disabled = FieldProperty(bool)
+ suppress_emails = FieldProperty(bool)
show_download_button=FieldProperty(S.Deprecated)
short_description=FieldProperty(str, if_missing='')
summary=FieldProperty(str, if_missing='')
@@ -388,11 +389,17 @@
delta_ordinal = 0
max_ordinal = 0
neighborhood_admin_mode = False
- if self == self.neighborhood.neighborhood_project:
- delta_ordinal = 1
- neighborhood_admin_mode = True
- entries.append({'ordinal':0,'entry':SitemapEntry('Home', self.neighborhood.url(), ui_icon="tool-home")})
+ if self.is_user_project:
+ entries.append({'ordinal': delta_ordinal, 'entry':SitemapEntry('Profile', "%sprofile/" % self.url(), ui_icon="tool-home")})
+ max_ordinal = delta_ordinal
+ delta_ordinal = delta_ordinal + 1
+
+ if self == self.neighborhood.neighborhood_project:
+ entries.append({'ordinal':delta_ordinal, 'entry':SitemapEntry('Home', self.neighborhood.url(), ui_icon="tool-home")})
+ max_ordinal = delta_ordinal
+ delta_ordinal = delta_ordinal + 1
+ neighborhood_admin_mode = True
for sub in self.direct_subprojects:
ordinal = sub.ordinal + delta_ordinal
@@ -417,9 +424,6 @@
entries.append({'ordinal': max_ordinal + 1,'entry':SitemapEntry('Moderate', "%s_moderate/" % self.neighborhood.url(), ui_icon="tool-admin")})
max_ordinal += 1
- if self.is_user_project:
- entries.append({'ordinal': max_ordinal + 1,'entry':SitemapEntry('Profile', "%sprofile/" % self.url(), ui_icon="tool-home")})
-
entries = sorted(entries, key=lambda e: e['ordinal'])
for e in entries:
sitemap.children.append(e['entry'])
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index ba8ffef..5ed3a87 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -5,6 +5,7 @@
import logging
import string
import re
+from difflib import SequenceMatcher
from hashlib import sha1
from datetime import datetime
from collections import defaultdict
@@ -20,7 +21,6 @@
from ming.orm import FieldProperty, session, Mapper
from ming.orm.declarative import MappedClass
-from allura.lib.patience import SequenceMatcher
from allura.lib import helpers as h
from allura.lib import utils
diff --git a/Allura/allura/nf/allura/css/site_style.css b/Allura/allura/nf/allura/css/site_style.css
index 3e08a4f..a07fd51 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -20,7 +20,7 @@
* Color variables for the theme.
*
*/
-/*
+/*
* Your run-of-the-mill reset CSS, inspired by:
*
* yui.yahooapis.com/2.8.1/build/base/base.css
@@ -99,7 +99,7 @@
background: #fff;
}
-/*
+/*
* Setup a minimal, baseline CSS, layered on top of a reset
* to define the default styles we've come to expect. Inspired by:
*
@@ -245,7 +245,7 @@
font-weight: normal;
}
-/*
+/*
* General CSS rules governing high-level elements.
*
*/
@@ -380,7 +380,7 @@
padding-left: 1em;
}
-/*
+/*
* Style elements in the main header and footer areas.
*
*/
@@ -2707,3 +2707,43 @@
.neighborhood_feed_entry h3 {
font-size: 1.1em;
}
+
+/*linenumbers in codeblock viewer style*/
+
+table.codehilitetable {
+ background: #F8F8F8;
+ margin-left:0px;
+}
+
+td.linenos {
+ width:auto;
+ padding: 0;
+}
+div.linenodiv {
+
+}
+td.linenos pre {
+ font-size: 100%;
+ padding: 1px;
+ padding-left: 7px;
+ padding-right: 5px;
+ margin-left: 15px;
+ background-color: #EBEBEB;
+ color: #555;
+ border-right: solid 1px #DDD;
+}
+td.code {
+ padding-left: 0px;
+ width:100%;
+}
+
+div.codehilite pre {
+ padding-left: 0px;
+ padding-top:10px;
+ padding-bottom:10px;
+}
+div.codehilite pre div.code_block {
+ padding-left:10px;
+ width: 97%;
+}
+
diff --git a/Allura/allura/public/nf/css/forge/hilite.css b/Allura/allura/public/nf/css/forge/hilite.css
index cf5a6a7..44d2eb4 100644
--- a/Allura/allura/public/nf/css/forge/hilite.css
+++ b/Allura/allura/public/nf/css/forge/hilite.css
@@ -66,4 +66,9 @@
.codehilite div { margin:0; padding: 0; }
.codehilite .code_block { width:100%; }
.codehilite .code_block:hover { background-color: #ffff99; }
-.codehilite .lineno { background-color: #ebebeb; display:inline-block; padding:0 .5em; border-width: 0 1px 0 0; border-style: solid; border-color: #ddd; }
+.codehilite .lineno { background-color: #ebebeb;
+ display:inline-block;
+ padding:0 .5em;
+ border-width: 0 1px 0 0;
+ border-style: solid;
+ border-color: #ddd; }
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index 8a9c2ee..f0c24d5 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -1,10 +1,13 @@
import shutil
import logging
+import traceback
from pylons import c
from allura.lib.decorators import task
from allura.lib.repository import RepositoryApp
+from allura.lib import helpers as h
+from allura.tasks.mail_tasks import sendmail
@task
def init(**kwargs):
@@ -20,15 +23,49 @@
cloned_from_path,
cloned_from_name,
cloned_from_url):
- from allura import model as M
- c.app.repo.init_as_clone(
- cloned_from_path,
- cloned_from_name,
- cloned_from_url)
- M.Notification.post_user(
- c.user, c.app.repo, 'created',
- text='Repository %s/%s created' % (
- c.project.shortname, c.app.config.options.mount_point))
+ try:
+ from allura import model as M
+ c.app.repo.init_as_clone(
+ cloned_from_path,
+ cloned_from_name,
+ cloned_from_url)
+ M.Notification.post_user(
+ c.user, c.app.repo, 'created',
+ text='Repository %s/%s created' % (
+ c.project.shortname, c.app.config.options.mount_point))
+ if not c.project.suppress_emails:
+ sendmail(
+ destinations=[str(c.user._id)],
+ fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
+ reply_to=u'noreply@in.sf.net',
+ subject=u'SourceForge Repo Clone Complete',
+ message_id=h.gen_message_id(),
+ text=u''.join([
+ u'Clone of repo %s in project %s from %s is complete. Your repo is now ready to use.\n'
+ ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url))
+ except:
+ sendmail(
+ destinations=['sfengineers@geek.net'],
+ fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
+ reply_to=u'noreply@in.sf.net',
+ subject=u'SourceForge Repo Clone Failure',
+ message_id=h.gen_message_id(),
+ text=u''.join([
+ u'Clone of repo %s in project %s from %s failed.\n',
+ u'\n',
+ u'%s',
+ ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url, traceback.format_exc()))
+ if not c.project.suppress_emails:
+ sendmail(
+ destinations=[str(c.user._id)],
+ fromaddr=u'SourceForge.net <noreply+project-upgrade@in.sf.net>',
+ reply_to=u'noreply@in.sf.net',
+ subject=u'SourceForge Repo Clone Failed',
+ message_id=h.gen_message_id(),
+ text=u''.join([
+ u'Clone of repo %s in project %s from %s failed. ',
+ u'The SourceForge engineering team has been notified.\n',
+ ]) % (c.app.config.options.mount_point, c.project.shortname, cloned_from_url))
@task
def reclone(*args, **kwargs):
diff --git a/Allura/allura/templates/repo/file.html b/Allura/allura/templates/repo/file.html
index 1e75f1e..9928258 100644
--- a/Allura/allura/templates/repo/file.html
+++ b/Allura/allura/templates/repo/file.html
@@ -70,7 +70,7 @@
<div class="clip grid-19">
<h3><span class="ico-l"><b data-icon="{{g.icons['table'].char}}" class="ico {{g.icons['table'].css}}"></b> {{h.really_unicode(blob.name)}}</span></h3>
{% if blob.has_pypeline_view %}
- {{h.render_any_markup(blob.name, blob.text, code_mode=True)}}
+ {{h.render_any_markup(blob.name, blob.text, code_mode=True, linenumbers_style=h.TABLE)}}
{% else %}
{{g.highlight(blob.text, filename=blob.name)}}
{% endif %}
diff --git a/Allura/allura/templates/repo/fork.html b/Allura/allura/templates/repo/fork.html
index d760f24..77af8d4 100644
--- a/Allura/allura/templates/repo/fork.html
+++ b/Allura/allura/templates/repo/fork.html
@@ -16,9 +16,13 @@
{% endfor %}
</select>
</div>
- <label class="grid-4">Repository Name:</label>
+ <label class="grid-4">Label:</label>
<div class="grid-15">
- <input type="text" name="to_name" value="{{to_name}}"/>
+ <input type="text" name="mount_label" value="{{mount_label}}"/>
+ </div>
+ <label class="grid-4">Mount point:</label>
+ <div class="grid-15">
+ <input type="text" name="mount_point" value="{{mount_point}}"/>
</div>
<label class="grid-4"> </label>
<div class="grid-15">
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 9ea68e6..1450d41 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -8,6 +8,7 @@
@td.with_user_project('test-admin')
def test_profile(self):
response = self.app.get('/u/test-admin/profile/')
+ assert '<h2 class="dark title">Test Admin' in response
assert 'OpenIDs' in response
response = self.app.get('/u/test-admin/profile/configuration')
assert 'Configure Dashboard' in response
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index c43efe3..f80805e 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -167,7 +167,7 @@
p = t.post(text=u'test message', forum= None, subject= '', file_info=fs)
ThreadLocalORMSession.flush_all()
n = M.Notification.query.get(subject=u'[test:wiki] Test comment notification')
- assert u'test message\nfake.txt (37 bytes in text/plain)'==n.text
+ assert_equals(u'test message\n\n\nAttachment: fake.txt (37 Bytes; text/plain)', n.text)
@with_setup(setUp, tearDown)
def test_discussion_delete():
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 6bcdb8a..94de9dd 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -1,10 +1,12 @@
from nose.tools import assert_raises
-from alluratest.controller import setup_basic_test, setup_global_objects
-from allura.command import script, set_neighborhood_features
-from allura import model as M
-from allura.lib.exceptions import InvalidNBFeatureValueError
+from ming.orm.ormsession import ThreadLocalORMSession
+from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.command import script, set_neighborhood_features, rssfeeds
+from allura import model as M
+from forgeblog import model as BM
+from allura.lib.exceptions import InvalidNBFeatureValueError
test_config = 'test.ini#main'
@@ -116,3 +118,21 @@
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', '2.8'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', 'None'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', 'True'])
+
+def test_pull_rss_feeds():
+ base_app = M.AppConfig.query.find().all()[0]
+ tmp_app = M.AppConfig(tool_name=u'Blog', discussion_id=base_app.discussion_id,
+ project_id=base_app.project_id,
+ options={u'ordinal': 0, u'show_right_bar': True,
+ u'project_name': base_app.project.name,
+ u'mount_point': u'blog',
+ u'mount_label': u'Blog'})
+ new_external_feeds = ['http://wordpress.org/news/feed/']
+ BM.Globals(app_config_id=tmp_app._id, external_feeds=new_external_feeds)
+ ThreadLocalORMSession.flush_all()
+
+ cmd = rssfeeds.RssFeedsCommand('pull-rss-feeds')
+ cmd.run([test_config, '-a', tmp_app._id])
+ cmd.command()
+
+ assert len(BM.BlogPost.query.find({'app_config_id': tmp_app._id}).all()) > 0
diff --git a/Allura/allura/tests/test_patience.py b/Allura/allura/tests/test_patience.py
deleted file mode 100644
index 0537544..0000000
--- a/Allura/allura/tests/test_patience.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from os import path, environ
-from collections import defaultdict
-
-from allura.lib import patience
-
-def text2lines(text):
- return [l + '\n' for l in text.split('\n')]
-
-def test_region():
- r = patience.Region('foobar')
- r2 = r.clone()
- assert id(r) != id(r2)
- assert '-'.join(r) == '-'.join(r2)
- subr = r[1:5]
- assert type(subr) is type(r)
- assert ''.join(subr) == ''.join(r)[1:5]
- repr(r)
- repr(patience.Region('fffffffffffffffffffffffffffffffffffffffff'))
-
-def test_unified_diff():
- text1 = '''\
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.script.appinstall import SetupCommand
-from paste.script.appinstall import SetupCommand
-from paste.script.appinstall import SetupCommand
-from paste.script.appinstall import SetupCommand
-from paste.deploy import appconfig
-'''
- text2 = '''\
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.deploy import loadapp
-from paste.script.appinstall import SetupCommand2
-from paste.script.appinstall import SetupCommand3
-from paste.script.appinstall import SetupCommand4
-from paste.deploy import appconfig
-'''
- line_uni_diff = '''\
- from paste.deploy import loadapp
- from paste.deploy import loadapp
- from paste.deploy import loadapp
--from paste.script.appinstall import SetupCommand
--from paste.script.appinstall import SetupCommand
--from paste.script.appinstall import SetupCommand
--from paste.script.appinstall import SetupCommand
-+from paste.script.appinstall import SetupCommand2
-+from paste.script.appinstall import SetupCommand3
-+from paste.script.appinstall import SetupCommand4
- from paste.deploy import appconfig'''
-
- line_diff = '''\
- from paste.deploy import loadapp
-''' + line_uni_diff
-
- lines1 = text2lines(text1)
- lines2 = text2lines(text2)
- diff = patience.unified_diff(lines1, lines2)
- diff = ''.join(diff)
- assert diff == '''\
----
-+++
-@@ -2,9 +2,8 @@
-%s
-
-''' % line_uni_diff, '=' + diff + '='
-
- sm = patience.SequenceMatcher(None, lines1, lines2)
- buf = ''
- for prefix, line in patience.diff_gen(lines1, lines2, sm.get_opcodes()):
- assert prefix[1] == ' '
- buf += prefix[0] + line
- assert buf == line_diff + '\n \n', '=' + buf + '='
diff --git a/Allura/setup.py b/Allura/setup.py
index 6420acd..9b4f5a0 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -113,6 +113,7 @@
create-neighborhood = allura.command:CreateNeighborhoodCommand
create-trove-categories = allura.command:CreateTroveCategoriesCommand
set-neighborhood-features = allura.command:SetNeighborhoodFeaturesCommand
+ pull-rss-feeds = allura.command.rssfeeds:RssFeedsCommand
[easy_widgets.resources]
ew_resources=allura.config.resources:register_ew_resources
diff --git a/Allura/test.ini b/Allura/test.ini
index 55be3ea..a8b1c81 100644
--- a/Allura/test.ini
+++ b/Allura/test.ini
@@ -75,7 +75,7 @@
scm.repos.root = /tmp
-stats.sample_rate=0
+#stats.sample_rate = 0
disable_csrf_protection=1
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index bacc532..5d039b3 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -12,12 +12,14 @@
from formencode import validators
from webob import exc
+from ming.orm import session
+
# Pyforge-specific imports
from allura.app import Application, ConfigOption, SitemapEntry
from allura.app import DefaultAdminController
from allura.lib import helpers as h
from allura.lib.search import search
-from allura.lib.decorators import require_post
+from allura.lib.decorators import require_post, Property
from allura.lib.security import has_access, require_access
from allura.lib import widgets as w
from allura.lib.widgets.subscriptions import SubscribeForm
@@ -56,6 +58,7 @@
ordinal=14
installable=True
config_options = Application.config_options
+ default_external_feeds = []
icons={
24:'images/blog_24.png',
32:'images/blog_32.png',
@@ -67,6 +70,24 @@
self.root = RootController()
self.admin = BlogAdminController(self)
+ @Property
+ def external_feeds_list():
+ def fget(self):
+ globals = BM.Globals.query.get(app_config_id=self.config._id)
+ if globals is not None:
+ external_feeds = globals.external_feeds
+ else:
+ external_feeds = self.default_external_feeds
+ return external_feeds
+ def fset(self, new_external_feeds):
+ globals = BM.Globals.query.get(app_config_id=self.config._id)
+ if globals is not None:
+ globals.external_feeds = new_external_feeds
+ elif len(new_external_feeds) > 0:
+ globals = BM.Globals(app_config_id=self.config._id, external_feeds=new_external_feeds)
+ if globals is not None:
+ session(globals).flush()
+
@property
@h.exceptionless([], log)
def sitemap(self):
@@ -94,7 +115,12 @@
return links
def admin_menu(self):
- return super(ForgeBlogApp, self).admin_menu(force_options=True)
+ admin_url = c.project.url() + 'admin/' + self.config.options.mount_point + '/'
+ # temporarily disabled until some bugs are fixed
+ links = []#[SitemapEntry('External feeds', admin_url + 'exfeed', className='admin_modal')]
+ links += super(ForgeBlogApp, self).admin_menu(force_options=True)
+ return links
+ #return super(ForgeBlogApp, self).admin_menu(force_options=True)
def install(self, project):
'Set up any default permissions and roles here'
@@ -170,7 +196,7 @@
require_access(c.app, 'write')
now = datetime.utcnow()
post = dict(
- state='draft')
+ state='published')
c.form = W.new_post_form
return dict(post=post)
@@ -359,3 +385,34 @@
self.app.config.options['show_discussion'] = show_discussion and True or False
flash('Blog options updated')
redirect(h.really_unicode(c.project.url()+'admin/tools').encode('utf-8'))
+
+ @without_trailing_slash
+ @expose('jinja:forgeblog:templates/blog/admin_exfeed.html')
+ def exfeed(self):
+ #self.app.external_feeds_list = ['feed1', 'feed2']
+ #log.info("EXFEED: %s" % self.app.external_feeds_list)
+ feeds_list = []
+ for feed in self.app.external_feeds_list:
+ feeds_list.append(feed)
+ return dict(app=self.app,
+ feeds_list=feeds_list,
+ allow_config=has_access(self.app, 'configure')())
+
+ @without_trailing_slash
+ @expose()
+ @require_post()
+ def set_exfeed(self, **kw):
+ new_exfeed = kw.get('new_exfeed', None)
+ exfeed_val = kw.get('exfeed', [])
+ if type(exfeed_val) == unicode:
+ exfeed_list = []
+ exfeed_list.append(exfeed_val)
+ else:
+ exfeed_list = exfeed_val
+
+ if new_exfeed is not None and new_exfeed != '':
+ exfeed_list.append(new_exfeed)
+
+ self.app.external_feeds_list = exfeed_list
+ flash('External feeds updated')
+ redirect(c.project.url()+'admin/tools')
diff --git a/ForgeBlog/forgeblog/model/__init__.py b/ForgeBlog/forgeblog/model/__init__.py
index 3f6c73e..3a7399f 100644
--- a/ForgeBlog/forgeblog/model/__init__.py
+++ b/ForgeBlog/forgeblog/model/__init__.py
@@ -1 +1 @@
-from blog import BlogPost, Attachment, BlogPostSnapshot
+from blog import Globals, BlogPost, Attachment, BlogPostSnapshot
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index 938b408..06177d7 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -1,3 +1,4 @@
+import difflib
from datetime import datetime
from random import randint
@@ -9,13 +10,28 @@
from ming import schema
from ming.orm import FieldProperty, ForeignIdProperty, Mapper, session, state
+from ming.orm.declarative import MappedClass
+
from allura import model as M
from allura.lib import helpers as h
-from allura.lib import utils, patience
+from allura.lib import utils
config = utils.ConfigProxy(
common_suffix='forgemail.domain')
+class Globals(MappedClass):
+
+ class __mongometa__:
+ name = 'blog-globals'
+ session = M.project_orm_session
+ indexes = [ 'app_config_id' ]
+
+ type_s = 'BlogGlobals'
+ _id = FieldProperty(schema.ObjectId)
+ app_config_id = ForeignIdProperty('AppConfig', if_missing=lambda:c.app.config._id)
+ external_feeds=FieldProperty([str])
+
+
class BlogPostSnapshot(M.Snapshot):
class __mongometa__:
name='blog_post_snapshot'
@@ -166,7 +182,7 @@
v2 = self
la = [ line + '\n' for line in v1.text.splitlines() ]
lb = [ line + '\n' for line in v2.text.splitlines() ]
- diff = ''.join(patience.unified_diff(
+ diff = ''.join(difflib.unified_diff(
la, lb,
'v%d' % v1.version,
'v%d' % v2.version))
diff --git a/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html b/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html
new file mode 100644
index 0000000..2c337bc
--- /dev/null
+++ b/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html
@@ -0,0 +1,27 @@
+<form method="POST" action="{{c.project.url()}}admin/{{app.config.options.mount_point}}/set_exfeed">
+ <label class="grid-13">Existing external feeds:</label>
+ <div class="grid-13">
+ <ul>
+ {% if allow_config %}
+ {% for feed in feeds_list %}
+ <li><input type="checkbox" name="exfeed" value="{{ feed }}" checked="checked"><span>{{ feed }}</span></li>
+ {% endfor %}
+ {% else %}
+ {% for feed in feeds_list %}
+ <li><span>{{ feed.value }}</span></li>
+ {% endfor %}
+ {% endif %}
+ </div>
+ {% if allow_config %}
+ <div class="grid-13"> </div>
+ <div class="grid-13">
+ <input type="text" name="new_exfeed" id="new_exfeed" value=""/>
+ </div>
+ <div class="grid-13"> </div>
+ <hr>
+ <div class="grid-13"> </div>
+ <div class="grid-13">
+ <input type="submit" value="Save"/>
+ </div>
+ {% endif %}
+</form>
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 4f6a4b1..1f50dc0 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -56,6 +56,7 @@
def test_root_new_post(self):
response = self.app.get('/blog/new')
+ assert '<option selected value="published">Published</option>' in response
assert 'Enter your title here' in response
def test_validation(self):
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 2660955..b9816d7 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -37,18 +37,26 @@
ThreadLocalORMSession.close_all()
def test_fork(self):
+ r = self.app.get('%sfork/' % c.app.repo.url())
+ assert '<input type="text" name="mount_point" value="test"/>' in r
+ assert '<input type="text" name="mount_label" value="test - Git"/>' in r
+
to_project = M.Project.query.get(shortname='test2', neighborhood_id=c.project.neighborhood_id)
+ mount_point = 'reponame'
r = self.app.post('/src-git/fork', params=dict(
project_id=str(to_project._id),
- to_name='code'))
+ mount_point=mount_point,
+ mount_label='Test forked repository'))
+ assert "{status: 'error'}" not in str(r.follow())
cloned_from = c.app.repo
- with h.push_context('test2', 'code', neighborhood='Projects'):
+ with h.push_context('test2', mount_point, neighborhood='Projects'):
c.app.repo.init_as_clone(
cloned_from.full_fs_path,
cloned_from.app.config.script_name(),
cloned_from.full_fs_path)
- r = self.app.get('/p/test2/code').follow().follow().follow()
+ r = self.app.get('/p/test2/%s' % mount_point).follow().follow().follow()
assert 'Clone of' in r
+ assert 'Test forked repository' in r
r = self.app.get('/src-git/').follow().follow()
assert 'Forks' in r
@@ -56,7 +64,7 @@
to_project = M.Project.query.get(shortname='test2', neighborhood_id=c.project.neighborhood_id)
r = self.app.post('/src-git/fork', params=dict(
project_id=str(to_project._id),
- to_name='code'))
+ mount_point='code'))
cloned_from = c.app.repo
with h.push_context('test2', 'code', neighborhood='Projects'):
c.app.repo.init_as_clone(
@@ -147,8 +155,8 @@
def test_file(self):
ci = self._get_ci()
resp = self.app.get(ci + 'tree/README')
- assert 'README' in resp.html.find('h2',{'class':'dark title'}).contents[2]
- content = str(resp.html.find('div',{'class':'clip grid-19'}))
+ assert 'README' in resp.html.find('h2', {'class':'dark title'}).contents[2]
+ content = str(resp.html.find('div', {'class':'clip grid-19'}))
assert 'This is readme' in content, content
assert '<span id="l1" class="code_block">' in resp
assert 'var hash = window.location.hash.substring(1);' in resp
@@ -174,6 +182,6 @@
def test_file_force_display(self):
ci = self._get_ci()
resp = self.app.get(ci + 'tree/README?force=True')
- content = str(resp.html.find('div',{'class':'clip grid-19'}))
+ content = str(resp.html.find('div', {'class':'clip grid-19'}))
assert re.search(r'<pre>.*This is readme', content), content
assert '</pre>' in content, content
diff --git a/ForgeHg/forgehg/tests/functional/test_controllers.py b/ForgeHg/forgehg/tests/functional/test_controllers.py
index 896d550..c01601e 100644
--- a/ForgeHg/forgehg/tests/functional/test_controllers.py
+++ b/ForgeHg/forgehg/tests/functional/test_controllers.py
@@ -1,6 +1,9 @@
import json
import pkg_resources
+import pylons
+pylons.c = pylons.tmpl_context
+pylons.g = pylons.app_globals
from pylons import c
from ming.orm import ThreadLocalORMSession
from datadiff.tools import assert_equal
@@ -35,7 +38,8 @@
to_project = M.Project.query.get(shortname='test2', neighborhood_id=c.project.neighborhood_id)
r = self.app.post('/src-hg/fork', params=dict(
project_id=str(to_project._id),
- to_name='code'))
+ mount_point='code'))
+ assert "{status: 'error'}" not in str(r.follow())
cloned_from = c.app.repo
with h.push_context('test2', 'code', neighborhood='Projects'):
c.app.repo.init_as_clone(
@@ -51,7 +55,8 @@
to_project = M.Project.query.get(shortname='test2', neighborhood_id=c.project.neighborhood_id)
r = self.app.post('/src-hg/fork', params=dict(
project_id=str(to_project._id),
- to_name='code'))
+ mount_point='code'))
+ assert "{status: 'error'}" not in str(r.follow())
cloned_from = c.app.repo
with h.push_context('test2', 'code', neighborhood='Projects'):
c.app.repo.init_as_clone(
@@ -117,14 +122,14 @@
def test_tree(self):
ci = self._get_ci()
resp = self.app.get(ci + 'tree/')
- assert len(resp.html.findAll('tr')) ==2, resp.showbrowser()
+ assert len(resp.html.findAll('tr')) == 2, resp.showbrowser()
assert 'README' in resp, resp.showbrowser()
def test_file(self):
ci = self._get_ci()
resp = self.app.get(ci + 'tree/README')
- assert 'README' in resp.html.find('h2',{'class':'dark title'}).contents[2]
- content = str(resp.html.find('div',{'class':'clip grid-19'}))
+ assert 'README' in resp.html.find('h2', {'class':'dark title'}).contents[2]
+ content = str(resp.html.find('div', {'class':'clip grid-19'}))
assert 'This is readme' in content, content
assert '<span id="l1" class="code_block">' in resp
assert 'var hash = window.location.hash.substring(1);' in resp
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index 399c4f2..128dc2e 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -259,12 +259,15 @@
log.info('ClientError processing %r %r, treating as empty', ci, self._repo, exc_info=True)
log_entry = Object(date=0, message='', changed_paths=[])
# Save commit metadata
+ log_date = None
+ if hasattr(log_entry, 'date'):
+ log_date = datetime.utcfromtimestamp(log_entry.date)
ci.committed = Object(
name=log_entry.get('author', '--none--'),
email='',
- date=datetime.utcfromtimestamp(log_entry.date))
+ date=log_date)
ci.authored=Object(ci.committed)
- ci.message=log_entry.message
+ ci.message=log_entry.get("message", "--none--")
if revno > 1:
parent_oid = self._oid(revno - 1)
ci.parent_ids = [ parent_oid ]
@@ -278,13 +281,14 @@
D=ci.diffs.removed,
M=ci.diffs.changed,
R=ci.diffs.changed)
- for path in log_entry.changed_paths:
- if path.copyfrom_path:
- ci.diffs.copied.append(dict(
- old=h.really_unicode(path.copyfrom_path),
- new=h.really_unicode(path.path)))
- continue
- lst[path.action].append(h.really_unicode(path.path))
+ if hasattr(log_entry, 'changed_paths'):
+ for path in log_entry.changed_paths:
+ if path.copyfrom_path:
+ ci.diffs.copied.append(dict(
+ old=h.really_unicode(path.copyfrom_path),
+ new=h.really_unicode(path.path)))
+ continue
+ lst[path.action].append(h.really_unicode(path.path))
def refresh_commit_info(self, oid, seen_object_ids, lazy=True):
from allura.model.repo import CommitDoc
@@ -301,15 +305,18 @@
except pysvn.ClientError:
log.info('ClientError processing %r %r, treating as empty', oid, self._repo, exc_info=True)
log_entry = Object(date='', message='', changed_paths=[])
+ log_date = None
+ if hasattr(log_entry, 'date'):
+ log_date = datetime.utcfromtimestamp(log_entry.date)
user = Object(
name=log_entry.get('author', '--none--'),
email='',
- date=datetime.utcfromtimestamp(log_entry.date))
+ date=log_date)
args = dict(
tree_id=None,
committed=user,
authored=user,
- message=log_entry.message,
+ message=log_entry.get("message", "--none--"),
parent_ids=[],
child_ids=[])
if revno > 1:
diff --git a/ForgeSVN/forgesvn/tests/functional/test_controllers.py b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
index 7274804..a021b58 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_controllers.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
@@ -73,8 +73,8 @@
def test_file(self):
resp = self.app.get('/src/1/tree/README')
- assert 'README' in resp.html.find('h2',{'class':'dark title'}).contents[2]
- content = str(resp.html.find('div',{'class':'clip grid-19'}))
+ assert 'README' in resp.html.find('h2', {'class':'dark title'}).contents[2]
+ content = str(resp.html.find('div', {'class':'clip grid-19'}))
assert 'This is readme' in content, content
assert '<span id="l1" class="code_block">' in resp
assert 'var hash = window.location.hash.substring(1);' in resp
diff --git a/ForgeTracker/forgetracker/data/ticket_changed_tmpl b/ForgeTracker/forgetracker/data/ticket_changed_tmpl
index 6f735ad..3dbf2f7 100644
--- a/ForgeTracker/forgetracker/data/ticket_changed_tmpl
+++ b/ForgeTracker/forgetracker/data/ticket_changed_tmpl
@@ -1,4 +1,4 @@
-{% python from allura.lib import patience %}\
+{% python import difflib %}\
{% python from allura.model import User %}\
{% for key, values in changelist %}\
{% choose %}\
@@ -9,7 +9,7 @@
~~~~
-${'\n'.join(patience.unified_diff(
+${'\n'.join(difflib.unified_diff(
a=values[0].splitlines(),
b=values[1].splitlines(),
fromfile='old',
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 7e0d19c..0e81a91 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -1,6 +1,7 @@
import logging
import urllib
import json
+import difflib
from datetime import datetime, timedelta
import bson
@@ -20,7 +21,6 @@
from allura.model import Artifact, VersionedArtifact, Snapshot, project_orm_session, BaseAttachment
from allura.model import User, Feed, Thread, Notification, ProjectRole
from allura.model import ACE, ALL_PERMISSIONS, DENY_ALL
-from allura.lib import patience
from allura.lib import security
from allura.lib.search import search_artifact
from allura.lib import utils
@@ -392,7 +392,7 @@
if old.description != self.description:
changes.append('Description updated:')
changes.append('\n'.join(
- patience.unified_diff(
+ difflib.unified_diff(
a=old.description.split('\n'),
b=self.description.split('\n'),
fromfile='description-old',
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index c3635e9..0cd3b7f 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -1241,8 +1241,9 @@
self.app.globals.closed_status_names=post_data['closed_status_names']
custom_fields = post_data.get('custom_fields', [])
for field in custom_fields:
- field['name'] = '_' + '_'.join([
- w for w in NONALNUM_RE.split(field['label'].lower()) if w])
+ if 'name' not in field or not field['name']:
+ field['name'] = '_' + '_'.join([
+ w for w in NONALNUM_RE.split(field['label'].lower()) if w])
if field['type'] == 'milestone':
field.setdefault('milestones', [])
diff --git a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py b/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
index c222202..4b2dacc 100644
--- a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
+++ b/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
@@ -81,6 +81,7 @@
yield ew.JSLink('tracker_js/custom-fields.js')
fields = [
+ ew.HiddenField(name='name'),
ew.TextField(name='label'),
ew.Checkbox(
name='show_in_search',
diff --git a/ForgeWiki/forgewiki/model/wiki.py b/ForgeWiki/forgewiki/model/wiki.py
index 2317aa8..0b41a13 100644
--- a/ForgeWiki/forgewiki/model/wiki.py
+++ b/ForgeWiki/forgewiki/model/wiki.py
@@ -1,4 +1,5 @@
import pylons
+import difflib
pylons.c = pylons.tmpl_context
pylons.g = pylons.app_globals
from pylons import g #g is a namespace for globally accessable app helpers
@@ -11,7 +12,6 @@
from allura.model import VersionedArtifact, Snapshot, Feed, Thread, Post, User, BaseAttachment
from allura.model import Notification, project_orm_session
from allura.lib import helpers as h
-from allura.lib import patience
from allura.lib import utils
config = utils.ConfigProxy(
@@ -86,7 +86,7 @@
v2 = self
la = [ line + '\n' for line in v1.text.splitlines() ]
lb = [ line + '\n' for line in v2.text.splitlines() ]
- diff = ''.join(patience.unified_diff(
+ diff = ''.join(difflib.unified_diff(
la, lb,
'v%d' % v1.version,
'v%d' % v2.version))
diff --git a/requirements-common.txt b/requirements-common.txt
index c6b1a0f..19dfc9f 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -15,6 +15,7 @@
# dep of Creoleparser
Genshi==0.6
# dep of oauth2
+html2text==3.200.3
httplib2==0.7.4
iso8601==0.1.4
Jinja2==2.6
@@ -39,6 +40,7 @@
textile==2.1.5
# dep of colander
translationstring==0.4
+TimerMiddleware==0.2.1
TurboGears2==2.1.3
# part of the stdlib, but with a version number. see http://guide.python-distribute.org/pip.html#listing-installed-packages
wsgiref==0.1.2
diff --git a/requirements-sf.txt b/requirements-sf.txt
index a5d07c1..c1135d4 100644
--- a/requirements-sf.txt
+++ b/requirements-sf.txt
@@ -14,7 +14,6 @@
pyzmq==2.1.7
# for the migration scripts only
-html2text==3.101
postmarkup==1.1.4
# suds needed for teamforge import script
suds==0.4
diff --git a/scripts/migrations/010-fix-home-permissions.py b/scripts/migrations/010-fix-home-permissions.py
index 05212da..f66e76a 100644
--- a/scripts/migrations/010-fix-home-permissions.py
+++ b/scripts/migrations/010-fix-home-permissions.py
@@ -7,6 +7,7 @@
from bson import ObjectId
from allura import model as M
+from allura.lib import utils
from forgewiki.wiki_main import ForgeWikiApp
log = logging.getLogger('fix-home-permissions')
@@ -22,8 +23,9 @@
else:
log.info('Fixing permissions for all Home Wikis')
- for some_projects in chunked_project_iterator({'neighborhood_id': {'$nin': [ObjectId('4be2faf8898e33156f00003e'), # /u
- ObjectId('4dbf2563bfc09e6362000005')]}}): # /motorola
+ for some_projects in utils.chunked_find(M.Project, {'neighborhood_id': {
+ '$nin': [ObjectId('4be2faf8898e33156f00003e'), # /u
+ ObjectId('4dbf2563bfc09e6362000005')]}}): # /motorola
for project in some_projects:
c.project = project
home_app = project.app_instance('home')
@@ -56,21 +58,6 @@
home_app.config.acl = map(dict, new_acl.values())
session(home_app.config).flush()
-PAGESIZE=1024
-
-def chunked_project_iterator(q_project):
- '''shamelessly copied from refresh-all-repos.py'''
- page = 0
- while True:
- results = (M.Project.query
- .find(q_project)
- .skip(PAGESIZE*page)
- .limit(PAGESIZE)
- .all())
- if not results: break
- yield results
- page += 1
-
def project_role(project, name):
role = M.ProjectRole.query.get(project_id=project._id, name=name)
if role is None:
diff --git a/scripts/migrations/011-fix-subroles.py b/scripts/migrations/011-fix-subroles.py
index c982a87..2d1e007 100644
--- a/scripts/migrations/011-fix-subroles.py
+++ b/scripts/migrations/011-fix-subroles.py
@@ -12,11 +12,11 @@
import sys
import logging
-from pylons import c
from ming.orm import session
from ming.orm.ormsession import ThreadLocalORMSession
from allura import model as M
+from allura.lib import utils
log = logging.getLogger('fix-subroles')
log.addHandler(logging.StreamHandler(sys.stdout))
@@ -27,7 +27,7 @@
log.info('Examining subroles in all non-user projects.')
n_users = M.Neighborhood.query.get(name='Users')
project_filter = dict(neighborhood_id={'$ne':n_users._id})
- for some_projects in chunked_project_iterator(project_filter):
+ for some_projects in utils.chunked_find(M.Project, project_filter):
for project in some_projects:
project_name = '%s.%s' % (project.neighborhood.name, project.shortname)
project_roles = {}
@@ -53,7 +53,7 @@
for user in project.users():
pr = user.project_role(project=project)
if not pr.roles: continue
- for parent, children in [('Admin', ('Developer', 'Member')),
+ for parent, children in [('Admin', ('Developer', 'Member')),
('Developer', ('Member',))]:
if project_roles[parent]._id not in pr.roles: continue
for role_name in children:
@@ -73,21 +73,5 @@
log.info('%s projects examined.' % num_projects_examined)
-
-PAGESIZE=1024
-
-def chunked_project_iterator(q_project):
- '''shamelessly copied from refresh-all-repos.py'''
- page = 0
- while True:
- results = (M.Project.query
- .find(q_project)
- .skip(PAGESIZE*page)
- .limit(PAGESIZE)
- .all())
- if not results: break
- yield results
- page += 1
-
if __name__ == '__main__':
main()
diff --git a/scripts/migrations/012-uninstall-home.py b/scripts/migrations/012-uninstall-home.py
index 7f1f9f5..a8b66f5 100644
--- a/scripts/migrations/012-uninstall-home.py
+++ b/scripts/migrations/012-uninstall-home.py
@@ -7,6 +7,7 @@
from mock import Mock, patch
from allura.lib import helpers as h
+from allura.lib import utils
from allura import model as M
from forgewiki import model as WM
from allura.ext.project_home import ProjectHomeApp
@@ -21,7 +22,8 @@
possibly_orphaned_projects = 0
solr_delete = Mock()
notification_post = Mock()
- for some_projects in chunked_project_iterator({'neighborhood_id': {'$ne': ObjectId("4be2faf8898e33156f00003e")}}):
+ for some_projects in utils.chunked_find(M.Project, {'neighborhood_id': {
+ '$ne': ObjectId("4be2faf8898e33156f00003e")}}):
for project in some_projects:
c.project = project
old_home_app = project.app_instance('home')
@@ -102,20 +104,5 @@
assert solr_delete.call_count == affected_projects, solr_delete.call_count
assert notification_post.call_count == 2 * affected_projects, notification_post.call_count
-PAGESIZE=1024
-
-def chunked_project_iterator(q_project):
- '''shamelessly copied from refresh-all-repos.py'''
- page = 0
- while True:
- results = (M.Project.query
- .find(q_project)
- .skip(PAGESIZE*page)
- .limit(PAGESIZE)
- .all())
- if not results: break
- yield results
- page += 1
-
if __name__ == '__main__':
main()
diff --git a/scripts/migrations/013-update-ordinals.py b/scripts/migrations/013-update-ordinals.py
index 9d8b13d..ef81d32 100644
--- a/scripts/migrations/013-update-ordinals.py
+++ b/scripts/migrations/013-update-ordinals.py
@@ -6,6 +6,7 @@
from ming.orm.ormsession import ThreadLocalORMSession
from allura import model as M
+from allura.lib import utils
log = logging.getLogger('update-ordinals')
log.addHandler(logging.StreamHandler(sys.stdout))
@@ -14,7 +15,7 @@
test = sys.argv[-1] == 'test'
num_projects_examined = 0
log.info('Examining all projects for mount order.')
- for some_projects in chunked_project_iterator({}):
+ for some_projects in utils.chunked_find(M.Project):
for project in some_projects:
c.project = project
mounts = project.ordered_mounts(include_search=True)
@@ -47,21 +48,5 @@
ThreadLocalORMSession.flush_all()
ThreadLocalORMSession.close_all()
-
-PAGESIZE=1024
-
-def chunked_project_iterator(q_project):
- '''shamelessly copied from refresh-all-repos.py'''
- page = 0
- while True:
- results = (M.Project.query
- .find(q_project)
- .skip(PAGESIZE*page)
- .limit(PAGESIZE)
- .all())
- if not results: break
- yield results
- page += 1
-
if __name__ == '__main__':
main()
diff --git a/scripts/migrations/023-migrate-custom-profile-text.py b/scripts/migrations/023-migrate-custom-profile-text.py
deleted file mode 100644
index e2eda7d..0000000
--- a/scripts/migrations/023-migrate-custom-profile-text.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import logging
-import re
-
-from pylons import c
-
-from ming.orm import ThreadLocalORMSession
-
-from allura import model as M
-from forgewiki import model as WM
-from forgewiki.wiki_main import ForgeWikiApp
-
-log = logging.getLogger(__name__)
-
-default_description = r'^\s*(?:You can edit this description in the admin page)?\s*$'
-
-default_personal_project_tmpl = ("This is the personal project of %s."
- " This project is created automatically during user registration"
- " as an easy place to store personal data that doesn't need its own"
- " project such as cloned repositories.\n\n%s")
-
-def main():
- for p in M.Project.query.find().all():
- user = p.user_project_of
- if not user:
- continue
-
- description = p.description
- if description is None or re.match(default_description, description):
- continue
-
- app = p.app_instance('wiki')
- if app is None:
- p.install_app('wiki')
-
- page = WM.Page.query.get(app_config_id=app.config._id, title='Home')
- if page is None:
- continue
-
- c.app = app
- c.project = p
- c.user = user
-
- if "This is the personal project of" in page.text:
- if description not in page.text:
- page.text = "%s\n\n%s" % (page.text, description)
- log.info("Update wiki home page text for %s" % user.username)
- elif "This is the default page" in page.text:
- page.text = default_personal_project_tmpl % (user.display_name, description)
- log.info("Update wiki home page text for %s" % user.username)
- else:
- pass
-
- ThreadLocalORMSession.flush_all()
-
-if __name__ == '__main__':
- main()
diff --git a/scripts/migrations/024-migrate-custom-profile-text.py b/scripts/migrations/024-migrate-custom-profile-text.py
new file mode 100644
index 0000000..db199eb
--- /dev/null
+++ b/scripts/migrations/024-migrate-custom-profile-text.py
@@ -0,0 +1,59 @@
+import logging
+import re
+
+from pylons import c
+
+from ming.orm import ThreadLocalORMSession
+
+from allura import model as M
+from allura.lib import utils
+from forgewiki import model as WM
+from forgewiki.wiki_main import ForgeWikiApp
+
+log = logging.getLogger(__name__)
+
+default_description = r'^\s*(?:You can edit this description in the admin page)?\s*$'
+
+default_personal_project_tmpl = ("This is the personal project of %s."
+ " This project is created automatically during user registration"
+ " as an easy place to store personal data that doesn't need its own"
+ " project such as cloned repositories.\n\n%s")
+
+def main():
+ users = M.Neighborhood.query.get(name='Users')
+ for chunk in utils.chunked_find(M.Project, {'neighborhood_id': users._id}):
+ for p in chunk:
+ user = p.user_project_of
+ if not user:
+ continue
+
+ description = p.description
+ if description is None or re.match(default_description, description):
+ continue
+
+ app = p.app_instance('wiki')
+ if app is None:
+ p.install_app('wiki')
+
+ page = WM.Page.query.get(app_config_id=app.config._id, title='Home')
+ if page is None:
+ continue
+
+ c.app = app
+ c.project = p
+ c.user = user
+
+ if "This is the personal project of" in page.text:
+ if description not in page.text:
+ page.text = "%s\n\n%s" % (page.text, description)
+ log.info("Update wiki home page text for %s" % user.username)
+ elif "This is the default page" in page.text:
+ page.text = default_personal_project_tmpl % (user.display_name, description)
+ log.info("Update wiki home page text for %s" % user.username)
+ else:
+ pass
+
+ ThreadLocalORMSession.flush_all()
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/refresh-all-repos.py b/scripts/refresh-all-repos.py
index c62d0f3..99cb4d1 100644
--- a/scripts/refresh-all-repos.py
+++ b/scripts/refresh-all-repos.py
@@ -1,11 +1,11 @@
import logging
import optparse
-from collections import defaultdict
from pylons import c
from ming.orm import ThreadLocalORMSession
from allura import model as M
+from allura.lib import utils
log = logging.getLogger(__name__)
@@ -45,7 +45,7 @@
M.repo.DiffInfoDoc.m.remove({})
M.repo.LastCommitDoc.m.remove({})
M.repo.CommitRunDoc.m.remove({})
- for chunk in chunked_project_iterator(q_project):
+ for chunk in utils.chunked_find(M.Project, q_project):
for p in chunk:
c.project = p
if projects:
@@ -73,18 +73,5 @@
ThreadLocalORMSession.flush_all()
ThreadLocalORMSession.close_all()
-def chunked_project_iterator(q_project):
- page = 0
- while True:
- results = (M.Project.query
- .find(q_project)
- .skip(PAGESIZE*page)
- .limit(PAGESIZE)
- .all())
- if not results: break
- yield results
- page += 1
-
-
if __name__ == '__main__':
main()