diff --git a/swh/web/api/views/utils.py b/swh/web/api/views/utils.py index 4d44f210..ef422a20 100644 --- a/swh/web/api/views/utils.py +++ b/swh/web/api/views/utils.py @@ -1,90 +1,90 @@ # Copyright (C) 2015-2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from rest_framework.response import Response from rest_framework.decorators import api_view from types import GeneratorType from swh.web.common.exc import NotFoundExc from swh.web.api.apiurls import APIUrls, api_route # canned doc string snippets that are used in several doc strings doc_arg_content_id = """A "[hash_type:]hash" content identifier, where hash_type is one of "sha1" (the default), "sha1_git", "sha256", and hash is a checksum obtained with the hash_type hashing algorithm.""" doc_arg_last_elt = 'element to start listing from, for pagination purposes' doc_arg_per_page = 'number of elements to list, for pagination purposes' doc_exc_bad_id = 'syntax error in the given identifier(s)' doc_exc_id_not_found = 'no object matching the given criteria could be found' doc_ret_revision_meta = 'metadata of the revision identified by sha1_git' doc_ret_revision_log = """list of dictionaries representing the metadata of each revision found in the commit log heading to revision sha1_git. For each commit at least the following information are returned: author/committer, authoring/commit timestamps, revision id, commit message, parent (i.e., immediately preceding) commits, "root" directory id.""" doc_header_link = """indicates that a subsequent result page is available, pointing to it""" def api_lookup(lookup_fn, *args, notfound_msg='Object not found', enrich_fn=lambda x: x): """Capture a redundant behavior of: - looking up the backend with a criteria (be it an identifier or checksum) passed to the function lookup_fn - if nothing is found, raise an NotFoundExc exception with error message notfound_msg. - Otherwise if something is returned: - either as list, map or generator, map the enrich_fn function to it and return the resulting data structure as list. - either as dict and pass to enrich_fn and return the dict enriched. Args: - criteria: discriminating criteria to lookup - lookup_fn: function expects one criteria and optional supplementary *args. - notfound_msg: if nothing matching the criteria is found, raise NotFoundExc with this error message. - enrich_fn: Function to use to enrich the result returned by lookup_fn. Default to the identity function if not provided. - *args: supplementary arguments to pass to lookup_fn. Raises: NotFoundExp or whatever `lookup_fn` raises. """ res = lookup_fn(*args) if not res: raise NotFoundExc(notfound_msg) if isinstance(res, (map, list, GeneratorType)): return [enrich_fn(x) for x in res] return enrich_fn(res) @api_view(['GET', 'HEAD']) def api_home(request): return Response({}, template_name='api.html') -APIUrls.add_url_pattern(r'^$', api_home, view_name='api_homepage') +APIUrls.add_url_pattern(r'^$', api_home, view_name='api-homepage') @api_route(r'/', 'endpoints') def api_endpoints(request): """Display the list of opened api endpoints. """ routes = APIUrls.get_app_endpoints().copy() for route, doc in routes.items(): doc['doc_intro'] = doc['docstring'].split('\n\n')[0] # Return a list of routes with consistent ordering env = { 'doc_routes': sorted(routes.items()) } return Response(env, template_name="api-endpoints.html") diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py index 88cd7847..d628bb25 100644 --- a/swh/web/browse/views/content.py +++ b/swh/web/browse/views/content.py @@ -1,155 +1,156 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from django.http import HttpResponse from django.shortcuts import render from django.template.defaultfilters import filesizeformat from swh.model.hashutil import hash_to_hex from swh.web.common import query from swh.web.common.utils import reverse from swh.web.common.exc import handle_view_exception from swh.web.browse.utils import ( gen_path_info, request_content, prepare_content_for_display ) from swh.web.browse.browseurls import browse_route @browse_route(r'content/(?P.+)/raw/', view_name='browse-content-raw') def content_raw(request, query_string): """Django view that produces a raw display of a SWH content identified by its hash value. The url that points to it is :http:get:`/browse/content/[(algo_hash):](hash)/raw/` Args: request: input django http request query_string: a string of the form "[ALGO_HASH:]HASH" where optional ALGO_HASH can be either *sha1*, *sha1_git*, *sha256*, or *blake2s256* (default to *sha1*) and HASH the hexadecimal representation of the hash value Returns: The raw bytes of the content. """ # noqa try: algo, checksum = query.parse_hash(query_string) checksum = hash_to_hex(checksum) content_data = request_content(query_string) except Exception as exc: return handle_view_exception(request, exc) filename = request.GET.get('filename', None) if not filename: filename = '%s_%s' % (algo, checksum) if content_data['mimetype'].startswith('text/'): response = HttpResponse(content_data['raw_data'], content_type="text/plain") response['Content-disposition'] = 'filename=%s' % filename else: response = HttpResponse(content_data['raw_data'], content_type='application/octet-stream') response['Content-disposition'] = 'attachment; filename=%s' % filename return response @browse_route(r'content/(?P.+)/', view_name='browse-content') def content_display(request, query_string): """Django view that produces an HTML display of a SWH content identified by its hash value. The url that points to it is :http:get:`/browse/content/[(algo_hash):](hash)/` Args: request: input django http request query_string: a string of the form "[ALGO_HASH:]HASH" where optional ALGO_HASH can be either *sha1*, *sha1_git*, *sha256*, or *blake2s256* (default to *sha1*) and HASH the hexadecimal representation of the hash value Returns: The HTML rendering of the requested content. """ # noqa try: algo, checksum = query.parse_hash(query_string) checksum = hash_to_hex(checksum) content_data = request_content(query_string) except Exception as exc: return handle_view_exception(request, exc) path = request.GET.get('path', None) content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) root_dir = None filename = None path_info = None breadcrumbs = [] if path: split_path = path.split('/') root_dir = split_path[0] filename = split_path[-1] path = path.replace(root_dir + '/', '') path = path.replace(filename, '') path_info = gen_path_info(path) breadcrumbs.append({'name': root_dir[:7], 'url': reverse('browse-directory', kwargs={'sha1_git': root_dir})}) for pi in path_info: breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-directory', kwargs={'sha1_git': root_dir, 'path': pi['path']})}) breadcrumbs.append({'name': filename, 'url': None}) query_params = None if filename: query_params = {'filename': filename} content_raw_url = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params=query_params) content_metadata = { 'sha1 checksum': content_data['checksums']['sha1'], 'sha1_git checksum': content_data['checksums']['sha1_git'], 'sha256 checksum': content_data['checksums']['sha256'], 'blake2s256 checksum': content_data['checksums']['blake2s256'], 'mime type': content_data['mimetype'], 'encoding': content_data['encoding'], 'size': filesizeformat(content_data['length']), 'language': content_data['language'], 'licenses': content_data['licenses'] } return render(request, 'content.html', - {'top_panel_visible': True, + {'heading': 'Content information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Content', 'top_panel_text_right': '%s: %s' % (algo, checksum), 'swh_object_metadata': content_metadata, 'main_panel_visible': True, 'content': content_display_data['content_data'], 'mimetype': content_data['mimetype'], 'language': content_display_data['language'], 'breadcrumbs': breadcrumbs, 'branches': None, 'branch': None, 'top_right_link': content_raw_url, 'top_right_link_text': 'Raw File'}) diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py index 8e874651..a1e5d546 100644 --- a/swh/web/browse/views/directory.py +++ b/swh/web/browse/views/directory.py @@ -1,97 +1,98 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from django.shortcuts import render from django.template.defaultfilters import filesizeformat from swh.web.common import service from swh.web.common.utils import reverse from swh.web.common.exc import handle_view_exception from swh.web.browse.utils import ( gen_path_info, get_directory_entries ) from swh.web.browse.browseurls import browse_route @browse_route(r'directory/(?P[0-9a-f]+)/', r'directory/(?P[0-9a-f]+)/(?P.+)/', view_name='browse-directory') def directory_browse(request, sha1_git, path=None): """Django view for browsing the content of a SWH directory identified by its sha1_git value. The url that points to it is :http:get:`/browse/directory/(sha1_git)/[(path)/]` Args: request: input django http request sha1_git: swh sha1_git identifer of the directory to browse path: optionnal path parameter used to navigate in directories reachable from the provided root one Returns: The HTML rendering for the content of the provided directory. """ # noqa root_sha1_git = sha1_git try: if path: dir_info = service.lookup_directory_with_path(sha1_git, path) sha1_git = dir_info['target'] dirs, files = get_directory_entries(sha1_git) except Exception as exc: return handle_view_exception(request, exc) path_info = gen_path_info(path) breadcrumbs = [] breadcrumbs.append({'name': root_sha1_git[:7], 'url': reverse('browse-directory', kwargs={'sha1_git': root_sha1_git})}) for pi in path_info: breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-directory', kwargs={'sha1_git': root_sha1_git, 'path': pi['path']})}) path = '' if path is None else (path + '/') for d in dirs: d['url'] = reverse('browse-directory', kwargs={'sha1_git': root_sha1_git, 'path': path + d['name']}) sum_file_sizes = 0 for f in files: query_string = 'sha1_git:' + f['target'] f['url'] = reverse('browse-content', kwargs={'query_string': query_string}, query_params={'path': root_sha1_git + '/' + path + f['name']}) sum_file_sizes += f['length'] f['length'] = filesizeformat(f['length']) sum_file_sizes = filesizeformat(sum_file_sizes) dir_metadata = {'id': sha1_git, 'number of regular files': len(files), 'number of subdirectories': len(dirs), 'sum of regular file sizes': sum_file_sizes} return render(request, 'directory.html', - {'top_panel_visible': True, + {'heading': 'Directory information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Directory', 'top_panel_text_right': 'Sha1 git: ' + sha1_git, 'swh_object_metadata': dir_metadata, 'main_panel_visible': True, 'dirs': dirs, 'files': files, 'breadcrumbs': breadcrumbs, 'branches': None, 'branch': None, 'top_right_link': None, 'top_right_link_text': None}) diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py index 4a24f9a4..853e9f85 100644 --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -1,607 +1,611 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import dateutil from django.shortcuts import render from django.template.defaultfilters import filesizeformat from swh.web.common import service from swh.web.common.utils import reverse, format_utc_iso_date from swh.web.common.exc import NotFoundExc, handle_view_exception from swh.web.browse.utils import ( get_origin_visits, get_origin_visit, get_origin_visit_branches, gen_path_info, get_directory_entries, request_content, prepare_content_for_display, gen_link, prepare_revision_log_for_display ) from swh.web.browse.browseurls import browse_route @browse_route(r'origin/(?P[0-9]+)/', r'origin/(?P[a-z]+)/url/(?P.+)/', view_name='browse-origin') def origin_browse(request, origin_id=None, origin_type=None, origin_url=None): """Django view that produces an HTML display of a swh origin identified by its id or its url. The url scheme that points to it is :http:get:`/browse/origin/(origin_id)/`. Args: request: input django http request origin_id: a swh origin id origin_type: type of origin (git, svn, ...) origin_url: url of the origin (e.g. https://github.com//) Returns: The HMTL rendering for the metadata of the provided origin. """ # noqa try: if origin_id: origin_request_params = { 'id': origin_id, } else: origin_request_params = { 'type': origin_type, 'url': origin_url } origin_info = service.lookup_origin(origin_request_params) origin_id = origin_info['id'] origin_visits = get_origin_visits(origin_id) except Exception as exc: return handle_view_exception(request, exc) origin_info['last swh visit browse url'] = \ reverse('browse-origin-directory', kwargs={'origin_id': origin_id}) origin_visits_data = [] for visit in origin_visits: visit_date = dateutil.parser.parse(visit['date']) visit['date'] = format_utc_iso_date(visit['date']) visit['browse_url'] = reverse('browse-origin-directory', kwargs={'origin_id': origin_id, 'visit_id': visit['visit']}) origin_visits_data.append( {'date': visit_date.timestamp()}) return render(request, 'origin.html', - {'top_panel_visible': True, + {'heading': 'Origin information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Origin', 'top_panel_text_right': 'Url: ' + origin_info['url'], 'swh_object_metadata': origin_info, 'main_panel_visible': True, 'origin_visits_data': origin_visits_data, 'visits': list(reversed(origin_visits)), 'browse_url_base': '/browse/origin/%s/' % origin_id}) def _get_origin_branches_and_url_args(origin_id, visit_id, ts): if not visit_id and ts: branches = get_origin_visit_branches(origin_id, visit_ts=ts) url_args = {'origin_id': origin_id, 'timestamp': ts} else: branches = get_origin_visit_branches(origin_id, visit_id) url_args = {'origin_id': origin_id, 'visit_id': visit_id} return branches, url_args def _raise_exception_if_branch_not_found(origin_id, visit_id, ts, branch): if visit_id: raise NotFoundExc('Branch %s associated to visit with' ' id %s for origin with id %s' ' not found!' % (branch, visit_id, origin_id)) else: raise NotFoundExc('Branch %s associated to visit with' ' timestamp %s for origin with id %s' ' not found!' % (branch, ts, origin_id)) def _get_branch(branches, branch_name): """ Utility function to get a specific branch from an origin branches list. Its purpose is to get the default HEAD branch as some SWH origin (e.g those with svn type) does not have it. In that latter case, check if there is a master branch instead and returns it. """ filtered_branches = [b for b in branches if b['name'].endswith(branch_name)] # noqa if len(filtered_branches) > 0: return filtered_branches[0] elif branch_name == 'HEAD': filtered_branches = [b for b in branches if b['name'].endswith('master')] # noqa if len(filtered_branches) > 0: return filtered_branches[0] return None def _gen_origin_link(origin_id, origin_url): origin_browse_url = reverse('browse-origin', kwargs={'origin_id': origin_id}) return gen_link(origin_browse_url, 'Origin: ' + origin_url) @browse_route(r'origin/(?P[0-9]+)/directory/', r'origin/(?P[0-9]+)/directory/(?P.+)/', r'origin/(?P[0-9]+)/visit/(?P[0-9]+)/directory/', # noqa r'origin/(?P[0-9]+)/visit/(?P[0-9]+)/directory/(?P.+)/', # noqa r'origin/(?P[0-9]+)/ts/(?P.+)/directory/', # noqa r'origin/(?P[0-9]+)/ts/(?P.+)/directory/(?P.+)/', # noqa view_name='browse-origin-directory') def origin_directory_browse(request, origin_id, visit_id=None, timestamp=None, path=None): """Django view for browsing the content of a swh directory associated to an origin for a given visit. The url scheme that points to it is the following: * :http:get:`/browse/origin/(origin_id)/directory/[(path)/]` * :http:get:`/browse/origin/(origin_id)/visit/(visit_id)/directory/[(path)/]` * :http:get:`/browse/origin/(origin_id)/ts/(timestamp)/directory/[(path)/]` Args: request: input django http request origin_id: a swh origin id visit_id: optionnal visit id parameter (the last one will be used by default) timestamp: optionnal visit timestamp parameter (the last one will be used by default) path: optionnal path parameter used to navigate in directories reachable from the origin root one branch: optionnal query parameter that specifies the origin branch from which to retrieve the directory revision: optional query parameter to specify the origin revision from which to retrieve the directory Returns: The HTML rendering for the content of the directory associated to the provided origin and visit. """ # noqa try: if not visit_id and not timestamp: origin_visits = get_origin_visits(origin_id) if not origin_visits: raise NotFoundExc('No SWH visit associated to ' 'origin with id %s' % origin_id) return origin_directory_browse(request, origin_id, origin_visits[-1]['visit'], path=path) origin_info = service.lookup_origin({'id': origin_id}) branches, url_args = \ _get_origin_branches_and_url_args(origin_id, visit_id, timestamp) visit_info = get_origin_visit(origin_id, visit_id, timestamp) for b in branches: branch_url_args = dict(url_args) if path: b['path'] = path branch_url_args['path'] = path b['url'] = reverse('browse-origin-directory', kwargs=branch_url_args, query_params={'branch': b['name']}) revision_id = request.GET.get('revision', None) if revision_id: revision = service.lookup_revision(revision_id) root_sha1_git = revision['directory'] branches.append({'name': revision_id, 'revision': revision_id, 'directory': root_sha1_git, 'url': None}) branch_name = revision_id else: branch_name = request.GET.get('branch', 'HEAD') branch = _get_branch(branches, branch_name) if branch: branch_name = branch['name'] root_sha1_git = branch['directory'] else: _raise_exception_if_branch_not_found(origin_id, visit_id, timestamp, branch_name) sha1_git = root_sha1_git if path: dir_info = service.lookup_directory_with_path(root_sha1_git, path) sha1_git = dir_info['target'] dirs, files = get_directory_entries(sha1_git) except Exception as exc: return handle_view_exception(request, exc) if revision_id: query_params = {'revision': revision_id} else: query_params = {'branch': branch_name} path_info = gen_path_info(path) breadcrumbs = [] breadcrumbs.append({'name': root_sha1_git[:7], 'url': reverse('browse-origin-directory', kwargs=url_args, query_params=query_params)}) for pi in path_info: bc_url_args = dict(url_args) bc_url_args['path'] = pi['path'] breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-origin-directory', kwargs=bc_url_args, query_params=query_params)}) path = '' if path is None else (path + '/') for d in dirs: bc_url_args = dict(url_args) bc_url_args['path'] = path + d['name'] d['url'] = reverse('browse-origin-directory', kwargs=bc_url_args, query_params=query_params) sum_file_sizes = 0 for f in files: bc_url_args = dict(url_args) bc_url_args['path'] = path + f['name'] f['url'] = reverse('browse-origin-content', kwargs=bc_url_args, query_params=query_params) sum_file_sizes += f['length'] f['length'] = filesizeformat(f['length']) history_url = reverse('browse-origin-log', kwargs=url_args, query_params=query_params) sum_file_sizes = filesizeformat(sum_file_sizes) dir_metadata = {'id': sha1_git, 'number of regular files': len(files), 'number of subdirectories': len(dirs), 'sum of regular file sizes': sum_file_sizes, 'origin id': origin_info['id'], 'origin type': origin_info['type'], 'origin url': origin_info['url'], 'origin visit': format_utc_iso_date(visit_info['date']), 'path': '/' + path} return render(request, 'directory.html', - {'top_panel_visible': True, + {'heading': 'Directory information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Directory', 'top_panel_text_right': _gen_origin_link( origin_id, origin_info['url']), 'swh_object_metadata': dir_metadata, 'main_panel_visible': True, 'dirs': dirs, 'files': files, 'breadcrumbs': breadcrumbs, 'branches': branches, 'branch': branch_name, 'top_right_link': history_url, 'top_right_link_text': 'History'}) @browse_route(r'origin/(?P[0-9]+)/content/(?P.+)/', r'origin/(?P[0-9]+)/visit/(?P[0-9]+)/content/(?P.+)/', # noqa r'origin/(?P[0-9]+)/ts/(?P.+)/content/(?P.+)/', # noqa view_name='browse-origin-content') def origin_content_display(request, origin_id, path, visit_id=None, timestamp=None): """Django view that produces an HTML display of a swh content associated to an origin for a given visit. The url scheme that points to it is the following: * :http:get:`/browse/origin/(origin_id)/content/(path)/` * :http:get:`/browse/origin/(origin_id)/visit/(visit_id)/content/(path)/` * :http:get:`/browse/origin/(origin_id)/ts/(timestamp)/content/(path)/` Args: request: input django http request origin_id: id of a swh origin path: path of the content relative to the origin root directory visit_id: optionnal visit id parameter (the last one will be used by default) timestamp: optionnal visit timestamp parameter (the last one will be used by default) branch: optionnal query parameter that specifies the origin branch from which to retrieve the content revision: optional query parameter to specify the origin revision from which to retrieve the content Returns: The HTML rendering of the requested content associated to the provided origin and visit. """ # noqa try: if not visit_id and not timestamp: origin_visits = get_origin_visits(origin_id) if not origin_visits: raise NotFoundExc('No SWH visit associated to ' 'origin with id %s' % origin_id) return origin_content_display(request, origin_id, path, origin_visits[-1]['visit']) origin_info = service.lookup_origin({'id': origin_id}) branches, url_args = \ _get_origin_branches_and_url_args(origin_id, visit_id, timestamp) visit_info = get_origin_visit(origin_id, visit_id, timestamp) for b in branches: bc_url_args = dict(url_args) bc_url_args['path'] = path b['url'] = reverse('browse-origin-content', kwargs=bc_url_args, query_params={'branch': b['name']}) revision_id = request.GET.get('revision', None) if revision_id: revision = service.lookup_revision(revision_id) root_sha1_git = revision['directory'] branches.append({'name': revision_id, 'revision': revision_id, 'directory': root_sha1_git, 'url': None}) branch_name = revision_id else: branch_name = request.GET.get('branch', 'HEAD') branch = _get_branch(branches, branch_name) if branch: branch_name = branch['name'] root_sha1_git = branch['directory'] else: _raise_exception_if_branch_not_found(origin_id, visit_id, timestamp, branch_name) content_info = service.lookup_directory_with_path(root_sha1_git, path) sha1_git = content_info['target'] query_string = 'sha1_git:' + sha1_git content_data = request_content(query_string) except Exception as exc: return handle_view_exception(request, exc) if revision_id: query_params = {'revision': revision_id} else: query_params = {'branch': branch_name} content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) filename = None path_info = None breadcrumbs = [] split_path = path.split('/') filename = split_path[-1] path = path.replace(filename, '') path_info = gen_path_info(path) breadcrumbs.append({'name': root_sha1_git[:7], 'url': reverse('browse-origin-directory', kwargs=url_args, query_params=query_params)}) for pi in path_info: bc_url_args = dict(url_args) bc_url_args['path'] = pi['path'] breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-origin-directory', kwargs=bc_url_args, query_params=query_params)}) breadcrumbs.append({'name': filename, 'url': None}) content_raw_url = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params={'filename': filename}) content_metadata = { 'sha1 checksum': content_data['checksums']['sha1'], 'sha1_git checksum': content_data['checksums']['sha1_git'], 'sha256 checksum': content_data['checksums']['sha256'], 'blake2s256 checksum': content_data['checksums']['blake2s256'], 'mime type': content_data['mimetype'], 'encoding': content_data['encoding'], 'size': filesizeformat(content_data['length']), 'language': content_data['language'], 'licenses': content_data['licenses'], 'origin id': origin_info['id'], 'origin type': origin_info['type'], 'origin url': origin_info['url'], 'origin visit': format_utc_iso_date(visit_info['date']), 'path': '/' + path, 'filename': filename } return render(request, 'content.html', - {'top_panel_visible': True, + {'heading': 'Content information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Content', 'top_panel_text_right': _gen_origin_link( origin_id, origin_info['url']), 'swh_object_metadata': content_metadata, 'main_panel_visible': True, 'content': content_display_data['content_data'], 'mimetype': content_data['mimetype'], 'language': content_display_data['language'], 'breadcrumbs': breadcrumbs, 'branches': branches, 'branch': branch_name, 'top_right_link': content_raw_url, 'top_right_link_text': 'Raw File'}) def _gen_directory_link(url_args, revision, link_text): directory_url = reverse('browse-origin-directory', kwargs=url_args, query_params={'revision': revision}) return gen_link(directory_url, link_text) NB_LOG_ENTRIES = 20 @browse_route(r'origin/(?P[0-9]+)/log/', r'origin/(?P[0-9]+)/visit/(?P[0-9]+)/log/', # noqa r'origin/(?P[0-9]+)/ts/(?P.+)/log/', view_name='browse-origin-log') def origin_log_browse(request, origin_id, visit_id=None, timestamp=None): """Django view that produces an HTML display of revisions history (aka the commit log) associated to a SWH origin. The url scheme that points to it is the following: * :http:get:`/browse/origin/(origin_id)/log/` * :http:get:`/browse/origin/(origin_id)/visit/(visit_id)/log/` * :http:get:`/browse/origin/(origin_id)/ts/(timestamp)/log/` Args: request: input django http request origin_id: id of a swh origin visit_id: optionnal visit id parameter (the last one will be used by default) timestamp: optionnal visit timestamp parameter (the last one will be used by default) revs_breadcrumb: query parameter used internally to store the navigation breadcrumbs (i.e. the list of descendant revisions visited so far). per_page: optionnal query parameter used to specify the number of log entries per page branch: optionnal query parameter that specifies the origin branch from which to retrieve the content revision: optional query parameter to specify the origin revision from which to retrieve the directory Returns: The HTML rendering of revisions history for a given SWH visit. """ # noqa try: if not visit_id and not timestamp: origin_visits = get_origin_visits(origin_id) if not origin_visits: raise NotFoundExc('No SWH visit associated to ' 'origin with id %s' % origin_id) return origin_log_browse(request, origin_id, origin_visits[-1]['visit']) branches, url_args = \ _get_origin_branches_and_url_args(origin_id, visit_id, timestamp) visit_info = get_origin_visit(origin_id, visit_id, timestamp) for b in branches: b['url'] = reverse('browse-origin-log', kwargs=url_args, query_params={'branch': b['name']}) revision_id = request.GET.get('revision', None) revs_breadcrumb = request.GET.get('revs_breadcrumb', None) branch_name = request.GET.get('branch', 'HEAD') if revision_id: revision = service.lookup_revision(revision_id) branches.append({'name': revision_id, 'revision': revision_id, 'directory': revision['directory']}) revision = revision_id branch_name = revision_id elif revs_breadcrumb: revs = revs_breadcrumb.split('/') revision = revs[-1] else: branch = _get_branch(branches, branch_name) if branch: branch_name = branch['name'] revision = branch['revision'] else: _raise_exception_if_branch_not_found(origin_id, visit_id, timestamp, branch_name) per_page = int(request.GET.get('per_page', NB_LOG_ENTRIES)) revision_log = service.lookup_revision_log(revision, limit=per_page+1) revision_log = list(revision_log) except Exception as exc: return handle_view_exception(request, exc) revision_log_display_data = prepare_revision_log_for_display( revision_log, per_page, revs_breadcrumb, origin_context=True) prev_rev = revision_log_display_data['prev_rev'] prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb'] prev_log_url = None if prev_rev: prev_log_url = \ reverse('browse-origin-log', kwargs=url_args, query_params={'revs_breadcrumb': prev_revs_breadcrumb, 'per_page': per_page, 'branch': branch_name}) next_rev = revision_log_display_data['next_rev'] next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb'] next_log_url = None if next_rev: next_log_url = \ reverse('browse-origin-log', kwargs=url_args, query_params={'revs_breadcrumb': next_revs_breadcrumb, 'per_page': per_page, 'branch': branch_name}) revision_log_data = revision_log_display_data['revision_log_data'] for i, log in enumerate(revision_log_data): log['directory'] = _gen_directory_link(url_args, revision_log[i]['id'], 'Tree') origin_info = service.lookup_origin({'id': origin_id}) revision_metadata = { 'origin id': origin_info['id'], 'origin type': origin_info['type'], 'origin url': origin_info['url'], 'origin visit': format_utc_iso_date(visit_info['date']) } return render(request, 'revision-log.html', - {'top_panel_visible': True, + {'heading': 'Revision history information', + 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text_left': 'SWH object: Revision history', 'top_panel_text_right': _gen_origin_link( origin_id, origin_info['url']), 'swh_object_metadata': revision_metadata, 'main_panel_visible': True, 'revision_log': revision_log_data, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, 'branches': branches, 'branch': branch_name, 'top_right_link': None, 'top_right_link_text': None, 'include_top_navigation': True, 'no_origin_context': False}) diff --git a/swh/web/browse/views/person.py b/swh/web/browse/views/person.py index d79b1e53..f7e37476 100644 --- a/swh/web/browse/views/person.py +++ b/swh/web/browse/views/person.py @@ -1,40 +1,41 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from django.shortcuts import render from swh.web.common import service from swh.web.common.exc import handle_view_exception from swh.web.browse.browseurls import browse_route @browse_route(r'person/(?P[0-9]+)/', view_name='browse-person') def person_browse(request, person_id): """ Django view that produces an HTML display of a swh person identified by its id. The url that points to it is :http:get:`/browse/person/(person_id)/`. Args: request: input django http request person_id (int): a swh person id Returns: The HMTL rendering for the metadata of the provided person. """ try: person = service.lookup_person(person_id) except Exception as exc: return handle_view_exception(request, exc) return render(request, 'person.html', - {'top_panel_visible': True, + {'heading': 'Person information', + 'top_panel_visible': True, 'top_panel_collapsible': False, 'top_panel_text_left': 'SWH object: Person', 'top_panel_text_right': 'Name: ' + person['name'], 'swh_object_metadata': person, 'main_panel_visible': False}) diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py index e1aaa2ea..563ec8bd 100644 --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -1,164 +1,166 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import json from django.shortcuts import render from django.utils.safestring import mark_safe from swh.web.common import service from swh.web.common.utils import reverse, format_utc_iso_date from swh.web.common.exc import handle_view_exception from swh.web.browse.browseurls import browse_route from swh.web.browse.utils import ( gen_link, gen_person_link, gen_revision_link, prepare_revision_log_for_display ) def _gen_directory_link(sha1_git, link_text): directory_url = reverse('browse-directory', kwargs={'sha1_git': sha1_git}) return gen_link(directory_url, link_text) def _gen_revision_log_link(revision_id): revision_log_url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id}) return gen_link(revision_log_url, revision_log_url) @browse_route(r'revision/(?P[0-9a-f]+)/', view_name='browse-revision') def revision_browse(request, sha1_git): """ Django view that produces an HTML display of a SWH revision identified by its id. The url that points to it is :http:get:`/browse/revision/(sha1_git)/`. Args: request: input django http request sha1_git: a SWH revision id Returns: The HMTL rendering for the metadata of the provided revision. """ try: revision = service.lookup_revision(sha1_git) except Exception as exc: return handle_view_exception(request, exc) revision_data = {} revision_data['author'] = gen_person_link( revision['author']['id'], revision['author']['name']) revision_data['committer'] = gen_person_link( revision['committer']['id'], revision['committer']['name']) revision_data['committer date'] = format_utc_iso_date( revision['committer_date']) revision_data['date'] = format_utc_iso_date(revision['date']) revision_data['directory'] = _gen_directory_link(revision['directory'], revision['directory']) revision_data['history log'] = _gen_revision_log_link(sha1_git) revision_data['id'] = sha1_git revision_data['merge'] = revision['merge'] revision_data['message'] = revision['message'] revision_data['metadata'] = json.dumps(revision['metadata'], sort_keys=True, indent=4, separators=(',', ': ')) parents = '' for p in revision['parents']: parent_link = gen_revision_link(p) parents += parent_link + '
' revision_data['parents'] = mark_safe(parents) revision_data['synthetic'] = revision['synthetic'] revision_data['type'] = revision['type'] return render(request, 'revision.html', - {'top_panel_visible': False, + {'heading': 'Revision information', + 'top_panel_visible': False, 'top_panel_collapsible': False, 'top_panel_text_left': 'SWH object: Revision', 'top_panel_text_right': 'Sha1 git: ' + sha1_git, 'swh_object_metadata': None, 'main_panel_visible': True, 'revision': revision_data}) NB_LOG_ENTRIES = 20 @browse_route(r'revision/(?P[0-9a-f]+)/log/', view_name='browse-revision-log') def revision_log_browse(request, sha1_git): """ Django view that produces an HTML display of the history log for a SWH revision identified by its id. The url that points to it is :http:get:`/browse/revision/(sha1_git)/log/`. Args: request: input django http request sha1_git: a SWH revision id Returns: The HMTL rendering of the revision history log. """ # noqa try: per_page = int(request.GET.get('per_page', NB_LOG_ENTRIES)) revision_log = service.lookup_revision_log(sha1_git, limit=per_page+1) revision_log = list(revision_log) except Exception as exc: return handle_view_exception(request, exc) revs_breadcrumb = request.GET.get('revs_breadcrumb', None) revision_log_display_data = prepare_revision_log_for_display( revision_log, per_page, revs_breadcrumb) prev_rev = revision_log_display_data['prev_rev'] prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb'] prev_log_url = None if prev_rev: prev_log_url = \ reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': prev_revs_breadcrumb, 'per_page': per_page}) next_rev = revision_log_display_data['next_rev'] next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb'] next_log_url = None if next_rev: next_log_url = \ reverse('browse-revision-log', kwargs={'sha1_git': next_rev}, query_params={'revs_breadcrumb': next_revs_breadcrumb, 'per_page': per_page}) revision_log_data = revision_log_display_data['revision_log_data'] for log in revision_log_data: log['directory'] = _gen_directory_link(log['directory'], 'Tree') return render(request, 'revision-log.html', - {'top_panel_visible': False, + {'heading': 'Revision history information', + 'top_panel_visible': False, 'top_panel_collapsible': False, 'top_panel_text_left': 'SWH object: Revision history', 'top_panel_text_right': 'Sha1 git: ' + sha1_git, 'swh_object_metadata': None, 'main_panel_visible': True, 'revision_log': revision_log_data, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, 'branches': None, 'branch': None, 'top_right_link': None, 'top_right_link_text': None, 'include_top_navigation': False, 'no_origin_context': True}) diff --git a/swh/web/static/img/swh-api.png b/swh/web/static/img/swh-api.png new file mode 100644 index 00000000..29da830a Binary files /dev/null and b/swh/web/static/img/swh-api.png differ diff --git a/swh/web/static/img/swh-browse.png b/swh/web/static/img/swh-browse.png new file mode 100644 index 00000000..19f39701 Binary files /dev/null and b/swh/web/static/img/swh-browse.png differ diff --git a/swh/web/templates/browse.html b/swh/web/templates/browse.html index 49df049b..6f8f0049 100644 --- a/swh/web/templates/browse.html +++ b/swh/web/templates/browse.html @@ -1,54 +1,56 @@ {% extends "layout.html" %} {% load swh_templatetags %} +{% block title %}{{ heading }} – Software Heritage archive {% endblock %} + {% block content %}
{% if top_panel_visible %}
{% if top_panel_collapsible %}
{% endif %} {% for key, val in swh_object_metadata.items|dictsort:"0.lower" %} {% endfor %}
{% if top_panel_collapsible %}
{% endif %}
{% endif %} {% if main_panel_visible %}
{% block swh-browse-main-panel-content %}{% endblock %}
{% endif %}
{% block swh-browse-after-panels %}{% endblock %} {% endblock %} diff --git a/swh/web/templates/content.html b/swh/web/templates/content.html index 14e5a6f6..32225f1c 100644 --- a/swh/web/templates/content.html +++ b/swh/web/templates/content.html @@ -1,43 +1,41 @@ {% extends "browse.html" %} {% load static %} {% block header %} - + {% endblock %} -{% block title %}Content display{% endblock %} - {% block swh-browse-main-panel-content %} {% include "includes/top-navigation.html" %}
{% if "inode/x-empty" == mimetype %} File is empty {% elif "text/" in mimetype %}
     {{ content }}
   
{% elif "image/" in mimetype and content %} {% else %} Content with mime type {{ mimetype }} can not be displayed {% endif %}
{% endblock %} diff --git a/swh/web/templates/directory.html b/swh/web/templates/directory.html index 7fb5777d..7c30a7c0 100644 --- a/swh/web/templates/directory.html +++ b/swh/web/templates/directory.html @@ -1,44 +1,42 @@ {% extends "browse.html" %} -{% block title %}Directory browse{% endblock %} - {% block swh-browse-main-panel-content %} {% include "includes/top-navigation.html" %}
{% for d in dirs %} {% endfor %} {% for f in files %} {% endfor %}
File Mode Size Sha1 git
{{ d.name }} {{ d.perms }} {{ d.target }}
{{ f.name }} {{ f.perms }} {{ f.length }} {{ f.target }}
{% endblock %} diff --git a/swh/web/templates/error.html b/swh/web/templates/error.html index de5b34e2..e30796a2 100644 --- a/swh/web/templates/error.html +++ b/swh/web/templates/error.html @@ -1,31 +1,33 @@ {% extends "layout.html" %} {% load static %} +{% block title %}Error {{ error_code }} – Software Heritage archive {% endblock %} + {% block content %}
Error
{{ error_code }}

{{ error_message }}

{% endblock %} \ No newline at end of file diff --git a/swh/web/templates/homepage.html b/swh/web/templates/homepage.html new file mode 100644 index 00000000..d98ce96d --- /dev/null +++ b/swh/web/templates/homepage.html @@ -0,0 +1,58 @@ +{% extends "layout.html" %} +{% load static %} +{% block title %}The Software Heritage archive{% endblock %} +{% block content %} +

Welcolme to the Software Heritage archive

+ +

Overview

+ +

+The long term goal of the Software Heritage initiative is to collect +all publicly available software in source code form together with its +development history, replicate it massively to ensure its preservation, +and share it with everyone who needs it. + +The Software Heritage archive is growing over time as we crawl new source code from software +projects and development forges. We will incrementally release archive search +and browse functionalities — as of now you can check whether source code you care +about is already present in the archive or not. +

+ +

Content

+ +

+A significant amount of source code has already been ingested in the Software Heritage +archive. It currently includes: +

    +
  • public repositories from GitHub
  • +
  • public repositories from the former Gitorious code hosting service
  • +
  • public repositories from the former Google Code project hosting service
  • +
  • source packages from the Debian distribution (as of August 2015, via the snapshot service)
  • +
  • releases from the GNU project (as of August 2015)
  • +
+ +

+ +

Archive access

+ + + + +{% endblock %} \ No newline at end of file diff --git a/swh/web/templates/includes/apidoc-header.html b/swh/web/templates/includes/apidoc-header.html index 170feeac..3d70f48f 100644 --- a/swh/web/templates/includes/apidoc-header.html +++ b/swh/web/templates/includes/apidoc-header.html @@ -1,106 +1,106 @@ -

This document describes the Software Heritage Web API.

+

Software Heritage Web API

Endpoint index

You can jump directly to the endpoint index, which lists all available API functionalities, or read on for more general information about the API.

Data model

The Software Heritage project harvests publicly available source code by tracking software distribution channels such as version control systems, tarball releases, and distribution packages.

All retrieved source code and related metadata are stored in the Software Heritage archive, that is conceptually a Merkle DAG. All nodes in the graph are content-addressable, i.e., their node identifiers are computed by hashing their content and, transitively, that of all nodes reachable from them; and no node or edge is ever removed from the graph: the Software Heritage archive is an append-only data structure.

The following types of objects (i.e., graph nodes) can be found in the Software Heritage archive (for more information see the Software Heritage glossary):

  • Content: a specific version of a file stored in the archive, identified by its cryptographic hashes (currently: SHA1, Git-like "salted" SHA1, SHA256). Note that content objects are nameless; their names are context-dependent and stored as part of directory entries (see below).
    Also known as: "blob"
  • Directory: a list of directory entries, where each entry can point to content objects ("file entries"), revisions ("revision entries"), or transitively to other directories ("directory entries"). All entries are associated to the local name of the entry (i.e., a relative path without any path separator) and permission metadata (e.g., chmod value or equivalent).
  • Revision: a point in time snapshot of the content of a directory, together with associated development metadata (e.g., author, timestamp, log message, etc).
    Also known as: "commit".
  • Release: a revision that has been marked as noteworthy with a specific name (e.g., a version number), together with associated development metadata (e.g., author, timestamp, etc).
    Also known as: "tag"
  • Origin: an Internet-based location from which a coherent set of objects (contents, revisions, releases, etc.) archived by Software Heritage has been obtained. Origins are currently identified by URLs.
  • Visit: the passage of Software Heritage on a given origin, to retrieve all source code and metadata available there at the time. A visit object stores the state of all visible branches (if any) available at the origin at visit time; each of them points to a revision object in the archive. Future visits of the same origin will create new visit objects, without removing previous ones.
  • Person: an entity referenced by a revision as either the author or the committer of the corresponding change. A person is associated to a full name and/or an email address.

Version

The current version of the API is v1.

Warning: this version of the API is not to be considered stable yet. Non-backward compatible changes might happen even without changing the API version number.

Schema

API access is over HTTPS.

All API endpoints are rooted at https://archive.softwareheritage.org/api/1/.

Data is sent and received as JSON by default.

Example:

  • from the command line:

    curl -i https://archive.softwareheritage.org/api/1/stat/counters/

Response format override

The response format can be overridden using the Accept request header. In particular, Accept: text/html (that web browsers send by default) requests HTML pretty-printing, whereas Accept: application/yaml requests YAML-encoded responses.

Example:

  • /api/1/stat/counters/
  • from the command line:

    curl -i -H 'Accept: application/yaml' https://archive.softwareheritage.org/api/1/stat/counters/

Parameters

Some API endpoints can be tweaked by passing optional parameters. For GET requests, optional parameters can be passed as an HTTP query string.

The optional parameter fields is accepted by all endpoints that return dictionaries and can be used to restrict the list of fields returned by the API, in case you are not interested in all of them. By default, all available fields are returned.

Example:

Errors

While API endpoints will return different kinds of errors depending on their own semantics, some error patterns are common across all endpoints.

Sending malformed data, including syntactically incorrect object identifiers, will result in a 400 Bad Request HTTP response. Example:

  • /api/1/content/deadbeef/ (client error: "deadbeef" is too short to be a syntactically valid object identifier)
  • from the command line:

    curl -i https://archive.softwareheritage.org/api/1/content/deadbeef/

Requesting non existent resources will result in a 404 Not Found HTTP response. Example:

Unavailability of the underlying storage backend will result in a 503 Service Unavailable HTTP response.

Pagination

Requests that might potentially return many items will be paginated.

Page size is set to a default (usually: 10 items), but might be overridden with the per_page query parameter up to a maximum (usually: 50 items). Example:

curl https://archive.softwareheritage.org/api/1/origin/1/visits/?per_page=2

To navigate through paginated results, a Link HTTP response header is available to link the current result page to the next one. Example:

curl -i https://archive.softwareheritage.org/api/1/origin/1/visits/?per_page=2 | grep ^Link:
 Link: </api/1/origin/1/visits/?last_visit=2&per_page=2>; rel="next",

Rate limiting

Due to limited resource availability on the back end side, API usage is currently rate limited. Furthermore, as API usage is currently entirely anonymous (i.e., without any authentication), API "users" are currently identified by their origin IP address.

Three HTTP response fields will inform you about the current state of limits that apply to your current rate limiting bucket:

  • X-RateLimit-Limit: maximum number of permitted requests per hour
  • X-RateLimit-Remaining: number of permitted requests remaining before the next reset
  • X-RateLimit-Reset: the time (expressed in Unix time seconds) at which the current rate limiting will expire, resetting to a fresh X-RateLimit-Limit

Example:

curl -i https://archive.softwareheritage.org/api/1/stat/counters/ | grep ^X-RateLimit
 X-RateLimit-Limit: 60
 X-RateLimit-Remaining: 54
 X-RateLimit-Reset: 1485794532
diff --git a/swh/web/templates/includes/apidoc-header.md b/swh/web/templates/includes/apidoc-header.md index ff6fe334..96f7de3d 100644 --- a/swh/web/templates/includes/apidoc-header.md +++ b/swh/web/templates/includes/apidoc-header.md @@ -1,199 +1,199 @@ -This document describes the Software Heritage Web API. +# Software Heritage Web API ### Endpoint index You can jump directly to the endpoint index, which lists all available API functionalities, or read on for more general information about the API. ### Data model The [Software Heritage](https://www.softwareheritage.org/) project harvests publicly available source code by tracking software distribution channels such as version control systems, tarball releases, and distribution packages. All retrieved source code and related metadata are stored in the Software Heritage archive, that is conceptually a [Merkle DAG](https://en.wikipedia.org/wiki/Merkle_tree). All nodes in the graph are content-addressable, i.e., their node identifiers are computed by hashing their content and, transitively, that of all nodes reachable from them; and no node or edge is ever removed from the graph: the Software Heritage archive is an append-only data structure. The following types of objects (i.e., graph nodes) can be found in the Software Heritage archive (for more information see the [Software Heritage glossary](https://wiki.softwareheritage.org/index.php?title=Glossary)): - **Content**: a specific version of a file stored in the archive, identified by its cryptographic hashes (currently: SHA1, Git-like "salted" SHA1, SHA256). Note that content objects are nameless; their names are context-dependent and stored as part of directory entries (see below).
*Also known as:* "blob" - **Directory**: a list of directory entries, where each entry can point to content objects ("file entries"), revisions ("revision entries"), or transitively to other directories ("directory entries"). All entries are associated to the local name of the entry (i.e., a relative path without any path separator) and permission metadata (e.g., chmod value or equivalent). - **Revision**: a point in time snapshot of the content of a directory, together with associated development metadata (e.g., author, timestamp, log message, etc).
*Also known as:* "commit". - **Release**: a revision that has been marked as noteworthy with a specific name (e.g., a version number), together with associated development metadata (e.g., author, timestamp, etc).
*Also known as:* "tag" - **Origin**: an Internet-based location from which a coherent set of objects (contents, revisions, releases, etc.) archived by Software Heritage has been obtained. Origins are currently identified by URLs. - **Visit**: the passage of Software Heritage on a given origin, to retrieve all source code and metadata available there at the time. A visit object stores the state of all visible branches (if any) available at the origin at visit time; each of them points to a revision object in the archive. Future visits of the same origin will create new visit objects, without removing previous ones. - **Person**: an entity referenced by a revision as either the author or the committer of the corresponding change. A person is associated to a full name and/or an email address. ### Version The current version of the API is **v1**. **Warning:** this version of the API is not to be considered stable yet. Non-backward compatible changes might happen even without changing the API version number. ### Schema API access is over HTTPS. All API endpoints are rooted at . Data is sent and received as JSON by default. Example: - from the command line: ``` shell curl -i https://archive.softwareheritage.org/api/1/stat/counters/ ``` #### Response format override The response format can be overridden using the `Accept` request header. In particular, `Accept: text/html` (that web browsers send by default) requests HTML pretty-printing, whereas `Accept: application/yaml` requests YAML-encoded responses. Example: - [/api/1/stat/counters/](/api/1/stat/counters/) - from the command line: ``` shell curl -i -H 'Accept: application/yaml' https://archive.softwareheritage.org/api/1/stat/counters/ ``` ### Parameters Some API endpoints can be tweaked by passing optional parameters. For GET requests, optional parameters can be passed as an HTTP query string. The optional parameter `fields` is accepted by all endpoints that return dictionaries and can be used to restrict the list of fields returned by the API, in case you are not interested in all of them. By default, all available fields are returned. Example: - [/api/1/stat/counters/\?fields\=content,directory,revision](/api/1/stat/counters/?fields=content,directory,revision) - from the command line: ``` shell curl https://archive.softwareheritage.org/api/1/stat/counters/?fields=content,directory,revision ``` ### Errors While API endpoints will return different kinds of errors depending on their own semantics, some error patterns are common across all endpoints. Sending malformed data, including syntactically incorrect object identifiers, will result in a `400 Bad Request` HTTP response. Example: - [/api/1/content/deadbeef/](/api/1/content/deadbeef/) (client error: "deadbeef" is too short to be a syntactically valid object identifier) - from the command line: ``` shell curl -i https://archive.softwareheritage.org/api/1/content/deadbeef/ ``` Requesting non existent resources will result in a `404 Not Found` HTTP response. Example: - [/api/1/content/0123456789abcdef0123456789abcdef01234567/](/api/1/content/0123456789abcdef0123456789abcdef01234567/) (error: no object with that identifier is available [yet?]) - from the command line: ``` shell curl -i https://archive.softwareheritage.org/api/1/content/04740277a81c5be6c16f6c9da488ca073b770d7f/ ``` Unavailability of the underlying storage backend will result in a `503 Service Unavailable` HTTP response. ### Pagination Requests that might potentially return many items will be paginated. Page size is set to a default (usually: 10 items), but might be overridden with the `per_page` query parameter up to a maximum (usually: 50 items). Example: ``` shell curl https://archive.softwareheritage.org/api/1/origin/1/visits/?per_page=2 ``` To navigate through paginated results, a `Link` HTTP response header is available to link the current result page to the next one. Example: curl -i https://archive.softwareheritage.org/api/1/origin/1/visits/?per_page=2 | grep ^Link: Link: ; rel="next", ### Rate limiting Due to limited resource availability on the back end side, API usage is currently rate limited. Furthermore, as API usage is currently entirely anonymous (i.e., without any authentication), API "users" are currently identified by their origin IP address. Three HTTP response fields will inform you about the current state of limits that apply to your current rate limiting bucket: - `X-RateLimit-Limit`: maximum number of permitted requests per hour - `X-RateLimit-Remaining`: number of permitted requests remaining before the next reset - `X-RateLimit-Reset`: the time (expressed in [Unix time](https://en.wikipedia.org/wiki/Unix_time) seconds) at which the current rate limiting will expire, resetting to a fresh `X-RateLimit-Limit` Example: curl -i https://archive.softwareheritage.org/api/1/stat/counters/ | grep ^X-RateLimit X-RateLimit-Limit: 60 X-RateLimit-Remaining: 54 X-RateLimit-Reset: 1485794532 diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html index 3e44112e..7e97ead2 100644 --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,79 +1,79 @@ {% load static %} {% block title %}{% endblock %} {% block header %}{% endblock %} - + - +
{% block content %}{% endblock %}
back to top
diff --git a/swh/web/templates/origin.html b/swh/web/templates/origin.html index d81692ce..4a09f9cf 100644 --- a/swh/web/templates/origin.html +++ b/swh/web/templates/origin.html @@ -1,60 +1,58 @@ {% extends "browse.html" %} {% load static %} {% load swh_templatetags %} -{% block title %}Origin information{% endblock %} - {% block swh-browse-main-panel-content %}

Visits history

Calendar

List

{% for v in visits %} {% endfor %}
Visit id Visit date Visit status Browse revision url
{{ v.visit }} {{ v.date }} {{ v.status }} {{ v.browse_url }}
{% endblock %} diff --git a/swh/web/templates/person.html b/swh/web/templates/person.html index 6a0c992a..f40323ad 100644 --- a/swh/web/templates/person.html +++ b/swh/web/templates/person.html @@ -1,3 +1 @@ {% extends "browse.html" %} - -{% block title %}Person{% endblock %} diff --git a/swh/web/templates/revision-log.html b/swh/web/templates/revision-log.html index d1a3519a..e3e4c24f 100644 --- a/swh/web/templates/revision-log.html +++ b/swh/web/templates/revision-log.html @@ -1,63 +1,62 @@ {% extends "browse.html" %} -{% block title %}Revision Log{% endblock %} {% block swh-browse-main-panel-content %} {% if no_origin_context %}

{{ top_panel_text_left }}

{{ top_panel_text_right }}

{% endif %} {% if include_top_navigation %} {% include "includes/top-navigation.html" %} {% endif %}
{% for log in revision_log %} {% endfor %}
Author Revision Message Date
{{ log.author }} {{ log.revision }} {{ log.message_shorten }} {{ log.date }} {{ log.directory }}
{% endblock %} {% block swh-browse-after-panels %}
    {% if next_log_url %}
  • Newer
  • {% else %}
  • Newer
  • {% endif %} {% if prev_log_url %}
  • Older
  • {% else %}
  • Older
  • {% endif %}
{% endblock %} diff --git a/swh/web/templates/revision.html b/swh/web/templates/revision.html index 833aa896..c198d7de 100644 --- a/swh/web/templates/revision.html +++ b/swh/web/templates/revision.html @@ -1,25 +1,24 @@ {% extends "browse.html" %} -{% block title %}Revision{% endblock %} {% block swh-browse-main-panel-content %}
-

{{ top_panel_text_left }}

+

{{ top_panel_text_left }}

{{ top_panel_text_right }}

- +
{% for key, val in revision.items|dictsort:"0.lower" %} {% endfor %}
{% endblock %} \ No newline at end of file diff --git a/swh/web/urls.py b/swh/web/urls.py index 07556784..058ef4a6 100644 --- a/swh/web/urls.py +++ b/swh/web/urls.py @@ -1,37 +1,37 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from django.conf.urls import ( url, include, handler400, handler403, handler404, handler500 ) from django.contrib.staticfiles.urls import staticfiles_urlpatterns -from django.shortcuts import redirect +from django.shortcuts import render from django.views.generic.base import RedirectView from swh.web.common.exc import ( swh_handle400, swh_handle403, swh_handle404, swh_handle500 ) favicon_view = RedirectView.as_view(url='/static/img/icons/swh-logo-32x32.png', permanent=True) def default_view(request): - return redirect('api_homepage') + return render(request, "homepage.html") urlpatterns = [ url(r'^favicon\.ico$', favicon_view), url(r'^api/', include('swh.web.api.urls')), url(r'^browse/', include('swh.web.browse.urls')), url(r'^$', default_view, name='swh-web-homepage'), ] urlpatterns += staticfiles_urlpatterns() handler400 = swh_handle400 # noqa handler403 = swh_handle403 # noqa handler404 = swh_handle404 # noqa handler500 = swh_handle500 # noqa