We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Link to 'edit project' when logged in user is owner
I have added a button in the UI to link *back* the the manage project page, when the user is viewing their project.

![screenshot from 2018-01-18 07-25-07](https://user-images.githubusercontent.com/3323703/35085471-ca4590a4-fc20-11e7-8f2e-4800b119b9fe.png)

This is currently commented out.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `warehouse/routes.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 
14 def includeme(config):
15     # We need to get the value of the Warehouse and Forklift domains, we'll use
16     # these to segregate the Warehouse routes from the Forklift routes until
17     # Forklift is properly split out into it's own project.
18     warehouse = config.get_settings().get("warehouse.domain")
19     files_url = config.get_settings()["files.url"]
20 
21     # Simple Route for health checks.
22     config.add_route("health", "/_health/")
23 
24     # Internal route to make it easier to force a particular status for
25     # debugging HTTPException templates.
26     config.add_route("force-status", "/_force-status/{status:[45]\d\d}/")
27 
28     # Basic global routes
29     config.add_route("index", "/", domain=warehouse)
30     config.add_route("robots.txt", "/robots.txt", domain=warehouse)
31     config.add_route("opensearch.xml", "/opensearch.xml", domain=warehouse)
32     config.add_route("index.sitemap.xml", "/sitemap.xml", domain=warehouse)
33     config.add_route(
34         "bucket.sitemap.xml",
35         "/{bucket}.sitemap.xml",
36         domain=warehouse,
37     )
38 
39     # Some static, template driven pages
40     config.add_template_view("help", "/help/", "pages/help.html")
41     config.add_template_view("security", "/security/", "pages/security.html")
42     config.add_template_view(
43         "sponsors",
44         "/sponsors/",
45         # Use the full resource path here to make it able to be overridden by
46         # pypi-theme.
47         "warehouse:templates/pages/sponsors.html",
48     )
49 
50     # Our legal policies
51     config.add_policy("terms-of-use", "terms.md")
52 
53     # HTML Snippets for including into other pages.
54     config.add_route(
55         "includes.current-user-indicator",
56         "/_includes/current-user-indicator/",
57         domain=warehouse,
58     )
59     config.add_route(
60         "includes.flash-messages",
61         "/_includes/flash-messages/",
62         domain=warehouse,
63     )
64     config.add_route(
65         "includes.current-user-profile-callout",
66         "/_includes/current-user-profile-callout/{username}",
67         factory="warehouse.accounts.models:UserFactory",
68         traverse="/{username}",
69         domain=warehouse,
70     )
71 
72     # Search Routes
73     config.add_route("search", "/search/", domain=warehouse)
74 
75     # Accounts
76     config.add_route(
77         "accounts.profile",
78         "/user/{username}/",
79         factory="warehouse.accounts.models:UserFactory",
80         traverse="/{username}",
81         domain=warehouse,
82     )
83     config.add_route("accounts.login", "/account/login/", domain=warehouse)
84     config.add_route("accounts.logout", "/account/logout/", domain=warehouse)
85     config.add_route(
86         "accounts.register",
87         "/account/register/",
88         domain=warehouse,
89     )
90     config.add_route(
91         "accounts.request-password-reset",
92         "/account/request-password-reset/",
93         domain=warehouse,
94     )
95     config.add_route(
96         "accounts.reset-password",
97         "/account/reset-password/",
98         domain=warehouse,
99     )
100 
101     # Management (views for logged-in users)
102     config.add_route("manage.profile", "/manage/profile/", domain=warehouse)
103     config.add_route("manage.projects", "/manage/projects/", domain=warehouse)
104     config.add_route(
105         "manage.project.settings",
106         "/manage/project/{name}/settings/",
107         factory="warehouse.packaging.models:ProjectFactory",
108         traverse="/{name}",
109         domain=warehouse,
110     )
111     config.add_route(
112         "manage.project.roles",
113         "/manage/project/{name}/collaboration/",
114         factory="warehouse.packaging.models:ProjectFactory",
115         traverse="/{name}",
116         domain=warehouse,
117     )
118     config.add_route(
119         "manage.project.change_role",
120         "/manage/project/{name}/collaboration/change/",
121         factory="warehouse.packaging.models:ProjectFactory",
122         traverse="/{name}",
123         domain=warehouse,
124     )
125     config.add_route(
126         "manage.project.delete_role",
127         "/manage/project/{name}/collaboration/delete/",
128         factory="warehouse.packaging.models:ProjectFactory",
129         traverse="/{name}",
130         domain=warehouse,
131     )
132 
133     # Packaging
134     config.add_route(
135         "packaging.project",
136         "/project/{name}/",
137         factory="warehouse.packaging.models:ProjectFactory",
138         traverse="/{name}",
139         domain=warehouse,
140     )
141     config.add_route(
142         "packaging.release",
143         "/project/{name}/{version}/",
144         factory="warehouse.packaging.models:ProjectFactory",
145         traverse="/{name}/{version}",
146         domain=warehouse,
147     )
148     config.add_route("packaging.file", files_url)
149 
150     # RSS
151     config.add_route("rss.updates", "/rss/updates.xml", domain=warehouse)
152     config.add_route("rss.packages", "/rss/packages.xml", domain=warehouse)
153 
154     # Legacy URLs
155     config.add_route("legacy.api.simple.index", "/simple/", domain=warehouse)
156     config.add_route(
157         "legacy.api.simple.detail",
158         "/simple/{name}/",
159         factory="warehouse.packaging.models:ProjectFactory",
160         traverse="/{name}/",
161         read_only=True,
162         domain=warehouse,
163     )
164     config.add_route(
165         "legacy.api.json.project",
166         "/pypi/{name}/json",
167         factory="warehouse.packaging.models:ProjectFactory",
168         traverse="/{name}",
169         read_only=True,
170         domain=warehouse,
171     )
172     config.add_route(
173         "legacy.api.json.release",
174         "/pypi/{name}/{version}/json",
175         factory="warehouse.packaging.models:ProjectFactory",
176         traverse="/{name}/{version}",
177         read_only=True,
178         domain=warehouse,
179     )
180 
181     # Legacy Action URLs
182     # TODO: We should probably add Warehouse routes for these that just error
183     #       and direct people to use upload.pypi.io
184     config.add_pypi_action_route(
185         "legacy.api.pypi.file_upload",
186         "file_upload",
187         domain=warehouse,
188     )
189     config.add_pypi_action_route(
190         "legacy.api.pypi.submit",
191         "submit",
192         domain=warehouse,
193     )
194     config.add_pypi_action_route(
195         "legacy.api.pypi.submit_pkg_info",
196         "submit_pkg_info",
197         domain=warehouse,
198     )
199     config.add_pypi_action_route(
200         "legacy.api.pypi.doc_upload",
201         "doc_upload",
202         domain=warehouse,
203     )
204     config.add_pypi_action_route(
205         "legacy.api.pypi.doap",
206         "doap",
207         domain=warehouse,
208     )
209     config.add_pypi_action_route(
210         "legacy.api.pypi.list_classifiers",
211         "list_classifiers",
212         domain=warehouse,
213     )
214 
215     # Legacy XMLRPC
216     config.add_xmlrpc_endpoint(
217         "pypi",
218         pattern="/pypi",
219         header="Content-Type:text/xml",
220         domain=warehouse,
221     )
222 
223     # Legacy Documentation
224     config.add_route("legacy.docs", config.registry.settings["docs.url"])
225 
226     # Legacy Redirects
227     config.add_redirect("/pypi/{name}/", "/project/{name}/", domain=warehouse)
228     config.add_redirect(
229         "/pypi/{name}/{version}/",
230         "/project/{name}/{version}/",
231         domain=warehouse,
232     )
233     config.add_redirect("/packages/{path:.*}", files_url, domain=warehouse)
234 
235     # Legacy Action Redirects
236     config.add_pypi_action_redirect(
237         "rss",
238         "/rss/updates.xml",
239         domain=warehouse,
240     )
241     config.add_pypi_action_redirect(
242         "packages_rss",
243         "/rss/packages.xml",
244         domain=warehouse,
245     )
246
```

Path: `warehouse/packaging/views.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 from first import first
14 from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
15 from pyramid.view import view_config
16 from sqlalchemy.orm.exc import NoResultFound
17 
18 from warehouse.accounts.models import User
19 from warehouse.cache.origin import origin_cache
20 from warehouse.packaging.models import Release, Role
21 
22 
23 @view_config(
24     route_name="packaging.project",
25     renderer="packaging/detail.html",
26     decorator=[
27         origin_cache(
28             1 * 24 * 60 * 60,                         # 1 day
29             stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day
30             stale_if_error=5 * 24 * 60 * 60,          # 5 days
31         ),
32     ],
33 )
34 def project_detail(project, request):
35     if project.name != request.matchdict.get("name", project.name):
36         return HTTPMovedPermanently(
37             request.current_route_path(name=project.name),
38         )
39 
40     try:
41         release = (
42             request.db.query(Release)
43                       .filter(Release.project == project)
44                       .order_by(
45                           Release.is_prerelease.nullslast(),
46                           Release._pypi_ordering.desc())
47                       .limit(1)
48                       .one()
49         )
50     except NoResultFound:
51         return HTTPNotFound()
52 
53     return release_detail(release, request)
54 
55 
56 @view_config(
57     route_name="packaging.release",
58     renderer="packaging/detail.html",
59     decorator=[
60         origin_cache(
61             1 * 24 * 60 * 60,                         # 1 day
62             stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day
63             stale_if_error=5 * 24 * 60 * 60,          # 5 days
64         ),
65     ],
66 )
67 def release_detail(release, request):
68     project = release.project
69 
70     if project.name != request.matchdict.get("name", project.name):
71         return HTTPMovedPermanently(
72             request.current_route_path(name=project.name),
73         )
74 
75     # Get all of the registered versions for this Project, in order of newest
76     # to oldest.
77     all_releases = (
78         request.db.query(Release)
79                   .filter(Release.project == project)
80                   .with_entities(
81                       Release.version,
82                       Release.is_prerelease,
83                       Release.created)
84                   .order_by(Release._pypi_ordering.desc())
85                   .all()
86     )
87 
88     # Get the latest non-prerelease of this Project, or the latest release if
89     # all releases are prereleases.
90     latest_release = first(
91         all_releases,
92         key=lambda r: not r.is_prerelease,
93         default=all_releases[0],
94     )
95 
96     # Get all of the maintainers for this project.
97     maintainers = [
98         r.user
99         for r in (
100             request.db.query(Role)
101             .join(User)
102             .filter(Role.project == project)
103             .distinct(User.username)
104             .order_by(User.username)
105             .all()
106         )
107     ]
108 
109     # Get the license from the classifiers or metadata, preferring classifiers.
110     license = None
111     if release.license:
112         # Make a best effort when the entire license text is given
113         # by using the first line only.
114         license = release.license.split('\n')[0]
115     license_classifiers = [c.split(" :: ")[-1] for c in release.classifiers
116                            if c.startswith("License")]
117     if license_classifiers:
118         license = ', '.join(license_classifiers)
119 
120     return {
121         "project": project,
122         "release": release,
123         "files": release.files.all(),
124         "latest_release": latest_release,
125         "all_releases": all_releases,
126         "maintainers": maintainers,
127         "license": license,
128     }
129
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/warehouse/packaging/views.py b/warehouse/packaging/views.py
--- a/warehouse/packaging/views.py
+++ b/warehouse/packaging/views.py
@@ -126,3 +126,13 @@
         "maintainers": maintainers,
         "license": license,
     }
+
+
+@view_config(
+    route_name="includes.edit-project-button",
+    renderer="includes/edit-project-button.html",
+    uses_session=True,
+    permission="manage",
+)
+def edit_project_button(project, request):
+    return {'project': project}
diff --git a/warehouse/routes.py b/warehouse/routes.py
--- a/warehouse/routes.py
+++ b/warehouse/routes.py
@@ -68,6 +68,13 @@
         traverse="/{username}",
         domain=warehouse,
     )
+    config.add_route(
+        "includes.edit-project-button",
+        "/_includes/edit-project-button/{project_name}",
+        factory="warehouse.packaging.models:ProjectFactory",
+        traverse="/{project_name}",
+        domain=warehouse,
+    )
 
     # Search Routes
     config.add_route("search", "/search/", domain=warehouse)

{"golden_diff": "diff --git a/warehouse/packaging/views.py b/warehouse/packaging/views.py\n--- a/warehouse/packaging/views.py\n+++ b/warehouse/packaging/views.py\n@@ -126,3 +126,13 @@\n         \"maintainers\": maintainers,\n         \"license\": license,\n     }\n+\n+\n+@view_config(\n+    route_name=\"includes.edit-project-button\",\n+    renderer=\"includes/edit-project-button.html\",\n+    uses_session=True,\n+    permission=\"manage\",\n+)\n+def edit_project_button(project, request):\n+    return {'project': project}\ndiff --git a/warehouse/routes.py b/warehouse/routes.py\n--- a/warehouse/routes.py\n+++ b/warehouse/routes.py\n@@ -68,6 +68,13 @@\n         traverse=\"/{username}\",\n         domain=warehouse,\n     )\n+    config.add_route(\n+        \"includes.edit-project-button\",\n+        \"/_includes/edit-project-button/{project_name}\",\n+        factory=\"warehouse.packaging.models:ProjectFactory\",\n+        traverse=\"/{project_name}\",\n+        domain=warehouse,\n+    )\n \n     # Search Routes\n     config.add_route(\"search\", \"/search/\", domain=warehouse)\n", "issue": "Link to 'edit project' when logged in user is owner\nI have added a button in the UI to link *back* the the manage project page, when the user is viewing their project.\r\n\r\n![screenshot from 2018-01-18 07-25-07](https://user-images.githubusercontent.com/3323703/35085471-ca4590a4-fc20-11e7-8f2e-4800b119b9fe.png)\r\n\r\nThis is currently commented out.\r\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\ndef includeme(config):\n    # We need to get the value of the Warehouse and Forklift domains, we'll use\n    # these to segregate the Warehouse routes from the Forklift routes until\n    # Forklift is properly split out into it's own project.\n    warehouse = config.get_settings().get(\"warehouse.domain\")\n    files_url = config.get_settings()[\"files.url\"]\n\n    # Simple Route for health checks.\n    config.add_route(\"health\", \"/_health/\")\n\n    # Internal route to make it easier to force a particular status for\n    # debugging HTTPException templates.\n    config.add_route(\"force-status\", \"/_force-status/{status:[45]\\d\\d}/\")\n\n    # Basic global routes\n    config.add_route(\"index\", \"/\", domain=warehouse)\n    config.add_route(\"robots.txt\", \"/robots.txt\", domain=warehouse)\n    config.add_route(\"opensearch.xml\", \"/opensearch.xml\", domain=warehouse)\n    config.add_route(\"index.sitemap.xml\", \"/sitemap.xml\", domain=warehouse)\n    config.add_route(\n        \"bucket.sitemap.xml\",\n        \"/{bucket}.sitemap.xml\",\n        domain=warehouse,\n    )\n\n    # Some static, template driven pages\n    config.add_template_view(\"help\", \"/help/\", \"pages/help.html\")\n    config.add_template_view(\"security\", \"/security/\", \"pages/security.html\")\n    config.add_template_view(\n        \"sponsors\",\n        \"/sponsors/\",\n        # Use the full resource path here to make it able to be overridden by\n        # pypi-theme.\n        \"warehouse:templates/pages/sponsors.html\",\n    )\n\n    # Our legal policies\n    config.add_policy(\"terms-of-use\", \"terms.md\")\n\n    # HTML Snippets for including into other pages.\n    config.add_route(\n        \"includes.current-user-indicator\",\n        \"/_includes/current-user-indicator/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"includes.flash-messages\",\n        \"/_includes/flash-messages/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"includes.current-user-profile-callout\",\n        \"/_includes/current-user-profile-callout/{username}\",\n        factory=\"warehouse.accounts.models:UserFactory\",\n        traverse=\"/{username}\",\n        domain=warehouse,\n    )\n\n    # Search Routes\n    config.add_route(\"search\", \"/search/\", domain=warehouse)\n\n    # Accounts\n    config.add_route(\n        \"accounts.profile\",\n        \"/user/{username}/\",\n        factory=\"warehouse.accounts.models:UserFactory\",\n        traverse=\"/{username}\",\n        domain=warehouse,\n    )\n    config.add_route(\"accounts.login\", \"/account/login/\", domain=warehouse)\n    config.add_route(\"accounts.logout\", \"/account/logout/\", domain=warehouse)\n    config.add_route(\n        \"accounts.register\",\n        \"/account/register/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"accounts.request-password-reset\",\n        \"/account/request-password-reset/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"accounts.reset-password\",\n        \"/account/reset-password/\",\n        domain=warehouse,\n    )\n\n    # Management (views for logged-in users)\n    config.add_route(\"manage.profile\", \"/manage/profile/\", domain=warehouse)\n    config.add_route(\"manage.projects\", \"/manage/projects/\", domain=warehouse)\n    config.add_route(\n        \"manage.project.settings\",\n        \"/manage/project/{name}/settings/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.roles\",\n        \"/manage/project/{name}/collaboration/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.change_role\",\n        \"/manage/project/{name}/collaboration/change/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.delete_role\",\n        \"/manage/project/{name}/collaboration/delete/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n\n    # Packaging\n    config.add_route(\n        \"packaging.project\",\n        \"/project/{name}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"packaging.release\",\n        \"/project/{name}/{version}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/{version}\",\n        domain=warehouse,\n    )\n    config.add_route(\"packaging.file\", files_url)\n\n    # RSS\n    config.add_route(\"rss.updates\", \"/rss/updates.xml\", domain=warehouse)\n    config.add_route(\"rss.packages\", \"/rss/packages.xml\", domain=warehouse)\n\n    # Legacy URLs\n    config.add_route(\"legacy.api.simple.index\", \"/simple/\", domain=warehouse)\n    config.add_route(\n        \"legacy.api.simple.detail\",\n        \"/simple/{name}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/\",\n        read_only=True,\n        domain=warehouse,\n    )\n    config.add_route(\n        \"legacy.api.json.project\",\n        \"/pypi/{name}/json\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        read_only=True,\n        domain=warehouse,\n    )\n    config.add_route(\n        \"legacy.api.json.release\",\n        \"/pypi/{name}/{version}/json\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/{version}\",\n        read_only=True,\n        domain=warehouse,\n    )\n\n    # Legacy Action URLs\n    # TODO: We should probably add Warehouse routes for these that just error\n    #       and direct people to use upload.pypi.io\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.file_upload\",\n        \"file_upload\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.submit\",\n        \"submit\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.submit_pkg_info\",\n        \"submit_pkg_info\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.doc_upload\",\n        \"doc_upload\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.doap\",\n        \"doap\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.list_classifiers\",\n        \"list_classifiers\",\n        domain=warehouse,\n    )\n\n    # Legacy XMLRPC\n    config.add_xmlrpc_endpoint(\n        \"pypi\",\n        pattern=\"/pypi\",\n        header=\"Content-Type:text/xml\",\n        domain=warehouse,\n    )\n\n    # Legacy Documentation\n    config.add_route(\"legacy.docs\", config.registry.settings[\"docs.url\"])\n\n    # Legacy Redirects\n    config.add_redirect(\"/pypi/{name}/\", \"/project/{name}/\", domain=warehouse)\n    config.add_redirect(\n        \"/pypi/{name}/{version}/\",\n        \"/project/{name}/{version}/\",\n        domain=warehouse,\n    )\n    config.add_redirect(\"/packages/{path:.*}\", files_url, domain=warehouse)\n\n    # Legacy Action Redirects\n    config.add_pypi_action_redirect(\n        \"rss\",\n        \"/rss/updates.xml\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_redirect(\n        \"packages_rss\",\n        \"/rss/packages.xml\",\n        domain=warehouse,\n    )\n", "path": "warehouse/routes.py"}, {"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom first import first\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.accounts.models import User\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.models import Release, Role\n\n\n@view_config(\n    route_name=\"packaging.project\",\n    renderer=\"packaging/detail.html\",\n    decorator=[\n        origin_cache(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=5 * 24 * 60 * 60,          # 5 days\n        ),\n    ],\n)\ndef project_detail(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    try:\n        release = (\n            request.db.query(Release)\n                      .filter(Release.project == project)\n                      .order_by(\n                          Release.is_prerelease.nullslast(),\n                          Release._pypi_ordering.desc())\n                      .limit(1)\n                      .one()\n        )\n    except NoResultFound:\n        return HTTPNotFound()\n\n    return release_detail(release, request)\n\n\n@view_config(\n    route_name=\"packaging.release\",\n    renderer=\"packaging/detail.html\",\n    decorator=[\n        origin_cache(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=5 * 24 * 60 * 60,          # 5 days\n        ),\n    ],\n)\ndef release_detail(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    # Get all of the registered versions for this Project, in order of newest\n    # to oldest.\n    all_releases = (\n        request.db.query(Release)\n                  .filter(Release.project == project)\n                  .with_entities(\n                      Release.version,\n                      Release.is_prerelease,\n                      Release.created)\n                  .order_by(Release._pypi_ordering.desc())\n                  .all()\n    )\n\n    # Get the latest non-prerelease of this Project, or the latest release if\n    # all releases are prereleases.\n    latest_release = first(\n        all_releases,\n        key=lambda r: not r.is_prerelease,\n        default=all_releases[0],\n    )\n\n    # Get all of the maintainers for this project.\n    maintainers = [\n        r.user\n        for r in (\n            request.db.query(Role)\n            .join(User)\n            .filter(Role.project == project)\n            .distinct(User.username)\n            .order_by(User.username)\n            .all()\n        )\n    ]\n\n    # Get the license from the classifiers or metadata, preferring classifiers.\n    license = None\n    if release.license:\n        # Make a best effort when the entire license text is given\n        # by using the first line only.\n        license = release.license.split('\\n')[0]\n    license_classifiers = [c.split(\" :: \")[-1] for c in release.classifiers\n                           if c.startswith(\"License\")]\n    if license_classifiers:\n        license = ', '.join(license_classifiers)\n\n    return {\n        \"project\": project,\n        \"release\": release,\n        \"files\": release.files.all(),\n        \"latest_release\": latest_release,\n        \"all_releases\": all_releases,\n        \"maintainers\": maintainers,\n        \"license\": license,\n    }\n", "path": "warehouse/packaging/views.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\ndef includeme(config):\n    # We need to get the value of the Warehouse and Forklift domains, we'll use\n    # these to segregate the Warehouse routes from the Forklift routes until\n    # Forklift is properly split out into it's own project.\n    warehouse = config.get_settings().get(\"warehouse.domain\")\n    files_url = config.get_settings()[\"files.url\"]\n\n    # Simple Route for health checks.\n    config.add_route(\"health\", \"/_health/\")\n\n    # Internal route to make it easier to force a particular status for\n    # debugging HTTPException templates.\n    config.add_route(\"force-status\", \"/_force-status/{status:[45]\\d\\d}/\")\n\n    # Basic global routes\n    config.add_route(\"index\", \"/\", domain=warehouse)\n    config.add_route(\"robots.txt\", \"/robots.txt\", domain=warehouse)\n    config.add_route(\"opensearch.xml\", \"/opensearch.xml\", domain=warehouse)\n    config.add_route(\"index.sitemap.xml\", \"/sitemap.xml\", domain=warehouse)\n    config.add_route(\n        \"bucket.sitemap.xml\",\n        \"/{bucket}.sitemap.xml\",\n        domain=warehouse,\n    )\n\n    # Some static, template driven pages\n    config.add_template_view(\"help\", \"/help/\", \"pages/help.html\")\n    config.add_template_view(\"security\", \"/security/\", \"pages/security.html\")\n    config.add_template_view(\n        \"sponsors\",\n        \"/sponsors/\",\n        # Use the full resource path here to make it able to be overridden by\n        # pypi-theme.\n        \"warehouse:templates/pages/sponsors.html\",\n    )\n\n    # Our legal policies\n    config.add_policy(\"terms-of-use\", \"terms.md\")\n\n    # HTML Snippets for including into other pages.\n    config.add_route(\n        \"includes.current-user-indicator\",\n        \"/_includes/current-user-indicator/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"includes.flash-messages\",\n        \"/_includes/flash-messages/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"includes.current-user-profile-callout\",\n        \"/_includes/current-user-profile-callout/{username}\",\n        factory=\"warehouse.accounts.models:UserFactory\",\n        traverse=\"/{username}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"includes.edit-project-button\",\n        \"/_includes/edit-project-button/{project_name}\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{project_name}\",\n        domain=warehouse,\n    )\n\n    # Search Routes\n    config.add_route(\"search\", \"/search/\", domain=warehouse)\n\n    # Accounts\n    config.add_route(\n        \"accounts.profile\",\n        \"/user/{username}/\",\n        factory=\"warehouse.accounts.models:UserFactory\",\n        traverse=\"/{username}\",\n        domain=warehouse,\n    )\n    config.add_route(\"accounts.login\", \"/account/login/\", domain=warehouse)\n    config.add_route(\"accounts.logout\", \"/account/logout/\", domain=warehouse)\n    config.add_route(\n        \"accounts.register\",\n        \"/account/register/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"accounts.request-password-reset\",\n        \"/account/request-password-reset/\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"accounts.reset-password\",\n        \"/account/reset-password/\",\n        domain=warehouse,\n    )\n\n    # Management (views for logged-in users)\n    config.add_route(\"manage.profile\", \"/manage/profile/\", domain=warehouse)\n    config.add_route(\"manage.projects\", \"/manage/projects/\", domain=warehouse)\n    config.add_route(\n        \"manage.project.settings\",\n        \"/manage/project/{name}/settings/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.roles\",\n        \"/manage/project/{name}/collaboration/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.change_role\",\n        \"/manage/project/{name}/collaboration/change/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"manage.project.delete_role\",\n        \"/manage/project/{name}/collaboration/delete/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n\n    # Packaging\n    config.add_route(\n        \"packaging.project\",\n        \"/project/{name}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        domain=warehouse,\n    )\n    config.add_route(\n        \"packaging.release\",\n        \"/project/{name}/{version}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/{version}\",\n        domain=warehouse,\n    )\n    config.add_route(\"packaging.file\", files_url)\n\n    # RSS\n    config.add_route(\"rss.updates\", \"/rss/updates.xml\", domain=warehouse)\n    config.add_route(\"rss.packages\", \"/rss/packages.xml\", domain=warehouse)\n\n    # Legacy URLs\n    config.add_route(\"legacy.api.simple.index\", \"/simple/\", domain=warehouse)\n    config.add_route(\n        \"legacy.api.simple.detail\",\n        \"/simple/{name}/\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/\",\n        read_only=True,\n        domain=warehouse,\n    )\n    config.add_route(\n        \"legacy.api.json.project\",\n        \"/pypi/{name}/json\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}\",\n        read_only=True,\n        domain=warehouse,\n    )\n    config.add_route(\n        \"legacy.api.json.release\",\n        \"/pypi/{name}/{version}/json\",\n        factory=\"warehouse.packaging.models:ProjectFactory\",\n        traverse=\"/{name}/{version}\",\n        read_only=True,\n        domain=warehouse,\n    )\n\n    # Legacy Action URLs\n    # TODO: We should probably add Warehouse routes for these that just error\n    #       and direct people to use upload.pypi.io\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.file_upload\",\n        \"file_upload\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.submit\",\n        \"submit\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.submit_pkg_info\",\n        \"submit_pkg_info\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.doc_upload\",\n        \"doc_upload\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.doap\",\n        \"doap\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_route(\n        \"legacy.api.pypi.list_classifiers\",\n        \"list_classifiers\",\n        domain=warehouse,\n    )\n\n    # Legacy XMLRPC\n    config.add_xmlrpc_endpoint(\n        \"pypi\",\n        pattern=\"/pypi\",\n        header=\"Content-Type:text/xml\",\n        domain=warehouse,\n    )\n\n    # Legacy Documentation\n    config.add_route(\"legacy.docs\", config.registry.settings[\"docs.url\"])\n\n    # Legacy Redirects\n    config.add_redirect(\"/pypi/{name}/\", \"/project/{name}/\", domain=warehouse)\n    config.add_redirect(\n        \"/pypi/{name}/{version}/\",\n        \"/project/{name}/{version}/\",\n        domain=warehouse,\n    )\n    config.add_redirect(\"/packages/{path:.*}\", files_url, domain=warehouse)\n\n    # Legacy Action Redirects\n    config.add_pypi_action_redirect(\n        \"rss\",\n        \"/rss/updates.xml\",\n        domain=warehouse,\n    )\n    config.add_pypi_action_redirect(\n        \"packages_rss\",\n        \"/rss/packages.xml\",\n        domain=warehouse,\n    )\n", "path": "warehouse/routes.py"}, {"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom first import first\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.accounts.models import User\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.models import Release, Role\n\n\n@view_config(\n    route_name=\"packaging.project\",\n    renderer=\"packaging/detail.html\",\n    decorator=[\n        origin_cache(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=5 * 24 * 60 * 60,          # 5 days\n        ),\n    ],\n)\ndef project_detail(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    try:\n        release = (\n            request.db.query(Release)\n                      .filter(Release.project == project)\n                      .order_by(\n                          Release.is_prerelease.nullslast(),\n                          Release._pypi_ordering.desc())\n                      .limit(1)\n                      .one()\n        )\n    except NoResultFound:\n        return HTTPNotFound()\n\n    return release_detail(release, request)\n\n\n@view_config(\n    route_name=\"packaging.release\",\n    renderer=\"packaging/detail.html\",\n    decorator=[\n        origin_cache(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=5 * 24 * 60 * 60,          # 5 days\n        ),\n    ],\n)\ndef release_detail(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    # Get all of the registered versions for this Project, in order of newest\n    # to oldest.\n    all_releases = (\n        request.db.query(Release)\n                  .filter(Release.project == project)\n                  .with_entities(\n                      Release.version,\n                      Release.is_prerelease,\n                      Release.created)\n                  .order_by(Release._pypi_ordering.desc())\n                  .all()\n    )\n\n    # Get the latest non-prerelease of this Project, or the latest release if\n    # all releases are prereleases.\n    latest_release = first(\n        all_releases,\n        key=lambda r: not r.is_prerelease,\n        default=all_releases[0],\n    )\n\n    # Get all of the maintainers for this project.\n    maintainers = [\n        r.user\n        for r in (\n            request.db.query(Role)\n            .join(User)\n            .filter(Role.project == project)\n            .distinct(User.username)\n            .order_by(User.username)\n            .all()\n        )\n    ]\n\n    # Get the license from the classifiers or metadata, preferring classifiers.\n    license = None\n    if release.license:\n        # Make a best effort when the entire license text is given\n        # by using the first line only.\n        license = release.license.split('\\n')[0]\n    license_classifiers = [c.split(\" :: \")[-1] for c in release.classifiers\n                           if c.startswith(\"License\")]\n    if license_classifiers:\n        license = ', '.join(license_classifiers)\n\n    return {\n        \"project\": project,\n        \"release\": release,\n        \"files\": release.files.all(),\n        \"latest_release\": latest_release,\n        \"all_releases\": all_releases,\n        \"maintainers\": maintainers,\n        \"license\": license,\n    }\n\n\n@view_config(\n    route_name=\"includes.edit-project-button\",\n    renderer=\"includes/edit-project-button.html\",\n    uses_session=True,\n    permission=\"manage\",\n)\ndef edit_project_button(project, request):\n    return {'project': project}\n", "path": "warehouse/packaging/views.py"}]}