-
Odoo - Developer Mode Button (userScript)
Odoo ERP için geliştirmiş olduğum bu userScript sayesinde sistem tepsisine eklenen yeni bir simge aracılığıyla geliştirici modları arasında kolaylıkla geçiş yapabilirsiniz.
Kullanım
Bu bir userScript betiğidir, çalıştırabilmek için kullandığınız tarayıcıda Tampermonkey, Violentmonkey vb. gibi bir komut dosyası yöneticisi yüklü olmalıdır.

Bilgilendirme
userScript bir tarayıcı komut dosyası olarak çalışır, yani sadece yüklediğiniz tarayıcı ile bu betiği kullanabilirsiniz. userScript hakkında detaylı bilgi edinmek için buraya tıklayabilirsiniz.
Test Edilen Sürümler
- Odoo 16.x (Chromium ve Tampermonkey ile test edildi)
- Odoo 17.x (Chromium ve Tampermonkey ile test edildi)
- Odoo 18.x (Chromium ve Tampermonkey ile test edildi)
- Odoo 19.x (Chromium ve Tampermonkey ile test edildi)
Değişiklik günlüğü
1.5
- Simge güncellendi
- Buton elementinde bulunan "dropdown-toggle" sınıfı "o_nav_entry" ile değiştirilerek Odoo 18 ve daha yeni sürümlerde butonun yanında gözüken ok simgesinin giderilmesi sağlandı
- Butona tıklayıp listeyi açtıktan sonra sayfada başka bir yere tıklandığında listenin kapanması için tespit iyileştirildi
- @match kısmı
*://*/*olarak değiştirildi, script artık mevcut sayfanın Odoo olup olmadığını "document.getElementById('web.layout.odooscript');" komutuyla tespit ediyor.
1.4
- Sayfada DOM değişikliği olduğunda (Studio modülüne geçip tekrar çıkmak gibi) butonun kaybolmasına neden olan hata giderildi
1.3
- Dil seçeneği eklendi, artık Odoo'da seçili olan dile göre İngilizce ve Türkçe arasında geçiş yapıyor
- Sayfada diğer elementlere tıklayınca ve sayfa arası gezinmelerde menünün kapanmayıp ekranda kalması problemi giderildi
1.2
- Artık açılır menüde her zaman tüm seçenekler var ve her seçenek RadioButton formatında. Her zaman aktif olan durum seçili olarak geliyor.
- Dropdown menü aktifken buton
<div>elementineshowsınıfı ekleniyor. - Menünün daha kompakt görünmesi için yazım düzenlemeleri yapıldı
1.1
- Buton artık URL'de debug değeri boş ise, yani "?debug=1" yerine "?debug=" gözüküyorsa geliştirici modunda olmadığını anlıyor
- Artık butona tıklayınca anında geliştirici moduna geçmek yerine hangi geliştirici moduna geçileceğinin seçilebildiği bir menü açıyor
1.0
Bu betiğin yayınlandığı ilk sürümdür.
Yükleme adımlarını görmek için tıklayın
- Kullandığınız tarayıcıya Tampermonkey, Violentmonkey vb. gibi bir komut dosyası yöneticisi yükleyin.
- Yükledikten sonra uzantı seçeneklerinden "Kullanıcı Betiklerine İzin Ver" seçeneğini etkinleştirin.
- Chromium tabanlı bir tarayıcı kullanıyorsanız "Uzantılar" sayfasından geliştirici modunu etkinleştirin.
- Tarayıcınızı yeniden başlatın.
- Aşağıdaki snippetin sağ üst köşesinde bulunan "Aç" butonuna tıklayın.
- Karşınıza yükleme ekranı çıkacaktır, Yükle butonuna tıklayarak yükleme işlemini tamamlayabilirsiniz. Olası bir güncelleme yayınlanması durumunda bu ekran otomatik olarak tekrar karşınıza çıkacaktır.
// ==UserScript==
// @name Odoo Developer Mode Button
// @name:tr Odoo Geliştirici Modu Butonu
// @namespace https://github.com/sipsak
// @version 1.5
// @description Adds a button to the Odoo menu to switch between developer modes.
// @description:tr Odoo menüsüne geliştirici modları arasında geçiş yapmaya yarayan bir buton ekler.
// @author Burak Şipşak
// @match *://*/*
// @grant none
// @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQuNTIxIDUuNWE0LjQ3NyA0LjQ3NyAwIDAgMSAwIDYuMzMybC0zNC4xOSAzNC4xOUg0VjM5LjY5TDM4LjE5IDUuNWE0LjQ3NyA0LjQ3NyAwIDAgMSA2LjMzMSAwWiIgZmlsbD0iIzJFQkNGQSIvPjxwYXRoIGQ9Ik0xMC45IDE1LjEyMiA0Ljg5OCA5LjEyYTkuMDA0IDkuMDA0IDAgMCAwIDEwLjQ4IDEyLjU2OGwyMy4wMDEgMjNhNC40NzcgNC40NzcgMCAwIDAgNi4zMzEtNi4zM2wtMjMtMjMuMDAxQTkuMDA0IDkuMDA0IDAgMCAwIDkuMTQxIDQuODc3bDYuMDAyIDYuMDAyLTQuMjQzIDQuMjQzWiIgZmlsbD0iIzk4NTE4NCIvPjxwYXRoIGQ9Ik0yNS4wMjMgMTguNjcgMTguNjkgMjVsNi4zMzIgNi4zMzFMMzEuMzUyIDI1bC02LjMzLTYuMzMxWiIgZmlsbD0iIzE0NDQ5NiIvPjwvc3ZnPgo=
// @updateURL https://raw.githubusercontent.com/sipsak/Odoo-Developer-Mode-Button/main/Odoo-Developer-Mode-Button.user.js
// @downloadURL https://raw.githubusercontent.com/sipsak/Odoo-Developer-Mode-Button/main/Odoo-Developer-Mode-Button.user.js
// ==/UserScript==
(function () {
'use strict';
async function init() {
const scriptTag = document.getElementById('web.layout.odooscript');
if (!scriptTag) {
return;
}
function checkAndInsertButton() {
const systray = document.querySelector('.o_menu_systray');
if (systray && !document.querySelector('#dev-mode-button')) {
insertDevModeButton(systray);
}
}
function insertDevModeButton(systray) {
if (!systray || document.querySelector('#dev-mode-button')) return;
let isTurkish = false;
try {
if (window.odoo && window.odoo.__session_info__ && window.odoo.__session_info__.user_context) {
const lang = window.odoo.__session_info__.user_context.lang;
if (lang && lang.startsWith('tr')) {
isTurkish = true;
}
}
} catch (e) {}
if (!isTurkish) {
try {
const scriptTags = document.querySelectorAll('script');
for (const script of scriptTags) {
if (script.textContent && script.textContent.includes('odoo.__session_info__')) {
if (script.textContent.includes('"lang": "tr')) {
isTurkish = true;
break;
}
}
}
} catch (e) {}
}
if (!isTurkish) {
try {
const htmlTag = document.querySelector('html');
if (htmlTag && htmlTag.getAttribute('lang') && htmlTag.getAttribute('lang').startsWith('tr')) {
isTurkish = true;
}
} catch (e) {}
}
if (!isTurkish) {
try {
const urlParams = new URLSearchParams(window.location.search);
const langParam = urlParams.get('lang');
if (langParam && langParam.startsWith('tr')) {
isTurkish = true;
}
} catch (e) {}
}
const translations = {
title: isTurkish ? 'Geliştirici modu yönetimi' : 'Developer mode management',
options: [
{
label: isTurkish ? 'Geliştirici modu devre dışı' : 'Developer mode disabled',
value: 'none'
},
{
label: isTurkish ? 'Geliştirici modu' : 'Developer mode',
value: '1'
},
{
label: isTurkish ? 'Geliştirici modu (varlıklarla birlikte)' : 'Developer mode (with assets)',
value: 'assets'
},
{
label: isTurkish ? 'Geliştirici modu (varlıklarla testle birlikte)' : 'Developer mode (with tests assets)',
value: 'assets,tests'
},
]
};
const url = new URL(window.location.href);
const debugParam = url.searchParams.get('debug') || '';
const normalized = decodeURIComponent(debugParam).replace(/\s/g, '').toLowerCase();
const currentDebug = normalized || 'none';
const outerDiv = document.createElement('div');
outerDiv.className = 'o-dropdown dropdown o-mail-DiscussSystray-class o-dropdown--no-caret';
outerDiv.style.position = 'relative';
outerDiv.id = 'dev-mode-container';
const btn = document.createElement('button');
btn.id = 'dev-mode-button';
btn.className = 'o_nav_entry fw-normal';
btn.title = translations.title;
btn.tabIndex = 0;
btn.type = 'button';
btn.setAttribute('aria-expanded', 'false');
const icon = document.createElement('i');
icon.className = 'fa fa-lg fa-code';
icon.setAttribute('role', 'img');
icon.setAttribute('aria-label', translations.title);
if (currentDebug !== 'none') icon.classList.add('text-success');
btn.appendChild(icon);
outerDiv.appendChild(btn);
const dropdownMenu = document.createElement('ul');
dropdownMenu.className = 'dropdown-menu';
dropdownMenu.style.position = 'absolute';
dropdownMenu.style.top = '100%';
dropdownMenu.style.zIndex = '9999';
dropdownMenu.style.display = 'none';
dropdownMenu.style.minWidth = 'max-content';
dropdownMenu.style.whiteSpace = 'nowrap';
translations.options.forEach((opt, index) => {
const li = document.createElement('li');
const a = document.createElement('a');
a.className = 'dropdown-item d-flex align-items-center gap-2';
a.href = '#';
const radio = document.createElement('input');
radio.type = 'radio';
radio.className = 'form-check-input o_radio_input';
radio.name = 'debug-mode-radio';
radio.id = 'radio-debug-' + index;
radio.dataset.value = opt.value;
radio.checked = (opt.value === currentDebug);
radio.style.cursor = 'pointer';
const span = document.createElement('span');
span.textContent = opt.label;
a.appendChild(radio);
a.appendChild(span);
a.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const currentUrl = new URL(window.location.href);
if (opt.value === 'none') {
currentUrl.searchParams.delete('debug');
} else {
currentUrl.searchParams.set('debug', opt.value);
}
const finalUrl = currentUrl.href.includes('#')
? currentUrl.origin + currentUrl.pathname + currentUrl.search + window.location.hash
: currentUrl.toString();
window.location.href = finalUrl;
});
li.appendChild(a);
dropdownMenu.appendChild(li);
});
outerDiv.appendChild(dropdownMenu);
function toggleOurMenu(e) {
e.stopPropagation();
e.preventDefault();
const rect = btn.getBoundingClientRect();
const isNearRightEdge = rect.right + 250 > window.innerWidth;
dropdownMenu.style.left = isNearRightEdge ? 'auto' : '0';
dropdownMenu.style.right = isNearRightEdge ? '0' : 'auto';
const isOpen = dropdownMenu.style.display === 'block';
if (isOpen) {
dropdownMenu.style.display = 'none';
dropdownMenu.classList.remove('show');
outerDiv.classList.remove('show');
btn.setAttribute('aria-expanded', 'false');
} else {
dropdownMenu.style.display = 'block';
dropdownMenu.classList.add('show');
outerDiv.classList.add('show');
btn.setAttribute('aria-expanded', 'true');
}
}
btn.addEventListener('click', toggleOurMenu);
document.addEventListener('click', (event) => {
if (!outerDiv.contains(event.target)) {
dropdownMenu.style.display = 'none';
dropdownMenu.classList.remove('show');
outerDiv.classList.remove('show');
btn.setAttribute('aria-expanded', 'false');
}
}, true);
const closeMenuObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.target !== outerDiv) {
if (mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.classList &&
(node.classList.contains('dropdown-menu') ||
node.classList.contains('modal') ||
node.classList.contains('o_dialog') ||
node.querySelector('.dropdown-menu'))) {
if (dropdownMenu.style.display === 'block') {
dropdownMenu.style.display = 'none';
dropdownMenu.classList.remove('show');
outerDiv.classList.remove('show');
btn.setAttribute('aria-expanded', 'false');
}
}
}
}
}
}
if (mutation.type === 'attributes' &&
mutation.attributeName === 'class' &&
mutation.target !== outerDiv &&
!outerDiv.contains(mutation.target)) {
if (mutation.target.classList &&
mutation.target.classList.contains('show') &&
dropdownMenu.style.display === 'block') {
dropdownMenu.style.display = 'none';
dropdownMenu.classList.remove('show');
outerDiv.classList.remove('show');
btn.setAttribute('aria-expanded', 'false');
}
}
}
});
closeMenuObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style']
});
const messagesDiv = Array.from(systray.children).find(div =>
div.querySelector('i.fa-comments')
);
if (messagesDiv) {
systray.insertBefore(outerDiv, messagesDiv);
} else {
systray.appendChild(outerDiv);
}
}
function waitForSystrayAndInsert() {
const systray = document.querySelector('.o_menu_systray');
if (!systray) return setTimeout(waitForSystrayAndInsert, 500);
insertDevModeButton(systray);
}
function startObservingForSystray() {
const bodyObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
const systray = document.querySelector('.o_menu_systray');
if (systray && !document.querySelector('#dev-mode-button')) {
insertDevModeButton(systray);
}
}
}
});
bodyObserver.observe(document.body, {
childList: true,
subtree: true
});
}
function listenForRouteChanges() {
window.addEventListener('hashchange', () => {
setTimeout(checkAndInsertButton, 500);
});
const originalPushState = window.history.pushState;
const originalReplaceState = window.history.replaceState;
window.history.pushState = function() {
originalPushState.apply(this, arguments);
setTimeout(checkAndInsertButton, 500);
};
window.history.replaceState = function() {
originalReplaceState.apply(this, arguments);
setTimeout(checkAndInsertButton, 500);
};
window.addEventListener('load', () => {
setTimeout(checkAndInsertButton, 500);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
waitForSystrayAndInsert();
startObservingForSystray();
listenForRouteChanges();
});
} else {
waitForSystrayAndInsert();
startObservingForSystray();
listenForRouteChanges();
}
setInterval(checkAndInsertButton, 500);
}
init();
})();