diff --git a/PKG-INFO b/PKG-INFO
index d57f7c8e..4aaac2a1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,11 @@
Metadata-Version: 2.1
Name: swh.deposit
-Version: 0.0.40
+Version: 0.0.41
Summary: Software Heritage Deposit Server
Home-page: https://forge.softwareheritage.org/source/swh-deposit/
Author: Software Heritage developers
Author-email: swh-devel@inria.fr
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Provides-Extra: loader
diff --git a/swh.deposit.egg-info/PKG-INFO b/swh.deposit.egg-info/PKG-INFO
index d57f7c8e..4aaac2a1 100644
--- a/swh.deposit.egg-info/PKG-INFO
+++ b/swh.deposit.egg-info/PKG-INFO
@@ -1,11 +1,11 @@
Metadata-Version: 2.1
Name: swh.deposit
-Version: 0.0.40
+Version: 0.0.41
Summary: Software Heritage Deposit Server
Home-page: https://forge.softwareheritage.org/source/swh-deposit/
Author: Software Heritage developers
Author-email: swh-devel@inria.fr
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Provides-Extra: loader
diff --git a/swh.deposit.egg-info/SOURCES.txt b/swh.deposit.egg-info/SOURCES.txt
index 8f4f4248..f7b5fef9 100644
--- a/swh.deposit.egg-info/SOURCES.txt
+++ b/swh.deposit.egg-info/SOURCES.txt
@@ -1,130 +1,141 @@
.gitignore
AUTHORS
LICENSE
MANIFEST.in
Makefile
Makefile.local
README.md
requirements-swh.txt
requirements.txt
setup.py
version.txt
bin/Makefile
bin/content.sh
bin/create_deposit.sh
bin/create_deposit_atom.sh
bin/create_deposit_with_metadata.sh
bin/default-setup
bin/download-deposit-archive.sh
bin/home.sh
bin/replace-deposit-archive.sh
bin/service-document.sh
bin/status.sh
bin/update-deposit-with-another-archive.sh
bin/update-status.sh
debian/changelog
debian/compat
debian/control
debian/copyright
debian/rules
debian/source/format
docs/.gitignore
docs/Makefile
docs/conf.py
docs/dev-info.md
docs/getting-started.md
docs/index.rst
docs/metadata.md
docs/spec-api.md
docs/spec-loading.md
docs/sys-info.md
docs/_static/.placeholder
docs/_templates/.placeholder
docs/images/deposit-create-chart.png
docs/images/deposit-delete-chart.png
docs/images/deposit-update-chart.png
resources/deposit/server.yml
swh/__init__.py
swh/manage.py
swh.deposit.egg-info/PKG-INFO
swh.deposit.egg-info/SOURCES.txt
swh.deposit.egg-info/dependency_links.txt
swh.deposit.egg-info/requires.txt
swh.deposit.egg-info/top_level.txt
swh/deposit/__init__.py
swh/deposit/apps.py
swh/deposit/auth.py
swh/deposit/config.py
swh/deposit/create_user.py
swh/deposit/errors.py
swh/deposit/models.py
swh/deposit/parsers.py
swh/deposit/signals.py
swh/deposit/urls.py
swh/deposit/wsgi.py
swh/deposit/api/__init__.py
swh/deposit/api/common.py
swh/deposit/api/deposit.py
swh/deposit/api/deposit_content.py
swh/deposit/api/deposit_status.py
swh/deposit/api/deposit_update.py
swh/deposit/api/service_document.py
swh/deposit/api/urls.py
swh/deposit/api/private/__init__.py
swh/deposit/api/private/deposit_check.py
swh/deposit/api/private/deposit_read.py
swh/deposit/api/private/deposit_update_status.py
swh/deposit/api/private/urls.py
swh/deposit/fixtures/__init__.py
swh/deposit/fixtures/deposit_data.yaml
swh/deposit/loader/__init__.py
swh/deposit/loader/checker.py
swh/deposit/loader/client.py
swh/deposit/loader/loader.py
swh/deposit/loader/scheduler.py
swh/deposit/loader/tasks.py
swh/deposit/migrations/0001_initial.py
swh/deposit/migrations/0002_depositrequest_archive.py
swh/deposit/migrations/0003_temporaryarchive.py
swh/deposit/migrations/0004_delete_temporaryarchive.py
swh/deposit/migrations/0005_auto_20171019_1436.py
swh/deposit/migrations/0006_depositclient_url.py
swh/deposit/migrations/0007_auto_20171129_1609.py
swh/deposit/migrations/0008_auto_20171130_1513.py
swh/deposit/migrations/0009_deposit_parent.py
+swh/deposit/migrations/0010_auto_20180110_0953.py
swh/deposit/migrations/__init__.py
swh/deposit/settings/__init__.py
swh/deposit/settings/common.py
swh/deposit/settings/development.py
swh/deposit/settings/production.py
swh/deposit/settings/testing.py
swh/deposit/static/robots.txt
+swh/deposit/static/css/bootstrap-responsive.min.css
+swh/deposit/static/css/style.css
+swh/deposit/static/img/swh-logo-deposit.png
+swh/deposit/static/img/swh-logo-deposit.svg
+swh/deposit/static/img/icons/swh-logo-32x32.png
+swh/deposit/static/img/icons/swh-logo-deposit-180x180.png
+swh/deposit/static/img/icons/swh-logo-deposit-192x192.png
+swh/deposit/static/img/icons/swh-logo-deposit-270x270.png
swh/deposit/templates/__init__.py
+swh/deposit/templates/homepage.html
+swh/deposit/templates/layout.html
swh/deposit/templates/deposit/__init__.py
swh/deposit/templates/deposit/content.xml
swh/deposit/templates/deposit/deposit_receipt.xml
swh/deposit/templates/deposit/error.xml
swh/deposit/templates/deposit/service_document.xml
swh/deposit/templates/deposit/status.xml
swh/deposit/templates/rest_framework/api.html
swh/deposit/tests/__init__.py
swh/deposit/tests/common.py
swh/deposit/tests/api/__init__.py
swh/deposit/tests/api/test_common.py
swh/deposit/tests/api/test_deposit.py
swh/deposit/tests/api/test_deposit_atom.py
swh/deposit/tests/api/test_deposit_binary.py
swh/deposit/tests/api/test_deposit_check.py
swh/deposit/tests/api/test_deposit_delete.py
swh/deposit/tests/api/test_deposit_multipart.py
swh/deposit/tests/api/test_deposit_read_archive.py
swh/deposit/tests/api/test_deposit_read_metadata.py
swh/deposit/tests/api/test_deposit_status.py
swh/deposit/tests/api/test_deposit_update.py
swh/deposit/tests/api/test_deposit_update_status.py
swh/deposit/tests/api/test_service_document.py
swh/deposit/tests/loader/__init__.py
swh/deposit/tests/loader/common.py
swh/deposit/tests/loader/test_checker.py
swh/deposit/tests/loader/test_client.py
swh/deposit/tests/loader/test_loader.py
\ No newline at end of file
diff --git a/swh/deposit/api/private/deposit_check.py b/swh/deposit/api/private/deposit_check.py
index f6961f6a..93d853b0 100644
--- a/swh/deposit/api/private/deposit_check.py
+++ b/swh/deposit/api/private/deposit_check.py
@@ -1,158 +1,181 @@
-# Copyright (C) 2017 The Software Heritage developers
+# Copyright (C) 2017-2018 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import json
import zipfile
from rest_framework import status
from ..common import SWHGetDepositAPI, SWHPrivateAPIView
from ...config import DEPOSIT_STATUS_READY, DEPOSIT_STATUS_REJECTED
from ...config import ARCHIVE_TYPE, METADATA_TYPE
from ...models import Deposit, DepositRequest
class SWHChecksDeposit(SWHGetDepositAPI, SWHPrivateAPIView):
"""Dedicated class to read a deposit's raw archives content.
Only GET is supported.
"""
def _deposit_requests(self, deposit, request_type):
"""Given a deposit, yields its associated deposit_request
Args:
deposit (Deposit): Deposit to list requests for
request_type (str): Archive or metadata type
Yields:
deposit requests of type request_type associated to the deposit
"""
deposit_requests = DepositRequest.objects.filter(
type=self.deposit_request_types[request_type],
deposit=deposit).order_by('id')
for deposit_request in deposit_requests:
yield deposit_request
def _check_deposit_archives(self, deposit):
"""Given a deposit, check each deposit request of type archive.
Args:
The deposit to check archives for
Returns
True if all archives are ok, False otherwise.
"""
requests = list(self._deposit_requests(
deposit, request_type=ARCHIVE_TYPE))
if len(requests) == 0: # no associated archive is refused
return False
for dr in requests:
check = self._check_archive(dr.archive)
if not check:
return False
return True
def _check_archive(self, archive):
"""Check that a given archive is actually ok for reading.
Args:
archive (File): Archive to check
Returns:
True if archive is successfully read, False otherwise.
"""
try:
zf = zipfile.ZipFile(archive.path)
zf.infolist()
except Exception as e:
return False
else:
return True
- def _check_deposit_metadata(self, deposit):
- """Given a deposit, check each deposit request of type metadata.
+ def _metadata_get(self, deposit):
+ """Given a deposit, aggregate all metadata requests.
Args:
The deposit to check metadata for.
Returns:
True if the deposit's associated metadata are ok, False otherwise.
"""
metadata = {}
for dr in self._deposit_requests(deposit, request_type=METADATA_TYPE):
metadata.update(dr.metadata)
-
- return self._check_metadata(metadata)
+ return metadata
def _check_metadata(self, metadata):
- """Check to execute on all metadata.
+ """Check to execute on all metadata for mandatory field presence.
Args:
- metadata (): Metadata to actually check
+ metadata (dict): Metadata to actually check
Returns:
True if metadata is ok, False otherwise.
"""
required_fields = (('url',),
('external_identifier',),
('name', 'title'),
('author',))
result = all(any(name in field
for field in metadata
for name in possible_names)
for possible_names in required_fields)
return result
+ def _check_url(self, client_domain, metadata):
+ """Check compatibility between client_domain and url field in metadata
+
+ Args:
+ client_domain (str): url associated with the deposit's client
+ metadata (dict): Metadata where to find url
+ Returns:
+ True if url is ok, False otherwise.
+
+ """
+ metadata_urls = []
+ for field in metadata:
+ if 'url' in field:
+ metadata_urls.append(metadata[field])
+
+ return any(client_domain in url
+ for url in metadata_urls)
+
def process_get(self, req, collection_name, deposit_id):
"""Build a unique tarball from the multiple received and stream that
content to the client.
Args:
req (Request):
collection_name (str): Collection owning the deposit
deposit_id (id): Deposit concerned by the reading
Returns:
Tuple status, stream of content, content-type
"""
deposit = Deposit.objects.get(pk=deposit_id)
+ client_domain = deposit.client.domain
+ metadata = self._metadata_get(deposit)
problems = []
# will check each deposit's associated request (both of type
# archive and metadata) for errors
archives_status = self._check_deposit_archives(deposit)
if not archives_status:
problems.append('archive(s)')
- metadata_status = self._check_deposit_metadata(deposit)
+ metadata_status = self._check_metadata(metadata)
if not metadata_status:
problems.append('metadata')
- deposit_status = archives_status and metadata_status
+ url_status = self._check_url(client_domain, metadata)
+ if not url_status:
+ problems.append('url')
+
+ deposit_status = archives_status and metadata_status and url_status
# if any problems arose, the deposit is rejected
if not deposit_status:
deposit.status = DEPOSIT_STATUS_REJECTED
else:
deposit.status = DEPOSIT_STATUS_READY
deposit.save()
return (status.HTTP_200_OK,
json.dumps({
'status': deposit.status,
'details': 'Some %s failed the checks.' % (
' and '.join(problems), ),
}),
'application/json')
diff --git a/swh/deposit/api/private/deposit_read.py b/swh/deposit/api/private/deposit_read.py
index 20b24a32..fc5d3dbc 100644
--- a/swh/deposit/api/private/deposit_read.py
+++ b/swh/deposit/api/private/deposit_read.py
@@ -1,228 +1,235 @@
-# Copyright (C) 2017 The Software Heritage developers
+# Copyright (C) 2017 -2018 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import json
import os
import shutil
import tempfile
from contextlib import contextmanager
from django.http import FileResponse
from rest_framework import status
from swh.core import tarball
from swh.model import identifiers
from ...config import SWH_PERSON
from ..common import SWHGetDepositAPI, SWHPrivateAPIView
from ...models import Deposit, DepositRequest
@contextmanager
def aggregate_tarballs(extraction_dir, archive_paths):
"""Aggregate multiple tarballs into one and returns this new archive's
path.
Args:
extraction_dir (path): Path to use for the tarballs computation
archive_paths ([str]): Deposit's archive paths
Returns:
Tuple (directory to clean up, archive path (aggregated or not))
"""
if len(archive_paths) > 1: # need to rebuild one archive
# from multiple ones
os.makedirs(extraction_dir, 0o755, exist_ok=True)
dir_path = tempfile.mkdtemp(prefix='swh.deposit-',
dir=extraction_dir)
# root folder to build an aggregated tarball
aggregated_tarball_rootdir = os.path.join(dir_path, 'aggregate')
os.makedirs(aggregated_tarball_rootdir, 0o755, exist_ok=True)
# uncompress in a temporary location all archives
for archive_path in archive_paths:
tarball.uncompress(archive_path, aggregated_tarball_rootdir)
# Aggregate into one big tarball the multiple smaller ones
temp_tarpath = tarball.compress(
aggregated_tarball_rootdir + '.zip',
nature='zip',
dirpath_or_files=aggregated_tarball_rootdir)
# can already clean up temporary directory
shutil.rmtree(aggregated_tarball_rootdir)
try:
yield temp_tarpath
finally:
shutil.rmtree(dir_path)
else: # only 1 archive, no need to do fancy actions (and no cleanup step)
yield archive_paths[0]
class SWHDepositReadArchives(SWHGetDepositAPI, SWHPrivateAPIView):
"""Dedicated class to read a deposit's raw archives content.
Only GET is supported.
"""
ADDITIONAL_CONFIG = {
'extraction_dir': ('str', '/tmp/swh-deposit/archive/'),
}
def __init__(self):
super().__init__()
self.extraction_dir = self.config['extraction_dir']
if not os.path.exists(self.extraction_dir):
os.makedirs(self.extraction_dir)
def retrieve_archives(self, deposit_id):
"""Given a deposit identifier, returns its associated archives' path.
Yields:
path to deposited archives
"""
deposit = Deposit.objects.get(pk=deposit_id)
deposit_requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['archive']).order_by('id')
for deposit_request in deposit_requests:
yield deposit_request.archive.path
def process_get(self, req, collection_name, deposit_id):
"""Build a unique tarball from the multiple received and stream that
content to the client.
Args:
req (Request):
collection_name (str): Collection owning the deposit
deposit_id (id): Deposit concerned by the reading
Returns:
Tuple status, stream of content, content-type
"""
archive_paths = list(self.retrieve_archives(deposit_id))
with aggregate_tarballs(self.extraction_dir,
archive_paths) as path:
return FileResponse(open(path, 'rb'),
status=status.HTTP_200_OK,
content_type='application/octet-stream')
class SWHDepositReadMetadata(SWHGetDepositAPI, SWHPrivateAPIView):
"""Class in charge of aggregating metadata on a deposit.
"""
ADDITIONAL_CONFIG = {
'provider': ('dict', {
# 'provider_name': '', # those are not set since read from the
# 'provider_url': '', # deposit's client
'provider_type': 'deposit_client',
'metadata': {}
}),
'tool': ('dict', {
'name': 'swh-deposit',
'version': '0.0.1',
'configuration': {
'sword_version': '2'
}
})
}
def __init__(self):
super().__init__()
self.provider = self.config['provider']
self.tool = self.config['tool']
def _aggregate_metadata(self, deposit, metadata_requests):
"""Retrieve and aggregates metadata information.
"""
metadata = {}
for req in metadata_requests:
metadata.update(req.metadata)
return metadata
+ def _retrieve_url(self, deposit, metadata):
+ client_domain = deposit.client.domain
+ for field in metadata:
+ if 'url' in field:
+ if client_domain in metadata[field]:
+ return metadata[field]
+
def aggregate(self, deposit, requests):
"""Aggregate multiple data on deposit into one unified data dictionary.
Args:
deposit (Deposit): Deposit concerned by the data aggregation.
requests ([DepositRequest]): List of associated requests which
need aggregation.
Returns:
Dictionary of data representing the deposit to inject in swh.
"""
data = {}
# Retrieve tarballs/metadata information
metadata = self._aggregate_metadata(deposit, requests)
-
+ # create origin_url from metadata only after deposit_check validates it
+ origin_url = self._retrieve_url(deposit, metadata)
# Read information metadata
data['origin'] = {
'type': 'deposit',
- 'url': os.path.join(deposit.client.url.rstrip('/'),
- deposit.external_id),
+ 'url': origin_url
}
# revision
fullname = deposit.client.get_full_name()
author_committer = SWH_PERSON
# metadata provider
self.provider['provider_name'] = deposit.client.last_name
- self.provider['provider_url'] = deposit.client.url
+ self.provider['provider_url'] = deposit.client.provider_url
revision_type = 'tar'
revision_msg = '%s: Deposit %s in collection %s' % (
fullname, deposit.id, deposit.collection.name)
complete_date = identifiers.normalize_timestamp(deposit.complete_date)
data['revision'] = {
'synthetic': True,
'date': complete_date,
'committer_date': complete_date,
'author': author_committer,
'committer': author_committer,
'type': revision_type,
'message': revision_msg,
'metadata': metadata,
}
if deposit.parent:
parent_revision = deposit.parent.swh_id
data['revision']['parents'] = [parent_revision]
data['occurrence'] = {
'branch': 'master'
}
data['origin_metadata'] = {
'provider': self.provider,
'tool': self.tool,
'metadata': metadata
}
return data
def process_get(self, req, collection_name, deposit_id):
deposit = Deposit.objects.get(pk=deposit_id)
requests = DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata'])
data = self.aggregate(deposit, requests)
d = {}
if data:
d = json.dumps(data)
return status.HTTP_200_OK, d, 'application/json'
diff --git a/swh/deposit/migrations/0010_auto_20180110_0953.py b/swh/deposit/migrations/0010_auto_20180110_0953.py
new file mode 100644
index 00000000..799771ad
--- /dev/null
+++ b/swh/deposit/migrations/0010_auto_20180110_0953.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-10 09:53
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('deposit', '0009_deposit_parent'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='depositclient',
+ old_name='url',
+ new_name='provider_url',
+ ),
+ migrations.AddField(
+ model_name='depositclient',
+ name='domain',
+ field=models.TextField(default=''),
+ preserve_default=False,
+ ),
+ ]
diff --git a/swh/deposit/models.py b/swh/deposit/models.py
index 3270bef1..9785757a 100644
--- a/swh/deposit/models.py
+++ b/swh/deposit/models.py
@@ -1,207 +1,210 @@
# Copyright (C) 2017 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
# Generated from:
# cd swh_deposit && \
# python3 -m manage inspectdb
from django.contrib.postgres.fields import JSONField, ArrayField
from django.contrib.auth.models import User, UserManager
from django.db import models
from django.utils.timezone import now
from .config import DEPOSIT_STATUS_READY, DEPOSIT_STATUS_READY_FOR_CHECKS
from .config import DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_LOAD_SUCCESS
from .config import DEPOSIT_STATUS_LOAD_FAILURE
class Dbversion(models.Model):
"""Db version
"""
version = models.IntegerField(primary_key=True)
release = models.DateTimeField(default=now, null=True)
description = models.TextField(blank=True, null=True)
class Meta:
db_table = 'dbversion'
def __str__(self):
return str({
'version': self.version,
'release': self.release,
'description': self.description
})
"""Possible status"""
DEPOSIT_STATUS = [
(DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_PARTIAL),
('expired', 'expired'),
(DEPOSIT_STATUS_READY_FOR_CHECKS, DEPOSIT_STATUS_READY_FOR_CHECKS),
(DEPOSIT_STATUS_READY, DEPOSIT_STATUS_READY),
('rejected', 'rejected'),
('loading', 'loading'),
(DEPOSIT_STATUS_LOAD_SUCCESS, DEPOSIT_STATUS_LOAD_SUCCESS),
(DEPOSIT_STATUS_LOAD_FAILURE, DEPOSIT_STATUS_LOAD_FAILURE),
]
"""Possible status and the detailed meaning."""
DEPOSIT_STATUS_DETAIL = {
DEPOSIT_STATUS_PARTIAL: 'Deposit is partially received. To finalize it, '
'In-Progress header should be false',
'expired': 'Deposit has been there too long and is now '
'deemed ready to be garbage collected',
DEPOSIT_STATUS_READY_FOR_CHECKS: 'Deposit is ready for additional checks '
'(tarball ok, metadata, etc...)',
DEPOSIT_STATUS_READY: 'Deposit is fully received, checked, and '
'ready for loading',
'rejected': 'Deposit failed the checks',
'loading': "Loading is ongoing on swh's side",
DEPOSIT_STATUS_LOAD_SUCCESS: 'The deposit has been successfully '
'loaded into the Software Heritage archive',
DEPOSIT_STATUS_LOAD_FAILURE: 'The deposit loading into the '
'Software Heritage archive failed',
}
class DepositClient(User):
"""Deposit client
"""
collections = ArrayField(models.IntegerField(), null=True)
objects = UserManager()
- url = models.TextField(null=False)
+ provider_url = models.TextField(null=False)
+ domain = models.TextField(null=False)
class Meta:
db_table = 'deposit_client'
def __str__(self):
return str({
'id': self.id,
'collections': self.collections,
'username': super().username,
+ 'domain': self.domain,
+ 'provider_url': self.provider_url,
})
class Deposit(models.Model):
"""Deposit reception table
"""
id = models.BigAutoField(primary_key=True)
# First deposit reception date
reception_date = models.DateTimeField(auto_now_add=True)
# Date when the deposit is deemed complete and ready for loading
complete_date = models.DateTimeField(null=True)
# collection concerned by the deposit
collection = models.ForeignKey(
'DepositCollection', models.DO_NOTHING)
# Deposit's external identifier
external_id = models.TextField()
# Deposit client
client = models.ForeignKey('DepositClient', models.DO_NOTHING)
# SWH's loading result identifier
swh_id = models.TextField(blank=True, null=True)
# Deposit's status regarding loading
status = models.TextField(
choices=DEPOSIT_STATUS,
default=DEPOSIT_STATUS_PARTIAL)
# deposit can have one parent
parent = models.ForeignKey('self', null=True)
class Meta:
db_table = 'deposit'
def __str__(self):
return str({
'id': self.id,
'reception_date': self.reception_date,
'collection': self.collection.name,
'external_id': self.external_id,
'client': self.client.username,
'status': self.status
})
class DepositRequestType(models.Model):
"""Deposit request type made by clients (either archive or metadata)
"""
id = models.BigAutoField(primary_key=True)
name = models.TextField()
class Meta:
db_table = 'deposit_request_type'
def __str__(self):
return str({'id': self.id, 'name': self.name})
def client_directory_path(instance, filename):
"""Callable to upload archive in MEDIA_ROOT/user_/
Args:
instance (DepositRequest): DepositRequest concerned by the upload
filename (str): Filename of the uploaded file
Returns:
A path to be prefixed by the MEDIA_ROOT to access physically
to the file uploaded.
"""
return 'client_{0}/{1}'.format(instance.deposit.client.id, filename)
class DepositRequest(models.Model):
"""Deposit request associated to one deposit.
"""
id = models.BigAutoField(primary_key=True)
# Deposit concerned by the request
deposit = models.ForeignKey(Deposit, models.DO_NOTHING)
date = models.DateTimeField(auto_now_add=True)
# Deposit request information on the data to inject
# this can be null when type is 'archive'
metadata = JSONField(null=True)
# this can be null when type is 'metadata'
archive = models.FileField(null=True, upload_to=client_directory_path)
type = models.ForeignKey(
'DepositRequestType', models.DO_NOTHING)
class Meta:
db_table = 'deposit_request'
def __str__(self):
meta = None
if self.metadata:
from json import dumps
meta = dumps(self.metadata)
archive_name = None
if self.archive:
archive_name = self.archive.name
return str({
'id': self.id,
'deposit': self.deposit,
'metadata': meta,
'archive': archive_name
})
class DepositCollection(models.Model):
id = models.BigAutoField(primary_key=True)
# Human readable name for the collection type e.g HAL, arXiv, etc...
name = models.TextField()
class Meta:
db_table = 'deposit_collection'
def __str__(self):
return str({'id': self.id, 'name': self.name})
diff --git a/swh/deposit/static/css/bootstrap-responsive.min.css b/swh/deposit/static/css/bootstrap-responsive.min.css
new file mode 100644
index 00000000..96a435be
--- /dev/null
+++ b/swh/deposit/static/css/bootstrap-responsive.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Responsive v2.3.2
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
diff --git a/swh/deposit/static/css/style.css b/swh/deposit/static/css/style.css
new file mode 100644
index 00000000..c74ea14b
--- /dev/null
+++ b/swh/deposit/static/css/style.css
@@ -0,0 +1,501 @@
+/*
+version: 0.1
+date: 21/09/15
+author: swh
+email: swh
+website: softwareheritage.org
+version history: /style.css
+*/
+
+@import url(https://fonts.googleapis.com/css?family=Alegreya:400,400italic,700,700italic);
+@import url(https://fonts.googleapis.com/css?family=Alegreya+Sans:400,400italic,500,500italic,700,700italic,100,300,100italic,300italic);
+
+html {
+ height: 100%;
+}
+
+body {
+ font-family: 'Alegreya Sans', sans-serif;
+ font-size: 1.7rem;
+ line-height: 1.5;
+ color: rgba(0, 0, 0, 0.55);
+ padding-bottom: 120px;
+ min-height: 100%;
+ margin: 0;
+ position: relative;
+}
+
+.heading {
+ font-family: 'Alegreya', serif;
+}
+
+.shell, .text {
+ font-size: 0.7em;
+}
+
+.logo img {
+ max-height: 40px;
+}
+.logo .navbar-brand {
+ padding: 5px;
+}
+.logo .sitename {
+ padding: 15px 5px;
+}
+
+.jumbotron {
+ padding: 0;
+ background-color: rgba(0, 0, 0, 0);
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 10;
+}
+
+#swh-navbar-collapse {
+ border-top-style: none;
+ border-left-style: none;
+ border-right-style: none;
+ border-bottom: 5px solid;
+ border-image: linear-gradient(to right, rgb(226, 0, 38) 0%, rgb(254, 205, 27) 100%) 1 1 1 1;
+ width: 100%;
+ padding: 5px;
+}
+
+.nav-horizontal {
+ float: right;
+}
+
+h3[id], h4[id], a[id] { /* avoid in-page links covered by navbar */
+ padding-top: 80px;
+ margin-top: -70px;
+}
+
+h1, h2, h3, h4 {
+ margin: 0;
+ color: #e20026;
+ padding-bottom: 10px;
+}
+h1 { font-size: 1.8em; }
+h2 { font-size: 1.2em; }
+h3 { font-size: 1.1em; }
+
+a {
+ color: rgba(0, 0, 0, 0.75);
+ border-bottom-style: dotted;
+ border-bottom-width: 1px;
+ border-bottom-color: rgb(91, 94, 111);
+}
+
+a:hover {
+ color: black;
+}
+
+ul.dropdown-menu a,
+.navbar-header a,
+ul.navbar-nav a { /* No decoration on links in dropdown menu */
+ border-bottom-style: none;
+ color: #323232;
+ font-weight: 700;
+}
+
+.navbar-header a:hover,
+ul.navbar-nav a:hover {
+ color: #8f8f8f;
+}
+
+.sitename .first-word, .sitename .second-word {
+ color: rgba(0, 0, 0, 0.75);
+ font-weight: normal;
+ font-size: 1.8rem;
+}
+
+.sitename .first-word {
+ font-family: 'Alegreya Sans', sans-serif;
+}
+
+.sitename .second-word {
+ font-family: 'Alegreya', serif;
+}
+
+ul.dropdown-menu > li,
+ul.dropdown-menu > li > ul > li { /* No decoration on bullet points in dropdown menu */
+ list-style-type: none;
+}
+
+.page {
+ margin: 2em auto;
+ width: 35em;
+ border: 5px solid #ccc;
+ padding: 0.8em;
+ background: white;
+}
+.entries {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+.entries li {
+ margin: 0.8em 1.2em;
+}
+.entries li h2 {
+ margin-left: -1em;
+}
+.add-entry {
+ font-size: 0.9em;
+ border-bottom: 1px solid #ccc;
+}
+.add-entry dl {
+ font-weight: bold;
+}
+.metanav {
+ text-align: right;
+ font-size: 0.8em;
+ padding: 0.3em;
+ margin-bottom: 1em;
+ background: #fafafa;
+}
+.flash {
+ background: #cee5F5;
+ padding: 0.5em;
+ border: 1px solid #aacbe2;
+}
+.error {
+ background: #f0d6d6;
+ padding: 0.5em;
+}
+
+.file-found {
+ color: #23BA49;
+}
+.file-notfound {
+ color: #FF4747;
+}
+
+/* Bootstrap custom styling to correctly render multiple
+ * form-controls in an input-group:
+ * github.com/twbs/bootstrap/issues/12732 */
+
+.input-group-field {
+ display: table-cell;
+ vertical-align: middle;
+ border-radius:4px;
+ min-width:1%;
+ white-space: nowrap;
+}
+
+.input-group-field .form-control {
+ border-radius: inherit !important;
+}
+
+.input-group-field:not(:first-child):not(:last-child) {
+ border-radius:0;
+}
+
+.input-group-field:not(:first-child):not(:last-child) .form-control {
+ border-left-width: 0;
+ border-right-width: 0;
+}
+
+.input-group-field:last-child {
+ border-top-left-radius:0;
+ border-bottom-left-radius:0;
+}
+
+.input-group > span:not(:last-child) > button {
+ border-radius: 0;
+}
+
+.multi-input-group > .input-group-btn {
+ vertical-align: bottom;
+ padding: 0;
+}
+
+.dataTables_filter {
+ margin-top: 15px;
+}
+
+.dataTables_filter input {
+ width: 70%;
+ float: right;
+}
+
+tr.api-doc-route-upcoming > td, tr.api-doc-route-upcoming > td > a {
+ font-size: 90%;
+}
+
+tr.api-doc-route-deprecated > td, tr.api-doc-route-deprecated > td > a {
+ color: red;
+}
+
+#back-to-top {
+ display: initial;
+ position: fixed;
+ bottom: 30px;
+ right: 30px;
+ z-index: 10;
+}
+
+#back-to-top a img {
+ display: block;
+ width: 32px;
+ height: 32px;
+ background-size: 32px 32px;
+ text-indent: -999px;
+ overflow: hidden;
+}
+
+.table > thead > tr > th {
+ border-bottom: 1px solid #e20026;
+}
+
+.table > tbody > tr > td {
+ border-style: none;
+}
+
+pre {
+ background-color: #f5f5f5;
+}
+
+.dataTables_wrapper {
+ position: static;
+}
+
+/* breadcrumbs */
+
+.bread-crumbs{
+ display: inline-block;
+ overflow: hidden;
+ color: rgba(0, 0, 0, 0.55);
+}
+
+bread-crumbs ul {
+ list-style-type: none;
+}
+
+.bread-crumbs li {
+ float: left;
+ list-style-type: none;
+}
+
+.bread-crumbs a {
+ color: rgba(0, 0, 0, 0.75);
+ border-bottom-style: none;
+}
+
+.bread-crumbs a:hover {
+ color: rgba(0, 0, 0, 0.85);
+ text-decoration: underline;
+}
+
+.title-small .bread-crumbs{
+ margin: -30px 0 25px;
+}
+
+#footer {
+ background-color: #262626;
+ color: hsl(0, 0%, 100%);
+ font-size: 1.2rem;
+ text-align: center;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+#footer a,
+#footer a:visited {
+ color: hsl(0, 0%, 100%);
+}
+#footer a:hover {
+ text-decoration: underline;
+}
+
+.highlightjs pre {
+ background-color: transparent;
+ border-radius: 0px;
+ border-color: transparent;
+}
+
+.hljs {
+ background-color: transparent;
+ white-space: pre;
+}
+
+.scrollable-menu {
+ max-height: 180px;
+ overflow-x: hidden;
+}
+
+.swh-browse-top-navigation {
+ border-bottom: 1px solid #ddd;
+ min-height: 42px;
+ padding: 4px 5px 0px 5px;
+}
+
+.swh-browse-bread-crumbs {
+ font-size: inherit;
+ vertical-align: text-top;
+ margin-bottom: 1px;
+}
+
+.swh-browse-bread-crumbs li:nth-child(n+2)::before {
+ content: "";
+ display: inline-block;
+ margin: 0 2px;
+}
+
+.swh-metadata-table-row {
+ border-top: 1px solid #ddd !important;
+}
+
+/* for block of numbers */
+td.hljs-ln-numbers {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ text-align: center;
+ color: #ccc;
+ border-right: 1px solid #CCC;
+ vertical-align: top;
+ padding-right: 5px;
+
+ /* your custom style here */
+}
+
+/* for block of code */
+td.hljs-ln-code {
+ padding-left: 10px;
+}
+
+.btn-swh {
+ color: #6C6C6C;
+ background-color: #EAEAEA;
+ border-color: #ddd;
+ background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);
+ background-repeat: repeat-x;
+ outline: none;
+}
+
+.btn-swh:hover,
+.btn-swh:focus,
+.btn-swh:active,
+.btn-swh.active,
+.open .dropdown-toggle.btn-swh {
+ background-color: #e6ebf1;
+ background-image: linear-gradient(to bottom,#f1f1f1 0,#e6e6e6 100%);
+ border-color: rgb(197, 197, 197);
+}
+
+.btn-swh.disabled,
+.btn-swh[disabled],
+fieldset[disabled] .btn-swh,
+.btn-swh.disabled:hover,
+.btn-swh[disabled]:hover,
+fieldset[disabled] .btn-swh:hover,
+.btn-swh.disabled:focus,
+.btn-swh[disabled]:focus,
+fieldset[disabled] .btn-swh:focus,
+.btn-swh.disabled:active,
+.btn-swh[disabled]:active,
+fieldset[disabled] .btn-swh:active,
+.btn-swh.disabled.active,
+.btn-swh[disabled].active,
+fieldset[disabled] .btn-swh.active {
+ background-color: #EAEAEA;
+ border-color: #EAEAEA;
+}
+
+.btn-swh .badge {
+ color: #EAEAEA;
+ background-color: #6C6C6C;
+}
+
+.swh-http-error {
+ margin: 0 auto;
+ text-align: center;
+}
+
+.swh-http-error-head {
+ color: #2d353c;
+ font-size: 30px;
+}
+
+.swh-http-error-code {
+ bottom: 60%;
+ color: #2d353c;
+ font-size: 96px;
+ line-height: 80px;
+ margin-bottom: 10px!important;
+}
+
+.swh-http-error-desc {
+ font-size: 12px;
+ color: #647788;
+}
+
+.swh-http-error-desc pre {
+ text-align: left;
+}
+
+.swh-table {
+ border-bottom: none !important;
+}
+
+.swh-counter {
+ font-size: 150%;
+}
+.swh-loading {
+ display : none;
+}
+
+.swh-loading.show {
+ display:inline-block;
+ position: fixed;
+ background: white;
+ border: 1px solid black;
+ top: 50%;
+ left: 50%;
+ margin: -50px 0px 0px -50px;
+ text-align: center;
+ z-index:100;
+}
+
+.swh-readme a {
+ outline: none;
+ border: none;
+}
+
+.swh-readme table {
+ border-collapse: collapse;
+}
+
+.swh-readme table,
+.swh-readme table th,
+.swh-readme table td {
+ padding: 6px 13px;
+ border: 1px solid #dfe2e5;
+}
+
+.swh-readme table tr:nth-child(even) {
+ background-color: #f2f2f2;
+}
+
+.swh-web-app-link:hover {
+ background-color: #efeff2;
+}
+
+.swh-web-app-link a {
+ text-decoration: none;
+ outline: none;
+ border: none;
+}
+
+.pager a {
+ outline: none;
+}
diff --git a/swh/deposit/static/img/icons/swh-logo-32x32.png b/swh/deposit/static/img/icons/swh-logo-32x32.png
new file mode 100644
index 00000000..0207798d
Binary files /dev/null and b/swh/deposit/static/img/icons/swh-logo-32x32.png differ
diff --git a/swh/deposit/static/img/icons/swh-logo-deposit-180x180.png b/swh/deposit/static/img/icons/swh-logo-deposit-180x180.png
new file mode 100644
index 00000000..545d6138
Binary files /dev/null and b/swh/deposit/static/img/icons/swh-logo-deposit-180x180.png differ
diff --git a/swh/deposit/static/img/icons/swh-logo-deposit-192x192.png b/swh/deposit/static/img/icons/swh-logo-deposit-192x192.png
new file mode 100644
index 00000000..ad59eedc
Binary files /dev/null and b/swh/deposit/static/img/icons/swh-logo-deposit-192x192.png differ
diff --git a/swh/deposit/static/img/icons/swh-logo-deposit-270x270.png b/swh/deposit/static/img/icons/swh-logo-deposit-270x270.png
new file mode 100644
index 00000000..8ba4c216
Binary files /dev/null and b/swh/deposit/static/img/icons/swh-logo-deposit-270x270.png differ
diff --git a/swh/deposit/static/img/swh-logo-deposit.png b/swh/deposit/static/img/swh-logo-deposit.png
new file mode 100644
index 00000000..c183277f
Binary files /dev/null and b/swh/deposit/static/img/swh-logo-deposit.png differ
diff --git a/swh/deposit/static/img/swh-logo-deposit.svg b/swh/deposit/static/img/swh-logo-deposit.svg
new file mode 100644
index 00000000..74c14ba4
--- /dev/null
+++ b/swh/deposit/static/img/swh-logo-deposit.svg
@@ -0,0 +1,160 @@
+
+
+
+
\ No newline at end of file
diff --git a/swh/deposit/templates/homepage.html b/swh/deposit/templates/homepage.html
new file mode 100644
index 00000000..764f789c
--- /dev/null
+++ b/swh/deposit/templates/homepage.html
@@ -0,0 +1,44 @@
+{% extends "layout.html" %}
+{% load static %}
+{% block title %}The Software Heritage Deposit{% endblock %}
+
+{% block content %}
+
Welcome to the Software Heritage deposit
+
+
Overview
+
+
+The long term goal of the Software Heritage initiative is
+to collect all publicly available software in source
+code form together with its development history, replicate it
+massively to ensure its preservation,
+and share it with everyone who needs it.
+
+
+
What's the deposit?
+
+This is Software Heritage's
+SWORD
+2.0 Server implementation.
+
+**S.W.O.R.D** (**S**imple **W**eb-Service **O**ffering **R**epository
+**D**eposit) is an interoperability standard for digital file deposit.
+
+This implementation will permit interaction between a client (a
+repository, e.g. hal) and a server
+(SWH repository) to
+permit deposits of software source code archives with associated
+metadata.
+
+
+