• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

allura


Commit MetaInfo

Revision0891930e3979da21ed0c27ea03ed877ce8559e43 (tree)
Time2012-03-22 04:33:16
AuthorYaroslav Luzin <jardev@gmai...>
CommiterYaroslav Luzin

Log Message

Merge branch 'master' into t27_donotshowcss

Change Summary

Incremental Difference

--- a/Allura/allura/command/set_neighborhood_level.py
+++ b/Allura/allura/command/set_neighborhood_level.py
@@ -1,8 +1,8 @@
1-from . import base
1+from allura.command import base
22
33 from bson import ObjectId
44 from allura import model as M
5-from allura.lib import plugin
5+from allura.lib import plugin, exceptions
66 from ming.orm import session
77
88
@@ -12,8 +12,9 @@ class SetNeighborhoodLevelCommand(base.Command):
1212 min_args = 3
1313 max_args = 3
1414 usage = "<ini file> <neighborhood> <level>" # not sure if we need ini file
15- summary = "Change neighborhood level. <neighgborhood> - " \
16- "should neightborhood's name or id"
15+ summary = "Change the neighborhood level\r\n" \
16+ "\t<neighgborhood> - the neighborhood name or object id\r\n" \
17+ "\t<level> - silver, gold or platinum"
1718 parser = base.Command.standard_parser(verbose=True)
1819
1920 def command(self):
@@ -21,15 +22,17 @@ class SetNeighborhoodLevelCommand(base.Command):
2122 n_id = self.args[1]
2223 n_level = self.args[2]
2324 if n_level not in ["silver", "gold", "platinum"]:
24- base.log.error("You must select one of three level types (silver, gold, or platinum)")
25- return
25+ raise exceptions.NoSuchNBLevelError("%s is not a valid " \
26+ "neighborhood level. The valid levels are \"silver\", " \
27+ "\"gold\" and \"platinum\"" % n_level)
2628
2729 n = M.Neighborhood.query.get(name=n_id)
2830 if not n:
2931 n = M.Neighborhood.query.get(_id=ObjectId(n_id))
3032
3133 if not n:
32- base.log.error("The neighborhood % scould not be found" % n_id)
34+ raise exceptions.NoSuchNeighborhoodError("The neighborhood %s " \
35+ "could not be found in the database" % n_id)
3336 else:
3437 n.level = n_level
3538 if n_level == "gold":
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -87,10 +87,10 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
8787
8888 if config.get('zarkov.host'):
8989 try:
90- from zarkov import client as zclient
91- except ImportError, e:
92- raise ImportError, "Unable to import the zarkov library. Please"\
93- " check that zarkov is installed or comment out"\
90+ import zmq
91+ except ImportError:
92+ raise ImportError, "Unable to import the zmq library. Please"\
93+ " check that zeromq is installed or comment out"\
9494 " the zarkov.host setting in your ini file."
9595
9696 app = tg.TGApp()
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -384,6 +384,8 @@ class CommitBrowser(BaseController):
384384 result = dict(commit=self._commit)
385385 if self._commit:
386386 result.update(self._commit.context())
387+ result['artifacts'] = [(t,f) for t in ('added', 'removed', 'changed', 'copied')
388+ for f in self._commit.diffs[t]]
387389 return result
388390
389391 @expose('jinja:allura:templates/repo/commit_basic.html')
@@ -473,6 +475,9 @@ class FileBrowser(BaseController):
473475 elif 'diff' in kw:
474476 tg.decorators.override_template(self.index, 'jinja:allura:templates/repo/diff.html')
475477 return self.diff(kw['diff'])
478+ elif 'barediff' in kw:
479+ tg.decorators.override_template(self.index, 'jinja:allura:templates/repo/barediff.html')
480+ return self.diff(kw['barediff'])
476481 else:
477482 force_display = 'force' in kw
478483 context = self._blob.context()
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -4,14 +4,11 @@
44
55 __all__ = ['Globals']
66 import logging
7-import socket
87 import cgi
98 import json
109 import shlex
1110 import datetime
1211 from urllib import urlencode
13-from ConfigParser import RawConfigParser
14-from collections import defaultdict
1512
1613 import pkg_resources
1714
@@ -21,7 +18,6 @@ import pygments
2118 import pygments.lexers
2219 import pygments.formatters
2320 import pygments.util
24-import webob.exc
2521 from tg import config, session
2622 from pylons import c, request
2723 from paste.deploy.converters import asbool, asint
@@ -31,11 +27,6 @@ import ew as ew_core
3127 import ew.jinja2_ew as ew
3228 from ming.utils import LazyProperty
3329
34-try:
35- from zarkov import client as zclient
36-except ImportError:
37- zclient = None
38-
3930 import allura.tasks.event_tasks
4031 from allura import model as M
4132 from allura.lib.markdown_extensions import ForgeExtension
@@ -45,6 +36,7 @@ from allura.lib import helpers as h
4536 from allura.lib.widgets import analytics
4637 from allura.lib.security import Credentials
4738 from allura.lib.async import Connection, MockAMQ
39+from allura.lib.zarkov_helpers import ZarkovClient, zmq
4840
4941 log = logging.getLogger(__name__)
5042
@@ -173,7 +165,7 @@ class Globals(object):
173165 mount_point=None,
174166 is_project_member=False)
175167
176- if not zclient:
168+ if not zmq:
177169 return
178170
179171 user = user or getattr(c, 'user', None)
@@ -197,7 +189,7 @@ class Globals(object):
197189
198190 try:
199191 if self._zarkov is None:
200- self._zarkov = zclient.ZarkovClient(
192+ self._zarkov = ZarkovClient(
201193 config.get('zarkov.host', 'tcp://127.0.0.1:6543'))
202194 self._zarkov.event(event_type, context, extra)
203195 except Exception, ex:
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -5,6 +5,7 @@ class NoSuchProjectError(ForgeError): pass
55 class NoSuchNeighborhoodError(ForgeError): pass
66 class MailError(ForgeError): pass
77 class AddressException(MailError): pass
8+class NoSuchNBLevelError(ForgeError): pass
89
910 class CompoundError(ForgeError):
1011 def __repr__(self):
--- a/Allura/allura/lib/zarkov_helpers.py
+++ b/Allura/allura/lib/zarkov_helpers.py
@@ -1,6 +1,24 @@
11 import calendar
22 from datetime import datetime, timedelta
33
4+try:
5+ import zmq
6+except ImportError:
7+ zmq = None
8+import bson
9+
10+class ZarkovClient(object):
11+
12+ def __init__(self, addr):
13+ context = zmq.Context.instance()
14+ self._sock = context.socket(zmq.PUSH)
15+ self._sock.connect(addr)
16+
17+ def event(self, type, context, extra=None):
18+ obj = dict(
19+ type=type, context=context, extra=extra)
20+ self._sock.send(bson.BSON.encode(obj))
21+
422 def zero_fill_zarkov_result(zarkov_data, period, start_date, end_date):
523 """Return a new copy of zarkov_data (a dict returned from a zarkov
624 query) with the timeseries data zero-filled for missing dates.
@@ -36,10 +54,8 @@ def zero_fill_time_series(time_series, period, start_date, end_date):
3654 time_series (list): A list of [timestamp, value] pairs, e.g.:
3755 [[1306886400000.0, 1], [1309478400000.0, 0]]
3856 period (str): 'month' or 'date' for monthly or daily timestamps
39- start_date (datetime or str): Start of the date range. If a str is
40- passed, it must be in %Y-%m-%d format.
41- end_date (datetime or str): End of the date range. If a str is
42- passed, it must be in %Y-%m-%d format.
57+ start_date (datetime): Start of the date range.
58+ end_date (datetime or str): End of the date range.
4359
4460 Returns:
4561 list. A new copy of time_series, zero-filled.
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -172,8 +172,10 @@ class Thread(Artifact):
172172 Feed.post(self, title=p.subject, description=p.text)
173173 return p
174174
175- def post(self, text, message_id=None, parent_id=None, timestamp=None, **kw):
176- require_access(self, 'post')
175+ def post(self, text, message_id=None, parent_id=None,
176+ timestamp=None, ignore_security=False, **kw):
177+ if not ignore_security:
178+ require_access(self, 'post')
177179 if self.ref_id and self.artifact:
178180 self.artifact.subscribe()
179181 if message_id is None: message_id = h.gen_message_id()
@@ -190,7 +192,7 @@ class Thread(Artifact):
190192 if timestamp is not None: kwargs['timestamp'] = timestamp
191193 if message_id is not None: kwargs['_id'] = message_id
192194 post = self.post_class()(**kwargs)
193- if has_access(self, 'unmoderated_post')():
195+ if ignore_security or has_access(self, 'unmoderated_post')():
194196 log.info('Auto-approving message from %s', c.user.username)
195197 post.approve()
196198 else:
Binary files /dev/null and b/Allura/allura/public/nf/images/spinner.gif differ
--- /dev/null
+++ b/Allura/allura/templates/repo/barediff.html
@@ -0,0 +1,10 @@
1+{% if a.has_image_view and b.has_image_view %}
2+ <img src="{{a.url()}}?format=raw"
3+ alt="{{h.text.truncate(a._commit._id, 10)}}"
4+ title="{{h.text.truncate(a._commit._id, 10)}}"/>
5+ <img src="{{b.url()}}?format=raw"
6+ alt="{{h.text.truncate(b._commit._id, 10)}}"
7+ title="{{h.text.truncate(b._commit._id, 10)}}"/>
8+{% else %}
9+ {{g.highlight(diff, lexer='diff')}}
10+{% endif %}
--- a/Allura/allura/templates/repo/commit.html
+++ b/Allura/allura/templates/repo/commit.html
@@ -15,38 +15,47 @@ Commit <a href="{{commit.url()}}">{{commit.shorthand_id()}}</a> {{commit_labels(
1515 {% endblock %}
1616
1717 {% block content %}
18- {{ clone_info(c.app.repo) }}
18+{{ clone_info(c.app.repo) }}
1919 {{c.revision_widget.display(value=commit, prev=prev, next=next)}}
2020 <table>
2121 <tbody>
22- {% for diff in commit.diffs.added %}
22+ {% for type, file in artifacts %}
2323 <tr>
24- <td>add</td>
25- <td><a href="{{commit.url()}}tree{{h.really_unicode(diff)}}">{{h.really_unicode(diff)}}</a></td>
26- </tr>
27- {% endfor %}{% for diff in commit.diffs.removed %}
28- <tr>
29- <td>remove</td>
30- <td><a href="{{prev[0].url()}}tree{{h.really_unicode(diff)}}">{{h.really_unicode(diff)}}</a></td>
31- </tr>
32- {% endfor %}{% for diff in commit.diffs.changed %}
33- <tr>
34- <td>change</td>
35- <td>
36- <a href="{{commit.url()}}tree{{h.really_unicode(diff)}}">{{h.really_unicode(diff)}}</a>
37- <a href="{{commit.url()}}tree{{h.really_unicode(diff)}}?diff={{prev[0].object_id}}">(diff)</a>
38- </td>
39- </tr>
40- {% endfor %}{% for diff in commit.diffs.copied %}
41- <tr>
42- <td>copy</td>
43- <td>
44- <a href="{{prev[0].url()}}tree{{h.really_unicode(diff.old)}}">{{h.really_unicode(diff.old)}}</a>
45- <br/>to<br/>
46- <a href="{{commit.url()}}tree{{h.really_unicode(diff.new)}}">{{h.really_unicode(diff.new)}}</a>
47- </td>
24+ <td>{{ type }}</td>
25+ <td><a href="#diff-{{loop.index}}">{{h.really_unicode(file)}}</a></td>
4826 </tr>
4927 {% endfor %}
5028 </tbody>
5129 </table>
30+
31+{% for type, file in artifacts %}
32+ <div class="inline-diff">
33+ <h6>
34+ {% if type in ('added', 'changed') %}
35+ <a href="{{commit.url()}}tree{{h.really_unicode(file)}}">{{h.really_unicode(file)}}</a>
36+ <a class="commit-diff-link" href="{{commit.url()}}tree{{h.really_unicode(file)}}?diff={{prev[0].object_id}}">Diff</a>
37+ {% elif type == 'removed' %}
38+ <a href="{{prev[0].url()}}tree{{h.really_unicode(file)}}">{{h.really_unicode(file)}}</a>
39+ {% elif type == 'copied' %}
40+ <a href="{{prev[0].url()}}tree{{h.really_unicode(file.old)}}">{{h.really_unicode(file.old)}}</a>
41+ to
42+ <a href="{{commit.url()}}tree{{h.really_unicode(file.new)}}">{{h.really_unicode(file.new)}}</a>
43+ {% endif %}
44+ </h6>
45+ <div id="diff-{{loop.index}}" class="inline-diff-body">
46+ {% if type != 'removed' %}
47+ <img src="{{g.forge_static('images/spinner.gif')}}" class="loading_icon" alt="Loading..."/>
48+ {% else %}
49+ <span class="empty-diff">File was removed.</span>
50+ {% endif %}
51+ </div>
52+ </div>
53+ {% if type != 'removed' %}
54+ <script type="text/javascript">
55+ $(document).ready(function() {
56+ $('#diff-{{loop.index}}').load('{{commit.url()}}tree{{h.really_unicode(file)}}?barediff={{prev[0].object_id}}');
57+ });
58+ </script>
59+ {% endif %}
60+{% endfor %}
5261 {% endblock %}
--- a/Allura/allura/templates/repo/diff.html
+++ b/Allura/allura/templates/repo/diff.html
@@ -2,12 +2,16 @@
22 {% do g.register_forge_css('css/forge/hilite.css') %}
33
44 {% block title %}
5- {{c.project.name}} / {{c.app.config.options.mount_label}} / Diff of {{h.really_unicode(a.path())}}
5+ {{c.project.name}} / {{c.app.config.options.mount_label}} / Diff of {{h.really_unicode(b.path())}}
66 {% endblock %}
77
88 {% block header %}Diff of
99 <a href="{{b.url()}}">{{b.path()}}</a>
10+{% if a %}
1011 <a href="{{a.url()}}">{{a.commit.shorthand_id()}}</a>
12+{% else %}
13+[000000]
14+{% endif %}
1115 ..
1216 <a href="{{b.url()}}">{{b.commit.shorthand_id()}}</a>
1317 {% endblock %}
--- a/Allura/allura/templates/widgets/repo/revision.html
+++ b/Allura/allura/templates/widgets/repo/revision.html
@@ -1,38 +1,45 @@
11 {% from 'allura:templates/jinja_master/lib.html' import email_gravatar, abbr_date with context %}
2-<p>
3-{% if value.author_url %}
4- Authored by
5- <a href="{{value.author_url}}">{{email_gravatar(value.authored.email, title=h.really_unicode(value.authored.name), size=16)}}</a>
6- <a href="{{value.author_url}}">{{h.really_unicode(value.authored.name)}}</a>
7-{% else %}
8- Authored by
9- {{email_gravatar(value.authored.email, title=h.really_unicode(value.authored.name), size=16)}} {{h.really_unicode(value.authored.name)}}
10-{% endif %}
11-{% if value.authored.date %}{{abbr_date(value.authored.date)}}{% endif %}
12-{% if value.committed.email != value.authored.email %}
13- {% if value.committer_url %}
14- Committed by
15- <a href="{{value.committer_url}}">{{email_gravatar(value.committed.email, title=h.really_unicode(value.committed.name), size=16)}}</a>
16- <a href="{{value.committer_url}}">{{h.really_unicode(value.committed.name)}}</a>
17- {% else %}
18- Committed by
19- {{email_gravatar(value.committed.email, title=h.really_unicode(value.committed.name), size=16)}} {{h.really_unicode(value.committed.name)}}
20- {% endif %}
21- {% if value.committed.date %}{{abbr_date(value.committed.date)}}{% endif %}
22-{% endif %}
23-</p>
2+<div class="commit-details">
3+ <div class="commit-message">
4+ <div class="first-line">{{g.markdown.convert(h.really_unicode(value.message.split('\n')[0]))}}</div>
5+ {{g.markdown.convert(h.really_unicode('\n'.join(value.message.split('\n')[1:])))}}
6+ </div>
7+ <h2 class="commit-details">
8+ <ul class="commit-links">
9+ <li><a class="commit-tree-link" href="{{value.url()}}tree/">Tree</a></li>
10+ {% if prev %}
11+ <li class="commit-parents">
12+ Parent(s):
13+ {% for ci in prev %}<a href="{{ci.url()}}">{{ci.shorthand_id()}}</a>{% endfor %}
14+ </li>
15+ {% endif %}
16+ {% if next %}
17+ <li class="commit-children">
18+ Child(ren):
19+ {% for ci in next %}<a href="{{ci.url()}}">{{ci.shorthand_id()}}</a>{% endfor %}
20+ </li>
21+ {% endif %}
22+ </ul>
2423
25-<p><a href="{{value.url()}}tree/">Tree</a></p>
26-{% if prev %}
27-<p>
28- Parent(s):
29- {% for ci in prev %}<a href="{{ci.url()}}">{{ci.shorthand_id()}}</a>{% endfor %}
30-</p>
31-{% endif %}
32-{% if next %}
33-<p>
34- Child(ren):
35- {% for ci in next %}<a href="{{ci.url()}}">{{ci.shorthand_id()}}</a>{% endfor %}
36-</p>
37-{% endif %}
38-{{g.markdown.convert(h.really_unicode(value.message))}}
24+ Authored by
25+ {% if value.author_url %}
26+ <a href="{{value.author_url}}">{{email_gravatar(value.authored.email, title=h.really_unicode(value.authored.name), size=16)}}</a>
27+ <a href="{{value.author_url}}">{{h.really_unicode(value.authored.name)}}</a>
28+ {% else %}
29+ {{email_gravatar(value.authored.email, title=h.really_unicode(value.authored.name), size=16)}} {{h.really_unicode(value.authored.name)}}
30+ {% endif %}
31+
32+ {% if value.authored.date %}{{abbr_date(value.authored.date)}}{% endif %}
33+
34+ {% if value.committed.email != value.authored.email %}
35+ Committed by
36+ {% if value.committer_url %}
37+ <a href="{{value.committer_url}}">{{email_gravatar(value.committed.email, title=h.really_unicode(value.committed.name), size=16)}}</a>
38+ <a href="{{value.committer_url}}">{{h.really_unicode(value.committed.name)}}</a>
39+ {% else %}
40+ {{email_gravatar(value.committed.email, title=h.really_unicode(value.committed.name), size=16)}} {{h.really_unicode(value.committed.name)}}
41+ {% endif %}
42+ {% if value.committed.date %}{{abbr_date(value.committed.date)}}{% endif %}
43+ {% endif %}
44+ </h2>
45+</div>
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -11,7 +11,7 @@ from nose.tools import assert_raises, assert_equals, with_setup
1111 import mock
1212
1313 from ming.orm.ormsession import ThreadLocalORMSession
14-from webob import Request, Response
14+from webob import Request, Response, exc
1515
1616 from allura import model as M
1717 from allura.lib.app_globals import Globals
@@ -192,4 +192,14 @@ def test_post_delete():
192192 ThreadLocalORMSession.flush_all()
193193 p.delete()
194194
195-
195+@with_setup(setUp, tearDown)
196+def test_post_permission_check():
197+ d = M.Discussion(shortname='test', name='test')
198+ t = M.Thread(discussion_id=d._id, subject='Test Thread')
199+ c.user = M.User.anonymous()
200+ try:
201+ p1 = t.post('This post will fail the check.')
202+ assert False, "Expected an anonymous post to fail."
203+ except exc.HTTPUnauthorized:
204+ pass
205+ p2 = t.post('This post will pass the check.', ignore_security=True)
\ No newline at end of file
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -4,7 +4,7 @@ from datadiff.tools import assert_equal
44 import pylons
55
66 from alluratest.controller import setup_basic_test, setup_global_objects
7-from allura.command import script
7+from allura.command import script, set_neighborhood_level
88 from allura import model as M
99
1010
@@ -23,3 +23,14 @@ def test_script():
2323 cmd.run([test_config, 'allura/tests/tscript.py' ])
2424 cmd.command()
2525 assert_raises(ValueError, cmd.run, [test_config, 'allura/tests/tscript_error.py' ])
26+
27+def test_set_neighborhood_level():
28+ neighborhood = M.Neighborhood.query.find().first()
29+ n_id = neighborhood._id
30+
31+ cmd = set_neighborhood_level.SetNeighborhoodLevelCommand('setnblevel')
32+ cmd.run([test_config, str(n_id), 'gold'])
33+ cmd.command()
34+
35+ neighborhood = M.Neighborhood.query.get(_id=n_id)
36+ assert neighborhood.level == 'gold'
--- /dev/null
+++ b/Allura/allura/tests/test_zarkov_helpers.py
@@ -0,0 +1,110 @@
1+# -*- coding: utf-8 -*-
2+import unittest
3+from calendar import timegm
4+from datetime import datetime
5+
6+import bson
7+import mock
8+
9+from allura.lib import zarkov_helpers as zh
10+
11+class TestZarkovClient(unittest.TestCase):
12+
13+ def setUp(self):
14+ addr = 'tcp://0.0.0.0:0'
15+ ctx = mock.Mock()
16+ self.socket = mock.Mock()
17+ ctx.socket = mock.Mock(return_value=self.socket)
18+ PUSH=mock.Mock()
19+ with mock.patch('allura.lib.zarkov_helpers.zmq') as zmq:
20+ zmq.PUSH=PUSH
21+ zmq.Context.instance.return_value = ctx
22+ self.client = zh.ZarkovClient(addr)
23+ zmq.Context.instance.assert_called_once_with()
24+ ctx.socket.assert_called_once_with(PUSH)
25+ self.socket.connect.assert_called_once_with(addr)
26+
27+ def test_event(self):
28+ self.client.event('test', dict(user='testuser'))
29+ obj = bson.BSON.encode(dict(
30+ type='test',
31+ context=dict(user='testuser'),
32+ extra=None))
33+ self.socket.send.assert_called_once_with(obj)
34+
35+class TestZeroFill(unittest.TestCase):
36+
37+ def setUp(self):
38+ self.dt_begin = datetime(2010, 6, 1)
39+ self.dt_end = datetime(2011, 7, 1)
40+ ts_begin = timegm(self.dt_begin.timetuple())
41+ ts_end = timegm(self.dt_end.timetuple())
42+ self.ts_ms_begin = ts_begin * 1000.0
43+ self.ts_ms_end = ts_end * 1000.0
44+ self.zarkov_data = dict(
45+ a=dict(
46+ a1=[ (self.ts_ms_begin, 1000), (self.ts_ms_end, 1000) ],
47+ a2=[ (self.ts_ms_begin, 1000), (self.ts_ms_end, 1000) ] ),
48+ b=dict(
49+ b1=[ (self.ts_ms_begin, 2000), (self.ts_ms_end, 2000) ],
50+ b2=[ (self.ts_ms_begin, 2000), (self.ts_ms_end, 2000) ] ))
51+
52+ def test_to_utc_timestamp(self):
53+ self.assertEqual(
54+ zh.to_utc_timestamp(self.dt_begin),
55+ self.ts_ms_begin)
56+ self.assertEqual(
57+ zh.to_utc_timestamp(self.dt_end),
58+ self.ts_ms_end)
59+
60+ def test_zero_fill_time_series_month(self):
61+ result = zh.zero_fill_time_series(
62+ self.zarkov_data['a']['a1'], 'month',
63+ datetime(2010, 5, 1), datetime(2011, 9, 1))
64+ self.assertEqual(result[0][1], 0)
65+ self.assertEqual(result[-1][1], 0)
66+ self.assertEqual(len(result), 17)
67+ self.assertEqual(result[1][1], 1000)
68+ self.assertEqual(result[-3][1], 1000)
69+ days_ms = 24 * 3600 * 1000
70+ min_delta = 28 * days_ms
71+ max_delta= 31 * days_ms
72+ for p1, p2 in zip(result, result[1:]):
73+ delta = p2[0]-p1[0]
74+ assert min_delta <= delta <= max_delta, delta
75+
76+ def test_zero_fill_time_series_date(self):
77+ result = zh.zero_fill_time_series(
78+ self.zarkov_data['a']['a1'], 'date',
79+ datetime(2010, 5, 1), datetime(2011, 9, 1))
80+ self.assertEqual(len(result), 489)
81+ days_ms = 24 * 3600 * 1000
82+ for p1, p2 in zip(result, result[1:]):
83+ delta = p2[0]-p1[0]
84+ assert delta == days_ms
85+
86+ def test_zero_fill_zarkov_month_dt(self):
87+ result = zh.zero_fill_zarkov_result(
88+ self.zarkov_data, 'month',
89+ datetime(2010, 5, 1), datetime(2011, 9, 1))
90+ a_result = result['a']['a1']
91+ b_result = result['b']['b2']
92+ self.assertEqual(a_result[0][1], 0)
93+ self.assertEqual(a_result[-1][1], 0)
94+ self.assertEqual(len(a_result), 17)
95+ self.assertEqual(a_result[1][1], 1000)
96+ self.assertEqual(a_result[-3][1], 1000)
97+ self.assertEqual(b_result[0][1], 0)
98+ self.assertEqual(b_result[-1][1], 0)
99+ self.assertEqual(len(b_result), 17)
100+ self.assertEqual(b_result[1][1], 2000)
101+ self.assertEqual(b_result[-3][1], 2000)
102+
103+ def test_zero_fill_zarkov_month_str(self):
104+ result0 = zh.zero_fill_zarkov_result(
105+ self.zarkov_data, 'month',
106+ datetime(2010, 5, 1), datetime(2011, 9, 1))
107+ result1 = zh.zero_fill_zarkov_result(
108+ self.zarkov_data, 'month',
109+ '2010-5-1', '2011-09-1')
110+ self.assertEqual(result0, result1)
--- a/ForgeTracker/forgetracker/templates/tracker/index.html
+++ b/ForgeTracker/forgetracker/templates/tracker/index.html
@@ -16,7 +16,7 @@
1616 {{c.subscribe_form.display(value=subscribed, action='subscribe', style='icon')}}
1717 {% endif %}
1818 {% if allow_edit %}
19- <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
19+ <a href="{{tg.url(c.app.url+'edit/', dict(q=url_q, limit=limit, sort=sort, page=page))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
2020 {% endif %}
2121 {% endblock %}
2222
--- a/ForgeTracker/forgetracker/templates/tracker/mass_edit.html
+++ b/ForgeTracker/forgetracker/templates/tracker/mass_edit.html
@@ -13,7 +13,7 @@
1313 {% block header %}ForgeTracker for {{c.project.shortname}}{% endblock %}
1414
1515 {% block actions %}
16- <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}} active"></b></a>
16+ <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort, page=page))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}} active"></b></a>
1717 {% endblock %}
1818
1919 {% block edit_box %}
--- a/ForgeTracker/forgetracker/templates/tracker/milestone.html
+++ b/ForgeTracker/forgetracker/templates/tracker/milestone.html
@@ -8,7 +8,7 @@
88
99 {% block actions %}
1010 {% if allow_edit %}
11- <a href="{{tg.url('edit/', dict(q=q, limit=limit, sort=sort))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
11+ <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort, page=page))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
1212 {% endif %}
1313 {% endblock %}
1414
--- a/ForgeTracker/forgetracker/templates/tracker/search.html
+++ b/ForgeTracker/forgetracker/templates/tracker/search.html
@@ -13,7 +13,7 @@
1313 {% block actions %}
1414 <a href="{{tg.url(c.app.url+'search_feed/', dict(q=q, limit=limit, sort=sort))}}" title="Feed"><b data-icon="{{g.icons['feed'].char}}" class="ico {{g.icons['feed'].css}}"></b></a>
1515 {% if allow_edit and count != 0 %}
16- <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
16+ <a href="{{tg.url(c.app.url+'edit/', dict(q=q, limit=limit, sort=sort, page=page))}}" title="Bulk Edit"><b data-icon="{{g.icons['pencil'].char}}" class="ico {{g.icons['pencil'].css}}"></b></a>
1717 {% endif %}
1818 {% endblock %}
1919
--- a/ForgeTracker/forgetracker/templates/tracker_widgets/mass_edit.html
+++ b/ForgeTracker/forgetracker/templates/tracker_widgets/mass_edit.html
@@ -1,5 +1,6 @@
11 <div>
2- {% if tickets.__len__() %}
2+ {% if tickets|length %}
3+ {{widget.fields['page_size'].display(page=page, count=count, limit=limit)}}
34 <table>
45 <thead>
56 <tr>
@@ -26,6 +27,8 @@
2627 {% endfor %}
2728 </tbody>
2829 </table>
30+ {{widget.fields['lightbox'].display()}}
31+ {{widget.fields['page_list'].display(limit=limit, page=page, count=count)}}
2932 <script type="text/javascript">
3033 var q="{{query}}", count={{count}}, limit={{limit}}, page={{page}}, sort="{{sort}}";
3134 </script>
--- a/ForgeTracker/forgetracker/templates/tracker_widgets/mass_edit_form.html
+++ b/ForgeTracker/forgetracker/templates/tracker_widgets/mass_edit_form.html
@@ -1,7 +1,7 @@
11 <form id="update-values">
22 {% for field in globals.custom_fields %}
33 {% if field.name == '_milestone' %}
4- <div class="grid-4">{{milestones}}
4+ <div class="grid-6">{{milestones}}
55 <label for="{{field.name}}" class="cr">{{field.label}}:</label>
66 <select name="{{field.name}}" class="wide">
77 <option value="" selected="selected">no change</option>
@@ -14,7 +14,7 @@
1414 </div>
1515 {% endif %}
1616 {% endfor %}
17- <div class="grid-4">
17+ <div class="grid-6">
1818 <label for="status" class="cr">Status:</label>
1919 <select name="status" class="wide">
2020 <option value="" selected="selected">no change</option>
@@ -23,17 +23,17 @@
2323 {% endfor %}
2424 </select>
2525 </div>
26- <div class="grid-4">
26+ <div class="grid-6">
2727 <label for="assigned_to" class="cr">Owner:</label>
2828 {{c.user_select.display(name='assigned_to', value='', className='wide')}}
2929 </div>
30- {% set cf_count = -1 %}
30+ {% set cf_count = 0 %}
3131 {% for field in globals.custom_fields %}
3232 {% if field.type != 'sum' and field.name != '_milestone' %}
33- {% if cf_count%4 == 0 %}
33+ {% if cf_count%3 == 0 %}
3434 <div style="clear: both"></div>
3535 {% endif %}
36- <div class="grid-4">
36+ <div class="grid-6">
3737 <label for="{{field.id}}" class="cr">{{field.label}}:</label>
3838 {% if field.type == 'boolean' %}
3939 <input name="{{field.name}}" type="checkbox" value="True"/>
@@ -64,7 +64,7 @@
6464 {% set cf_count=cf_count+1 %}
6565 {% endif %}
6666 {% endfor %}
67- <div class="grid-16">
67+ <div class="grid-18">
6868 <input type="button" onclick="update_tickets()" value="Save"/>
6969 <a href="{{cancel_href}}" class="btn link">Cancel</a>
7070 <!-- tg.url(c.app.url+'search/', dict(q=query, limit=limit, sort=sort))}}" class="btn link">Cancel</a>-->
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -382,7 +382,7 @@ class RootController(BaseController):
382382 @with_trailing_slash
383383 @h.vardec
384384 @expose('jinja:forgetracker:templates/tracker/index.html')
385- def index(self, limit=25, columns=None, page=0, sort='ticket_num desc', **kw):
385+ def index(self, limit=25, columns=None, page=0, sort='ticket_num_i asc', **kw):
386386 kw.pop('q', None) # it's just our original query mangled and sent back to us
387387 result = TM.Ticket.paged_query(c.app.globals.not_closed_mongo_query,
388388 sort=sort, limit=int(limit),
@@ -391,6 +391,7 @@ class RootController(BaseController):
391391 result['subscribed'] = M.Mailbox.subscribed()
392392 result['allow_edit'] = has_access(c.app, 'write')()
393393 result['help_msg'] = c.app.config.options.get('TicketHelpSearch')
394+ result['url_q'] = c.app.globals.not_closed_query
394395 c.ticket_search_results = W.ticket_search_results
395396 return result
396397
@@ -584,7 +585,7 @@ class RootController(BaseController):
584585 sort=validators.UnicodeString(if_empty='ticket_num_i asc')))
585586 def edit(self, q=None, limit=None, page=None, sort=None, **kw):
586587 require_access(c.app, 'write')
587- result = self.paged_query(q, sort=sort, **kw)
588+ result = self.paged_query(q, sort=sort, limit=limit, page=page, **kw)
588589 # if c.app.globals.milestone_names is None:
589590 # c.app.globals.milestone_names = ''
590591 result['globals'] = c.app.globals
--- a/ForgeTracker/forgetracker/widgets/ticket_search.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_search.py
@@ -32,10 +32,10 @@ class TicketSearchResults(ew_core.SimpleForm):
3232 for r in super(TicketSearchResults, self).resources():
3333 yield r
3434
35-class MassEdit(ew_core.Widget):
35+class MassEdit(ew_core.SimpleForm):
3636 template='jinja:forgetracker:templates/tracker_widgets/mass_edit.html'
3737 defaults=dict(
38- ew_core.Widget.defaults,
38+ ew_core.SimpleForm.defaults,
3939 count=None,
4040 limit=None,
4141 query=None,
@@ -43,8 +43,16 @@ class MassEdit(ew_core.Widget):
4343 page=1,
4444 sort=None)
4545
46+ class fields(ew_core.NameList):
47+ page_list=ffw.PageList()
48+ page_size=ffw.PageSize()
49+ lightbox=ffw.Lightbox(name='col_list',trigger='#col_menu')
50+
4651 def resources(self):
4752 yield ew.JSLink('tracker_js/ticket-list.js')
53+ yield ew.CSSLink('tracker_css/ticket-list.css')
54+ for r in super(MassEdit, self).resources():
55+ yield r
4856
4957 class MassEditForm(ew_core.Widget):
5058 template='jinja:forgetracker:templates/tracker_widgets/mass_edit_form.html'
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,10 +9,6 @@ phpserialize==1.2
99 psycopg2==2.2.2
1010 sf.phpsession==0.1
1111 pyzmq==2.1.7
12-Zarkov==0.1.0dev-20110815
13-gevent==0.13.6
14-greenlet==0.3.1
15-gevent-zeromq==0.2.0
1612 Pygments==1.4sf1-20110601
1713
1814 # for the migration scripts only