Skip to content

Commit f52447b

Browse files
committed
Merge branch 'development-0.6' of https://github.com/buildingSMART/validate into gh-pages
2 parents e98a18e + c8f8d25 commit f52447b

37 files changed

+2485
-522
lines changed

.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ PUBLIC_URL = https://dev.validate.buildingsmart.org
77
# Django
88
MEDIA_ROOT = /files_storage
99
DJANGO_DB = postgresql
10+
DJANGO_SECRET_KEY = django-insecure-um7-^+&jbk_=80*xcc9uf4nh$4koida7)ja&6!vb*$8@n288jk
1011
DJANGO_ALLOWED_HOSTS = dev.validate.buildingsmart.org
11-
DJANGO_CSRF_TRUSTED_ORIGINS = https://dev.validate.buildingsmart.org https://authentication.buildingsmart.org
12+
DJANGO_TRUSTED_ORIGINS = https://dev.validate.buildingsmart.org https://authentication.buildingsmart.org
1213
DJANGO_LOG_LEVEL = INFO
1314

1415
# DB

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
*(Work In Progress - dev-v0.6-alpha)*
22

3+
# Software Infrastructure
4+
5+
![image](https://github.com/buildingSMART/validate/assets/155643707/5286c847-cf2a-478a-8940-fcdbd6fffeea)
6+
37

48
# Application Structure
59

@@ -58,14 +62,13 @@ or
5862
docker compose up
5963
```
6064

61-
3. This pulls Docker-hub images, builds and spins up **six** different services:
65+
3. This pulls Docker-hub images, builds and spins up **five** different services:
6266

6367
```
6468
db - PostgreSQL database
6569
redis - Redis instance
6670
backend - Django Admin + API's
6771
worker - Celery worker
68-
flower - Celery flower dashboard
6972
frontend - React UI
7073
```
7174

@@ -89,7 +92,6 @@ exit
8992
- Django Admin UI: http://localhost/admin (or http://localhost:8000/admin) - default user/password: root/root
9093
- Django API - Swagger: http://localhost/api/swagger-ui
9194
- Django API - Redoc: http://localhost/api/redoc
92-
- Celery Flower UI: http://localhost:5555
9395

9496
6. Optionally, use a tool like curl or Postman to invoke API requests directly
9597

@@ -157,4 +159,4 @@ DJANGO_SUPERUSER_USERNAME=SYSTEM DJANGO_SUPERUSER_PASSWORD=system DJANGO_SUPERUS
157159
- Django API - Redoc: http://localhost:8000/api/redoc
158160
- Celery Flower UI: http://localhost:5555
159161

160-
9. Optionally, use a tool like curl or Postman to invoke API requests directly
162+
9. Optionally, use a tool like curl or Postman to invoke API requests directly

backend/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ PUBLIC_URL = http://localhost:3000
1616
MEDIA_ROOT = .dev/files_storage
1717
DJANGO_DB = postgresql
1818
TEST_DJANGO_DB = sqlite
19-
DJANGO_CSRF_TRUSTED_ORIGINS = http://localhost:3000 http://localhost
19+
DJANGO_TRUSTED_ORIGINS = http://localhost:3000 http://localhost http://localhost:8000
2020
DJANGO_LOG_FOLDER = .dev/logging
2121
DJANGO_LOG_LEVEL = INFO
2222

backend/apps/ifc_validation/admin.py

Lines changed: 133 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
from datetime import timedelta
33

44
from django.contrib import admin
5+
from django.contrib import messages
56
from django.contrib.auth import get_permission_codename
67
from django.contrib.auth.models import User
8+
from django.http import HttpResponseRedirect
9+
from django.shortcuts import render
10+
from django.utils.translation import ngettext
711
from core import utils
812

913
from apps.ifc_validation_models.models import ValidationRequest, ValidationTask, ValidationOutcome
@@ -27,22 +31,30 @@ def save_model(self, request, obj, form, change):
2731
super().save_model(request, obj, form, change)
2832

2933

30-
class ValidationRequestAdmin(BaseAdmin):
34+
class NonAdminAddable(admin.ModelAdmin):
35+
36+
def has_add_permission(self, request):
37+
38+
# disable add via Admin ('+ Add' button)
39+
return False
40+
41+
42+
class ValidationRequestAdmin(BaseAdmin, NonAdminAddable):
3143

3244
fieldsets = [
33-
('General Information', {"classes": ("wide"), "fields": ["id", "file_name", "file", "file_size_text"]}),
45+
('General Information', {"classes": ("wide"), "fields": ["id", "public_id", "file_name", "file", "file_size_text", "deleted"]}),
3446
('Status Information', {"classes": ("wide"), "fields": ["status", "status_reason", "progress"]}),
3547
('Auditing Information', {"classes": ("wide"), "fields": [("created", "created_by"), ("updated", "updated_by")]})
3648
]
3749

38-
list_display = ["id", "file_name", "file_size_text", "status", "progress", "duration_text", "created", "created_by", "updated", "updated_by"]
39-
readonly_fields = ["id", "file_name", "file", "file_size_text", "duration", "duration_text", "created", "created_by", "updated", "updated_by"]
50+
list_display = ["id", "public_id", "file_name", "file_size_text", "status", "progress", "duration_text", "created", "created_by", "updated", "updated_by", "is_deleted"]
51+
readonly_fields = ["id", "public_id", "deleted", "file_name", "file", "file_size_text", "duration", "duration_text", "created", "created_by", "updated", "updated_by"]
4052
date_hierarchy = "created"
4153

42-
list_filter = ["status", "created_by", "created", "updated"]
54+
list_filter = ["status", "deleted", "created_by", "created", "updated"]
4355
search_fields = ('file_name', 'status', 'created_by__username', 'updated_by__username')
4456

45-
actions = ["mark_as_failed_action", "restart_processing_action"]
57+
actions = ["soft_delete_action", "soft_restore_action", "mark_as_failed_action", "restart_processing_action", "hard_delete_action"]
4658
actions_on_top = True
4759

4860
@admin.display(description="Duration (sec)")
@@ -56,11 +68,89 @@ def duration_text(self, obj):
5668
else:
5769
return None
5870

71+
@admin.display(description="Deleted ?")
72+
def is_deleted(self, obj):
73+
74+
return ("Yes" if obj.deleted else "No")
75+
5976
@admin.display(description="File Size")
6077
def file_size_text(self, obj):
6178

6279
return utils.format_human_readable_file_size(obj.size)
6380

81+
@admin.action(
82+
description="Permanently delete selected Validation Requests",
83+
permissions=["hard_delete"]
84+
)
85+
def hard_delete_action(self, request, queryset):
86+
87+
if 'apply' in request.POST:
88+
89+
for obj in queryset:
90+
obj.hard_delete()
91+
92+
self.message_user(
93+
request,
94+
ngettext(
95+
"%d Validation Request was successfully deleted.",
96+
"%d Validation Requests were successfully deleted.",
97+
len(queryset),
98+
)
99+
% len(queryset),
100+
messages.SUCCESS,
101+
)
102+
return HttpResponseRedirect(request.get_full_path())
103+
104+
return render(request, 'admin/hard_delete_intermediate.html', context={'val_requests': queryset, 'entity_name': 'Validation Request(s)'})
105+
106+
@admin.action(
107+
description="Soft-delete selected Validation Requests",
108+
permissions=["soft_delete"]
109+
)
110+
def soft_delete_action(self, request, queryset):
111+
# TODO: move to middleware component?
112+
if request.user.is_authenticated:
113+
logger.info(f"Authenticated, user.id = {request.user.id}")
114+
set_user_context(request.user)
115+
116+
for obj in queryset:
117+
obj.soft_delete()
118+
119+
self.message_user(
120+
request,
121+
ngettext(
122+
"%d Validation Request was successfully marked as deleted.",
123+
"%d Validation Requests were successfully marked as deleted.",
124+
len(queryset),
125+
)
126+
% len(queryset),
127+
messages.SUCCESS,
128+
)
129+
130+
@admin.action(
131+
description="Soft-restore selected Validation Requests",
132+
permissions=["soft_restore"]
133+
)
134+
def soft_restore_action(self, request, queryset):
135+
# TODO: move to middleware component?
136+
if request.user.is_authenticated:
137+
logger.info(f"Authenticated, user.id = {request.user.id}")
138+
set_user_context(request.user)
139+
140+
for obj in queryset:
141+
obj.undo_delete()
142+
143+
self.message_user(
144+
request,
145+
ngettext(
146+
"%d Validation Request was successfully marked as restored.",
147+
"%d Validation Requests were successfully marked as restored.",
148+
len(queryset),
149+
)
150+
% len(queryset),
151+
messages.SUCCESS,
152+
)
153+
64154
@admin.action(
65155
description="Mark selected Validation Requests as Failed",
66156
permissions=["change_status"]
@@ -70,6 +160,7 @@ def mark_as_failed_action(self, request, queryset):
70160
if request.user.is_authenticated:
71161
logger.info(f"Authenticated, user.id = {request.user.id}")
72162
set_user_context(request.user)
163+
73164
queryset.update(status=ValidationRequest.Status.FAILED)
74165

75166
@admin.action(
@@ -90,26 +181,47 @@ def restart_processing_action(self, request, queryset):
90181
ifc_file_validation_task.delay(obj.id, obj.file_name)
91182
logger.info(f"Task 'ifc_file_validation_task' re-submitted for id:{obj.id} file_name: {obj.file_name}")
92183

184+
def get_actions(self, request):
185+
186+
actions = super().get_actions(request)
187+
188+
# remove default 'delete' action from list
189+
if 'delete_selected' in actions:
190+
del actions['delete_selected']
191+
192+
return actions
193+
93194
def has_change_status_permission(self, request):
94195

95-
"""
96-
Does the user have the 'change status' permission?
97-
"""
98196
opts = self.opts
99197
codename = get_permission_codename("change_status", opts)
100198
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
199+
200+
def has_hard_delete_permission(self, request):
201+
202+
opts = self.opts
203+
codename = get_permission_codename("delete", opts)
204+
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
101205

206+
def has_soft_delete_permission(self, request):
102207

103-
class ValidationTaskAdmin(BaseAdmin):
208+
return self.has_hard_delete_permission(request)
209+
210+
def has_soft_restore_permission(self, request):
211+
212+
return self.has_soft_delete_permission(request)
213+
214+
215+
class ValidationTaskAdmin(BaseAdmin, NonAdminAddable):
104216

105217
fieldsets = [
106-
('General Information', {"classes": ("wide"), "fields": ["id", "request", "type", "process_id", "process_cmd"]}),
218+
('General Information', {"classes": ("wide"), "fields": ["id", "public_id", "request", "type", "process_id", "process_cmd"]}),
107219
('Status Information', {"classes": ("wide"), "fields": ["status", "status_reason", "progress", "started", "ended", "duration"]}),
108220
('Auditing Information', {"classes": ("wide"), "fields": ["created", "updated"]})
109221
]
110222

111-
list_display = ["id", "request", "type", "status", "progress", "started", "ended", "duration_text", "created", "updated"]
112-
readonly_fields = ["id", "request", "type", "process_id", "process_cmd", "started", "ended", "duration", "created", "updated"]
223+
list_display = ["id", "public_id", "request", "type", "status", "progress", "started", "ended", "duration_text", "created", "updated"]
224+
readonly_fields = ["id", "public_id", "request", "type", "process_id", "process_cmd", "started", "ended", "duration", "created", "updated"]
113225
date_hierarchy = "created"
114226

115227
list_filter = ["status", "type", "status", "started", "ended", "created", "updated"]
@@ -130,10 +242,10 @@ def duration_text(self, obj):
130242
return None
131243

132244

133-
class ValidationOutcomeAdmin(BaseAdmin):
245+
class ValidationOutcomeAdmin(BaseAdmin, NonAdminAddable):
134246

135-
list_display = ["id", "file_name_text", "type_text", "instance_id", "feature", "feature_version", "outcome_code", "severity", "expected", "observed", "created", "updated"]
136-
readonly_fields = ["id", "created", "updated"]
247+
list_display = ["id", "public_id", "file_name_text", "type_text", "instance_id", "feature", "feature_version", "outcome_code", "severity", "expected", "observed", "created", "updated"]
248+
readonly_fields = ["id", "public_id", "created", "updated"]
137249

138250
list_filter = ['validation_task__type', 'severity', 'outcome_code']
139251
search_fields = ('validation_task__request__file_name', 'feature', 'feature_version', 'outcome_code', 'severity', 'expected', 'observed')
@@ -147,10 +259,10 @@ def type_text(self, obj):
147259
return obj.validation_task.type
148260

149261

150-
class ModelAdmin(BaseAdmin):
262+
class ModelAdmin(BaseAdmin, NonAdminAddable):
151263

152-
list_display = ["id", "file_name", "size_text", "date", "schema", "mvd", "nbr_of_elements", "nbr_of_geometries", "nbr_of_properties", "produced_by", "created", "updated"]
153-
readonly_fields = ["id", "file", "file_name", "size", "size_text", "date", "schema", "mvd", "number_of_elements", "number_of_geometries", "number_of_properties", "produced_by", "created", "updated"]
264+
list_display = ["id", "public_id", "file_name", "size_text", "date", "schema", "mvd", "nbr_of_elements", "nbr_of_geometries", "nbr_of_properties", "produced_by", "created", "updated"]
265+
readonly_fields = ["id", "public_id", "file", "file_name", "size", "size_text", "date", "schema", "mvd", "number_of_elements", "number_of_geometries", "number_of_properties", "produced_by", "created", "updated"]
154266

155267
search_fields = ('file_name', 'schema', 'mvd', 'produced_by__name', 'produced_by__version')
156268

@@ -175,9 +287,9 @@ def size_text(self, obj):
175287
return utils.format_human_readable_file_size(obj.size)
176288

177289

178-
class ModelInstanceAdmin(BaseAdmin):
290+
class ModelInstanceAdmin(BaseAdmin, NonAdminAddable):
179291

180-
list_display = ["id", "stepfile_id", "model", "ifc_type", "created", "updated"]
292+
list_display = ["id", "public_id", "stepfile_id", "model", "ifc_type", "created", "updated"]
181293

182294
search_fields = ('stepfile_id', 'model__file_name', 'ifc_type')
183295

backend/apps/ifc_validation/email_tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def send_completion_email_task(id, file_name):
145145
# load and merge email template
146146
merge_data = {
147147
'FILE_NAME': file_name,
148-
'ID': id,
148+
'ID': request.public_id,
149149
'STATUS_SYNTAX': ("p" if (request.model is None or request.model.status_syntax is None) else request.model.status_syntax) in ['v', 'w', 'i'],
150150
"STATUS_SCHEMA": status_combine(
151151
"p" if (request.model is None or request.model.status_schema is None) else request.model.status_schema,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ISO-10303-21;
2+
HEADER;
3+
FILE_DESCRIPTION(('ViewDefinition [<script></script>]'),'2;1');
4+
FILE_NAME('<script></script>.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.7.0','<script></script>-0.7.0','');
5+
FILE_SCHEMA((IFC4'));
6+
ENDSEC;
7+
DATA;
8+
#1=IFCPERSON($,$,'',$,$,$,$,$);
9+
ENDSEC;
10+
END-ISO-10303-21;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ISO-10303-21;
2+
HEADER;
3+
FILE_DESCRIPTION(('ViewDefinition [<script></script>]'),'2;1');
4+
FILE_NAME('<script></script>.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.7.0','<script></script>-0.7.0','');
5+
FILE_SCHEMA(('IFC4'));
6+
ENDSEC;
7+
DATA;
8+
#1=IFCPERSON($,$,'',$,$,$,$,$);
9+
ENDSEC;
10+
END-ISO-10303-21;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ISO-10303-21;
2+
HEADER;
3+
FILE_DESCRIPTION(('ViewDefinition [<script>alert(document.cookie);</script>]'),'2;1');
4+
FILE_NAME('test.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.1.0','Test-0.1.0','');
5+
FILE_SCHEMA(('IFC4'));
6+
ENDSEC;
7+
DATA;
8+
#1=IFCPERSON($, '<script>alert(document.cookie);</script>','<script></script>',$,$,$,$,$);
9+
ENDSEC;
10+
END-ISO-10303-21;

0 commit comments

Comments
 (0)