etobella
7 years ago
committed by
Pedro M. Baeza
17 changed files with 555 additions and 144 deletions
-
53base_location/README.rst
-
1base_location/__init__.py
-
5base_location/__openerp__.py
-
8base_location/demo/better_zip.xml
-
1base_location/models/__init__.py
-
101base_location/models/better_zip.py
-
64base_location/models/company.py
-
60base_location/models/partner.py
-
1base_location/models/state.py
-
2base_location/security/ir.model.access.csv
-
3base_location/tests/__init__.py
-
270base_location/tests/test_base_location.py
-
49base_location/tests/test_completion.py
-
35base_location/views/better_zip_view.xml
-
10base_location/views/company_view.xml
-
20base_location/views/partner_view.xml
-
16base_location/views/res_country_view.xml
@ -1,4 +1,3 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2016 Nicolas Bessi, Camptocamp SA |
# Copyright 2016 Nicolas Bessi, Camptocamp SA |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
@ -1,12 +1,8 @@ |
|||||
<?xml version = "1.0" encoding="utf-8"?> |
<?xml version = "1.0" encoding="utf-8"?> |
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
|
<odoo> |
||||
<record id="demo_brussels" model="res.better.zip"> |
<record id="demo_brussels" model="res.better.zip"> |
||||
<field name="name">1000</field> |
<field name="name">1000</field> |
||||
<field name="city">Brussels</field> |
<field name="city">Brussels</field> |
||||
<field name="country_id" ref="base.be"/> |
<field name="country_id" ref="base.be"/> |
||||
</record> |
</record> |
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
||||
|
</odoo> |
@ -1,18 +1,66 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2016 Nicolas Bessi, Camptocamp SA |
# Copyright 2016 Nicolas Bessi, Camptocamp SA |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
from odoo import models, fields, api |
|
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
class ResPartner(models.Model): |
class ResPartner(models.Model): |
||||
_inherit = 'res.partner' |
_inherit = 'res.partner' |
||||
zip_id = fields.Many2one('res.better.zip', 'City/Location') |
|
||||
|
zip_id = fields.Many2one('res.better.zip', 'ZIP Location') |
||||
|
|
||||
|
@api.onchange('city_id') |
||||
|
def _onchange_city_id(self): |
||||
|
if not self.zip_id: |
||||
|
super(ResPartner, self)._onchange_city_id() |
||||
|
if self.zip_id and self.city_id != self.zip_id.city_id: |
||||
|
self.zip_id = False |
||||
|
self.zip = False |
||||
|
self.city = False |
||||
|
if self.city_id: |
||||
|
return { |
||||
|
'domain': { |
||||
|
'zip_id': [('city_id', '=', self.city_id.id)] |
||||
|
}, |
||||
|
} |
||||
|
return {'domain': {'zip_id': []}} |
||||
|
|
||||
|
@api.onchange('state_id') |
||||
|
def _onchange_state_id(self): |
||||
|
if self.zip_id and self.state_id != self.zip_id.state_id: |
||||
|
self.zip_id = False |
||||
|
self.zip = False |
||||
|
self.city = False |
||||
|
|
||||
|
@api.onchange('country_id') |
||||
|
def _onchange_country_id(self): |
||||
|
res = super(ResPartner, self)._onchange_country_id() |
||||
|
if self.zip_id and self.zip_id.country_id != self.country_id: |
||||
|
self.zip_id = False |
||||
|
return res |
||||
|
|
||||
@api.onchange('zip_id') |
@api.onchange('zip_id') |
||||
def onchange_zip_id(self): |
|
||||
|
def _onchange_zip_id(self): |
||||
if self.zip_id: |
if self.zip_id: |
||||
|
self.country_id = self.zip_id.country_id |
||||
|
if self.country_id.enforce_cities: |
||||
|
self.city_id = self.zip_id.city_id |
||||
self.zip = self.zip_id.name |
self.zip = self.zip_id.name |
||||
self.city = self.zip_id.city |
|
||||
self.state_id = self.zip_id.state_id |
self.state_id = self.zip_id.state_id |
||||
self.country_id = self.zip_id.country_id |
|
||||
|
self.city = self.zip_id.city |
||||
|
|
||||
|
@api.constrains('zip_id', 'country_id', 'city_id', 'state_id') |
||||
|
def _check_zip(self): |
||||
|
for rec in self.filtered('zip_id'): |
||||
|
if rec.zip_id.state_id != rec.state_id: |
||||
|
raise ValidationError(_( |
||||
|
"The state of the partner %s differs from that in " |
||||
|
"location %s") % (rec.name, rec.zip_id.name)) |
||||
|
if rec.zip_id.country_id != rec.country_id: |
||||
|
raise ValidationError(_( |
||||
|
"The country of the partner %s differs from that in " |
||||
|
"location %s") % (rec.name, rec.zip_id.name)) |
||||
|
if rec.zip_id.city_id != rec.city_id: |
||||
|
raise ValidationError(_( |
||||
|
"The city of partner %s differs from that in " |
||||
|
"location %s") % (rec.name, rec.zip_id.name)) |
@ -1,3 +1,3 @@ |
|||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
||||
"ir_model_access_betterzip0","res_better_zip group_user_all","model_res_better_zip",base.group_user,1,0,0,0 |
"ir_model_access_betterzip0","res_better_zip group_user_all","model_res_better_zip",base.group_user,1,0,0,0 |
||||
"ir_model_access_betterzip1","res_better_zip group_user","model_res_better_zip","base.group_partner_manager",1,1,1,1 |
|
||||
|
"ir_model_access_betterzip1","res_better_zip group_user","model_res_better_zip","base.group_partner_manager",1,1,1,1 |
@ -1,5 +1,4 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2015 Yannick Vaucher, Camptocamp SA |
# Copyright 2015 Yannick Vaucher, Camptocamp SA |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
from . import test_completion |
|
||||
|
from . import test_base_location |
@ -0,0 +1,270 @@ |
|||||
|
# Copyright 2015 Yannick Vaucher, Camptocamp SA |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo.tests.common import TransactionCase |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
|
class TestBaseLocation(TransactionCase): |
||||
|
|
||||
|
def test_onchange_better_zip_state_id(self): |
||||
|
""" Test onchange on res.better.zip """ |
||||
|
usa_MA = self.env.ref('base.state_us_34') |
||||
|
better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) |
||||
|
better_zip1.state_id = usa_MA |
||||
|
better_zip1._onchange_state_id() |
||||
|
self.assertEqual(better_zip1.country_id, usa_MA.country_id) |
||||
|
|
||||
|
def test_onchange_better_zip_city_id(self): |
||||
|
better_zip2 = self.env['res.better.zip'].new(self.values_better_zip2()) |
||||
|
better_zip2.city_id = self.city_madrid |
||||
|
better_zip2._onchange_city_id() |
||||
|
self.assertEqual(better_zip2.city, self.city_madrid.name) |
||||
|
|
||||
|
def test_onchange_better_zip_country_id(self): |
||||
|
better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) |
||||
|
better_zip1.country_id = self.env.ref('base.es') |
||||
|
better_zip1._onchange_country_id() |
||||
|
self.assertFalse(better_zip1.state_id) |
||||
|
|
||||
|
def test_onchange_better_zip_none(self): |
||||
|
better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) |
||||
|
better_zip1.country_id = False |
||||
|
better_zip1._onchange_country_id() |
||||
|
self.assertFalse(better_zip1.state_id) |
||||
|
|
||||
|
def test_onchange_partner_city_completion(self): |
||||
|
partner1 = self.env['res.partner'].new({ |
||||
|
'name': 'Camptocamp', |
||||
|
}) |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
better_zip2.country_id.enforce_cities = True |
||||
|
partner1.zip_id = better_zip2 |
||||
|
partner1._onchange_zip_id() |
||||
|
self.assertEqual(partner1.zip, better_zip2.name) |
||||
|
self.assertEqual(partner1.city, better_zip2.city) |
||||
|
self.assertEqual(partner1.state_id, better_zip2.state_id) |
||||
|
self.assertEqual(partner1.country_id, better_zip2.country_id) |
||||
|
|
||||
|
def test_onchange_company_city_completion(self): |
||||
|
company = self.env['res.company'].new({'name': 'Test'}) |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
company.zip_id = better_zip1 |
||||
|
company._onchange_zip_id() |
||||
|
self.assertEqual(company.zip, better_zip1.name) |
||||
|
self.assertEqual(company.city, better_zip1.city) |
||||
|
self.assertEqual(company.state_id, better_zip1.state_id) |
||||
|
self.assertEqual(company.country_id, better_zip1.country_id) |
||||
|
|
||||
|
def test_company_address_fields(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1() |
||||
|
) |
||||
|
company = self.env['res.company'].create({ |
||||
|
'name': 'Test', |
||||
|
}) |
||||
|
self.assertTrue(company.partner_id) |
||||
|
company.partner_id.write({ |
||||
|
'zip_id': better_zip1.id, |
||||
|
'state_id': better_zip1.state_id.id, |
||||
|
'country_id': better_zip1.country_id.id, |
||||
|
'city_id': better_zip1.city_id.id, |
||||
|
'city': better_zip1.city, |
||||
|
'zip': better_zip1.name, |
||||
|
}) |
||||
|
company._compute_address() |
||||
|
self.assertEqual(company.zip_id, company.partner_id.zip_id) |
||||
|
self.assertEqual(company.city_id, company.partner_id.city_id) |
||||
|
|
||||
|
def test_company_address_fields_inverse(self): |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2() |
||||
|
) |
||||
|
company = self.env['res.company'].new({ |
||||
|
'name': 'Test', |
||||
|
'partner_id': self.env['res.partner'].new({}).id |
||||
|
# Partner must be initiated in order to be filled |
||||
|
}) |
||||
|
company.update({ |
||||
|
'zip_id': better_zip2.id, |
||||
|
}) |
||||
|
company._inverse_city_id() |
||||
|
company._inverse_zip_id() |
||||
|
self.assertEqual(company.zip_id, company.partner_id.zip_id) |
||||
|
self.assertEqual(company.city_id, company.partner_id.city_id) |
||||
|
|
||||
|
def test_onchange_company_city_id_completion(self): |
||||
|
company = self.env['res.company'].new({'name': 'Test'}) |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
company.zip_id = better_zip2 |
||||
|
company._onchange_zip_id() |
||||
|
self.assertEqual(company.city_id, better_zip2.city_id) |
||||
|
|
||||
|
def test_constrains_better_zip_01(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
|
||||
|
better_zip1.city_id = self.city_lausanne |
||||
|
with self.assertRaises(ValidationError): |
||||
|
better_zip2.city_id = better_zip1.city_id |
||||
|
|
||||
|
def test_constrains_better_zip_02(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
better_zip2.country_id = better_zip1.country_id |
||||
|
|
||||
|
def test_constrains_better_zip_03(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
better_zip2.state_id = better_zip1.state_id |
||||
|
|
||||
|
def test_constrains_better_zip_04(self): |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
better_zip2.city_id = self.city_madrid |
||||
|
|
||||
|
def test_constrains_partner_01(self): |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
with self.assertRaises(ValidationError): |
||||
|
self.env['res.partner'].create({ |
||||
|
'name': 'P1', |
||||
|
'zip_id': better_zip2.id, |
||||
|
}) |
||||
|
|
||||
|
def test_constrains_partner_02(self): |
||||
|
better_zip2 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip2()) |
||||
|
partner = self.env['res.partner'].create({ |
||||
|
'name': 'P1', |
||||
|
'zip_id': better_zip2.id, |
||||
|
'country_id': better_zip2.country_id.id, |
||||
|
'state_id': better_zip2.state_id.id, |
||||
|
'city_id': better_zip2.city_id.id, |
||||
|
}) |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
partner.country_id = self.ref('base.ch') |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
partner.state_id = self.state_vd.id, |
||||
|
|
||||
|
with self.assertRaises(ValidationError): |
||||
|
partner.city_id = self.city_lausanne |
||||
|
|
||||
|
def values_better_zip1(self): |
||||
|
return { |
||||
|
'name': 1000, |
||||
|
'city': 'Lausanne', |
||||
|
'state_id': self.state_vd.id, |
||||
|
'country_id': self.ref('base.ch'), |
||||
|
} |
||||
|
|
||||
|
def values_better_zip2(self): |
||||
|
return { |
||||
|
'city_id': self.city_bcn.id, |
||||
|
'city': self.city_bcn.name, |
||||
|
'state_id': self.state_bcn.id, |
||||
|
'country_id': self.ref('base.es'), |
||||
|
} |
||||
|
|
||||
|
def test_partner_onchange_country(self): |
||||
|
country_es = self.browse_ref('base.es') |
||||
|
country_es.enforce_cities = True |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
partner = self.env['res.partner'].new({ |
||||
|
'name': 'TEST', |
||||
|
'zip_id': better_zip1.id |
||||
|
}) |
||||
|
partner.country_id = country_es |
||||
|
partner._onchange_country_id() |
||||
|
self.assertFalse(partner.zip_id) |
||||
|
|
||||
|
def test_partner_onchange_city(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
partner = self.env['res.partner'].new({ |
||||
|
'name': 'TEST', |
||||
|
'zip_id': better_zip1.id |
||||
|
}) |
||||
|
self.city_bcn.country_id.enforce_cities = False |
||||
|
partner.city_id = self.city_bcn |
||||
|
partner._onchange_city_id() |
||||
|
self.assertFalse(partner.zip_id) |
||||
|
partner.city_id = False |
||||
|
res = partner._onchange_city_id() |
||||
|
self.assertFalse(res['domain']['zip_id']) |
||||
|
|
||||
|
def test_partner_onchange_state(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
partner = self.env['res.partner'].new({ |
||||
|
'name': 'TEST', |
||||
|
'zip_id': better_zip1.id |
||||
|
}) |
||||
|
partner.state_id = self.state_bcn |
||||
|
partner._onchange_state_id() |
||||
|
self.assertFalse(partner.zip_id) |
||||
|
|
||||
|
def test_display_name(self): |
||||
|
better_zip1 = self.env['res.better.zip'].create( |
||||
|
self.values_better_zip1()) |
||||
|
self.assertEqual( |
||||
|
better_zip1.display_name, '1000, Lausanne, Vaud, '+self.browse_ref( |
||||
|
'base.ch' |
||||
|
).name |
||||
|
) |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseLocation, self).setUp() |
||||
|
self.state_vd = self.env['res.country.state'].create({ |
||||
|
'name': 'Vaud', |
||||
|
'code': 'VD', |
||||
|
'country_id': self.ref('base.ch'), |
||||
|
}) |
||||
|
self.env['res.country'].browse(self.ref('base.es')).write({ |
||||
|
'enforce_cities': True |
||||
|
}) |
||||
|
self.company = self.env.ref('base.main_company') |
||||
|
|
||||
|
self.state_bcn = self.env['res.country.state'].create({ |
||||
|
'name': 'Barcelona', |
||||
|
'code': '08', |
||||
|
'country_id': self.ref('base.es'), |
||||
|
}) |
||||
|
self.state_madrid = self.env['res.country.state'].create({ |
||||
|
'name': 'Madrid', |
||||
|
'code': '28', |
||||
|
'country_id': self.ref('base.es'), |
||||
|
}) |
||||
|
self.city_bcn = self.env['res.city'].create({ |
||||
|
'name': 'Barcelona', |
||||
|
'state_id': self.state_bcn.id, |
||||
|
'country_id': self.ref('base.es'), |
||||
|
}) |
||||
|
self.city_madrid = self.env['res.city'].create({ |
||||
|
'name': 'Madrid', |
||||
|
'state_id': self.state_madrid.id, |
||||
|
'country_id': self.ref('base.es'), |
||||
|
}) |
||||
|
self.city_lausanne = self.env['res.city'].create({ |
||||
|
'name': 'Lausanne', |
||||
|
'state_id': self.state_vd.id, |
||||
|
'country_id': self.ref('base.ch'), |
||||
|
}) |
@ -1,49 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2015 Yannick Vaucher, Camptocamp SA |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo.tests.common import TransactionCase |
|
||||
|
|
||||
|
|
||||
class TestCompletion(TransactionCase): |
|
||||
|
|
||||
def test_onchange_better_zip_state_id(self): |
|
||||
""" Test onchange on res.better.zip """ |
|
||||
usa_MA = self.env.ref('base.state_us_34') |
|
||||
self.better_zip1.state_id = usa_MA |
|
||||
self.better_zip1.onchange_state_id() |
|
||||
self.assertEqual(self.better_zip1.country_id, usa_MA.country_id) |
|
||||
|
|
||||
def test_onchange_partner_city_completion(self): |
|
||||
self.partner1.zip_id = self.better_zip1 |
|
||||
self.partner1.onchange_zip_id() |
|
||||
self.assertEqual(self.partner1.zip, self.better_zip1.name) |
|
||||
self.assertEqual(self.partner1.city, self.better_zip1.city) |
|
||||
self.assertEqual(self.partner1.state_id, self.better_zip1.state_id) |
|
||||
self.assertEqual(self.partner1.country_id, self.better_zip1.country_id) |
|
||||
|
|
||||
def test_onchange_company_city_completion(self): |
|
||||
self.company.better_zip_id = self.better_zip1 |
|
||||
self.company.on_change_city() |
|
||||
self.assertEqual(self.company.zip, self.better_zip1.name) |
|
||||
self.assertEqual(self.company.city, self.better_zip1.city) |
|
||||
self.assertEqual(self.company.state_id, self.better_zip1.state_id) |
|
||||
self.assertEqual(self.company.country_id, self.better_zip1.country_id) |
|
||||
|
|
||||
def setUp(self): |
|
||||
super(TestCompletion, self).setUp() |
|
||||
state_vd = self.env['res.country.state'].create({ |
|
||||
'name': 'Vaud', |
|
||||
'code': 'VD', |
|
||||
'country_id': self.ref('base.ch'), |
|
||||
}) |
|
||||
self.company = self.env.ref('base.main_company') |
|
||||
self.better_zip1 = self.env['res.better.zip'].create({ |
|
||||
'name': 1000, |
|
||||
'city': 'Lausanne', |
|
||||
'state_id': state_vd.id, |
|
||||
'country_id': self.ref('base.ch'), |
|
||||
}) |
|
||||
self.partner1 = self.env['res.partner'].create({ |
|
||||
'name': 'Camptocamp', |
|
||||
}) |
|
@ -1,24 +1,26 @@ |
|||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||
<odoo> |
<odoo> |
||||
|
|
||||
<record id="view_partner_form" model="ir.ui.view"> |
<record id="view_partner_form" model="ir.ui.view"> |
||||
<field name="name">res.partner.zip_id.2</field> |
<field name="name">res.partner.zip_id.2</field> |
||||
<field name="model">res.partner</field> |
<field name="model">res.partner</field> |
||||
<field name="inherit_id" ref="base.view_partner_form" /> |
|
||||
|
<field name="inherit_id" ref="base.view_partner_form"/> |
||||
<field name="arch" type="xml"> |
<field name="arch" type="xml"> |
||||
<field name="city" position="before"> |
<field name="city" position="before"> |
||||
<field name="zip_id" |
<field name="zip_id" |
||||
options="{'create_name_field': 'city', 'no_open': True, 'no_create': True}" |
|
||||
placeholder="City completion" |
|
||||
class="oe_edit_only" /> |
|
||||
|
options="{'create_name_field': 'city', 'no_open': True, 'no_create': True}" |
||||
|
placeholder="Location completion" |
||||
|
class="oe_edit_only" |
||||
|
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> |
||||
</field> |
</field> |
||||
<xpath expr="//field[@name='child_ids']/form//field[@name='city']" position="before"> |
|
||||
|
<xpath expr="//field[@name='child_ids']/form//field[@name='city']" |
||||
|
position="before"> |
||||
<field name="zip_id" |
<field name="zip_id" |
||||
options="{'create_name_field': 'city', 'no_open': True, 'no_create': True}" |
|
||||
placeholder="City completion" |
|
||||
class="oe_edit_only" /> |
|
||||
|
options="{'create_name_field': 'city', 'no_open': True, 'no_create': True}" |
||||
|
placeholder="City completion" |
||||
|
class="oe_edit_only"/> |
||||
</xpath> |
</xpath> |
||||
</field> |
</field> |
||||
</record> |
</record> |
||||
|
|
||||
</odoo> |
</odoo> |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue