You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by gc...@apache.org on 2022/07/13 16:04:08 UTC

[allura] branch gc/8444 updated (a8817dcbb -> 29a826c21)

This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a change to branch gc/8444
in repository https://gitbox.apache.org/repos/asf/allura.git


 discard a8817dcbb fixup! [#8444] updated pagination_meta_tags with new args, added test and comments for querystring helper
 discard 3d3855709 [#8444] updated pagination_meta_tags with new args, added test and comments for querystring helper
 discard ec66ea3c2 [#8444] adde canonical tag and rel=next/prev where pagination is used to wiki, discussions and tickets
     add cfcde7880 clean up some py2-only special methods
     add b4d5e1c15 Latest pip/setuptools so travis builds ok
     add 2e5f24b9d [#8442] added nofollow to links inside code repositories including the sidebar
     add 818a14c92 [#8442] added missing rel=nofollow attribute to links
     add ba5a4d716 [#8443] nofollow on links that point to code tool repositories
     add f7e96f76f text fixes for links in code repositories
     add 204044ef2 [#8422] greedily prefetch thread artifact refs and attachments in batches
     add 67233a562 [#8445] Upgrade cryptography 36.0.2 -> 37.0.4, and its deps: cffi,pycparser
     add 8d449bfca [#8445] limit markupsafe; Upgrade EasyWidgets 0.4.1, and its deps: formencode,six,markupsafe,paste,setuptools,six,python-dateutil,six,six,webob
     add e3b321900 [#8445] Upgrade feedparser 6.0.8 -> 6.0.10, and its deps: sgmllib3k
     add fb830af8c [#8445] Upgrade Jinja2 3.1.1 -> 3.1.2, and its deps: markupsafe
     add 811886601 [#8445] Upgrade markdown 3.3.6 -> 3.3.7, and its deps: importlib-metadata,typing-extensions,zipp
     add d5724c0c7 [#8445] Upgrade Pillow 9.1.1 -> 9.2.0
     add 2247e50e1 [#8445] Upgrade Pygments 2.11.2 -> 2.12.0
     add c07cea512 [#8445] Upgrade Pypeline 0.6.0 -> no upgrade, and its deps: bleach,six,webencodings,html5lib,six,webencodings,Creoleparser,Genshi,six,six,markdown,importlib-metadata,typing-extensions,zipp,textile,html5lib,six,webencodings,regex
     add b45cd8d08 [#8445] Upgrade python-magic 0.4.25 -> 0.4.27
     add 967a275e4 [#8445] Upgrade setproctitle 1.2.2 -> 1.2.3
     add da4fa03df [#8445] Upgrade Werkzeug 2.1.1 -> 2.1.2
     add e8d2310e1 [#8445] Upgrade wrapt 1.14.0 -> 1.14.1
     add f0a23b4e5 Lower SVN import retry count significantly; block imports from plugins.svn.wordpress.org since it has millions of revisions
     add f0f2fed0d Add generic require_method helper, alongside require_post
     add 148ba11b0 update timermiddleware with perf improvement
     new 3152a1ebd [#8444] adde canonical tag and rel=next/prev where pagination is used to wiki, discussions and tickets
     new 29a826c21 [#8444] updated pagination_meta_tags with new args, added test and comments for querystring helper

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (a8817dcbb)
            \
             N -- N -- N   refs/heads/gc/8444 (29a826c21)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .travis.yml                                        |  1 +
 Allura/allura/lib/decorators.py                    | 14 ++++++++
 Allura/allura/lib/repository.py                    | 26 +++++++++-----
 Allura/allura/lib/utils.py                         |  2 --
 Allura/allura/lib/widgets/discuss.py               | 25 ++++++++++---
 Allura/allura/model/artifact.py                    | 10 ++++--
 Allura/allura/model/discuss.py                     |  6 ----
 Allura/allura/model/repository.py                  |  7 ++--
 Allura/allura/templates/jinja_master/lib.html      |  4 +--
 Allura/allura/templates/repo/commit.html           | 14 ++++----
 Allura/allura/templates/repo/commit_basic.html     | 18 +++++-----
 Allura/allura/templates/repo/diff.html             |  6 ++--
 Allura/allura/templates/repo/file.html             |  4 +--
 Allura/allura/templates/repo/forks.html            |  2 +-
 Allura/allura/templates/repo/merge_request.html    |  8 ++---
 Allura/allura/templates/repo/merge_requests.html   | 10 +++---
 Allura/allura/templates/repo/repo_master.html      |  2 +-
 Allura/allura/templates/repo/tags.html             |  2 +-
 Allura/allura/templates/repo/tarball.html          |  2 +-
 Allura/allura/templates/repo/tree.html             |  4 +--
 Allura/allura/templates/widgets/repo/log.html      | 10 +++---
 Allura/allura/templates/widgets/repo/revision.html | 14 ++++----
 .../allura/templates/widgets/repo/tree_widget.html | 10 +++---
 Allura/development.ini                             |  2 +-
 ForgeActivity/forgeactivity/main.py                |  6 ++--
 ForgeActivity/forgeactivity/templates/macros.html  |  4 ++-
 .../forgeactivity/templates/timeline.html          |  1 -
 .../forgegit/tests/functional/test_controllers.py  | 14 ++++----
 ForgeSVN/forgesvn/model/svn.py                     |  2 +-
 ForgeSVN/forgesvn/widgets.py                       |  7 ++++
 requirements.in                                    |  2 +-
 requirements.txt                                   | 42 +++++++++++-----------
 32 files changed, 163 insertions(+), 118 deletions(-)


[allura] 02/02: [#8444] updated pagination_meta_tags with new args, added test and comments for querystring helper

Posted by gc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a commit to branch gc/8444
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 29a826c211df894a41a5da7b5825c4d9881270f7
Author: Guillermo Cruz <gu...@slashdotmedia.com>
AuthorDate: Wed Jul 13 08:55:20 2022 -0600

    [#8444] updated pagination_meta_tags with new args, added test and comments for querystring helper
---
 Allura/allura/lib/helpers.py                            | 15 +++++++++++++--
 Allura/allura/templates/jinja_master/lib.html           | 17 +++++------------
 Allura/allura/tests/test_helpers.py                     | 13 +++++++++++++
 ForgeBlog/forgeblog/templates/blog/index.html           |  4 +++-
 .../templates/discussionforums/search.html              |  2 +-
 .../templates/discussionforums/thread.html              |  2 +-
 ForgeDiscussion/forgediscussion/templates/index.html    |  2 +-
 ForgeTracker/forgetracker/templates/tracker/index.html  |  2 ++
 .../forgetracker/templates/tracker/milestone.html       |  3 +--
 ForgeTracker/forgetracker/templates/tracker/search.html |  2 +-
 ForgeWiki/forgewiki/templates/wiki/browse.html          |  2 +-
 ForgeWiki/forgewiki/templates/wiki/browse_tags.html     |  2 +-
 ForgeWiki/forgewiki/templates/wiki/page_history.html    |  2 +-
 ForgeWiki/forgewiki/templates/wiki/search.html          |  2 +-
 14 files changed, 45 insertions(+), 25 deletions(-)

diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 38b335747..9bd0eb57e 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -67,6 +67,7 @@ from allura.lib import exceptions as exc
 from allura.lib import utils
 import urllib.parse as urlparse
 from urllib.parse import urlencode
+import math
 from webob.multidict import MultiDict
 
 # import to make available to templates, don't delete:
@@ -155,9 +156,17 @@ def make_safe_path_portion(ustr, relaxed=True):
 def escape_json(data):
     return json.dumps(data).replace('<', '\\u003C')
 
-def querystring(request, data):
+def querystring(request, url_params):
+    """
+    add/update/remove url parameters. When a value is set to None the key will
+    be removed from the final constructed url.
+
+    :param request: request object
+    :param url_params: dict with the params that should be updated/added/deleted.
+    :return: a full url with updated url parameters.
+    """
     params = urlparse.parse_qs(request.query_string)
-    params.update(data)
+    params.update(url_params)
     for param in list(params.keys()):
         if params[param] is None:
             del params[param]
@@ -167,6 +176,8 @@ def querystring(request, data):
     url = url_parts._replace(query=urlencode(params)).geturl()
     return url
 
+def ceil(number):
+    return math.ceil(number)
 
 def strip_bad_unicode(s):
     """
diff --git a/Allura/allura/templates/jinja_master/lib.html b/Allura/allura/templates/jinja_master/lib.html
index 568ee3503..027913e9c 100644
--- a/Allura/allura/templates/jinja_master/lib.html
+++ b/Allura/allura/templates/jinja_master/lib.html
@@ -889,20 +889,13 @@ This page is based on some examples from Greg Schueler, <a href="mailto:greg@var
 {% macro canonical_tag() %}
 {% set page= '?page=' ~ request.GET['page'] if 'page=' in request.query_string and request.GET['page']|int > 0  else '' %}
 <link rel="canonical" href="{{ request.host_url ~ request.path }}{{ page }}"/>
-{%  endmacro %}
+{% endmacro %}
 
-{% macro pagination_meta_tags(request, last_page=None) %}
-{% if 'page=' in request.query_string and request.GET['page']|int > 0 and last_page %}
-    {% set current_page = request.GET['page']|int %}
-    {%- if current_page > 1 -%}
+{% macro pagination_meta_tags(request, current_page=None, results_count=None, limit=None) %}
+    {%- if current_page > 0  -%}
         <link rel="prev" href="{{ h.querystring(request, dict(page=current_page-1,limit=None)) }}"/>
-    {% elif current_page == 1 %}
-        <link rel="prev" href="{{ request.path_qs.split('?')[0] }}"/>
     {% endif %}
-
-    {% if current_page+1 < last_page -%}
-        <link rel="next" href="{{ h.querystring(request, dict(page=current_page+1,limit=None))}}"/>
+    {% if results_count and current_page+1 <  h.ceil(results_count/limit) -%}
+        <link rel="next" href="{{ h.querystring(request, dict(page=current_page+1,limit=None)) }}"/>
     {% endif %}
-{%- endif %}
-
 {% endmacro %}
\ No newline at end of file
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 3c1428586..650ba023f 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -472,6 +472,7 @@ class TestUrlOpen(TestCase):
 
         def side_effect(url, timeout=None):
             raise socket.timeout()
+
         urlopen.side_effect = side_effect
         self.assertRaises(socket.timeout, h.urlopen, 'myurl')
         self.assertEqual(urlopen.call_count, 4)
@@ -483,6 +484,7 @@ class TestUrlOpen(TestCase):
 
         def side_effect(url, timeout=None):
             raise OSError(errno.ECONNRESET, 'Connection reset by peer')
+
         urlopen.side_effect = side_effect
         self.assertRaises(socket.error, h.urlopen, 'myurl')
         self.assertEqual(urlopen.call_count, 4)
@@ -493,6 +495,7 @@ class TestUrlOpen(TestCase):
 
         def side_effect(url, timeout=None):
             raise HTTPError('url', 408, 'timeout', None, None)
+
         urlopen.side_effect = side_effect
         self.assertRaises(HTTPError, h.urlopen, 'myurl')
         self.assertEqual(urlopen.call_count, 4)
@@ -503,6 +506,7 @@ class TestUrlOpen(TestCase):
 
         def side_effect(url, timeout=None):
             raise HTTPError('url', 404, 'timeout', None, None)
+
         urlopen.side_effect = side_effect
         self.assertRaises(HTTPError, h.urlopen, 'myurl')
         self.assertEqual(urlopen.call_count, 1)
@@ -681,3 +685,12 @@ def test_hide_private_info():
 
 def test_emojize():
     assert_equals(h.emojize(':smile:'), '😄')
+
+
+def test_querystring():
+    req = Request.blank('/p/test/foobar?page=1&limit=10&count=100', remote_addr='127.0.0.1',
+                        base_url='https://mysite.com/p/test/foobar')
+    assert_equals(h.querystring(req, dict(page=2, limit=5)),
+                  'https://mysite.com/p/test/foobar/p/test/foobar?page=2&limit=5&count=100')
+    assert_equals(h.querystring(req, dict(page=5, limit=2, count=None)),
+                  'https://mysite.com/p/test/foobar/p/test/foobar?page=5&limit=2')
diff --git a/ForgeBlog/forgeblog/templates/blog/index.html b/ForgeBlog/forgeblog/templates/blog/index.html
index b9e82d71c..8998b54d0 100644
--- a/ForgeBlog/forgeblog/templates/blog/index.html
+++ b/ForgeBlog/forgeblog/templates/blog/index.html
@@ -17,13 +17,15 @@
        under the License.
 -#}
 {% extends g.theme.master %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}}{% endblock %}
 
 {% block head %}
     {% if count == 0 %}
     <meta name="robots" content="noindex, follow"/>
     {% endif %}
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}{{c.project.name}} / {{c.app.config.options.mount_label}}: Recent posts{% endblock %}
diff --git a/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html b/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
index 19e0373c2..bd2568393 100644
--- a/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
+++ b/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
@@ -23,7 +23,7 @@
 {% block head %}
     <meta name="robots" content="noindex, follow">
     {{ lib.canonical_tag() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}Search {{c.app.config.options.mount_point}}: {{q}}{% endblock %}
diff --git a/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html b/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
index 9f1ed0530..c06d9676b 100644
--- a/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
+++ b/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
@@ -24,7 +24,7 @@
 {% endblock %}
 {%  block head %}
     {{ lib.canonical_tag() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 {% block header %}{{'subject' in thread and thread.subject or '(no subject)'}}{% endblock %}
 {% block actions %}
diff --git a/ForgeDiscussion/forgediscussion/templates/index.html b/ForgeDiscussion/forgediscussion/templates/index.html
index 96529598d..8f199a05f 100644
--- a/ForgeDiscussion/forgediscussion/templates/index.html
+++ b/ForgeDiscussion/forgediscussion/templates/index.html
@@ -24,7 +24,7 @@
         <meta name="robots" content="noindex, follow">
     {% endif %}
     {{ lib.canonical_tag() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {%  endblock %}
 
 {% block actions %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/index.html b/ForgeTracker/forgetracker/templates/tracker/index.html
index 92bda4c0a..d0ddfc337 100644
--- a/ForgeTracker/forgetracker/templates/tracker/index.html
+++ b/ForgeTracker/forgetracker/templates/tracker/index.html
@@ -26,8 +26,10 @@
   <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss"/>
   <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom"/>
   {{ lib.canonical_tag() }}
+  {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
+
 {% block header %}{{c.app.config.options.mount_label}}{% endblock %}
 
 {% block actions %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/milestone.html b/ForgeTracker/forgetracker/templates/tracker/milestone.html
index 1a65a4d51..35ecf8a3c 100644
--- a/ForgeTracker/forgetracker/templates/tracker/milestone.html
+++ b/ForgeTracker/forgetracker/templates/tracker/milestone.html
@@ -18,7 +18,6 @@
 -#}
 {% extends g.theme.master %}
 {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
-{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% do g.register_app_css('css/tracker.css') %}
 
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / {{field.label}} {{milestone.name}}{% endblock %}
@@ -27,7 +26,7 @@
 
 {% block head %}
     {{ lib.canonical_tag() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block actions %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/search.html b/ForgeTracker/forgetracker/templates/tracker/search.html
index 62b1d2167..e492043e0 100644
--- a/ForgeTracker/forgetracker/templates/tracker/search.html
+++ b/ForgeTracker/forgetracker/templates/tracker/search.html
@@ -31,7 +31,7 @@
     <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss"/>
     <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom"/>
     {{ lib.canonical_tag() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 
 {% endblock %}
 
diff --git a/ForgeWiki/forgewiki/templates/wiki/browse.html b/ForgeWiki/forgewiki/templates/wiki/browse.html
index 8f2493a60..3a96aa719 100644
--- a/ForgeWiki/forgewiki/templates/wiki/browse.html
+++ b/ForgeWiki/forgewiki/templates/wiki/browse.html
@@ -23,7 +23,7 @@
 
 {% block head %}
     {{ super() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}Browse Pages{% endblock %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/browse_tags.html b/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
index 98a76a216..7225526b6 100644
--- a/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
+++ b/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
@@ -22,7 +22,7 @@
 
 {% block head %}
     {{ super() }}
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}Browse Labels{% endblock %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/page_history.html b/ForgeWiki/forgewiki/templates/wiki/page_history.html
index ccc41acdc..da255c5dd 100644
--- a/ForgeWiki/forgewiki/templates/wiki/page_history.html
+++ b/ForgeWiki/forgewiki/templates/wiki/page_history.html
@@ -25,7 +25,7 @@
 {%  block head %}
     {{ super() }}
     <meta name="robots" content="noindex,follow" />
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/search.html b/ForgeWiki/forgewiki/templates/wiki/search.html
index 236308d5f..9faff6f2c 100644
--- a/ForgeWiki/forgewiki/templates/wiki/search.html
+++ b/ForgeWiki/forgewiki/templates/wiki/search.html
@@ -23,7 +23,7 @@
 {%  block head %}
     {{ super() }}
     <meta name="robots" content="noindex, follow">
-    {{ lib.pagination_meta_tags(request, count) }}
+    {{ lib.pagination_meta_tags(request, page, count, limit) }}
 {% endblock %}
 
 {% block header %}Search {{c.app.config.options.mount_point}}: {{q}}{% endblock %}


[allura] 01/02: [#8444] adde canonical tag and rel=next/prev where pagination is used to wiki, discussions and tickets

Posted by gc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a commit to branch gc/8444
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 3152a1ebd1e05ab499213703ef3b6211c596f8d8
Author: Guillermo Cruz <gu...@slashdotmedia.com>
AuthorDate: Wed Jun 29 13:40:34 2022 -0600

    [#8444] adde canonical tag and rel=next/prev where pagination is used to wiki, discussions and tickets
---
 Allura/allura/lib/helpers.py                        | 15 +++++++++++++++
 Allura/allura/templates/jinja_master/lib.html       | 21 +++++++++++++++++++++
 .../templates/discussionforums/search.html          |  4 +++-
 .../templates/discussionforums/thread.html          | 10 ++++++----
 .../forgediscussion/templates/index.html            |  4 +++-
 .../forgetracker/templates/tracker/index.html       |  1 +
 .../forgetracker/templates/tracker/milestone.html   |  7 ++++++-
 .../forgetracker/templates/tracker/search.html      |  3 +++
 .../forgetracker/tests/functional/test_root.py      | 21 +++++++++++++++++++++
 ForgeWiki/forgewiki/templates/wiki/browse.html      |  7 ++++++-
 ForgeWiki/forgewiki/templates/wiki/browse_tags.html |  7 ++++++-
 ForgeWiki/forgewiki/templates/wiki/master.html      |  7 +++++++
 .../forgewiki/templates/wiki/page_history.html      |  4 +++-
 ForgeWiki/forgewiki/templates/wiki/page_view.html   |  7 ++++---
 ForgeWiki/forgewiki/templates/wiki/search.html      |  2 ++
 ForgeWiki/forgewiki/tests/functional/test_root.py   |  7 +++++++
 16 files changed, 114 insertions(+), 13 deletions(-)

diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index c6ec0e973..38b335747 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -65,6 +65,9 @@ from webob.exc import HTTPUnauthorized
 
 from allura.lib import exceptions as exc
 from allura.lib import utils
+import urllib.parse as urlparse
+from urllib.parse import urlencode
+from webob.multidict import MultiDict
 
 # import to make available to templates, don't delete:
 from .security import has_access, is_allowed_by_role, is_site_admin
@@ -152,6 +155,18 @@ def make_safe_path_portion(ustr, relaxed=True):
 def escape_json(data):
     return json.dumps(data).replace('<', '\\u003C')
 
+def querystring(request, data):
+    params = urlparse.parse_qs(request.query_string)
+    params.update(data)
+    for param in list(params.keys()):
+        if params[param] is None:
+            del params[param]
+    # flatten dict values
+    params = {k: v[0] if isinstance(v, list) else v for k, v in params.items()}
+    url_parts = urlparse.urlparse(request.url)
+    url = url_parts._replace(query=urlencode(params)).geturl()
+    return url
+
 
 def strip_bad_unicode(s):
     """
diff --git a/Allura/allura/templates/jinja_master/lib.html b/Allura/allura/templates/jinja_master/lib.html
index 41689b43f..568ee3503 100644
--- a/Allura/allura/templates/jinja_master/lib.html
+++ b/Allura/allura/templates/jinja_master/lib.html
@@ -885,3 +885,24 @@ This page is based on some examples from Greg Schueler, <a href="mailto:greg@var
     {%- do g.register_forge_js('js/create-react-class.min.js', location=location) %}
     {%- do g.register_forge_js('js/prop-types.min.js', location=location) %}
 {%- endmacro %}
+
+{% macro canonical_tag() %}
+{% set page= '?page=' ~ request.GET['page'] if 'page=' in request.query_string and request.GET['page']|int > 0  else '' %}
+<link rel="canonical" href="{{ request.host_url ~ request.path }}{{ page }}"/>
+{%  endmacro %}
+
+{% macro pagination_meta_tags(request, last_page=None) %}
+{% if 'page=' in request.query_string and request.GET['page']|int > 0 and last_page %}
+    {% set current_page = request.GET['page']|int %}
+    {%- if current_page > 1 -%}
+        <link rel="prev" href="{{ h.querystring(request, dict(page=current_page-1,limit=None)) }}"/>
+    {% elif current_page == 1 %}
+        <link rel="prev" href="{{ request.path_qs.split('?')[0] }}"/>
+    {% endif %}
+
+    {% if current_page+1 < last_page -%}
+        <link rel="next" href="{{ h.querystring(request, dict(page=current_page+1,limit=None))}}"/>
+    {% endif %}
+{%- endif %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html b/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
index 85de03713..19e0373c2 100644
--- a/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
+++ b/ForgeDiscussion/forgediscussion/templates/discussionforums/search.html
@@ -17,11 +17,13 @@
        under the License.
 -#}
 {% extends g.theme.master %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Search{% endblock %}
 
 {% block head %}
     <meta name="robots" content="noindex, follow">
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, count) }}
 {% endblock %}
 
 {% block header %}Search {{c.app.config.options.mount_point}}: {{q}}{% endblock %}
diff --git a/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html b/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
index cbf05173d..9f1ed0530 100644
--- a/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
+++ b/ForgeDiscussion/forgediscussion/templates/discussionforums/thread.html
@@ -17,14 +17,16 @@
        under the License.
 -#}
 {% extends g.theme.master %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}
-  {{c.project.name}} / {{c.app.config.options.mount_label}} / 
+  {{c.project.name}} / {{c.app.config.options.mount_label}} /
   {{thread.subject and '%s: %s' % (thread.discussion.name, (thread.subject or '(no subject)')) or thread.discussion.name}}
 {% endblock %}
-
+{%  block head %}
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, count) }}
+{% endblock %}
 {% block header %}{{'subject' in thread and thread.subject or '(no subject)'}}{% endblock %}
-
 {% block actions %}
   {% if show_moderate and h.has_access(thread, 'moderate')() %}
     {{ g.icons['edit'].render(id='mod_thread_link') }}
diff --git a/ForgeDiscussion/forgediscussion/templates/index.html b/ForgeDiscussion/forgediscussion/templates/index.html
index 57af42022..96529598d 100644
--- a/ForgeDiscussion/forgediscussion/templates/index.html
+++ b/ForgeDiscussion/forgediscussion/templates/index.html
@@ -17,12 +17,14 @@
        under the License.
 -#}
 {% extends 'allura:templates/discussion/index.html' %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block head %}
     {{ super() }}
     {% if not threads|length %}
         <meta name="robots" content="noindex, follow">
     {% endif %}
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, count) }}
 {%  endblock %}
 
 {% block actions %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/index.html b/ForgeTracker/forgetracker/templates/tracker/index.html
index c4c3cb189..92bda4c0a 100644
--- a/ForgeTracker/forgetracker/templates/tracker/index.html
+++ b/ForgeTracker/forgetracker/templates/tracker/index.html
@@ -25,6 +25,7 @@
 {% block head %}
   <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss"/>
   <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom"/>
+  {{ lib.canonical_tag() }}
 {% endblock %}
 
 {% block header %}{{c.app.config.options.mount_label}}{% endblock %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/milestone.html b/ForgeTracker/forgetracker/templates/tracker/milestone.html
index bb695f122..1a65a4d51 100644
--- a/ForgeTracker/forgetracker/templates/tracker/milestone.html
+++ b/ForgeTracker/forgetracker/templates/tracker/milestone.html
@@ -18,13 +18,18 @@
 -#}
 {% extends g.theme.master %}
 {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% do g.register_app_css('css/tracker.css') %}
 
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / {{field.label}} {{milestone.name}}{% endblock %}
 
 {% block header %}{{field.label}} {{milestone.name}}{% endblock %}
 
+{% block head %}
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, count) }}
+{% endblock %}
+
 {% block actions %}
 {{ lib.maximize_content_button() }}
 {% if allow_edit %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/search.html b/ForgeTracker/forgetracker/templates/tracker/search.html
index 03c86c914..62b1d2167 100644
--- a/ForgeTracker/forgetracker/templates/tracker/search.html
+++ b/ForgeTracker/forgetracker/templates/tracker/search.html
@@ -30,6 +30,9 @@
     {% endif %}
     <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss"/>
     <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom"/>
+    {{ lib.canonical_tag() }}
+    {{ lib.pagination_meta_tags(request, count) }}
+
 {% endblock %}
 
 {% block actions %}
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index a5c8b23b3..c42237a67 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1380,6 +1380,7 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/search/?q=test&limit=2')
+        response.mustcontain('canonical')
         response.mustcontain('results of 3')
         response.mustcontain('test second ticket')
         next_page_link = response.html.select('.page_list a')[0]
@@ -1390,6 +1391,26 @@ class TestFunctionalController(TrackerTestController):
         # 'filter' is special kwarg, don't let it cause problems
         r = self.app.get('/p/test/bugs/search/?q=test&filter=blah')
 
+    def test_search_canonical(self):
+        self.new_ticket(summary='test first ticket')
+        self.new_ticket(summary='test second ticket')
+        self.new_ticket(summary='test third ticket')
+        self.new_ticket(summary='test fourth ticket')
+        self.new_ticket(summary='test fifth ticket')
+        self.new_ticket(summary='test sixth ticket')
+        ThreadLocalORMSession.flush_all()
+        M.MonQTask.run_ready()
+        ThreadLocalORMSession.flush_all()
+        response = self.app.get('/p/test/bugs/search/?q=test&limit=2')
+        canonical = response.html.select_one('link[rel=canonical]')
+        assert ('limit=2' not in canonical['href'])
+        response = self.app.get('/p/test/bugs/search/?q=test&limit=2&page=3')
+        next = response.html.select_one('link[rel=next]')
+        assert ('page=4' in next['href'])
+        prev = response.html.select_one('link[rel=prev]')
+        assert ('page=2' in prev['href'])
+
+
     def test_search_with_strange_chars(self):
         r = self.app.get('/p/test/bugs/search/?' +
                          urlencode({'q': 'tést'}))
diff --git a/ForgeWiki/forgewiki/templates/wiki/browse.html b/ForgeWiki/forgewiki/templates/wiki/browse.html
index dcc49059f..8f2493a60 100644
--- a/ForgeWiki/forgewiki/templates/wiki/browse.html
+++ b/ForgeWiki/forgewiki/templates/wiki/browse.html
@@ -18,9 +18,14 @@
 -#}
 {% extends 'forgewiki:templates/wiki/master.html' %}
 {% from 'allura:templates/jinja_master/lib.html' import abbr_date with context %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Browse Pages{% endblock %}
 
+{% block head %}
+    {{ super() }}
+    {{ lib.pagination_meta_tags(request, count) }}
+{% endblock %}
+
 {% block header %}Browse Pages{% endblock %}
 
 {% block wiki_content %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/browse_tags.html b/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
index e464a39ba..98a76a216 100644
--- a/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
+++ b/ForgeWiki/forgewiki/templates/wiki/browse_tags.html
@@ -17,9 +17,14 @@
        under the License.
 -#}
 {% extends 'forgewiki:templates/wiki/master.html' %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Browse Labels{% endblock %}
 
+{% block head %}
+    {{ super() }}
+    {{ lib.pagination_meta_tags(request, count) }}
+{% endblock %}
+
 {% block header %}Browse Labels{% endblock %}
 
 {% block wiki_content %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/master.html b/ForgeWiki/forgewiki/templates/wiki/master.html
index e2f561ea8..37d237a59 100644
--- a/ForgeWiki/forgewiki/templates/wiki/master.html
+++ b/ForgeWiki/forgewiki/templates/wiki/master.html
@@ -17,7 +17,14 @@
        under the License.
 -#}
 {% extends g.theme.master %}
+{% import g.theme.jinja_macros as theme_macros with context %}
 {% do g.register_app_css('css/wiki.css', compress=False) %}
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+{% block head %}
+    {{ lib.canonical_tag() }}
+{% endblock %}
+
 
 {% block edit_box %}
   {% if show_meta %}{% block wiki_meta %}{% endblock %}{% endif %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/page_history.html b/ForgeWiki/forgewiki/templates/wiki/page_history.html
index 4bc43b11a..ccc41acdc 100644
--- a/ForgeWiki/forgewiki/templates/wiki/page_history.html
+++ b/ForgeWiki/forgewiki/templates/wiki/page_history.html
@@ -19,11 +19,13 @@
 {% extends 'forgewiki:templates/wiki/master.html' %}
 {% from 'allura:templates/jinja_master/lib.html' import abbr_date with context %}
 {% import 'allura:templates/jinja_master/dialog_macros.html' as dialog_macros with context %}
-
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / {{title}}{% endblock %}
 
 {%  block head %}
+    {{ super() }}
     <meta name="robots" content="noindex,follow" />
+    {{ lib.pagination_meta_tags(request, count) }}
 {% endblock %}
 
 {% block header %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/page_view.html b/ForgeWiki/forgewiki/templates/wiki/page_view.html
index e26d9e149..07b1249b4 100644
--- a/ForgeWiki/forgewiki/templates/wiki/page_view.html
+++ b/ForgeWiki/forgewiki/templates/wiki/page_view.html
@@ -19,6 +19,7 @@
 {% extends 'forgewiki:templates/wiki/master.html' %}
 {% do g.register_forge_css('css/forge/hilite.css', compress=False) %}
 {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+{% import g.theme.jinja_macros as theme_macros with context %}
 
 {%  set base_url = page.url().split('?')[0] %}
 
@@ -34,14 +35,14 @@
 {% endblock %}
 
 {% block head %}
-    {%  if noindex %}
+    {{ super() }}
+    {%-  if noindex -%}
         <meta name="robots" content="noindex, follow">
-    {% endif %}
+    {%- endif -%}
 <link rel="alternate" type="application/rss+xml" title="Page RSS" href="feed.rss"/>
 <link rel="alternate" type="application/atom+xml" title="Page Atom" href="feed.atom"/>
 <link rel="alternate" type="application/rss+xml" title="Wiki RSS" href="../feed.rss"/>
 <link rel="alternate" type="application/atom+xml" title="Wiki Atom" href="../feed.atom"/>
-<link rel="canonical" href="{{ h.absurl(base_url) }}">
 
 {% endblock %}
 {% block body_css_class %} {{super()}} wiki-{{(page.title).replace(' ','_')}}{% endblock %}
diff --git a/ForgeWiki/forgewiki/templates/wiki/search.html b/ForgeWiki/forgewiki/templates/wiki/search.html
index c13f1171b..236308d5f 100644
--- a/ForgeWiki/forgewiki/templates/wiki/search.html
+++ b/ForgeWiki/forgewiki/templates/wiki/search.html
@@ -21,7 +21,9 @@
 {% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Search{% endblock %}
 
 {%  block head %}
+    {{ super() }}
     <meta name="robots" content="noindex, follow">
+    {{ lib.pagination_meta_tags(request, count) }}
 {% endblock %}
 
 {% block header %}Search {{c.app.config.options.mount_point}}: {{q}}{% endblock %}
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 698e0feb3..cf79c1748 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -478,6 +478,13 @@ class TestRootController(TestController):
         r = self.app.get('/wiki/browse_tags/?page=3')
         assert '<td>label77</td>' in r
         assert '<td>label99</td>' in r
+        r.mustcontain('canonical')
+        canonical = r.html.select_one('link[rel=canonical]')
+        assert 'browse_tags' in canonical['href']
+        next = r.html.select_one('link[rel=next]')
+        assert('page=4' in next['href'])
+        prev = r.html.select_one('link[rel=prev]')
+        assert('page=2' in prev['href'])
 
     def test_new_attachment(self):
         self.app.post(