From 5a1e53d5f907013758c3fa69531c6eba8bbb9ab8 Mon Sep 17 00:00:00 2001 From: David Beal Date: Sat, 26 Oct 2024 19:33:36 +0200 Subject: [PATCH] [ADD] polars_process: process polars dataframe from file --- polars_process/README.rst | 115 +++++ polars_process/__init__.py | 2 + polars_process/__manifest__.py | 34 ++ polars_process/data/action.xml | 10 + polars_process/data/demo.xml | 44 ++ polars_process/models/__init__.py | 4 + polars_process/models/dataframe.py | 39 ++ polars_process/models/df_field.py | 32 ++ polars_process/models/df_source.py | 100 ++++ polars_process/models/ir_model_fields.py | 14 + polars_process/pyproject.toml | 3 + polars_process/readme/DESCRIPTION.md | 26 + polars_process/security/ir.model.access.xml | 41 ++ polars_process/static/description/icon.png | Bin 0 -> 2605 bytes polars_process/static/description/index.html | 453 ++++++++++++++++++ polars_process/tests/__init__.py | 1 + polars_process/tests/files/4_fields.xlsx | Bin 0 -> 5432 bytes .../tests/files/missing_required_column.xlsx | Bin 0 -> 5340 bytes polars_process/tests/files/wrong_date.xlsx | Bin 0 -> 5415 bytes polars_process/tests/test_module.py | 45 ++ polars_process/views/dataframe.xml | 69 +++ polars_process/views/df_field.xml | 27 ++ polars_process/views/df_source.xml | 75 +++ polars_process/views/menu.xml | 33 ++ polars_process/wizards/__init__.py | 1 + polars_process/wizards/df_process.py | 116 +++++ polars_process/wizards/df_process.xml | 52 ++ requirements.txt | 3 + 28 files changed, 1339 insertions(+) create mode 100644 polars_process/README.rst create mode 100644 polars_process/__init__.py create mode 100644 polars_process/__manifest__.py create mode 100644 polars_process/data/action.xml create mode 100644 polars_process/data/demo.xml create mode 100644 polars_process/models/__init__.py create mode 100644 polars_process/models/dataframe.py create mode 100644 polars_process/models/df_field.py create mode 100644 polars_process/models/df_source.py create mode 100644 polars_process/models/ir_model_fields.py create mode 100644 polars_process/pyproject.toml create mode 100644 polars_process/readme/DESCRIPTION.md create mode 100644 polars_process/security/ir.model.access.xml create mode 100644 polars_process/static/description/icon.png create mode 100644 polars_process/static/description/index.html create mode 100644 polars_process/tests/__init__.py create mode 100644 polars_process/tests/files/4_fields.xlsx create mode 100644 polars_process/tests/files/missing_required_column.xlsx create mode 100644 polars_process/tests/files/wrong_date.xlsx create mode 100644 polars_process/tests/test_module.py create mode 100644 polars_process/views/dataframe.xml create mode 100644 polars_process/views/df_field.xml create mode 100644 polars_process/views/df_source.xml create mode 100644 polars_process/views/menu.xml create mode 100644 polars_process/wizards/__init__.py create mode 100644 polars_process/wizards/df_process.py create mode 100644 polars_process/wizards/df_process.xml create mode 100644 requirements.txt diff --git a/polars_process/README.rst b/polars_process/README.rst new file mode 100644 index 0000000000..45e7cd0438 --- /dev/null +++ b/polars_process/README.rst @@ -0,0 +1,115 @@ +============== +Polars Process +============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3fb8a401fe8c3d73e23b477915bbd9ff0b2bcb331711726a4fd5be280ad53d5d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github + :target: https://github.com/OCA/reporting-engine/tree/18.0/polars_process + :alt: OCA/reporting-engine +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-polars_process + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +From an imported spreadsheet (xlsx) or db query, this module allows to +transform data in Polars dataframe and process them according to rules +in order to: + +- filter data and display +- obtain another dataframe with only the expected data to use in Odoo + +A such dataframe can help to prepare data in order to be used to +create/update or import + +Typical use case: + +You receive files from your vendors and these files have many difference +(column names, number of columns, dirty paging) but contains data +related to same concepts. Then you want apply them a common process to +automate things. For that you need to transform/arrange data to the same +way + +Why dataframe ? + +- a dataframe is a kind of in-memory dataset on which you can operate +- you can operates on your entire dataset a bit like with a database + but in memory: you don't need to iterate on each line to perform + operations +- the operations are powerful: filter, add column resulting from + calculation, select a subset of data + +Why Polars ? + +- performance: code in rust +- environment consideration +- dynamic project + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-bealdav| image:: https://github.com/bealdav.png?size=40px + :target: https://github.com/bealdav + :alt: bealdav + +Current `maintainer `__: + +|maintainer-bealdav| + +This module is part of the `OCA/reporting-engine `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/polars_process/__init__.py b/polars_process/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/polars_process/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/polars_process/__manifest__.py b/polars_process/__manifest__.py new file mode 100644 index 0000000000..bbdd05fe25 --- /dev/null +++ b/polars_process/__manifest__.py @@ -0,0 +1,34 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Polars Process", + "version": "18.0.1.0.0", + "summary": "Allow to create a Polars dataframe from file or db query and " + "process it according to rules", + "category": "Reporting", + "license": "AGPL-3", + "author": "Akretion, Odoo Community Association (OCA)", + "development_status": "Alpha", + "website": "https://github.com/OCA/reporting-engine", + "maintainers": ["bealdav"], + "depends": [ + "contacts", + ], + "external_dependencies": { + "python": [ + "polars", + "fastexcel", + ] + }, + "data": [ + "data/action.xml", + "data/demo.xml", + "security/ir.model.access.xml", + "wizards/df_process.xml", + "views/dataframe.xml", + "views/df_field.xml", + "views/df_source.xml", + "views/menu.xml", + ], + "installable": True, +} diff --git a/polars_process/data/action.xml b/polars_process/data/action.xml new file mode 100644 index 0000000000..dede8792e1 --- /dev/null +++ b/polars_process/data/action.xml @@ -0,0 +1,10 @@ + + + + 🐻‍❄️ Populate file polars example + + + code + env["df.source"]._populate() + + diff --git a/polars_process/data/demo.xml b/polars_process/data/demo.xml new file mode 100644 index 0000000000..94411d3d8a --- /dev/null +++ b/polars_process/data/demo.xml @@ -0,0 +1,44 @@ + + + + stop + import_preprocess + + + + + + + Country + + + + + Name + + + + + + Street + + + + + + 2nd Street + street2 + + + + + Date + + + + + + Colour + Color + + diff --git a/polars_process/models/__init__.py b/polars_process/models/__init__.py new file mode 100644 index 0000000000..96b8121bf9 --- /dev/null +++ b/polars_process/models/__init__.py @@ -0,0 +1,4 @@ +from . import dataframe +from . import df_field +from . import df_source +from . import ir_model_fields diff --git a/polars_process/models/dataframe.py b/polars_process/models/dataframe.py new file mode 100644 index 0000000000..983291d882 --- /dev/null +++ b/polars_process/models/dataframe.py @@ -0,0 +1,39 @@ +from odoo import fields, models + + +class Dataframe(models.Model): + _name = "dataframe" + _inherit = "mail.thread" + _description = "File Configuration" + _rec_name = "code" + + model_id = fields.Many2one( + comodel_name="ir.model", + required=True, + copy=False, + ondelete="cascade", + tracking=True, + ) + code = fields.Char(help="Allow to browse between several identical models") + rename = fields.Boolean(help="Rename dataframe fields") + action = fields.Selection( + selection=[ + ("display", "Display"), + ("dataframe", "Dataframe"), + ], + default="display", + tracking=True, + help="Some other behaviors can be implemented", + ) + on_fail = fields.Selection( + selection=[("stop", "Stop"), ("skip", "Skip record (TODO)")], + default="stop", + tracking=True, + help="What should be the behavior in case of failure regarding constraint " + "fields (required, format, etc)\n\n" + " - Stop: stop the process by raising an exception\n" + " - Skip record: current line'll be ignored from the next process", + ) + field_ids = fields.One2many( + comodel_name="df.field", inverse_name="dataframe_id", copy=True + ) diff --git a/polars_process/models/df_field.py b/polars_process/models/df_field.py new file mode 100644 index 0000000000..45f41e3855 --- /dev/null +++ b/polars_process/models/df_field.py @@ -0,0 +1,32 @@ +from odoo import fields, models + + +class FileField(models.Model): + _name = "df.field" + _inherit = ["mail.thread"] + _description = "Configuration de l'import de champ" + _order = "field_id ASC" + + dataframe_id = fields.Many2one( + comodel_name="dataframe", required=True, ondelete="cascade" + ) + sequence = fields.Integer() + field_id = fields.Many2one( + comodel_name="ir.model.fields", + ondelete="cascade", + required=True, + domain="[('model_id', '=', model_id)]", + ) + model_id = fields.Many2one( + comodel_name="ir.model", + related="dataframe_id.model_id", + readonly=True, + ) + name = fields.Char(help="Name field in the source file (spreadsheet)") + renamed = fields.Char(help="If specified, renamed in dataframe") + required = fields.Boolean( + help="Prevent to import missing data if field is missing in some records", + ) + check_type = fields.Boolean( + help="Check data type is compatible", + ) diff --git a/polars_process/models/df_source.py b/polars_process/models/df_source.py new file mode 100644 index 0000000000..7c441b1874 --- /dev/null +++ b/polars_process/models/df_source.py @@ -0,0 +1,100 @@ +import base64 +from pathlib import Path + +from odoo import _, fields, models +from odoo.modules.module import get_module_path + + +class DfSource(models.Model): + _name = "df.source" + _description = "Dataframe data source" + + dataframe_id = fields.Many2one( + comodel_name="dataframe", required=True, ondelete="cascade" + ) + name = fields.Char() + sequence = fields.Integer() + rename = fields.Boolean(help="Display renamed Dataframe in wizard") + template = fields.Binary(string="File", attachment=False) + readonly = fields.Boolean(help="Imported records from module are readonly created") + + def _populate(self): + def create_attach(myfile, addon, idstring, relative_path): + with open(myfile, "rb") as f: + name = f.name[f.name.find(addon) :] + vals = { + "dataframe_id": self.env.ref(idstring).id, + "name": name, + "readonly": True, + "rename": True, + } + if ".sql" in name: + vals["query"] = self._get_file(name) + self.env[self._name].sudo().create(vals) + + self.env[self._name].search([("template", "=", False)]).unlink() + paths = self._get_test_file_paths() + for addon, data in paths.items(): + relative_path = data["relative_path"] + idstring = data["xmlid"] + if self.env.ref(idstring): + mpath = Path(get_module_path(addon)) / relative_path + for mfile in tuple(mpath.iterdir()): + create_attach(mfile, addon, idstring, relative_path) + action = self.env.ref("polars_process.df_source_action")._get_action_dict() + return action + + def start(self): + self.ensure_one() + vals = { + "filename": self.name, + "df_source_id": self.id, + "dataframe_id": self.dataframe_id.id, + "file": base64.b64encode(self._get_file()), + } + transient = self.env["df.process.wiz"].create(vals) + action = self.env.ref("polars_process.df_process_wiz_action")._get_action_dict() + action["res_id"] = transient.id + return action + + def _get_file(self, name=None): + # TODO Clean + if self.template: + return self.template + name = self.name or name + module = name[: name.find("/")] + relative = self._get_test_file_paths().get(module) + relative = relative and relative.get("relative_path") + if relative: + path = Path(get_module_path(module)) + path = path / relative / name[name.rfind("/") + 1 :] + with open(path, "rb") as f: + return f.read() + + def _get_test_file_paths(self): + """ + You may override if you want populate files in your module + returns: + {"module_name": { + "relative_path": "tests/files", + "xmlid": "dataframe_xml_id"} + } + } + """ + return { + "polars_process": { + "relative_path": "tests/files", + "xmlid": "polars_process.dataframe_contact", + } + } + + def ui_form(self): + self.ensure_one() + return { + "name": _("Dataframe source"), + "res_model": self._name, + "view_mode": "form", + "res_id": self.id, + "type": "ir.actions.act_window", + "target": "current", + } diff --git a/polars_process/models/ir_model_fields.py b/polars_process/models/ir_model_fields.py new file mode 100644 index 0000000000..14d2039c81 --- /dev/null +++ b/polars_process/models/ir_model_fields.py @@ -0,0 +1,14 @@ +from odoo import api, models + + +class IrModelFields(models.Model): + _inherit = "ir.model.fields" + + @api.depends("field_description", "model") + def _compute_display_name(self): + super()._compute_display_name() + if self.env.context.get("technical_name"): + for field in self: + if self.env.context.get("technical_name"): + field.display_name = field.name + return diff --git a/polars_process/pyproject.toml b/polars_process/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/polars_process/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/polars_process/readme/DESCRIPTION.md b/polars_process/readme/DESCRIPTION.md new file mode 100644 index 0000000000..9d5d69103f --- /dev/null +++ b/polars_process/readme/DESCRIPTION.md @@ -0,0 +1,26 @@ +From an imported spreadsheet (xlsx) or db query, this module allows to transform data in Polars dataframe and process them according to rules in order to: + +- filter data and display +- obtain another dataframe with only the expected data to use in Odoo + +A such dataframe can help to prepare data in order to be used to create/update or import + +Typical use case: + +You receive files from your vendors and these files have many difference (column names, number of columns, dirty paging) but contains data related to same concepts. +Then you want apply them a common process to automate things. +For that you need to transform/arrange data to the same way + + +Why dataframe ? + +- a dataframe is a kind of in-memory dataset on which you can operate +- you can operates on your entire dataset a bit like with a database but in memory: you don't need to iterate on each line to perform operations +- the operations are powerful: filter, add column resulting from calculation, select a subset of data + + +Why Polars ? + +- performance: code in rust +- environment consideration +- dynamic project diff --git a/polars_process/security/ir.model.access.xml b/polars_process/security/ir.model.access.xml new file mode 100644 index 0000000000..7bb63a8d88 --- /dev/null +++ b/polars_process/security/ir.model.access.xml @@ -0,0 +1,41 @@ + + + Dataframe + + + + + + + + + + Df Field + + + + + + + + + + df.process.wiz + + + + + + + + + + df.source + + + + + + + + diff --git a/polars_process/static/description/icon.png b/polars_process/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d1d2b572970f1b1180a2fef75dc439bffb034de6 GIT binary patch literal 2605 zcmV+|3exq7P)^6 zrc5)2DbtK$$}~ea1ozl_)bf6v8Nl{M&h_PP``crr>kS+4>^^UL?T7`y`ZD+SN@ug1 z`o=|DGWf~4+z>oia!40tJI|P%esa1!ST)Y)2@=vp4@dYzgghq1!^`z{0eEtc{gzV) z;Oq-c0A4<1^>bJ?#j|>fr^82HYiA6!Fk@t}(?`B^(CP^iON)KgW$rgmWWQUR3E=Nf zIcALtr0I=hq=mAZowQ^-c_#kcxTeH^=#G%<@{>0@=p*$k0D{2kDW2-T=ALY10o1H< zcsb&W+Zg~{eyY06eWi_-?>lQ$pzQtpCbw>)l{@}?OKRUsLdqyR(ZJR=>PZ4?XSfA1 ze39b;V6rW|$|eqCx%F^ix7+0>XllS=E@EEm?`sIJJ z1K2pb>y^1(iBFO|Wt=1itUG|Cn>t?;pz}NQsBQq~MSflYNubomseF1? zVHZXwj*Y0`C$qZaj^PEoeKPBvQ&|AsSm@d?v-?Ur4WP|SwRxjI$<=lmKu?go(?tyt zWgBL8ZDxs? z8)eJ?Xs>P31L$wqFCMh+xnu;ee^sm1&?ihIi0DQ_PZo(Y`p5c>Zk;zo0J!K#bhDH0 zJju$PZqeHeyv>lj63|9jOT@)=`l16Txy#`pqbL)_K8@Ks^TQ)IH>lieQK9ekd9FZ+ zcxYS-_E6~&K3k&}KPHHwcK=GjBxg8c!a$2JJrLVr$v-0q5kRSplT`ZnoH){A+wtr( z&5X?`eDG+y%CIK4Zu6lWKZmELdRCTtqI%Op*+eV1xuOezeGp3cf`eK1i9Ns*KP+=^ zt8^;6`FobG+`|K6WIs6m3nM7>iN;0XYi;>myu4(8CU42BZd4nk}v4sDbv9eEZj%qj3ovOH>- z%TLb!FyHAX=a2F4UD=wF2UWo@zsjt+X#kLC;;T~O_)K)zjiF#f{_uY**PhQ9|-R)=@Z<}Dd-hdN{#u*c;Eh2tpKE0f9G7oy`5fi_UJgpi_dRmJiFfpV0)!=eI#=m6YI;}>&xW{MLd`k9KG{!=WQn~ zd5vZ_wQyG+fblupzkiULGS8mN#tRMx!1=pOl!P)dN!m+t7(~Ljuazkwi(ywkUG*)% zAbeS0E8FyW&f)qD06UkrMyV_1iC9=(ONX3Xy1XfwB zn7Aw-VrcVaPw%tc?4$u4e)ewEj@g^TQnX1@8X%7Z`X;j1&ghwx%LyV;o*w|PcSWmo zSZb6xR7WuKgaAk)=KRT550Zwqg&B-^q97l=WA^5-6m3C}2FN1;pwJR3wDczal?ox> z%*$sLdVeKNrT9}Z|B#;WkPw5i4F{}ud#Ef{-1S)7cuP23m@*=UrvLv$49fOhHA;<6 zAGxT|KTw8#R=!$_aquX#g!Ggc;0gI0(S3qA3$w08zx~TBAbgZi*_Fi#$|QtRV=8j< zK(gn{rrCoH8}Gz^=!C1~=|CLH&a^Pnp&h$PSUtt_v-z&;9WXAT5;ToB3mFTjYEh7co9g=o1Eq!?Z@dAQEMH z0an#9ZC&8}R|9LH#d%}=CmL9-)$$ZV8)Z9cGmq7?*{ty1k`DDlD3fzJAcnIS#Xjv_ z=S*AvnFCE%nsro?b<9#*u&T)C2@*?+eTY=m0O&|j`CwFfHiI~nEuG+% zlF^k#egJP)cAjq5XJ-i0MGcilxfB=WtuTenP&VK(M1O$P%M+& P00000NkvXXu0mjf2J`yU literal 0 HcmV?d00001 diff --git a/polars_process/static/description/index.html b/polars_process/static/description/index.html new file mode 100644 index 0000000000..0113640e53 --- /dev/null +++ b/polars_process/static/description/index.html @@ -0,0 +1,453 @@ + + + + + +Polars Process + + + +
+

Polars Process

+ + +

Alpha License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

+

From an imported spreadsheet (xlsx) or db query, this module allows to +transform data in Polars dataframe and process them according to rules +in order to:

+
    +
  • filter data and display
  • +
  • obtain another dataframe with only the expected data to use in Odoo
  • +
+

A such dataframe can help to prepare data in order to be used to +create/update or import

+

Typical use case:

+

You receive files from your vendors and these files have many difference +(column names, number of columns, dirty paging) but contains data +related to same concepts. Then you want apply them a common process to +automate things. For that you need to transform/arrange data to the same +way

+

Why dataframe ?

+
    +
  • a dataframe is a kind of in-memory dataset on which you can operate
  • +
  • you can operates on your entire dataset a bit like with a database +but in memory: you don’t need to iterate on each line to perform +operations
  • +
  • the operations are powerful: filter, add column resulting from +calculation, select a subset of data
  • +
+

Why Polars ?

+
    +
  • performance: code in rust
  • +
  • environment consideration
  • +
  • dynamic project
  • +
+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

bealdav

+

This module is part of the OCA/reporting-engine project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/polars_process/tests/__init__.py b/polars_process/tests/__init__.py new file mode 100644 index 0000000000..d9b96c4fa5 --- /dev/null +++ b/polars_process/tests/__init__.py @@ -0,0 +1 @@ +from . import test_module diff --git a/polars_process/tests/files/4_fields.xlsx b/polars_process/tests/files/4_fields.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..815bedfc2aba0d8a63554938426a0d7dd061f396 GIT binary patch literal 5432 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 literal 0 HcmV?d00001 diff --git a/polars_process/tests/files/missing_required_column.xlsx b/polars_process/tests/files/missing_required_column.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c6f9f226f49d09d86e0380b1b372de298abac1f2 GIT binary patch literal 5340 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 literal 0 HcmV?d00001 diff --git a/polars_process/tests/files/wrong_date.xlsx b/polars_process/tests/files/wrong_date.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..52e5c8213610202a04a622234479ab8f91800977 GIT binary patch literal 5415 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 PclGkQY9g;7=wJT<4Lt-6 literal 0 HcmV?d00001 diff --git a/polars_process/tests/test_module.py b/polars_process/tests/test_module.py new file mode 100644 index 0000000000..e7bafcad7e --- /dev/null +++ b/polars_process/tests/test_module.py @@ -0,0 +1,45 @@ +from lxml import etree + +from odoo.tests.common import TransactionCase + + +class TestModule(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env["df.source"]._populate() + cls.file_records = cls.env["df.source"].search([]) + + def test_missing_required_column(self): + wiz = self.get_wizard(self.file_records, "missing_required_column") + comment = sanitize(str(wiz.comment)) + root = etree.fromstring(comment) + self.assertEqual( + root.xpath('//div[@id="missing-columns-data"]')[0].text, + "['Street']", + ) + + def test_four_fields(self): + wiz = self.get_wizard(self.file_records, "4_fields") + comment = sanitize(str(wiz.comment)) + root = etree.fromstring(comment) + self.assertEqual( + len(root.xpath('//div[@id="missing-values-in-name"]')), + 1, + "Missing value in Name column", + ) + self.assertEqual( + len(root.xpath('//div[@id="missing-values-in-street"]')), + 1, + "Missing values in Street column", + ) + + def get_wizard(self, source_recs, file_str): + source = source_recs.filtered(lambda s, file_str=file_str: file_str in s.name) + action = source.start() + return self.env["df.process.wiz"].browse(action.get("res_id")) + + +def sanitize(string): + string = string.replace("
", "
") + return string diff --git a/polars_process/views/dataframe.xml b/polars_process/views/dataframe.xml new file mode 100644 index 0000000000..90f0b9e8b0 --- /dev/null +++ b/polars_process/views/dataframe.xml @@ -0,0 +1,69 @@ + + + dataframe + +
+ +
+ + + + + + + + + + + + + + + + + + + + + dataframe + + + + + + + + + + + dataframe + + + + + + + + + + + + + + + Dataframe + dataframe + list,form + dataframe + + diff --git a/polars_process/views/df_field.xml b/polars_process/views/df_field.xml new file mode 100644 index 0000000000..d5161e2e98 --- /dev/null +++ b/polars_process/views/df_field.xml @@ -0,0 +1,27 @@ + + + df.field + + + + + + + + + + + + + + + Df Field + df.field + list + df-field + + diff --git a/polars_process/views/df_source.xml b/polars_process/views/df_source.xml new file mode 100644 index 0000000000..6eab51c5a3 --- /dev/null +++ b/polars_process/views/df_source.xml @@ -0,0 +1,75 @@ + + + df.source + +
+ +
+ + + + + + + + + + + + + + + + + df.source + + + + +