Skip to content

Commit

Permalink
⚡ Sync Order
Browse files Browse the repository at this point in the history
  • Loading branch information
yelizariev committed May 23, 2024
1 parent f42baed commit 014b433
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 31 deletions.
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
2 changes: 2 additions & 0 deletions sync/doc/MAGIC.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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
9 changes: 9 additions & 0 deletions sync/doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
`13.0.0`
-------

- **Fix:** use `__sync.` for xmlid namespace to avoid data lose on module update
- **New:** add *Sync Order* — advanced manual trigger with blackjack, partners list, text input, etc.
- **New:** support `data.markdown` for custom documentation at the `DATA.🐫` tab
- **New:** add `MAGIC.group_by_lang` to eval context
- **Improvement:** add `DATA.*` to the library eval context

`11.0.1`
-------

Expand Down
1 change: 1 addition & 0 deletions sync/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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
Expand Down
2 changes: 1 addition & 1 deletion 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
47 changes: 47 additions & 0 deletions sync/models/sync_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2024 Ivan Yelizariev <https://twitter.com/yelizariev>
from odoo import api, fields, models


class SyncOrder(models.Model):
_name = "sync.order"
_description = "Sync Order"
_inherit = ["mail.thread", "mail.activity.mixin"]

name = fields.Char("Title")
body = fields.Char("Order")
sync_project_id = fields.Many2one("sync.project", ondelete="cascade")
sync_task_id = fields.Many2one(
"sync.task",
ondelete="cascade",
domain="[('project_id', '=', sync_project_id)]",
required=True,
)
description = fields.Html(related="sync_task_id.description")
record_id = fields.Reference(
string="Record",
selection="_selection_record_id",
help="Optional extra information to perform this task",
)

partner_ids = fields.Many2many("res.partner", string="Partners")
state = fields.Selection(
[
("draft", "Draft"),
("open", "In Progress"),
("done", "Done"),
("cancel", "Canceled"),
]
)

@api.model
def _selection_record_id(self):
mm = self.sync_task_id.sync_order_model_id
if not mm:
return []
return [(mm.model, mm.name)]

def action_confirm(self):
self.write({"state": "open"})

def action_cancel(self):
self.write({"state": "cancel"})
93 changes: 83 additions & 10 deletions sync/models/sync_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
# License MIT (https://opensource.org/licenses/MIT).

import base64
import csv
import io
import logging
import os
from datetime import datetime
from itertools import groupby
from operator import itemgetter

import urllib3
from pytz import timezone
Expand Down Expand Up @@ -103,13 +107,19 @@ class SyncProject(models.Model):
trigger_webhook_count = fields.Integer(
compute="_compute_triggers", help="Enabled Webhooks"
)
sync_order_model_id = fields.Many2one("ir.model")
sync_order_ids = fields.One2many(
"sync.order", "project_id", string="Sync Orders", copy=True
)
sync_order_count = fields.Integer(compute="_compute_sync_order_count")
job_ids = fields.One2many("sync.job", "project_id")
job_count = fields.Integer(compute="_compute_job_count")
log_ids = fields.One2many("ir.logging", "sync_project_id")
log_count = fields.Integer(compute="_compute_log_count")
link_ids = fields.One2many("sync.link", "project_id")
link_count = fields.Integer(compute="_compute_link_count")
data_ids = fields.One2many("sync.data", "project_id")
data_description = fields.Html(readonly=True)

def copy(self, default=None):
default = dict(default or {})
Expand All @@ -129,6 +139,11 @@ def _compute_task_count(self):
for r in self:
r.task_count = len(r.with_context(active_test=False).task_ids)

@api.depends("sync_order_ids")
def _compute_sync_order_count(self):
for r in self:
r.sync_order_count = len(r.sync_order_ids)

@api.depends("job_ids")
def _compute_job_count(self):
for r in self:
Expand Down Expand Up @@ -259,6 +274,43 @@ def record2image(record, fname="image_1920"):
)
)

def group_by_lang(partners, default_lang="en_US"):
"""
Yield groups of partners grouped by their language.
:param partners: recordset of res.partner
:return: generator yielding tuples of (lang, partners)
"""
if not partners:
return

# Sort the partners by 'lang' to ensure groupby works correctly
partners = partners.sorted(key=lambda p: p.lang)

# Group the partners by 'lang'
for lang, group in groupby(partners, key=itemgetter("lang")):
partner_group = partners.browse([partner.id for partner in group])
yield lang or default_lang, partner_group

def gen2csv(generator):
# Prepare a StringIO buffer to hold the CSV data
output = io.StringIO()

# Create a CSV writer with quoting enabled
writer = csv.writer(output, quoting=csv.QUOTE_ALL)

# Write rows from the generator
for row in generator:
writer.writerow(row)

# Get the CSV content
csv_content = output.getvalue()

# Close the StringIO buffer
output.close()

return csv_content

context = dict(self.env.context, log_function=log, sync_project_id=self.id)
env = self.env(context=context)
link_functions = env["sync.link"]._get_eval_context()
Expand Down Expand Up @@ -296,6 +348,8 @@ def record2image(record, fname="image_1920"):
"b64decode": base64.b64decode,
"type2str": type2str,
"record2image": record2image,
"gen2csv": gen2csv,
"group_by_lang": group_by_lang,
"DEFAULT_SERVER_DATETIME_FORMAT": DEFAULT_SERVER_DATETIME_FORMAT,
"AttrDict": AttrDict,
},
Expand Down Expand Up @@ -508,11 +562,15 @@ def magic_upgrade(self):
vals[field_name] = gist_files[file_name]

# [DATA]
file_content = gist_files.get("data.markdown")
if file_content:
vals["data_description"] = compile_markdown_to_html(file_content)

http = urllib3.PoolManager()
for file_info in gist_content["files"].values():
# e.g. "data.emoji.csv"
file_name = file_info["filename"]
if not file_name.startswith("data."):
if not (file_name.startswith("data.") and file_name != "data.markdown"):
continue
raw_url = file_info["raw_url"]
response = http.request("GET", raw_url)
Expand Down Expand Up @@ -574,6 +632,18 @@ def magic_upgrade(self):
else None,
"project_id": self.id,
}
# Sync Order Model
if meta.get("SYNC_ORDER_MODEL"):
model = self._get_model(meta.get("SYNC_ORDER_MODEL"))
task_vals["sync_order_model_id"] = model.id

# Parse docs
description = gist_files.get(
file_name[len("task.") : -len(".py")] + ".markdown"
)
if description:
task_vals["description"] = compile_markdown_to_html(description)

task = self.env["sync.task"]._create_or_update_by_xmlid(
task_vals, task_technical_name, namespace=self.id
)
Expand All @@ -596,20 +666,23 @@ def create_trigger(model, data):
create_trigger("sync.trigger.webhook", data)

for data in meta.get("DB_TRIGGERS", []):
model_id = self.env["ir.model"]._get(data["model"]).id
if not model_id:
raise ValidationError(
_(
"Model %s is not available. Check if you need to install an extra module first."
)
% data["model"]
)
model = self._get_model(data["model"])
create_trigger(
"sync.trigger.automation", dict(data, model_id=model_id, model=None)
"sync.trigger.automation", dict(data, model_id=model.id, model=None)
)

self.update(vals)

def _get_model(self, model_name):
model = self.env["ir.model"]._get(model_name)
if not model:
raise ValidationError(
_(
"Model %s is not available. Check if you need to install an extra module first."
)
% model_name
)


class SyncProjectParamMixin(models.AbstractModel):

Expand Down
4 changes: 4 additions & 0 deletions sync/models/sync_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class SyncTask(models.Model):

project_id = fields.Many2one("sync.project", ondelete="cascade")
name = fields.Char("Name", help="e.g. Sync Products", required=True)
description = fields.Html(readonly=True)
code = fields.Text("Code")
code_check = fields.Text("Syntax check", store=False, readonly=True)
active = fields.Boolean(default=True)
Expand All @@ -35,6 +36,9 @@ class SyncTask(models.Model):
"sync.trigger.automation", "sync_task_id", copy=True
)
webhook_ids = fields.One2many("sync.trigger.webhook", "sync_task_id", copy=True)
# sync_trigger_order_ids = fields.One2many(
# "sync.trigger.order", "sync_task_id", string="Sync Order Triggers", copy=True
# )
active_cron_ids = fields.Many2many(
"sync.trigger.cron",
string="Enabled Crons",
Expand Down
9 changes: 5 additions & 4 deletions sync/models/sync_trigger_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ def write(self, vals):
self._update_name(vals)
return res

@api.model
def create(self, vals):
res = super().create(vals)
res._update_name(vals)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
res = super().create(vals)
res._update_name(vals)
return res

def default_get(self, fields):
Expand Down
Loading

0 comments on commit 014b433

Please sign in to comment.