Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
officejs-appstore
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
nexedi
officejs-appstore
Commits
f7705f5a
Commit
f7705f5a
authored
Feb 02, 2022
by
Roque
Browse files
Options
Browse Files
Download
Plain Diff
Switch back caching policy to must-revalidate (update upgrader constraint)
See merge request
!22
parents
6f61db52
d33ccbe4
Pipeline
#19577
failed with stage
in 0 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
38 additions
and
501 deletions
+38
-501
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenario.py
...eItem/portal_components/test.erp5.testOfficeJSScenario.py
+10
-9
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenario.xml
...Item/portal_components/test.erp5.testOfficeJSScenario.xml
+25
-17
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenarioRjsUI.py
.../portal_components/test.erp5.testOfficeJSScenarioRjsUI.py
+0
-348
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenarioRjsUI.xml
...portal_components/test.erp5.testOfficeJSScenarioRjsUI.xml
+0
-123
bt5/officejs_test/bt/template_test_id_list
bt5/officejs_test/bt/template_test_id_list
+0
-1
bt5/officejs_upgrader/SkinTemplateItem/portal_skins/officejs_upgrader/WebSection_checkAppstoreApplicationCachingPolicyConsistency.py
...ction_checkAppstoreApplicationCachingPolicyConsistency.py
+3
-3
No files found.
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenario.py
View file @
f7705f5a
...
...
@@ -136,7 +136,7 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=
600, stale-while-revalidate=360000, stale-if-error=31536000, public
'
'max-age=
0, public, must-revalidate
'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
...
...
@@ -152,12 +152,11 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
'
Accept-Language,
Cookie,Authorization,Accept-Encoding'
)
# First access to Base_getWebDocumentDrivenModificationDate writes in ZODB
self
.
tic
()
###################################
### Anonymous: access the redirect application appcache
connection
.
request
(
...
...
@@ -168,7 +167,8 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
page_content
=
response
.
read
()
self
.
assertEquals
(
"""CACHE MANIFEST
# development / %s"""
%
web_section
.
Base_getWebDocumentDrivenModificationDate
().
rfc822
(),
CACHE:
NETWORK:"""
,
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
...
...
@@ -258,7 +258,7 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=
600, stale-while-revalidate=360000, stale-if-error=31536000, public
'
'max-age=
0, public, must-revalidate
'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
...
...
@@ -274,7 +274,7 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
'
Accept-Language,
Cookie,Authorization,Accept-Encoding'
)
###################################
...
...
@@ -306,7 +306,7 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=
600, stale-while-revalidate=360000, stale-if-error=31536000, public
'
'max-age=
0, public, must-revalidate
'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
...
...
@@ -322,7 +322,7 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
'
Accept-Language,
Cookie,Authorization,Accept-Encoding'
)
###################################
...
...
@@ -335,7 +335,8 @@ class TestOfficeJSScenario(testOfficeJSAppstoreMixin):
page_content
=
response
.
read
()
self
.
assertEquals
(
"""CACHE MANIFEST
# %s / %s"""
%
(
web_section
.
getId
(),
web_section
.
Base_getWebDocumentDrivenModificationDate
().
rfc822
()),
CACHE:
NETWORK:"""
,
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
...
...
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenario.xml
View file @
f7705f5a
...
...
@@ -45,7 +45,11 @@
<item>
<key>
<string>
text_content_warning_message
</string>
</key>
<value>
<tuple/>
<tuple>
<string>
W:113, 28: Unused variable \'api_path\' (unused-variable)
</string>
<string>
W:113, 38: Unused variable \'api_query\' (unused-variable)
</string>
<string>
W:114, 6: Unused variable \'api_fragment\' (unused-variable)
</string>
</tuple>
</value>
</item>
<item>
...
...
@@ -100,24 +104,28 @@
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
patches.WorkflowTool
"
/>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
Workflow
"
/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<string>
validate
</string>
</value>
</item>
<item>
<key>
<string>
validation_state
</string>
</key>
<value>
<string>
validated
</string>
</value>
</item>
</dictionary>
</list>
</tuple>
<dictionary>
<item>
<key>
<string>
_log
</string>
</key>
<value>
<list>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<string>
validate
</string>
</value>
</item>
<item>
<key>
<string>
validation_state
</string>
</key>
<value>
<string>
validated
</string>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenarioRjsUI.py
deleted
100644 → 0
View file @
6f61db52
from
erp5.component.test.testOfficeJSAppstoreMixin
import
\
testOfficeJSAppstoreMixin
import
time
from
StringIO
import
StringIO
import
zipfile
import
httplib
import
urlparse
import
ssl
class
LocalStringIO
(
StringIO
):
__allow_access_to_unprotected_subobjects__
=
1
def
__init__
(
self
,
filename
,
*
args
,
**
kw
):
self
.
filename
=
filename
StringIO
.
__init__
(
self
,
*
args
,
**
kw
)
class
TestOfficeJSScenario
(
testOfficeJSAppstoreMixin
):
def
getTitle
(
self
):
return
"Test OfficeJS Scenario"
def
createNewUser
(
self
,
first_name
=
"John_test"
,
last_name
=
"Smith_test"
,
email
=
"john.smith@example.com"
):
"""
Add new member using script for the website sign-in
"""
user
=
self
.
portal
.
person_module
.
newContent
(
portal_type
=
"Person"
,
first_name
=
first_name
,
last_name
=
last_name
,
default_email_coordinate_text
=
email
,
career_role_list
=
[
"author"
]
)
user
.
validate
()
assignment
=
user
.
newContent
(
portal_type
=
"Assignment"
,
role
=
"author"
)
assignment
.
open
()
return
user
def
createNewAdmin
(
self
,
first_name
=
"Brandon_test"
,
last_name
=
"Cougar_test"
,
email
=
"brandon.cougar@example.com"
):
"""
Add new member using script for the website sign-in
"""
user
=
self
.
portal
.
person_module
.
newContent
(
portal_type
=
"Person"
,
first_name
=
first_name
,
last_name
=
last_name
,
default_email_coordinate_text
=
email
,
career_role_list
=
[
"administration"
]
)
user
.
validate
()
assignment
=
user
.
newContent
(
portal_type
=
"Assignment"
,
group
=
"my_group"
)
assignment
.
open
()
return
user
def
test_01_user_can_contribute
(
self
):
timestamp
=
"%s"
%
time
.
time
()
###################################
### Manager: Create user
person_user
=
self
.
createNewUser
()
person_admin
=
self
.
createNewAdmin
()
self
.
tic
()
###################################
### User: submit application
self
.
logout
()
self
.
login
(
person_user
.
getUserId
())
import_file
=
LocalStringIO
(
'your.zip'
)
my_zip
=
zipfile
.
ZipFile
(
import_file
,
'w'
)
my_zip
.
writestr
(
'index.html'
,
'My super content'
)
for
i
in
range
(
50
):
my_zip
.
writestr
(
'gadget_n_'
+
str
(
i
)
+
'.html'
,
'My super gadget '
+
str
(
i
))
my_zip
.
close
()
title
=
'My Application %s'
%
timestamp
self
.
portal
.
SoftwareProductModule_createNewApplication
(
import_file
,
title
)
self
.
tic
()
###################################
### Alarm: extract zip file
self
.
logout
()
self
.
login
()
self
.
tic
()
self
.
portal
.
portal_alarms
.
officejs_process_submit_software_publication
\
.
activeSense
()
self
.
tic
()
software_product
=
self
.
portal
.
portal_catalog
.
getResultValue
(
portal_type
=
'Software Product'
,
title
=
title
)
web_site
=
software_product
.
SoftwareProduct_getRelatedWebSite
()
web_section
=
web_site
.
objectValues
()[
0
]
###################################
### Anonymous: access the redirect application
self
.
logout
()
api_scheme
,
api_netloc
,
api_path
,
api_query
,
\
api_fragment
=
urlparse
.
urlsplit
(
self
.
portal
.
absolute_url
())
if
(
api_scheme
==
'https'
):
connection
=
httplib
.
HTTPSConnection
(
api_netloc
,
context
=
ssl
.
_create_unverified_context
(),
timeout
=
10
)
else
:
connection
=
httplib
.
HTTPConnection
(
api_netloc
,
timeout
=
10
)
connection
.
request
(
method
=
'GET'
,
url
=
'%s/'
%
web_section
.
getParentValue
().
absolute_url
()
)
response
=
connection
.
getresponse
()
page_content
=
response
.
read
()
self
.
assertTrue
(
'<script data-appconfig="latest_version" type="text/x-renderjs-configuration">development</script>'
in
page_content
,
page_content
)
self
.
assertTrue
(
'manifest="WebSection_renderOfficeJSRedirectAppCache"'
in
page_content
,
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=600, stale-while-revalidate=360000, stale-if-error=31536000, public'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Content-Security-Policy'
),
"default-src 'self';"
)
self
.
assertEquals
(
response
.
getheader
(
'X-Frame-Options'
),
'SAMEORIGIN'
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
)
# First access to Base_getWebDocumentDrivenModificationDate writes in ZODB
self
.
tic
()
###################################
### Anonymous: access the redirect application appcache
connection
.
request
(
method
=
'GET'
,
url
=
'%s/WebSection_renderOfficeJSRedirectAppCache'
%
web_section
.
getParentValue
().
absolute_url
()
)
response
=
connection
.
getresponse
()
page_content
=
response
.
read
()
self
.
assertEquals
(
"""CACHE MANIFEST
# development / %s"""
%
web_section
.
Base_getWebDocumentDrivenModificationDate
().
rfc822
(),
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertTrue
(
'text/cache-manifest;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=600, stale-while-revalidate=360000, stale-if-error=31536000, public'
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
),
None
)
###################################
### Anonymous: access the application version
connection
.
request
(
method
=
'GET'
,
url
=
'%s/'
%
web_section
.
absolute_url
()
)
response
=
connection
.
getresponse
()
self
.
assertEquals
(
response
.
read
(),
'My super content'
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=31536000, stale-while-revalidate=31536000, stale-if-error=31536000, public'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Content-Security-Policy'
),
None
)
self
.
assertEquals
(
response
.
getheader
(
'X-Frame-Options'
),
None
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
)
###################################
### User: open the publication to be accepted
self
.
logout
()
self
.
login
(
person_user
.
getUserId
())
software_publication
=
self
.
portal
.
portal_catalog
.
getResultValue
(
portal_type
=
'Software Publication'
,
reference
=
'SP-%s'
%
web_section
.
getId
()
)
software_publication
.
open
()
self
.
tic
()
###################################
### Anonymous: access the redirect application
self
.
logout
()
api_scheme
,
api_netloc
,
api_path
,
api_query
,
\
api_fragment
=
urlparse
.
urlsplit
(
self
.
portal
.
absolute_url
())
if
(
api_scheme
==
'https'
):
connection
=
httplib
.
HTTPSConnection
(
api_netloc
,
context
=
ssl
.
_create_unverified_context
(),
timeout
=
10
)
else
:
connection
=
httplib
.
HTTPConnection
(
api_netloc
,
timeout
=
10
)
connection
.
request
(
method
=
'GET'
,
url
=
'%s/'
%
web_section
.
getParentValue
().
absolute_url
()
)
response
=
connection
.
getresponse
()
page_content
=
response
.
read
()
self
.
assertTrue
(
'<script data-appconfig="latest_version" type="text/x-renderjs-configuration">development</script>'
in
page_content
,
page_content
)
self
.
assertTrue
(
'manifest="WebSection_renderOfficeJSRedirectAppCache"'
in
page_content
,
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=600, stale-while-revalidate=360000, stale-if-error=31536000, public'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Content-Security-Policy'
),
"default-src 'self';"
)
self
.
assertEquals
(
response
.
getheader
(
'X-Frame-Options'
),
'SAMEORIGIN'
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
)
###################################
### Site admin: accept the software publication
self
.
logout
()
self
.
login
(
person_admin
.
getUserId
())
software_publication
.
accept
()
self
.
tic
()
###################################
### Anonymous: access the redirect application
self
.
logout
()
connection
.
request
(
method
=
'GET'
,
url
=
'%s/'
%
web_section
.
getParentValue
().
absolute_url
()
)
response
=
connection
.
getresponse
()
page_content
=
response
.
read
()
self
.
assertTrue
(
'<script data-appconfig="latest_version" type="text/x-renderjs-configuration">%s</script>'
%
web_section
.
getId
()
in
page_content
,
page_content
)
self
.
assertTrue
(
'manifest="WebSection_renderOfficeJSRedirectAppCache"'
in
page_content
,
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=600, stale-while-revalidate=360000, stale-if-error=31536000, public'
)
self
.
assertTrue
(
'text/html;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Content-Security-Policy'
),
"default-src 'self';"
)
self
.
assertEquals
(
response
.
getheader
(
'X-Frame-Options'
),
'SAMEORIGIN'
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
).
replace
(
' '
,
''
),
'Cookie,Authorization,Accept-Encoding'
)
###################################
### Anonymous: access the redirect application appcache
connection
.
request
(
method
=
'GET'
,
url
=
'%s/WebSection_renderOfficeJSRedirectAppCache'
%
web_section
.
getParentValue
().
absolute_url
()
)
response
=
connection
.
getresponse
()
page_content
=
response
.
read
()
self
.
assertEquals
(
"""CACHE MANIFEST
# %s / %s"""
%
(
web_section
.
getId
(),
web_section
.
Base_getWebDocumentDrivenModificationDate
().
rfc822
()),
page_content
)
self
.
assertEquals
(
response
.
status
,
200
)
self
.
assertTrue
(
'text/cache-manifest;'
in
response
.
getheader
(
'Content-Type'
),
response
.
getheader
(
'Content-Type'
)
)
self
.
assertEquals
(
response
.
getheader
(
'Cache-Control'
),
'max-age=600, stale-while-revalidate=360000, stale-if-error=31536000, public'
)
self
.
assertEquals
(
response
.
getheader
(
'Vary'
),
None
)
bt5/officejs_test/TestTemplateItem/portal_components/test.erp5.testOfficeJSScenarioRjsUI.xml
deleted
100644 → 0
View file @
6f61db52
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Test Component"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_recorded_property_dict
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAI=
</string>
</persistent>
</value>
</item>
<item>
<key>
<string>
default_reference
</string>
</key>
<value>
<string>
testOfficeJSScenarioRjsUI
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
test.erp5.testOfficeJSScenarioRjsUI
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Test Component
</string>
</value>
</item>
<item>
<key>
<string>
sid
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
text_content_error_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
text_content_warning_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
version
</string>
</key>
<value>
<string>
erp5
</string>
</value>
</item>
<item>
<key>
<string>
workflow_history
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAM=
</string>
</persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"2"
aka=
"AAAAAAAAAAI="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"3"
aka=
"AAAAAAAAAAM="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary>
<item>
<key>
<string>
component_validation_workflow
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAQ=
</string>
</persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.patches.WorkflowTool"
/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<string>
validate
</string>
</value>
</item>
<item>
<key>
<string>
validation_state
</string>
</key>
<value>
<string>
validated
</string>
</value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
bt5/officejs_test/bt/template_test_id_list
View file @
f7705f5a
test.erp5.testOfficeJSSecurity
test.erp5.testOfficeJSScenario
test.erp5.testOfficeJSScenarioRjsUI
test.erp5.testFunctionalOfficeJSAppstorePublisherUI
test.erp5.testFunctionalOfficeJSAppstoreSiteUI
test.erp5.testOfficeJSScenarioAppstore
...
...
bt5/officejs_upgrader/SkinTemplateItem/portal_skins/officejs_upgrader/WebSection_checkAppstoreApplicationCachingPolicyConsistency.py
View file @
f7705f5a
...
...
@@ -4,10 +4,10 @@ error_list = []
person
=
web_section
.
getSourceValue
(
portal_type
=
'Person'
)
if
(
web_section
.
getParentId
()
==
'application-list'
)
\
and
(
web_section
.
getAggregate
()
==
'web_page_module/gadget_ojs_appstore_redirect_page_html'
)
\
and
(
web_section
.
getCachingPolicy
()
==
'
must-revali
date'
):
error_list
.
append
(
'"
Must-revali
date" caching policy is deprecated'
)
and
(
web_section
.
getCachingPolicy
()
==
'
one-hour-max-modification-
date'
):
error_list
.
append
(
'"
One-hour-max-modification-
date" caching policy is deprecated'
)
if
fixit
:
web_section
.
edit
(
caching_policy
=
'
one-hour-max-modification-
date'
)
web_section
.
edit
(
caching_policy
=
'
must-revali
date'
)
return
error_list
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment