mirror of
https://github.com/brain-tec/account_ebics.git
synced 2024-11-27 14:37:25 +00:00
Merge pull request #19 from Noviat/16.0
Syncing from upstream Noviat/account_ebics (16.0)
This commit is contained in:
commit
db46dac21c
@ -187,9 +187,18 @@ You can also find this information in the doc folder of this module (file EBICS_
|
|||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
Electronic Distributed Signature (EDS)
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
This is supported via external signing apps, e.g. BankingVEU:
|
||||||
|
|
||||||
|
https://play.google.com/store/apps/details?id=subsembly.bankingveu
|
||||||
|
https://apps.apple.com/de/app/bankingveu/id1578694190
|
||||||
|
|
||||||
|
|
||||||
Known Issues / Roadmap
|
Known Issues / Roadmap
|
||||||
======================
|
======================
|
||||||
|
|
||||||
- Add support to import externally generated keys & certificates (currently only 3SKey signature certificate).
|
- Add support to import externally generated keys & certificates (currently only 3SKey signature certificate).
|
||||||
- Electronic Distributed Signature (EDS) is not supported in the current version of this module.
|
- Add support for SWIFT 3SKey signing javascript lib (SConnect, cf https://www2.swift.com/3skey/help/sconnect.html).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "EBICS banking protocol",
|
"name": "EBICS banking protocol",
|
||||||
"version": "16.0.1.6.2",
|
"version": "16.0.1.7.0",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"author": "Noviat",
|
"author": "Noviat",
|
||||||
"website": "https://www.noviat.com/",
|
"website": "https://www.noviat.com/",
|
||||||
|
@ -72,7 +72,7 @@ class EbicsUserID(models.Model):
|
|||||||
help="Users who are allowed to use this EBICS UserID for "
|
help="Users who are allowed to use this EBICS UserID for "
|
||||||
" bank transactions.",
|
" bank transactions.",
|
||||||
)
|
)
|
||||||
# Currently only a singe signature class per user is supported
|
# Currently only a single signature class per user is supported
|
||||||
# Classes A and B are not yet supported.
|
# Classes A and B are not yet supported.
|
||||||
signature_class = fields.Selection(
|
signature_class = fields.Selection(
|
||||||
selection=[("E", "Single signature"), ("T", "Transport signature")],
|
selection=[("E", "Single signature"), ("T", "Transport signature")],
|
||||||
@ -115,6 +115,17 @@ class EbicsUserID(models.Model):
|
|||||||
ebics_passphrase_invisible = fields.Boolean(
|
ebics_passphrase_invisible = fields.Boolean(
|
||||||
compute="_compute_ebics_passphrase_view_modifiers"
|
compute="_compute_ebics_passphrase_view_modifiers"
|
||||||
)
|
)
|
||||||
|
ebics_sig_passphrase = fields.Char(
|
||||||
|
string="EBICS Signature Passphrase",
|
||||||
|
store=False,
|
||||||
|
help="You can set here a different passphrase for the EBICS "
|
||||||
|
"signing key. This passphrase will never be stored hence "
|
||||||
|
"you'll need to specify your passphrase for each transaction that "
|
||||||
|
"requires a digital signature.",
|
||||||
|
)
|
||||||
|
ebics_sig_passphrase_invisible = fields.Boolean(
|
||||||
|
compute="_compute_ebics_sig_passphrase_invisible"
|
||||||
|
)
|
||||||
ebics_ini_letter = fields.Binary(
|
ebics_ini_letter = fields.Binary(
|
||||||
string="EBICS INI Letter",
|
string="EBICS INI Letter",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
@ -227,13 +238,22 @@ class EbicsUserID(models.Model):
|
|||||||
rec.ebics_passphrase_required = False
|
rec.ebics_passphrase_required = False
|
||||||
rec.ebics_passphrase_invisible = True
|
rec.ebics_passphrase_invisible = True
|
||||||
|
|
||||||
|
@api.depends("state")
|
||||||
|
def _compute_ebics_sig_passphrase_invisible(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.ebics_sig_passphrase_invisible = True
|
||||||
|
if fintech.__version_info__ < (7, 3, 1):
|
||||||
|
continue
|
||||||
|
if rec.transaction_rights != "down" and rec.state == "draft":
|
||||||
|
rec.ebics_sig_passphrase_invisible = False
|
||||||
|
|
||||||
@api.constrains("ebics_key_x509")
|
@api.constrains("ebics_key_x509")
|
||||||
def _check_ebics_key_x509(self):
|
def _check_ebics_key_x509(self):
|
||||||
for cfg in self:
|
for cfg in self:
|
||||||
if cfg.ebics_version == "H005" and not cfg.ebics_key_x509:
|
if cfg.ebics_version == "H005" and not cfg.ebics_key_x509:
|
||||||
raise UserError(_("X.509 certificates must be used with EBICS 3.0."))
|
raise UserError(_("X.509 certificates must be used with EBICS 3.0."))
|
||||||
|
|
||||||
@api.constrains("ebics_passphrase")
|
@api.constrains("ebics_passphrase", "ebics_sig_passphrase")
|
||||||
def _check_ebics_passphrase(self):
|
def _check_ebics_passphrase(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.ebics_passphrase and len(rec.ebics_passphrase) < 8:
|
if rec.ebics_passphrase and len(rec.ebics_passphrase) < 8:
|
||||||
@ -295,9 +315,13 @@ class EbicsUserID(models.Model):
|
|||||||
|
|
||||||
ebics_version = self.ebics_config_id.ebics_version
|
ebics_version = self.ebics_config_id.ebics_version
|
||||||
try:
|
try:
|
||||||
keyring = EbicsKeyRing(
|
keyring_params = {
|
||||||
keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase
|
"keys": self.ebics_keys_fn,
|
||||||
)
|
"passphrase": self.ebics_passphrase,
|
||||||
|
}
|
||||||
|
if self.ebics_sig_passphrase:
|
||||||
|
keyring_params["ebics_sig_passphrase"] = self.ebics_sig_passphrase
|
||||||
|
keyring = EbicsKeyRing(**keyring_params)
|
||||||
bank = EbicsBank(
|
bank = EbicsBank(
|
||||||
keyring=keyring,
|
keyring=keyring,
|
||||||
hostid=self.ebics_config_id.ebics_host,
|
hostid=self.ebics_config_id.ebics_host,
|
||||||
@ -536,7 +560,7 @@ class EbicsUserID(models.Model):
|
|||||||
|
|
||||||
def change_passphrase(self):
|
def change_passphrase(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
ctx = dict(self._context, default_ebics_userid_id=self.id)
|
ctx = dict(self.env.context, default_ebics_userid_id=self.id)
|
||||||
module = __name__.split("addons.")[1].split(".")[0]
|
module = __name__.split("addons.")[1].split(".")[0]
|
||||||
view = self.env.ref("%s.ebics_change_passphrase_view_form" % module)
|
view = self.env.ref("%s.ebics_change_passphrase_view_form" % module)
|
||||||
return {
|
return {
|
||||||
|
@ -538,12 +538,19 @@ You can also find this information in the doc folder of this module (file EBICS_
|
|||||||
<div class="line"><br /></div>
|
<div class="line"><br /></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="section" id="electronic-distributed-signature-eds">
|
||||||
|
<h3>Electronic Distributed Signature (EDS)</h3>
|
||||||
|
<p>This is supported via external signing apps, e.g. BankingVEU:</p>
|
||||||
|
<blockquote>
|
||||||
|
<a class="reference external" href="https://play.google.com/store/apps/details?id=subsembly.bankingveu">https://play.google.com/store/apps/details?id=subsembly.bankingveu</a>
|
||||||
|
<a class="reference external" href="https://apps.apple.com/de/app/bankingveu/id1578694190">https://apps.apple.com/de/app/bankingveu/id1578694190</a></blockquote>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="known-issues-roadmap">
|
<div class="section" id="known-issues-roadmap">
|
||||||
<h2>Known Issues / Roadmap</h2>
|
<h2>Known Issues / Roadmap</h2>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>Add support to import externally generated keys & certificates (currently only 3SKey signature certificate).</li>
|
<li>Add support to import externally generated keys & certificates (currently only 3SKey signature certificate).</li>
|
||||||
<li>Electronic Distributed Signature (EDS) is not supported in the current version of this module.</li>
|
<li>Add support for SWIFT 3SKey signing javascript lib (SConnect, cf <a class="reference external" href="https://www2.swift.com/3skey/help/sconnect.html">https://www2.swift.com/3skey/help/sconnect.html</a>).</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
<field name="ebics_version" invisible="1" />
|
<field name="ebics_version" invisible="1" />
|
||||||
<field name="ebics_passphrase_required" invisible="1" />
|
<field name="ebics_passphrase_required" invisible="1" />
|
||||||
<field name="ebics_passphrase_invisible" invisible="1" />
|
<field name="ebics_passphrase_invisible" invisible="1" />
|
||||||
|
<field name="ebics_sig_passphrase_invisible" invisible="1" />
|
||||||
<group name="main-left">
|
<group name="main-left">
|
||||||
<field name="name" />
|
<field name="name" />
|
||||||
<field
|
<field
|
||||||
@ -96,6 +97,11 @@
|
|||||||
attrs="{'required': [('ebics_passphrase_required', '=', True)], 'invisible': [('ebics_passphrase_invisible', '=', True)]}"
|
attrs="{'required': [('ebics_passphrase_required', '=', True)], 'invisible': [('ebics_passphrase_invisible', '=', True)]}"
|
||||||
/>
|
/>
|
||||||
<field name="ebics_passphrase_store" />
|
<field name="ebics_passphrase_store" />
|
||||||
|
<field
|
||||||
|
name="ebics_sig_passphrase"
|
||||||
|
password="True"
|
||||||
|
attrs="{'invisible': [('ebics_sig_passphrase_invisible', '=', True)]}"
|
||||||
|
/>
|
||||||
<field name="transaction_rights" />
|
<field name="transaction_rights" />
|
||||||
<field name="active" />
|
<field name="active" />
|
||||||
</group>
|
</group>
|
||||||
|
@ -24,37 +24,75 @@ class EbicsChangePassphrase(models.TransientModel):
|
|||||||
ebics_userid_id = fields.Many2one(
|
ebics_userid_id = fields.Many2one(
|
||||||
comodel_name="ebics.userid", string="EBICS UserID", readonly=True
|
comodel_name="ebics.userid", string="EBICS UserID", readonly=True
|
||||||
)
|
)
|
||||||
old_pass = fields.Char(string="Old Passphrase", required=True)
|
old_pass = fields.Char(string="Old Passphrase")
|
||||||
new_pass = fields.Char(string="New Passphrase", required=True)
|
new_pass = fields.Char(string="New Passphrase")
|
||||||
new_pass_check = fields.Char(string="New Passphrase (verification)", required=True)
|
new_pass_check = fields.Char(string="New Passphrase (verification)")
|
||||||
|
old_sig_pass = fields.Char(string="Old Signature Passphrase")
|
||||||
|
new_sig_pass = fields.Char(string="New Signature Passphrase")
|
||||||
|
new_sig_pass_check = fields.Char(string="New Signature Passphrase (verification)")
|
||||||
|
ebics_sig_passphrase_invisible = fields.Boolean(
|
||||||
|
compute="_compute_ebics_sig_passphrase_invisible"
|
||||||
|
)
|
||||||
note = fields.Text(string="Notes", readonly=True)
|
note = fields.Text(string="Notes", readonly=True)
|
||||||
|
|
||||||
|
def _compute_ebics_sig_passphrase_invisible(self):
|
||||||
|
for rec in self:
|
||||||
|
if fintech.__version_info__ < (7, 3, 1):
|
||||||
|
rec.ebics_sig_passphrase_invisible = True
|
||||||
|
else:
|
||||||
|
rec.ebics_sig_passphrase_invisible = False
|
||||||
|
|
||||||
def change_passphrase(self):
|
def change_passphrase(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
self.note = ""
|
||||||
if (
|
if (
|
||||||
self.ebics_userid_id.ebics_passphrase_store
|
self.ebics_userid_id.ebics_passphrase_store
|
||||||
|
and self.old_pass
|
||||||
and self.old_pass != self.ebics_userid_id.ebics_passphrase
|
and self.old_pass != self.ebics_userid_id.ebics_passphrase
|
||||||
):
|
):
|
||||||
raise UserError(_("Incorrect old passphrase."))
|
raise UserError(_("Incorrect old passphrase."))
|
||||||
if self.new_pass != self.new_pass_check:
|
if self.new_pass != self.new_pass_check:
|
||||||
raise UserError(_("New passphrase verification error."))
|
raise UserError(_("New passphrase verification error."))
|
||||||
if self.new_pass == self.ebics_userid_id.ebics_passphrase:
|
if self.new_pass and self.new_pass == self.ebics_userid_id.ebics_passphrase:
|
||||||
raise UserError(_("New passphrase equal to old passphrase."))
|
raise UserError(_("New passphrase equal to old passphrase."))
|
||||||
try:
|
if (
|
||||||
|
self.new_sig_pass
|
||||||
|
and self.old_sig_pass
|
||||||
|
and self.new_sig_pass == self.old_sig_pass
|
||||||
|
):
|
||||||
|
raise UserError(
|
||||||
|
_("New signature passphrase equal to old signature passphrase.")
|
||||||
|
)
|
||||||
|
if self.new_sig_pass != self.new_sig_pass_check:
|
||||||
|
raise UserError(_("New signature passphrase verification error."))
|
||||||
passphrase = (
|
passphrase = (
|
||||||
self.ebics_userid_id.ebics_passphrase_store
|
self.ebics_userid_id.ebics_passphrase_store
|
||||||
and self.ebics_userid_id.ebics_passphrase
|
and self.ebics_userid_id.ebics_passphrase
|
||||||
or self.old_pass
|
or self.old_pass
|
||||||
)
|
)
|
||||||
keyring = EbicsKeyRing(
|
try:
|
||||||
keys=self.ebics_userid_id.ebics_keys_fn,
|
keyring_params = {
|
||||||
passphrase=passphrase,
|
"keys": self.ebics_userid_id.ebics_keys_fn,
|
||||||
)
|
"passphrase": passphrase,
|
||||||
keyring.change_passphrase(self.new_pass)
|
}
|
||||||
except ValueError as err:
|
if self.new_sig_pass:
|
||||||
|
keyring_params["sig_passphrase"] = self.old_sig_pass
|
||||||
|
keyring = EbicsKeyRing(**keyring_params)
|
||||||
|
change_params = {}
|
||||||
|
if self.new_pass:
|
||||||
|
change_params["passphrase"] = self.new_pass
|
||||||
|
if self.new_sig_pass:
|
||||||
|
change_params["sig_passphrase"] = self.new_sig_pass
|
||||||
|
if change_params:
|
||||||
|
keyring.change_passphrase(**change_params)
|
||||||
|
except (ValueError, RuntimeError) as err:
|
||||||
raise UserError(str(err)) from err
|
raise UserError(str(err)) from err
|
||||||
|
|
||||||
|
if self.new_pass:
|
||||||
self.ebics_userid_id.ebics_passphrase = self.new_pass
|
self.ebics_userid_id.ebics_passphrase = self.new_pass
|
||||||
self.note = "The EBICS Passphrase has been changed."
|
self.note += "The EBICS Passphrase has been changed."
|
||||||
|
if self.new_sig_pass:
|
||||||
|
self.note += "The EBICS Signature Passphrase has been changed."
|
||||||
|
|
||||||
module = __name__.split("addons.")[1].split(".")[0]
|
module = __name__.split("addons.")[1].split(".")[0]
|
||||||
result_view = self.env.ref(
|
result_view = self.env.ref(
|
||||||
|
@ -8,19 +8,37 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="EBICS Keys Change Passphrase">
|
<form string="EBICS Keys Change Passphrase">
|
||||||
<group>
|
<group>
|
||||||
|
<group name="pass">
|
||||||
<field name="old_pass" password="True" />
|
<field name="old_pass" password="True" />
|
||||||
<field name="new_pass" password="True" />
|
<field name="new_pass" password="True" />
|
||||||
<field name="new_pass_check" password="True" />
|
<field name="new_pass_check" password="True" />
|
||||||
</group>
|
</group>
|
||||||
|
<group
|
||||||
|
name="sig_pass"
|
||||||
|
attrs="{'invisible': [('ebics_sig_passphrase_invisible', '=', True)]}"
|
||||||
|
>
|
||||||
|
<field name="old_sig_pass" password="True" />
|
||||||
|
<field name="new_sig_pass" password="True" />
|
||||||
|
<field name="new_sig_pass_check" password="True" />
|
||||||
|
</group>
|
||||||
|
<group name="invisible" invisible="1">
|
||||||
|
<field name="ebics_sig_passphrase_invisible" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
<footer>
|
<footer>
|
||||||
<button
|
<button
|
||||||
name="change_passphrase"
|
name="change_passphrase"
|
||||||
string="Change Passphrase"
|
string="Change Passphrase"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_highlight"
|
class="btn-primary"
|
||||||
|
data-hotkey="q"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
string="Cancel"
|
||||||
|
class="btn-secondary"
|
||||||
|
special="cancel"
|
||||||
|
data-hotkey="z"
|
||||||
/>
|
/>
|
||||||
or
|
|
||||||
<button string="Cancel" class="oe_link" special="cancel" />
|
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
@ -35,7 +53,12 @@
|
|||||||
<separator colspan="4" string="Results :" />
|
<separator colspan="4" string="Results :" />
|
||||||
<field name="note" colspan="4" nolabel="1" width="850" height="400" />
|
<field name="note" colspan="4" nolabel="1" width="850" height="400" />
|
||||||
<footer>
|
<footer>
|
||||||
<button name="button_close" type="object" string="Close" />
|
<button
|
||||||
|
name="button_close"
|
||||||
|
type="object"
|
||||||
|
string="Close"
|
||||||
|
data-hotkey="z"
|
||||||
|
/>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
@ -67,6 +67,12 @@ class EbicsXfer(models.TransientModel):
|
|||||||
ebics_passphrase_store = fields.Boolean(
|
ebics_passphrase_store = fields.Boolean(
|
||||||
related="ebics_userid_id.ebics_passphrase_store"
|
related="ebics_userid_id.ebics_passphrase_store"
|
||||||
)
|
)
|
||||||
|
ebics_sig_passphrase = fields.Char(
|
||||||
|
string="EBICS Signature Passphrase",
|
||||||
|
)
|
||||||
|
ebics_sig_passphrase_invisible = fields.Boolean(
|
||||||
|
compute="_compute_ebics_sig_passphrase_invisible"
|
||||||
|
)
|
||||||
date_from = fields.Date()
|
date_from = fields.Date()
|
||||||
date_to = fields.Date()
|
date_to = fields.Date()
|
||||||
upload_data = fields.Binary(string="File to Upload")
|
upload_data = fields.Binary(string="File to Upload")
|
||||||
@ -110,6 +116,14 @@ class EbicsXfer(models.TransientModel):
|
|||||||
else:
|
else:
|
||||||
return cfg_mod
|
return cfg_mod
|
||||||
|
|
||||||
|
def _compute_ebics_sig_passphrase_invisible(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.ebics_sig_passphrase_invisible = True
|
||||||
|
if fintech.__version_info__ < (7, 3, 1):
|
||||||
|
rec.ebics_sig_passphrase_invisible = True
|
||||||
|
else:
|
||||||
|
rec.ebics_sig_passphrase_invisible = False
|
||||||
|
|
||||||
@api.onchange("ebics_config_id")
|
@api.onchange("ebics_config_id")
|
||||||
def _onchange_ebics_config_id(self):
|
def _onchange_ebics_config_id(self):
|
||||||
avail_userids = self.ebics_config_id.ebics_userid_ids.filtered(
|
avail_userids = self.ebics_config_id.ebics_userid_ids.filtered(
|
||||||
@ -139,11 +153,11 @@ class EbicsXfer(models.TransientModel):
|
|||||||
if len(avail_userids) == 1:
|
if len(avail_userids) == 1:
|
||||||
self.ebics_userid_id = avail_userids
|
self.ebics_userid_id = avail_userids
|
||||||
else:
|
else:
|
||||||
with_passphrs_userids = avail_userids.filtered(
|
with_passphrase_userids = avail_userids.filtered(
|
||||||
lambda r: r.ebics_passphrase_store
|
lambda r: r.ebics_passphrase_store
|
||||||
)
|
)
|
||||||
if len(with_passphrs_userids) == 1:
|
if len(with_passphrase_userids) == 1:
|
||||||
self.ebics_userid_id = with_passphrs_userids
|
self.ebics_userid_id = with_passphrase_userids
|
||||||
else:
|
else:
|
||||||
self.ebics_userid_id = False
|
self.ebics_userid_id = False
|
||||||
|
|
||||||
@ -444,10 +458,14 @@ class EbicsXfer(models.TransientModel):
|
|||||||
def _setup_client(self):
|
def _setup_client(self):
|
||||||
self.ebics_config_id._check_ebics_keys()
|
self.ebics_config_id._check_ebics_keys()
|
||||||
passphrase = self._get_passphrase()
|
passphrase = self._get_passphrase()
|
||||||
|
keyring_params = {
|
||||||
|
"keys": self.ebics_userid_id.ebics_keys_fn,
|
||||||
|
"passphrase": passphrase,
|
||||||
|
}
|
||||||
|
if self.ebics_sig_passphrase:
|
||||||
|
keyring_params["sig_passphrase"] = self.ebics_sig_passphrase
|
||||||
try:
|
try:
|
||||||
keyring = EbicsKeyRing(
|
keyring = EbicsKeyRing(**keyring_params)
|
||||||
keys=self.ebics_userid_id.ebics_keys_fn, passphrase=passphrase
|
|
||||||
)
|
|
||||||
except (RuntimeError, ValueError) as err:
|
except (RuntimeError, ValueError) as err:
|
||||||
error = _("Error while accessing the EBICS Keys:")
|
error = _("Error while accessing the EBICS Keys:")
|
||||||
error += "\n"
|
error += "\n"
|
||||||
|
@ -78,7 +78,13 @@
|
|||||||
password="True"
|
password="True"
|
||||||
attrs="{'invisible': [('ebics_passphrase_store', '=', True)], 'required': [('ebics_passphrase_store', '=', False)]}"
|
attrs="{'invisible': [('ebics_passphrase_store', '=', True)], 'required': [('ebics_passphrase_store', '=', False)]}"
|
||||||
/>
|
/>
|
||||||
|
<field
|
||||||
|
name="ebics_sig_passphrase"
|
||||||
|
password="True"
|
||||||
|
attrs="{'invisible': [('ebics_sig_passphrase_invisible', '=', True)]}"
|
||||||
|
/>
|
||||||
<field name="ebics_passphrase_store" invisible="1" />
|
<field name="ebics_passphrase_store" invisible="1" />
|
||||||
|
<field name="ebics_sig_passphrase_invisible" invisible="1" />
|
||||||
<separator string="Select your file :" colspan="2" />
|
<separator string="Select your file :" colspan="2" />
|
||||||
<field name="upload_data" filename="upload_fname" required="1" />
|
<field name="upload_data" filename="upload_fname" required="1" />
|
||||||
<field name="upload_fname" invisible="1" />
|
<field name="upload_fname" invisible="1" />
|
||||||
|
Loading…
Reference in New Issue
Block a user