Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡ Sync Order #479

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions partner_telegram/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ def _compute_telegram(self):
def _inverse_telegram(self):
for record in self:
value = record.telegram
if value.startswith("@"):
value = value[1:]
elif value.startswith("+"):
value = value.replace("-", "").replace(" ", "")
if not value:
record.telegram_username = False
record.telegram_mobile = False
elif value.startswith("@"):
record.telegram_username = value[1:]
elif value.startswith("https://t.me/"):
value = value[len("https://t.me/") :]
record.telegram_mobile = value
record.telegram_username = value[len("https://t.me/") :]
elif value.startswith("+"):
record.telegram_mobile = value.replace("-", "").replace(" ", "")
2 changes: 2 additions & 0 deletions partner_telegram/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# License MIT (https://opensource.org/licenses/MIT).
from . import test_telegram
49 changes: 49 additions & 0 deletions partner_telegram/tests/test_telegram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2024 Ivan Yelizariev <https://twitter.com/yelizariev>
# License MIT (https://opensource.org/licenses/MIT).
from odoo.tests.common import TransactionCase


class TestResPartnerTelegram(TransactionCase):
def setUp(self):
super(TestResPartnerTelegram, self).setUp()
self.ResPartner = self.env["res.partner"]
# Create a partner without telegram information
self.partner = self.ResPartner.create({"name": "Test"})

def test_telegram_set_username(self):
# Set the telegram field with a username
self.partner.telegram = "@testuser"

# Check the computed fields
self.assertEqual(self.partner.telegram_username, "testuser")
self.assertEqual(self.partner.telegram_url, "https://t.me/testuser")
self.assertEqual(self.partner.telegram, "testuser")

def test_telegram_set_mobile(self):
# Set the telegram field with a mobile number
self.partner.telegram = "+1234567890"

# Check the computed fields
self.assertEqual(self.partner.telegram_mobile, "+1234567890")
self.assertEqual(self.partner.telegram_url, "https://t.me/+1234567890")
self.assertEqual(self.partner.telegram, "+1234567890")

def test_telegram_set_url(self):
# Set the telegram field with a URL
self.partner.telegram = "https://t.me/testuser"

# Check the computed fields
self.assertEqual(self.partner.telegram_username, "testuser")
self.assertEqual(self.partner.telegram_url, "https://t.me/testuser")
self.assertEqual(self.partner.telegram, "testuser")

def test_telegram_clear(self):
# Set the telegram field and then clear it
self.partner.telegram = "@testuser"
self.partner.telegram = ""

# Check the computed fields
self.assertFalse(self.partner.telegram_username)
self.assertFalse(self.partner.telegram_mobile)
self.assertFalse(self.partner.telegram_url)
self.assertFalse(self.partner.telegram)
2 changes: 1 addition & 1 deletion sync/README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. image:: https://itpp.dev/images/infinity-readme.png
:alt: Tested and maintained by IT Projects Labs
:target: https://itpp.dev
:target: https://odoomagic.com

.. image:: https://img.shields.io/badge/license-MIT-blue.svg
:target: https://opensource.org/licenses/MIT
Expand Down
14 changes: 6 additions & 8 deletions sync/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
"name": "Sync 🪬 Studio",
"summary": """Join the Amazing 😍 Community ⤵️""",
"category": "VooDoo ✨ Magic",
"version": "16.0.11.0.1",
"version": "16.0.13.0.0",
"application": True,
"author": "Ivan Kropotkin",
"support": "[email protected]",
"website": "https://sync_studio.t.me/",
"license": "Other OSI approved licence", # MIT
"depends": ["base_automation", "mail", "queue_job"],
# The `partner_telegram` dependency is not directly needed,
# but it plays an important role in the **Sync 🪬 Studio** ecosystem
# and is added for the quick onboarding of new **Cyber ✨ Pirates**.
"depends": ["base_automation", "mail", "queue_job", "partner_telegram"],
"external_dependencies": {"python": ["markdown", "pyyaml"], "bin": []},
"data": [
"security/sync_groups.xml",
Expand All @@ -25,6 +28,7 @@
"views/sync_trigger_automation_views.xml",
"views/sync_trigger_webhook_views.xml",
"views/sync_trigger_button_views.xml",
"views/sync_order_views.xml",
"views/sync_task_views.xml",
"views/sync_link_views.xml",
"views/sync_project_views.xml",
Expand All @@ -37,12 +41,6 @@
},
"demo": [
"data/sync_project_unittest_demo.xml",
# Obsolete
# "data/sync_project_context_demo.xml",
# "data/sync_project_telegram_demo.xml",
# "data/sync_project_odoo2odoo_demo.xml",
# "data/sync_project_trello_github_demo.xml",
# "data/sync_project_context_demo.xml",
],
"qweb": [],
"post_load": None,
Expand Down
3 changes: 3 additions & 0 deletions sync/doc/MAGIC.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Libs
* ``MAGIC.timezone``
* ``MAGIC.b64encode``
* ``MAGIC.b64decode``
* ``MAGIC.sha256``

Tools
=====
Expand All @@ -80,6 +81,8 @@ Tools
* ``MAGIC.type2str``: get type of the given object
* ``MAGIC.DEFAULT_SERVER_DATETIME_FORMAT``
* ``MAGIC.AttrDict``: Extended dictionary that allows for attribute-style access
* ``MAGIC.group_by_lang(partners, default_lang="en_US")``: yields `lang, partners` grouped by lang
* ``MAGIC.gen2csv(generator)``: prepares csv as a string

Exceptions
==========
Expand Down
14 changes: 14 additions & 0 deletions sync/doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
`13.0.0`
-------

- **Fix:** Use `__sync.` for xmlid namespace to avoid data loss on module update.
- **Fix:** Use task ID in xmlid namespace for the task triggers.
- **Fix:** Keep job records (and their logs) on task deletion.
- **New:** Add *Sync Order* — advanced manual trigger with blackjack, partners list, text input, etc.
- **New:** Support `data.markdown` for custom documentation in the `DATA.🐫` tab.
- **New:** Add `MAGIC.group_by_lang` to eval context.
- **New:** Add dynamic Setting update via `PARAMS._update_param`.
- **New:** Add computed field `text` to the model `sync.data`. Usage example in dynamic code: `DATA.restaurant.text`.
- **Improvement:** Add `DATA.*` to the library eval context.
- **Improvement:** Update API for attaching dynamic values: `_set_sync_value`, `_get_sync_value`. No need to use `ir.property`.

`11.0.1`
-------

Expand Down
2 changes: 1 addition & 1 deletion sync/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from . import sync_task
from . import sync_job
from . import sync_data
from . import sync_order
from . import ir_logging
from . import ir_actions
from . import ir_attachment
from . import ir_fields
from . import sync_link
from . import base
101 changes: 26 additions & 75 deletions sync/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def search_links(self, relation_name, refs=None):
._search_links_odoo(self, relation_name, refs)
)

def _create_or_update_by_xmlid(self, vals, code, namespace="XXX", module="sync"):
def _create_or_update_by_xmlid(self, vals, code, namespace="XXX", module="__sync"):
"""
Create or update a record by a dynamically generated XML ID.
Warning! The field `noupdate` is ignored, i.e. existing records are always updated.
Expand Down Expand Up @@ -66,34 +66,35 @@ def _create_or_update_by_xmlid(self, vals, code, namespace="XXX", module="sync")
"noupdate": False,
}
)

return record

def _set_sync_property(self, property_name, property_type, property_value):
"""
Set or create a property for the current record. If the property field
does not exist, create it dynamically.

Args:
property_name (str): Name of the property field to set.
property_value (Any): The value to assign to the property.
property_type (str): Type of the property field.
"""
Property = self.env["ir.property"]
def _sync_field_name(self, property_name, property_type):
sync_project_id = self.env.context.get("sync_project_id")

if not sync_project_id:
raise exceptions.UserError(
_("The 'sync_project_id' must be provided in the context.")
)

field_name = "x_sync_%s_%s_%s" % (sync_project_id, property_name, property_type)
return "x_sync_%s_%s_%s" % (sync_project_id, property_name, property_type)

def _set_sync_value(self, property_name, property_type, property_value):
"""
Set or create a property for the current record. If the field
does not exist, create it dynamically.

Args:
property_name (str): Name of the property field to set.
property_type (str): Type of the property field.
property_value (Any): The value to assign to the property.
"""
self.ensure_one()
field_name = self._sync_field_name(property_name, property_type)
field = self.env["ir.model.fields"].search(
[
("name", "=", field_name),
("model", "=", self._name),
("ttype", "=", property_type),
("sync_project_id", "=", sync_project_id),
],
limit=1,
)
Expand All @@ -104,73 +105,23 @@ def _set_sync_property(self, property_name, property_type, property_value):
{
"name": field_name,
"ttype": property_type,
"model_id": self.env["ir.model"]
.search([("model", "=", self._name)], limit=1)
.id,
"model_id": self.env["ir.model"]._get_id(self._name),
"field_description": property_name.capitalize().replace("_", " "),
"sync_project_id": sync_project_id, # Link to the sync project
}
)
self[field_name] = property_value

res_id = f"{self._name},{self.id}"
prop = Property.search(
[
("name", "=", property_name),
("res_id", "=", res_id),
("fields_id", "=", field.id),
],
limit=1,
)

vals = {"type": property_type, "value": property_value}
if prop:
prop.write(vals)
else:
vals.update(
{
"name": property_name,
"fields_id": field.id,
"res_id": res_id,
}
)
Property.create(vals)

def _get_sync_property(self, property_name, property_type):
def _get_sync_value(self, property_name, property_type):
"""
Get the value of a property for the current record.
Get the value of a dynamic field for the current record.

Args:
property_name (str): Name of the property field to get.
property_type (str): Type of the property field.
"""
Property = self.env["ir.property"]
sync_project_id = self.env.context.get("sync_project_id")

if not sync_project_id:
raise exceptions.UserError(
_("The 'sync_project_id' must be provided in the context.")
)

field_name = "x_sync_%s_%s_%s" % (sync_project_id, property_name, property_type)
field = self.env["ir.model.fields"].search(
[
("name", "=", field_name),
("model", "=", self._name),
("sync_project_id", "=", sync_project_id),
],
limit=1,
)

if not field:
self.ensure_one()
field_name = self._sync_field_name(property_name, property_type)
try:
return self[field_name]
except KeyError:
return None

res_id = f"{self._name},{self.id}"
prop = Property.search(
[
("name", "=", property_name),
("res_id", "=", res_id),
("fields_id", "=", field.id),
],
limit=1,
)

return prop.get_by_record() if prop else None
12 changes: 0 additions & 12 deletions sync/models/ir_fields.py

This file was deleted.

24 changes: 14 additions & 10 deletions sync/models/sync_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import yaml

from odoo import fields, models
from odoo import api, fields, models


class SyncData(models.Model):
Expand All @@ -17,29 +17,33 @@ class SyncData(models.Model):
project_id = fields.Many2one("sync.project", ondelete="cascade")
file_name = fields.Char("File Name")
file_content = fields.Binary("File Content")
text = fields.Text("Decoded Text", compute="_compute_text")

@api.depends("file_content")
def _compute_text(self):
for record in self:
if record.file_content:
decoded_content = base64.b64decode(record.file_content)
record.text = decoded_content.decode("utf-8")
else:
record.text = False

def csv(self, *args, **kwargs):
"""Parse CSV file from binary field."""
if self.file_content:
file_content = base64.b64decode(self.file_content)
file_content = file_content.decode("utf-8")
file_like_object = StringIO(file_content)
file_like_object = StringIO(self.text)
reader = csv.DictReader(file_like_object, *args, **kwargs)
return [row for row in reader]
return []

def json(self):
"""Parse JSON file from binary field."""
if self.file_content:
file_content = base64.b64decode(self.file_content)
file_content = file_content.decode("utf-8")
return json.loads(file_content)
return json.loads(self.text)
return {}

def yaml(self):
"""Parse YAML file from binary field."""
if self.file_content:
file_content = base64.b64decode(self.file_content)
file_content = file_content.decode("utf-8")
return yaml.safe_load(file_content)
return yaml.safe_load(self.text)
return None
2 changes: 1 addition & 1 deletion sync/models/sync_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SyncJob(models.Model):
trigger_webhook_id = fields.Many2one("sync.trigger.webhook", readonly=True)
trigger_button_id = fields.Many2one("sync.trigger.button", readonly=True)
task_id = fields.Many2one(
"sync.task", compute="_compute_sync_task_id", store=True, ondelete="cascade"
"sync.task", compute="_compute_sync_task_id", store=True, ondelete="set null"
)
project_id = fields.Many2one(
"sync.project", related="task_id.project_id", readonly=True
Expand Down
Loading
Loading