Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
Klaus Wölfel
erp5
Commits
41f9cd88
Commit
41f9cd88
authored
10 years ago
by
Gabriel Monnerat
Browse files
Options
Download
Email Patches
Plain Diff
Remove trailing whitespaces
parent
96df5d16
Changes
587
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
120 additions
and
120 deletions
+120
-120
product/AGProjects/patches/SOAPpy_WSDL.py
product/AGProjects/patches/SOAPpy_WSDL.py
+3
-3
product/CMFActivity/ActiveObject.py
product/CMFActivity/ActiveObject.py
+1
-1
product/CMFActivity/ActiveProcess.py
product/CMFActivity/ActiveProcess.py
+1
-1
product/CMFActivity/Activity/Queue.py
product/CMFActivity/Activity/Queue.py
+2
-2
product/CMFActivity/Activity/SQLBase.py
product/CMFActivity/Activity/SQLBase.py
+1
-1
product/CMFActivity/Activity/SQLDict.py
product/CMFActivity/Activity/SQLDict.py
+3
-3
product/CMFActivity/Activity/SQLQueue.py
product/CMFActivity/Activity/SQLQueue.py
+2
-2
product/CMFActivity/ActivityTool.py
product/CMFActivity/ActivityTool.py
+9
-9
product/CMFActivity/tests/testCMFActivity.py
product/CMFActivity/tests/testCMFActivity.py
+26
-26
product/CMFCategory/Category.py
product/CMFCategory/Category.py
+18
-18
product/CMFCategory/CategoryTool.py
product/CMFCategory/CategoryTool.py
+17
-17
product/CMFCategory/Filter.py
product/CMFCategory/Filter.py
+1
-1
product/CMFCategory/Renderer.py
product/CMFCategory/Renderer.py
+1
-1
product/CMFCategory/tests/testCMFCategory.py
product/CMFCategory/tests/testCMFCategory.py
+16
-16
product/ERP5/AggregatedAmountList.py
product/ERP5/AggregatedAmountList.py
+2
-2
product/ERP5/Capacity/GLPK.py
product/ERP5/Capacity/GLPK.py
+7
-7
product/ERP5/Constraint/AccountTypeConstraint.py
product/ERP5/Constraint/AccountTypeConstraint.py
+3
-3
product/ERP5/Constraint/AccountingTransactionBalance.py
product/ERP5/Constraint/AccountingTransactionBalance.py
+4
-4
product/ERP5/Constraint/DocumentReferenceConstraint.py
product/ERP5/Constraint/DocumentReferenceConstraint.py
+1
-1
product/ERP5/Constraint/TransactionQuantityValueFeasability.py
...ct/ERP5/Constraint/TransactionQuantityValueFeasability.py
+2
-2
No files found.
product/AGProjects/patches/SOAPpy_WSDL.py
View file @
41f9cd88
...
...
@@ -5,17 +5,17 @@
# That licence is (as of release 0.12.0):
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
#
# Neither the name of actzero, inc. nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/ActiveObject.py
View file @
41f9cd88
...
...
@@ -53,7 +53,7 @@ class ActiveObject(ExtensionClass.Base):
>>> obj.activate().aMethod()
This will defer the call to obj.aMethod()
This will defer the call to obj.aMethod()
"""
security
=
ClassSecurityInfo
()
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/ActiveProcess.py
View file @
41f9cd88
...
...
@@ -145,7 +145,7 @@ class ActiveProcess(Base):
security
.
declareProtected
(
CMFCorePermissions
.
View
,
'hasErrorActivity'
)
def
hasErrorActivity
(
self
,
**
kw
):
"""
Tells if some attached activities are in a error
Tells if some attached activities are in a error
"""
return
self
.
hasActivity
(
processing_node
=
INVOKE_ERROR_STATE
)
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/Activity/Queue.py
View file @
41f9cd88
...
...
@@ -94,7 +94,7 @@ class Queue(object):
if
not
getattr
(
m
,
'is_deleted'
,
0
):
# We try not to delete twice
# However this can not be garanteed in the case of messages loaded from SQL
activity_tool
.
deferredDeleteMessage
(
self
,
m
)
activity_tool
.
deferredDeleteMessage
(
self
,
m
)
m
.
is_deleted
=
1
def
dequeueMessage
(
self
,
activity_tool
,
processing_node
):
...
...
@@ -199,7 +199,7 @@ class Queue(object):
def
hasActivity
(
self
,
activity_tool
,
object
,
processing_node
=
None
,
active_process
=
None
,
**
kw
):
return
0
def
flush
(
self
,
activity_tool
,
object
,
**
kw
):
def
flush
(
self
,
activity_tool
,
object
,
**
kw
):
pass
def
getOrderValidationText
(
self
,
message
):
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/Activity/SQLBase.py
View file @
41f9cd88
...
...
@@ -90,7 +90,7 @@ class SQLBase(Queue):
"""
Define a set of common methods for SQL-based storage of activities.
"""
def
getNow
(
self
,
context
):
"""
Return the current value for SQL server's NOW().
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/Activity/SQLDict.py
View file @
41f9cd88
...
...
@@ -36,7 +36,7 @@ import transaction
from
zLOG
import
LOG
,
TRACE
,
WARNING
,
ERROR
,
INFO
,
PANIC
# Stop validating more messages when this limit is reached
MAX_VALIDATED_LIMIT
=
1000
MAX_VALIDATED_LIMIT
=
1000
# Read up to this number of messages to validate.
READ_MESSAGE_LIMIT
=
1000
...
...
@@ -331,9 +331,9 @@ class SQLDict(SQLBase):
path
=
[
path
]
if
isinstance
(
method_id
,
str
):
method_id
=
[
method_id
]
result
=
activity_tool
.
SQLDict_validateMessageList
(
method_id
=
method_id
,
result
=
activity_tool
.
SQLDict_validateMessageList
(
method_id
=
method_id
,
path
=
path
,
message_uid
=
message_uid
,
message_uid
=
message_uid
,
tag
=
tag
,
serialization_tag
=
None
,
count
=
1
)
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/Activity/SQLQueue.py
View file @
41f9cd88
...
...
@@ -107,9 +107,9 @@ class SQLQueue(SQLBase):
path
=
[
path
]
if
isinstance
(
method_id
,
str
):
method_id
=
[
method_id
]
result
=
activity_tool
.
SQLQueue_validateMessageList
(
method_id
=
method_id
,
result
=
activity_tool
.
SQLQueue_validateMessageList
(
method_id
=
method_id
,
path
=
path
,
message_uid
=
message_uid
,
message_uid
=
message_uid
,
tag
=
tag
,
serialization_tag
=
None
,
count
=
1
)
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/ActivityTool.py
View file @
41f9cd88
...
...
@@ -395,7 +395,7 @@ Named Parameters: %r
is_executed can be one of MESSAGE_NOT_EXECUTED, MESSAGE_EXECUTED and
MESSAGE_NOT_EXECUTABLE (variables defined above).
exc_info must be - if given - similar to sys.exc_info() return value.
log must be - if given - True or False. If True, a log line will be
...
...
@@ -579,10 +579,10 @@ class ActivityTool (Folder, UniqueObject):
security
.
declareProtected
(
CMFCorePermissions
.
ManagePortal
,
'manage_overview'
)
manage_overview
=
DTMLFile
(
'dtml/explainActivityTool'
,
globals
()
)
security
.
declareProtected
(
CMFCorePermissions
.
ManagePortal
,
'manageLoadBalancing'
)
manageLoadBalancing
=
DTMLFile
(
'dtml/manageLoadBalancing'
,
globals
()
)
distributingNode
=
''
_nodes
=
()
activity_creation_trace
=
False
...
...
@@ -643,7 +643,7 @@ class ActivityTool (Folder, UniqueObject):
if
not
service
:
LOG
(
'ActivityTool'
,
INFO
,
'TimerService not available'
)
return
False
path
=
'/'
.
join
(
self
.
getPhysicalPath
())
if
path
in
service
.
lisSubscriptions
():
return
True
...
...
@@ -782,7 +782,7 @@ class ActivityTool (Folder, UniqueObject):
def
manage_beforeDelete
(
self
,
item
,
container
):
self
.
unsubscribe
()
Folder
.
inheritedAttribute
(
'manage_beforeDelete'
)(
self
,
item
,
container
)
def
manage_afterAdd
(
self
,
item
,
container
):
self
.
subscribe
()
Folder
.
inheritedAttribute
(
'manage_afterAdd'
)(
self
,
item
,
container
)
...
...
@@ -826,7 +826,7 @@ class ActivityTool (Folder, UniqueObject):
'node identifier.'
,
DeprecationWarning
)
currentNode
=
self
.
getServerAddress
()
return
currentNode
security
.
declarePublic
(
'getDistributingNode'
)
def
getDistributingNode
(
self
):
""" Return the distributingNode """
...
...
@@ -881,10 +881,10 @@ class ActivityTool (Folder, UniqueObject):
def
_isValidNodeName
(
self
,
node_name
)
:
"""Check we have been provided a good node name"""
return
isinstance
(
node_name
,
str
)
security
.
declarePublic
(
'manage_setDistributingNode'
)
def
manage_setDistributingNode
(
self
,
distributingNode
,
REQUEST
=
None
):
""" set the distributing node """
""" set the distributing node """
if
not
distributingNode
or
self
.
_isValidNodeName
(
distributingNode
):
self
.
distributingNode
=
distributingNode
if
REQUEST
is
not
None
:
...
...
@@ -1212,7 +1212,7 @@ class ActivityTool (Folder, UniqueObject):
# XXX: PATH_INFO might not be set when runing unit tests.
if
request
.
environ
.
get
(
'PATH_INFO'
)
is
None
:
request
.
environ
[
'PATH_INFO'
]
=
'/Control_Panel/timer_service/process_timer'
# restore request information
new_request
=
request
.
clone
()
request_info
=
message
.
request_info
...
...
This diff is collapsed.
Click to expand it.
product/CMFActivity/tests/testCMFActivity.py
View file @
41f9cd88
...
...
@@ -1211,7 +1211,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
# Just wait for the active object to be abandoned.
self
.
flushAllActivities
(
silent
=
1
,
loop_size
=
100
)
self
.
assertEqual
(
len
(
activity_tool
.
getMessageList
()),
1
)
self
.
assertEqual
(
activity_tool
.
getMessageList
()[
0
].
processing_node
,
self
.
assertEqual
(
activity_tool
.
getMessageList
()[
0
].
processing_node
,
INVOKE_ERROR_STATE
)
# Make sure that persistent objects are not present in the connection
...
...
@@ -1462,7 +1462,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
ZopeTestCase
.
_print
(
message
)
LOG
(
'Testing... '
,
0
,
message
)
portal
=
self
.
getPortal
()
portal
=
self
.
getPortal
()
organisation
=
portal
.
organisation
.
_getOb
(
self
.
company_id
)
# Defined a group method
foobar_list
=
[]
...
...
@@ -1471,7 +1471,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
for
obj
,
args
,
kw
,
_
in
object_list
:
obj
.
foobar
=
getattr
(
obj
.
aq_base
,
'foobar'
,
0
)
+
kw
.
get
(
'number'
,
1
)
from
Products.ERP5Type.Core.Folder
import
Folder
Folder
.
setFoobar
=
setFoobar
Folder
.
setFoobar
=
setFoobar
def
getFoobar
(
self
):
return
(
getattr
(
self
,
'foobar'
,
0
))
...
...
@@ -1557,7 +1557,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
assertNotEquals
(
0
,
len
(
messages_for_o1
))
for
m
in
messages_for_o1
:
self
.
assertEqual
(
m
.
activity_kw
.
get
(
'tag'
),
'The Tag'
)
def
test_82_LossOfVolatileAttribute
(
self
,
quiet
=
0
,
run
=
run_all_test
):
"""
Test that the loss of volatile attribute doesn't loose activities
...
...
@@ -1730,7 +1730,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
pass
invoke
=
activity_tool
.
__class__
.
invoke
invokeGroup
=
activity_tool
.
__class__
.
invokeGroup
try
:
try
:
activity_tool
.
__class__
.
invoke
=
modifySQLAndFail
activity_tool
.
__class__
.
invokeGroup
=
modifySQLAndFail
Organisation
.
dummy
=
dummy
...
...
@@ -1746,7 +1746,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
delattr
(
Organisation
,
'dummy'
)
activity_tool
.
__class__
.
invoke
=
invoke
activity_tool
.
__class__
.
invokeGroup
=
invokeGroup
def
test_87_ActivityToolInvokeFailureDoesNotCommitCMFActivitySQLConnectionSQLQueue
(
self
,
quiet
=
0
,
run
=
run_all_test
):
"""
Check that CMFActivity SQL connection is rollback if activity_tool.invoke raises.
...
...
@@ -1968,7 +1968,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
ZopeTestCase
.
_print
(
message
)
LOG
(
'Testing... '
,
0
,
message
)
self
.
TryUserNotificationRaise
(
'SQLQueue'
)
def
test_94_ActivityToolCommitFailureDoesNotCommitCMFActivitySQLConnectionSQLDict
(
self
,
quiet
=
0
,
run
=
run_all_test
):
"""
Check that CMFActivity SQL connection is rollback if transaction commit raises.
...
...
@@ -2007,7 +2007,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
def
fake_commit
(
*
args
,
**
kw
):
transaction
.
get
().
__class__
.
commit
=
commit
raise
KeyError
,
'always fail'
try
:
try
:
Organisation
.
modifySQL
=
modifySQL
obj
=
self
.
getPortal
().
organisation_module
.
newContent
(
portal_type
=
'Organisation'
)
group_method_id
=
'%s/modifySQL'
%
(
obj
.
getPath
(),
)
...
...
@@ -2023,7 +2023,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
assertEqual
(
activity_tool
.
countMessage
(
method_id
=
'dummy_activity'
),
0
)
finally
:
delattr
(
Organisation
,
'modifySQL'
)
def
test_95_ActivityToolCommitFailureDoesNotCommitCMFActivitySQLConnectionSQLQueue
(
self
,
quiet
=
0
,
run
=
run_all_test
):
"""
Check that CMFActivity SQL connection is rollback if activity_tool.invoke raises.
...
...
@@ -2461,7 +2461,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
assertEqual
(
o
.
absolute_url
(),
'http://test.erp5.org:9080/virtual_root/test_obj'
)
o
.
activate
().
checkAbsoluteUrl
()
# Reset server URL and virtual root before executing messages.
# This simulates the case of activities beeing executed with different
# REQUEST, such as TimerServer.
...
...
@@ -2588,7 +2588,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
# release activity_lock wait for activity_lock internal wait
# wait for activity_thread (finish) internal wait
# wait for process_shutdown_thread None (finish)
#
#
# This test only checks that:
# - activity tool can exit between 2 processable activity batches
# - activity tool won't process activities after process_shutdown was called
...
...
@@ -2707,7 +2707,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
def
test_active_object_hasActivity_does_not_catch_exceptions
(
self
):
"""
Some time ago, hasActivity was doing a silent try/except, and this was
a possible disaster for some projects. Here we make sure that if the
a possible disaster for some projects. Here we make sure that if the
SQL request fails, then the exception is not ignored
"""
active_object
=
self
.
portal
.
organisation_module
.
newContent
(
...
...
@@ -2751,7 +2751,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
for
i
in
range
(
10
):
o
.
activate
(
activity
=
'SQLQueue'
).
dummy_counter
()
self
.
flushAllActivities
()
self
.
assertEqual
(
call_count
,
10
)
finally
:
...
...
@@ -2776,7 +2776,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
method_name
=
'dummy_counter_%s'
%
i
setattr
(
Organisation
,
method_name
,
dummy_counter
)
getattr
(
o
.
activate
(
activity
=
'SQLDict'
),
method_name
)()
self
.
flushAllActivities
()
self
.
assertEqual
(
call_count
,
10
)
finally
:
...
...
@@ -2887,7 +2887,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
Test behaviour of PlacelessDefaultReindexParameters.
"""
portal
=
self
.
portal
# Make a new Person object to make sure that the portal type
# is migrated to an instance of a portal type class, otherwise
# the portal type may generate an extra active object.
...
...
@@ -2897,10 +2897,10 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
original_reindex_parameters
=
portal
.
getPlacelessDefaultReindexParameters
()
if
original_reindex_parameters
is
None
:
original_reindex_parameters
=
{}
tag
=
'SOME_RANDOM_TAG'
tag
=
'SOME_RANDOM_TAG'
activate_kw
=
original_reindex_parameters
.
get
(
'activate_kw'
,
{}).
copy
()
activate_kw
[
'tag'
]
=
tag
activate_kw
[
'tag'
]
=
tag
portal
.
setPlacelessDefaultReindexParameters
(
activate_kw
=
activate_kw
,
\
**
original_reindex_parameters
)
current_default_reindex_parameters
=
portal
.
getPlacelessDefaultReindexParameters
()
...
...
@@ -2913,12 +2913,12 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
assertEqual
(
1
,
portal
.
portal_activities
.
countMessageWithTag
(
tag
))
self
.
tic
()
self
.
assertEqual
(
0
,
portal
.
portal_activities
.
countMessageWithTag
(
tag
))
# restore originals ones
portal
.
setPlacelessDefaultReindexParameters
(
**
original_reindex_parameters
)
person
=
portal
.
person_module
.
newContent
(
portal_type
=
'Person'
)
# .. now no messages with this tag should apper
self
.
assertEqual
(
0
,
portal
.
portal_activities
.
countMessageWithTag
(
tag
))
self
.
assertEqual
(
0
,
portal
.
portal_activities
.
countMessageWithTag
(
tag
))
def
TryNotificationSavedOnEventLogWhenNotifyUserRaises
(
self
,
activity
):
activity_tool
=
self
.
getActivityTool
()
...
...
@@ -2937,7 +2937,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
_catch_log_errors
()
obj
.
activate
(
activity
=
activity
,
priority
=
6
).
failingMethod
()
self
.
commit
()
self
.
flushAllActivities
(
silent
=
1
,
loop_size
=
100
)
self
.
flushAllActivities
(
silent
=
1
,
loop_size
=
100
)
message
,
=
activity_tool
.
getMessageList
()
self
.
commit
()
for
log_record
in
self
.
logged
:
...
...
@@ -2970,7 +2970,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
message
=
'
\n
Check the error is saved on event log even if the mail notification is not sent (SQLQueue)'
ZopeTestCase
.
_print
(
message
)
LOG
(
'Testing... '
,
0
,
message
)
self
.
TryNotificationSavedOnEventLogWhenNotifyUserRaises
(
'SQLQueue'
)
self
.
TryNotificationSavedOnEventLogWhenNotifyUserRaises
(
'SQLQueue'
)
def
TryUserMessageContainingNoTracebackIsStillSent
(
self
,
activity
):
portal
=
self
.
getPortalObject
()
...
...
@@ -3002,7 +3002,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
activity_tool
.
manageCancel
(
message
.
object_path
,
message
.
method_id
)
finally
:
Message
.
notifyUser
=
original_notifyUser
delattr
(
Organisation
,
'failingMethod'
)
delattr
(
Organisation
,
'failingMethod'
)
def
test_120_sendMessageWithNoTracebackWithSQLQueue
(
self
,
quiet
=
0
,
run
=
run_all_test
):
if
not
run
:
return
...
...
@@ -3019,7 +3019,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
ZopeTestCase
.
_print
(
message
)
LOG
(
'Testing... '
,
0
,
message
)
self
.
TryUserMessageContainingNoTracebackIsStillSent
(
'SQLDict'
)
def
TryNotificationSavedOnEventLogWhenSiteErrorLoggerRaises
(
self
,
activity
):
# Make sure that no active object is installed.
activity_tool
=
self
.
getPortal
().
portal_activities
...
...
@@ -3056,7 +3056,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
self
.
commit
()
for
log_record
in
self
.
logged
:
if
log_record
.
name
==
'ActivityTool'
and
log_record
.
levelname
==
'WARNING'
:
type
,
value
,
trace
=
log_record
.
exc_info
type
,
value
,
trace
=
log_record
.
exc_info
self
.
assertTrue
(
activity_unit_test_error
is
value
)
finally
:
SiteErrorLog
.
raising
=
original_raising
...
...
@@ -3100,7 +3100,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
conn
.
db
().
invalidate
({
oid
:
tid
})
except
TypeError
:
conn
.
db
().
invalidate
(
tid
,
{
oid
:
tid
})
activity_tool
.
__class__
.
doSomething
=
doSomething
activity_tool
.
activate
(
activity
=
'SQLQueue'
).
doSomething
()
self
.
commit
()
...
...
This diff is collapsed.
Click to expand it.
product/CMFCategory/Category.py
View file @
41f9cd88
...
...
@@ -282,7 +282,7 @@ class Category(Folder):
are not included.
DEPRECATED, use filter_node=1 if you don't want
to display categories having childs.
is_self_excluded - if set to 1, exclude this category from the list
sort_on, sort_order - the same semantics as ZSQLCatalog
...
...
@@ -408,7 +408,7 @@ class Category(Folder):
given list of base categories. Uses getTitleOrId as default method
"""
return
self
.
getCategoryChildItemList
(
recursive
=
recursive
,
display_id
=
'title_or_id'
,
base
=
base
,
**
kw
)
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getCategoryChildTitleAndIdItemList'
)
def
getCategoryChildTitleAndIdItemList
(
self
,
recursive
=
1
,
base
=
0
,
**
kw
):
...
...
@@ -429,7 +429,7 @@ class Category(Folder):
return
self
.
getCategoryChildItemList
(
recursive
=
recursive
,
display_id
=
'compact_title'
,
base
=
base
,
**
kw
)
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getCategoryChildTranslatedCompactTitleItemList'
)
def
getCategoryChildTranslatedCompactTitleItemList
(
self
,
recursive
=
1
,
base
=
0
,
**
kw
):
...
...
@@ -451,7 +451,7 @@ class Category(Folder):
return
self
.
getCategoryChildItemList
(
recursive
=
recursive
,
display_id
=
'logical_path'
,
base
=
base
,
**
kw
)
def
getCategoryChildTranslatedLogicalPathItemList
(
self
,
recursive
=
1
,
base
=
0
,
**
kw
):
"""
...
...
@@ -485,7 +485,7 @@ class Category(Folder):
return
self
.
getCategoryChildItemList
(
recursive
=
recursive
,
display_id
=
'compact_logical_path'
,
base
=
base
,
**
kw
)
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getCategoryChildIndentedTitleItemList'
)
def
getCategoryChildIndentedTitleItemList
(
self
,
...
...
@@ -583,13 +583,13 @@ class Category(Folder):
if
not
cache
:
return
_renderCategoryChildItemList
(
recursive
=
recursive
,
base
=
base
,
**
kw
)
# If checked_permission is specified, we include the username in the
# cache key
username
=
None
if
'checked_permission'
in
kw
:
username
=
str
(
getSecurityManager
().
getUser
())
# Some methods are language dependent so we include the language in the
# key
localizer
=
getToolByName
(
self
,
'Localizer'
)
...
...
@@ -672,7 +672,7 @@ class Category(Folder):
if
strict_membership
:
sql_text
=
'(%s.category_uid = %s AND %s.base_category_uid = %s '
\
'AND %s.category_strict_membership = 1)'
%
\
(
table
,
self
.
getUid
(),
table
,
(
table
,
self
.
getUid
(),
table
,
base_category
.
getBaseCategoryUid
(),
table
)
else
:
sql_text
=
'(%s.category_uid = %s AND %s.base_category_uid = %s)'
%
\
...
...
@@ -684,7 +684,7 @@ class Category(Folder):
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'asSqlExpression'
)
asSqlExpression
=
asSQLExpression
# A Category's categories is self
...
...
@@ -701,11 +701,11 @@ class Category(Folder):
"""
Tests if an object if member of a given category
Category is a string here. It could be more than a string (ex. an object)
Keywords parameters :
Keywords parameters :
- strict_membership: if we want strict membership checking
- strict : alias for strict_membership (deprecated but still here for
- strict : alias for strict_membership (deprecated but still here for
skins backward compatibility. )
"""
strict_membership
=
kw
.
get
(
'strict_membership'
,
kw
.
get
(
'strict'
,
0
))
if
strict_membership
:
...
...
@@ -821,7 +821,7 @@ class BaseCategory(Category):
# sql_text += ' OR %s' % o.asSQLExpression()
return
sql_text
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getBaseCategoryId'
)
def
getBaseCategoryId
(
self
):
"""
...
...
@@ -831,7 +831,7 @@ class BaseCategory(Category):
"""
return
self
.
getBaseCategory
().
id
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getBaseCategoryUid'
)
def
getBaseCategoryUid
(
self
):
"""
...
...
@@ -841,7 +841,7 @@ class BaseCategory(Category):
"""
return
self
.
getBaseCategory
().
getUid
()
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getBaseCategoryValue'
)
def
getBaseCategoryValue
(
self
):
"""
...
...
@@ -879,13 +879,13 @@ class BaseCategory(Category):
local_sort_method - When using the default preorder traversal, use
this function to sort objects of the same depth.
local_sort_id - When using the default preorder traversal, sort
objects of the same depth by comparing their
'local_sort_id' property. local_sort_id can be a
list, in this case properties are compared in the
same order than this list.
Renderer parameters are also supported here.
"""
...
...
@@ -911,7 +911,7 @@ class BaseCategory(Category):
# sort objects at the current level
child_value_list
=
list
(
child_value_list
)
child_value_list
.
sort
(
local_sort_method
)
if
recursive
:
for
c
in
child_value_list
:
value_list
.
extend
(
c
.
getCategoryChildValueList
(
recursive
=
1
,
...
...
This diff is collapsed.
Click to expand it.
product/CMFCategory/CategoryTool.py
View file @
41f9cd88
...
...
@@ -638,7 +638,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
spec -- a list or a tuple of portal types
checked_permission -- a string which defined the permission
checked_permission -- a string which defined the permission
to filter the object on
"""
...
...
@@ -724,7 +724,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
spec -- a list or a tuple of portal types
checked_permission -- a string which defined the permission
checked_permission -- a string which defined the permission
to filter the object on
"""
...
...
@@ -748,7 +748,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getSingleCategoryMembershipList'
)
def
getSingleCategoryMembershipList
(
self
,
context
,
base_category
,
base
=
0
,
spec
=
(),
filter
=
None
,
spec
=
(),
filter
=
None
,
checked_permission
=
None
,
**
kw
):
"""
Returns the local membership of the context for a single base category
...
...
@@ -763,12 +763,12 @@ class CategoryTool( UniqueObject, Folder, Base ):
base -- if set to 1, returns relative URLs to portal_categories
if set to 0, returns relative URLs to the base category
checked_permission -- a string which defined the permission
checked_permission -- a string which defined the permission
to filter the object on
"""
# XXX We must use filters in the future
# where_expression = self._buildQuery(spec, filter, kw)
if
spec
is
():
if
spec
is
():
spec
=
kw
.
get
(
'portal_type'
,
())
# Build the ckecked_permission filter
...
...
@@ -903,7 +903,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
base -- if set to 1, returns relative URLs to portal_categories
if set to 0, returns relative URLs to the base category
checked_permission -- a string which defined the permission
checked_permission -- a string which defined the permission
to filter the object on
alt_base_category -- an alternative base category if the first one fails
...
...
@@ -1038,7 +1038,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
#if hasattr(my_acquisition_object, '_categories'): # This would be a bug since we have category acquisition
#LOG('my_acquisition_object',0, str(getattr(my_acquisition_object, '_categories', ())))
#LOG('my_acquisition_object',0, str(base_category))
# We should only consider objects which define that category
if
base_category
in
getattr
(
my_acquisition_object
,
'_categories'
,
())
or
base_category_value
.
getFallbackBaseCategoryList
():
if
(
not
acquired_portal_type
)
or
my_acquisition_object
.
portal_type
in
acquired_portal_type
:
...
...
@@ -1523,7 +1523,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
if
result
==
''
:
result
=
[]
return
result
# SQL Expression Building
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'buildAdvancedSQLSelector'
)
def
buildAdvancedSQLSelector
(
self
,
category_list
,
query_table
=
'category'
,
...
...
@@ -1699,11 +1699,11 @@ class CategoryTool( UniqueObject, Folder, Base ):
# for example, with source/sale_order_module/1/1/1, because
# we do not want to acquire a Sale Order when a Line or a Cell is
# not present.
#
#
# I describe my own idea about the categorisation system in ERP5
# here, because I think it is important to understand why
# resolveCategory is implemented in this way.
#
#
# The goal of resolveCategory is to provide either a conceptual
# element or a concrete element from a certain viewpoint. There
# are 5 different actors in this system:
...
...
@@ -1713,24 +1713,24 @@ class CategoryTool( UniqueObject, Folder, Base ):
# - Certain view (= Category)
# - Classification of documents (= Module or Tool)
# - Document (= Document)
#
#
# Categories are conceptually a tree structure with the root
# of Category Tool. The next level is always Base Categories,
# to represent abstract concepts. The deeper going down in a tree,
# the more concrete a viewpoint is.
#
#
# Base Categories may contain other Base Categories, because an
# abstract concept can be a part of another abstract concept,
# simply representing a multi-level concept. Base Categories may
# contain Categories, because an abstract concept gets more concrete.
# This is the same for Modules and Tools.
#
#
# Categories may contain Categories only in a way that views
# are more concrete downwards. Thus a category may not acquire
# a Base Category or a upper-level category. Also, Categories
# may not contain Modules or Tools, because they don't narrow
# views.
#
#
# In a sense, Modules and Tools are similar to Categories,
# as they do narrow things down, but they are fundamentally
# different from Categories, because their purpose is to
...
...
@@ -1739,10 +1739,10 @@ class CategoryTool( UniqueObject, Folder, Base ):
# procedures by more abstract viewpoints. The difference between
# Modules and Tools are about whether procedures are business
# oriented or system oriented.
#
#
# Documents may contain Documents, but only to a downward direction.
# Otherwise, things get more abstract in a tree.
#
#
# According to those ideas, the current implementation may not
# always behave correctly, because you can resolve a category
# which violates the rules. For example, you can resolve
...
...
@@ -1805,7 +1805,7 @@ class CategoryTool( UniqueObject, Folder, Base ):
obj
=
restrictedGetOb
(
obj
,
key
,
default
)
if
obj
is
None
:
LOG
(
'CMFCategory'
,
WARNING
,
LOG
(
'CMFCategory'
,
WARNING
,
'Could not access object %s'
%
relative_url
)
if
cache
is
not
None
:
...
...
This diff is collapsed.
Click to expand it.
product/CMFCategory/Filter.py
View file @
41f9cd88
...
...
@@ -41,7 +41,7 @@ class Filter(Implicit):
filter_method - filter_method allows for extending filtering in an arbitrary way
filter_method must be provided with a category and should
return 0 (False) or 1 (True)
"""
#LOG('Filter __init__', 0, 'self = %s, spec = %s, filter = %s, portal_type = %s' % (str(self), str(spec), str(filter), str(portal_type)))
if
type
(
filter
)
is
type
({}):
...
...
This diff is collapsed.
Click to expand it.
product/CMFCategory/Renderer.py
View file @
41f9cd88
...
...
@@ -223,7 +223,7 @@ class Renderer(Filter):
base_category_display_method_id
=
'getTitleOrId'
# If we are asked a translated version, display translated title of the
# base category
if
self
.
translate_display
or
(
self
.
display_id
and
if
self
.
translate_display
or
(
self
.
display_id
and
'translated'
in
self
.
display_id
.
lower
()):
base_category_display_method_id
=
'getTranslatedTitleOrId'
if
self
.
base_category
:
...
...
This diff is collapsed.
Click to expand it.
product/CMFCategory/tests/testCMFCategory.py
View file @
41f9cd88
...
...
@@ -693,10 +693,10 @@ class TestCMFCategory(ERP5TypeTestCase):
c11
=
c1
.
newContent
(
portal_type
=
'Category'
,
id
=
'1.1'
)
c111
=
c11
.
newContent
(
portal_type
=
'Category'
,
id
=
'1.1.1'
)
c2
=
bc
.
newContent
(
portal_type
=
'Category'
,
id
=
'2'
)
self
.
assertSameSet
(
bc
.
getCategoryChildValueList
(),
(
c1
,
c11
,
c111
,
c2
))
self
.
assertSameSet
(
c1
.
getCategoryChildValueList
(),
(
c11
,
c111
,))
# recursive
self
.
assertSameSet
(
bc
.
getCategoryChildValueList
(
recursive
=
0
),
(
c1
,
c2
))
self
.
assertSameSet
(
c1
.
getCategoryChildValueList
(
recursive
=
0
),
(
c11
,
))
...
...
@@ -723,12 +723,12 @@ class TestCMFCategory(ERP5TypeTestCase):
c12
=
c1
.
newContent
(
portal_type
=
'Category'
,
id
=
'1.2'
,
int_index
=
3
,
title
=
'Z'
)
c2
=
bc
.
newContent
(
portal_type
=
'Category'
,
id
=
'2'
,
int_index
=
30
,
title
=
'B'
)
c3
=
bc
.
newContent
(
portal_type
=
'Category'
,
id
=
'3'
,
int_index
=
20
,
title
=
'A'
)
# the default ordering is preorder:
self
.
assertEqual
(
list
(
bc
.
getCategoryChildValueList
()),
[
c1
,
c11
,
c111
,
c12
,
c2
,
c3
])
self
.
assertEqual
(
list
(
c1
.
getCategoryChildValueList
()),
[
c11
,
c111
,
c12
])
# but this order can be controlled for categories of the same depth, ie. we
# can sort each level independantly (this is different from sort_on /
# sort_order which sort the whole list regardless of the original
...
...
@@ -771,12 +771,12 @@ class TestCMFCategory(ERP5TypeTestCase):
local_sort_id
=
[
'int_index'
,
'title'
])),
[
c2
,
c1
,
c12
,
c11
,
c111
,
c3
])
def
test_25_getCategoryChildItemList_base_parameter
(
self
):
pc
=
self
.
getCategoriesTool
()
bc
=
pc
.
newContent
(
portal_type
=
'Base Category'
,
id
=
'foo'
)
c1
=
bc
.
newContent
(
portal_type
=
'Category'
,
id
=
'1'
,
title
=
'C'
)
self
.
assertEqual
([[
''
,
''
],
[
'C'
,
'1'
]],
bc
.
getCategoryChildTitleItemList
())
self
.
assertEqual
([[
''
,
''
],
[
'C'
,
'1'
]],
...
...
@@ -786,7 +786,7 @@ class TestCMFCategory(ERP5TypeTestCase):
self
.
assertEqual
([[
''
,
''
],
[
'C'
,
'bar/foo/1'
]],
bc
.
getCategoryChildTitleItemList
(
base
=
'bar'
))
def
test_getSingleCategoryAcquiredMembershipList
(
self
):
pc
=
self
.
getCategoriesTool
()
obj
=
self
.
portal
.
person_module
.
newContent
(
portal_type
=
'Person'
)
...
...
@@ -863,7 +863,7 @@ class TestCMFCategory(ERP5TypeTestCase):
pc
.
getSingleCategoryMembershipList
(
obj
,
'parent'
,
checked_permission
=
'Manage portal'
,
portal_type
=
'Person Module'
))
def
test_28_getCategoryChildItemList_checked_permission
(
self
):
pc
=
self
.
getCategoriesTool
()
...
...
@@ -874,7 +874,7 @@ class TestCMFCategory(ERP5TypeTestCase):
b1
=
b
.
newContent
(
portal_type
=
'Category'
,
id
=
'21'
,
title
=
'B1'
)
checked_permission
=
'View'
self
.
assertEqual
(
[[
''
,
''
],
[
'A'
,
'1'
],
[
'B'
,
'2'
],
[
'B1'
,
'2/21'
]],
bc
.
getCategoryChildTitleItemList
(
cache
=
0
))
...
...
@@ -884,21 +884,21 @@ class TestCMFCategory(ERP5TypeTestCase):
cache
=
0
))
b
.
manage_permission
(
checked_permission
,
roles
=
[],
acquire
=
0
)
self
.
assertEqual
(
3
,
len
(
bc
.
getCategoryChildValueList
(
cache
=
0
)))
self
.
assertEqual
(
1
,
len
(
bc
.
getCategoryChildValueList
(
checked_permission
=
checked_permission
,
cache
=
0
)))
self
.
assertEqual
(
[
'%s/1'
%
bc_id
,
'%s/2'
%
bc_id
,
'%s/2/21'
%
bc_id
],
bc
.
getCategoryChildRelativeUrlList
())
self
.
assertEqual
(
[
'%s/1'
%
bc_id
],
bc
.
getCategoryChildRelativeUrlList
(
checked_permission
=
checked_permission
))
self
.
assertEqual
(
[[
''
,
''
],
[
'A'
,
'1'
],
[
'B'
,
'2'
],
[
'B1'
,
'2/21'
]],
bc
.
getCategoryChildTitleItemList
(
cache
=
0
))
...
...
@@ -908,7 +908,7 @@ class TestCMFCategory(ERP5TypeTestCase):
cache
=
0
))
def
test_28b_getCategoryChildItemList_checked_permission_cache
(
self
):
# getCategoryChildTitleItemList take into account user
# getCategoryChildTitleItemList take into account user
pc
=
self
.
getCategoriesTool
()
bc_id
=
'barfoo'
...
...
@@ -923,13 +923,13 @@ class TestCMFCategory(ERP5TypeTestCase):
login
=
PortalTestCase
.
login
checked_permission
=
'View'
b
.
manage_permission
(
checked_permission
,
roles
=
[
'Assignor'
],
acquire
=
0
)
login
(
self
,
'alice'
)
self
.
assertEqual
(
[[
''
,
''
],
[
'A'
,
'1'
],
[
'B'
,
'2'
],
[
'B1'
,
'2/21'
]],
bc
.
getCategoryChildTitleItemList
(
checked_permission
=
checked_permission
,))
login
(
self
,
'bob'
)
self
.
assertEqual
(
[[
''
,
''
],
[
'A'
,
'1'
]],
...
...
@@ -1057,7 +1057,7 @@ class TestCMFCategory(ERP5TypeTestCase):
self
.
assertEqual
(
person
.
getRegion
(),
'europe/west'
)
self
.
assertEqual
(
person
.
getRegionList
(),
[
'europe/west'
])
# Let's continue with resource because its ID conflict with
# Let's continue with resource because its ID conflict with
# "traversing namespaces" names
resource_value
=
portal_categories
.
resource
.
newContent
(
portal_type
=
'Category'
,
id
=
'id1'
)
organisation
.
setResource
(
'resource/id1'
)
...
...
This diff is collapsed.
Click to expand it.
product/ERP5/AggregatedAmountList.py
View file @
41f9cd88
...
...
@@ -58,13 +58,13 @@ class AggregatedAmountList(list):
"""
Return total duration
"""
result
=
sum
(
filter
(
lambda
y
:
y
is
not
None
,
result
=
sum
(
filter
(
lambda
y
:
y
is
not
None
,
map
(
lambda
x
:
x
.
getDuration
(),
self
)))
return
result
def
multiplyQuantity
(
self
,
context
=
None
):
"""
Take into account the quantity of the
Take into account the quantity of the
context. Change the quantity of each element.
"""
quantity
=
None
...
...
This diff is collapsed.
Click to expand it.
product/ERP5/Capacity/GLPK.py
View file @
41f9cd88
...
...
@@ -76,11 +76,11 @@ def writeModelFile(file, matrix, point):
"""
n
=
shape
(
matrix
)[
0
]
d
=
shape
(
matrix
)[
1
]
file
.
write
(
MODEL_HEAD
)
file
.
write
(
"param n := %d;
\n
"
%
n
)
file
.
write
(
"param d := %d;
\n
"
%
d
)
file
.
write
(
"param s
\n
:
\t
"
)
def
insertTab
(
x
,
y
):
return
str
(
x
)
+
"
\t
"
+
str
(
y
)
file
.
write
(
reduce
(
insertTab
,
range
(
1
,
d
+
1
)))
...
...
@@ -90,16 +90,16 @@ def writeModelFile(file, matrix, point):
file
.
write
(
reduce
(
insertTab
,
matrix
[
i
],
""
))
file
.
write
(
"
\n
"
)
file
.
write
(
";
\n
"
)
file
.
write
(
"param q := "
)
def
insertComma
(
x
,
y
):
return
str
(
x
)
+
','
+
str
(
y
)
def
flatten
(
x
):
return
str
(
x
[
0
])
+
' '
+
str
(
x
[
1
])
file
.
write
(
reduce
(
insertComma
,
map
(
flatten
,
map
(
None
,
range
(
1
,
d
+
1
),
point
))))
file
.
write
(
";
\n
"
)
file
.
write
(
MODEL_TAIL
)
def
getOptimalValue
(
file
):
"""
Solve an LP problem described in MathProg language, and return
...
...
@@ -150,8 +150,8 @@ if __name__ == '__main__':
[
60
,
61
,
62
,
63
,
64
,
65
],
[
70
,
71
,
72
,
73
,
74
,
75
]])
print
m
p
=
([
1
,
2
,
3
,
4
,
5
,
6
])
print
p
print
solve
(
m
,
p
)
This diff is collapsed.
Click to expand it.
product/ERP5/Constraint/AccountTypeConstraint.py
View file @
41f9cd88
...
...
@@ -36,7 +36,7 @@ class AccountTypeConstraint(Constraint):
category is consistent with the gap category.
This is an abstract class, subclasses have to define a mapping as a class
attribute, like this one:
_account_type_map = (
('gap/fr/pcg/4/40', ('liability/payable',),
('gap/fr/pcg/4', ('asset/receivable', 'liability/payable'),
...
...
@@ -50,9 +50,9 @@ class AccountTypeConstraint(Constraint):
This constraints supports fixing consistency.
"""
_message_id_list
=
[
'message_inconsistent_account_type'
]
message_inconsistent_account_type
=
translateString
(
'Account is member'
' of ${category}, this should have account_type'
...
...
This diff is collapsed.
Click to expand it.
product/ERP5/Constraint/AccountingTransactionBalance.py
View file @
41f9cd88
...
...
@@ -32,10 +32,10 @@ translateString = lambda msg: msg # just to extract messages
class
AccountingTransactionBalance
(
Constraint
):
"""Check that accounting transaction total debit and total credit are equals.
"""
_message_id_list
=
[
'message_transaction_not_balanced_for_source'
,
'message_transaction_not_balanced_for_destination'
]
message_transaction_not_balanced_for_source
=
translateString
(
'Transaction is not balanced for ${section_title}'
)
message_transaction_not_balanced_for_destination
=
translateString
(
...
...
@@ -57,7 +57,7 @@ class AccountingTransactionBalance(Constraint):
section
=
line
.
getDestinationSectionValue
()
destination_sum
[
section
]
=
destination_sum
.
get
(
section
,
0
)
+
\
(
line
.
getDestinationInventoriatedTotalAssetPrice
()
or
0
)
for
section
,
total
in
source_sum
.
items
():
precision
=
2
if
section
is
not
None
and
\
...
...
@@ -70,7 +70,7 @@ class AccountingTransactionBalance(Constraint):
'message_transaction_not_balanced_for_source'
),
mapping
=
dict
(
section_title
=
section
.
getTranslatedTitle
())))
break
for
section
,
total
in
destination_sum
.
items
():
precision
=
2
if
section
is
not
None
and
\
...
...
This diff is collapsed.
Click to expand it.
product/ERP5/Constraint/DocumentReferenceConstraint.py
View file @
41f9cd88
...
...
@@ -44,7 +44,7 @@ class DocumentReferenceConstraint(Constraint):
_message_id_list
=
[
'message_property_not_defined'
,
'message_another_document_exists'
,
'message_multiple_documents_exists'
]
message_property_not_defined
=
translateString
(
'Property ${property_id} was not defined'
)
message_another_document_exists
=
translateString
(
...
...
This diff is collapsed.
Click to expand it.
product/ERP5/Constraint/TransactionQuantityValueFeasability.py
View file @
41f9cd88
...
...
@@ -43,9 +43,9 @@ class TransactionQuantityValueFeasability(Constraint):
source_cell
=
object
.
getSourceValue
()
destination_cell
=
object
.
getDestinationValue
()
# Check for source and destination
for
node
,
sign
,
node_title
in
((
source_cell
,
1
,
'source'
),
for
node
,
sign
,
node_title
in
((
source_cell
,
1
,
'source'
),
(
destination_cell
,
-
1
,
'destination'
)):
# As the quantity can change a few lines letter,
# As the quantity can change a few lines letter,
# we need to get it each time.
object_quantity
=
object
.
getQuantity
()
quantity
=
object_quantity
*
sign
...
...
This diff is collapsed.
Click to expand it.
Prev
1
2
3
4
5
…
30
Next
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