Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ jobs:
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.6-8b5b400-linux64.zip"
mkdir -p venv/lib/python3.11/site-packages
unzip -d venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip
# BCF export - --no-deps so pip cannot pull PyPI ifcopenshell over the pinned build
pip install --no-deps bcf-client==0.8.5
pip install xsdata==26.2

- name: Check Django config
run: |
Expand All @@ -116,4 +119,5 @@ jobs:
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_schema_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_status_combine --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_bcf_export --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
4 changes: 4 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ jobs:
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.6-8b5b400-linux64.zip"
mkdir -p venv/lib/python3.11/site-packages
unzip -d venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip
# BCF export - --no-deps so pip cannot pull PyPI ifcopenshell over the pinned build
pip install --no-deps bcf-client==0.8.5
pip install xsdata==26.2

- name: Check Django config
run: |
Expand All @@ -117,6 +120,7 @@ jobs:
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_schema_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3
MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_bcf_export --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3

deploy:

Expand Down
3 changes: 2 additions & 1 deletion backend/apps/ifc_validation/api/v1/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import re_path

from .views import ValidationRequestListAPIView, ValidationRequestDetailAPIView
from .views import ValidationRequestListAPIView, ValidationRequestDetailAPIView, ValidationRequestBcfAPIView
from .views import ValidationTaskListAPIView, ValidationTaskDetailAPIView
from .views import ValidationOutcomeListAPIView, ValidationOutcomeDetailAPIView
from .views import ModelListAPIView, ModelDetailAPIView
Expand All @@ -11,6 +11,7 @@
# REST API
# using re_path to make trailing slashes optional
re_path(r'validationrequest/?$', ValidationRequestListAPIView.as_view()),
re_path(r'validationrequest/(?P<id>[\w-]+)/bcf/?$', ValidationRequestBcfAPIView.as_view()),
re_path(r'validationrequest/(?P<id>[\w-]+)/?$', ValidationRequestDetailAPIView.as_view()),
re_path(r'validationtask/?$', ValidationTaskListAPIView.as_view()),
re_path(r'validationtask/(?P<id>[\w-]+)/?$', ValidationTaskDetailAPIView.as_view()),
Expand Down
54 changes: 54 additions & 0 deletions backend/apps/ifc_validation/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import traceback
import sys
import logging
import os
import re

from django.db import transaction
from django.http import HttpResponse
from django.utils.http import content_disposition_header
from core.utils import get_client_ip_address
from core.settings import MAX_FILES_PER_UPLOAD

Expand All @@ -18,6 +21,7 @@
from rest_framework.throttling import ScopedRateThrottle
from rest_framework.decorators import throttle_classes
from drf_spectacular.utils import extend_schema
from drf_spectacular.types import OpenApiTypes

from apps.ifc_validation_models.models import set_user_context
from apps.ifc_validation_models.models import ValidationRequest
Expand Down Expand Up @@ -94,6 +98,56 @@ def delete(self, request, id, *args, **kwargs):
return Response(data, status=status.HTTP_404_NOT_FOUND)


@extend_schema(tags=['Validation Request'])
class ValidationRequestBcfAPIView(APIView):

queryset = ValidationRequest.objects.all()
permission_classes = [IsAuthenticated]
serializer_class = ValidationRequestSerializer
throttle_classes = [UserRateThrottle]

@extend_schema(
operation_id='validationrequest_bcf',
responses={
(200, 'application/zip'): OpenApiTypes.BINARY,
404: None,
501: None,
}
)
def get(self, request, id, *args, **kwargs):

"""
Downloads the Validation Outcomes of a Validation Request as a BCF 2.1 file (experimental).
Only outcomes with severity Error or Warning are included, capped per rule like the report UI.
"""

logger.info('API request v%s - User IP: %s Request Method: %s Request URL: %s Content-Length: %s' % (self.request.version, get_client_ip_address(request), request.method, request.path, request.META.get('CONTENT_LENGTH')))

instance = ValidationRequest.objects.filter(created_by__id=request.user.id, deleted=False, id=ValidationRequest.to_private_id(id)).first()
if not instance:
data = {'detail': f"Validation Request with public_id={id} does not exist for user with id={request.user.id}."}
return Response(data, status=status.HTTP_404_NOT_FOUND)

try:
import bcf # noqa: F401 - fail early with a clear message when not installed
from apps.ifc_validation.bcf_export import generate_bcf_download
except ImportError:
data = {'detail': "BCF export is not available on this server."}
return Response(data, status=status.HTTP_501_NOT_IMPLEMENTED)

try:
content, _ = generate_bcf_download(instance)
except Exception:
logger.exception(f'BCF generation failed for request {id}')
data = {'detail': "BCF generation failed."}
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

file_name = os.path.splitext(instance.file_name)[0]
response = HttpResponse(content, content_type='application/zip')
response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=f'{file_name}.bcf')
return response


@extend_schema(tags=['Validation Request'])
class ValidationRequestListAPIView(ListCreateAPIView):
permission_classes = [IsAuthenticated]
Expand Down
Loading
Loading