diff --git a/muk_utils/LICENSE b/muk_utils/LICENSE
new file mode 100644
index 0000000..11e8067
--- /dev/null
+++ b/muk_utils/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/muk_utils/README.rst b/muk_utils/README.rst
new file mode 100644
index 0000000..8e5cbfc
--- /dev/null
+++ b/muk_utils/README.rst
@@ -0,0 +1,112 @@
+=========
+MuK Utils
+=========
+
+Technical module to provide some utility features and libraries that can be used
+in other applications. This module has no direct effect on the running system.
+
+Installation
+============
+
+To install this module, you need to:
+
+Download the module and add it to your Odoo addons folder. Afterward, log on to
+your Odoo server and go to the Apps menu. Trigger the debug mode and update the
+list by clicking on the "Update Apps List" link. Now install the module by
+clicking on the install button.
+
+Another way to install this module is via the package management for Python
+(`PyPI `_).
+
+To install our modules using the package manager make sure
+`odoo-autodiscover `_ is installed
+correctly. Then open a console and install the module by entering the following
+command:
+
+``pip install --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+The module name consists of the Odoo version and the module name, where
+underscores are replaced by a dash.
+
+**Module:**
+
+``odoo-addon-``
+
+**Example:**
+
+``sudo -H pip3 install --extra-index-url https://nexus.mukit.at/repository/odoo/simple odoo13-addon-muk-utils``
+
+Once the installation has been successfully completed, the app is already in the
+correct folder. Log on to your Odoo server and go to the Apps menu. Trigger the
+debug mode and update the list by clicking on the "Update Apps List" link. Now
+install the module by clicking on the install button.
+
+The biggest advantage of this variant is that you can now also update the app
+using the "pip" command. To do this, enter the following command in your console:
+
+``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+When the process is finished, restart your server and update the application in
+Odoo. The steps are the same as for the installation only the button has changed
+from "Install" to "Upgrade".
+
+You can also view available Apps directly in our `repository `_
+and find a more detailed installation guide on our `website `_.
+
+For modules licensed under a proprietary license, you will receive the access data after you purchased
+the module. If the purchase were made at the Odoo store please contact our `support `_
+with a confirmation of the purchase to receive the corresponding access data.
+
+Upgrade
+============
+
+To upgrade this module, you need to:
+
+Download the module and add it to your Odoo addons folder. Restart the server
+and log on to your Odoo server. Select the Apps menu and upgrade the module by
+clicking on the upgrade button.
+
+If you installed the module using the "pip" command, you can also update the
+module in the same way. Just type the following command into the console:
+
+``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+When the process is finished, restart your server and update the application in
+Odoo, just like you would normally.
+
+Configuration
+=============
+
+No additional configuration is needed to use this module.
+
+Usage
+=====
+
+This module has no direct visible effect on the system. It provide utility features.
+
+Credit
+======
+
+Contributors
+------------
+
+* Mathias Markl
+
+Images
+------
+
+Some pictures are based on or inspired by the icon set of Font Awesome:
+
+* `Font Awesome `_
+
+Author & Maintainer
+-------------------
+
+This module is maintained by the `MuK IT GmbH `_.
+
+MuK IT is an Austrian company specialized in customizing and extending Odoo.
+We develop custom solutions for your individual needs to help you focus on
+your strength and expertise to grow your business.
+
+If you want to get in touch please contact us via `mail `_
+or visit our `website `_.
diff --git a/muk_utils/__init__.py b/muk_utils/__init__.py
new file mode 100644
index 0000000..b8ecfdc
--- /dev/null
+++ b/muk_utils/__init__.py
@@ -0,0 +1,22 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from . import models, tools
diff --git a/muk_utils/__manifest__.py b/muk_utils/__manifest__.py
new file mode 100644
index 0000000..18fe880
--- /dev/null
+++ b/muk_utils/__manifest__.py
@@ -0,0 +1,42 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+{
+ "name": "MuK Utils",
+ "summary": """Utility Features""",
+ "version": "13.0.1.0.0",
+ "category": "Extra Tools",
+ "license": "LGPL-3",
+ "author": "MuK IT",
+ "website": "https://www.mukit.at",
+ "contributors": ["Mathias Markl "],
+ "depends": ["base_setup"],
+ "data": [
+ "actions/ir_attachment.xml",
+ "views/ir_attachment.xml",
+ "views/mixins_groups.xml",
+ "views/res_config_settings.xml",
+ ],
+ "images": ["static/description/banner.png"],
+ "application": False,
+ "installable": True,
+ "auto_install": False,
+}
diff --git a/muk_utils/actions/ir_attachment.xml b/muk_utils/actions/ir_attachment.xml
new file mode 100644
index 0000000..1fd39a2
--- /dev/null
+++ b/muk_utils/actions/ir_attachment.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+ Migrate
+
+
+ code
+ records.action_migrate()
+
+
+
diff --git a/muk_utils/doc/changelog.rst b/muk_utils/doc/changelog.rst
new file mode 100644
index 0000000..fcc9635
--- /dev/null
+++ b/muk_utils/doc/changelog.rst
@@ -0,0 +1,34 @@
+`1.6.0`
+-------
+
+- Override Attachment to make it more extendable
+
+`1.5.0`
+-------
+
+- Storage Migration Action
+
+`1.4.0`
+-------
+
+- Added Hierarchy Mixin
+
+`1.3.0`
+-------
+
+- Added SCSS Editor
+
+`1.2.0`
+-------
+
+- Added Group Mixin
+
+`1.1.0`
+-------
+
+- Added Storage Settings
+
+`1.0.0`
+-------
+
+- Init Version
diff --git a/muk_utils/doc/index.rst b/muk_utils/doc/index.rst
new file mode 100644
index 0000000..8e5cbfc
--- /dev/null
+++ b/muk_utils/doc/index.rst
@@ -0,0 +1,112 @@
+=========
+MuK Utils
+=========
+
+Technical module to provide some utility features and libraries that can be used
+in other applications. This module has no direct effect on the running system.
+
+Installation
+============
+
+To install this module, you need to:
+
+Download the module and add it to your Odoo addons folder. Afterward, log on to
+your Odoo server and go to the Apps menu. Trigger the debug mode and update the
+list by clicking on the "Update Apps List" link. Now install the module by
+clicking on the install button.
+
+Another way to install this module is via the package management for Python
+(`PyPI `_).
+
+To install our modules using the package manager make sure
+`odoo-autodiscover `_ is installed
+correctly. Then open a console and install the module by entering the following
+command:
+
+``pip install --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+The module name consists of the Odoo version and the module name, where
+underscores are replaced by a dash.
+
+**Module:**
+
+``odoo-addon-``
+
+**Example:**
+
+``sudo -H pip3 install --extra-index-url https://nexus.mukit.at/repository/odoo/simple odoo13-addon-muk-utils``
+
+Once the installation has been successfully completed, the app is already in the
+correct folder. Log on to your Odoo server and go to the Apps menu. Trigger the
+debug mode and update the list by clicking on the "Update Apps List" link. Now
+install the module by clicking on the install button.
+
+The biggest advantage of this variant is that you can now also update the app
+using the "pip" command. To do this, enter the following command in your console:
+
+``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+When the process is finished, restart your server and update the application in
+Odoo. The steps are the same as for the installation only the button has changed
+from "Install" to "Upgrade".
+
+You can also view available Apps directly in our `repository `_
+and find a more detailed installation guide on our `website `_.
+
+For modules licensed under a proprietary license, you will receive the access data after you purchased
+the module. If the purchase were made at the Odoo store please contact our `support `_
+with a confirmation of the purchase to receive the corresponding access data.
+
+Upgrade
+============
+
+To upgrade this module, you need to:
+
+Download the module and add it to your Odoo addons folder. Restart the server
+and log on to your Odoo server. Select the Apps menu and upgrade the module by
+clicking on the upgrade button.
+
+If you installed the module using the "pip" command, you can also update the
+module in the same way. Just type the following command into the console:
+
+``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple ``
+
+When the process is finished, restart your server and update the application in
+Odoo, just like you would normally.
+
+Configuration
+=============
+
+No additional configuration is needed to use this module.
+
+Usage
+=====
+
+This module has no direct visible effect on the system. It provide utility features.
+
+Credit
+======
+
+Contributors
+------------
+
+* Mathias Markl
+
+Images
+------
+
+Some pictures are based on or inspired by the icon set of Font Awesome:
+
+* `Font Awesome `_
+
+Author & Maintainer
+-------------------
+
+This module is maintained by the `MuK IT GmbH `_.
+
+MuK IT is an Austrian company specialized in customizing and extending Odoo.
+We develop custom solutions for your individual needs to help you focus on
+your strength and expertise to grow your business.
+
+If you want to get in touch please contact us via `mail `_
+or visit our `website `_.
diff --git a/muk_utils/i18n/ar.po b/muk_utils/i18n/ar.po
new file mode 100644
index 0000000..20c2caf
--- /dev/null
+++ b/muk_utils/i18n/ar.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:39+0000\n"
+"PO-Revision-Date: 2019-07-13 09:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "مرفق"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "المُرفقات"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr "الأساس"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "ضبط الإعدادات"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "اسم العرض"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "المجموعة"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "اسم المجموعة"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "المجموعات"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr "المعرف"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr ""
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "المسار الأصلي"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr "باراميتر النظام"
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "المستخدمون"
diff --git a/muk_utils/i18n/de.po b/muk_utils/i18n/de.po
new file mode 100644
index 0000000..cb29f86
--- /dev/null
+++ b/muk_utils/i18n/de.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:39+0000\n"
+"PO-Revision-Date: 2019-07-13 09:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr "SpeichernSie diese Seite, bevor Sie die Migration auslösen."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr "Alle Daten"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr "Dokumenten Feld"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr "Dokumenten Model"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Dateianhang"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr "Speicherort des Attachments"
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr "Speicherort des Attachments."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Dateianhänge"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr "Basis"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr "Untergruppen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "Konfiguration "
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Anzeigename"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr "Explizite Benutzer"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr "Felddaten"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr "Speichermigration erzwingen"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Gruppe"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr "Gruppen Mixin"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Gruppenname"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr "Gruppenbenutzer"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Gruppen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr "Hierarchie Mixin"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Zuletzt geändert am"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr "Migrieren"
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr "Übergeordnete Gruppe"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "Übergeordneter Pfad"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr "Json Pfad"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr "Pfadnamen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr "Speicher"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr "Speicherort"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr "Speicherort geändert"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr "Systemparameter"
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr "Der Name der Gruppe muss einzigartig sein!"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Benutzer"
diff --git a/muk_utils/i18n/es.po b/muk_utils/i18n/es.po
new file mode 100644
index 0000000..e111f90
--- /dev/null
+++ b/muk_utils/i18n/es.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:39+0000\n"
+"PO-Revision-Date: 2019-07-13 09:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr "Save esta página antes de iniciar la migración."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr "Todos los datos"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr "Campo de documento adjunto"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr "Modelo de documento adjunto"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Adjunto"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr "Almacén de anexos"
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr "Almacén de archivos adjuntos."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Adjuntos"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr "Grupos de niños"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "Opciones de Configuración"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Nombre mostrado"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr "Usuarios Explícitos"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr "Datos de campo"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr "Migración de almacenamiento forzado"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Grupo"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr "Mezcla de Grupo"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Nombre del grupo"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr "Usuarios del grupo"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Grupos"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr "Mezcla de jerarquías"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Última modificación en"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr "Migrar"
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr "Grupo de padres"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "Trayectoria de los padres"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr "Camino Json"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr "Nombres de senderos"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr "Editor de Scss"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr "Almacenamiento"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr "Almacén"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr "Almacén modificado"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr "Parámetros del sistema"
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr "El nombre del grupo debe ser único!"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Usuarios"
diff --git a/muk_utils/i18n/fr.po b/muk_utils/i18n/fr.po
new file mode 100644
index 0000000..c8cca24
--- /dev/null
+++ b/muk_utils/i18n/fr.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:40+0000\n"
+"PO-Revision-Date: 2019-07-13 09:40+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr "Save cette page avant de déclencher la migration."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr "Toutes les données"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr "Champ du document joint"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr "Modèle de document joint"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Pièce jointe"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr "Pièce jointe"
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr "Emplacement de stockage des pièces jointes"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Pièces jointes"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr "Base"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "Paramètres de config"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Nom affiché"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr "Nom d'affichage"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr "Utilisateurs explicites"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr "Données de terrain"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Groupe"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr "Groupe"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Nom du groupe"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr "Nom du groupe"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Groupes"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr "Groupes"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Dernière modification le"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr "Dernière modification le"
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr "Seuls les administrateurs peuvent exécuter cette action."
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "Chemin parent"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr "Cheminement des parents"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr "Sentier Json"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr "Noms des chemins"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr "Editeur Scss"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr "Stockage"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr "Emplacement de stockage"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr "Paramètres du système"
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr "Changement de magasin"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Utilisateurs"
diff --git a/muk_utils/i18n/hi.po b/muk_utils/i18n/hi.po
new file mode 100644
index 0000000..7c00033
--- /dev/null
+++ b/muk_utils/i18n/hi.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:40+0000\n"
+"PO-Revision-Date: 2019-07-13 09:40+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr ""
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr ""
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr ""
diff --git a/muk_utils/i18n/muk_utils.pot b/muk_utils/i18n/muk_utils.pot
new file mode 100644
index 0000000..1138072
--- /dev/null
+++ b/muk_utils/i18n/muk_utils.pot
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:39+0000\n"
+"PO-Revision-Date: 2019-07-13 09:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr ""
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr ""
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr ""
diff --git a/muk_utils/i18n/nl.po b/muk_utils/i18n/nl.po
new file mode 100644
index 0000000..ffdabfc
--- /dev/null
+++ b/muk_utils/i18n/nl.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:40+0000\n"
+"PO-Revision-Date: 2019-07-13 09:40+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr "Save deze pagina vooraleer de migratie te starten."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr "Alle gegevens"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr "Bijgevoegd documentveld"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr "Bijgevoegd documentmodel"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Bijlage"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr "Opslaglocatie voor hulpstukken"
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr "Opslagplaats voor bevestigingsmateriaal."
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Bijlagen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr "Basis"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr "Kind groepen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "Configuratie instellingen"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Weergavenaam"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr "Expliciete gebruikers"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr "Veldgegevens"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr "Krachtenopslag migratie"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Groep"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr "Groepsmixer"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Groepsnaam"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr "Groep gebruikers"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Groepen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr "Hiërarchie Mixin"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Laatst gewijzigd op"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr "Migreren"
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr "Moedergroep"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "Bovenliggend pad"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr "Pad Json"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr "Padnamen"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr "Scss-editor"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr "Opslag"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr "Opslaglocatie"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr "Opslaglocatie veranderd"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr "Systeem parameter"
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr "De naam van de groep moet uniek zijn!"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Gebruikers"
diff --git a/muk_utils/i18n/pt.po b/muk_utils/i18n/pt.po
new file mode 100644
index 0000000..7d39a89
--- /dev/null
+++ b/muk_utils/i18n/pt.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:40+0000\n"
+"PO-Revision-Date: 2019-07-13 09:40+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Anexo"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Anexos"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "config configurações"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Nome a Exibir"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Grupo"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Nome do Grupo"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Grupos"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr "Id."
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Última Modificação em"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr ""
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr "Caminho ascendente "
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr ""
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Utilizadores"
diff --git a/muk_utils/i18n/ru.po b/muk_utils/i18n/ru.po
new file mode 100644
index 0000000..b6bded4
--- /dev/null
+++ b/muk_utils/i18n/ru.po
@@ -0,0 +1,210 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * muk_utils
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0-20190522\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-13 09:40+0000\n"
+"PO-Revision-Date: 2019-07-13 09:40+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Save this page before triggering the migration."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "All Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Field"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attached Document Model"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_attachment
+msgid "Attachment"
+msgstr "Приложение"
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Attachment storage location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,help:muk_utils.field_res_config_settings__attachment_location
+msgid "Attachment storage location."
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Attachments"
+msgstr "Вложения"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_base
+msgid "Base"
+msgstr "Базовый"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__child_groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Child Groups"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_res_config_settings
+msgid "Config Settings"
+msgstr "Настройки конфигурации"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__display_name
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__display_name
+msgid "Display Name"
+msgstr "Отображаемое Имя"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__explicit_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Explicit Users"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_search
+msgid "Field Data"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+msgid "Force Storage Migration"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Group"
+msgstr "Группа"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_groups
+msgid "Group Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__name
+msgid "Group Name"
+msgstr "Наименование Группы"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__users
+msgid "Group Users"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__groups
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_tree
+msgid "Groups"
+msgstr "Группы"
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_mixins_hierarchy
+msgid "Hierarchy Mixin"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__id
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor__id
+msgid "ID"
+msgstr "Номер"
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy____last_update
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_scss_editor____last_update
+msgid "Last Modified on"
+msgstr "Последнее изменение"
+
+#. module: muk_utils
+#: model:ir.actions.server,name:muk_utils.action_attachment_migrate
+msgid "Migrate"
+msgstr ""
+
+#. module: muk_utils
+#: code:addons/muk_utils/models/ir_attachment.py:87
+#, python-format
+msgid "Only administrators can execute this action."
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_group
+msgid "Parent Group"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__parent_path
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path
+msgid "Parent Path"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_json
+msgid "Path Json"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_hierarchy__parent_path_names
+msgid "Path Names"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_muk_utils_scss_editor
+msgid "Scss Editor"
+msgstr ""
+
+#. module: muk_utils
+#: model_terms:ir.ui.view,arch_db:muk_utils.res_config_settings_view_form
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_attachment_form
+msgid "Storage"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location
+msgid "Storage Location"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_res_config_settings__attachment_location_changed
+msgid "Storage Location Changed"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model,name:muk_utils.model_ir_config_parameter
+msgid "System Parameter"
+msgstr ""
+
+#. module: muk_utils
+#: sql_constraint:muk_utils.mixins.groups:0
+msgid "The name of the group must be unique!"
+msgstr ""
+
+#. module: muk_utils
+#: model:ir.model.fields,field_description:muk_utils.field_muk_utils_mixins_groups__count_users
+#: model_terms:ir.ui.view,arch_db:muk_utils.view_mixins_groups_form
+msgid "Users"
+msgstr "Пользователи"
diff --git a/muk_utils/models/__init__.py b/muk_utils/models/__init__.py
new file mode 100644
index 0000000..bc28605
--- /dev/null
+++ b/muk_utils/models/__init__.py
@@ -0,0 +1,29 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from . import (
+ base,
+ ir_attachment,
+ ir_config_parameter,
+ mixins_groups,
+ mixins_hierarchy,
+ res_config_settings,
+)
diff --git a/muk_utils/models/base.py b/muk_utils/models/base.py
new file mode 100644
index 0000000..804ff2f
--- /dev/null
+++ b/muk_utils/models/base.py
@@ -0,0 +1,210 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo import api, models
+from odoo.addons.muk_utils.tools import utils
+from odoo.osv import expression
+
+_logger = logging.getLogger(__name__)
+
+
+class Base(models.AbstractModel):
+
+ _inherit = "base"
+
+ # ----------------------------------------------------------
+ # Helper Methods
+ # ----------------------------------------------------------
+
+ @api.model
+ def _check_parent_field(self):
+ if self._parent_name not in self._fields:
+ raise TypeError(
+ "The parent ({}) field does not exist.".format(self._parent_name)
+ )
+
+ @api.model
+ def _build_search_childs_domain(self, parent_id, domain=[]):
+ self._check_parent_field()
+ parent_domain = [[self._parent_name, "=", parent_id]]
+ return expression.AND([parent_domain, domain]) if domain else parent_domain
+
+ @api.model
+ def _check_context_bin_size(self, field):
+ return any(
+ key in self.env.context for key in ["bin_size", "bin_size_{}".format(field)]
+ )
+
+ # ----------------------------------------------------------
+ # Security
+ # ----------------------------------------------------------
+
+ def _filter_access(self, operation, in_memory=True):
+ if self.check_access_rights(operation, False):
+ if in_memory:
+ return self._filter_access_rules_python(operation)
+ else:
+ return self._filter_access_rules(operation)
+ return self.env[self._name]
+
+ def _filter_access_ids(self, operation, in_memory=True):
+ return self._filter_access(operation, in_memory=in_memory).ids
+
+ def check_access(self, operation, raise_exception=False):
+ """ Verifies that the operation given by ``operation`` is allowed for
+ the current user according to the access level.
+
+ :param operation: one of ``read``, ``create``, ``write``, ``unlink``
+ :raise AccessError: * if current level of access do not permit this operation.
+ :return: True if the operation is allowed
+ """
+ try:
+ access_right = self.check_access_rights(operation, raise_exception)
+ access_rule = self.check_access_rule(operation) is None
+ return access_right and access_rule
+ except AccessError:
+ if raise_exception:
+ raise
+ return False
+
+ # ----------------------------------------------------------
+ # Hierarchy Methods
+ # ----------------------------------------------------------
+
+ @api.model
+ def search_parents(self, domain=[], offset=0, limit=None, order=None, count=False):
+ """ This method finds the top level elements of the hierarchy for a given search query.
+
+ :param domain: a search domain (default: empty list)
+ :param order: a string to define the sort order of the query (default: none)
+ :returns: the top level elements for the given search query
+ """
+ res = self._search_parents(
+ domain=domain, offset=offset, limit=limit, order=order, count=count
+ )
+ return res if count else self.browse(res)
+
+ @api.model
+ def search_read_parents(
+ self, domain=[], fields=None, offset=0, limit=None, order=None
+ ):
+ """ This method finds the top level elements of the hierarchy for a given search query.
+
+ :param domain: a search domain (default: empty list)
+ :param fields: a list of fields to read (default: all fields of the model)
+ :param order: a string to define the sort order of the query (default: none)
+ :returns: the top level elements for the given search query
+ """
+ records = self.search_parents(
+ domain=domain, offset=offset, limit=limit, order=order
+ )
+ if not records:
+ return []
+ if fields and fields == ["id"]:
+ return [{"id": record.id} for record in records]
+ result = records.read(fields)
+ if len(result) <= 1:
+ return result
+ index = {vals["id"]: vals for vals in result}
+ return [index[record.id] for record in records if record.id in index]
+
+ @api.model
+ def _search_parents(self, domain=[], offset=0, limit=None, order=None, count=False):
+ self._check_parent_field()
+ self.check_access_rights("read")
+ if expression.is_false(self, domain):
+ return 0 if count else []
+ self._flush_search(domain, fields=[self._parent_name], order=order)
+ query = self._where_calc(domain)
+ self._apply_ir_rules(query, "read")
+ from_clause, where_clause, where_clause_arguments = query.get_sql()
+ parent_where = where_clause and (" WHERE {}".format(where_clause)) or ""
+ parent_query = (
+ 'SELECT "{}".id FROM '.format(self._table) + from_clause + parent_where
+ )
+ no_parent_clause = '"{table}"."{field}" IS NULL'.format(
+ table=self._table, field=self._parent_name
+ )
+ no_access_clause = '"{table}"."{field}" NOT IN ({query})'.format(
+ table=self._table, field=self._parent_name, query=parent_query
+ )
+ parent_clause = "({} OR {})".format(no_parent_clause, no_access_clause)
+ order_by = self._generate_order_by(order, query)
+ from_clause, where_clause, where_clause_params = query.get_sql()
+ where_str = (
+ where_clause
+ and (" WHERE {} AND {}".format(where_clause, parent_clause))
+ or (" WHERE {}".format(parent_clause))
+ )
+ if count:
+ query_str = "SELECT count(1) FROM " + from_clause + where_str
+ self.env.cr.execute(query_str, where_clause_params + where_clause_arguments)
+ return self.env.cr.fetchone()[0]
+ limit_str = limit and " limit %d" % limit or ""
+ offset_str = offset and " offset %d" % offset or ""
+ query_str = (
+ 'SELECT "{}".id FROM '.format(self._table)
+ + from_clause
+ + where_str
+ + order_by
+ + limit_str
+ + offset_str
+ )
+ self.env.cr.execute(query_str, where_clause_params + where_clause_arguments)
+ return utils.uniquify_list([x[0] for x in self.env.cr.fetchall()])
+
+ @api.model
+ def search_childs(
+ self, parent_id, domain=[], offset=0, limit=None, order=None, count=False
+ ):
+ """ This method finds the direct child elements of the parent record for a given search query.
+
+ :param parent_id: the integer representing the ID of the parent record
+ :param domain: a search domain (default: empty list)
+ :param offset: the number of results to ignore (default: none)
+ :param limit: maximum number of records to return (default: all)
+ :param order: a string to define the sort order of the query (default: none)
+ :param count: counts and returns the number of matching records (default: False)
+ :returns: the top level elements for the given search query
+ """
+ domain = self._build_search_childs_domain(parent_id, domain=domain)
+ return self.search(domain, offset=offset, limit=limit, order=order, count=count)
+
+ @api.model
+ def search_read_childs(
+ self, parent_id, domain=[], fields=None, offset=0, limit=None, order=None
+ ):
+ """ This method finds the direct child elements of the parent record for a given search query.
+
+ :param parent_id: the integer representing the ID of the parent record
+ :param domain: a search domain (default: empty list)
+ :param fields: a list of fields to read (default: all fields of the model)
+ :param offset: the number of results to ignore (default: none)
+ :param limit: maximum number of records to return (default: all)
+ :param order: a string to define the sort order of the query (default: none)
+ :returns: the top level elements for the given search query
+ """
+ domain = self._build_search_childs_domain(parent_id, domain=domain)
+ return self.search_read(
+ domain=domain, fields=fields, offset=offset, limit=limit, order=order
+ )
diff --git a/muk_utils/models/ir_attachment.py b/muk_utils/models/ir_attachment.py
new file mode 100644
index 0000000..d8e734c
--- /dev/null
+++ b/muk_utils/models/ir_attachment.py
@@ -0,0 +1,101 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo import _, api, models
+from odoo.exceptions import AccessError
+from odoo.osv import expression
+
+_logger = logging.getLogger(__name__)
+
+
+class IrAttachment(models.Model):
+
+ _inherit = "ir.attachment"
+
+ # ----------------------------------------------------------
+ # Helper
+ # ----------------------------------------------------------
+
+ @api.model
+ def _get_storage_domain(self, storage):
+ return {
+ "db": [("db_datas", "=", False)],
+ "file": [("store_fname", "=", False)],
+ }[storage]
+
+ # ----------------------------------------------------------
+ # Actions
+ # ----------------------------------------------------------
+
+ def action_migrate(self):
+ self.migrate()
+
+ # ----------------------------------------------------------
+ # Functions
+ # ----------------------------------------------------------
+
+ @api.model
+ def storage_locations(self):
+ return ["db", "file"]
+
+ @api.model
+ def force_storage(self):
+ """Force all attachments to be stored in the currently configured storage"""
+ if not self.env.user._is_admin():
+ raise AccessError(_("Only administrators can execute this action."))
+ self.search(
+ expression.AND(
+ [
+ self._get_storage_domain(self._storage()),
+ [
+ "&",
+ "|",
+ ("res_field", "=", False),
+ ("res_field", "!=", False),
+ ("type", "=", "binary"),
+ ],
+ ]
+ )
+ ).migrate(batch_size=100)
+ return True
+
+ def migrate(self, batch_size=None):
+ commit_on_batch = bool(batch_size)
+ attachments_to_migrate = len(self)
+ batch_size = batch_size or len(self) or 1
+ storage_location = self._storage().upper()
+ for index, attachment in enumerate(self, start=1):
+ _logger.info(
+ "Migrate Attachment {index} of {total} to {storage}".format(
+ **{
+ "index": index,
+ "total": attachments_to_migrate,
+ "storage": storage_location,
+ }
+ )
+ )
+ attachment.write(
+ {"datas": attachment.datas, "mimetype": attachment.mimetype}
+ )
+ if commit_on_batch and not index % batch_size:
+ self.env.cr.commit()
diff --git a/muk_utils/models/ir_config_parameter.py b/muk_utils/models/ir_config_parameter.py
new file mode 100644
index 0000000..f606f0d
--- /dev/null
+++ b/muk_utils/models/ir_config_parameter.py
@@ -0,0 +1,32 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from odoo import api, models
+
+
+class IrConfigParameter(models.Model):
+
+ _inherit = "ir.config_parameter"
+
+ @api.model
+ def set_params(self, params):
+ for key, value in params.items():
+ self.set_param(key, value)
diff --git a/muk_utils/models/mixins_groups.py b/muk_utils/models/mixins_groups.py
new file mode 100644
index 0000000..0133068
--- /dev/null
+++ b/muk_utils/models/mixins_groups.py
@@ -0,0 +1,142 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from odoo import api, fields, models
+
+
+class Groups(models.AbstractModel):
+
+ _name = "muk_utils.mixins.groups"
+ _description = "Group Mixin"
+
+ _parent_store = True
+ _parent_name = "parent_group"
+
+ # ----------------------------------------------------------
+ # Database
+ # ----------------------------------------------------------
+
+ name = fields.Char(string="Group Name", required=True, translate=True)
+
+ parent_path = fields.Char(string="Parent Path", index=True)
+
+ count_users = fields.Integer(compute="_compute_users", string="Users", store=True)
+
+ @api.model
+ def _add_magic_fields(self):
+ super(Groups, self)._add_magic_fields()
+
+ def add(name, field):
+ if name not in self._fields:
+ self._add_field(name, field)
+
+ add(
+ "parent_group",
+ fields.Many2one(
+ _module=self._module,
+ comodel_name=self._name,
+ string="Parent Group",
+ ondelete="cascade",
+ auto_join=True,
+ index=True,
+ automatic=True,
+ ),
+ )
+ add(
+ "child_groups",
+ fields.One2many(
+ _module=self._module,
+ comodel_name=self._name,
+ inverse_name="parent_group",
+ string="Child Groups",
+ automatic=True,
+ ),
+ )
+ add(
+ "groups",
+ fields.Many2many(
+ _module=self._module,
+ comodel_name="res.groups",
+ relation="{}_groups_rel".format(self._table),
+ column1="gid",
+ column2="rid",
+ string="Groups",
+ automatic=True,
+ ),
+ )
+ add(
+ "explicit_users",
+ fields.Many2many(
+ _module=self._module,
+ comodel_name="res.users",
+ relation="{}_explicit_users_rel".format(self._table),
+ column1="gid",
+ column2="uid",
+ string="Explicit Users",
+ automatic=True,
+ ),
+ )
+ add(
+ "users",
+ fields.Many2many(
+ _module=self._module,
+ comodel_name="res.users",
+ relation="{}_users_rel".format(self._table),
+ column1="gid",
+ column2="uid",
+ string="Group Users",
+ compute="_compute_users",
+ store=True,
+ automatic=True,
+ ),
+ )
+
+ _sql_constraints = [
+ ("name_uniq", "unique (name)", "The name of the group must be unique!")
+ ]
+
+ # ----------------------------------------------------------
+ # Functions
+ # ----------------------------------------------------------
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super(Groups, self).default_get(fields_list)
+ if not self.env.context.get("groups_no_autojoin"):
+ if "explicit_users" in res and res["explicit_users"]:
+ res["explicit_users"] = res["explicit_users"] + [self.env.uid]
+ else:
+ res["explicit_users"] = [self.env.uid]
+ return res
+
+ # ----------------------------------------------------------
+ # Read, View
+ # ----------------------------------------------------------
+
+ @api.depends(
+ "parent_group", "parent_group.users", "groups", "groups.users", "explicit_users"
+ )
+ def _compute_users(self):
+ for record in self:
+ users = record.mapped("groups.users")
+ users |= record.mapped("explicit_users")
+ users |= record.mapped("parent_group.users")
+ record.update({"users": users, "count_users": len(users)})
diff --git a/muk_utils/models/mixins_hierarchy.py b/muk_utils/models/mixins_hierarchy.py
new file mode 100644
index 0000000..db96b84
--- /dev/null
+++ b/muk_utils/models/mixins_hierarchy.py
@@ -0,0 +1,183 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import functools
+import json
+import operator
+
+from odoo import api, fields, models
+from odoo.osv import expression
+
+
+class Hierarchy(models.AbstractModel):
+
+ _name = "muk_utils.mixins.hierarchy"
+ _description = "Hierarchy Mixin"
+
+ _parent_store = True
+ _parent_path_sudo = False
+ _parent_path_store = False
+
+ _name_path_context = "show_path"
+
+ # ----------------------------------------------------------
+ # Database
+ # ----------------------------------------------------------
+
+ parent_path = fields.Char(string="Parent Path", index=True)
+
+ @api.model
+ def _add_magic_fields(self):
+ super(Hierarchy, self)._add_magic_fields()
+
+ def add(name, field):
+ if name not in self._fields:
+ self._add_field(name, field)
+
+ path_names_search = None
+ if not self._parent_path_store:
+ path_names_search = "_search_parent_path_names"
+ add(
+ "parent_path_names",
+ fields.Char(
+ _module=self._module,
+ compute="_compute_parent_paths",
+ compute_sudo=self._parent_path_sudo,
+ store=self._parent_path_store,
+ search=path_names_search,
+ string="Path Names",
+ readonly=True,
+ automatic=True,
+ ),
+ )
+ add(
+ "parent_path_json",
+ fields.Text(
+ _module=self._module,
+ compute="_compute_parent_paths",
+ compute_sudo=self._parent_path_sudo,
+ store=self._parent_path_store,
+ string="Path Json",
+ readonly=True,
+ automatic=True,
+ ),
+ )
+
+ # ----------------------------------------------------------
+ # Helper
+ # ----------------------------------------------------------
+
+ @api.model
+ def _get_depends_parent_paths(self):
+ depends = ["parent_path"]
+ if self._rec_name:
+ depends += [self._rec_name]
+ elif "name" in self._fields:
+ depends += ["name"]
+ elif "x_name" in self._fields:
+ depends += ["x_name"]
+ return depends
+
+ # ----------------------------------------------------------
+ # Search
+ # ----------------------------------------------------------
+
+ @api.model
+ def _search_parent_path_names(self, operator, operand):
+ domain = []
+ for value in operand.split("/"):
+ args = [(self._rec_name_fallback(), operator, value)]
+ domain = expression.OR([args, domain]) if domain else args
+ return domain if domain else [(self._rec_name_fallback(), operator, "")]
+
+ # ----------------------------------------------------------
+ # Read, View
+ # ----------------------------------------------------------
+
+ @api.depends(lambda self: self._get_depends_parent_paths())
+ def _compute_parent_paths(self):
+ records = self.filtered("parent_path")
+ records_without_parent_path = self - records
+ paths = [list(map(int, rec.parent_path.split("/")[:-1])) for rec in records]
+ ids = paths and set(functools.reduce(operator.concat, paths)) or []
+ model_without_path = self.with_context(**{self._name_path_context: False})
+ filtered_records = model_without_path.browse(ids)._filter_access("read")
+ data = dict(filtered_records.name_get())
+ for record in records:
+ path_names = [""]
+ path_json = []
+ for id in reversed(list(map(int, record.parent_path.split("/")[:-1]))):
+ if id not in data:
+ break
+ path_names.append(data[id])
+ path_json.append({"model": record._name, "name": data[id], "id": id})
+ path_names.reverse()
+ path_json.reverse()
+ record.update(
+ {
+ "parent_path_names": "/".join(path_names),
+ "parent_path_json": json.dumps(path_json),
+ }
+ )
+ records_without_parent_path.update(
+ {"parent_path_names": False, "parent_path_json": False}
+ )
+
+ @api.model
+ def _name_search(
+ self, name="", args=None, operator="ilike", limit=100, name_get_uid=None
+ ):
+ domain = list(args or [])
+ if not (name == "" and operator == "ilike"):
+ if "/" in name:
+ domain += [("parent_path_names", operator, name)]
+ else:
+ domain += [(self._rec_name, operator, name)]
+ records = self.browse(
+ self._search(domain, limit=limit, access_rights_uid=name_get_uid)
+ )
+ return models.lazy_name_get(records.with_user(name_get_uid or self.env.uid))
+
+ def name_get(self):
+ if self.env.context.get(self._name_path_context):
+ res = []
+ for record in self:
+ names = record.parent_path_names
+ if not names:
+ res.append(super(Hierarchy, record).name_get()[0])
+ elif not len(names) > 50:
+ res.append((record.id, names))
+ else:
+ res.append((record.id, ".." + names[-48:]))
+ return res
+ return super(Hierarchy, self).name_get()
+
+ # ----------------------------------------------------------
+ # Create, Update, Delete
+ # ----------------------------------------------------------
+
+ def write(self, vals):
+ res = super(Hierarchy, self).write(vals)
+ if self._parent_path_store and self._rec_name_fallback() in vals:
+ domain = [("id", "child_of", self.ids)]
+ records = self.sudo().search(domain)
+ records.modified(["parent_path"])
+ return res
diff --git a/muk_utils/models/res_config_settings.py b/muk_utils/models/res_config_settings.py
new file mode 100644
index 0000000..54a250a
--- /dev/null
+++ b/muk_utils/models/res_config_settings.py
@@ -0,0 +1,55 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+
+ _inherit = "res.config.settings"
+
+ # ----------------------------------------------------------
+ # Selections
+ # ----------------------------------------------------------
+
+ def _attachment_location_selection(self):
+ locations = self.env["ir.attachment"].storage_locations()
+ return list(map(lambda location: (location, location.upper()), locations))
+
+ # ----------------------------------------------------------
+ # Database
+ # ----------------------------------------------------------
+
+ attachment_location = fields.Selection(
+ selection=lambda self: self._attachment_location_selection(),
+ config_parameter="ir_attachment.location",
+ string="Storage Location",
+ help="Attachment storage location.",
+ required=True,
+ default="file",
+ )
+
+ # ----------------------------------------------------------
+ # Actions
+ # ----------------------------------------------------------
+
+ def action_attachment_force_storage(self):
+ self.env["ir.attachment"].force_storage()
diff --git a/muk_utils/static/description/banner.png b/muk_utils/static/description/banner.png
new file mode 100644
index 0000000..cf5692b
Binary files /dev/null and b/muk_utils/static/description/banner.png differ
diff --git a/muk_utils/static/description/icon.png b/muk_utils/static/description/icon.png
new file mode 100644
index 0000000..1b124a9
Binary files /dev/null and b/muk_utils/static/description/icon.png differ
diff --git a/muk_utils/static/description/icon.svg b/muk_utils/static/description/icon.svg
new file mode 100644
index 0000000..45683e2
--- /dev/null
+++ b/muk_utils/static/description/icon.svg
@@ -0,0 +1 @@
+
diff --git a/muk_utils/static/description/index.html b/muk_utils/static/description/index.html
new file mode 100644
index 0000000..41933a0
--- /dev/null
+++ b/muk_utils/static/description/index.html
@@ -0,0 +1,168 @@
+
+
+
MuK Utils
+
Utility Features
+
+ MuK IT GmbH - www.mukit.at
+
+
+
+
+
+
+
+
Overview
+
+ Technical module to provide some utility features. The module is mainly
+ used as a dependency by other modules and to provide a collection of
+ common libraries. It has no direct visible effect on the system.
+
+
+
diff --git a/muk_utils/static/description/logo.png b/muk_utils/static/description/logo.png
new file mode 100644
index 0000000..9427ce3
Binary files /dev/null and b/muk_utils/static/description/logo.png differ
diff --git a/muk_utils/static/description/preview.png b/muk_utils/static/description/preview.png
new file mode 100644
index 0000000..1deb1cc
Binary files /dev/null and b/muk_utils/static/description/preview.png differ
diff --git a/muk_utils/static/description/service_customization.png b/muk_utils/static/description/service_customization.png
new file mode 100644
index 0000000..3eac664
Binary files /dev/null and b/muk_utils/static/description/service_customization.png differ
diff --git a/muk_utils/static/description/service_development.png b/muk_utils/static/description/service_development.png
new file mode 100644
index 0000000..580d460
Binary files /dev/null and b/muk_utils/static/description/service_development.png differ
diff --git a/muk_utils/static/description/service_implementation.png b/muk_utils/static/description/service_implementation.png
new file mode 100644
index 0000000..d64b66b
Binary files /dev/null and b/muk_utils/static/description/service_implementation.png differ
diff --git a/muk_utils/static/description/service_integration.png b/muk_utils/static/description/service_integration.png
new file mode 100644
index 0000000..76c5e80
Binary files /dev/null and b/muk_utils/static/description/service_integration.png differ
diff --git a/muk_utils/static/description/service_support.png b/muk_utils/static/description/service_support.png
new file mode 100644
index 0000000..4c530fa
Binary files /dev/null and b/muk_utils/static/description/service_support.png differ
diff --git a/muk_utils/tests/__init__.py b/muk_utils/tests/__init__.py
new file mode 100644
index 0000000..14e21de
--- /dev/null
+++ b/muk_utils/tests/__init__.py
@@ -0,0 +1,30 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from . import (
+ test_attachment_migration,
+ test_file_tools,
+ test_http_tools,
+ test_json_tools,
+ test_search_childs,
+ test_search_parents,
+ test_security_helper,
+)
diff --git a/muk_utils/tests/test_attachment_migration.py b/muk_utils/tests/test_attachment_migration.py
new file mode 100644
index 0000000..d986905
--- /dev/null
+++ b/muk_utils/tests/test_attachment_migration.py
@@ -0,0 +1,69 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo.osv import expression
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class MigrationTestCase(common.TransactionCase):
+ def setUp(self):
+ super(MigrationTestCase, self).setUp()
+ self.model = self.env["ir.attachment"]
+ self.params = self.env["ir.config_parameter"].sudo()
+ self.location = self.params.get_param("ir_attachment.location")
+ if self.location == "file":
+ self.params.set_param("ir_attachment.location", "db")
+ else:
+ self.params.set_param("ir_attachment.location", "file")
+
+ def tearDown(self):
+ self.params.set_param("ir_attachment.location", self.location)
+ super(MigrationTestCase, self).tearDown()
+
+ def test_storage_domain(self):
+ self.assertEqual(
+ self.model._get_storage_domain("db"), [("db_datas", "=", False)]
+ )
+ self.assertEqual(
+ self.model._get_storage_domain("file"), [("store_fname", "=", False)]
+ )
+
+ def test_force_storage_domain(self):
+ force_storage_domain = expression.AND(
+ [
+ self.model._get_storage_domain("db"),
+ [
+ "&",
+ "|",
+ ("res_field", "=", False),
+ ("res_field", "!=", False),
+ ("type", "=", "binary"),
+ ],
+ ]
+ )
+ self.assertFalse(expression.is_false(self.model, force_storage_domain))
+
+ def test_migration(self):
+ self.model.search([("type", "=", "binary")], limit=25).migrate()
diff --git a/muk_utils/tests/test_file_tools.py b/muk_utils/tests/test_file_tools.py
new file mode 100644
index 0000000..40c12d1
--- /dev/null
+++ b/muk_utils/tests/test_file_tools.py
@@ -0,0 +1,80 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+import os
+import shutil
+import tempfile
+
+from odoo.addons.muk_utils.tools import file
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class FileTestCase(common.TransactionCase):
+ def test_check_name(self):
+ self.assertTrue(file.check_name("Test"))
+ self.assertFalse(file.check_name("T/est"))
+
+ def test_compute_name(self):
+ self.assertEqual(file.compute_name("Test", 1, False), "Test(1)")
+ self.assertEqual(file.compute_name("Test.png", 1, True), "Test(1).png")
+
+ def test_unique_name(self):
+ self.assertEqual(file.unique_name("Test", ["A", "B"]), "Test")
+ self.assertEqual(file.unique_name("Test", ["Test"]), "Test(1)")
+
+ def test_unique_files(self):
+ files = file.unique_files(
+ [("Test.png", b"\xff data"), ("Test.png", b"\xff data")]
+ )
+ self.assertEqual(
+ files, [("Test.png", b"\xff data"), ("Test(1).png", b"\xff data")]
+ )
+
+ def test_guess_extension(self):
+ self.assertEqual(file.guess_extension(filename="Test.png"), "png")
+ self.assertEqual(file.guess_extension(mimetype="image/png"), "png")
+
+ def test_ensure_path_directories(self):
+ tmp_dir = tempfile.mkdtemp()
+ try:
+ path = os.path.join(tmp_dir, "Test/Test/")
+ file.ensure_path_directories(path)
+ self.assertTrue(os.path.exists(path))
+ finally:
+ shutil.rmtree(tmp_dir)
+ return True
+
+ def test_remove_empty_directories(self):
+ tmp_dir = tempfile.mkdtemp()
+ try:
+ dir = os.path.join(tmp_dir, "Test/")
+ path = os.path.join(dir, "Test/")
+ os.makedirs(path)
+ open(os.path.join(dir, "F"), "ab").close()
+ file.remove_empty_directories(path)
+ self.assertFalse(os.path.exists(path))
+ self.assertTrue(os.path.exists(dir))
+ finally:
+ shutil.rmtree(tmp_dir)
+ return True
diff --git a/muk_utils/tests/test_http_tools.py b/muk_utils/tests/test_http_tools.py
new file mode 100644
index 0000000..4d7d4b9
--- /dev/null
+++ b/muk_utils/tests/test_http_tools.py
@@ -0,0 +1,35 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import base64
+import logging
+
+from odoo.addons.muk_utils.tools import http
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class HttpTestCase(common.TransactionCase):
+ def test_decode_http_basic_authentication(self):
+ credentials = base64.b64encode(b"username:password").decode("ascii")
+ res = http.decode_http_basic_authentication("Basic {}".format(credentials))
+ self.assertEqual(res, ("username", "password"))
diff --git a/muk_utils/tests/test_json_tools.py b/muk_utils/tests/test_json_tools.py
new file mode 100644
index 0000000..2ac6262
--- /dev/null
+++ b/muk_utils/tests/test_json_tools.py
@@ -0,0 +1,34 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import json
+import logging
+
+from odoo.addons.muk_utils.tools.json import RecordEncoder
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class JsonTestCase(common.TransactionCase):
+ def test_json_dumps(self):
+ record = self.env["ir.attachment"].search_read([], limit=1)
+ json.dumps(record, sort_keys=True, indent=4, cls=RecordEncoder)
diff --git a/muk_utils/tests/test_search_childs.py b/muk_utils/tests/test_search_childs.py
new file mode 100644
index 0000000..4e8d971
--- /dev/null
+++ b/muk_utils/tests/test_search_childs.py
@@ -0,0 +1,60 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class SearchChildsTestCase(common.TransactionCase):
+ def setUp(self):
+ super(SearchChildsTestCase, self).setUp()
+ self.model = self.env["res.partner.category"]
+ self.parent = self.model.create(
+ {"parent_id": False, "name": "Parent", "active": True}
+ )
+ self.child = self.model.create(
+ {"parent_id": self.parent.id, "name": "Child", "active": True}
+ )
+ self.child_parent = self.model.create(
+ {"parent_id": self.parent.id, "name": "Child Parent", "active": True}
+ )
+ self.child_parent_child = self.model.create(
+ {
+ "parent_id": self.child_parent.id,
+ "name": "Child Parent Child",
+ "active": True,
+ }
+ )
+
+ def tearDown(self):
+ super(SearchChildsTestCase, self).tearDown()
+
+ def test_search_childs(self):
+ childs = self.model.search_childs(self.parent.id)
+ self.assertEqual(set(childs.ids), {self.child.id, self.child_parent.id})
+
+ def test_search_read_childs(self):
+ childs = self.model.search_childs(self.parent.id)
+ childs_names = self.model.search_read_childs(self.parent.id, fields=["name"])
+ self.assertEqual(childs.read(["name"]), childs_names)
diff --git a/muk_utils/tests/test_search_parents.py b/muk_utils/tests/test_search_parents.py
new file mode 100644
index 0000000..9068a47
--- /dev/null
+++ b/muk_utils/tests/test_search_parents.py
@@ -0,0 +1,112 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class SearchParentTestCase(common.TransactionCase):
+ def setUp(self):
+ super(SearchParentTestCase, self).setUp()
+ self.model = self.env["res.partner.category"]
+ self.parent = self.model.create(
+ {"parent_id": False, "name": "Parent", "active": True}
+ )
+ self.child = self.model.create(
+ {"parent_id": self.parent.id, "name": "Child", "active": True}
+ )
+ self.child_parent = self.model.create(
+ {"parent_id": self.parent.id, "name": "Child Parent", "active": True}
+ )
+ self.child_parent_child = self.model.create(
+ {
+ "parent_id": self.child_parent.id,
+ "name": "Child Parent Child",
+ "active": True,
+ }
+ )
+ self.ids = [
+ self.parent.id,
+ self.child.id,
+ self.child_parent.id,
+ self.child_parent_child.id,
+ ]
+ self.domain = [("id", "in", self.ids)]
+
+ def tearDown(self):
+ super(SearchParentTestCase, self).tearDown()
+
+ def _evaluate_parent_result(self, parents, records):
+ for parent in parents:
+ self.assertTrue(
+ not parent.parent_id or parent.parent_id.id not in records.ids
+ )
+
+ def test_search_parents(self):
+ records = self.model.search([])
+ parents = self.model.search_parents([])
+ self._evaluate_parent_result(parents, records)
+
+ def test_search_parents_domain(self):
+ records = self.model.search([("id", "!=", 1)])
+ parents = self.model.search_parents([("id", "!=", 1)])
+ self._evaluate_parent_result(parents, records)
+
+ def test_search_parents_domain(self):
+ records = self.model.search([("id", "!=", 1)])
+ parents = self.model.search_parents([("id", "!=", 1)])
+ self._evaluate_parent_result(parents, records)
+
+ def test_search_parents_args(self):
+ records = self.model.search([], offset=1, limit=1, order="name desc")
+ parents = self.model.search_parents(offset=1, limit=1, order="name desc")
+ self._evaluate_parent_result(parents, records)
+
+ def test_search_parents_count(self):
+ parents = self.model.search_parents(self.domain, count=False)
+ parent_count = self.model.search_parents(self.domain, count=True)
+ self.assertTrue(len(parents) == parent_count)
+
+ def test_search_parents_access_rights(self):
+ model = self.model.with_user(self.browse_ref("base.user_admin"))
+ parents = model.search_parents(self.domain)
+ self._evaluate_parent_result(parents, model.browse(self.ids))
+ self.assertTrue(len(parents) == 1 and parents.id == self.parent.id)
+ access_rule = self.env["ir.rule"].create(
+ {
+ "model_id": self.browse_ref("base.model_res_partner_category").id,
+ "domain_force": [("id", "!=", self.parent.id)],
+ "name": "Restrict Access",
+ }
+ )
+ records = model.search(self.domain)
+ parents = model.search_parents(self.domain)
+ self._evaluate_parent_result(parents, records)
+ self.assertTrue(set(parents.ids) == {self.child.id, self.child_parent.id})
+
+ def test_search_read_parents(self):
+ parents = self.model.search_parents([])
+ read_names = parents.read(["name"])
+ search_names = self.model.search_read_parents([], ["name"])
+ self.assertTrue(read_names == search_names)
diff --git a/muk_utils/tests/test_security_helper.py b/muk_utils/tests/test_security_helper.py
new file mode 100644
index 0000000..07ffa8b
--- /dev/null
+++ b/muk_utils/tests/test_security_helper.py
@@ -0,0 +1,50 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import logging
+
+from odoo.tests import common
+
+_logger = logging.getLogger(__name__)
+
+
+class SecurityTestCase(common.TransactionCase):
+ def setUp(self):
+ super(SecurityTestCase, self).setUp()
+ self.model = self.env["res.partner"].with_user(
+ self.browse_ref("base.user_admin")
+ )
+ self.record_ids = self.model.search([], limit=25).ids
+
+ def tearDown(self):
+ super(SecurityTestCase, self).tearDown()
+
+ def test_check_access(self):
+ self.model.browse(self.record_ids).check_access("read")
+ self.model.browse(self.record_ids).check_access("create")
+ self.model.browse(self.record_ids).check_access("write")
+ self.model.browse(self.record_ids).check_access("unlink")
+
+ def test_filter_access(self):
+ self.model.browse(self.record_ids)._filter_access("read", in_memory=True)
+ self.model.browse(self.record_ids)._filter_access("read", in_memory=False)
+ self.model.browse(self.record_ids)._filter_access_ids("read", in_memory=True)
+ self.model.browse(self.record_ids)._filter_access_ids("read", in_memory=False)
diff --git a/muk_utils/tools/__init__.py b/muk_utils/tools/__init__.py
new file mode 100644
index 0000000..3f12832
--- /dev/null
+++ b/muk_utils/tools/__init__.py
@@ -0,0 +1,22 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+from . import file, http, json, patch, security
diff --git a/muk_utils/tools/file.py b/muk_utils/tools/file.py
new file mode 100644
index 0000000..a7376ef
--- /dev/null
+++ b/muk_utils/tools/file.py
@@ -0,0 +1,97 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import mimetypes
+import os
+import shutil
+import tempfile
+
+from odoo.tools.mimetypes import guess_mimetype
+
+
+def check_name(name):
+ tmp_dir = tempfile.mkdtemp()
+ try:
+ open(os.path.join(tmp_dir, name), "a").close()
+ except IOError:
+ return False
+ finally:
+ shutil.rmtree(tmp_dir)
+ return True
+
+
+def compute_name(name, suffix, escape_suffix):
+ if escape_suffix:
+ name, extension = os.path.splitext(name)
+ return "{}({}){}".format(name, suffix, extension)
+ else:
+ return "{}({})".format(name, suffix)
+
+
+def unique_name(name, names, escape_suffix=False):
+ if not name in names:
+ return name
+ else:
+ suffix = 1
+ name = compute_name(name, suffix, escape_suffix)
+ while name in names:
+ suffix += 1
+ name = compute_name(name, suffix, escape_suffix)
+ return name
+
+
+def unique_files(files):
+ ufiles = []
+ unames = []
+ for file in files:
+ uname = unique_name(file[0], unames, escape_suffix=True)
+ ufiles.append((uname, file[1]))
+ unames.append(uname)
+ return ufiles
+
+
+def guess_extension(filename=None, mimetype=None, binary=None):
+ extension = filename and os.path.splitext(filename)[1][1:].strip().lower()
+ if not extension and mimetype:
+ extension = mimetypes.guess_extension(mimetype)[1:].strip().lower()
+ if not extension and binary:
+ mimetype = guess_mimetype(binary, default="")
+ extension = mimetypes.guess_extension(mimetype)[1:].strip().lower()
+ return extension
+
+
+def ensure_path_directories(path):
+ directory_path = os.path.dirname(path)
+ if not os.path.exists(directory_path):
+ os.makedirs(directory_path)
+
+
+def remove_empty_directories(path):
+ if not os.path.isdir(path):
+ return
+ entries = os.listdir(path)
+ if len(entries) > 0:
+ for entry in entries:
+ subpath = os.path.join(path, entry)
+ if os.path.isdir(subpath):
+ self._remove_empty_directories(subpath)
+ else:
+ os.rmdir(path)
diff --git a/muk_utils/tools/http.py b/muk_utils/tools/http.py
new file mode 100644
index 0000000..ffbbb8c
--- /dev/null
+++ b/muk_utils/tools/http.py
@@ -0,0 +1,46 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import base64
+import urllib
+
+from werkzeug.datastructures import CombinedMultiDict
+
+
+def decode_http_basic_authentication_value(value):
+ try:
+ username, password = base64.b64decode(value).decode().split(":", 1)
+ return urllib.parse.unquote(username), urllib.parse.unquote(password)
+ except:
+ return None, None
+
+
+def decode_http_basic_authentication(encoded_header):
+ header_values = encoded_header.strip().split(" ")
+ if len(header_values) == 1:
+ return decode_http_basic_authentication_value(header_values[0])
+ if len(header_values) == 2 and header_values[0].strip().lower() == "basic":
+ return decode_http_basic_authentication_value(header_values[1])
+ return None, None
+
+
+def request_params(httprequest):
+ return CombinedMultiDict([httprequest.args, httprequest.form, httprequest.files])
diff --git a/muk_utils/tools/json.py b/muk_utils/tools/json.py
new file mode 100644
index 0000000..1d810d3
--- /dev/null
+++ b/muk_utils/tools/json.py
@@ -0,0 +1,44 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import datetime
+import json
+
+from odoo import fields, models
+from odoo.tools import ustr
+
+
+class ResponseEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, datetime.date):
+ if isinstance(obj, datetime.datetime):
+ return fields.Datetime.to_string(obj)
+ return fields.Date.to_string(obj)
+ if isinstance(obj, (bytes, bytearray)):
+ return obj.decode()
+ return ustr(obj)
+
+
+class RecordEncoder(ResponseEncoder):
+ def default(self, obj):
+ if isinstance(obj, models.BaseModel):
+ return obj.name_get()
+ return ResponseEncoder.default(self, obj)
diff --git a/muk_utils/tools/patch.py b/muk_utils/tools/patch.py
new file mode 100644
index 0000000..ee02c03
--- /dev/null
+++ b/muk_utils/tools/patch.py
@@ -0,0 +1,30 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+
+def monkey_patch(cls):
+ def decorate(func):
+ name = func.__name__
+ func.super = getattr(cls, name, None)
+ setattr(cls, name, func)
+ return func
+
+ return decorate
diff --git a/muk_utils/tools/security.py b/muk_utils/tools/security.py
new file mode 100644
index 0000000..e382ade
--- /dev/null
+++ b/muk_utils/tools/security.py
@@ -0,0 +1,30 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+import random
+import string
+
+UNICODE_ASCII_CHARACTERS = string.ascii_letters + string.digits
+
+
+def generate_token(length=30, chars=UNICODE_ASCII_CHARACTERS):
+ generator = random.SystemRandom()
+ return "".join(generator.choice(chars) for index in range(length))
diff --git a/muk_utils/tools/utils.py b/muk_utils/tools/utils.py
new file mode 100644
index 0000000..22cd82f
--- /dev/null
+++ b/muk_utils/tools/utils.py
@@ -0,0 +1,25 @@
+###################################################################################
+#
+# Copyright (c) 2017-2019 MuK IT GmbH.
+#
+# This file is part of MuK Utils
+# (see https://mukit.at).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+###################################################################################
+
+
+def uniquify_list(seq):
+ seen = set()
+ return [val for val in seq if val not in seen and not seen.add(val)]
diff --git a/muk_utils/views/ir_attachment.xml b/muk_utils/views/ir_attachment.xml
new file mode 100644
index 0000000..47bb25d
--- /dev/null
+++ b/muk_utils/views/ir_attachment.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ ir_attachment.search
+ ir.attachment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ir_attachment.tree
+ ir.attachment
+
+
+
+
+
+
+
+
+
+ ir_attachment.form
+ ir.attachment
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/muk_utils/views/mixins_groups.xml b/muk_utils/views/mixins_groups.xml
new file mode 100644
index 0000000..2978ab5
--- /dev/null
+++ b/muk_utils/views/mixins_groups.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+ muk_utils_mixins_groups.search
+ muk_utils.mixins.groups
+
+
+
+
+
+
+
+
+ muk_utils_mixins_groups.tree
+ muk_utils.mixins.groups
+
+
+
+
+
+
+
+
+
+ muk_utils_mixins_groups.form
+ muk_utils.mixins.groups
+
+
+
+
+
+
diff --git a/muk_utils/views/res_config_settings.xml b/muk_utils/views/res_config_settings.xml
new file mode 100644
index 0000000..35c8442
--- /dev/null
+++ b/muk_utils/views/res_config_settings.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+ res.config.settings.view.form
+ res.config.settings
+
+
+