Browse Source

Jun 30: [ADD] Initial commit 'odoo_webhook'

15.0
Cybrosys Technologies 1 month ago
parent
commit
92b0d1d2d3
  1. 46
      odoo_webhook/README.rst
  2. 22
      odoo_webhook/__init__.py
  3. 44
      odoo_webhook/__manifest__.py
  4. 7
      odoo_webhook/doc/RELEASE_NOTES.md
  5. 23
      odoo_webhook/models/__init__.py
  6. 577
      odoo_webhook/models/models.py
  7. 41
      odoo_webhook/models/webhook_webhook.py
  8. 2
      odoo_webhook/security/ir.model.access.csv
  9. BIN
      odoo_webhook/static/description/assets/icons/check.png
  10. BIN
      odoo_webhook/static/description/assets/icons/chevron.png
  11. BIN
      odoo_webhook/static/description/assets/icons/cogs.png
  12. BIN
      odoo_webhook/static/description/assets/icons/consultation.png
  13. BIN
      odoo_webhook/static/description/assets/icons/ecom-black.png
  14. BIN
      odoo_webhook/static/description/assets/icons/education-black.png
  15. BIN
      odoo_webhook/static/description/assets/icons/hotel-black.png
  16. BIN
      odoo_webhook/static/description/assets/icons/license.png
  17. BIN
      odoo_webhook/static/description/assets/icons/lifebuoy.png
  18. BIN
      odoo_webhook/static/description/assets/icons/logo.png
  19. BIN
      odoo_webhook/static/description/assets/icons/manufacturing-black.png
  20. BIN
      odoo_webhook/static/description/assets/icons/pos-black.png
  21. BIN
      odoo_webhook/static/description/assets/icons/puzzle.png
  22. BIN
      odoo_webhook/static/description/assets/icons/restaurant-black.png
  23. BIN
      odoo_webhook/static/description/assets/icons/service-black.png
  24. BIN
      odoo_webhook/static/description/assets/icons/trading-black.png
  25. BIN
      odoo_webhook/static/description/assets/icons/training.png
  26. BIN
      odoo_webhook/static/description/assets/icons/update.png
  27. BIN
      odoo_webhook/static/description/assets/icons/user.png
  28. BIN
      odoo_webhook/static/description/assets/icons/wrench.png
  29. BIN
      odoo_webhook/static/description/assets/modules/dash.png
  30. BIN
      odoo_webhook/static/description/assets/modules/l2.png
  31. BIN
      odoo_webhook/static/description/assets/modules/l3.png
  32. BIN
      odoo_webhook/static/description/assets/modules/multi_product.png
  33. BIN
      odoo_webhook/static/description/assets/modules/product.png
  34. BIN
      odoo_webhook/static/description/assets/modules/sign.jpg
  35. BIN
      odoo_webhook/static/description/assets/modules/systray.jpg
  36. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot1.png
  37. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot10.png
  38. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot2.png
  39. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot3.png
  40. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot4.png
  41. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot6.png
  42. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot7.png
  43. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot8.png
  44. BIN
      odoo_webhook/static/description/assets/screenshots/Screenshot9.png
  45. BIN
      odoo_webhook/static/description/assets/screenshots/hero.gif
  46. BIN
      odoo_webhook/static/description/banner.png
  47. BIN
      odoo_webhook/static/description/icon.png
  48. 661
      odoo_webhook/static/description/index.html
  49. 45
      odoo_webhook/views/webhook_webhook_views.xml

46
odoo_webhook/README.rst

@ -0,0 +1,46 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
Odoo Webhook
============
Odoo will send POST payload to the registered url when an event triggered.
Configuration
=============
* No additional configurations needed
License
-------
General Public License, Version 3 (AGPL v3).
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
Credits
-------
Developer: (V15) Unnimaya C O, Contact : odoo@cybrosys.com
Contacts
--------
* Mail Contact : odoo@cybrosys.com
* Website : https://cybrosys.com
Bug Tracker
-----------
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
Maintainer
==========
.. image:: https://cybrosys.com/images/logo.png
:target: https://cybrosys.com
This module is maintained by Cybrosys Technologies.
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
Further information
===================
HTML Description: `<static/description/index.html>`__

22
odoo_webhook/__init__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Unnimaya C O (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import models

44
odoo_webhook/__manifest__.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Unnimaya C O (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
{
'name': "Odoo Webhook",
'version': '15.0.1.0.0',
'category': 'Productivity',
'summary': """Odoo will send payload to the registered urls when an
event occurs on the selected model.""",
'description': """Webhooks are a potent tool for creating integrations
across various services and applications. A payload will be posted to
the registered urls when an event occurs on the chosen model.""",
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': 'https://www.cybrosys.com',
'data': [
'security/ir.model.access.csv',
'views/webhook_webhook_views.xml',
],
'images': ['static/description/banner.png'],
'license': 'AGPL-3',
'installable': True,
'auto_install': False,
'application': False,
}

7
odoo_webhook/doc/RELEASE_NOTES.md

@ -0,0 +1,7 @@
## Module <odoo_webhook>
#### 16.12.2024
#### Version 15.0.1.0.0
#### ADD
- Initial commit for Odoo Webhook

23
odoo_webhook/models/__init__.py

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Unnimaya C O (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import models
from . import webhook_webhook

577
odoo_webhook/models/models.py

@ -0,0 +1,577 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Unnimaya C O (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import json
import requests
from collections import defaultdict
from odoo import api, SUPERUSER_ID, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.models import BaseModel, LOG_ACCESS_COLUMNS, _unlink
from odoo.tools import attrgetter, clean_context, OrderedSet
@api.model
def _create(self, data_list):
""" Override _create method for sending the payload to the registered
webhook url. When a new record is added to the model, it will check
whether the model is added to webhook_webhook model. If it is added,
the payload will be posted to the create_url."""
assert data_list
cr = self.env.cr
quote = '"{}"'.format
# insert rows
ids = [] # ids of created records
other_fields = OrderedSet() # non-column fields
translated_fields = OrderedSet() # translated fields
for data in data_list:
# determine column values
stored = data['stored']
columns = [('id', "nextval(%s)", self._sequence)]
for name, val in sorted(stored.items()):
field = self._fields[name]
assert field.store
if field.column_type:
col_val = field.convert_to_column(val, self, stored)
columns.append((name, field.column_format, col_val))
if field.translate is True:
translated_fields.add(field)
else:
other_fields.add(field)
# Insert rows one by one
# - as records don't all specify the same columns, code building
# batch-insert query
# was very complex
# - and the gains were low, so not worth spending so much complexity
#
# It also seems that we have to be careful with INSERTs in batch,
# because they have the
# same problem as SELECTs:
# If we inject a lot of data in a single query, we fall into
# pathological perfs in
# terms of SQL parser and the execution of the query itself.
# In SELECT queries, we inject max 1000 ids (integers) when we can,
# because we know
# that this limit is well managed by PostgreSQL.
# In INSERT queries, we inject integers (small) and larger data
# (TEXT blocks for
# example).
#
# The problem then becomes: how to "estimate" the right size of the
# batch to have
# good performance?
#
# This requires extensive testing, and it was preferred not to
# introduce INSERTs in
# batch, to avoid regressions as much as possible.
#
# That said, we haven't closed the door completely.
query = "INSERT INTO {} ({}) VALUES ({}) RETURNING id".format(
quote(self._table),
", ".join(quote(name) for name, fmt, val in columns),
", ".join(fmt for name, fmt, val in columns),
)
params = [val for name, fmt, val in columns]
cr.execute(query, params)
ids.append(cr.fetchone()[0])
# put the new records in cache, and update inverse fields, for many2one
#
# cachetoclear is an optimization to avoid modified()'s cost until
# other_fields are processed
cachetoclear = []
records = self.browse(ids)
inverses_update = defaultdict(list) # {(field, value): ids}
for data, record in zip(data_list, records):
data['record'] = record
# DLE P104: test_inherit.py, test_50_search_one2many
vals = dict(
{k: v for d in data['inherited'].values() for k, v in d.items()},
**data['stored'])
set_vals = list(vals) + LOG_ACCESS_COLUMNS + [
self.CONCURRENCY_CHECK_FIELD, 'id', 'parent_path']
for field in self._fields.values():
if field.type in ('one2many', 'many2many'):
self.env.cache.set(record, field, ())
elif field.related and not field.column_type:
self.env.cache.set(record, field,
field.convert_to_cache(None, record))
# DLE P123: `test_adv_activity`, `test_message_assignation_inbox`,
# `test_message_log`, `test_create_mail_simple`, ...
# Set `mail.message.parent_id` to False in cache so it doesn't
# do the useless SELECT when computing the modified of `child_ids`
# in other words, if `parent_id` is not set, no other message
# `child_ids` are impacted.
# + avoid the fetch of fields which are False. e.g. if a boolean
# field is not passed in vals and as no default set in the field
# attributes,
# then we know it can be set to False in the cache in the case of
# a create.
elif field.name not in set_vals and not field.compute:
self.env.cache.set(record, field,
field.convert_to_cache(None, record))
for fname, value in vals.items():
field = self._fields[fname]
if field.type in ('one2many', 'many2many'):
cachetoclear.append((record, field))
else:
cache_value = field.convert_to_cache(value, record)
self.env.cache.set(record, field, cache_value)
if field.type in ('many2one', 'many2one_reference') and \
self.pool.field_inverses[field]:
inverses_update[(field, cache_value)].append(record.id)
for (field, value), record_ids in inverses_update.items():
field._update_inverses(self.browse(record_ids), value)
# update parent_path
records._parent_store_create()
# protect fields being written against recomputation
protected = [(data['protected'], data['record']) for data in data_list]
with self.env.protecting(protected):
# mark computed fields as todo
records.modified(self._fields, create=True)
if other_fields:
# discard default values from context for other fields
others = records.with_context(clean_context(self._context))
for field in sorted(other_fields, key=attrgetter('_sequence')):
field.create([
(other, data['stored'][field.name])
for other, data in zip(others, data_list)
if field.name in data['stored']
])
# mark fields to recompute
records.modified([field.name for field in other_fields],
create=True)
# if value in cache has not been updated by other_fields, remove it
for record, field in cachetoclear:
if self.env.cache.contains(record,
field) and not self.env.cache.get(record,
field):
self.env.cache.remove(record, field)
# check Python constraints for stored fields
records._validate_fields(
name for data in data_list for name in data['stored'])
records.check_access_rule('create')
# add translations
if self.env.lang and self.env.lang != 'en_US':
Translations = self.env['ir.translation']
for field in translated_fields:
tname = "%s,%s" % (field.model_name, field.name)
for data in data_list:
if field.name in data['stored']:
record = data['record']
val = data['stored'][field.name]
Translations._set_ids(tname, 'model', self.env.lang,
record.ids, val, val)
webhook = self.env['webhook.webhook'].search(
[('model_id', '=', self.env['ir.model'].sudo().search(
[('model', '=', records._name)]).id)]).filtered(
lambda r: r.create_url).mapped(
'create_url')
if webhook:
# Create payload if the model is added to webhook
for rec in records:
val_list = rec.search_read([('id', '=', rec.id)])
for item in val_list[0].keys():
field = (self.env['ir.model.fields'].sudo().search(
[('model', '=', rec._name), ('name', '=', item)]))
if field.ttype == 'binary':
if val_list[0][field.name]:
base_url = self.env[
'ir.config_parameter'].sudo().get_param(
'web.base.url')
val_list[0][field.name] = (
f'{base_url}/web/image/{self._name}/'
f'{val_list[0]["id"]}'
f'/{field.name}')
for item in webhook:
# Post payload to the registered url
val_list[0]['model'] = self._name
try:
response = requests.post(item, data=json.dumps(val_list[0],
default=str),
headers={
'Content-Type': 'application/json'})
response.raise_for_status()
except requests.exceptions.RequestException as e:
raise ValidationError(f"Error during POST request: {e}")
return records
def write(self, vals):
""" write(vals)
Updates all records in the current set with the provided values.
:param dict vals: fields to update and the value to set on them e.g::
{'foo': 1, 'bar': "Qux"}
will set the field ``foo`` to ``1`` and the field ``bar`` to
``"Qux"`` if those are valid (otherwise it will trigger an error).
:raise AccessError: * if user has no write rights on the requested object
* if user tries to bypass access rules for write on the requested object
:raise ValidationError: if user tries to enter invalid value for a field that is not in selection
:raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
* For numeric fields (:class:`~odoo.fields.Integer`,
:class:`~odoo.fields.Float`) the value should be of the
corresponding type
* For :class:`~odoo.fields.Boolean`, the value should be a
:class:`python:bool`
* For :class:`~odoo.fields.Selection`, the value should match the
selection values (generally :class:`python:str`, sometimes
:class:`python:int`)
* For :class:`~odoo.fields.Many2one`, the value should be the
database identifier of the record to set
* Other non-relational fields use a string for value
.. danger::
for historical and compatibility reasons,
:class:`~odoo.fields.Date` and
:class:`~odoo.fields.Datetime` fields use strings as values
(written and read) rather than :class:`~python:datetime.date` or
:class:`~python:datetime.datetime`. These date strings are
UTC-only and formatted according to
:const:`odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT` and
:const:`odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT`
* .. _openerp/models/relationals/format:
The expected value of a :class:`~odoo.fields.One2many` or
:class:`~odoo.fields.Many2many` relational field is a list of
:class:`~odoo.fields.Command` that manipulate the relation the
implement. There are a total of 7 commands:
:meth:`~odoo.fields.Command.create`,
:meth:`~odoo.fields.Command.update`,
:meth:`~odoo.fields.Command.delete`,
:meth:`~odoo.fields.Command.unlink`,
:meth:`~odoo.fields.Command.link`,
:meth:`~odoo.fields.Command.clear`, and
:meth:`~odoo.fields.Command.set`.
"""
if not self:
return True
self.check_access_rights('write')
self.check_field_access_rights('write', vals.keys())
self.check_access_rule('write')
env = self.env
bad_names = {'id', 'parent_path'}
if self._log_access:
# the superuser can set log_access fields while loading registry
if not (self.env.uid == SUPERUSER_ID and not self.pool.ready):
bad_names.update(LOG_ACCESS_COLUMNS)
# set magic fields
vals = {key: val for key, val in vals.items() if key not in bad_names}
if self._log_access:
vals.setdefault('write_uid', self.env.uid)
vals.setdefault('write_date', self.env.cr.now())
field_values = [] # [(field, value)]
determine_inverses = defaultdict(list) # {inverse: fields}
records_to_inverse = {} # {field: records}
relational_names = []
protected = set()
check_company = False
for fname, value in vals.items():
field = self._fields.get(fname)
if not field:
raise ValueError(
"Invalid field %r on model %r" % (fname, self._name))
field_values.append((field, value))
if field.inverse:
if field.type in ('one2many', 'many2many'):
# The written value is a list of commands that must applied
# on the field's current value. Because the field is
# protected while being written, the field's current value
# will not be computed and default to an empty recordset. So
# make sure the field's value is in cache before writing, in
# order to avoid an inconsistent update.
self[fname]
determine_inverses[field.inverse].append(field)
# DLE P150: `test_cancel_propagation`, `test_manufacturing_3_steps`, `test_manufacturing_flow`
# TODO: check whether still necessary
records_to_inverse[field] = self.filtered('id')
if field.relational or self.pool.field_inverses[field]:
relational_names.append(fname)
if field.inverse or (field.compute and not field.readonly):
if field.store or field.type not in ('one2many', 'many2many'):
# Protect the field from being recomputed while being
# inversed. In the case of non-stored x2many fields, the
# field's value may contain unexpeced new records (created
# by command 0). Those new records are necessary for
# inversing the field, but should no longer appear if the
# field is recomputed afterwards. Not protecting the field
# will automatically invalidate the field from the cache,
# forcing its value to be recomputed once dependencies are
# up-to-date.
protected.update(self.pool.field_computed.get(field, [field]))
if fname == 'company_id' or (field.relational and field.check_company):
check_company = True
# force the computation of fields that are computed with some assigned
# fields, but are not assigned themselves
to_compute = [field.name
for field in protected
if field.compute and field.name not in vals]
if to_compute:
self.recompute(to_compute, self)
# protect fields being written against recomputation
with env.protecting(protected, self):
# Determine records depending on values. When modifying a relational
# field, you have to recompute what depends on the field's values
# before and after modification. This is because the modification
# has an impact on the "data path" between a computed field and its
# dependency. Note that this double call to modified() is only
# necessary for relational fields.
#
# It is best explained with a simple example: consider two sales
# orders SO1 and SO2. The computed total amount on sales orders
# indirectly depends on the many2one field 'order_id' linking lines
# to their sales order. Now consider the following code:
#
# line = so1.line_ids[0] # pick a line from SO1
# line.order_id = so2 # move the line to SO2
#
# In this situation, the total amount must be recomputed on *both*
# sales order: the line's order before the modification, and the
# line's order after the modification.
self.modified(relational_names, before=True)
real_recs = self.filtered('id')
# field.write_sequence determines a priority for writing on fields.
# Monetary fields need their corresponding currency field in cache
# for rounding values. X2many fields must be written last, because
# they flush other fields when deleting lines.
for field, value in sorted(field_values,
key=lambda item: item[0].write_sequence):
field.write(self, value)
# determine records depending on new values
#
# Call modified after write, because the modified can trigger a
# search which can trigger a flush which can trigger a recompute
# which remove the field from the recompute list while all the
# values required for the computation could not be yet in cache.
# e.g. Write on `name` of `res.partner` trigger the recompute of
# `display_name`, which triggers a search on child_ids to find the
# childs to which the display_name must be recomputed, which
# triggers the flush of `display_name` because the _order of
# res.partner includes display_name. The computation of display_name
# is then done too soon because the parent_id was not yet written.
# (`test_01_website_reset_password_tour`)
self.modified(vals)
if self._parent_store and self._parent_name in vals:
self.flush([self._parent_name])
# validate non-inversed fields first
inverse_fields = [f.name for fs in determine_inverses.values() for f in
fs]
real_recs._validate_fields(vals, inverse_fields)
for fields in determine_inverses.values():
# write again on non-stored fields that have been invalidated from cache
for field in fields:
if not field.store and any(
self.env.cache.get_missing_ids(real_recs, field)):
field.write(real_recs, vals[field.name])
# inverse records that are not being computed
try:
fields[0].determine_inverse(real_recs)
except AccessError as e:
if fields[0].inherited:
description = self.env['ir.model']._get(self._name).name
raise AccessError(
_("%(previous_message)s\n\nImplicitly accessed through '%(document_kind)s' (%(document_model)s).") % {
'previous_message': e.args[0],
'document_kind': description,
'document_model': self._name,
}
)
raise
# validate inversed fields
real_recs._validate_fields(inverse_fields)
if self._name != 'ir.module.module':
webhook = self.env['webhook.webhook'].search(
[('model_id', '=', self.env['ir.model'].sudo().search(
[('model', '=', self._name)]).id)]).filtered(
lambda r: r.edit_url).mapped(
'edit_url')
if webhook:
# Create payload of the model is added to webhook
for item in vals.keys():
field = (self.env['ir.model.fields'].sudo().search(
[('model', '=', self._name), ('name', '=', item)]))
if field.ttype == 'binary':
if vals[field.name]:
base_url = self.env[
'ir.config_parameter'].sudo().get_param(
'web.base.url')
vals[field.name] = (
f'{base_url}/web/image/{self._name}/{self.id}'
f'/{field.name}')
for rec in self:
vals['id'] = rec.id
vals['model'] = rec._name
for item in webhook:
# Post payload to the registered url
try:
response = requests.post(item, data=json.dumps(vals,
default=str),
headers={
'Content-Type': 'application/json'})
response.raise_for_status()
except requests.exceptions.RequestException as e:
raise ValidationError(f"Error during POST request: {e}")
if check_company and self._check_company_auto:
self._check_company()
return True
def unlink(self):
"""Override unlink method for sending the payload to the registered
webhook url. When a record is deleted from a model, it will check
whether the model is added to the webhook_webhook model. If it is added,
the payload will be posted to the delete_url."""
if not self:
return True
self.check_access_rights('unlink')
self.check_access_rule('unlink')
self._check_concurrency()
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
for func in self._ondelete_methods:
# func._ondelete is True if it should be called during uninstallation
if func._ondelete or not self._context.get(MODULE_UNINSTALL_FLAG):
func(self)
# mark fields that depend on 'self' to recompute them after 'self' has
# been deleted (like updating a sum of lines after deleting one line)
self.flush()
self.modified(self._fields, before=True)
with self.env.norecompute():
cr = self._cr
Data = self.env['ir.model.data'].sudo().with_context({})
Defaults = self.env['ir.default'].sudo()
Property = self.env['ir.property'].sudo()
Attachment = self.env['ir.attachment'].sudo()
ir_model_data_unlink = Data
ir_attachment_unlink = Attachment
# TOFIX: this avoids an infinite loop when trying to recompute a
# field, which triggers the recomputation of another field using the
# same compute function, which then triggers again the computation
# of those two fields
for field in self._fields.values():
self.env.remove_to_compute(field, self)
webhook = self.env['webhook.webhook'].search(
[('model_id', '=', self.env['ir.model'].sudo().search(
[('model', '=', self._name)]).id)]).filtered(
lambda r: r.delete_url).mapped(
'delete_url')
# Create payload of the model is added to webhook
if webhook:
val_list = []
for rec in self:
val_list = rec.search_read([('id', '=', rec.id)])
for item in val_list[0].keys():
field = (self.env['ir.model.fields'].sudo().search(
[('model', '=', rec._name), ('name', '=', item)]))
if field.ttype == 'binary':
if val_list[0][field.name]:
base_url = self.env[
'ir.config_parameter'].sudo().get_param(
'web.base.url')
val_list[0][field.name] = (
f'{base_url}/web/image/'
f'{self._name}/{rec.id}'
f'/{field.name}')
for rec in webhook:
# Post payload to the registered url
for val in self:
val_list[0]['model'] = val._name
try:
response = requests.post(rec,
data=json.dumps(val_list[0], default=str),
headers={'Content-Type': 'application/json'})
response.raise_for_status()
except requests.exceptions.RequestException as e:
raise ValidationError(f"Error during POST request: {e}")
for sub_ids in cr.split_for_in_conditions(self.ids):
# Check if the records are used as default properties.
refs = ['%s,%s' % (self._name, i) for i in sub_ids]
if Property.search(
[('res_id', '=', False), ('value_reference', 'in', refs)],
limit=1):
raise UserError(
_('Unable to delete this document because it is used as a '
'default property'))
# Delete the records' properties.
Property.search([('res_id', 'in', refs)]).unlink()
query = "DELETE FROM %s WHERE id IN %%s" % self._table
cr.execute(query, (sub_ids,))
# Removing the ir_model_data reference if the record being deleted
# is a record created by xml/csv file, as these are not connected
# with real database foreign keys, and would be dangling references.
#
# Note: the following steps are performed as superuser to avoid
# access rights restrictions, and with no context to avoid possible
# side-effects during admin calls.
data = Data.search(
[('model', '=', self._name), ('res_id', 'in', sub_ids)])
if data:
ir_model_data_unlink |= data
# For the same reason, remove the defaults having some of the
# records as value
Defaults.discard_records(self.browse(sub_ids))
# For the same reason, remove the relevant records in ir_attachment
# (the search is performed with sql as the search method of
# ir_attachment is overridden to hide attachments of deleted
# records)
query = ('SELECT id FROM ir_attachment WHERE res_model=%s AND '
'res_id IN %s')
cr.execute(query, (self._name, sub_ids))
attachments = Attachment.browse([row[0] for row in cr.fetchall()])
if attachments:
ir_attachment_unlink |= attachments.sudo()
# invalidate the *whole* cache, since the orm does not handle all
# changes made in the database, like cascading delete!
self.invalidate_cache()
if ir_model_data_unlink:
ir_model_data_unlink.unlink()
if ir_attachment_unlink:
ir_attachment_unlink.unlink()
# DLE P93: flush after unlink, for recompute fields depending on
# the modified of unlink
self.flush()
# auditing: deletions are infrequent and leave no trace in the database
_unlink.info('User #%s deleted %s records with IDs: %r', self._uid,
self._name, self.ids)
return True
BaseModel._create = _create
BaseModel.unlink = unlink
BaseModel.write = write

41
odoo_webhook/models/webhook_webhook.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Unnimaya C O (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from odoo import fields, models
class WebhookWebhook(models.Model):
"""Model that holds the webhook model and the urls to receive the payload"""
_name = 'webhook.webhook'
_description = 'Webhook'
_rec_name = 'model_id'
model_id = fields.Many2one('ir.model', string='Model',
help='Choose the model')
create_url = fields.Char(string="Url for Create Event",
help="The URL to which payload to be send when a "
"new record is created")
edit_url = fields.Char(string="Url for Edit Event",
help="The URL to which payload to be send when a "
"record is modified")
delete_url = fields.Char(string="Url for Delete Event",
help="The URL to which payload to be send when a "
"record is deleted")

2
odoo_webhook/security/ir.model.access.csv

@ -0,0 +1,2 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_webhook_webhook_user,access.webhook.webhook.user,model_webhook_webhook,base.group_user,1,1,1,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 access_webhook_webhook_user access.webhook.webhook.user model_webhook_webhook base.group_user 1 1 1 1

BIN
odoo_webhook/static/description/assets/icons/check.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
odoo_webhook/static/description/assets/icons/chevron.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

BIN
odoo_webhook/static/description/assets/icons/cogs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
odoo_webhook/static/description/assets/icons/consultation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
odoo_webhook/static/description/assets/icons/ecom-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
odoo_webhook/static/description/assets/icons/education-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
odoo_webhook/static/description/assets/icons/hotel-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

BIN
odoo_webhook/static/description/assets/icons/license.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
odoo_webhook/static/description/assets/icons/lifebuoy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_webhook/static/description/assets/icons/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
odoo_webhook/static/description/assets/icons/manufacturing-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
odoo_webhook/static/description/assets/icons/pos-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

BIN
odoo_webhook/static/description/assets/icons/puzzle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

BIN
odoo_webhook/static/description/assets/icons/restaurant-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
odoo_webhook/static/description/assets/icons/service-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
odoo_webhook/static/description/assets/icons/trading-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

BIN
odoo_webhook/static/description/assets/icons/training.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

BIN
odoo_webhook/static/description/assets/icons/update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_webhook/static/description/assets/icons/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

BIN
odoo_webhook/static/description/assets/icons/wrench.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_webhook/static/description/assets/modules/dash.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
odoo_webhook/static/description/assets/modules/l2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
odoo_webhook/static/description/assets/modules/l3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
odoo_webhook/static/description/assets/modules/multi_product.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
odoo_webhook/static/description/assets/modules/product.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
odoo_webhook/static/description/assets/modules/sign.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
odoo_webhook/static/description/assets/modules/systray.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot10.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot7.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot8.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
odoo_webhook/static/description/assets/screenshots/Screenshot9.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
odoo_webhook/static/description/assets/screenshots/hero.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

BIN
odoo_webhook/static/description/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
odoo_webhook/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

661
odoo_webhook/static/description/index.html

@ -0,0 +1,661 @@
<div class="container"
style="padding: 1rem !important; margin-bottom: 1rem !important;">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 d-flex justify-content-between"
style="border-bottom: 1px solid #d5d5d5;">
<div class="my-3">
<img src="./assets/icons/logo.png"
style="width: auto !important; height: 40px !important;">
</div>
<div class="my-3 d-flex align-items-center">
<div
style="background-color: #7C7BAD !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;">
<i class="fa fa-check mr-1"></i>Community
</div>
<div
style="background-color: #875A7B !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
</div>
</div>
</div>
</div>
<div class="container" style="padding: 0rem 1.5rem 4rem !important">
<div class="row" style="height: 900px !important;">
<div class="col-sm-12 col-md-12 col-lg-12"
style="padding: 4rem 1rem !important; background-color: #714B67 !important; height: 600px !important; border-radius: 20px !important;">
<h1
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #FFFFFF !important; font-size: 3.5rem !important; text-align: center !important;">
Odoo Webhook</h1>
<p
style="font-family: 'Montserrat', sans-serif !important; font-weight: 300 !important; color: #FFFFFF !important; font-size: 1.4rem !important; text-align: center !important;">
Odoo Will Send Payload to the Registered Urls When
an Event Occurred on the Selected Model.
</p>
<img src="./assets/screenshots/hero.gif" class="img-responsive"
width="100%" height="auto"/>
</div>
</div>
<div class="row">
<div class="col-md-12"
style="border-bottom: 1px solid #d5d5d5 !important; margin-bottom: 2rem !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-compass mr-2"></i>Explore this module
</h2>
</div>
<div class="col-md-6">
<a href="#overview" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Overview</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
Learn more about this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right"
style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="#features" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Features</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
View features of this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right"
style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="#screenshots" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Screenshots</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
See key screenshots of this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right"
style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
</div>
<div class="row" id="overview">
<div class="col-md-12"
style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-pie-chart mr-2"></i>Overview
</h2>
</div>
<div class="col-mg-12 pl-3">
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important; line-height: 30px !important;">
Webhooks are a potent tool for creating integrations across
various
services and applications. A payload will be posted to the
registered
urls when an event occurs on the chosen model.
</p>
</div>
</div>
<div class="row" id="features">
<div class="col-md-12"
style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-star mr-2"></i>Features
</h2>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<h4
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Webhook can be generated for Create event of any model.</h4>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<h4 style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Webhook can be generated for Edit event of any model.</h4>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<h4 style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Webhook can be generated for Delete event of any model.</h4>
</div>
</div>
</div>
</div>
<div class="row" id="screenshots">
<div class="col-md-12"
style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-image mr-2"></i>Screenshots
</h2>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
New menu under Technical in General Settings</h4>
<img src="assets/screenshots/Screenshot1.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Create a Webhook.</h4>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
Select the Model for which the Webhook needs to be generated.
Url for Create Event, Url
for Delete Event and Url for Edit Event can be
filled with your website's urls where the payload should be
received when Create, Delete or Edit event occurs on the
selected Model.
</p>
<img src="assets/screenshots/Screenshot2.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
New Webhook is created for the Model Contact.</h4>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
As an example,
the urls provided by Webhook.site(www.webhook.site) is given
for the fields Url
for Create
Event, Url for Delete Event and Url for Edit Event.
</p>
<img src="assets/screenshots/Screenshot3.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Create new Contact.</h4>
<img src="assets/screenshots/Screenshot4.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
After adding a new Contact, a payload was received at the Url
for
Create Event.</h4>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
In our example, the payload is received at the url provided by
Webhook.site(www.webhook.site)
</p>
<img src="assets/screenshots/Screenshot6.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Apply any modifications to the Contact. The Contact's Company
Name is added here.
</h4>
<img src="assets/screenshots/Screenshot7.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Payload received at the Url for Edit Event after
modifying the record.</h4>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
In our example, the payload is received at the url provided by
Webhook.site(www.webhook.site)
</p>
<img src="assets/screenshots/Screenshot8.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Delete a Contact.</h4>
<img src="assets/screenshots/Screenshot9.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
<div class="col-lg-12 my-2">
<h4 class="mt-2"
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;">
Payload received at the Url for Delete Event after
deleting the record.</h4>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
In our example, the payload is received at the url provided by
Webhook.site(www.webhook.site)
</p>
<img src="assets/screenshots/Screenshot10.png"
class="img-responsive img-thumbnail border" width="100%"
height="auto"/>
</div>
</div>
<!-- SUGGESTED PRODUCTS -->
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center"
style="text-align: center; padding: 2.5rem 1rem !important;">
<h2 style="color: #212529 !important;">Suggested Products</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;"/>
<div id="demo1" class="row carousel slide" data-ride="carousel">
<!-- The slideshow -->
<div class="carousel-inner">
<div class="carousel-item active" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/project_dashboard_odoo/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/dash.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/agriculture_management_odoo/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/l2.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/statement_report/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/l3.png">
</div>
</a>
</div>
</div>
<div class="carousel-item" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/systray_world_clock/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/systray.jpg">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/pdf_report_with_sign/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/sign.jpg">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/product_image_suggestion/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/product.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1" data-slide="prev"
style="left:-25px;width: 35px;color: #000;">
<span class="carousel-control-prev-icon"><i
class="fa fa-chevron-left"
style="font-size:24px"></i></span> </a>
<a class="carousel-control-next" href="#demo1" data-slide="next"
style="right:-25px;width: 35px;color: #000;">
<span class="carousel-control-next-icon"><i
class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
</div>
</div>
<!-- END OF SUGGESTED PRODUCTS -->
<!-- OUR SERVICES -->
<section class="container" style="margin-top: 6rem !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Our Services</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;"/>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png"
class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</section>
<!-- END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<section class="container" style="margin-top: 6rem !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Our Industries</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;"/>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/trading-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easily procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/pos-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/education-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
A platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/manufacturing-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Plan, track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/ecom-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/service-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Keep track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/restaurant-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Run your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/hotel-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</section>
<!--END OF OUR INDUSTRIES -->
<!-- FOOTER -->
<!-- Footer Section -->
<section class="container" style="margin: 5rem auto 2rem;">
<div class="row" style="max-width:1540px;">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Need Help?</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;"/>
</div>
</div>
<!-- Contact Cards -->
<div class="row d-flex justify-content-center align-items-center"
style="max-width:1540px; margin: 0 auto 2rem auto;">
<div class="col-lg-12"
style="padding: 0rem 3rem 2rem; border-radius: 10px; margin-right: 3rem; ">
<div class="row mt-4">
<div class="col-lg-6">
<a href="mailto:odoo@cybrosys.com" target="_blank"
class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #4d4d4d; color: #FFF; border-radius: 4px;"><i
class="fa fa-envelope mr-2"></i>odoo@cybrosys.com</a>
</div>
<div class="col-lg-6">
<a href="https://api.whatsapp.com/send?phone=918606827707"
target="_blank"
class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #25D366; color: #FFF; border-radius: 4px;"><i
class="fa fa-whatsapp mr-2"></i>+91 86068 27707</a>
</div>
</div>
</div>
</div>
<!-- End of Contact Cards -->
</section>
<!-- Footer -->
<section class="oe_container" style="padding: 2rem 3rem 1rem;">
<div class="row"
style="max-width:1540px; margin: 0 auto; margin-right: 3rem; ">
<!-- Logo -->
<div class="col-lg-12 d-flex justify-content-center align-items-center"
style="margin-top: 3rem;">
<img src="https://www.cybrosys.com/images/logo.png"
width="200px" height="auto"/>
</div>
<!-- End of Logo -->
<div class="col-lg-12">
<hr
style="margin-top: 3rem;background: linear-gradient(90deg, rgba(2,0,36,0) 0%, rgba(229,229,229,1) 33%, rgba(229,229,229,1) 58%, rgba(0,212,255,0) 100%); height: 2px; border-style: none;">
<!-- End of Footer Section -->
</div>
</div>
</section>
<!-- END OF FOOTER -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF"
crossorigin="anonymous"></script>

45
odoo_webhook/views/webhook_webhook_views.xml

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Webhook webhook Form View-->
<record id="webhook_webhook_view_form" model="ir.ui.view">
<field name="name">webhook.webhook.view.form</field>
<field name="model">webhook.webhook</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<form>
<sheet name="Webhook">
<group>
<group>
<field name="model_id"/>
<field name="create_url"/>
</group>
<group>
<field name="delete_url"/>
<field name="edit_url"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Webhook webhook Tree View-->
<record id="webhook_webhook_view_tree" model="ir.ui.view">
<field name="name">webhook.webhook.view.tree</field>
<field name="model">webhook.webhook</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<tree>
<field name="model_id"/>
</tree>
</field>
</record>
<!-- Webhook Menu Action-->
<record id="webhook_webhook_action" model="ir.actions.act_window">
<field name="name">Webhook</field>
<field name="res_model">webhook.webhook</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Webhook Menu-->
<menuitem id="webhook_webhook_menu" name="Webhook"
parent="base.menu_automation" action="webhook_webhook_action"/>
</odoo>
Loading…
Cancel
Save