Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
7
Merge Requests
7
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
Jérome Perrin
erp5
Commits
918c8dd7
Commit
918c8dd7
authored
Oct 16, 2022
by
Kazuhiko Shiozaki
Committed by
Jérome Perrin
May 20, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
py2/py3: use six.moves.urllib for compatibility.
parent
4eb71e3b
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
56 additions
and
54 deletions
+56
-54
bt5/erp5_barcode/ExtensionTemplateItem/portal_components/extension.erp5.Barcode.py
...nTemplateItem/portal_components/extension.erp5.Barcode.py
+1
-1
bt5/erp5_commerce/ExtensionTemplateItem/portal_components/extension.erp5.Ecommerce.py
...emplateItem/portal_components/extension.erp5.Ecommerce.py
+2
-2
bt5/erp5_knowledge_pad/ExtensionTemplateItem/portal_components/extension.erp5.GetRssDataAsDict.py
...Item/portal_components/extension.erp5.GetRssDataAsDict.py
+4
-4
bt5/erp5_palo/DocumentTemplateItem/portal_components/document.erp5.PALOETLConnection.py
...Item/portal_components/document.erp5.PALOETLConnection.py
+2
-2
bt5/erp5_paypal_secure_payment/DocumentTemplateItem/portal_components/document.erp5.PaypalService.py
...lateItem/portal_components/document.erp5.PaypalService.py
+2
-2
bt5/erp5_run_my_doc/ExtensionTemplateItem/portal_components/extension.erp5.ERP5RunMyDocs.py
...ateItem/portal_components/extension.erp5.ERP5RunMyDocs.py
+2
-2
bt5/erp5_short_message/DocumentTemplateItem/portal_components/document.erp5.EssendexGateway.py
...teItem/portal_components/document.erp5.EssendexGateway.py
+13
-12
bt5/erp5_short_message/DocumentTemplateItem/portal_components/document.erp5.MobytGateway.py
...plateItem/portal_components/document.erp5.MobytGateway.py
+8
-7
bt5/erp5_sso_openam/ExtensionTemplateItem/portal_components/extension.erp5.OpenAMLogout.py
...lateItem/portal_components/extension.erp5.OpenAMLogout.py
+2
-2
bt5/erp5_test_result/DocumentTemplateItem/portal_components/document.erp5.GitlabRESTConnector.py
...em/portal_components/document.erp5.GitlabRESTConnector.py
+1
-1
bt5/erp5_ui_test_core/ExtensionTemplateItem/portal_components/extension.erp5.ERP5Zuite.py
...emplateItem/portal_components/extension.erp5.ERP5Zuite.py
+2
-2
bt5/erp5_web/DocumentTemplateItem/portal_components/document.erp5.StaticWebSection.py
...eItem/portal_components/document.erp5.StaticWebSection.py
+2
-2
bt5/erp5_wechat_secure_payment/DocumentTemplateItem/portal_components/document.erp5.WechatService.py
...lateItem/portal_components/document.erp5.WechatService.py
+6
-5
product/ERP5Type/patches/urllib_opener.py
product/ERP5Type/patches/urllib_opener.py
+9
-9
product/ERP5Type/tests/ERP5TypeTestCase.py
product/ERP5Type/tests/ERP5TypeTestCase.py
+0
-1
No files found.
bt5/erp5_barcode/ExtensionTemplateItem/portal_components/extension.erp5.Barcode.py
View file @
918c8dd7
...
...
@@ -26,7 +26,7 @@
##############################################################################
import
os
from
urllib
import
urlencode
from
six.moves.urllib.parse
import
urlencode
import
tempfile
from
DateTime
import
DateTime
from
zLOG
import
LOG
...
...
bt5/erp5_commerce/ExtensionTemplateItem/portal_components/extension.erp5.Ecommerce.py
View file @
918c8dd7
from
Products.ERP5Type.UnrestrictedMethod
import
UnrestrictedMethod
import
urllib
from
six.moves.urllib.parse
import
urlencode
import
mechanize
def
getProductPrice
(
product
):
...
...
@@ -9,7 +9,7 @@ def getProductPrice(product):
def
submitPaypalNVPRequest
(
parameter_dict
,
nvp_url
):
request
=
mechanize
.
Request
(
nvp_url
)
params
=
url
lib
.
url
encode
(
parameter_dict
)
params
=
urlencode
(
parameter_dict
)
try
:
response
=
mechanize
.
urlopen
(
request
,
data
=
params
)
except
:
...
...
bt5/erp5_knowledge_pad/ExtensionTemplateItem/portal_components/extension.erp5.GetRssDataAsDict.py
View file @
918c8dd7
import
feedparser
,
urllib2
,
socket
import
feedparser
,
six
.
moves
.
urllib
.
request
,
six
.
moves
.
urllib
.
error
,
socket
from
hashlib
import
md5
def
getRssDataAsDict
(
context
,
url
,
username
=
None
,
password
=
None
):
...
...
@@ -11,9 +11,9 @@ def getRssDataAsDict(context, url, username=None, password=None):
# use authentication or not?
handlers
=
[]
if
username
is
not
None
and
password
is
not
None
:
passman
=
urllib2
.
HTTPPasswordMgrWithDefaultRealm
()
passman
=
six
.
moves
.
urllib
.
request
.
HTTPPasswordMgrWithDefaultRealm
()
passman
.
add_password
(
None
,
url
,
username
,
password
)
auth_handler
=
urllib2
.
HTTPBasicAuthHandler
(
passman
)
auth_handler
=
six
.
moves
.
urllib
.
request
.
HTTPBasicAuthHandler
(
passman
)
handlers
.
append
(
auth_handler
)
# set shorter timeouts and revert default at enf of read
...
...
@@ -24,7 +24,7 @@ def getRssDataAsDict(context, url, username=None, password=None):
finally
:
socket
.
setdefaulttimeout
(
default_timeout
)
if
d
.
bozo
and
isinstance
(
d
.
bozo_exception
,
urllib2
.
URLError
):
if
d
.
bozo
and
isinstance
(
d
.
bozo_exception
,
six
.
moves
.
urllib
.
error
.
URLError
):
# we have an URL error
return
{
'status'
:
-
2
}
elif
d
.
bozo
:
...
...
bt5/erp5_palo/DocumentTemplateItem/portal_components/document.erp5.PALOETLConnection.py
View file @
918c8dd7
...
...
@@ -26,7 +26,7 @@
##############################################################################
import
suds
import
urllib2
import
six.moves.urllib.request
import
ssl
import
lxml.etree
...
...
@@ -39,7 +39,7 @@ from suds.transport.https import HttpAuthenticated
class
HTTPAuthenticatedUnverifiedSSL
(
HttpAuthenticated
):
def
u2handlers
(
self
):
handlers
=
[
urllib2
.
HTTPSHandler
(
context
=
ssl
.
_create_unverified_context
())
]
handlers
=
[
six
.
moves
.
urllib
.
request
.
HTTPSHandler
(
context
=
ssl
.
_create_unverified_context
())
]
handlers
.
extend
(
HttpAuthenticated
.
u2handlers
(
self
))
return
handlers
...
...
bt5/erp5_paypal_secure_payment/DocumentTemplateItem/portal_components/document.erp5.PaypalService.py
View file @
918c8dd7
...
...
@@ -27,8 +27,8 @@
#
##############################################################################
import
zope
from
urllib
import
urlencode
from
urllib2
import
urlopen
,
Request
from
six.moves.urllib.parse
import
urlencode
from
six.moves.urllib.request
import
urlopen
,
Request
from
zLOG
import
LOG
,
DEBUG
from
AccessControl
import
ClassSecurityInfo
from
Products.ERP5Type
import
Permissions
,
PropertySheet
...
...
bt5/erp5_run_my_doc/ExtensionTemplateItem/portal_components/extension.erp5.ERP5RunMyDocs.py
View file @
918c8dd7
...
...
@@ -57,8 +57,8 @@ def parseTestReport(text):
Return the content of a web page
"""
def
urlread
(
url
):
import
urllib
return
url
lib
.
url
open
(
url
).
read
()
from
six.moves.urllib.request
import
urlopen
return
urlopen
(
url
).
read
()
"""
Remove everything but the test in a webpage
...
...
bt5/erp5_short_message/DocumentTemplateItem/portal_components/document.erp5.EssendexGateway.py
View file @
918c8dd7
...
...
@@ -29,7 +29,8 @@
"""Receive or send SMS"""
#Import python module
import
urllib
from
six.moves.urllib.parse
import
urlencode
,
unquote
from
six.moves.urllib.request
import
urlopen
from
lxml
import
etree
from
DateTime
import
DateTime
...
...
@@ -91,14 +92,14 @@ class EssendexGateway(XMLObject):
if
len
(
parts
)
==
1
:
data
=
parts
[
0
].
split
(
'='
)
#Remove \n et \r from value
result
[
data
[
0
]]
=
u
rllib
.
u
nquote
(
data
[
1
].
replace
(
'
\
r
'
,
''
).
replace
(
'
\
n
'
,
''
))
result
[
data
[
0
]]
=
unquote
(
data
[
1
].
replace
(
'
\
r
'
,
''
).
replace
(
'
\
n
'
,
''
))
else
:
#Mutil values
subresult
=
{}
for
part
in
parts
:
data
=
part
.
split
(
'='
)
subresult
[
data
[
0
]]
=
u
rllib
.
u
nquote
(
data
[
1
].
replace
(
'
\
r
'
,
''
).
replace
(
'
\
n
'
,
''
))
subresult
[
data
[
0
]]
=
unquote
(
data
[
1
].
replace
(
'
\
r
'
,
''
).
replace
(
'
\
n
'
,
''
))
result
[
index
]
=
subresult
#Increment index for next
index
+=
1
...
...
@@ -161,8 +162,8 @@ class EssendexGateway(XMLObject):
params
[
'Test'
]
=
1
LOG
(
"EssendexGateway"
,
INFO
,
params
)
params
=
url
lib
.
url
encode
(
params
)
page
=
url
lib
.
url
open
(
base_url
,
params
)
params
=
urlencode
(
params
)
page
=
urlopen
(
base_url
,
params
)
result
=
self
.
_fetchPageAsDict
(
page
)
if
result
[
'Result'
]
==
"OK"
:
message_ids
=
result
.
get
(
'MessageIDs'
,
""
)
...
...
@@ -171,7 +172,7 @@ class EssendexGateway(XMLObject):
return
message_ids
.
split
(
","
)
elif
result
[
'Result'
]
==
"Error"
:
#we get an error when call the gateway
raise
SMSGatewayError
(
u
rllib
.
u
nquote
(
result
.
get
(
'Message'
,
"Impossible to send the SMS"
)))
raise
SMSGatewayError
(
unquote
(
result
.
get
(
'Message'
,
"Impossible to send the SMS"
)))
elif
result
[
'Result'
]
==
"Test"
:
#just a test, no message id
return
None
...
...
@@ -190,15 +191,15 @@ class EssendexGateway(XMLObject):
'MessageID'
:
message_id
,
}
params
=
url
lib
.
url
encode
(
params
)
page
=
url
lib
.
url
open
(
base_url
,
params
)
params
=
urlencode
(
params
)
page
=
urlopen
(
base_url
,
params
)
result
=
self
.
_fetchPageAsDict
(
page
)
if
result
[
'Result'
]
==
"OK"
:
return
result
.
get
(
'MessageStatus'
).
lower
()
elif
result
[
'Result'
]
==
"Error"
:
#we get an error when call the gateway
raise
SMSGatewayError
(
u
rllib
.
u
nquote
(
result
.
get
(
'Message'
,
"Impossible to get the message status"
)))
raise
SMSGatewayError
(
unquote
(
result
.
get
(
'Message'
,
"Impossible to get the message status"
)))
security
.
declarePublic
(
'receive'
)
def
receive
(
self
,
REQUEST
,
**
kw
):
...
...
@@ -319,8 +320,8 @@ class EssendexGateway(XMLObject):
params
[
'Test'
]
=
1
LOG
(
"EssendexGateway"
,
INFO
,
params
)
params
=
url
lib
.
url
encode
(
params
)
page
=
url
lib
.
url
open
(
base_url
,
params
)
params
=
urlencode
(
params
)
page
=
urlopen
(
base_url
,
params
)
result
=
self
.
_fetchPageAsDict
(
page
)
if
result
[
'Result'
]
==
"OK"
:
...
...
@@ -344,6 +345,6 @@ class EssendexGateway(XMLObject):
LOG
(
"EssendexGateway"
,
INFO
,
result
)
elif
result
[
'Result'
]
==
"Error"
:
#we get an error when call the gateway
raise
SMSGatewayError
(
u
rllib
.
u
nquote
(
result
.
get
(
'Message'
,
"Impossible to get last message list"
)))
raise
SMSGatewayError
(
unquote
(
result
.
get
(
'Message'
,
"Impossible to get last message list"
)))
bt5/erp5_short_message/DocumentTemplateItem/portal_components/document.erp5.MobytGateway.py
View file @
918c8dd7
...
...
@@ -29,7 +29,8 @@
"""Receive or send SMS"""
#Import python module
import
urllib
from
six.moves.urllib.parse
import
urlencode
,
unquote
from
six.moves.urllib.request
import
urlopen
from
DateTime
import
DateTime
#Import Zope module
...
...
@@ -179,8 +180,8 @@ class MobytGateway(XMLObject):
LOG
(
"MobytGateway"
,
INFO
,
params
)
result
=
{
'status'
:
"Test"
}
else
:
params
=
url
lib
.
url
encode
(
params
)
page
=
url
lib
.
url
open
(
base_url
,
params
)
params
=
urlencode
(
params
)
page
=
urlopen
(
base_url
,
params
)
result
=
self
.
_fetchSendResponseAsDict
(
page
)
#Check result and return
...
...
@@ -188,7 +189,7 @@ class MobytGateway(XMLObject):
return
[
result
.
get
(
'status_info'
,
""
)]
#return message id (gateway side)
elif
result
[
'status'
]
==
"KO"
:
#we get an error when call the gateway
raise
SMSGatewayError
(
u
rllib
.
u
nquote
(
result
.
get
(
'status_info'
,
"Impossible to send the SMS"
)))
raise
SMSGatewayError
(
unquote
(
result
.
get
(
'status_info'
,
"Impossible to send the SMS"
)))
elif
result
[
'status'
]
==
"Test"
:
#just a test, no message id
return
None
...
...
@@ -206,8 +207,8 @@ class MobytGateway(XMLObject):
"type"
:
'notify'
,
"schema"
:
1
}
params
=
url
lib
.
url
encode
(
params
)
page
=
url
lib
.
url
open
(
base_url
,
params
)
params
=
urlencode
(
params
)
page
=
urlopen
(
base_url
,
params
)
result
=
self
.
_fetchStatusResponseAsDict
(
page
)
if
result
[
'status'
]
==
"OK"
:
...
...
@@ -223,7 +224,7 @@ class MobytGateway(XMLObject):
elif
result
[
'status'
]
==
"KO"
:
#we get an error when call the gateway
raise
SMSGatewayError
(
u
rllib
.
u
nquote
(
result
.
get
(
'status_info'
,
"Impossible to get the message status"
)))
raise
SMSGatewayError
(
unquote
(
result
.
get
(
'status_info'
,
"Impossible to get the message status"
)))
security
.
declarePublic
(
'receive'
)
def
receive
(
self
,
REQUEST
):
...
...
bt5/erp5_sso_openam/ExtensionTemplateItem/portal_components/extension.erp5.OpenAMLogout.py
View file @
918c8dd7
...
...
@@ -25,8 +25,8 @@
#
##############################################################################
from
urllib
import
urlencode
from
urllib2
import
urlopen
from
six.moves.urllib.parse
import
urlencode
from
six.moves.urllib.request
import
urlopen
def
OpenAMLogout
(
self
,
url
,
token
):
code
=
urlopen
(
url
,
urlencode
({
'subjectid'
:
token
})).
getcode
()
...
...
bt5/erp5_test_result/DocumentTemplateItem/portal_components/document.erp5.GitlabRESTConnector.py
View file @
918c8dd7
...
...
@@ -27,7 +27,7 @@
##############################################################################
from
urllib
import
quote_plus
from
six.moves.urllib.parse
import
quote_plus
from
six.moves.urllib.parse
import
urlparse
from
six.moves.urllib.parse
import
urljoin
import
logging
...
...
bt5/erp5_ui_test_core/ExtensionTemplateItem/portal_components/extension.erp5.ERP5Zuite.py
View file @
918c8dd7
...
...
@@ -41,9 +41,9 @@ def UpdateImage(image):
image
.
_update_image_info
()
def
urlread
(
url
,
safe_return
=
0
):
import
urllib
from
six.moves.urllib.request
import
urlopen
try
:
return
url
lib
.
url
open
(
url
).
read
()
return
urlopen
(
url
).
read
()
except
IOError
as
e
:
if
safe_return
:
# Return an Selenium test code that will obviously fail. This
...
...
bt5/erp5_web/DocumentTemplateItem/portal_components/document.erp5.StaticWebSection.py
View file @
918c8dd7
...
...
@@ -37,7 +37,7 @@ from Products.ERP5Type import Permissions
from
webdav.NullResource
import
NullResource
import
urllib
from
six.moves.urllib.parse
import
unquote
MARKER
=
[]
...
...
@@ -69,7 +69,7 @@ class StaticWebSection(WebSection):
# Drop the automatically added VirtualHostMonster object ID
virtual_url_part_tuple
=
request
.
get
(
'VIRTUAL_URL_PARTS'
,
None
)
if
(
virtual_url_part_tuple
is
not
None
)
and
\
(
not
u
rllib
.
u
nquote
(
virtual_url_part_tuple
[
-
1
]).
endswith
(
"/"
.
join
(
url_list
))):
(
not
unquote
(
virtual_url_part_tuple
[
-
1
]).
endswith
(
"/"
.
join
(
url_list
))):
url_list
.
pop
(
0
)
if
request
.
get
(
'ACTUAL_URL'
,
''
).
endswith
(
"/"
):
# or len(url_list) == 0:
...
...
bt5/erp5_wechat_secure_payment/DocumentTemplateItem/portal_components/document.erp5.WechatService.py
View file @
918c8dd7
...
...
@@ -3,7 +3,8 @@ from AccessControl import ClassSecurityInfo
from
Products.ERP5Type
import
Permissions
,
PropertySheet
from
Products.ERP5Type.XMLObject
import
XMLObject
from
zLOG
import
LOG
,
WARNING
import
random
,
string
,
hashlib
,
urllib2
,
socket
import
random
,
string
,
hashlib
,
socket
from
six.moves.urllib.request
import
Request
,
urlopen
from
six.moves.urllib.parse
import
urlparse
from
six
import
string_types
as
basestring
try
:
...
...
@@ -103,8 +104,8 @@ class WechatService(XMLObject):
params
[
'sign'
]
=
self
.
calculateSign
(
params
,
self
.
getServiceApiKey
())
LOG
(
'WechatService'
,
WARNING
,
"getSandboxKey : data = %s SANDBOX_KEY_URL = %s"
%
(
self
.
convert_dict_to_xml
(
params
),
SANDBOX_KEY_URL
),
error
=
False
)
result
=
urllib2
.
Request
(
SANDBOX_KEY_URL
,
data
=
self
.
convert_dict_to_xml
(
params
))
result_data
=
url
lib2
.
url
open
(
result
)
result
=
Request
(
SANDBOX_KEY_URL
,
data
=
self
.
convert_dict_to_xml
(
params
))
result_data
=
urlopen
(
result
)
result_read
=
result_data
.
read
()
result_dict_content
=
self
.
convert_xml_to_dict
(
result_read
)
return_code
=
result_dict_content
.
get
(
'return_code'
,
''
)
...
...
@@ -151,8 +152,8 @@ class WechatService(XMLObject):
LOG
(
'callWechatApi'
,
WARNING
,
"data = %s URL = %s"
%
(
self
.
convert_dict_to_xml
(
wechat_dict
),
wechat_url
+
URL
),
error
=
False
)
# send data
result
=
urllib2
.
Request
(
wechat_url
+
URL
,
data
=
self
.
convert_dict_to_xml
(
wechat_dict
))
result_data
=
url
lib2
.
url
open
(
result
)
result
=
Request
(
wechat_url
+
URL
,
data
=
self
.
convert_dict_to_xml
(
wechat_dict
))
result_data
=
urlopen
(
result
)
result_read
=
result_data
.
read
()
result_dict_content
=
self
.
convert_xml_to_dict
(
result_read
)
return_code
=
result_dict_content
[
'return_code'
]
...
...
product/ERP5Type/patches/urllib_opener.py
View file @
918c8dd7
...
...
@@ -36,7 +36,7 @@ import os
import
dircache
import
mimetypes
,
mimetools
from
email.utils
import
formatdate
class
DirectoryFileHandler
(
urllib2
.
FileHandler
):
class
DirectoryFileHandler
(
six
.
moves
.
urllib
.
request
.
FileHandler
):
"""
Extends the file handler to provide an HTML
representation of local directories.
...
...
@@ -55,7 +55,7 @@ class DirectoryFileHandler(urllib2.FileHandler):
def
open_local_file
(
self
,
req
):
host
=
req
.
get_host
()
file
=
req
.
get_selector
()
localfile
=
urllib2
.
url2pathname
(
file
)
localfile
=
six
.
moves
.
urllib
.
request
.
url2pathname
(
file
)
stats
=
os
.
stat
(
localfile
)
size
=
stats
.
st_size
modified
=
formatdate
(
stats
.
st_mtime
,
usegmt
=
True
)
...
...
@@ -64,7 +64,7 @@ class DirectoryFileHandler(urllib2.FileHandler):
'Content-type: %s
\
n
Content-length: %d
\
n
Last-modified: %s
\
n
'
%
(
mtype
or
'text/plain'
,
size
,
modified
)))
if
host
:
host
,
port
=
urllib
.
splitport
(
host
)
host
,
port
=
six
.
moves
.
urllib
.
parse
.
splitport
(
host
)
if
not
host
or
\
(
not
port
and
socket
.
gethostbyname
(
host
)
in
self
.
get_names
()):
try
:
...
...
@@ -73,16 +73,16 @@ class DirectoryFileHandler(urllib2.FileHandler):
s
.
write
(
'<html><head><base href="%s"/></head><body>'
%
(
'file:'
+
file
))
s
.
write
(
'<p>Directory Content:</p>'
)
for
f
in
file_list
:
s
.
write
(
'<p><a href="%s">%s</a></p>
\
n
'
%
(
urllib
.
quote
(
f
),
f
))
s
.
write
(
'<p><a href="%s">%s</a></p>
\
n
'
%
(
six
.
moves
.
urllib
.
parse
.
quote
(
f
),
f
))
s
.
write
(
'</body></html>'
)
s
.
seek
(
0
)
headers
=
mimetools
.
Message
(
StringIO
(
'Content-type: %s
\
n
Content-length: %d
\
n
Last-modified: %s
\
n
'
%
(
'text/html'
,
size
,
modified
)))
return
urllib2
.
addinfourl
(
s
,
headers
,
'file:'
+
file
)
return
six
.
moves
.
urllib
.
response
.
addinfourl
(
s
,
headers
,
'file:'
+
file
)
except
OSError
:
return
urllib2
.
addinfourl
(
open
(
localfile
,
'rb'
),
return
six
.
moves
.
urllib
.
response
.
addinfourl
(
open
(
localfile
,
'rb'
),
headers
,
'file:'
+
file
)
raise
urllib2
.
URLError
(
'file not on local host'
)
opener
=
urllib2
.
build_opener
(
DirectoryFileHandler
)
urllib2
.
install_opener
(
opener
)
raise
six
.
moves
.
urllib
.
error
.
URLError
(
'file not on local host'
)
opener
=
six
.
moves
.
urllib
.
request
.
build_opener
(
DirectoryFileHandler
)
six
.
moves
.
urllib
.
request
.
install_opener
(
opener
)
product/ERP5Type/tests/ERP5TypeTestCase.py
View file @
918c8dd7
...
...
@@ -18,7 +18,6 @@ import string
import
sys
import
time
import
traceback
import
urllib
from
six.moves
import
configparser
from
contextlib
import
contextmanager
from
io
import
BytesIO
...
...
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