diff --git a/swh/web/api/apiresponse.py b/swh/web/api/apiresponse.py index e6e18b1f..709f900e 100644 --- a/swh/web/api/apiresponse.py +++ b/swh/web/api/apiresponse.py @@ -1,180 +1,180 @@ # Copyright (C) 2017-2018 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 import json from rest_framework.response import Response from swh.storage.exc import StorageDBError, StorageAPIError from swh.web.common.exc import NotFoundExc, ForbiddenExc from swh.web.common.utils import shorten_path, gen_path_info from swh.web.api import utils def compute_link_header(rv, options): """Add Link header in returned value results. Args: rv (dict): dictionary with keys: - headers: potential headers with 'link-next' and 'link-prev' keys - results: containing the result to return options (dict): the initial dict to update with result if any Returns: dict: dictionary with optional keys 'link-next' and 'link-prev' """ link_headers = [] if 'headers' not in rv: return {} rv_headers = rv['headers'] if 'link-next' in rv_headers: link_headers.append('<%s>; rel="next"' % ( rv_headers['link-next'])) if 'link-prev' in rv_headers: link_headers.append('<%s>; rel="previous"' % ( rv_headers['link-prev'])) if link_headers: link_header_str = ','.join(link_headers) headers = options.get('headers', {}) headers.update({ 'Link': link_header_str }) return headers return {} def filter_by_fields(request, data): """Extract a request parameter 'fields' if it exists to permit the filtering on he data dict's keys. If such field is not provided, returns the data as is. """ fields = request.query_params.get('fields') if fields: fields = set(fields.split(',')) data = utils.filter_field_keys(data, fields) return data def transform(rv): """Transform an eventual returned value with multiple layer of information with only what's necessary. If the returned value rv contains the 'results' key, this is the associated value which is returned. Otherwise, return the initial dict without the potential 'headers' key. """ if 'results' in rv: return rv['results'] if 'headers' in rv: rv.pop('headers') return rv def make_api_response(request, data, doc_data={}, options={}): """Generates an API response based on the requested mimetype. Args: request: a DRF Request object data: raw data to return in the API response doc_data: documentation data for HTML response options: optionnal data that can be used to generate the response Returns: a DRF Response a object """ if data: options['headers'] = compute_link_header(data, options) data = transform(data) data = filter_by_fields(request, data) doc_env = doc_data headers = {} if 'headers' in options: doc_env['headers_data'] = options['headers'] headers = options['headers'] # get request status code doc_env['status_code'] = options.get('status', 200) response_args = {'status': doc_env['status_code'], 'headers': headers, 'content_type': request.accepted_media_type} # when requesting HTML, typically when browsing the API through its # documented views, we need to enrich the input data with documentation # related ones and inform DRF that we request HTML template rendering if request.accepted_media_type == 'text/html': if data: data = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')) doc_env['response_data'] = data doc_env['request'] = { 'path': request.path, 'method': request.method, 'absolute_uri': request.build_absolute_uri(), } doc_env['heading'] = shorten_path(str(request.path)) if 'route' in doc_env: doc_env['endpoint_path'] = gen_path_info(doc_env['route']) response_args['data'] = doc_env - response_args['template_name'] = 'apidoc.html' + response_args['template_name'] = 'api/apidoc.html' # otherwise simply return the raw data and let DRF picks # the correct renderer (JSON or YAML) else: response_args['data'] = data return Response(**response_args) def error_response(request, error, doc_data): """Private function to create a custom error response. Args: request: a DRF Request object error: the exception that caused the error doc_data: documentation data for HTML response """ error_code = 400 if isinstance(error, NotFoundExc): error_code = 404 elif isinstance(error, ForbiddenExc): error_code = 403 elif isinstance(error, StorageDBError): error_code = 503 elif isinstance(error, StorageAPIError): error_code = 503 error_opts = {'status': error_code} error_data = { 'exception': error.__class__.__name__, 'reason': str(error), } return make_api_response(request, error_data, doc_data, options=error_opts) diff --git a/swh/web/api/views/utils.py b/swh/web/api/views/utils.py index 47a97d0c..bf5bce3e 100644 --- a/swh/web/api/views/utils.py +++ b/swh/web/api/views/utils.py @@ -1,73 +1,73 @@ # Copyright (C) 2015-2018 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 def api_lookup(lookup_fn, *args, notfound_msg='Object not found', enrich_fn=None): """ 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: - 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. """ if enrich_fn is None: enrich_fn = (lambda x: x) 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') + return Response({}, template_name='api/api.html') 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") + return Response(env, template_name="api/endpoints.html") diff --git a/swh/web/browse/urls.py b/swh/web/browse/urls.py index 31c8587d..4a7e682b 100644 --- a/swh/web/browse/urls.py +++ b/swh/web/browse/urls.py @@ -1,45 +1,45 @@ # Copyright (C) 2017-2018 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 django.conf.urls import url from django.shortcuts import render import swh.web.browse.views.directory # noqa import swh.web.browse.views.content # noqa import swh.web.browse.views.origin # noqa import swh.web.browse.views.person # noqa import swh.web.browse.views.release # noqa import swh.web.browse.views.revision # noqa import swh.web.browse.views.snapshot # noqa from swh.web.browse.browseurls import BrowseUrls from swh.web.browse.identifiers import swh_id_browse def _browse_help_view(request): - return render(request, 'browse-help.html', + return render(request, 'browse/help.html', {'heading': 'How to browse the archive ?'}) def _browse_search_view(request): - return render(request, 'browse-search.html', + return render(request, 'browse/search.html', {'heading': 'Search software origins to browse'}) def _browse_vault_view(request): - return render(request, 'browse-vault-ui.html', + return render(request, 'browse/vault-ui.html', {'heading': 'Download archive content from the Vault'}) urlpatterns = [ url(r'^$', _browse_search_view), url(r'^help/$', _browse_help_view, name='browse-help'), url(r'^search/$', _browse_search_view, name='browse-search'), url(r'^vault/$', _browse_vault_view, name='browse-vault'), # for backward compatibility url(r'^(?Pswh:[0-9]+:[a-z]+:[0-9a-f]+.*)/$', swh_id_browse) ] urlpatterns += BrowseUrls.get_url_patterns() diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py index 2b59ae24..12076fdd 100644 --- a/swh/web/browse/views/content.py +++ b/swh/web/browse/views/content.py @@ -1,293 +1,293 @@ # Copyright (C) 2017-2018 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 import difflib import json from distutils.util import strtobool from django.http import HttpResponse from django.utils.safestring import mark_safe 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, gen_path_info ) from swh.web.common.exc import NotFoundExc, handle_view_exception from swh.web.browse.utils import ( request_content, prepare_content_for_display, content_display_max_size, get_snapshot_context, get_swh_persistent_ids, gen_link ) 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/` """ # noqa try: algo, checksum = query.parse_hash(query_string) checksum = hash_to_hex(checksum) content_data = request_content(query_string, max_size=None) 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/') or \ content_data['mimetype'] == 'inode/x-empty': 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 _auto_diff_size_limit = 20000 @browse_route(r'content/(?P.*)/diff/(?P.*)', # noqa view_name='diff-contents') def _contents_diff(request, from_query_string, to_query_string): """ Browse endpoint used to compute unified diffs between two contents. Diffs are generated only if the two contents are textual. By default, diffs whose size are greater than 20 kB will not be generated. To force the generation of large diffs, the 'force' boolean query parameter must be used. Args: request: input django http request from_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 identifying the first content to_query_string: same as above for identifying the second content Returns: A JSON object containing the unified diff. """ diff_data = {} content_from = None content_to = None content_from_size = 0 content_to_size = 0 content_from_lines = [] content_to_lines = [] force = request.GET.get('force', 'false') path = request.GET.get('path', None) language = 'nohighlight-swh' force = bool(strtobool(force)) if from_query_string == to_query_string: diff_str = 'File renamed without changes' else: try: text_diff = True if from_query_string: content_from = \ request_content(from_query_string, max_size=None) content_from_display_data = \ prepare_content_for_display(content_from['raw_data'], content_from['mimetype'], path) language = content_from_display_data['language'] content_from_size = content_from['length'] if not (content_from['mimetype'].startswith('text/') or content_from['mimetype'] == 'inode/x-empty'): text_diff = False if text_diff and to_query_string: content_to = request_content(to_query_string, max_size=None) content_to_display_data = prepare_content_for_display( content_to['raw_data'], content_to['mimetype'], path) language = content_to_display_data['language'] content_to_size = content_to['length'] if not (content_to['mimetype'].startswith('text/') or content_to['mimetype'] == 'inode/x-empty'): text_diff = False diff_size = abs(content_to_size - content_from_size) if not text_diff: diff_str = 'Diffs are not generated for non textual content' language = 'nohighlight-swh' elif not force and diff_size > _auto_diff_size_limit: diff_str = 'Large diffs are not automatically computed' language = 'nohighlight-swh' else: if content_from: content_from_lines = \ content_from['raw_data'].decode('utf-8')\ .splitlines(True) if content_from_lines and \ content_from_lines[-1][-1] != '\n': content_from_lines[-1] += '[swh-no-nl-marker]\n' if content_to: content_to_lines = content_to['raw_data'].decode('utf-8')\ .splitlines(True) if content_to_lines and content_to_lines[-1][-1] != '\n': content_to_lines[-1] += '[swh-no-nl-marker]\n' diff_lines = difflib.unified_diff(content_from_lines, content_to_lines) diff_str = ''.join(list(diff_lines)[2:]) except Exception as e: diff_str = str(e) diff_data['diff_str'] = diff_str diff_data['language'] = language diff_data_json = json.dumps(diff_data, separators=(',', ': ')) return HttpResponse(diff_data_json, content_type='application/json') @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)/` """ # noqa try: algo, checksum = query.parse_hash(query_string) checksum = hash_to_hex(checksum) content_data = request_content(query_string, raise_if_unavailable=False) origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) snapshot_context = None if origin_url: try: snapshot_context = get_snapshot_context(None, origin_type, origin_url) except Exception: raw_cnt_url = reverse('browse-content', kwargs={'query_string': query_string}) error_message = \ ('The Software Heritage archive has a content ' 'with the hash you provided but the origin ' 'mentioned in your request appears broken: %s. ' 'Please check the URL and try again.\n\n' 'Nevertheless, you can still browse the content ' 'without origin information: %s' % (gen_link(origin_url), gen_link(raw_cnt_url))) raise NotFoundExc(error_message) if snapshot_context: snapshot_context['visit_info'] = None except Exception as exc: return handle_view_exception(request, exc) path = request.GET.get('path', None) content = None language = None mimetype = None if content_data['raw_data'] is not None: content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) content = content_display_data['content_data'] language = content_display_data['language'] mimetype = content_display_data['mimetype'] 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[:-len(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'] } if filename: content_metadata['filename'] = filename sha1_git = content_data['checksums']['sha1_git'] swh_ids = get_swh_persistent_ids([{'type': 'content', 'id': sha1_git}]) heading = 'Content - %s' % sha1_git if breadcrumbs: content_path = '/'.join([bc['name'] for bc in breadcrumbs]) heading += ' - %s' % content_path - return render(request, 'content.html', + return render(request, 'browse/content.html', {'heading': heading, 'swh_object_name': 'Content', 'swh_object_metadata': content_metadata, 'content': content, 'content_size': content_data['length'], 'max_content_size': content_display_max_size, 'mimetype': mimetype, 'language': language, 'breadcrumbs': breadcrumbs, 'top_right_link': content_raw_url, 'top_right_link_text': mark_safe( 'Raw File'), 'snapshot_context': snapshot_context, 'vault_cooking': None, 'show_actions_menu': True, 'swh_ids': swh_ids, 'error_code': content_data['error_code'], 'error_message': content_data['error_message'], 'error_description': content_data['error_description']}, status=content_data['error_code']) diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py index c2587fa2..3165584b 100644 --- a/swh/web/browse/views/directory.py +++ b/swh/web/browse/views/directory.py @@ -1,148 +1,148 @@ # Copyright (C) 2017-2018 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 django.shortcuts import render, redirect from django.template.defaultfilters import filesizeformat from swh.web.common import service from swh.web.common.utils import ( reverse, gen_path_info ) from swh.web.common.exc import handle_view_exception, NotFoundExc from swh.web.browse.utils import ( get_directory_entries, get_snapshot_context, get_readme_to_display, get_swh_persistent_ids, gen_link ) 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)/]` """ # noqa root_sha1_git = sha1_git try: if path: dir_info = service.lookup_directory_with_path(sha1_git, path) # some readme files can reference assets reachable from the # browsed directory, handle that special case in order to # correctly displayed them if dir_info and dir_info['type'] == 'file': file_raw_url = reverse( 'browse-content-raw', kwargs={'query_string': dir_info['checksums']['sha1']}) return redirect(file_raw_url) sha1_git = dir_info['target'] dirs, files = get_directory_entries(sha1_git) origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) snapshot_context = None if origin_url: try: snapshot_context = get_snapshot_context(None, origin_type, origin_url) except Exception: raw_dir_url = reverse('browse-directory', kwargs={'sha1_git': sha1_git}) error_message = \ ('The Software Heritage archive has a directory ' 'with the hash you provided but the origin ' 'mentioned in your request appears broken: %s. ' 'Please check the URL and try again.\n\n' 'Nevertheless, you can still browse the directory ' 'without origin information: %s' % (gen_link(origin_url), gen_link(raw_dir_url))) raise NotFoundExc(error_message) if snapshot_context: snapshot_context['visit_info'] = None 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 readmes = {} 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']) if f['name'].lower().startswith('readme'): readmes[f['name']] = f['checksums']['sha1'] readme_name, readme_url, readme_html = get_readme_to_display(readmes) 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} vault_cooking = { 'directory_context': True, 'directory_id': sha1_git, 'revision_context': False, 'revision_id': None } swh_ids = get_swh_persistent_ids([{'type': 'directory', 'id': sha1_git}]) heading = 'Directory - %s' % sha1_git if breadcrumbs: dir_path = '/'.join([bc['name'] for bc in breadcrumbs]) + '/' heading += ' - %s' % dir_path - return render(request, 'directory.html', + return render(request, 'browse/directory.html', {'heading': heading, 'swh_object_name': 'Directory', 'swh_object_metadata': dir_metadata, 'dirs': dirs, 'files': files, 'breadcrumbs': breadcrumbs, 'top_right_link': None, 'top_right_link_text': None, 'readme_name': readme_name, 'readme_url': readme_url, 'readme_html': readme_html, 'snapshot_context': snapshot_context, 'vault_cooking': vault_cooking, 'show_actions_menu': True, 'swh_ids': swh_ids}) diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py index f4a6c468..244181f0 100644 --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -1,237 +1,237 @@ # Copyright (C) 2017-2018 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 import json from distutils.util import strtobool from django.http import HttpResponse from django.shortcuts import render, redirect from swh.web.common import service from swh.web.common.utils import ( reverse, format_utc_iso_date, parse_timestamp, get_origin_visits ) from swh.web.common.exc import handle_view_exception from swh.web.browse.utils import ( get_origin_info ) from swh.web.browse.browseurls import browse_route from .utils.snapshot_context import ( browse_snapshot_directory, browse_snapshot_content, browse_snapshot_log, browse_snapshot_branches, browse_snapshot_releases ) @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/directory/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/directory/(?P.+)/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/directory/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/directory/(?P.+)/', # noqa r'origin/(?P.+)/visit/(?P.+)/directory/', # noqa r'origin/(?P.+)/visit/(?P.+)/directory/(?P.+)/', # noqa r'origin/(?P.+)/directory/', # noqa r'origin/(?P.+)/directory/(?P.+)/', # noqa view_name='browse-origin-directory') def origin_directory_browse(request, origin_url, origin_type=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_type)/url/](origin_url)/directory/[(path)/]` * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visit/(timestamp)/directory/[(path)/]` """ # noqa return browse_snapshot_directory( request, origin_type=origin_type, origin_url=origin_url, timestamp=timestamp, path=path) @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/content/(?P.+)/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/content/(?P.+)/', # noqa r'origin/(?P.+)/visit/(?P.+)/content/(?P.+)/', # noqa r'origin/(?P.+)/content/(?P.+)/', # noqa view_name='browse-origin-content') def origin_content_browse(request, origin_url, origin_type=None, path=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_type)/url/](origin_url)/content/(path)/` * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visit/(timestamp)/content/(path)/` """ # noqa return browse_snapshot_content(request, origin_type=origin_type, origin_url=origin_url, timestamp=timestamp, path=path) PER_PAGE = 20 @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/log/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/log/', r'origin/(?P.+)/visit/(?P.+)/log/', # noqa r'origin/(?P.+)/log/', view_name='browse-origin-log') def origin_log_browse(request, origin_url, origin_type=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_type)/url/](origin_url)/log/` * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visit/(timestamp)/log/` """ # noqa return browse_snapshot_log(request, origin_type=origin_type, origin_url=origin_url, timestamp=timestamp) @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/branches/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/branches/', # noqa r'origin/(?P.+)/visit/(?P.+)/branches/', # noqa r'origin/(?P.+)/branches/', # noqa view_name='browse-origin-branches') def origin_branches_browse(request, origin_url, origin_type=None, timestamp=None): """Django view that produces an HTML display of the list of branches associated to an origin for a given visit. The url scheme that points to it is the following: * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/branches/` * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visit/(timestamp)/branches/` """ # noqa return browse_snapshot_branches(request, origin_type=origin_type, origin_url=origin_url, timestamp=timestamp) @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visit/(?P.+)/releases/', # noqa r'origin/(?P[a-z]+)/url/(?P.+)/releases/', # noqa r'origin/(?P.+)/visit/(?P.+)/releases/', # noqa r'origin/(?P.+)/releases/', # noqa view_name='browse-origin-releases') def origin_releases_browse(request, origin_url, origin_type=None, timestamp=None): """Django view that produces an HTML display of the list of releases associated to an origin for a given visit. The url scheme that points to it is the following: * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/releases/` * :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visit/(timestamp)/releases/` """ # noqa return browse_snapshot_releases(request, origin_type=origin_type, origin_url=origin_url, timestamp=timestamp) @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/visits/', r'origin/(?P.+)/visits/', view_name='browse-origin-visits') def origin_visits_browse(request, origin_url, origin_type=None): """Django view that produces an HTML display of visits reporting for a swh origin identified by its id or its url. The url that points to it is :http:get:`/browse/origin/[(origin_type)/url/](origin_url)/visits/`. """ # noqa try: origin_info = get_origin_info(origin_url, origin_type) origin_visits = get_origin_visits(origin_info) except Exception as exc: return handle_view_exception(request, exc) origin_info['last swh visit browse url'] = \ reverse('browse-origin-directory', kwargs={'origin_url': origin_url, 'origin_type': origin_type}) for i, visit in enumerate(origin_visits): url_date = format_utc_iso_date(visit['date'], '%Y-%m-%dT%H:%M:%SZ') visit['fmt_date'] = format_utc_iso_date(visit['date']) query_params = {} if i < len(origin_visits) - 1: if visit['date'] == origin_visits[i+1]['date']: query_params = {'visit_id': visit['visit']} if i > 0: if visit['date'] == origin_visits[i-1]['date']: query_params = {'visit_id': visit['visit']} snapshot = visit['snapshot'] if visit['snapshot'] else '' visit['browse_url'] = reverse('browse-origin-directory', kwargs={'origin_type': origin_type, 'origin_url': origin_url, 'timestamp': url_date}, query_params=query_params) if not snapshot: visit['snapshot'] = '' visit['date'] = parse_timestamp(visit['date']).timestamp() heading = 'Origin visits - %s' % origin_url - return render(request, 'origin-visits.html', + return render(request, 'browse/origin-visits.html', {'heading': heading, 'swh_object_name': 'Visits', 'swh_object_metadata': origin_info, 'origin_visits': origin_visits, 'origin_info': origin_info, 'browse_url_base': '/browse/origin/%s/url/%s/' % (origin_type, origin_url), 'vault_cooking': None, 'show_actions_menu': False}) @browse_route(r'origin/search/(?P.+)/', view_name='browse-origin-search') def _origin_search(request, url_pattern): """Internal browse endpoint to search for origins whose urls contain a provided string pattern or match a provided regular expression. The search is performed in a case insensitive way. """ offset = int(request.GET.get('offset', '0')) limit = int(request.GET.get('limit', '50')) regexp = request.GET.get('regexp', 'false') results = service.search_origin(url_pattern, offset, limit, bool(strtobool(regexp))) results = json.dumps(list(results), sort_keys=True, indent=4, separators=(',', ': ')) return HttpResponse(results, content_type='application/json') @browse_route(r'origin/(?P[0-9]+)/latest_snapshot/', view_name='browse-origin-latest-snapshot') def _origin_latest_snapshot(request, origin_id): """ Internal browse endpoint used to check if an origin has already been visited by Software Heritage and has at least one full visit. """ result = service.lookup_latest_origin_snapshot(origin_id, allowed_statuses=['full']) result = json.dumps(result, sort_keys=True, indent=4, separators=(',', ': ')) return HttpResponse(result, content_type='application/json') @browse_route(r'origin/(?P[a-z]+)/url/(?P.+)/', r'origin/(?P.+)/', view_name='browse-origin') def origin_browse(request, origin_url, origin_type=None): """Django view that redirects to the display of the latest archived snapshot for a given software origin. """ # noqa last_snapshot_url = reverse('browse-origin-directory', kwargs={'origin_type': origin_type, 'origin_url': origin_url}) return redirect(last_snapshot_url) diff --git a/swh/web/browse/views/person.py b/swh/web/browse/views/person.py index e093a1cc..1b8a7da9 100644 --- a/swh/web/browse/views/person.py +++ b/swh/web/browse/views/person.py @@ -1,52 +1,52 @@ # Copyright (C) 2017-2018 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 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 from swh.web.browse.utils import get_snapshot_context @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)/`. """ try: snapshot_context = None origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) snapshot_id = request.GET.get('snapshot_id', None) if origin_url: snapshot_context = get_snapshot_context(None, origin_type, origin_url) elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) person = service.lookup_person(person_id) except Exception as exc: return handle_view_exception(request, exc) heading = 'Person - %s' % person['fullname'] if snapshot_context: context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_url: context_found = 'origin: %s' % origin_url heading += ' - %s' % context_found - return render(request, 'person.html', + return render(request, 'browse/person.html', {'heading': heading, 'swh_object_name': 'Person', 'swh_object_metadata': person, 'snapshot_context': snapshot_context, 'vault_cooking': None, 'show_actions_menu': False}) diff --git a/swh/web/browse/views/release.py b/swh/web/browse/views/release.py index 7f63373f..fa00dd4d 100644 --- a/swh/web/browse/views/release.py +++ b/swh/web/browse/views/release.py @@ -1,179 +1,179 @@ # Copyright (C) 2017-2018 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 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 NotFoundExc, handle_view_exception from swh.web.browse.browseurls import browse_route from swh.web.browse.utils import ( gen_person_link, gen_revision_link, get_snapshot_context, gen_link, gen_snapshot_link, get_swh_persistent_ids ) @browse_route(r'release/(?P[0-9a-f]+)/', view_name='browse-release') def release_browse(request, sha1_git): """ Django view that produces an HTML display of a SWH release identified by its id. The url that points to it is :http:get:`/browse/release/(sha1_git)/`. """ try: release = service.lookup_release(sha1_git) snapshot_context = None origin_info = None snapshot_id = request.GET.get('snapshot_id', None) origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) timestamp = request.GET.get('timestamp', None) visit_id = request.GET.get('visit_id', None) if origin_url: try: snapshot_context = get_snapshot_context(snapshot_id, origin_type, # noqa origin_url, timestamp, visit_id) except Exception: raw_rel_url = reverse('browse-release', kwargs={'sha1_git': sha1_git}) error_message = \ ('The Software Heritage archive has a release ' 'with the hash you provided but the origin ' 'mentioned in your request appears broken: %s. ' 'Please check the URL and try again.\n\n' 'Nevertheless, you can still browse the release ' 'without origin information: %s' % (gen_link(origin_url), gen_link(raw_rel_url))) raise NotFoundExc(error_message) origin_info = snapshot_context['origin_info'] elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) except Exception as exc: return handle_view_exception(request, exc) release_data = {} author_name = release['author']['name'] or release['author']['fullname'] release_data['author'] = \ gen_person_link(release['author']['id'], author_name, snapshot_context) release_data['date'] = format_utc_iso_date(release['date']) release_data['id'] = sha1_git release_data['name'] = release['name'] release_data['synthetic'] = release['synthetic'] release_data['target type'] = release['target_type'] if release['target_type'] == 'revision': release_data['target'] = \ gen_revision_link(release['target'], snapshot_context=snapshot_context) elif release['target_type'] == 'content': content_url = \ reverse('browse-content', kwargs={'query_string': 'sha1_git:' + release['target']}) release_data['target'] = gen_link(content_url, release['target']) elif release['target_type'] == 'directory': directory_url = \ reverse('browse-directory', kwargs={'sha1_git': release['target']}) release_data['target'] = gen_link(directory_url, release['target']) elif release['target_type'] == 'release': release_url = \ reverse('browse-release', kwargs={'sha1_git': release['target']}) release_data['target'] = gen_link(release_url, release['target']) release_note_lines = [] if release['message']: release_note_lines = release['message'].split('\n') vault_cooking = None release_target_link = 'Target: ' if release['target_type'] == 'revision': release_target_link += '' # noqa try: revision = service.lookup_revision(release['target']) vault_cooking = { 'directory_context': True, 'directory_id': revision['directory'], 'revision_context': True, 'revision_id': release['target'] } except Exception: pass else: release_target_link += release['target_type'] release_target_link += ' ' + release_data['target'] if snapshot_context: release_data['snapshot id'] = snapshot_context['snapshot_id'] if origin_info: release_url = reverse('browse-release', kwargs={'sha1_git': release['id']}) release_data['context-independent release'] = \ gen_link(release_url, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) release_data['origin id'] = origin_info['id'] release_data['origin type'] = origin_info['type'] release_data['origin url'] = gen_link(origin_info['url'], origin_info['url']) browse_snapshot_link = \ gen_snapshot_link(snapshot_context['snapshot_id'], link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) release_data['snapshot'] = browse_snapshot_link swh_objects = [{'type': 'release', 'id': sha1_git}] if snapshot_context: snapshot_id = snapshot_context['snapshot_id'] if snapshot_id: swh_objects.append({'type': 'snapshot', 'id': snapshot_id}) swh_ids = get_swh_persistent_ids(swh_objects, snapshot_context) release_note_header = 'None' if len(release_note_lines) > 0: release_note_header = release_note_lines[0] heading = 'Release - %s' % release['name'] if snapshot_context: context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_info: context_found = 'origin: %s' % origin_info['url'] heading += ' - %s' % context_found - return render(request, 'release.html', + return render(request, 'browse/release.html', {'heading': heading, 'swh_object_name': 'Release', 'swh_object_metadata': release_data, 'release_name': release['name'], 'release_note_header': release_note_header, 'release_note_body': '\n'.join(release_note_lines[1:]), 'release_target_link': mark_safe(release_target_link), 'snapshot_context': snapshot_context, 'show_actions_menu': True, 'breadcrumbs': None, 'vault_cooking': vault_cooking, 'top_right_link': None, 'top_right_link_text': None, 'swh_ids': swh_ids}) diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py index a4f3d074..8707865c 100644 --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -1,530 +1,530 @@ # Copyright (C) 2017-2018 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 import hashlib import json import textwrap from django.http import HttpResponse from django.shortcuts import render, redirect from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe from swh.web.common import service from swh.web.common.utils import ( reverse, format_utc_iso_date, gen_path_info ) from swh.web.common.exc import NotFoundExc, 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, get_snapshot_context, gen_snapshot_directory_link, get_revision_log_url, get_directory_entries, gen_directory_link, request_content, prepare_content_for_display, content_display_max_size, gen_snapshot_link, get_readme_to_display, get_swh_persistent_ids ) def _gen_content_url(revision, query_string, path, snapshot_context): if snapshot_context: url_args = snapshot_context['url_args'] url_args['path'] = path query_params = snapshot_context['query_params'] query_params['revision'] = revision['id'] content_url = reverse('browse-origin-content', kwargs=url_args, query_params=query_params) else: content_path = '%s/%s' % (revision['directory'], path) content_url = reverse('browse-content', kwargs={'query_string': query_string}, query_params={'path': content_path}) return content_url def _gen_diff_link(idx, diff_anchor, link_text): if idx < _max_displayed_file_diffs: return gen_link(diff_anchor, link_text) else: return link_text # TODO: put in conf _max_displayed_file_diffs = 1000 def _gen_revision_changes_list(revision, changes, snapshot_context): """ Returns a HTML string describing the file changes introduced in a revision. As this string will be displayed in the browse revision view, links to adequate file diffs are also generated. Args: revision (str): hexadecimal representation of a revision identifier changes (list): list of file changes in the revision snapshot_context (dict): optional origin context used to reverse the content urls Returns: A string to insert in a revision HTML view. """ changes_msg = [] for i, change in enumerate(changes): hasher = hashlib.sha1() from_query_string = '' to_query_string = '' diff_id = 'diff-' if change['from']: from_query_string = 'sha1_git:' + change['from']['target'] diff_id += change['from']['target'] + '-' + change['from_path'] diff_id += '-' if change['to']: to_query_string = 'sha1_git:' + change['to']['target'] diff_id += change['to']['target'] + change['to_path'] change['path'] = change['to_path'] or change['from_path'] url_args = {'from_query_string': from_query_string, 'to_query_string': to_query_string} query_params = {'path': change['path']} change['diff_url'] = reverse('diff-contents', kwargs=url_args, query_params=query_params) hasher.update(diff_id.encode('utf-8')) diff_id = hasher.hexdigest() change['id'] = diff_id panel_diff_link = '#panel_' + diff_id if change['type'] == 'modify': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], snapshot_context) changes_msg.append('modified: %s' % _gen_diff_link(i, panel_diff_link, change['to_path'])) elif change['type'] == 'insert': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], snapshot_context) changes_msg.append('new file: %s' % _gen_diff_link(i, panel_diff_link, change['to_path'])) elif change['type'] == 'delete': parent = service.lookup_revision(revision['parents'][0]) change['content_url'] = \ _gen_content_url(parent, from_query_string, change['from_path'], snapshot_context) changes_msg.append('deleted: %s' % _gen_diff_link(i, panel_diff_link, change['from_path'])) elif change['type'] == 'rename': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], snapshot_context) link_text = change['from_path'] + ' → ' + change['to_path'] changes_msg.append('renamed: %s' % _gen_diff_link(i, panel_diff_link, link_text)) if not changes: changes_msg.append('No changes') return mark_safe('\n'.join(changes_msg)) @browse_route(r'revision/(?P[0-9a-f]+)/diff/', view_name='diff-revision') def _revision_diff(request, sha1_git): """ Browse internal endpoint to compute revision diff """ try: revision = service.lookup_revision(sha1_git) snapshot_context = None origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) timestamp = request.GET.get('timestamp', None) visit_id = request.GET.get('visit_id', None) if origin_url: snapshot_context = get_snapshot_context(None, origin_type, origin_url, timestamp, visit_id) except Exception as exc: return handle_view_exception(request, exc) changes = service.diff_revision(sha1_git) changes_msg = _gen_revision_changes_list(revision, changes, snapshot_context) diff_data = { 'total_nb_changes': len(changes), 'changes': changes[:_max_displayed_file_diffs], 'changes_msg': changes_msg } diff_data_json = json.dumps(diff_data, separators=(',', ': ')) return HttpResponse(diff_data_json, content_type='application/json') 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/`. """ # 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'], link_text='Browse files', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) - return render(request, 'revision-log.html', + return render(request, 'browse/revision-log.html', {'heading': 'Revision history', 'swh_object_name': 'Revision history', 'swh_object_metadata': None, 'revision_log': revision_log_data, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, 'top_right_link': None, 'top_right_link_text': None, 'snapshot_context': None, 'vault_cooking': None, 'show_actions_menu': True, 'swh_ids': None}) @browse_route(r'revision/(?P[0-9a-f]+)/', r'revision/(?P[0-9a-f]+)/(?P.+)/', view_name='browse-revision') def revision_browse(request, sha1_git, extra_path=None): """ 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)/`. """ try: revision = service.lookup_revision(sha1_git) # some readme files can reference assets reachable from the # browsed directory, handle that special case in order to # correctly displayed them if extra_path: dir_info = \ service.lookup_directory_with_path(revision['directory'], extra_path) if dir_info and dir_info['type'] == 'file': file_raw_url = reverse( 'browse-content-raw', kwargs={'query_string': dir_info['checksums']['sha1']}) return redirect(file_raw_url) origin_info = None snapshot_context = None origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) if not origin_url: origin_url = request.GET.get('origin', None) timestamp = request.GET.get('timestamp', None) visit_id = request.GET.get('visit_id', None) snapshot_id = request.GET.get('snapshot_id', None) path = request.GET.get('path', None) dir_id = None dirs, files = None, None content_data = None if origin_url: try: snapshot_context = get_snapshot_context(None, origin_type, origin_url, timestamp, visit_id) except Exception: raw_rev_url = reverse('browse-revision', kwargs={'sha1_git': sha1_git}) error_message = \ ('The Software Heritage archive has a revision ' 'with the hash you provided but the origin ' 'mentioned in your request appears broken: %s. ' 'Please check the URL and try again.\n\n' 'Nevertheless, you can still browse the revision ' 'without origin information: %s' % (gen_link(origin_url), gen_link(raw_rev_url))) raise NotFoundExc(error_message) origin_info = snapshot_context['origin_info'] snapshot_id = snapshot_context['snapshot_id'] elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) if path: file_info = \ service.lookup_directory_with_path(revision['directory'], path) if file_info['type'] == 'dir': dir_id = file_info['target'] else: query_string = 'sha1_git:' + file_info['target'] content_data = request_content(query_string, raise_if_unavailable=False) else: dir_id = revision['directory'] if dir_id: path = '' if path is None else (path + '/') dirs, files = get_directory_entries(dir_id) except Exception as exc: return handle_view_exception(request, exc) revision_data = {} author_name = revision['author']['name'] or revision['author']['fullname'] revision_data['author'] = \ gen_person_link(revision['author']['id'], author_name, snapshot_context) revision_data['committer'] = \ gen_person_link(revision['committer']['id'], revision['committer']['name'], snapshot_context) revision_data['committer date'] = format_utc_iso_date( revision['committer_date']) revision_data['date'] = format_utc_iso_date(revision['date']) if snapshot_context: revision_data['snapshot id'] = snapshot_id revision_data['directory'] = \ gen_snapshot_directory_link(snapshot_context, sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', # noqa 'role': 'button'}) else: revision_data['directory'] = \ gen_directory_link(revision['directory'], link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) revision_data['id'] = sha1_git revision_data['merge'] = revision['merge'] revision_data['metadata'] = json.dumps(revision['metadata'], sort_keys=True, indent=4, separators=(',', ': ')) if origin_info: revision_data['context-independent revision'] = \ gen_revision_link(sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) revision_data['origin id'] = origin_info['id'] revision_data['origin type'] = origin_info['type'] revision_data['origin url'] = gen_link(origin_info['url'], origin_info['url']) browse_snapshot_link = \ gen_snapshot_link(snapshot_id, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) revision_data['snapshot'] = browse_snapshot_link parents = '' for p in revision['parents']: parent_link = gen_revision_link(p, snapshot_context=snapshot_context) parents += parent_link + '
' revision_data['parents'] = mark_safe(parents) revision_data['synthetic'] = revision['synthetic'] revision_data['type'] = revision['type'] message_lines = revision['message'].split('\n') parents_links = '%s parent%s ' % \ (len(revision['parents']), '' if len(revision['parents']) == 1 else 's') parents_links += ' ' for p in revision['parents']: parent_link = gen_revision_link(p, shorten_id=True, snapshot_context=snapshot_context) parents_links += parent_link if p != revision['parents'][-1]: parents_links += ' + ' path_info = gen_path_info(path) query_params = {'snapshot_id': snapshot_id, 'origin_type': origin_type, 'origin': origin_url, 'timestamp': timestamp, 'visit_id': visit_id} breadcrumbs = [] breadcrumbs.append({'name': revision['directory'][:7], 'url': reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params)}) for pi in path_info: query_params['path'] = pi['path'] breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params)}) vault_cooking = { 'directory_context': False, 'directory_id': None, 'revision_context': True, 'revision_id': sha1_git } swh_objects = [{'type': 'revision', 'id': sha1_git}] content = None content_size = None mimetype = None language = None readme_name = None readme_url = None readme_html = None readmes = {} error_code = 200 error_message = '' error_description = '' if content_data: breadcrumbs[-1]['url'] = None content_size = content_data['length'] mimetype = content_data['mimetype'] if content_data['raw_data']: content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) content = content_display_data['content_data'] language = content_display_data['language'] query_params = {} if path: query_params['filename'] = path_info[-1]['name'] top_right_link = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params=query_params) top_right_link_text = mark_safe( 'Raw File') swh_objects.append({'type': 'content', 'id': file_info['target']}) error_code = content_data['error_code'] error_message = content_data['error_message'] error_description = content_data['error_description'] else: for d in dirs: query_params['path'] = path + d['name'] d['url'] = reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params) for f in files: query_params['path'] = path + f['name'] f['url'] = reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params) f['length'] = filesizeformat(f['length']) if f['name'].lower().startswith('readme'): readmes[f['name']] = f['checksums']['sha1'] readme_name, readme_url, readme_html = get_readme_to_display(readmes) top_right_link = get_revision_log_url(sha1_git, snapshot_context) top_right_link_text = mark_safe( '' 'History') vault_cooking['directory_context'] = True vault_cooking['directory_id'] = dir_id swh_objects.append({'type': 'directory', 'id': dir_id}) diff_revision_url = reverse('diff-revision', kwargs={'sha1_git': sha1_git}, query_params={'origin_type': origin_type, 'origin': origin_url, 'timestamp': timestamp, 'visit_id': visit_id}) if snapshot_id: swh_objects.append({'type': 'snapshot', 'id': snapshot_id}) swh_ids = get_swh_persistent_ids(swh_objects, snapshot_context) heading = 'Revision - %s - %s' %\ (sha1_git[:7], textwrap.shorten(message_lines[0], width=70)) if snapshot_context: context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_info: context_found = 'origin: %s' % origin_info['url'] heading += ' - %s' % context_found - return render(request, 'revision.html', + return render(request, 'browse/revision.html', {'heading': heading, 'swh_object_name': 'Revision', 'swh_object_metadata': revision_data, 'message_header': message_lines[0], 'message_body': '\n'.join(message_lines[1:]), 'parents_links': mark_safe(parents_links), 'snapshot_context': snapshot_context, 'dirs': dirs, 'files': files, 'content': content, 'content_size': content_size, 'max_content_size': content_display_max_size, 'mimetype': mimetype, 'language': language, 'readme_name': readme_name, 'readme_url': readme_url, 'readme_html': readme_html, 'breadcrumbs': breadcrumbs, 'top_right_link': top_right_link, 'top_right_link_text': top_right_link_text, 'vault_cooking': vault_cooking, 'diff_revision_url': diff_revision_url, 'show_actions_menu': True, 'swh_ids': swh_ids, 'error_code': error_code, 'error_message': error_message, 'error_description': error_description}, status=error_code) diff --git a/swh/web/browse/views/utils/snapshot_context.py b/swh/web/browse/views/utils/snapshot_context.py index c3a59e71..f34c0866 100644 --- a/swh/web/browse/views/utils/snapshot_context.py +++ b/swh/web/browse/views/utils/snapshot_context.py @@ -1,838 +1,838 @@ # Copyright (C) 2018 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 # Utility module implementing Django views for browsing the SWH archive # in a snapshot context. # Its purpose is to factorize code for the views reachable from the # /origin/.* and /snapshot/.* endpoints. from django.shortcuts import render, redirect from django.utils.safestring import mark_safe from django.template.defaultfilters import filesizeformat from swh.web.browse.utils import ( get_snapshot_context, get_directory_entries, gen_directory_link, gen_revision_link, request_content, gen_content_link, prepare_content_for_display, content_display_max_size, prepare_revision_log_for_display, gen_snapshot_directory_link, gen_revision_log_link, gen_link, get_readme_to_display, get_swh_persistent_ids ) from swh.web.common import service from swh.web.common.exc import ( handle_view_exception, NotFoundExc ) from swh.web.common.utils import ( reverse, gen_path_info, format_utc_iso_date ) def _get_branch(branches, branch_name): """ Utility function to get a specific branch from a 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)] 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')] if len(filtered_branches) > 0: return filtered_branches[0] elif len(branches) > 0: return branches[0] return None def _get_release(releases, release_name): """ Utility function to get a specific release from a releases list. Returns None if the release can not be found in the list. """ filtered_releases = \ [r for r in releases if r['name'] == release_name] if len(filtered_releases) > 0: return filtered_releases[0] else: return None def _branch_not_found(branch_type, branch, branches, snapshot_id=None, origin_info=None, timestamp=None, visit_id=None): """ Utility function to raise an exception when a specified branch/release can not be found. """ if branch_type == 'branch': branch_type = 'Branch' branch_type_plural = 'branches' else: branch_type = 'Release' branch_type_plural = 'releases' if snapshot_id and len(branches) == 0: msg = 'Snapshot with id %s has an empty list' \ ' of %s!' % (snapshot_id, branch_type_plural) elif snapshot_id: msg = '%s %s for snapshot with id %s' \ ' not found!' % (branch_type, branch, snapshot_id) elif visit_id and len(branches) == 0: msg = 'Origin with type %s and url %s' \ ' for visit with id %s has an empty list' \ ' of %s!' % (origin_info['type'], origin_info['url'], visit_id, branch_type_plural) elif visit_id: msg = '%s %s associated to visit with' \ ' id %s for origin with type %s and url %s' \ ' not found!' % (branch_type, branch, visit_id, origin_info['type'], origin_info['url']) elif len(branches) == 0: msg = 'Origin with type %s and url %s' \ ' for visit with timestamp %s has an empty list' \ ' of %s!' % (origin_info['type'], origin_info['url'], timestamp, branch_type_plural) else: msg = '%s %s associated to visit with' \ ' timestamp %s for origin with type %s' \ ' and url %s not found!' % (branch_type, branch, timestamp, origin_info['type'], origin_info['url']) raise NotFoundExc(msg) def _process_snapshot_request(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None, path=None, browse_context='directory'): """ Utility function to perform common input request processing for snapshot context views. """ visit_id = request.GET.get('visit_id', None) snapshot_context = get_snapshot_context(snapshot_id, origin_type, origin_url, timestamp, visit_id) swh_type = snapshot_context['swh_type'] origin_info = snapshot_context['origin_info'] branches = snapshot_context['branches'] releases = snapshot_context['releases'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] browse_view_name = 'browse-' + swh_type + '-' + browse_context for b in branches: branch_url_args = dict(url_args) branch_query_params = dict(query_params) branch_query_params['branch'] = b['name'] if path: b['path'] = path branch_url_args['path'] = path b['url'] = reverse(browse_view_name, kwargs=branch_url_args, query_params=branch_query_params) for r in releases: release_url_args = dict(url_args) release_query_params = dict(query_params) release_query_params['release'] = r['name'] if path: r['path'] = path release_url_args['path'] = path r['url'] = reverse(browse_view_name, kwargs=release_url_args, query_params=release_query_params) root_sha1_git = None revision_id = request.GET.get('revision', None) release_name = request.GET.get('release', None) release_id = None branch_name = 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 query_params['revision'] = revision_id elif release_name: release = _get_release(releases, release_name) if release: root_sha1_git = release['directory'] revision_id = release['target'] release_id = release['id'] query_params['release'] = release_name else: _branch_not_found("release", release_name, releases, snapshot_id, origin_info, timestamp, visit_id) else: branch_name = request.GET.get('branch', None) if branch_name: query_params['branch'] = branch_name branch = _get_branch(branches, branch_name or 'HEAD') if branch: branch_name = branch['name'] root_sha1_git = branch['directory'] revision_id = branch['revision'] else: _branch_not_found("branch", branch_name, branches, snapshot_id, origin_info, timestamp, visit_id) snapshot_context['query_params'] = query_params snapshot_context['root_sha1_git'] = root_sha1_git snapshot_context['revision_id'] = revision_id snapshot_context['branch'] = branch_name snapshot_context['release'] = release_name snapshot_context['release_id'] = release_id return snapshot_context def browse_snapshot_directory(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None, path=None): """ Django view implementation for browsing a directory in a snapshot context. """ try: snapshot_context = _process_snapshot_request(request, snapshot_id, origin_type, origin_url, timestamp, path, browse_context='directory') # noqa root_sha1_git = snapshot_context['root_sha1_git'] sha1_git = root_sha1_git if path: dir_info = service.lookup_directory_with_path(root_sha1_git, path) # some readme files can reference assets reachable from the # browsed directory, handle that special case in order to # correctly displayed them if dir_info and dir_info['type'] == 'file': file_raw_url = reverse( 'browse-content-raw', kwargs={'query_string': dir_info['checksums']['sha1']}) return redirect(file_raw_url) sha1_git = dir_info['target'] dirs, files = get_directory_entries(sha1_git) except Exception as exc: return handle_view_exception(request, exc) swh_type = snapshot_context['swh_type'] origin_info = snapshot_context['origin_info'] visit_info = snapshot_context['visit_info'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] revision_id = snapshot_context['revision_id'] snapshot_id = snapshot_context['snapshot_id'] path_info = gen_path_info(path) browse_view_name = 'browse-' + swh_type + '-directory' breadcrumbs = [] breadcrumbs.append({'name': root_sha1_git[:7], 'url': reverse(browse_view_name, 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_view_name, 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_view_name, kwargs=bc_url_args, query_params=query_params) sum_file_sizes = 0 readmes = {} browse_view_name = 'browse-' + swh_type + '-content' for f in files: bc_url_args = dict(url_args) bc_url_args['path'] = path + f['name'] f['url'] = reverse(browse_view_name, kwargs=bc_url_args, query_params=query_params) sum_file_sizes += f['length'] f['length'] = filesizeformat(f['length']) if f['name'].lower().startswith('readme'): readmes[f['name']] = f['checksums']['sha1'] readme_name, readme_url, readme_html = get_readme_to_display(readmes) browse_view_name = 'browse-' + swh_type + '-log' history_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) sum_file_sizes = filesizeformat(sum_file_sizes) browse_dir_link = \ gen_directory_link(sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) browse_rev_link = \ gen_revision_link(revision_id, snapshot_context=snapshot_context, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) dir_metadata = {'id': sha1_git, 'context-independent directory': browse_dir_link, 'number of regular files': len(files), 'number of subdirectories': len(dirs), 'sum of regular file sizes': sum_file_sizes, 'path': '/' + path, 'revision id': revision_id, 'revision': browse_rev_link, 'snapshot id': snapshot_id} if origin_info: dir_metadata['origin id'] = origin_info['id'] dir_metadata['origin type'] = origin_info['type'] dir_metadata['origin url'] = origin_info['url'] dir_metadata['origin visit date'] = format_utc_iso_date(visit_info['date']) # noqa dir_metadata['origin visit id'] = visit_info['visit'] snapshot_context_url = reverse('browse-snapshot-directory', kwargs={'snapshot_id': snapshot_id}, query_params=request.GET) browse_snapshot_link = \ gen_link(snapshot_context_url, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) dir_metadata['snapshot context'] = browse_snapshot_link vault_cooking = { 'directory_context': True, 'directory_id': sha1_git, 'revision_context': True, 'revision_id': revision_id } swh_objects = [{'type': 'directory', 'id': sha1_git}, {'type': 'revision', 'id': revision_id}, {'type': 'snapshot', 'id': snapshot_id}] release_id = snapshot_context['release_id'] if release_id: swh_objects.append({'type': 'release', 'id': release_id}) swh_ids = get_swh_persistent_ids(swh_objects, snapshot_context) dir_path = '/'.join([bc['name'] for bc in breadcrumbs]) + '/' context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_info: context_found = 'origin: %s' % origin_info['url'] heading = 'Directory - %s - %s - %s' %\ (dir_path, snapshot_context['branch'], context_found) - return render(request, 'directory.html', + return render(request, 'browse/directory.html', {'heading': heading, 'swh_object_name': 'Directory', 'swh_object_metadata': dir_metadata, 'dirs': dirs, 'files': files, 'breadcrumbs': breadcrumbs, 'top_right_link': history_url, 'top_right_link_text': mark_safe( '' 'History' ), 'readme_name': readme_name, 'readme_url': readme_url, 'readme_html': readme_html, 'snapshot_context': snapshot_context, 'vault_cooking': vault_cooking, 'show_actions_menu': True, 'swh_ids': swh_ids}) def browse_snapshot_content(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None, path=None): """ Django view implementation for browsing a content in a snapshot context. """ try: snapshot_context = _process_snapshot_request(request, snapshot_id, origin_type, origin_url, timestamp, path, browse_context='content') root_sha1_git = snapshot_context['root_sha1_git'] 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, raise_if_unavailable=False) except Exception as exc: return handle_view_exception(request, exc) swh_type = snapshot_context['swh_type'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] revision_id = snapshot_context['revision_id'] origin_info = snapshot_context['origin_info'] visit_info = snapshot_context['visit_info'] snapshot_id = snapshot_context['snapshot_id'] content = None language = None mimetype = None if content_data['raw_data'] is not None: content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) content = content_display_data['content_data'] language = content_display_data['language'] mimetype = content_display_data['mimetype'] filename = None path_info = None browse_view_name = 'browse-' + swh_type + '-directory' breadcrumbs = [] split_path = path.split('/') filename = split_path[-1] path_info = gen_path_info(path[:-len(filename)]) breadcrumbs.append({'name': root_sha1_git[:7], 'url': reverse(browse_view_name, 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_view_name, kwargs=bc_url_args, query_params=query_params)}) breadcrumbs.append({'name': filename, 'url': None}) browse_content_link = \ gen_content_link(sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) content_raw_url = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params={'filename': filename}) browse_rev_link = \ gen_revision_link(revision_id, snapshot_context=snapshot_context, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) content_metadata = { 'context-independent content': browse_content_link, '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'], 'path': '/' + path[:-len(filename)], 'filename': filename, 'revision id': revision_id, 'revision': browse_rev_link, 'snapshot id': snapshot_id } if origin_info: content_metadata['origin id'] = origin_info['id'] content_metadata['origin type'] = origin_info['type'] content_metadata['origin url'] = origin_info['url'] content_metadata['origin visit date'] = format_utc_iso_date(visit_info['date']) # noqa content_metadata['origin visit id'] = visit_info['visit'] browse_snapshot_url = reverse('browse-snapshot-content', kwargs={'snapshot_id': snapshot_id, 'path': path}, query_params=request.GET) browse_snapshot_link = \ gen_link(browse_snapshot_url, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) content_metadata['snapshot context'] = browse_snapshot_link cnt_sha1_git = content_data['checksums']['sha1_git'] swh_objects = [{'type': 'content', 'id': cnt_sha1_git}, {'type': 'revision', 'id': revision_id}, {'type': 'snapshot', 'id': snapshot_id}] release_id = snapshot_context['release_id'] if release_id: swh_objects.append({'type': 'release', 'id': release_id}) swh_ids = get_swh_persistent_ids(swh_objects, snapshot_context) content_path = '/'.join([bc['name'] for bc in breadcrumbs]) context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_info: context_found = 'origin: %s' % origin_info['url'] heading = 'Content - %s - %s - %s' %\ (content_path, snapshot_context['branch'], context_found) - return render(request, 'content.html', + return render(request, 'browse/content.html', {'heading': heading, 'swh_object_name': 'Content', 'swh_object_metadata': content_metadata, 'content': content, 'content_size': content_data['length'], 'max_content_size': content_display_max_size, 'mimetype': mimetype, 'language': language, 'breadcrumbs': breadcrumbs, 'top_right_link': content_raw_url, 'top_right_link_text': mark_safe( 'Raw File'), 'snapshot_context': snapshot_context, 'vault_cooking': None, 'show_actions_menu': True, 'swh_ids': swh_ids, 'error_code': content_data['error_code'], 'error_message': content_data['error_message'], 'error_description': content_data['error_description']}, status=content_data['error_code']) PER_PAGE = 20 def browse_snapshot_log(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None): """ Django view implementation for browsing a revision history in a snapshot context. """ try: snapshot_context = _process_snapshot_request(request, snapshot_id, origin_type, origin_url, timestamp, browse_context='log') # noqa revision_id = snapshot_context['revision_id'] current_rev = revision_id per_page = int(request.GET.get('per_page', PER_PAGE)) revs_breadcrumb = request.GET.get('revs_breadcrumb', None) if revs_breadcrumb: current_rev = revs_breadcrumb.split('/')[-1] revision_log = service.lookup_revision_log(current_rev, limit=per_page+1) revision_log = list(revision_log) except Exception as exc: return handle_view_exception(request, exc) swh_type = snapshot_context['swh_type'] origin_info = snapshot_context['origin_info'] visit_info = snapshot_context['visit_info'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] snapshot_id = snapshot_context['snapshot_id'] query_params['per_page'] = per_page revision_log_display_data = prepare_revision_log_for_display( revision_log, per_page, revs_breadcrumb, snapshot_context) browse_view_name = 'browse-' + swh_type + '-log' prev_rev = revision_log_display_data['prev_rev'] prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb'] prev_log_url = None query_params['revs_breadcrumb'] = prev_revs_breadcrumb if prev_rev: prev_log_url = \ reverse(browse_view_name, kwargs=url_args, query_params=query_params) next_rev = revision_log_display_data['next_rev'] next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb'] next_log_url = None query_params['revs_breadcrumb'] = next_revs_breadcrumb if next_rev: next_log_url = \ reverse(browse_view_name, kwargs=url_args, query_params=query_params) revision_log_data = revision_log_display_data['revision_log_data'] for i, log in enumerate(revision_log_data): params = { 'revision': revision_log[i]['id'], } if 'visit_id' in query_params: params['visit_id'] = query_params['visit_id'] log['directory'] = gen_snapshot_directory_link( snapshot_context, revision_log[i]['id'], link_text='Browse files', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) browse_log_link = \ gen_revision_log_link(revision_id, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) revision_metadata = { 'context-independent revision history': browse_log_link, 'snapshot id': snapshot_id } if origin_info: revision_metadata['origin id'] = origin_info['id'] revision_metadata['origin type'] = origin_info['type'] revision_metadata['origin url'] = origin_info['url'] revision_metadata['origin visit date'] = format_utc_iso_date(visit_info['date']) # noqa revision_metadata['origin visit id'] = visit_info['visit'] browse_snapshot_url = reverse('browse-snapshot-log', kwargs={'snapshot_id': snapshot_id}, query_params=request.GET) browse_snapshot_link = \ gen_link(browse_snapshot_url, link_text='Browse', link_attrs={'class': 'btn btn-default btn-sm', 'role': 'button'}) revision_metadata['snapshot context'] = browse_snapshot_link swh_objects = [{'type': 'revision', 'id': revision_id}, {'type': 'snapshot', 'id': snapshot_id}] release_id = snapshot_context['release_id'] if release_id: swh_objects.append({'type': 'release', 'id': release_id}) swh_ids = get_swh_persistent_ids(swh_objects, snapshot_context) context_found = 'snapshot: %s' % snapshot_context['snapshot_id'] if origin_info: context_found = 'origin: %s' % origin_info['url'] heading = 'Revision history - %s - %s' %\ (snapshot_context['branch'], context_found) - return render(request, 'revision-log.html', + return render(request, 'browse/revision-log.html', {'heading': heading, 'swh_object_name': 'Revision history', 'swh_object_metadata': revision_metadata, 'revision_log': revision_log_data, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, 'top_right_link': None, 'top_right_link_text': None, 'snapshot_context': snapshot_context, 'vault_cooking': None, 'show_actions_menu': True, 'swh_ids': swh_ids}) def browse_snapshot_branches(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None): """ Django view implementation for browsing a list of branches in a snapshot context. """ try: snapshot_context = _process_snapshot_request(request, snapshot_id, origin_type, origin_url, timestamp) except Exception as exc: return handle_view_exception(request, exc) branches_offset = int(request.GET.get('branches_offset', 0)) swh_type = snapshot_context['swh_type'] origin_info = snapshot_context['origin_info'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] browse_view_name = 'browse-' + swh_type + '-directory' branches = snapshot_context['branches'] displayed_branches = \ branches[branches_offset:branches_offset+PER_PAGE] for branch in displayed_branches: if snapshot_id: revision_url = reverse('browse-revision', kwargs={'sha1_git': branch['revision']}, query_params={'snapshot_id': snapshot_id}) else: revision_url = reverse('browse-revision', kwargs={'sha1_git': branch['revision']}, query_params={'origin_type': origin_info['type'], # noqa 'origin': origin_info['url']}) # noqa query_params['branch'] = branch['name'] directory_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) del query_params['branch'] branch['revision_url'] = revision_url branch['directory_url'] = directory_url browse_view_name = 'browse-' + swh_type + '-branches' prev_branches_url = None next_branches_url = None next_offset = branches_offset + PER_PAGE prev_offset = branches_offset - PER_PAGE if next_offset < len(branches): query_params['branches_offset'] = next_offset next_branches_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) query_params['branches_offset'] = None if prev_offset >= 0: if prev_offset != 0: query_params['branches_offset'] = prev_offset prev_branches_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) heading = 'Branches - ' if origin_info: heading += 'origin: %s' % origin_info['url'] else: heading += 'snapshot: %s' % snapshot_id - return render(request, 'branches.html', + return render(request, 'browse/branches.html', {'heading': heading, 'swh_object_name': 'Branches', 'swh_object_metadata': {}, 'top_right_link': None, 'top_right_link_text': None, 'displayed_branches': displayed_branches, 'prev_branches_url': prev_branches_url, 'next_branches_url': next_branches_url, 'snapshot_context': snapshot_context}) def browse_snapshot_releases(request, snapshot_id=None, origin_type=None, origin_url=None, timestamp=None): """ Django view implementation for browsing a list of releases in a snapshot context. """ try: snapshot_context = _process_snapshot_request(request, snapshot_id, origin_type, origin_url, timestamp) except Exception as exc: return handle_view_exception(request, exc) releases_offset = int(request.GET.get('releases_offset', 0)) swh_type = snapshot_context['swh_type'] origin_info = snapshot_context['origin_info'] url_args = snapshot_context['url_args'] query_params = snapshot_context['query_params'] releases = snapshot_context['releases'] displayed_releases = \ releases[releases_offset:releases_offset+PER_PAGE] for release in displayed_releases: if snapshot_id: release_url = reverse('browse-release', kwargs={'sha1_git': release['id']}, query_params={'snapshot_id': snapshot_id}) else: release_url = reverse('browse-release', kwargs={'sha1_git': release['id']}, query_params={'origin_type': origin_info['type'], # noqa 'origin': origin_info['url']}) # noqa query_params['release'] = release['name'] del query_params['release'] release['release_url'] = release_url browse_view_name = 'browse-' + swh_type + '-releases' prev_releases_url = None next_releases_url = None next_offset = releases_offset + PER_PAGE prev_offset = releases_offset - PER_PAGE if next_offset < len(releases): query_params['releases_offset'] = next_offset next_releases_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) query_params['releases_offset'] = None if prev_offset >= 0: if prev_offset != 0: query_params['releases_offset'] = prev_offset prev_releases_url = reverse(browse_view_name, kwargs=url_args, query_params=query_params) heading = 'Releases - ' if origin_info: heading += 'origin: %s' % origin_info['url'] else: heading += 'snapshot: %s' % snapshot_id - return render(request, 'releases.html', + return render(request, 'browse/releases.html', {'heading': heading, 'top_panel_visible': False, 'top_panel_collapsible': False, 'swh_object_name': 'Releases', 'swh_object_metadata': {}, 'top_right_link': None, 'top_right_link_text': None, 'displayed_releases': displayed_releases, 'prev_releases_url': prev_releases_url, 'next_releases_url': next_releases_url, 'snapshot_context': snapshot_context, 'vault_cooking': None, 'show_actions_menu': False}) diff --git a/swh/web/templates/api-endpoints.html b/swh/web/templates/api-endpoints.html deleted file mode 100644 index 77f6f916..00000000 --- a/swh/web/templates/api-endpoints.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "layout.html" %} - -{% comment %} -Copyright (C) 2015-2018 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 -{% endcomment %} - -{% load swh_templatetags %} - -{% block title %} Endpoints – Software Heritage API {% endblock %} - -{% block navbar-content %} - -{% endblock %} - -{% block content %} -
-

- Below you can find a list of the available endpoints for version 1 of the - Software Heritage API. For a more general introduction please refer to - the API overview. -

- -

- Endpoints marked "available" are considered stable for the current version - of the API; endpoints marked "upcoming" are work in progress that will be - stabilized in the near future. -

- - -
- -
- - - - - - - - - {% for route, doc in doc_routes %} - - {% if doc.tags|length > 0 %} - - - {% else %} - - - {% endif %} - - - {% endfor %} - -
EndpointStatusDescription
{% url doc.route_view_name %}{{ doc.tags|join:', ' }}{% url doc.route_view_name %}available{{ doc.doc_intro | safe_docstring_display | safe }}
-
- - -{% endblock %} diff --git a/swh/web/templates/api.html b/swh/web/templates/api/api.html similarity index 76% rename from swh/web/templates/api.html rename to swh/web/templates/api/api.html index cde81b5b..8503bf5f 100644 --- a/swh/web/templates/api.html +++ b/swh/web/templates/api/api.html @@ -1,23 +1,23 @@ {% extends "layout.html" %} {% comment %} Copyright (C) 2015-2018 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 {% endcomment %} {% block title %} Overview – Software Heritage API {% endblock %} {% block navbar-content %}

Web API

{% endblock %} {% block content %} -
- {% include 'includes/apidoc-header.html' %} -
- +
+ {% include 'includes/apidoc-header.html' %} +
+ {% endblock %} diff --git a/swh/web/templates/apidoc.html b/swh/web/templates/api/apidoc.html similarity index 99% rename from swh/web/templates/apidoc.html rename to swh/web/templates/api/apidoc.html index 59831950..fa13520c 100644 --- a/swh/web/templates/apidoc.html +++ b/swh/web/templates/api/apidoc.html @@ -1,182 +1,181 @@ {% extends "layout.html" %} {% comment %} Copyright (C) 2015-2018 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 {% endcomment %} {% load swh_templatetags %} -{% load render_bundle from webpack_loader %} {% block title %}{{ heading }} – Software Heritage API {% endblock %} {% block navbar-content %} {% endblock %} {% block content %} {% if description %}

Description

{{ description | safe_docstring_display | safe }}
{% endif %} {% if response_data %}

Request

{{ request.method }} {{ request.path }}

Response

{% if status_code != 200 %}
Status Code
{{ status_code }}
{% endif %} {% if headers_data %}
Headers
{% for header_name, header_value in headers_data.items %}
{{ header_name }} {{ header_value | urlize_header_links | safe }}
{% endfor %} {% endif %}
Body
{{ response_data | urlize_links_and_mails | safe }}
{% endif %}
{% if urls and urls|length > 0 %}
{% for url in urls %} {% endfor %}
URL Allowed Methods
{{ url.rule | safe_docstring_display | safe }} {{ url.methods | dictsort:0 | join:', ' }}

{% endif %} {% if args and args|length > 0 %}

Arguments

{% for arg in args %}
{{ arg.name }} ({{ arg.type }})
{{ arg.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if params and params|length > 0 %}

Query parameters

{% for param in params %}
{{ param.name }} ({{ param.type }})
{{ param.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if reqheaders and reqheaders|length > 0 %}

Request headers

{% for header in reqheaders %}
{{ header.name }}
{{ header.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if resheaders and resheaders|length > 0 %}

Response headers

{% for header in resheaders %}
{{ header.name }}
{{ header.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if return_type %}

Returns

{{ return_type }}

{% if return_type == 'array' %} an array of objects containing the following keys: {% elif return_type == 'octet stream' %} the raw data as an octet stream {% else %} an object containing the following keys: {% endif %} {{ returns_list | safe_docstring_display | safe }}


{% endif %} {% if status_codes and status_codes|length > 0 %}

HTTP status codes

{% for status in status_codes %}
{{ status.code }}
{{ status.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if examples and examples|length > 0 %}

Examples

{% for example in examples %}
{{ example }}
{% endfor %}
{% endif %} {% endblock %} diff --git a/swh/web/templates/api/endpoints.html b/swh/web/templates/api/endpoints.html new file mode 100644 index 00000000..6e81e310 --- /dev/null +++ b/swh/web/templates/api/endpoints.html @@ -0,0 +1,83 @@ +{% extends "layout.html" %} + +{% comment %} +Copyright (C) 2015-2018 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 +{% endcomment %} + +{% load swh_templatetags %} + +{% block title %} Endpoints – Software Heritage API {% endblock %} + +{% block navbar-content %} + +{% endblock %} + +{% block content %} +
+

+ Below you can find a list of the available endpoints for version 1 of the + Software Heritage API. For a more general introduction please refer to + the API overview. +

+ +

+ Endpoints marked "available" are considered stable for the current version + of the API; endpoints marked "upcoming" are work in progress that will be + stabilized in the near future. +

+ + +
+ +
+ + + + + + + + + {% for route, doc in doc_routes %} + + {% if doc.tags|length > 0 %} + + + {% else %} + + + {% endif %} + + + {% endfor %} + +
EndpointStatusDescription
{% url doc.route_view_name %}{{ doc.tags|join:', ' }}{% url doc.route_view_name %}available{{ doc.doc_intro | safe_docstring_display | safe }}
+
+ + +{% endblock %} diff --git a/swh/web/templates/branches.html b/swh/web/templates/browse/branches.html similarity index 98% rename from swh/web/templates/branches.html rename to swh/web/templates/browse/branches.html index b5a945d7..3b590228 100644 --- a/swh/web/templates/branches.html +++ b/swh/web/templates/browse/branches.html @@ -1,58 +1,58 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% block swh-browse-content %}

{{ swh_object_name }}

{% for branch in displayed_branches %} {% endfor %}
Name Revision Message Date
{{ branch.name }} {{ branch.revision|slice:":7" }} {{ branch.message }} {{ branch.date }}
{% endblock %} {% block swh-browse-after-content %} {% if prev_branches_url or next_branches_url %}
    {% if prev_branches_url %}
  • Previous
  • {% else %}
  • Previous
  • {% endif %} {% if next_branches_url %}
  • Next
  • {% else %}
  • Next
  • {% endif %}
{% endif %} {% endblock %} diff --git a/swh/web/templates/browse.html b/swh/web/templates/browse/browse.html similarity index 96% rename from swh/web/templates/browse.html rename to swh/web/templates/browse/browse.html index f1a17c93..7c7b8cf4 100644 --- a/swh/web/templates/browse.html +++ b/swh/web/templates/browse/browse.html @@ -1,36 +1,36 @@ -{% extends "browse-layout.html" %} +{% extends "./layout.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load swh_templatetags %} {% block title %}{{ heading }} – Software Heritage archive{% endblock %} {% block navbar-content %}

Browse the archive

{% endblock %} {% block browse-content %} {% block swh-browse-before-content %} {% if snapshot_context %} {% include "includes/snapshot-context.html" %} {% endif %} {% endblock %}
{% block swh-browse-content %}{% endblock %}
{% block swh-browse-after-content %}{% endblock %} {% endblock %} diff --git a/swh/web/templates/content.html b/swh/web/templates/browse/content.html similarity index 93% rename from swh/web/templates/content.html rename to swh/web/templates/browse/content.html index 7fd56266..9e06fde7 100644 --- a/swh/web/templates/content.html +++ b/swh/web/templates/browse/content.html @@ -1,13 +1,13 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% block swh-browse-content %} {% include "includes/top-navigation.html" %} {% include "includes/content-display.html" %} {% endblock %} diff --git a/swh/web/templates/directory.html b/swh/web/templates/browse/directory.html similarity index 94% rename from swh/web/templates/directory.html rename to swh/web/templates/browse/directory.html index 04623c5a..7b35c76e 100644 --- a/swh/web/templates/directory.html +++ b/swh/web/templates/browse/directory.html @@ -1,17 +1,17 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% block swh-browse-content %} {% include "includes/top-navigation.html" %} {% include "includes/directory-display.html" %} {% endblock %} {% block swh-browse-after-content %} {% include "includes/readme-display.html" %} {% endblock %} diff --git a/swh/web/templates/browse-help.html b/swh/web/templates/browse/help.html similarity index 99% rename from swh/web/templates/browse-help.html rename to swh/web/templates/browse/help.html index a13f768e..7d42f794 100644 --- a/swh/web/templates/browse-help.html +++ b/swh/web/templates/browse/help.html @@ -1,189 +1,189 @@ -{% extends "browse-layout.html" %} +{% extends "./layout.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% block navbar-content %}

How to browse the archive ?

{% endblock %} {% block browse-content %}

Overview

This web application aims to provide HTML views to easily navigate in the Software Heritage archive. This is an ongoing development and new features and improvements will be progressively added over time.

URI scheme

The current URI scheme of that web application is described below and depends on the type of Software Heritage object to browse. Its exhaustive documentation can be consulted from the official Software Heritage development documentation

Context-independent browsing

Context-independent URLs provide information about objects (e.g., revisions, directories, contents, persons, …), independently of the contexts where they have been found (e.g., specific software origins, branches, commits, …).

Below are some examples of endpoints used to just render the corresponding information for user consumption:

Where hyperlinks are created when browsing these kind of endpoints, they always point to other context-independent browsing URLs.

Context-dependent browsing

Context-dependent URLs provide information about objects, limited to specific contexts where the objects have been found.

Currently, browsing the Software Heritage objects in the context of an origin is available. Below are some examples of such endpoints:

Search software origins to browse

In order to facilitate the browsing of the archive and generate relevant entry points to it, a search interface is available. Currently, it enables to search software origins from the URLs they were retrieved from. More search criteria will be added in the future. {% endblock %} diff --git a/swh/web/templates/browse-layout.html b/swh/web/templates/browse/layout.html similarity index 100% rename from swh/web/templates/browse-layout.html rename to swh/web/templates/browse/layout.html diff --git a/swh/web/templates/origin-visits.html b/swh/web/templates/browse/origin-visits.html similarity index 98% rename from swh/web/templates/origin-visits.html rename to swh/web/templates/browse/origin-visits.html index 78c5b608..e24571f4 100644 --- a/swh/web/templates/origin-visits.html +++ b/swh/web/templates/browse/origin-visits.html @@ -1,84 +1,84 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load static %} {% load swh_templatetags %} {% load render_bundle from webpack_loader %} {% block header %} {{ block.super }} {% render_bundle 'origin' %} {% endblock %} {% block swh-browse-before-content %}

Origin: {{ origin_info.url }} {% if origin_info.url|slice:"0:4" == "http" %} {% endif %}

{% endblock %} {% block swh-browse-content %}

{{ swh_object_name }}

Overview

  • Total number of visits: {{ origin_visits|length }}
  • Last full visit:
  • First full visit:
  • Last visit:

History

Timeline
Calendar
List
{% endblock %} diff --git a/swh/web/templates/person.html b/swh/web/templates/browse/person.html similarity index 95% rename from swh/web/templates/person.html rename to swh/web/templates/browse/person.html index 44ecb112..3f26c017 100644 --- a/swh/web/templates/person.html +++ b/swh/web/templates/browse/person.html @@ -1,31 +1,31 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load swh_templatetags %} {% block swh-browse-content %}

{{ swh_object_name }}

{% for key, val in swh_object_metadata.items|dictsort:"0.lower" %} {% endfor %}
{% endblock %} \ No newline at end of file diff --git a/swh/web/templates/release.html b/swh/web/templates/browse/release.html similarity index 96% rename from swh/web/templates/release.html rename to swh/web/templates/browse/release.html index eefef0b7..cac663e8 100644 --- a/swh/web/templates/release.html +++ b/swh/web/templates/browse/release.html @@ -1,24 +1,24 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load swh_templatetags %} {% block swh-browse-content %} {% include "includes/top-navigation.html" %}
Release {{ swh_object_metadata.name }} created by {{ swh_object_metadata.author }} on {{ swh_object_metadata.date }}
 
{{ release_note_header }}
{{ release_note_body }}
{{ release_target_link }}
{% endblock %} diff --git a/swh/web/templates/releases.html b/swh/web/templates/browse/releases.html similarity index 98% rename from swh/web/templates/releases.html rename to swh/web/templates/browse/releases.html index 47875d63..45e63b9c 100644 --- a/swh/web/templates/releases.html +++ b/swh/web/templates/browse/releases.html @@ -1,60 +1,60 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% block swh-browse-content %}

{{ swh_object_name }}

{% for release in displayed_releases %} {% endfor %}
Name Target Message Date
{{ release.name }} {{ release.target_type }} {{ release.message }} {{ release.date }}
{% endblock %} {% block swh-browse-after-content %} {% if prev_releases_url or next_releases_url %}
    {% if prev_releases_url %}
  • Previous
  • {% else %}
  • Previous
  • {% endif %} {% if next_releases_url %}
  • Next
  • {% else %}
  • Next
  • {% endif %}
{% endif %} {% endblock %} diff --git a/swh/web/templates/revision-log.html b/swh/web/templates/browse/revision-log.html similarity index 98% rename from swh/web/templates/revision-log.html rename to swh/web/templates/browse/revision-log.html index e7ff6c0b..765e41e4 100644 --- a/swh/web/templates/revision-log.html +++ b/swh/web/templates/browse/revision-log.html @@ -1,68 +1,68 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load render_bundle from webpack_loader %} {% block header %} {{ block.super }} {% render_bundle 'revision' %} {% endblock %} {% block swh-browse-content %} {% if snapshot_context %} {% include "includes/top-navigation.html" %} {% else %}

{{ swh_object_name }}

{% endif %}
{% for log in revision_log %} {% endfor %}
Revision Author Message Date
{{ log.revision }} {{ log.message }} {{ log.directory }}
{% endblock %} {% block swh-browse-after-content %}
    {% 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/browse/revision.html similarity index 99% rename from swh/web/templates/revision.html rename to swh/web/templates/browse/revision.html index 38305cbb..5d41d8e7 100644 --- a/swh/web/templates/revision.html +++ b/swh/web/templates/browse/revision.html @@ -1,103 +1,103 @@ -{% extends "browse.html" %} +{% extends "./browse.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load static %} {% load swh_templatetags %} {% load render_bundle from webpack_loader %} {% block header %} {{ block.super }} {% render_bundle 'revision' %} {% endblock %} {% block swh-browse-content %}
Revision {{ swh_object_metadata.id }} authored by {{ swh_object_metadata.author }} on {{ swh_object_metadata.date }}
{% if message_body %}
{{ message_body }}
{% endif %}
{{ parents_links }}
{% include "includes/top-navigation.html" %} {% if content_size %} {% include "includes/content-display.html" %} {% else %} {% include "includes/directory-display.html" %} {% endif %}
{% endblock %} {% block swh-browse-after-content %} {% include "includes/readme-display.html" %} {% endblock %} diff --git a/swh/web/templates/browse-search.html b/swh/web/templates/browse/search.html similarity index 97% rename from swh/web/templates/browse-search.html rename to swh/web/templates/browse/search.html index fc6b52ce..8ba17e04 100644 --- a/swh/web/templates/browse-search.html +++ b/swh/web/templates/browse/search.html @@ -1,61 +1,61 @@ -{% extends "browse-layout.html" %} +{% extends "./layout.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load static %} {% block navbar-content %}

Search software origins to browse

{% endblock %} {% block browse-content %}

Searching origins ...

{% endblock %} \ No newline at end of file diff --git a/swh/web/templates/browse-vault-ui.html b/swh/web/templates/browse/vault-ui.html similarity index 98% rename from swh/web/templates/browse-vault-ui.html rename to swh/web/templates/browse/vault-ui.html index 8ec18122..bb9d4959 100644 --- a/swh/web/templates/browse-vault-ui.html +++ b/swh/web/templates/browse/vault-ui.html @@ -1,69 +1,69 @@ -{% extends "browse-layout.html" %} +{% extends "./layout.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} {% load render_bundle from webpack_loader %} {% block navbar-content %}

Download archive content from the Vault

{% endblock %} {% block browse-content %}

This interface enables to track the status of the different Software Heritage Vault cooking tasks created while browsing the archive.

Once a cooking task is finished, a link will be made available in order to download the associated archive.

Object type Object id Cooking status
{% endblock %} \ No newline at end of file diff --git a/swh/web/templates/error.html b/swh/web/templates/error.html index f50eb79d..f7d2af85 100644 --- a/swh/web/templates/error.html +++ b/swh/web/templates/error.html @@ -1,20 +1,18 @@ {% extends "layout.html" %} {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} -{% load static %} - {% block title %}Error {{ error_code }} – Software Heritage archive {% endblock %} {% block navbar-content %}

Error

{% endblock %} {% block content %} {% include "includes/http-error.html" %} {% endblock %} \ No newline at end of file diff --git a/swh/web/tests/api/test_apidoc.py b/swh/web/tests/api/test_apidoc.py index 428dda8c..ee985fe4 100644 --- a/swh/web/tests/api/test_apidoc.py +++ b/swh/web/tests/api/test_apidoc.py @@ -1,288 +1,288 @@ # Copyright (C) 2015-2018 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 nose.tools import istest, nottest from rest_framework.test import APITestCase from rest_framework.response import Response from swh.web.api.apidoc import api_doc, _parse_httpdomain_doc from swh.web.api.apiurls import api_route from swh.web.tests.testbase import SWHWebTestBase # flake8: noqa httpdomain_doc = """ .. http:get:: /api/1/revision/(sha1_git)/ Get information about a revision in the SWH archive. Revisions are identified by *sha1* checksums, compatible with Git commit identifiers. See :func:`swh.model.identifiers.revision_identifier` in our data model module for details about how they are computed. :param string sha1_git: hexadecimal representation of the revision *sha1_git* identifier :reqheader Accept: the requested response content type, either *application/json* (default) or *application/yaml* :resheader Content-Type: this depends on :http:header:`Accept` header of request :>json object author: information about the author of the revision :>json string author_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the author of the revision :>json object committer: information about the committer of the revision :>json string committer_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the committer of the revision :>json string committer_date: ISO representation of the commit date (in UTC) :>json string date: ISO representation of the revision date (in UTC) :>json string directory: the unique identifier that revision points to :>json string directory_url: link to :http:get:`/api/1/directory/(sha1_git)/[(path)/]` to get information about the directory associated to the revision :>json string id: the revision unique identifier :>json boolean merge: whether or not the revision corresponds to a merge commit :>json string message: the message associated to the revision :>json array parents: the parents of the revision, i.e. the previous revisions that head directly to it, each entry of that array contains an unique parent revision identifier but also a link to :http:get:`/api/1/revision/(sha1_git)/` to get more informations about it :>json string type: the type of the revision **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options` :statuscode 200: no error :statuscode 400: an invalid *sha1_git* value has been provided :statuscode 404: requested revision can not be found in the SWH archive **Request:** .. parsed-literal:: $ curl -i :swh_web_api:`revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/` """ class APIDocTestCase(SWHWebTestBase, APITestCase): @istest def apidoc_nodoc_failure(self): with self.assertRaises(Exception): @api_doc('/my/nodoc/url/') def apidoc_nodoc_tester(request, arga=0, argb=0): return Response(arga + argb) @staticmethod @api_route(r'/some/(?P[0-9]+)/(?P[0-9]+)/', 'some-doc-route') @api_doc('/some/doc/route/') @nottest def apidoc_route_tester(request, myarg, myotherarg, akw=0): """ Sample doc """ return {'result': int(myarg) + int(myotherarg) + akw} @istest def apidoc_route_doc(self): # when rv = self.client.get('/api/1/some/doc/route/') # then self.assertEqual(rv.status_code, 200) - self.assertTemplateUsed('apidoc.html') + self.assertTemplateUsed('api/apidoc.html') @istest def apidoc_route_fn(self): # when rv = self.client.get('/api/1/some/1/1/') # then self.assertEqual(rv.status_code, 200) @staticmethod @api_route(r'/some/full/(?P[0-9]+)/(?P[0-9]+)/', 'some-complete-doc-route') @api_doc('/some/complete/doc/route/') @nottest def apidoc_full_stack_tester(request, myarg, myotherarg, akw=0): """ Sample doc """ return {'result': int(myarg) + int(myotherarg) + akw} @istest def apidoc_full_stack_doc(self): # when rv = self.client.get('/api/1/some/complete/doc/route/') # then self.assertEqual(rv.status_code, 200) - self.assertTemplateUsed('apidoc.html') + self.assertTemplateUsed('api/apidoc.html') @istest def apidoc_full_stack_fn(self): # when rv = self.client.get('/api/1/some/full/1/1/') # then self.assertEqual(rv.status_code, 200) @istest def test_api_doc_parse_httpdomain(self): doc_data = { 'description': '', 'urls': [], 'args': [], 'params': [], 'resheaders': [], 'reqheaders': [], 'return_type': '', 'returns': [], 'status_codes': [], 'examples': [] } _parse_httpdomain_doc(httpdomain_doc, doc_data) expected_urls = [{ 'rule': '/api/1/revision/ **\\(sha1_git\\)** /', 'methods': ['GET', 'HEAD', 'OPTIONS'] }] self.assertIn('urls', doc_data) self.assertEqual(doc_data['urls'], expected_urls) expected_description = 'Get information about a revision in the SWH archive. \ Revisions are identified by *sha1* checksums, compatible with Git commit \ identifiers. See **swh.model.identifiers.revision_identifier** in our data \ model module for details about how they are computed.' self.assertIn('description', doc_data) self.assertEqual(doc_data['description'], expected_description) expected_args = [{ 'name': 'sha1_git', 'type': 'string', 'doc': 'hexadecimal representation of the revision *sha1_git* identifier' }] self.assertIn('args', doc_data) self.assertEqual(doc_data['args'], expected_args) expected_params = [] self.assertIn('params', doc_data) self.assertEqual(doc_data['params'], expected_params) expected_reqheaders = [{ 'doc': 'the requested response content type, either *application/json* or *application/yaml*', 'name': 'Accept' }] self.assertIn('reqheaders', doc_data) self.assertEqual(doc_data['reqheaders'], expected_reqheaders) expected_resheaders = [{ 'doc': 'this depends on **Accept** header of request', 'name': 'Content-Type' }] self.assertIn('resheaders', doc_data) self.assertEqual(doc_data['resheaders'], expected_resheaders) expected_statuscodes = [ { 'code': '200', 'doc': 'no error' }, { 'code': '400', 'doc': 'an invalid *sha1_git* value has been provided' }, { 'code': '404', 'doc': 'requested revision can not be found in the SWH archive' } ] self.assertIn('status_codes', doc_data) self.assertEqual(doc_data['status_codes'], expected_statuscodes) expected_return_type = 'object' self.assertIn('return_type', doc_data) self.assertEqual(doc_data['return_type'], expected_return_type) expected_returns = [ { 'name': 'author', 'type': 'object', 'doc': 'information about the author of the revision' }, { 'name': 'author_url', 'type': 'string', 'doc': 'link to ``_ to get information about the author of the revision' }, { 'name': 'committer', 'type': 'object', 'doc': 'information about the committer of the revision' }, { 'name': 'committer_url', 'type': 'string', 'doc': 'link to ``_ to get information about the committer of the revision' }, { 'name': 'committer_date', 'type': 'string', 'doc': 'ISO representation of the commit date (in UTC)' }, { 'name': 'date', 'type': 'string', 'doc': 'ISO representation of the revision date (in UTC)' }, { 'name': 'directory', 'type': 'string', 'doc': 'the unique identifier that revision points to' }, { 'name': 'directory_url', 'type': 'string', 'doc': 'link to ``_ to get information about the directory associated to the revision' }, { 'name': 'id', 'type': 'string', 'doc': 'the revision unique identifier' }, { 'name': 'merge', 'type': 'boolean', 'doc': 'whether or not the revision corresponds to a merge commit' }, { 'name': 'message', 'type': 'string', 'doc': 'the message associated to the revision' }, { 'name': 'parents', 'type': 'array', 'doc': 'the parents of the revision, i.e. the previous revisions that head directly to it, each entry of that array contains an unique parent revision identifier but also a link to ``_ to get more informations about it' }, { 'name': 'type', 'type': 'string', 'doc': 'the type of the revision' } ] self.assertIn('returns', doc_data) self.assertEqual(doc_data['returns'], expected_returns) expected_examples = ['/api/1/revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/'] self.assertIn('examples', doc_data) self.assertEqual(doc_data['examples'], expected_examples) diff --git a/swh/web/tests/api/test_apiresponse.py b/swh/web/tests/api/test_apiresponse.py index 4dbe223b..428727bd 100644 --- a/swh/web/tests/api/test_apiresponse.py +++ b/swh/web/tests/api/test_apiresponse.py @@ -1,180 +1,180 @@ # Copyright (C) 2015-2018 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 import json import unittest from rest_framework.test import APIRequestFactory from nose.tools import istest from unittest.mock import patch from swh.web.api.apiresponse import ( compute_link_header, transform, make_api_response, filter_by_fields ) api_request_factory = APIRequestFactory() class SWHComputeLinkHeaderTest(unittest.TestCase): @istest def compute_link_header(self): rv = { 'headers': {'link-next': 'foo', 'link-prev': 'bar'}, 'results': [1, 2, 3] } options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, { 'Link': '; rel="next",; rel="previous"', }) @istest def compute_link_header_nothing_changed(self): rv = {} options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, {}) @istest def compute_link_header_nothing_changed_2(self): rv = {'headers': {}} options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, {}) class SWHTransformProcessorTest(unittest.TestCase): @istest def transform_only_return_results_1(self): rv = {'results': {'some-key': 'some-value'}} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_only_return_results_2(self): rv = {'headers': {'something': 'do changes'}, 'results': {'some-key': 'some-value'}} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_do_remove_headers(self): rv = {'headers': {'something': 'do changes'}, 'some-key': 'some-value'} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_do_nothing(self): rv = {'some-key': 'some-value'} self.assertEquals(transform(rv), {'some-key': 'some-value'}) class RendererTestCase(unittest.TestCase): @patch('swh.web.api.apiresponse.json') @patch('swh.web.api.apiresponse.filter_by_fields') @patch('swh.web.api.apiresponse.shorten_path') @istest def swh_multi_response_mimetype(self, mock_shorten_path, mock_filter, mock_json): # given data = { 'data': [12, 34], 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc' } mock_filter.return_value = data mock_shorten_path.return_value = 'my_short_path' accepted_response_formats = {'html': 'text/html', 'yaml': 'application/yaml', 'json': 'application/json'} for format in accepted_response_formats: request = api_request_factory.get('/api/test/path/') mime_type = accepted_response_formats[format] setattr(request, 'accepted_media_type', mime_type) if mime_type == 'text/html': expected_data = { 'response_data': json.dumps(data), 'request': { 'path': request.path, 'method': request.method, 'absolute_uri': request.build_absolute_uri() }, 'headers_data': {}, 'heading': 'my_short_path', 'status_code': 200 } mock_json.dumps.return_value = json.dumps(data) else: expected_data = data # when rv = make_api_response(request, data) # then mock_filter.assert_called_with(request, data) self.assertEqual(rv.data, expected_data) self.assertEqual(rv.status_code, 200) if mime_type == 'text/html': - self.assertEqual(rv.template_name, 'apidoc.html') + self.assertEqual(rv.template_name, 'api/apidoc.html') @istest def swh_filter_renderer_do_nothing(self): # given input_data = {'a': 'some-data'} request = api_request_factory.get('/api/test/path/', data={}) setattr(request, 'query_params', request.GET) # when actual_data = filter_by_fields(request, input_data) # then self.assertEquals(actual_data, input_data) @patch('swh.web.api.apiresponse.utils.filter_field_keys') @istest def swh_filter_renderer_do_filter(self, mock_ffk): # given mock_ffk.return_value = {'a': 'some-data'} request = api_request_factory.get('/api/test/path/', data={'fields': 'a,c'}) setattr(request, 'query_params', request.GET) input_data = {'a': 'some-data', 'b': 'some-other-data'} # when actual_data = filter_by_fields(request, input_data) # then self.assertEquals(actual_data, {'a': 'some-data'}) mock_ffk.assert_called_once_with(input_data, {'a', 'c'}) diff --git a/swh/web/tests/browse/views/test_content.py b/swh/web/tests/browse/views/test_content.py index cf41e360..ff292597 100644 --- a/swh/web/tests/browse/views/test_content.py +++ b/swh/web/tests/browse/views/test_content.py @@ -1,306 +1,306 @@ # Copyright (C) 2017-2018 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 import base64 from unittest.mock import patch from nose.tools import istest from django.test import TestCase from django.utils.html import escape from django.utils.encoding import DjangoUnicodeDecodeError from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse, get_swh_persistent_id from swh.web.common.utils import gen_path_info from swh.web.tests.testbase import SWHWebTestBase from .data.content_test_data import ( stub_content_text_data, stub_content_text_path_with_root_dir, stub_content_bin_data, stub_content_bin_filename, stub_content_text_no_highlight_data, non_utf8_encoded_content_data, non_utf8_encoded_content, non_utf8_encoding, stub_content_too_large_data ) class SwhBrowseContentTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.content.request_content') @istest def content_view_text(self, mock_request_content): mock_request_content.return_value = stub_content_text_data sha1_git = stub_content_text_data['checksums']['sha1_git'] url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_data['raw_data'])) self.assertContains(resp, url_raw) swh_cnt_id = get_swh_persistent_id('content', sha1_git) swh_cnt_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_cnt_id}) self.assertContains(resp, swh_cnt_id) self.assertContains(resp, swh_cnt_id_url) @patch('swh.web.browse.views.content.request_content') @istest def content_view_text_no_highlight(self, mock_request_content): mock_request_content.return_value = stub_content_text_no_highlight_data sha1_git = stub_content_text_no_highlight_data['checksums']['sha1_git'] url = reverse('browse-content', kwargs={'query_string': stub_content_text_no_highlight_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_no_highlight_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_no_highlight_data['raw_data'])) # noqa self.assertContains(resp, url_raw) swh_cnt_id = get_swh_persistent_id('content', sha1_git) swh_cnt_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_cnt_id}) self.assertContains(resp, swh_cnt_id) self.assertContains(resp, swh_cnt_id_url) @patch('swh.web.browse.utils.service') @istest def content_view_no_utf8_text(self, mock_service): mock_service.lookup_content.return_value = \ non_utf8_encoded_content_data mock_service.lookup_content_raw.return_value = \ {'data': non_utf8_encoded_content} mock_service.lookup_content_filetype.return_value = None mock_service.lookup_content_language.return_value = None mock_service.lookup_content_license.return_value = None sha1_git = non_utf8_encoded_content_data['checksums']['sha1_git'] url = reverse('browse-content', kwargs={'query_string': non_utf8_encoded_content_data['checksums']['sha1']}) # noqa try: resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') swh_cnt_id = get_swh_persistent_id('content', sha1_git) swh_cnt_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_cnt_id}) self.assertContains(resp, swh_cnt_id_url) self.assertContains(resp, escape(non_utf8_encoded_content.decode(non_utf8_encoding).encode('utf-8'))) # noqa except DjangoUnicodeDecodeError: self.fail('Textual content is not encoded in utf-8') @patch('swh.web.browse.views.content.request_content') @istest def content_view_image(self, mock_request_content): mime_type = 'image/png' mock_request_content.return_value = stub_content_bin_data url = reverse('browse-content', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') pngEncoded = base64.b64encode(stub_content_bin_data['raw_data']) \ .decode('utf-8') self.assertContains(resp, '' % (mime_type, pngEncoded)) self.assertContains(resp, url_raw) @patch('swh.web.browse.views.content.request_content') @istest def content_view_with_path(self, mock_request_content): mock_request_content.return_value = stub_content_text_data url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'path': stub_content_text_path_with_root_dir}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_data['raw_data'])) split_path = stub_content_text_path_with_root_dir.split('/') root_dir_sha1 = split_path[0] filename = split_path[-1] path = stub_content_text_path_with_root_dir \ .replace(root_dir_sha1 + '/', '') \ .replace(filename, '') path_info = gen_path_info(path) root_dir_url = reverse('browse-directory', kwargs={'sha1_git': root_dir_sha1}) self.assertContains(resp, '
  • ', count=len(path_info)+1) self.assertContains(resp, '' + root_dir_sha1[:7] + '') for p in path_info: dir_url = reverse('browse-directory', kwargs={'sha1_git': root_dir_sha1, 'path': p['path']}) self.assertContains(resp, '' + p['name'] + '') self.assertContains(resp, '
  • ' + filename + '
  • ') url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'filename': filename}) self.assertContains(resp, url_raw) @patch('swh.web.browse.views.content.request_content') @istest def content_raw_text(self, mock_request_content): mock_request_content.return_value = stub_content_text_data url = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'text/plain') self.assertEqual(resp['Content-disposition'], 'filename=%s_%s' % ('sha1', stub_content_text_data['checksums']['sha1'])) # noqa self.assertEqual(resp.content, stub_content_text_data['raw_data']) filename = stub_content_text_path_with_root_dir.split('/')[-1] url = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'filename': filename}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'text/plain') self.assertEqual(resp['Content-disposition'], 'filename=%s' % filename) self.assertEqual(resp.content, stub_content_text_data['raw_data']) @patch('swh.web.browse.views.content.request_content') @istest def content_raw_bin(self, mock_request_content): mock_request_content.return_value = stub_content_bin_data url = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'application/octet-stream') self.assertEqual(resp['Content-disposition'], 'attachment; filename=%s_%s' % ('sha1', stub_content_bin_data['checksums']['sha1'])) self.assertEqual(resp.content, stub_content_bin_data['raw_data']) url = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}, # noqa query_params={'filename': stub_content_bin_filename}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'application/octet-stream') self.assertEqual(resp['Content-disposition'], 'attachment; filename=%s' % stub_content_bin_filename) self.assertEqual(resp.content, stub_content_bin_data['raw_data']) @patch('swh.web.browse.views.content.request_content') @istest def content_request_errors(self, mock_request_content): url = reverse('browse-content', kwargs={'query_string': '123456'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 400) self.assertTemplateUsed('error.html') mock_request_content.side_effect = NotFoundExc('content not found') url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') @patch('swh.web.browse.utils.service') @istest def content_bytes_missing(self, mock_service): content_data = dict(stub_content_text_data) content_data['raw_data'] = None mock_service.lookup_content.return_value = content_data mock_service.lookup_content_raw.side_effect = NotFoundExc('Content bytes not available!') # noqa url = reverse('browse-content', kwargs={'query_string': content_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 404) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') @patch('swh.web.browse.views.content.request_content') @istest def content_too_large(self, mock_request_content): mock_request_content.return_value = stub_content_too_large_data url = reverse('browse-content', kwargs={'query_string': stub_content_too_large_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_too_large_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('content.html') + self.assertTemplateUsed('browse/content.html') self.assertContains(resp, 'Content is too large to be displayed') self.assertContains(resp, url_raw) diff --git a/swh/web/tests/browse/views/test_directory.py b/swh/web/tests/browse/views/test_directory.py index 2ddb77c3..c4062c7c 100644 --- a/swh/web/tests/browse/views/test_directory.py +++ b/swh/web/tests/browse/views/test_directory.py @@ -1,137 +1,137 @@ # Copyright (C) 2017-2018 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 unittest.mock import patch from nose.tools import istest, nottest from django.test import TestCase from swh.web.common.exc import BadInputExc, NotFoundExc from swh.web.common.utils import reverse, get_swh_persistent_id from swh.web.common.utils import gen_path_info from swh.web.tests.testbase import SWHWebTestBase from .data.directory_test_data import ( stub_root_directory_sha1, stub_root_directory_data, stub_sub_directory_path, stub_sub_directory_data ) class SwhBrowseDirectoryTest(SWHWebTestBase, TestCase): @nottest def directory_view(self, root_directory_sha1, directory_entries, path=None): dirs = [e for e in directory_entries if e['type'] == 'dir'] files = [e for e in directory_entries if e['type'] == 'file'] url_args = {'sha1_git': root_directory_sha1} if path: url_args['path'] = path url = reverse('browse-directory', kwargs=url_args) root_dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('directory.html') + self.assertTemplateUsed('browse/directory.html') self.assertContains(resp, '' + root_directory_sha1[:7] + '') self.assertContains(resp, '', count=len(dirs)) self.assertContains(resp, '', count=len(files)) for d in dirs: dir_path = d['name'] if path: dir_path = "%s/%s" % (path, d['name']) dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1, 'path': dir_path}) self.assertContains(resp, dir_url) for f in files: file_path = "%s/%s" % (root_directory_sha1, f['name']) if path: file_path = "%s/%s/%s" % (root_directory_sha1, path, f['name']) query_string = 'sha1_git:' + f['target'] file_url = reverse('browse-content', kwargs={'query_string': query_string}, query_params={'path': file_path}) self.assertContains(resp, file_url) path_info = gen_path_info(path) self.assertContains(resp, '
  • ', count=len(path_info)+1) self.assertContains(resp, '%s' % (root_dir_url, root_directory_sha1[:7])) for p in path_info: dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1, 'path': p['path']}) self.assertContains(resp, '%s' % (dir_url, p['name'])) self.assertContains(resp, 'vault-cook-directory') swh_dir_id = get_swh_persistent_id('directory', directory_entries[0]['dir_id']) # noqa swh_dir_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_dir_id}) self.assertContains(resp, swh_dir_id) self.assertContains(resp, swh_dir_id_url) @patch('swh.web.browse.utils.service') @istest def root_directory_view(self, mock_service): mock_service.lookup_directory.return_value = \ stub_root_directory_data self.directory_view(stub_root_directory_sha1, stub_root_directory_data) @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.directory.service') @istest def sub_directory_view(self, mock_directory_service, mock_utils_service): mock_utils_service.lookup_directory.return_value = \ stub_sub_directory_data mock_directory_service.lookup_directory_with_path.return_value = \ {'target': stub_sub_directory_data[0]['dir_id'], 'type': 'dir'} self.directory_view(stub_root_directory_sha1, stub_sub_directory_data, stub_sub_directory_path) @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.directory.service') @istest def directory_request_errors(self, mock_directory_service, mock_utils_service): mock_utils_service.lookup_directory.side_effect = \ BadInputExc('directory not found') dir_url = reverse('browse-directory', kwargs={'sha1_git': '1253456'}) resp = self.client.get(dir_url) self.assertEquals(resp.status_code, 400) - self.assertTemplateUsed('error.html') + self.assertTemplateUsed('browse/error.html') mock_utils_service.lookup_directory.side_effect = \ NotFoundExc('directory not found') dir_url = reverse('browse-directory', kwargs={'sha1_git': '1253456'}) resp = self.client.get(dir_url) self.assertEquals(resp.status_code, 404) - self.assertTemplateUsed('error.html') + self.assertTemplateUsed('browse/error.html') diff --git a/swh/web/tests/browse/views/test_person.py b/swh/web/tests/browse/views/test_person.py index dbc685b6..d5b6c63a 100644 --- a/swh/web/tests/browse/views/test_person.py +++ b/swh/web/tests/browse/views/test_person.py @@ -1,56 +1,56 @@ # Copyright (C) 2017-2018 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 unittest.mock import patch from nose.tools import istest from django.test import TestCase from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse from swh.web.tests.testbase import SWHWebTestBase class SwhBrowsePersonTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.person.service') @istest def person_browse(self, mock_service): test_person_data = \ { "email": "j.adams440@gmail.com", "fullname": "oysterCrusher ", "id": 457587, "name": "oysterCrusher" } mock_service.lookup_person.return_value = test_person_data url = reverse('browse-person', kwargs={'person_id': 457587}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('person.html') + self.assertTemplateUsed('browse/person.html') self.assertContains(resp, '
    %s
    ' % test_person_data['id']) self.assertContains(resp, '
    %s
    ' % test_person_data['name']) self.assertContains(resp, '
    %s
    ' % (test_person_data['email'], test_person_data['email'])) self.assertContains(resp, '
    %s <%s>
    ' % # noqa (test_person_data['name'], test_person_data['email'], test_person_data['email'])) @patch('swh.web.browse.views.person.service') @istest def person_request_error(self, mock_service): mock_service.lookup_person.side_effect = \ NotFoundExc('Person not found') url = reverse('browse-person', kwargs={'person_id': 457587}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Person not found', status_code=404) diff --git a/swh/web/tests/browse/views/test_release.py b/swh/web/tests/browse/views/test_release.py index 738b743c..8874489d 100644 --- a/swh/web/tests/browse/views/test_release.py +++ b/swh/web/tests/browse/views/test_release.py @@ -1,114 +1,114 @@ # Copyright (C) 2018 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 # flake8: noqa from unittest.mock import patch from nose.tools import istest from django.test import TestCase from swh.web.common.exc import NotFoundExc from swh.web.common.utils import ( reverse, format_utc_iso_date, get_swh_persistent_id ) from swh.web.tests.testbase import SWHWebTestBase from .data.release_test_data import ( stub_release ) from .data.origin_test_data import stub_origin_visits class SwhBrowseReleaseTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.release.service') @patch('swh.web.browse.utils.service') @patch('swh.web.common.utils.service') @istest def release_browse(self, mock_service_common, mock_service_utils, mock_service): mock_service.lookup_release.return_value = stub_release url = reverse('browse-release', kwargs={'sha1_git': stub_release['id']}) release_id = stub_release['id'] release_name = stub_release['name'] author_id = stub_release['author']['id'] author_name = stub_release['author']['name'] author_url = reverse('browse-person', kwargs={'person_id': author_id}) release_date = stub_release['date'] message = stub_release['message'] target_type = stub_release['target_type'] target = stub_release['target'] target_url = reverse('browse-revision', kwargs={'sha1_git': target}) message_lines = stub_release['message'].split('\n') resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('release.html') + self.assertTemplateUsed('browse/release.html') self.assertContains(resp, '%s' % (author_url, author_name)) self.assertContains(resp, format_utc_iso_date(release_date)) self.assertContains(resp, '
    %s
    %s' % (message_lines[0], '\n'.join(message_lines[1:]))) self.assertContains(resp, release_id) self.assertContains(resp, release_name) self.assertContains(resp, target_type) self.assertContains(resp, '%s' % (target_url, target)) swh_rel_id = get_swh_persistent_id('release', release_id) swh_rel_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_rel_id}) self.assertContains(resp, swh_rel_id) self.assertContains(resp, swh_rel_id_url) origin_info = { 'id': 13706355, 'type': 'git', 'url': 'https://github.com/python/cpython' } mock_service_utils.lookup_origin.return_value = origin_info mock_service_common.lookup_origin_visits.return_value = stub_origin_visits mock_service_common.MAX_LIMIT = 20 url = reverse('browse-release', kwargs={'sha1_git': stub_release['id']}, query_params={'origin': origin_info['url']}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('release.html') + self.assertTemplateUsed('browse/release.html') self.assertContains(resp, author_url) self.assertContains(resp, author_name) self.assertContains(resp, format_utc_iso_date(release_date)) self.assertContains(resp, '
    %s
    %s' % (message_lines[0], '\n'.join(message_lines[1:]))) self.assertContains(resp, release_id) self.assertContains(resp, release_name) self.assertContains(resp, target_type) target_url = reverse('browse-revision', kwargs={'sha1_git': target}, query_params={'origin': origin_info['url']}) self.assertContains(resp, '%s' % (target_url, target)) mock_service.lookup_release.side_effect = \ NotFoundExc('Release not found') url = reverse('browse-release', kwargs={'sha1_git': 'ffff'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Release not found', status_code=404) diff --git a/swh/web/tests/browse/views/test_revision.py b/swh/web/tests/browse/views/test_revision.py index a40b062f..0de7e8e9 100644 --- a/swh/web/tests/browse/views/test_revision.py +++ b/swh/web/tests/browse/views/test_revision.py @@ -1,278 +1,278 @@ # Copyright (C) 2017-2018 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 # flake8: noqa from unittest.mock import patch from nose.tools import istest from django.test import TestCase from django.utils.html import escape from swh.web.common.exc import NotFoundExc from swh.web.common.utils import ( reverse, format_utc_iso_date, get_swh_persistent_id ) from swh.web.tests.testbase import SWHWebTestBase from .data.revision_test_data import ( revision_id_test, revision_metadata_test, revision_history_log_test ) from .data.origin_test_data import stub_origin_visits class SwhBrowseRevisionTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.revision.service') @patch('swh.web.browse.utils.service') @patch('swh.web.common.utils.service') @istest def revision_browse(self, mock_service_common, mock_service_utils, mock_service): mock_service.lookup_revision.return_value = revision_metadata_test url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}) author_id = revision_metadata_test['author']['id'] author_name = revision_metadata_test['author']['name'] committer_id = revision_metadata_test['committer']['id'] committer_name = revision_metadata_test['committer']['name'] dir_id = revision_metadata_test['directory'] author_url = reverse('browse-person', kwargs={'person_id': author_id}) committer_url = reverse('browse-person', kwargs={'person_id': committer_id}) directory_url = reverse('browse-directory', kwargs={'sha1_git': dir_id}) history_url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('revision.html') + self.assertTemplateUsed('browse/revision.html') self.assertContains(resp, '%s' % (author_url, author_name)) self.assertContains(resp, '%s' % (committer_url, committer_name)) self.assertContains(resp, directory_url) self.assertContains(resp, history_url) for parent in revision_metadata_test['parents']: parent_url = reverse('browse-revision', kwargs={'sha1_git': parent}) self.assertContains(resp, '%s' % (parent_url, parent)) author_date = revision_metadata_test['date'] committer_date = revision_metadata_test['committer_date'] message_lines = revision_metadata_test['message'].split('\n') self.assertContains(resp, format_utc_iso_date(author_date)) self.assertContains(resp, format_utc_iso_date(committer_date)) self.assertContains(resp, message_lines[0]) self.assertContains(resp, '\n'.join(message_lines[1:])) origin_info = { 'id': '7416001', 'type': 'git', 'url': 'https://github.com/webpack/webpack' } mock_service_utils.lookup_origin.return_value = origin_info mock_service_common.lookup_origin_visits.return_value = stub_origin_visits mock_service_common.MAX_LIMIT = 20 origin_directory_url = reverse('browse-origin-directory', kwargs={'origin_type': origin_info['type'], 'origin_url': origin_info['url']}, query_params={'revision': revision_id_test}) origin_revision_log_url = reverse('browse-origin-log', kwargs={'origin_type': origin_info['type'], 'origin_url': origin_info['url']}, query_params={'revision': revision_id_test}) url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}, query_params={'origin_type': origin_info['type'], 'origin': origin_info['url']}) resp = self.client.get(url) self.assertContains(resp, origin_directory_url) self.assertContains(resp, origin_revision_log_url) for parent in revision_metadata_test['parents']: parent_url = reverse('browse-revision', kwargs={'sha1_git': parent}, query_params={'origin_type': origin_info['type'], 'origin': origin_info['url']}) self.assertContains(resp, '%s' % (parent_url, parent)) self.assertContains(resp, 'vault-cook-directory') self.assertContains(resp, 'vault-cook-revision') swh_rev_id = get_swh_persistent_id('revision', revision_id_test) swh_rev_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_rev_id}) self.assertContains(resp, swh_rev_id) self.assertContains(resp, swh_rev_id_url) swh_dir_id = get_swh_persistent_id('directory', dir_id) swh_dir_id_url = reverse('browse-swh-id', kwargs={'swh_id': swh_dir_id}) self.assertContains(resp, swh_dir_id) self.assertContains(resp, swh_dir_id_url) @patch('swh.web.browse.views.revision.service') @istest def revision_log_browse(self, mock_service): per_page = 10 mock_service.lookup_revision_log.return_value = \ revision_history_log_test[:per_page+1] url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}, query_params={'per_page': per_page}) resp = self.client.get(url) prev_rev = revision_history_log_test[per_page]['id'] next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': revision_id_test, 'per_page': per_page}) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('revision-log.html') + self.assertTemplateUsed('browse/revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ') self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) for log in revision_history_log_test[:per_page]: author_url = reverse('browse-person', kwargs={'person_id': log['author']['id']}) revision_url = reverse('browse-revision', kwargs={'sha1_git': log['id']}) directory_url = reverse('browse-directory', kwargs={'sha1_git': log['directory']}) self.assertContains(resp, '%s' % (author_url, log['author']['name'])) self.assertContains(resp, '%s' % (revision_url, log['id'][:7])) self.assertContains(resp, directory_url) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[per_page:2*per_page+1] resp = self.client.get(next_page_url) prev_prev_rev = revision_history_log_test[2*per_page]['id'] prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}, query_params={'per_page': per_page}) next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, 'per_page': per_page}) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('revision-log.html') + self.assertTemplateUsed('browse/revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[2*per_page:3*per_page+1] resp = self.client.get(next_page_url) prev_prev_prev_rev = revision_history_log_test[3*per_page]['id'] prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': revision_id_test, 'per_page': per_page}) next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev + '/' + prev_prev_rev, 'per_page': per_page}) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('revision-log.html') + self.assertTemplateUsed('browse/revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[3*per_page:3*per_page+per_page//2] resp = self.client.get(next_page_url) prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, 'per_page': per_page}) self.assertEquals(resp.status_code, 200) - self.assertTemplateUsed('revision-log.html') + self.assertTemplateUsed('browse/revision-log.html') self.assertContains(resp, '', count=per_page//2) self.assertContains(resp, '
  • Older
  • ') self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.revision.service') @istest def revision_request_errors(self, mock_service, mock_utils_service): mock_service.lookup_revision.side_effect = \ NotFoundExc('Revision not found') url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Revision not found', status_code=404) mock_service.lookup_revision_log.side_effect = \ NotFoundExc('Revision not found') url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Revision not found', status_code=404) url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}, query_params={'origin_type': 'git', 'origin': 'https://github.com/foo/bar'}) mock_service.lookup_revision.side_effect = None mock_utils_service.lookup_origin.side_effect = \ NotFoundExc('Origin not found') resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Origin not found', status_code=404)