mirror of
https://github.com/NaN-tic/trytond-party_merge.git
synced 2023-12-14 03:13:02 +01:00
Initial commit.
Original module from openlabs with some improvements.
This commit is contained in:
commit
a50a7e96b5
16
Makefile
Normal file
16
Makefile
Normal file
|
@ -0,0 +1,16 @@
|
|||
test: test-sqlite test-postgres test-flake8
|
||||
|
||||
test-sqlite: install-dependencies
|
||||
coverage run setup.py test
|
||||
coverage report -m --fail-under 80
|
||||
|
||||
test-postgres: install-dependencies
|
||||
python setup.py test_on_postgres
|
||||
|
||||
test-flake8:
|
||||
pip install flake8
|
||||
flake8 .
|
||||
|
||||
install-dependencies:
|
||||
CFLAGS=-O0 pip install lxml
|
||||
pip install -r dev_requirements.txt
|
22
__init__.py
Normal file
22
__init__.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
__init__.py
|
||||
|
||||
:copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from trytond.pool import Pool
|
||||
|
||||
from party import Party, PartyMergeView, PartyMerge
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
Party,
|
||||
PartyMergeView,
|
||||
module='party_merge', type_='model'
|
||||
)
|
||||
Pool.register(
|
||||
PartyMerge,
|
||||
module='party_merge', type_='wizard'
|
||||
)
|
35
locale/de_DE.po
Normal file
35
locale/de_DE.po
Normal file
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:party.party.merge.view,duplicates:"
|
||||
msgid "Duplicates"
|
||||
msgstr "Duplikate"
|
||||
|
||||
msgctxt "field:party.party.merge.view,id:"
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
msgctxt "field:party.party.merge.view,target:"
|
||||
msgid "Target"
|
||||
msgstr "Ziel"
|
||||
|
||||
msgctxt "model:ir.action,name:wizard_party_merge"
|
||||
msgid "Merge Parties"
|
||||
msgstr "Parteien zusammenfassen"
|
||||
|
||||
msgctxt "model:party.party.merge.view,name:"
|
||||
msgid "Party Merge"
|
||||
msgstr "Parteien zusammenfassen"
|
||||
|
||||
msgctxt "view:party.party.merge.view:"
|
||||
msgid "Merge Parties"
|
||||
msgstr "Parteien zusammenfassen"
|
||||
|
||||
msgctxt "wizard_button:party.party.merge,merge,end:"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
msgctxt "wizard_button:party.party.merge,merge,result:"
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
122
party.py
Normal file
122
party.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
party.py
|
||||
|
||||
:copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from sql import Table
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import PoolMeta, Pool
|
||||
from trytond.pyson import Eval, Bool, Not
|
||||
from trytond.wizard import Wizard, StateView, StateTransition, Button
|
||||
|
||||
__metaclass__ = PoolMeta
|
||||
__all__ = ['Party', 'PartyMergeView', 'PartyMerge']
|
||||
|
||||
|
||||
class MergeMixin:
|
||||
def merge_into(self, target):
|
||||
"""
|
||||
Merge current record into target
|
||||
"""
|
||||
ModelField = Pool().get('ir.model.field')
|
||||
|
||||
model_fields = ModelField.search([
|
||||
('relation', '=', self.__name__),
|
||||
('ttype', '=', 'many2one'),
|
||||
])
|
||||
|
||||
if hasattr(self, 'active'):
|
||||
self.active = False
|
||||
self.merged_into = target
|
||||
self.save()
|
||||
|
||||
cursor = Transaction().cursor
|
||||
|
||||
to_validate = []
|
||||
for field in model_fields:
|
||||
Model = Pool().get(field.model.model)
|
||||
|
||||
if isinstance(getattr(Model, field.name), fields.Function):
|
||||
continue
|
||||
|
||||
if not hasattr(Model, '__table__'):
|
||||
continue
|
||||
|
||||
sql_table = Model.__table__()
|
||||
# Discard sql.Union or others generated by table_query()
|
||||
if not isinstance(sql_table, Table):
|
||||
continue
|
||||
|
||||
to_validate.append(field)
|
||||
sql_field = getattr(sql_table, field.name)
|
||||
cursor.execute(*sql_table.update(
|
||||
columns=[sql_field],
|
||||
values=[target.id],
|
||||
where=(sql_field == self.id)
|
||||
))
|
||||
|
||||
|
||||
# Validate all related records and target.
|
||||
# Do it at the very end because we may # temporarily leave
|
||||
# information inconsistent in the previous loop
|
||||
for field in model_fields:
|
||||
Model = Pool().get(field.model.model)
|
||||
|
||||
if not isinstance(Model, ModelSQL):
|
||||
continue
|
||||
|
||||
ff = getattr(Model, field.name)
|
||||
if isinstance(ff, fields.Function) and not ff.searcher:
|
||||
continue
|
||||
|
||||
with Transaction().set_context(active_test=False):
|
||||
Model.validate(Model.search([
|
||||
(field.name, '=', target.id),
|
||||
]))
|
||||
|
||||
self.validate([target])
|
||||
|
||||
|
||||
class Party(MergeMixin):
|
||||
__name__ = 'party.party'
|
||||
merged_into = fields.Many2One('party.party', 'Merged Into', readonly=True,
|
||||
states={
|
||||
'invisible': Not(Bool(Eval('merged_into'))),
|
||||
})
|
||||
|
||||
|
||||
class PartyMergeView(ModelView):
|
||||
'Party Merge'
|
||||
__name__ = 'party.party.merge.view'
|
||||
|
||||
duplicates = fields.One2Many('party.party', None, 'Duplicates',
|
||||
readonly=True)
|
||||
target = fields.Many2One('party.party', 'Target', required=True,
|
||||
domain=[('id', 'not in', Eval('duplicates'))], depends=['duplicates'])
|
||||
|
||||
|
||||
class PartyMerge(Wizard):
|
||||
__name__ = 'party.party.merge'
|
||||
start_state = 'merge'
|
||||
|
||||
merge = StateView(
|
||||
'party.party.merge.view',
|
||||
'party_merge.party_merge_view', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('OK', 'result', 'tryton-ok'),
|
||||
]
|
||||
)
|
||||
result = StateTransition()
|
||||
|
||||
def default_merge(self, fields):
|
||||
return {
|
||||
'duplicates': Transaction().context['active_ids'],
|
||||
}
|
||||
|
||||
def transition_result(self):
|
||||
for party in self.merge.duplicates:
|
||||
party.merge_into(self.merge.target)
|
||||
return 'end'
|
25
party.xml
Normal file
25
party.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="party_merge_view">
|
||||
<field name="model">party.party.merge.view</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">party_merge_view_form</field>
|
||||
</record>
|
||||
<record model="ir.action.wizard" id="wizard_party_merge">
|
||||
<field name="name">Merge Parties</field>
|
||||
<field name="wiz_name">party.party.merge</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="party_merge">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">party.party,-1</field>
|
||||
<field name="action" ref="wizard_party_merge"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="party_view_form">
|
||||
<field name="model">party.party</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">party_form</field>
|
||||
<field name="inherit" ref="party.party_view_form"/>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
psycopg2
|
||||
|
||||
# Install this package itself
|
||||
.
|
6
setup.cfg
Normal file
6
setup.cfg
Normal file
|
@ -0,0 +1,6 @@
|
|||
[flake8]
|
||||
exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,build,dist,upload.py,doc,scripts,selenium*,proteus*,Flask*,Genshi*,lxml*,relatorio*,trytond-*,docs,blinker-*,*.egg,.tox
|
||||
|
||||
|
||||
max-complexity=10
|
||||
max-line-length=80
|
145
setup.py
Normal file
145
setup.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env python
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import ConfigParser
|
||||
from setuptools import setup, Command
|
||||
|
||||
|
||||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
|
||||
class SQLiteTest(Command):
|
||||
"""
|
||||
Run the tests on SQLite
|
||||
"""
|
||||
description = "Run tests on SQLite"
|
||||
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
from trytond.config import CONFIG
|
||||
CONFIG['db_type'] = 'sqlite'
|
||||
os.environ['DB_NAME'] = ':memory:'
|
||||
|
||||
from tests import suite
|
||||
test_result = unittest.TextTestRunner(verbosity=3).run(suite())
|
||||
|
||||
if test_result.wasSuccessful():
|
||||
sys.exit(0)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
class PostgresTest(Command):
|
||||
"""
|
||||
Run the tests on Postgres.
|
||||
"""
|
||||
description = "Run tests on Postgresql"
|
||||
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
from trytond.config import CONFIG
|
||||
CONFIG['db_type'] = 'postgresql'
|
||||
CONFIG['db_host'] = 'localhost'
|
||||
CONFIG['db_port'] = 5432
|
||||
CONFIG['db_user'] = 'postgres'
|
||||
|
||||
os.environ['DB_NAME'] = 'test_' + str(int(time.time()))
|
||||
|
||||
from tests import suite
|
||||
test_result = unittest.TextTestRunner(verbosity=3).run(suite())
|
||||
|
||||
if test_result.wasSuccessful():
|
||||
sys.exit(0)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
config = ConfigParser.ConfigParser()
|
||||
config.readfp(open('tryton.cfg'))
|
||||
info = dict(config.items('tryton'))
|
||||
for key in ('depends', 'extras_depend', 'xml'):
|
||||
if key in info:
|
||||
info[key] = info[key].strip().splitlines()
|
||||
major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
|
||||
major_version = int(major_version)
|
||||
minor_version = int(minor_version)
|
||||
|
||||
requires = []
|
||||
|
||||
MODULE2PREFIX = {}
|
||||
|
||||
MODULE = "party_merge"
|
||||
PREFIX = "openlabs"
|
||||
for dep in info.get('depends', []):
|
||||
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
||||
requires.append(
|
||||
'%s_%s >= %s.%s, < %s.%s' % (
|
||||
MODULE2PREFIX.get(dep, 'trytond'), dep,
|
||||
major_version, minor_version, major_version,
|
||||
minor_version + 1
|
||||
)
|
||||
)
|
||||
requires.append(
|
||||
'trytond >= %s.%s, < %s.%s' % (
|
||||
major_version, minor_version, major_version, minor_version + 1
|
||||
)
|
||||
)
|
||||
setup(
|
||||
name='%s_%s' % (PREFIX, MODULE),
|
||||
version=info.get('version', '0.0.1'),
|
||||
description="Merge Parties",
|
||||
author="Openlabs Technologies and Consulting (P) Ltd.",
|
||||
author_email='info@openlabs.co.in',
|
||||
url='http://www.openlabs.co.in/',
|
||||
package_dir={'trytond.modules.%s' % MODULE: '.'},
|
||||
packages=[
|
||||
'trytond.modules.%s' % MODULE,
|
||||
'trytond.modules.%s.tests' % MODULE,
|
||||
],
|
||||
package_data={
|
||||
'trytond.modules.%s' % MODULE: info.get('xml', [])
|
||||
+ info.get('translation', [])
|
||||
+ ['tryton.cfg', 'locale/*.po', 'tests/*.rst', 'reports/*.odt']
|
||||
+ ['view/*.xml'],
|
||||
},
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: Plugins',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Framework :: Tryton',
|
||||
'Topic :: Office/Business',
|
||||
],
|
||||
license='GPL-3',
|
||||
install_requires=requires,
|
||||
zip_safe=False,
|
||||
entry_points="""
|
||||
[trytond.modules]
|
||||
%s = trytond.modules.%s
|
||||
""" % (MODULE, MODULE),
|
||||
test_suite='tests',
|
||||
test_loader='trytond.test_loader:Loader',
|
||||
cmdclass={
|
||||
'test': SQLiteTest,
|
||||
'test_on_postgres': PostgresTest,
|
||||
}
|
||||
)
|
28
tests/__init__.py
Normal file
28
tests/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests/__init__.py
|
||||
|
||||
:copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
|
||||
from tests.test_views_depends import TestViewsDepends
|
||||
from tests.test_party import TestParty
|
||||
|
||||
|
||||
def suite():
|
||||
"""
|
||||
Define suite
|
||||
"""
|
||||
test_suite = trytond.tests.test_tryton.suite()
|
||||
test_suite.addTests([
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestViewsDepends),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestParty),
|
||||
])
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
87
tests/test_party.py
Normal file
87
tests/test_party.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests/test_party.py
|
||||
|
||||
:copyright: (C) 2014 by Openlabs Technologies & Consulting (P) Limited
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
DIR = os.path.abspath(os.path.normpath(os.path.join(
|
||||
__file__, '..', '..', '..', '..', '..', 'trytond'
|
||||
)))
|
||||
if os.path.isdir(DIR):
|
||||
sys.path.insert(0, os.path.dirname(DIR))
|
||||
import unittest
|
||||
|
||||
if 'DB_NAME' not in os.environ:
|
||||
from trytond.config import CONFIG
|
||||
CONFIG['db_type'] = 'sqlite'
|
||||
os.environ['DB_NAME'] = ':memory:'
|
||||
|
||||
from trytond.tests.test_tryton import POOL, USER
|
||||
from trytond.tests.test_tryton import DB_NAME, CONTEXT
|
||||
from trytond.transaction import Transaction
|
||||
import trytond.tests.test_tryton
|
||||
|
||||
|
||||
class TestParty(unittest.TestCase):
|
||||
'''
|
||||
Test Party
|
||||
'''
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up data used in the tests.
|
||||
this method is called before each test function execution.
|
||||
"""
|
||||
trytond.tests.test_tryton.install_module('party_merge')
|
||||
|
||||
self.Party = POOL.get('party.party')
|
||||
|
||||
def test0005_merge_parties(self):
|
||||
"""Test party merge function.
|
||||
"""
|
||||
with Transaction().start(DB_NAME, USER, context=CONTEXT):
|
||||
party1, party2, party3 = self.Party.create([{
|
||||
'name': 'Party 1',
|
||||
'addresses': [('create', [{
|
||||
'name': 'party1',
|
||||
'street': 'ST2',
|
||||
'city': 'New Delhi',
|
||||
}])]
|
||||
}, {
|
||||
'name': 'Party 2',
|
||||
'addresses': [('create', [{
|
||||
'name': 'party2',
|
||||
'street': 'ST2',
|
||||
'city': 'Mumbai',
|
||||
}])]
|
||||
}, {
|
||||
'name': 'Party 3',
|
||||
'addresses': [('create', [{
|
||||
'name': 'party3',
|
||||
'street': 'ST2',
|
||||
'city': 'New Delhi',
|
||||
}])]
|
||||
}])
|
||||
|
||||
# Merge party2, party3 to party1
|
||||
party2.merge_into(party1)
|
||||
party3.merge_into(party1)
|
||||
|
||||
self.assertEqual(len(party1.addresses), 3)
|
||||
|
||||
|
||||
def suite():
|
||||
"""
|
||||
Define suite
|
||||
"""
|
||||
test_suite = trytond.tests.test_tryton.suite()
|
||||
test_suite.addTests(
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestParty)
|
||||
)
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
57
tests/test_views_depends.py
Normal file
57
tests/test_views_depends.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests/test_views_depends.py
|
||||
|
||||
:copyright: (C) 2014 by Openlabs Technologies & Consulting (P) Limited
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
DIR = os.path.abspath(os.path.normpath(os.path.join(
|
||||
__file__, '..', '..', '..', '..', '..', 'trytond'
|
||||
)))
|
||||
if os.path.isdir(DIR):
|
||||
sys.path.insert(0, os.path.dirname(DIR))
|
||||
import unittest
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
from trytond.tests.test_tryton import test_view, test_depends
|
||||
|
||||
|
||||
class TestViewsDepends(unittest.TestCase):
|
||||
'''
|
||||
Test views and depends
|
||||
'''
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up data used in the tests.
|
||||
this method is called before each test function execution.
|
||||
"""
|
||||
trytond.tests.test_tryton.install_module('party_merge')
|
||||
|
||||
def test0005views(self):
|
||||
'''
|
||||
Test views.
|
||||
'''
|
||||
test_view('party_merge')
|
||||
|
||||
def test0006depends(self):
|
||||
'''
|
||||
Test depends.
|
||||
'''
|
||||
test_depends()
|
||||
|
||||
|
||||
def suite():
|
||||
"""
|
||||
Define suite
|
||||
"""
|
||||
test_suite = trytond.tests.test_tryton.suite()
|
||||
test_suite.addTests(
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestViewsDepends)
|
||||
)
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
6
tryton.cfg
Normal file
6
tryton.cfg
Normal file
|
@ -0,0 +1,6 @@
|
|||
[tryton]
|
||||
version=3.2.1.0
|
||||
depends:
|
||||
party
|
||||
xml:
|
||||
party.xml
|
11
view/party_form.xml
Normal file
11
view/party_form.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/group[@id='checkboxes']" position="after">
|
||||
<group colspan="6" col="4" id="merged_into">
|
||||
<label name="merged_into"/>
|
||||
<field name="merged_into" colspan="3"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</data>
|
5
view/party_merge_view_form.xml
Normal file
5
view/party_merge_view_form.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<form string="Merge Parties" col="4">
|
||||
<label name="target"/>
|
||||
<field name="target" colspan="3"/>
|
||||
</form>
|
Loading…
Reference in a new issue