How to display a dialog with warning message from controller in Odoo? - odoo

Is it possible to display a warning message when overriding a controller function? This is the code I have:
raise Warning(_('Entered Quantity is greater than quantity on source.'))
return super(CheckCart, self).cart(**post)
I want to render the cart template but also raise a Warning, but I get the following:
500: Internal Server Error
Error Error message: ('Entered Quantity is greater than quantity on
source.', '')
Traceback
Without anything else.

Controller File.
request.render("custom_module_name.redirect_fail_page", {})
XML File.
<template id="redirect_fail_page" name="Failure Code Page">
<div>
<span>Failed</span>
</div>
<script>
setTimeout(function(){
window.location.href = '/';
}, 1100)
</script>
</template>
Here you can use request.render

You can do it like this way,
Called the Json Controller was based on logic return the value and on the js check with the value is satisfied then with Dialog you can raise the warning.
On Py File,
#http.route(['/custom/url'], type='json', auth="public", website=True)
def checkout_custom(self, **post):
# Logic Based on the that return True/False.
On JS File,
var ajax = require('web.ajax');
var Dialog = require('web.Dialog');
ajax.jsonRpc("/custom/url", 'call', {}).then(function(data) {
if (data) {
var dialog = new Dialog(this, {
size: 'medium',
$content: _t("<div style='font-size:14px;font-family:Helvetica, Arial, sans-serif;'>Warning Message.</div>"),
buttons: [{
text: _t('Ok'),
classes: "btn-primary",
close: true
}],
}).open();
} else {
// Process
}});
Thanks

Related

odoo15: How can I load js in qweb template?

I need to add events in some buttons in qweb template or maybe make a widget for this template. But I can't load js in this template, even if I add js file in web.assets_backend or web.assets_frontend.
controller.py
from odoo import http
class LogData(http.Controller):
#http.route("/log_data", type="http", auth="user")
def log_data_view(self, **kwargs):
return http.request.render(
"table_relation.log_data_template"
)
log_data_template.xml
<odoo>
<template id="log_data_template" name="Log Data">
<t t-call="web.layout">
<t t-set="head">
<t t-call-asssets="web.assets_common" t-js="false"/>
<t t-call-asssets="web.assets_frontend" t-js="false"/>
</t>
<div id="wrap" class="container">
<h1>Log Data</h1>
<div class="o_log_data">
<button id="start-log">日志</button>
<button id="cancel-log">停止</button>
<div id="log-content" style="height:500px;overflow: scroll;"/>
</div>
<button type="button" class="demo-btn">demo button</button>
</div>
</t>
</template>
</odoo>
log_data.js
odoo.define('log_data', function (require){
'use strict';
var publicWidget = require('web.public.widget');
console.log('==========')
publicWidget.registry.LogData = publicWidget.Widget.extend({
selector: '.o_log_data',
events: {
'click #start-log': '_startLog',
'click #cancel-log': '_cancelLog',
},
init: function () {
console.log('o_log_data')
},
start: function () {
console.log('o_log_data')
},
_startLog: function () {
console.log('_startLog')
},
});
publicWidget.registry.DemoBtn = publicWidget.Widget.extend({
selector: '.demo-btn',
events: {
click: '_onClick'
},
_onClick: function (e) {
console.log('_onClick')
},
});
})
manifest.py
'assets': {
'web.assets_backend': [
...
'table_relation/static/src/js/log_data.js',
]
'web.assets_frontend': [
...
'table_relation/static/src/js/log_data.js',
]
...
}
enter image description here
It seems not to load assets_backend bundle on this page, and log_data.js is not working.
As per the code you have mentioned above, it seems like you are trying to create a controller i.e a route that can be accessed by the User only but from the website side like the portal or eCommerce part.
So if that is the case, then you need to add your js files to web.assets_frontend instead of web.assets_backend in manifest.py file.
The answer is late but in case you still need this you can try the following code. It works in Odoo14 and should definitely work in Odoo15 as well since class Widget still have this statement:
// Now this class can simply be used with the following syntax::
// var myWidget = new MyWidget(this);
// myWidget.appendTo($(".some-div"));
You can find the reference here:
https://github.com/odoo/odoo/blob/f1f3fcef6cc471fc01da574da26712e643315da6/addons/web/static/src/legacy/js/core/widget.js#L49-L52
And your code refactored by following those instructions.
odoo.define('log_data', function (require){
'use strict';
var publicWidget = require('web.public.widget');
console.log('==========')
publicWidget.registry.LogData = publicWidget.Widget.extend({
selector: '.o_log_data',
events: {
'click #start-log': '_startLog',
'click #cancel-log': '_cancelLog',
},
init: function () {
console.log('o_log_data')
},
start: function () {
console.log('o_log_data')
},
_startLog: function () {
console.log('_startLog')
},
});
var LogData = new publicWidget.registry.LogData(this);
LogData.appendTo($(".o_log_data"));
publicWidget.registry.DemoBtn = publicWidget.Widget.extend({
selector: '.demo-btn',
events: {
click: '_onClick'
},
_onClick: function (e) {
console.log('_onClick')
},
});
var DemoBtn = new publicWidget.registry.DemoBtn(this);
DemoBtn.appendTo($(".demo-btn"));
});
I think that publicWidget are only rendered, as Krutarth Patel said, in the scope of some layout like 'website.layout'. I cannot provide much info about this because I still have to figure out the layouts, for example 'portal.layout' scope was not rendering the publicWidget extension for me.
But the Widget class is designed in a way that allows to 'force' render of the widget by inserting it into the dom, render, bound to specific selector and events.
So you can (probably?) automatically render by wrapping your template with the proper t-call, otherwise you can use the 'appendTo' syntax and append the widget to the DOM, make an istance out of it and use that istance.
I've been struggling with this for a couple of days before I could make it work, and I found a couple of post like this.
I hope this will help you, or other users to figure out some of the use you can make of odoo widgets.

How to show a message box in Odoo 13 website

I need to show a warning popup button when the user presses the "Checkout" button (just an ok/dismiss button)
I avoid using raise Warning() or raise ValidationError() since I want to remain on the existing page and simply show a pop-up warning.
Can you please share the simplest way to do this in Odoo 13?
On click proceed checkout button called jsonRpc where call your json controller with that add your custom logic on the controller and return that and on the js check with your condition and raise your Dialog-Box Like this,On Js:
var Dialog = require('web.Dialog');
ajax.jsonRpc("/custom/url", 'call', {}).then(function(data) {
if (data) {
var dialog = new Dialog(this, {
size: 'medium',
$content: _t("<div style='font-size:14px;font-family:Helvetica, Arial, sans-serif;'>Error.</div>"),
buttons: [{
text: _t('Ok'),
classes: "btn-primary",
close: true
}],
}).open();
} else {
// odoo logic
}
});
Thanks

V-select bug while selecting elements in Vuejs

I'm building a small application in vuejs 2 where I'm using v-select package for select box, Problem I'm facing is:
I've declared v-select in my component something like this:
<div class="form-group"><label class="col-sm-2 control-label">Company name:</label>
<div class="col-sm-6">
<v-select :options="companyOptions" v-model="company_name" :on-search="getOptions" placeholder="Company name"></v-select>
</div>
</div>
So accordingly I'm having data defined as company_name, and I'm calling an axios event to get the searchable data, while the component is being loaded I'm calling index data of first 50 set for initial selection and if anybody types then I'm calling a function getOptions to get data related to the input, now suppose if somebody selects any data and then removes it again from the selection and again search with key press event the searchable data is not displayed, I can see that my axios call is working fine and I'm able to get the relevant data. but it is not displaying in dropdown as it says:
Error in render function: "TypeError: Cannot read property 'label' of null"
Which is coming from the company_name model which was selected. Following is my code in codepen
In this my axios is not working as it says mixed content:
https://codepen.io/anon/pen/Bdeqam?editors=1011' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://connect.stellar-ir.com/api/companies'. This request has been blocked; the content must be served over HTTPS.
So I'm unable to explain properly in this code set. But my code looks same as declared in codepen.
Help me out in this.
The error is because your computed values are undefined and undefined is not a string, so no string methods (toLowerCase()) are available. The response.data.model.data must look like this:
[
{
id: 1234,
name: 'example'
}, {
id: 12345,
name: 'example2'
}
]
if you get an object instead of an array push it to the array: this.serverData.push(response.data.model.data)
Replace your axios call with:
this.serverData = [
{
id: 1234,
name: 'example'
}, {
id: 12345,
name: 'example2'
}
]
to test it.
In your getOptions() method you calling loading(true or false), but your fetchIndexData() method has an asynchronous axios call. Use async/await, a callback function or a promise chain to wait for the data and show the loading indicator correctly.
On every keypress an request is send to the server i would recommend to use a debounce function.
Tipp
Line 42: https://stackoverflow.com/a/42028776/6429774
axios.post('http://connect.stellar-ir.com/api/companies', searchData).then(response => {
if(response.status === 200)
{
this.serverData = response.data.model.data
}
}).catch(error => {
console.log(error)
});

Sharing data from a VueJS component

I have a VueJS address lookup component.
Vue.component('address-lookup',
{
template: '#address-lookup-template',
data: function()
{
return {
address: {'name': '', 'town:': '', 'postcode': ''},
errors: {'name': false, 'town': false, 'postcode': false},
states: {'busy': false, 'found': false},
result: {}
}
},
methods:
{
findAddress: function(event)
{
if( typeof event === 'object' && typeof event.target === 'object' )
{
event.target.blur();
}
$.ajax(
{
context: this,
url: '/lookup',
data:
{
'name': this.address.name,
'town': this.address.town,
'postcode': this.address.postcode
},
success: function(data)
{
this.states.busy = false;
this.states.found = true;
this.address.name = data.name;
this.result = data;
}
});
},
reset: function()
{
this.states.found = false;
this.result = {};
}
}
});
Inside my template I've then bound the result like so:
<p>{{ result.formatted_address }}</p>
There is some extra data returned within the result (like a twitter handle) that isn't part of the address lookup template, and occurs on a separate part of the form. For reasons relating to how my form is structured I can't include these inputs within the same template.
I found a way to bind those inputs, although it felt somewhat 'hacky'.
<input type="text" name="twitter" v-model="$refs.lookupResult._data.result.twitter">
That all works fine.
My problem is that the form is included as part of a larger template sometimes in the context of creating a new record, sometimes in the context of editing. When editing a record, the lookup component is removed (using an if server-side, so the template is no longer loaded at all) and when that happens I get this error.
$refs.lookupResult._data.result.twitter": TypeError: Cannot read property '_data' of undefined
This makes sense. lookupResult is defined when I include the template, and when editing I am removing this line:
<address-lookup v-ref:lookup-result></address-lookup>
I've worked around it by including a version of each extra input without the v-model attribute, again using a server-side if. But there are quite a few of these and it's getting a bit messy.
Is there a cleaner approach I could be using to better achieve this?
So I don't know the hierarchy of your layout, it isn't indicated above, but assuming that address-lookup component is a child of your parent, and you in fact need the results of address lookup in that parent, eg:
<parent-component> <!-- where you need the data -->
<address-lookup></address-lookup> <!-- where you lookup the data -->
</parent-component>
then you can simply pass the data props, either top-down only (default) or bidirectionally by defining 'address' for example on your parent's vue data hook:
// parent's data() function
data = function () {
return {
address: {}
}
}
// parent template, passed address with .sync modifier (to make it bi-directional)
<parent-component>
<address-lookup :address.sync='address'></address-lookup>
</parent-component>
// have the props accepted in the address look up component
var addressComponent = Vue.extend({
props: ['address']
})
Now in your $.ajax success function, simply set the props you need on this.address. Of course you can do this with all the props you need: errors, results, state etc. Even better, if you can nest them into a single key on the parent, you can pass the single key for the object containing all four elements instead of all four separately.

Pass data-attribute value of clicked element to ajax settings

For an implementation of Magnific Popup, I need to pass a post id to the ajax settings. The post id is stored in a data attribute of the element to which Magnific Popup is bound. I would like this to work:
html element:
<a data-id="412">Clicke me</a>
Javascript:
$('.element a').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST',
data: ({
action:'theme_post_example',
id: postId
})
}
}
});
Where postId is read from the data attribute.
Thanks in advance.
$('.element a').magnificPopup({
callbacks: {
elementParse: function(item){
postData = {
action :'theme_post_example',
id : $(item.el[0]).attr('data-id')
}
var mp = $.magnificPopup.instance;
mp.st.ajax.settings.data = postData;
}
},
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST'
}
}
});
Here is how to do it:
html:
<a class="modal" data-id="412" data-action="theme_post_example">Click me</a>
jquery:
$('a.modal').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url : php_array.admin_ajax,
dataType : 'json'
}
},
callbacks: {
elementParse: function() {
this.st.ajax.settings.data = {
action : this.st.el.attr('data-action'),
id : this.st.el.attr('data-id')
}
}
},
parseAjax: function( response )
{
response.data = response.data.html;
}
});
php
function theme_post_example()
{
$id = isset( $_GET['id'] ) ? $_GET['id'] : false;
$html = '<div class="white-popup mfp-with-anim">';
/**
* generate your $html code here ...
*/
$html .= '</div>';
echo json_encode( array( "html" => $html ) );
die();
}
As this answer was the original question regarding inserting data into Magnific's ajax call, I'll post this here.
After many hours of trying to figure this out, you should know that if you're using a gallery with the ability to move between gallery items without closing the popup, using elementParse to set your AJAX data will fail when you visit an item after already viewing it (while the popup is still open).
This is because elementParse is wrapped up in a check that it makes detect if an item has already been 'parsed'. Here's a small explanation as to what happens:
Open gallery at item index 2.
Item has not been parsed yet, so it sets the parsed flag to true and runs the elementParse callback (in that order). Your callback sets the ajax options to fetch this item's data, all is well.
Move (right) to item index 3.
Same as above. The item has not been parsed, so it runs the callback. Your callback sets the data. It works.
Move (left) back to item index 2.
This time the item has been parsed. It skips re-parsing the item's element for assumed potential performance reasons.Your callback is not executed. Magnific's ajax data settings will remain the same as if it were item index 3.
The AJAX call is executed with the old settings, it returns with item index 3's data instead, which is rendered to the user. Magnific will believe it is on index 2, but it is rendering index 3's data.
To resolve this, you need to hook onto a callback which is always executed pre-ajax call, like beforeChange.
The main difference is that the current item isn't passed through into the callback. Fortunately, at this point, magnific has updated their pointers to the correct index. You need to fetch the current item's element by using:
var data = {}; // Your key-value data object for jQuery's $.ajax call.
// For non-closures, you can reference mfp's instance using
// $.magnificPopup.instance instead of 'this'.
// e.g.
// var mfp = $.magnificPopup.instance;
// var itemElement = mfp.items[mfp.index].el;
var itemElement = this.items[this.index].el;
// Set the ajax data settings directly.
if(typeof this.st.ajax.settings !== 'object') {
this.st.ajax.settings = {};
}
this.st.ajax.settings.data = data;
This answer can also be used as a suitable alternative to the currently highest voted, as it will work either way.
You may use open public method to open popup dynamically http://dimsemenov.com/plugins/magnific-popup/documentation.html#public_methods
postId = $(this).attr('data-id')
$(this) retrieve the current element (the link you clicked on), and attr the value of the specified attribute.