From e25f3961875d257d16bc026134c659ad6f94a3d2 Mon Sep 17 00:00:00 2001 From: PSala Date: Wed, 2 Oct 2024 15:33:15 +0200 Subject: [PATCH 01/16] Fix py3 long check --- orm_mongodb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/orm_mongodb.py b/orm_mongodb.py index 62b08e8..bde223d 100644 --- a/orm_mongodb.py +++ b/orm_mongodb.py @@ -33,6 +33,7 @@ from tools.sql_utils import isolation import six import tools +from six import integer_types #mongodb stuff @@ -546,7 +547,7 @@ def unlink(self, cr, uid, ids, context=None): if not ids: return True - if isinstance(ids, (int, long)): + if isinstance(ids, integer_types): ids = [ids] self.pool.get('ir.model.access').check(cr, uid, self._name, @@ -575,7 +576,7 @@ def perm_read(self, cr, user, ids, context=None, details=True): if not ids: return [] - if isinstance(ids, (int, long)): + if isinstance(ids, integer_types): ids = [ids] collection = mdbpool.get_collection(self._table) From fd92f12014563d1f2f0e666daa297528941d3816 Mon Sep 17 00:00:00 2001 From: PSala Date: Wed, 2 Oct 2024 15:34:06 +0200 Subject: [PATCH 02/16] Update deprecates pymongo/server code --- orm_mongodb.py | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/orm_mongodb.py b/orm_mongodb.py index bde223d..70e1cd3 100644 --- a/orm_mongodb.py +++ b/orm_mongodb.py @@ -53,6 +53,12 @@ class orm_mongodb(orm.orm_template): _inherit_fields = {} + def _create_index_field(self, collection, field_name, background=True, **kwargs): + try: + res = collection.create_index(field_name, background=True, **kwargs) + except Exception as e: + raise except_orm('MongoDB create id field index error', '{}'.format(e)) + def _auto_init(self, cr, context=None): if context is None: context = {} @@ -71,10 +77,9 @@ def _auto_init(self, cr, context=None): collection.save(vals) collection = db[self._table] - #Create index for the id field + # Create index for the id field try: - # Replace for create_index in a future, ensure_index deprecated since 3. - collection.ensure_index([('id', pymongo.ASCENDING)], cache_for=300, unique=True) + collection.create_index([('id', pymongo.ASCENDING)], unique=True) except pymongo.errors.OperationFailure as e: if e.details and "An existing index has the same name as the requested index" in e.details.get("errmsg", " "): pass @@ -91,10 +96,8 @@ def _auto_init(self, cr, context=None): for field_name, field_obj in six.iteritems(self._columns): if getattr(field_obj, 'select', False): if field_name not in created_idx: - collection.ensure_index(field_name, background=True) + self._create_index_field(collection, field_name) - if db.error(): - raise except_orm('MongoDB create id field index error', db.error()) #Update docs with new default values if they do not exist #If we find at least one document with this field #we assume that the field is present in the collection @@ -108,15 +111,15 @@ def _auto_init(self, cr, context=None): %s of collection %s' % (def_fields, self._table)) def_values = self.default_get(cr, 1, def_fields) - collection.update({}, - {'$set': def_values}, - upsert=False, - manipulate=False, - w=1, - multi=True) - - if db.error(): - raise except_orm('MongoDB update defaults error', db.error()) + try: + collection.update({}, + {'$set': def_values}, + upsert=False, + manipulate=False, + w=1, + multi=True) + except Exception as e: + raise except_orm('MongoDB update defaults error', '{}'.format(e)) def __init__(self, cr): super(orm_mongodb, self).__init__(cr) @@ -401,12 +404,12 @@ def write(self, cr, user, ids, vals, context=None): }) #bulk update with modifiers, and safe mode - collection.update({'id': {'$in': ids}}, - {'$set': vals}, - False, False, True, True) - - if db.error(): - raise except_orm('MongoDB update error', db.error()) + try: + collection.update({'id': {'$in': ids}}, + {'$set': vals}, + False, False, True, True, w=1) + except Exception as e: + raise except_orm('MongoDB update error', '{}'.format(e)) return True From 7bb438a9b51fac43654132a713269abb0a6c4a87 Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:02:14 +0200 Subject: [PATCH 03/16] New index test --- tests/__init__.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index e3f79bf..255a4d0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -92,7 +92,8 @@ class MongoModelTest(osv_mongodb.osv_mongodb): _columns = { 'name': fields.char('Name', size=64), 'other_name': fields.char('Other name', size=64), - 'boolean_field': fields.boolean('Boolean Field', size=64) + 'boolean_field': fields.boolean('Boolean Field', size=64), + 'integer_field_with_index': fields.integer('Integer Field', select=1) } @@ -214,4 +215,21 @@ def test_boolean(self): expect(len(m_ids)).to(equal(0)) m_ids = mmt_obj.search(cursor, uid, [('boolean_field', '=', False)]) - expect(len(m_ids)).to(be_above(0)) \ No newline at end of file + expect(len(m_ids)).to(be_above(0)) + + def test_create_index_from_select(self): + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + # Create test + mmt_id = mmt_obj.create(cursor, uid, { + 'name': 'Foo', + 'other_name': 'Bar', + 'boolean_field': True, + 'integer_field_with_index': 8 + }) + from mongodb_backend.mongodb2 import mdbpool + db = mdbpool.get_db() + collection = db.mongomodel_test + self.assertIn('integer_field_with_index_1', collection.index_information()) From b3f9e52c5c008cd01d3539b003f6d3517d92dcbe Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:02:55 +0200 Subject: [PATCH 04/16] Test workflows mongo 3, 5 and 8 py2/3 --- .github/workflows/mongo3.yml | 113 +++++++++++++++++++++++++++++++++++ .github/workflows/mongo5.yml | 113 +++++++++++++++++++++++++++++++++++ .github/workflows/mongo8.yml | 113 +++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 .github/workflows/mongo3.yml create mode 100644 .github/workflows/mongo5.yml create mode 100644 .github/workflows/mongo8.yml diff --git a/.github/workflows/mongo3.yml b/.github/workflows/mongo3.yml new file mode 100644 index 0000000..696d3ae --- /dev/null +++ b/.github/workflows/mongo3.yml @@ -0,0 +1,113 @@ +name: Mongodb Backend Tests (mongo3) + +on: + pull_request: + types: [ labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.label.name || github.ref }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + run-tests: + if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [ "2.7", "3.11" ] + erp-module: [ "mongodb_backend" ] + services: + postgres: + image: timescale/timescaledb-ha:pg15-ts2.11-all + env: + POSTGRES_USER: erp + POSTGRES_PASSWORD: erp + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 127.0.0.1:5432:5432 + + redis: + image: redis:5.0 + ports: + - 127.0.0.1:6379:6379 + + mongo: + image: mongo:3.0 + ports: + - 127.0.0.1:27017:27017 + steps: + - name: Checkout code (mongodb_backend) + uses: actions/checkout@v4 + with: + path: src/mongodb_backend + + - name: Checkout code (oorq) + uses: actions/checkout@v4 + with: + path: src/oorq + repository: gisce/oorq + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Checkout code (ERP) + uses: actions/checkout@v4 + with: + path: src/erp + repository: gisce/erp + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Set up Python 3 + if: matrix.python-version != '2.7' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python 2 + if: matrix.python-version == '2.7' + run: | + sudo apt update + sudo apt install python2 python2-dev python-pip + sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1 + sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2 + printf '1\n' | sudo update-alternatives --config python + cd /usr/bin + sudo ln -s /usr/bin/pip2 ./pip + + - name: Install Requirements + run: | + pip install -r src/erp/requirements.txt + pip install -r src/erp/requirements-dev.txt + pip install pymongo==3.13.0 + pip install destral + + - name: Link addons + run: | + cd src/erp + python tools/link_addons.py + + - name: Run tests + env: + OPENERP_SECRET: shhhhhhhht + OPENERP_REDIS_URL: redis://localhost:6379/0 + OPENERP_ROOT_PATH: ${{ github.workspace }}/src/erp/server/bin + OPENERP_ADDONS_PATH: ${{ github.workspace }}/src/erp/server/bin/addons + PYTHONPATH: ${{ github.workspace }}/src/erp/server/bin:${{ github.workspace }}/src/erp/server/bin/addons:${{ github.workspace }}/src/erp/server/sitecustomize + OPENERP_DB_USER: erp + OPENERP_DB_PASSWORD: erp + OPENERP_DB_HOST: localhost + + run: | + destral --report-coverage --enable-coverage --report-junitxml ${{ github.workspace }}/report_tests -m ${{ matrix.erp-module }} + + - name: Publish tests Results Mongo + if: (success() || failure()) + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + junit_files: "report_tests/*.xml" + check_name: "TestsResults_mongo3${{ matrix.python-version }}_${{ matrix.erp-module }}" + comment_title: "Python Mongo 3 ${{ matrix.python-version }} Tests for ${{ matrix.erp-module }}" diff --git a/.github/workflows/mongo5.yml b/.github/workflows/mongo5.yml new file mode 100644 index 0000000..8f8069d --- /dev/null +++ b/.github/workflows/mongo5.yml @@ -0,0 +1,113 @@ +name: Mongodb Backend Tests (mongo5) + +on: + pull_request: + types: [ labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.label.name || github.ref }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + run-tests: + if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [ "2.7", "3.11" ] + erp-module: [ "mongodb_backend" ] + services: + postgres: + image: timescale/timescaledb-ha:pg15-ts2.11-all + env: + POSTGRES_USER: erp + POSTGRES_PASSWORD: erp + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 127.0.0.1:5432:5432 + + redis: + image: redis:5.0 + ports: + - 127.0.0.1:6379:6379 + + mongo: + image: mongo:5.0 + ports: + - 127.0.0.1:27017:27017 + steps: + - name: Checkout code (mongodb_backend) + uses: actions/checkout@v4 + with: + path: src/mongodb_backend + + - name: Checkout code (oorq) + uses: actions/checkout@v4 + with: + path: src/oorq + repository: gisce/oorq + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Checkout code (ERP) + uses: actions/checkout@v4 + with: + path: src/erp + repository: gisce/erp + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Set up Python 3 + if: matrix.python-version != '2.7' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python 2 + if: matrix.python-version == '2.7' + run: | + sudo apt update + sudo apt install python2 python2-dev python-pip + sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1 + sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2 + printf '1\n' | sudo update-alternatives --config python + cd /usr/bin + sudo ln -s /usr/bin/pip2 ./pip + + - name: Install Requirements + run: | + pip install -r src/erp/requirements.txt + pip install -r src/erp/requirements-dev.txt + pip install pymongo==3.13.0 + pip install destral + + - name: Link addons + run: | + cd src/erp + python tools/link_addons.py + + - name: Run tests + env: + OPENERP_SECRET: shhhhhhhht + OPENERP_REDIS_URL: redis://localhost:6379/0 + OPENERP_ROOT_PATH: ${{ github.workspace }}/src/erp/server/bin + OPENERP_ADDONS_PATH: ${{ github.workspace }}/src/erp/server/bin/addons + PYTHONPATH: ${{ github.workspace }}/src/erp/server/bin:${{ github.workspace }}/src/erp/server/bin/addons:${{ github.workspace }}/src/erp/server/sitecustomize + OPENERP_DB_USER: erp + OPENERP_DB_PASSWORD: erp + OPENERP_DB_HOST: localhost + + run: | + destral --report-coverage --enable-coverage --report-junitxml ${{ github.workspace }}/report_tests -m ${{ matrix.erp-module }} + + - name: Publish tests Results Mongo + if: (success() || failure()) + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + junit_files: "report_tests/*.xml" + check_name: "TestsResults_mongo5${{ matrix.python-version }}_${{ matrix.erp-module }}" + comment_title: "Python Mongo 5 ${{ matrix.python-version }} Tests for ${{ matrix.erp-module }}" diff --git a/.github/workflows/mongo8.yml b/.github/workflows/mongo8.yml new file mode 100644 index 0000000..136e89f --- /dev/null +++ b/.github/workflows/mongo8.yml @@ -0,0 +1,113 @@ +name: Mongodb Backend Tests (mongo8) + +on: + pull_request: + types: [ labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.label.name || github.ref }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + run-tests: + if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [ "2.7", "3.11" ] + erp-module: [ "mongodb_backend" ] + services: + postgres: + image: timescale/timescaledb-ha:pg15-ts2.11-all + env: + POSTGRES_USER: erp + POSTGRES_PASSWORD: erp + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 127.0.0.1:5432:5432 + + redis: + image: redis:5.0 + ports: + - 127.0.0.1:6379:6379 + + mongo: + image: mongo:8.0 + ports: + - 127.0.0.1:27017:27017 + steps: + - name: Checkout code (mongodb_backend) + uses: actions/checkout@v4 + with: + path: src/mongodb_backend + + - name: Checkout code (oorq) + uses: actions/checkout@v4 + with: + path: src/oorq + repository: gisce/oorq + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Checkout code (ERP) + uses: actions/checkout@v4 + with: + path: src/erp + repository: gisce/erp + token: ${{ secrets.RO_GITHUB_ACTIONS_REPOS }} + + - name: Set up Python 3 + if: matrix.python-version != '2.7' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python 2 + if: matrix.python-version == '2.7' + run: | + sudo apt update + sudo apt install python2 python2-dev python-pip + sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1 + sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2 + printf '1\n' | sudo update-alternatives --config python + cd /usr/bin + sudo ln -s /usr/bin/pip2 ./pip + + - name: Install Requirements + run: | + pip install -r src/erp/requirements.txt + pip install -r src/erp/requirements-dev.txt + pip install pymongo==3.13.0 + pip install destral + + - name: Link addons + run: | + cd src/erp + python tools/link_addons.py + + - name: Run tests + env: + OPENERP_SECRET: shhhhhhhht + OPENERP_REDIS_URL: redis://localhost:6379/0 + OPENERP_ROOT_PATH: ${{ github.workspace }}/src/erp/server/bin + OPENERP_ADDONS_PATH: ${{ github.workspace }}/src/erp/server/bin/addons + PYTHONPATH: ${{ github.workspace }}/src/erp/server/bin:${{ github.workspace }}/src/erp/server/bin/addons:${{ github.workspace }}/src/erp/server/sitecustomize + OPENERP_DB_USER: erp + OPENERP_DB_PASSWORD: erp + OPENERP_DB_HOST: localhost + + run: | + destral --report-coverage --enable-coverage --report-junitxml ${{ github.workspace }}/report_tests -m ${{ matrix.erp-module }} + + - name: Publish tests Results Mongo + if: (success() || failure()) + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + junit_files: "report_tests/*.xml" + check_name: "TestsResults_mongo8${{ matrix.python-version }}_${{ matrix.erp-module }}" + comment_title: "Python Mongo 8 ${{ matrix.python-version }} Tests for ${{ matrix.erp-module }}" From a3f939bafcc1a629a7cef43032f3b8640665157c Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:21:21 +0200 Subject: [PATCH 05/16] Update workflows --- .github/workflows/mongo3.yml | 3 ++- .github/workflows/mongo5.yml | 3 ++- .github/workflows/mongo8.yml | 3 ++- .travis.yml | 44 ------------------------------------ 4 files changed, 6 insertions(+), 47 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/mongo3.yml b/.github/workflows/mongo3.yml index 696d3ae..2149fc9 100644 --- a/.github/workflows/mongo3.yml +++ b/.github/workflows/mongo3.yml @@ -10,7 +10,7 @@ concurrency: jobs: - run-tests: + run-tests-3: if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' runs-on: ubuntu-latest strategy: @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install pymongo==3.13.0 + pip install rq-scheduler pip install destral - name: Link addons diff --git a/.github/workflows/mongo5.yml b/.github/workflows/mongo5.yml index 8f8069d..043df79 100644 --- a/.github/workflows/mongo5.yml +++ b/.github/workflows/mongo5.yml @@ -10,7 +10,7 @@ concurrency: jobs: - run-tests: + run-tests-5: if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' runs-on: ubuntu-latest strategy: @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install pymongo==3.13.0 + pip install rq-scheduler pip install destral - name: Link addons diff --git a/.github/workflows/mongo8.yml b/.github/workflows/mongo8.yml index 136e89f..f481f9a 100644 --- a/.github/workflows/mongo8.yml +++ b/.github/workflows/mongo8.yml @@ -10,7 +10,7 @@ concurrency: jobs: - run-tests: + run-tests-8: if: github.event_name == 'pull_request' && github.event.label.name == 'to be merged' runs-on: ubuntu-latest strategy: @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install pymongo==3.13.0 + pip install rq-scheduler pip install destral - name: Link addons diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7609bf6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -sudo: false -language: python -python: - - "2.7" -env: - - SERVER_DIR=/tmp/server - OPENERP_DB_HOST=localhost - OPENERP_DB_USER=postgres - OPENERP_ROOT_PATH=${SERVER_DIR}/bin - OPENERP_ADDONS_PATH=${OPENERP_ROOT_PATH}/addons - PYTHONPATH=${OPENERP_ROOT_PATH}:${OPENERP_ADDONS_PATH} - DESTRAL_USE_TEMPLATE=False - MONGODB=3.0.15 -services: - - postgresql -install: - - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB}.tgz - - tar xzf mongodb-linux-x86_64-${MONGODB}.tgz - - ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --version - - easy_install egenix-mx-base - - pip install lxml "psycopg2<2.8" babel vatnumber "reportlab==3.0" - - pip install https://github.com/gisce/destral/archive/master.zip - - pip install "pymongo<3.0" - - mkdir -p ${SERVER_DIR} - - pushd ${SERVER_DIR} - - curl -L https://github.com/odoo/odoo/archive/5.0.tar.gz | tar xvzf - --strip-components 1 - - pushd ${OPENERP_ADDONS_PATH} - - for m in `find ../../addons -name '__terp__.py' -exec dirname {} \;`; do ln -s $m .; done - - ln -s ${TRAVIS_BUILD_DIR} . - - popd - - popd - -before_script: - - mkdir ${PWD}/mongodb-linux-x86_64-${MONGODB}/data - - ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --dbpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/data --logpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/mongodb.log --fork - -script: - - destral -m mongodb_backend - -after_script: - - pkill mongod - -notifications: - irc: "irc.freenode.org#gisce-commits" From c9d324bfb1a01e74d2df8c6758d9da5c931c64e2 Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:27:13 +0200 Subject: [PATCH 06/16] Update workflows --- .github/workflows/mongo3.yml | 2 +- .github/workflows/mongo5.yml | 2 +- .github/workflows/mongo8.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mongo3.yml b/.github/workflows/mongo3.yml index 2149fc9..e3ccb80 100644 --- a/.github/workflows/mongo3.yml +++ b/.github/workflows/mongo3.yml @@ -82,8 +82,8 @@ jobs: run: | pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt + pip install -r src/erp/server/bin/addons/base/requirements.txt pip install pymongo==3.13.0 - pip install rq-scheduler pip install destral - name: Link addons diff --git a/.github/workflows/mongo5.yml b/.github/workflows/mongo5.yml index 043df79..63e1079 100644 --- a/.github/workflows/mongo5.yml +++ b/.github/workflows/mongo5.yml @@ -82,8 +82,8 @@ jobs: run: | pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt + pip install -r src/erp/server/bin/addons/base/requirements.txt pip install pymongo==3.13.0 - pip install rq-scheduler pip install destral - name: Link addons diff --git a/.github/workflows/mongo8.yml b/.github/workflows/mongo8.yml index f481f9a..469a952 100644 --- a/.github/workflows/mongo8.yml +++ b/.github/workflows/mongo8.yml @@ -82,8 +82,8 @@ jobs: run: | pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt + pip install -r src/erp/server/bin/addons/base/requirements.txt pip install pymongo==3.13.0 - pip install rq-scheduler pip install destral - name: Link addons From a596346e0f1806ce8bdb59f6959d941f36f685bc Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:56:32 +0200 Subject: [PATCH 07/16] Improve test coverage --- tests/__init__.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index 255a4d0..a7f6cb2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -233,3 +233,36 @@ def test_create_index_from_select(self): db = mdbpool.get_db() collection = db.mongomodel_test self.assertIn('integer_field_with_index_1', collection.index_information()) + + def test_orm_operation(self): + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + import uuid + unique_ident = '{}'.format(uuid.uuid4()) + + # Test create + mmt_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident, + 'other_name': 'Bar', + 'boolean_field': True, + 'integer_field_with_index': 8 + }) + self.assertTrue(mmt_id) + + # Test search + found_ids = mmt_obj.search(cursor, uid, [('name', '=', unique_ident)]) + self.assertTrue(found_ids) + self.assertIn(mmt_id, found_ids) + self.assertEqual(len(found_ids), 1) + + # Test write/read + mmt_obj.write(cursor, uid, found_ids, {'other_name': unique_ident}) + field_content = mmt_obj.read(cursor, uid, mmt_id, ['other_name'])['other_name'] + self.assertEqual(field_content, unique_ident) + + # Test Unlink + mmt_obj.unlink(cursor, uid, found_ids) + found_ids = mmt_obj.search(cursor, uid, [('name', '=', unique_ident)]) + self.assertFalse(found_ids) From 8420d34b6ed55c9f3b385c23e33c43c97b39dc82 Mon Sep 17 00:00:00 2001 From: PSala Date: Thu, 3 Oct 2024 11:57:10 +0200 Subject: [PATCH 08/16] Update unlink --- orm_mongodb.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/orm_mongodb.py b/orm_mongodb.py index 70e1cd3..60ca8fd 100644 --- a/orm_mongodb.py +++ b/orm_mongodb.py @@ -55,7 +55,7 @@ class orm_mongodb(orm.orm_template): def _create_index_field(self, collection, field_name, background=True, **kwargs): try: - res = collection.create_index(field_name, background=True, **kwargs) + res = collection.create_index(field_name, background=background, **kwargs) except Exception as e: raise except_orm('MongoDB create id field index error', '{}'.format(e)) @@ -559,10 +559,10 @@ def unlink(self, cr, uid, ids, context=None): # Remove binary fields (files in gridfs) self.unlink_binary_gridfs_fields(collection, ids) #Remove with safe mode - collection.remove({'id': {'$in': ids}}, True) - - if db.error(): - raise except_orm('MongoDB unlink error', db.error()) + try: + collection.remove({'id': {'$in': ids}}, True, w=1) + except Exception as e: + raise except_orm('MongoDB unlink error', '{}'.format(e)) return True From 0f81b05272ea2a6dc3bcd9b3c9ba5b8c34d21eeb Mon Sep 17 00:00:00 2001 From: PSala Date: Tue, 8 Oct 2024 09:16:33 +0200 Subject: [PATCH 09/16] Add more tests and increase coverage --- tests/__init__.py | 74 +++++++++++++++++++++++++++++++++++- tests/fixtures/15796004.png | Bin 0 -> 87197 bytes 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/15796004.png diff --git a/tests/__init__.py b/tests/__init__.py index a7f6cb2..6a45a9a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,6 +5,7 @@ from mongodb_backend import testing, osv_mongodb from expects import * from destral.transaction import Transaction +from mongodb_backend import fields as mdb_fields from mongodb_backend import mongodb2 from mongodb_backend import orm_mongodb @@ -93,7 +94,17 @@ class MongoModelTest(osv_mongodb.osv_mongodb): 'name': fields.char('Name', size=64), 'other_name': fields.char('Other name', size=64), 'boolean_field': fields.boolean('Boolean Field', size=64), - 'integer_field_with_index': fields.integer('Integer Field', select=1) + 'integer_field_with_index': fields.integer('Integer Field', select=1), + 'file_example': fields.binary('test') + } + + +class NoMongoModelTestWithGridFs(osv.osv): + _name = 'no.mongomodel.test.with.gridfs' + + _columns = { + 'name': fields.char('Name', size=64), + 'file_example': mdb_fields.gridfs('test') } @@ -136,8 +147,14 @@ def setUp(self): self.txn = Transaction().start(self.database) def tearDown(self): + self.cleanup() self.txn.stop() + def cleanup(self): + from mongodb_backend.mongodb2 import mdbpool + db = mdbpool.get_db() + db.drop_collection("mongomodel_test") + def create_model(self): cursor = self.txn.cursor MongoModelTest() @@ -266,3 +283,58 @@ def test_orm_operation(self): mmt_obj.unlink(cursor, uid, found_ids) found_ids = mmt_obj.search(cursor, uid, [('name', '=', unique_ident)]) self.assertFalse(found_ids) + + def test_binary(self): + from addons import get_module_resource + import uuid + from base64 import b64encode, b64decode + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + + unique_ident = '{}'.format(uuid.uuid4()) + + image_path = get_module_resource( + 'mongodb_backend', 'tests', 'fixtures', '15796004.png' + ) + + with open(image_path, 'rb') as image_fd: + fb = image_fd.read() + + mmt_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident, + 'other_name': 'Bar', + 'boolean_field': True, + 'integer_field_with_index': 8 + }) + mmt_obj.write(cursor, uid, [mmt_id], {'file_example': b64encode(fb)}) + res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] + self.assertEqual(b64decode(res_file), fb) + + def test_gridfs(self): + from addons import get_module_resource + from base64 import b64encode, b64decode + cursor = self.txn.cursor + uid = self.txn.user + NoMongoModelTestWithGridFs() + osv.class_pool[NoMongoModelTestWithGridFs._name].createInstance( + self.openerp.pool, 'mongodb_backend', cursor + ) + mmt_obj = self.openerp.pool.get(NoMongoModelTestWithGridFs._name) + mmt_obj._auto_init(cursor) + mmt_id = mmt_obj.create(cursor, uid, { + 'name': 'test' + }) + + image_path = get_module_resource( + 'mongodb_backend', 'tests', 'fixtures', '15796004.png' + ) + + with open(image_path, 'rb') as image_fd: + fb = image_fd.read() + + mmt_obj.write(cursor, uid, [mmt_id], {'file_example': b64encode(fb)}) + res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] + self.assertEqual(b64decode(res_file), fb) + diff --git a/tests/fixtures/15796004.png b/tests/fixtures/15796004.png new file mode 100644 index 0000000000000000000000000000000000000000..906a9a7ed36160d74035828734af7f21dcf05961 GIT binary patch literal 87197 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

X9&%Tjd<2!#wT~!$3s!G37jZqLG_rw_OK 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 c3jhHB|GDMGv2~P0HUIzs07*qoM6N<$f+4I Date: Tue, 8 Oct 2024 10:20:39 +0200 Subject: [PATCH 10/16] Add more tests and increase coverage --- tests/__init__.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 6a45a9a..dfaca22 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,6 +9,7 @@ from mongodb_backend import mongodb2 from mongodb_backend import orm_mongodb +import datetime class TestTranslateDomain(unittest.TestCase): @@ -95,7 +96,9 @@ class MongoModelTest(osv_mongodb.osv_mongodb): 'other_name': fields.char('Other name', size=64), 'boolean_field': fields.boolean('Boolean Field', size=64), 'integer_field_with_index': fields.integer('Integer Field', select=1), - 'file_example': fields.binary('test') + 'file_example': fields.binary('test'), + 'date_field': fields.date('Date Field'), + 'datetime_field': fields.datetime('Datetime Field') } @@ -338,3 +341,70 @@ def test_gridfs(self): res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] self.assertEqual(b64decode(res_file), fb) + def test_dates(self): + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + import uuid + + expected_date_str = '2022-12-31' + expected_datetime_str = '2022-12-31 12:00:00' + + unique_ident2 = '{}'.format(uuid.uuid4()) + + mmt2_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident2, + 'date_field': expected_date_str, + 'datetime_field': expected_datetime_str + }) + + res2 = mmt_obj.read(cursor, uid, mmt2_id, ['date_field', 'datetime_field']) + self.assertEqual(res2['date_field'], expected_date_str) + self.assertEqual(res2['datetime_field'], expected_datetime_str) + + expected_datetime_str_2 = '2023-12-31 00:00:00' + write_value = '2023-12-31' + + mmt_obj.write(cursor, uid, [mmt2_id], {'datetime_field': write_value}) + res2 = mmt_obj.read(cursor, uid, mmt2_id, ['datetime_field']) + self.assertEqual(res2['datetime_field'], expected_datetime_str_2) + + + def test_export_data(self): + from base64 import b64encode, b64decode + from io import BytesIO + import pandas as pd + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + import uuid + unique_ident = '{}'.format(uuid.uuid4()) + + # Test create + mmt_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident, + 'other_name': 'Bar', + 'boolean_field': True, + 'integer_field_with_index': 8 + }) + unique_ident2 = '{}'.format(uuid.uuid4()) + mmt2_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident2, + 'other_name': 'Bar2', + 'boolean_field': False, + 'integer_field_with_index': 4 + }) + res_csv = mmt_obj.export_data2( + cursor, uid, [], 100, ['name', 'other_name', 'boolean_field'], 'csv', {'prefetch': False} + ) + csv_f = BytesIO(b64decode(res_csv['datas'])) + res_excel = mmt_obj.export_data2( + cursor, uid, [], 100, ['name', 'other_name', 'boolean_field'], 'xlsx', {'prefetch': False} + ) + xlsx_f = BytesIO(b64decode(res_excel['datas'])) + df_csv = pd.read_csv(csv_f, sep=';') + df_xlsx = pd.read_excel(xlsx_f) + self.assertEqual(df_csv['Name'].tolist(), [unique_ident, unique_ident2]) + self.assertEqual(df_xlsx['Name'].tolist(), [unique_ident, unique_ident2]) From 7e6b009535ae0beaab23b62bff7beebcdce4c58c Mon Sep 17 00:00:00 2001 From: PSala Date: Tue, 8 Oct 2024 15:20:09 +0200 Subject: [PATCH 11/16] Add more tests and increase coverage --- tests/__init__.py | 81 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index dfaca22..c9eb759 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -98,7 +98,21 @@ class MongoModelTest(osv_mongodb.osv_mongodb): 'integer_field_with_index': fields.integer('Integer Field', select=1), 'file_example': fields.binary('test'), 'date_field': fields.date('Date Field'), - 'datetime_field': fields.datetime('Datetime Field') + 'datetime_field': fields.datetime('Datetime Field'), + 'function_field': fields.function( + lambda self, cursor, uid, ids, *a, **k: + {_id: 'test' for _id in (ids if isinstance(ids, (list, tuple)) else [ids])}, + string='func', type='text', method=True, store=False + ), + 'function_field_multi': fields.function( + lambda self, cursor, uid, ids, *a, **k: + {_id: {'function_field_multi': 'test'} for _id in (ids if isinstance(ids, (list, tuple)) else [ids])}, + string='func mult', type='text', method=True, store=False, multi='test' + ), + } + _defaults = { + 'date_field': lambda *a: '2024-01-01', + 'integer_field_with_index': lambda *a: 1 } @@ -281,6 +295,14 @@ def test_orm_operation(self): mmt_obj.write(cursor, uid, found_ids, {'other_name': unique_ident}) field_content = mmt_obj.read(cursor, uid, mmt_id, ['other_name'])['other_name'] self.assertEqual(field_content, unique_ident) + all_content = mmt_obj.read(cursor, uid, mmt_id, None) + expected_content = { + 'name': unique_ident, 'date_field': '2024-01-01', + 'boolean_field': True, 'integer_field_with_index': 8, + 'other_name': unique_ident, 'id': mmt_id, 'function_field': 'test', 'function_field_multi': 'test' + } + self.assertEqual(all_content, expected_content) + # Test Unlink mmt_obj.unlink(cursor, uid, found_ids) @@ -370,7 +392,6 @@ def test_dates(self): res2 = mmt_obj.read(cursor, uid, mmt2_id, ['datetime_field']) self.assertEqual(res2['datetime_field'], expected_datetime_str_2) - def test_export_data(self): from base64 import b64encode, b64decode from io import BytesIO @@ -408,3 +429,59 @@ def test_export_data(self): df_xlsx = pd.read_excel(xlsx_f) self.assertEqual(df_csv['Name'].tolist(), [unique_ident, unique_ident2]) self.assertEqual(df_xlsx['Name'].tolist(), [unique_ident, unique_ident2]) + + def test_order(self): + self.create_model() + cursor = self.txn.cursor + uid = self.txn.user + mmt_obj = self.openerp.pool.get(MongoModelTest._name) + import uuid + unique_ident = '{}'.format(uuid.uuid4()) + + # Test create + mmt_id = mmt_obj.create(cursor, uid, { + 'name': unique_ident, + 'other_name': 'Bar', + 'boolean_field': True, + 'integer_field_with_index': 7 + }) + + unique_ident2 = '{}'.format(uuid.uuid4()) + + # Test create + mmt_id_2 = mmt_obj.create(cursor, uid, { + 'name': unique_ident2, + 'other_name': 'Bar1', + 'integer_field_with_index': 8 + }) + + unique_ident3 = '{}'.format(uuid.uuid4()) + + # Test create + mmt_id_3 = mmt_obj.create(cursor, uid, { + 'name': unique_ident3, + 'other_name': 'Bar2', + 'integer_field_with_index': 5 + }) + + unique_ident4 = '{}'.format(uuid.uuid4()) + + mmt_id_4 = mmt_obj.create(cursor, uid, { + 'name': unique_ident4, + 'other_name': 'Bar4', + 'integer_field_with_index': 5 + }) + res = mmt_obj.search(cursor, uid, []) + self.assertEqual(res, [mmt_id, mmt_id_2, mmt_id_3, mmt_id_4]) + + res = mmt_obj.search(cursor, uid, [('name', '!=', False)]) + self.assertEqual(res, [mmt_id, mmt_id_2, mmt_id_3, mmt_id_4]) + + res = mmt_obj.search(cursor, uid, [('name', '!=', False)], order='integer_field_with_index desc') + self.assertEqual(res, [mmt_id_2, mmt_id, mmt_id_4, mmt_id_3]) + + res = mmt_obj.search(cursor, uid, [('name', '!=', False)], order='integer_field_with_index asc') + self.assertEqual(res, [mmt_id_3, mmt_id_4, mmt_id, mmt_id_2]) + + res = mmt_obj.search(cursor, uid, [('name', '!=', False)], order='integer_field_with_index asc, other_name desc') + self.assertEqual(res, [mmt_id_4, mmt_id_3, mmt_id, mmt_id_2]) From cad993e91bb7bd166c5e6907aa58f707c3f2e4a6 Mon Sep 17 00:00:00 2001 From: PSala Date: Tue, 8 Oct 2024 15:39:50 +0200 Subject: [PATCH 12/16] Fix gridfs field on mongo model if never writed --- orm_mongodb.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/orm_mongodb.py b/orm_mongodb.py index 60ca8fd..b25c1a7 100644 --- a/orm_mongodb.py +++ b/orm_mongodb.py @@ -160,9 +160,12 @@ def read_binary_gridfs_fields(self, fields, vals): if binary_fields: for val in vals: for binary_field in binary_fields_to_read: - val[binary_field] = self.transform_binary_gridfs_field( - binary_field, val[binary_field], 'read' - ) + if binary_field not in val: + continue + else: + val[binary_field] = self.transform_binary_gridfs_field( + binary_field, val[binary_field], 'read' + ) def write_binary_gridfs_fields(self, val): binary_fields = self.get_binary_gridfs_fields() From cfc0aad4ada7945c8ac2f8f8fe76b24a7795213e Mon Sep 17 00:00:00 2001 From: PSala Date: Tue, 8 Oct 2024 15:40:19 +0200 Subject: [PATCH 13/16] Add more tests and increase coverage --- tests/__init__.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index c9eb759..538735d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -96,7 +96,7 @@ class MongoModelTest(osv_mongodb.osv_mongodb): 'other_name': fields.char('Other name', size=64), 'boolean_field': fields.boolean('Boolean Field', size=64), 'integer_field_with_index': fields.integer('Integer Field', select=1), - 'file_example': fields.binary('test'), + 'file_example': fields.binary('test', gridfs=True), 'date_field': fields.date('Date Field'), 'datetime_field': fields.datetime('Datetime Field'), 'function_field': fields.function( @@ -299,7 +299,8 @@ def test_orm_operation(self): expected_content = { 'name': unique_ident, 'date_field': '2024-01-01', 'boolean_field': True, 'integer_field_with_index': 8, - 'other_name': unique_ident, 'id': mmt_id, 'function_field': 'test', 'function_field_multi': 'test' + 'other_name': unique_ident, 'id': mmt_id, 'function_field': 'test', + 'function_field_multi': 'test' } self.assertEqual(all_content, expected_content) @@ -331,8 +332,12 @@ def test_binary(self): 'name': unique_ident, 'other_name': 'Bar', 'boolean_field': True, - 'integer_field_with_index': 8 + 'integer_field_with_index': 8, + 'file_example': b64encode(fb) }) + res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] + self.assertEqual(b64decode(res_file), fb) + mmt_obj.write(cursor, uid, [mmt_id], {'file_example': b64encode(fb)}) res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] self.assertEqual(b64decode(res_file), fb) @@ -348,9 +353,6 @@ def test_gridfs(self): ) mmt_obj = self.openerp.pool.get(NoMongoModelTestWithGridFs._name) mmt_obj._auto_init(cursor) - mmt_id = mmt_obj.create(cursor, uid, { - 'name': 'test' - }) image_path = get_module_resource( 'mongodb_backend', 'tests', 'fixtures', '15796004.png' @@ -359,6 +361,11 @@ def test_gridfs(self): with open(image_path, 'rb') as image_fd: fb = image_fd.read() + mmt_id = mmt_obj.create(cursor, uid, { + 'name': 'test', + 'file_example': b64encode(fb) + }) + mmt_obj.write(cursor, uid, [mmt_id], {'file_example': b64encode(fb)}) res_file = mmt_obj.read(cursor, uid, mmt_id, ['file_example'])['file_example'] self.assertEqual(b64decode(res_file), fb) From 4963fdce9d79bab244f244226aae58dcf2e53fa2 Mon Sep 17 00:00:00 2001 From: PSala Date: Tue, 8 Oct 2024 16:38:53 +0200 Subject: [PATCH 14/16] Update requirements --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 80d6e44..653b182 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ pymongo==3.13.0;python_version<="2.7.18" pymongo>=3.13.0;python_version>"2.7.18" six +pandas==0.23.4;python_version<="2.7.18" +pandas;python_version>"2.7.18" +openpyxl From 8d2a9cc90ccae7c8257001a472815c1b0889d9c5 Mon Sep 17 00:00:00 2001 From: PSala Date: Mon, 18 Nov 2024 11:30:07 +0100 Subject: [PATCH 15/16] Update workflows --- .github/workflows/mongo3.yml | 1 + .github/workflows/mongo5.yml | 1 + .github/workflows/mongo8.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/mongo3.yml b/.github/workflows/mongo3.yml index e3ccb80..337632b 100644 --- a/.github/workflows/mongo3.yml +++ b/.github/workflows/mongo3.yml @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install -r src/erp/server/bin/addons/base/requirements.txt + pip install -r src/oorq/requirements.txt pip install pymongo==3.13.0 pip install destral diff --git a/.github/workflows/mongo5.yml b/.github/workflows/mongo5.yml index 63e1079..5950cd9 100644 --- a/.github/workflows/mongo5.yml +++ b/.github/workflows/mongo5.yml @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install -r src/erp/server/bin/addons/base/requirements.txt + pip install -r src/oorq/requirements.txt pip install pymongo==3.13.0 pip install destral diff --git a/.github/workflows/mongo8.yml b/.github/workflows/mongo8.yml index 469a952..ef0ffa7 100644 --- a/.github/workflows/mongo8.yml +++ b/.github/workflows/mongo8.yml @@ -83,6 +83,7 @@ jobs: pip install -r src/erp/requirements.txt pip install -r src/erp/requirements-dev.txt pip install -r src/erp/server/bin/addons/base/requirements.txt + pip install -r src/oorq/requirements.txt pip install pymongo==3.13.0 pip install destral From 654866c5c6250cf1808dc4cc842d4c35d5b7c657 Mon Sep 17 00:00:00 2001 From: Eduard Carreras Date: Wed, 11 Dec 2024 17:41:54 +0100 Subject: [PATCH 16/16] Bump to v0.14.0 --- __terp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__terp__.py b/__terp__.py index 7e4f4df..ddf0206 100644 --- a/__terp__.py +++ b/__terp__.py @@ -5,7 +5,7 @@ all the data in a NoSQL way. You can interact with this data the same way as it was stored in PostgreSQL from the client. """, - "version": "0.13.0", + "version": "0.14.0", "author": "Joan M. Grande", "license" : "GPL-3", "category": "Generic Modules",