Initial commit.

Original module from openlabs with some improvements.
This commit is contained in:
Albert Cervera i Areny 2015-02-05 19:20:31 +01:00
commit a50a7e96b5
14 changed files with 569 additions and 0 deletions

16
Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
psycopg2
# Install this package itself
.

6
setup.cfg Normal file
View 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
View 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
View 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
View 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())

View 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
View File

@ -0,0 +1,6 @@
[tryton]
version=3.2.1.0
depends:
party
xml:
party.xml

11
view/party_form.xml Normal file
View 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>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<form string="Merge Parties" col="4">
<label name="target"/>
<field name="target" colspan="3"/>
</form>