api.py 11 KB
Newer Older
André Anjos's avatar
André Anjos committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

###############################################################################
#                                                                             #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/           #
# Contact: beat.support@idiap.ch                                              #
#                                                                             #
# This file is part of the beat.web module of the BEAT platform.              #
#                                                                             #
# Commercial License Usage                                                    #
# Licensees holding valid commercial BEAT licenses may use this file in       #
# accordance with the terms contained in a written agreement between you      #
# and Idiap. For further information contact tto@idiap.ch                     #
#                                                                             #
# Alternatively, this file may be used under the terms of the GNU Affero      #
# Public License version 3 as published by the Free Software and appearing    #
# in the file LICENSE.AGPL included in the packaging of this file.            #
# The BEAT platform is distributed in the hope that it will be useful, but    #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  #
# or FITNESS FOR A PARTICULAR PURPOSE.                                        #
#                                                                             #
# You should have received a copy of the GNU Affero Public License along      #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/.           #
#                                                                             #
###############################################################################

28
import json
Samuel GAIST's avatar
Samuel GAIST committed
29
import logging
30
import os
31
from pathlib import PurePath
André Anjos's avatar
André Anjos committed
32

Samuel GAIST's avatar
Samuel GAIST committed
33
from rest_framework import exceptions as drf_exceptions
34
from rest_framework import permissions as drf_permissions
André Anjos's avatar
André Anjos committed
35
from rest_framework import views
36
from rest_framework.response import Response
André Anjos's avatar
André Anjos committed
37 38

from ..common import is_true
39
from ..common import permissions as beat_permissions
André Anjos's avatar
André Anjos committed
40 41
from ..common.api import ListCreateBaseView
from ..common.utils import ensure_html
Samuel GAIST's avatar
Samuel GAIST committed
42
from ..dataformats.serializers import ReferencedDataFormatSerializer
43 44 45 46
from .models import Database
from .models import DatabaseSetTemplate
from .serializers import DatabaseCreationSerializer
from .serializers import DatabaseSerializer
47

48 49
logger = logging.getLogger(__name__)

André Anjos's avatar
André Anjos committed
50

51 52
# ----------------------------------------------------------

André Anjos's avatar
André Anjos committed
53

Samuel GAIST's avatar
Samuel GAIST committed
54
def database_to_json(database, request_user, fields_to_return):
André Anjos's avatar
André Anjos committed
55 56 57 58

    # Prepare the response
    result = {}

59 60
    if "name" in fields_to_return:
        result["name"] = database.fullname()
André Anjos's avatar
André Anjos committed
61

62 63
    if "version" in fields_to_return:
        result["version"] = database.version
André Anjos's avatar
André Anjos committed
64

65
    if "last_version" in fields_to_return:
Samuel GAIST's avatar
Samuel GAIST committed
66 67 68 69 70 71 72 73
        latest = (
            Database.objects.for_user(request_user, True)
            .filter(name=database.name)
            .order_by("-version")[:1]
            .first()
        )

        result["last_version"] = database.version == latest.version
André Anjos's avatar
André Anjos committed
74

75 76
    if "short_description" in fields_to_return:
        result["short_description"] = database.short_description
André Anjos's avatar
André Anjos committed
77

78 79
    if "description" in fields_to_return:
        result["description"] = database.description
André Anjos's avatar
André Anjos committed
80

81 82 83 84 85 86
    if "previous_version" in fields_to_return:
        result["previous_version"] = (
            database.previous_version.fullname()
            if database.previous_version is not None
            else None
        )
André Anjos's avatar
André Anjos committed
87

88 89
    if "creation_date" in fields_to_return:
        result["creation_date"] = database.creation_date.isoformat(" ")
André Anjos's avatar
André Anjos committed
90

91 92
    if "hash" in fields_to_return:
        result["hash"] = database.hash
André Anjos's avatar
André Anjos committed
93

94 95
    if "accessibility" in fields_to_return:
        result["accessibility"] = database.accessibility_for(request_user)
André Anjos's avatar
André Anjos committed
96 97 98 99

    return result


100
def clean_paths(declaration):
101
    pseudo_path = "/path_to_db_folder"
102 103 104 105 106 107 108 109 110 111 112 113 114 115

    def _clean_path(item):
        parameters = item.get("parameters", {})

        if "annotations" not in parameters:
            return

        ppath = PurePath(parameters["annotations"])
        if not ppath.is_absolute():
            return

        cleaned_folder = ppath.parts[-2:]
        parameters["annotations"] = os.path.join(pseudo_path, *cleaned_folder)

116
    root_folder = declaration["root_folder"]
117
    cleaned_folder = os.path.basename(os.path.normpath(root_folder))
118 119
    declaration["root_folder"] = os.path.join(pseudo_path, cleaned_folder)
    for protocol in declaration["protocols"]:
120 121 122 123 124 125 126 127
        # sets is a key only available in the V1 version of databases
        if "sets" in protocol:
            for set_ in protocol["sets"]:
                _clean_path(set_)
        else:
            for view in protocol["views"].values():
                _clean_path(view)

128
    return declaration
129

130 131

# ----------------------------------------------------------
André Anjos's avatar
André Anjos committed
132 133


134
class ListCreateDatabasesView(ListCreateBaseView):
André Anjos's avatar
André Anjos committed
135 136 137 138 139
    """
    Read/Write end point that list the database available
    to a user and allows the creation of new databases only to
    platform administrator
    """
140

André Anjos's avatar
André Anjos committed
141
    model = Database
142
    permission_classes = [beat_permissions.IsAdminOrReadOnly]
André Anjos's avatar
André Anjos committed
143 144
    serializer_class = DatabaseSerializer
    writing_serializer_class = DatabaseCreationSerializer
145
    namespace = "api_databases"
André Anjos's avatar
André Anjos committed
146 147 148 149 150 151 152

    def get_queryset(self):
        user = self.request.user
        return self.model.objects.for_user(user, True)

    def get(self, request, *args, **kwargs):
        fields_to_return = self.get_serializer_fields(request)
153 154 155
        limit_to_latest_versions = is_true(
            request.query_params.get("latest_versions", False)
        )
André Anjos's avatar
André Anjos committed
156

157
        all_databases = self.get_queryset().order_by("name")
André Anjos's avatar
André Anjos committed
158 159 160

        if limit_to_latest_versions:
            all_databases = self.model.filter_latest_versions(all_databases)
161
            all_databases.sort(key=lambda x: x.fullname())
André Anjos's avatar
André Anjos committed
162

163 164 165
        serializer = self.get_serializer(
            all_databases, many=True, fields=fields_to_return
        )
André Anjos's avatar
André Anjos committed
166 167 168
        return Response(serializer.data)


169
# ----------------------------------------------------------
André Anjos's avatar
André Anjos committed
170 171 172 173 174 175


class ListTemplatesView(views.APIView):
    """
    List all templates available
    """
176

177
    permission_classes = [drf_permissions.AllowAny]
André Anjos's avatar
André Anjos committed
178 179 180 181 182

    def get(self, request):
        result = {}

        # Retrieve all the protocols available to user
183 184 185
        databases = Database.objects.for_user(request.user, True)
        databases = Database.filter_latest_versions(databases)

186 187 188 189 190 191
        for set_template in (
            DatabaseSetTemplate.objects.filter(sets__protocol__database__in=databases)
            .distinct()
            .order_by("name")
        ):
            (db_template, dataset) = set_template.name.split("__")
André Anjos's avatar
André Anjos committed
192

193
            if db_template not in result:
194
                result[db_template] = {"templates": {}, "sets": []}
André Anjos's avatar
André Anjos committed
195

196 197 198
            result[db_template]["templates"][dataset] = map(
                lambda x: x.name, set_template.outputs.order_by("name")
            )
André Anjos's avatar
André Anjos committed
199 200 201 202 203

            known_sets = []

            for db_set in set_template.sets.iterator():
                if db_set.name not in known_sets:
204 205 206
                    result[db_template]["sets"].append(
                        {"name": db_set.name, "template": dataset, "id": db_set.id}
                    )
André Anjos's avatar
André Anjos committed
207 208 209
                    known_sets.append(db_set.name)

        for name, entry in result.items():
210 211 212 213
            entry["sets"].sort(key=lambda x: x["id"])
            result[name]["sets"] = map(
                lambda x: {"name": x["name"], "template": x["template"]}, entry["sets"]
            )
André Anjos's avatar
André Anjos committed
214 215 216 217

        return Response(result)


218
# ----------------------------------------------------------
André Anjos's avatar
André Anjos committed
219 220 221 222 223 224 225


class RetrieveDatabaseView(views.APIView):
    """
    Returns the given database details
    """

Samuel GAIST's avatar
Samuel GAIST committed
226
    model = Database
227
    permission_classes = [drf_permissions.AllowAny]
André Anjos's avatar
André Anjos committed
228

Samuel GAIST's avatar
Samuel GAIST committed
229 230 231 232
    def get_object(self):
        version = self.kwargs["version"]
        database_name = self.kwargs["database_name"]
        user = self.request.user
André Anjos's avatar
André Anjos committed
233
        try:
Samuel GAIST's avatar
Samuel GAIST committed
234 235 236 237 238 239
            obj = self.model.objects.for_user(user, True).get(
                name__iexact=database_name, version=version
            )
        except self.model.DoesNotExist:
            raise drf_exceptions.NotFound()
        return obj
André Anjos's avatar
André Anjos committed
240

Samuel GAIST's avatar
Samuel GAIST committed
241 242 243 244
    def get(self, request, database_name, version):
        # Retrieve the database
        database = self.get_object()
        self.check_object_permissions(request, database)
André Anjos's avatar
André Anjos committed
245 246

        # Process the query string
247 248
        if "fields" in request.GET:
            fields_to_return = request.GET["fields"].split(",")
André Anjos's avatar
André Anjos committed
249
        else:
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
            fields_to_return = [
                "name",
                "version",
                "last_version",
                "short_description",
                "description",
                "fork_of",
                "previous_version",
                "is_owner",
                "accessibility",
                "sharing",
                "opensource",
                "hash",
                "creation_date",
                "declaration",
                "code",
            ]
André Anjos's avatar
André Anjos committed
267

Samuel GAIST's avatar
Samuel GAIST committed
268 269
        # Prepare the response
        result = database_to_json(database, request.user, fields_to_return)
André Anjos's avatar
André Anjos committed
270

Samuel GAIST's avatar
Samuel GAIST committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
        # Retrieve the code
        if "declaration" in fields_to_return:
            declaration = database.declaration
            cleaned_declaration = clean_paths(declaration)
            result["declaration"] = json.dumps(cleaned_declaration)

        # Retrieve the source code
        if "code" in fields_to_return:
            result["code"] = database.source_code

        # Retrieve the description in HTML format
        if "html_description" in fields_to_return:
            description = database.description
            if len(description) > 0:
                result["html_description"] = ensure_html(description)
            else:
                result["html_description"] = ""

        # Retrieve the referenced data formats
        if "referenced_dataformats" in fields_to_return:
            dataformats = database.all_referenced_dataformats()

            referenced_dataformats = []
            for dataformat in dataformats:
                (has_access, accessibility) = dataformat.accessibility_for(request.user)
                if has_access:
                    referenced_dataformats.append(dataformat)
            serializer = ReferencedDataFormatSerializer(referenced_dataformats)
            result["referenced_dataformats"] = serializer.data

        # Retrieve the needed data formats
        if "needed_dataformats" in fields_to_return:
            dataformats = database.all_needed_dataformats()

            needed_dataformats = []
            for dataformat in dataformats:
                (has_access, accessibility) = dataformat.accessibility_for(request.user)
                if has_access:
                    needed_dataformats.append(dataformat)
            serializer = ReferencedDataFormatSerializer(needed_dataformats)
            result["needed_dataformats"] = serializer.data

        # Return the result
        return Response(result)