Hope you guys doin well,
I'm curious about how can a certain string like # and # can trigger a popup of a user and channels in Odoo Chatter & Discuss. This chatter has mail.thread Models related to it but i can't seem to understand how is it possible to create my own custom trigger in chatter?
Is it belong to certain views and not in models?
Any help will be appreciated!
Those are suggestion delimiters for the models used to update the suggestion list.
const suggestionDelimiters = ['#', ':', '#', '/'];
For example # is used to fetch partners (mail.partner model) matching a given search term. The _updateSuggestionList function will search the suggestions, sort them then will show (update) the popup list
To add a new custom trigger, you need add the delimiter to the suggestionDelimiters list and alter the _computeSuggestionModelName function to return the related model, the model must be defined like the mail.partner model.
If you want to patch an existing model, check the following example (taken from the hr module):
/** #odoo-module **/
import {
registerInstancePatchModel,
registerFieldPatchModel,
} from '#mail/model/model_core';
import { attr, one2one } from '#mail/model/model_field';
// Patch model
registerInstancePatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', {
async checkIsEmployee() {
await this.async(() => this.messaging.models['hr.employee'].performRpcSearchRead({
context: { active_test: false },
domain: [['user_partner_id', '=', this.id]],
fields: ['user_id', 'user_partner_id'],
}));
this.update({ hasCheckedEmployee: true });
},
});
// Patch fields
registerFieldPatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', {
hasCheckedEmployee: attr({
default: false,
}),
});
There is another way, and that is using the / or "command" popup. You can add your own item in the popup list and trigger any actions when a user selects your item from the popup.
Inherit from mail.channel
class MailChannel(models.Model):
_inherit = 'mail.channel'
Add a new method that starts with _define_command_XXXXX. This registers the item in the popup.
def _define_command_sample(self):
return {'help': _('Here is some sample help')}
Add a new method that starts with _execute_command_XXXXX. This method is executed when the user uses your command.
def _execute_command_sample(self, **kwargs):
# Send a "temporary" message to the user
self._send_transient_message(self.env.user.partner_id, f"Hello! You just successfully used the /sample command. You typed {kwargs['body']}.")
Reference: Github where Odoo defines the /lead command
PS: The above is for Odoo V12.0 - V14.0. Odoo V15.0 works a bit different, but you can check this and this for how to do it.
Related
Ive written a provisioning module for WHMCS and attached it to a product but the module presents 6 buttons, Create, Suspend, Terminate, Change Package, and Change Password. I dont need these buttons as they make no sense for my module, instead I have some custom ones that do what I need, how do I remove these buttons from the product page?
Can't find anything on the WHMCS documentation to describe how to remove or even change the text of the buttons.
Did you check Custom Functions in the Provisioning Modules documentation?
To add client area buttons/functions:
function mymodule_ClientAreaCustomButtonArray() {
//Add or remove items as required
$buttonarray = array(
"Reboot Server" => "reboot",
"Custom Label" => "customlabel",
);
return $buttonarray;
}
//customlabel implementation
function mymodule_customlabel($params) {
# Code to perform customlabel action goes here...
if ($successful) {
$result = "success";
} else {
$result = "Error Message Goes Here...";
}
return $result;
}
I'm developing on Odoo 10.
I created a dynamic form view which search and display a product from its barcode, but I've got a problem.
Since the view has no initial record to display it is opened in edit mode, and that's ok, because I want to type the 'barcode' field.
But, after the product is displayed, when I exit from that view the 'can_be_discarded' function is fired, opening the confirm dialog.
Have I to create a new view type inheriting from FormView or is there a way to workaround this problem?
The view is a classic form view, with nothing special.
Here's the server code instead.
class ProductFromBarcode(models.TransientModel):
_name = 'levelprime_product_general_status.product_from_barcode'
_inherits = { 'product.product': 'product_id' }
product_id = fields.Many2one(
comodel_name='product.product',
store=False)
product_barcode = fields.Integer(help='Insert the barcode to search '
'the correspondent product',
store=False)
#api.onchange('product_barcode')
def on_barcode_changed(self):
if self.product_barcode != 0:
self.product_id = self.get_product_from_barcode(self.product_barcode)
#api.model
def get_product_from_barcode(self, barcode):
r = self.env['product.product'].search([('barcode', '=', barcode)])
if r:
return r
Ok, I think I'm in the right way, there are some rendering problem to solve, but the main behavior is what I was searching for.
I created a new type of view which inherit from the 'FormView' one and override the 'can_be_discarded' method to not execute controls about the data changes.
JS
odoo.define('levelprime_product_general_status.readonly_formview', function(require) {
'use strict'
var core = require('web.core')
var FormView = require('web.FormView')
var ReadOnly_FormView = FormView.extend({
init: function() {
this._super.apply(this, arguments)
},
start: function() {
this._super.apply(this, arguments)
},
can_be_discarded: function() {
return $.Deferred().resolve()
}
})
core.view_registry.add('readonly_form', ReadOnly_FormView)
return ReadOnly_FormView
})
PY
class ViewExtension(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(selection_add=[
('readonly_form', 'ReadOnly Form Version')])
Then you can simply use the tag in the xml.
You have used _inherits = { 'product.product': 'product_id' } in your wizard.
When using _inherits you will do a kind of polymorphic model in the database way.
For example product.product inherits product.template or res.users inherits res.partner.
This mean we create a model that gets the know how of a Model but adds aditional data/columns in a new database table. So when you create a user, all partner data is stored in res_partner table (and a partner is created) and all user related info is stored in res_users table.
In your code when wizard (levelprime_product_general_status.product_from_barcode) record will create then all product.product/product.template fields are required,due to that reason you are getting this type of error.
You can check difference between _inherit & _inherits from following link.
https://www.odoo.com/forum/how-to/developers-13/the-different-openerp-model-inheritance-mechanisms-what-s-the-difference-between-them-and-when-should-they-be-used-46
You need to follow following code.
class ProductFromBarcode(models.TransientModel):
_name = 'levelprime_product_general_status.product_from_barcode'
product_id= fields.Many2one(comodel_name='product.product',string="Product")
product_barcode = fields.Char(help='Insert the barcode to search '
'the correspondent product',
"Barcode")
#api.onchange('product_barcode')
def on_barcode_changed(self):
if self.product_barcode:
self.product_id = self.get_product_from_barcode(self.product_barcode)
#api.model
def get_product_from_barcode(self,barcode):
r = self.env['product.product'].search([('barcode', '=', barcode)])
if r:
return r
else:
return False
In above code just create wizard & add two fields.
This may help you.
from what you are saying you are using inherits to show the information of the product selected by the barcode remove it and use related field:
add the fields that you are showing on the form view to you model
name = fields.Char(related='product_id.name', readonly=True)
Now you can use name on your view it's like compute field or proxy.
you can make them readonly if you want to display them.
I would like to generate a Url in a list in keystoneJS. I prefer that the url not be stored in mongo.
Attempted:
Virtual field: works, but will not generate raw HTML for the href.
Types.Url: I get the href format, but I need a value from another field in my model, so it generates the url with undefined.. Example:
{ type: Types.Url, label: "Link", default: "[http://www.stackoverflow.com/ask?id=][1]" + this._id }
Any help on how to pull this off would be much appreciated.
For your second point, this._id is not available when adding fields to the model, hence why you're getting undefined.
Instead, try using a pre-save hook on your model:
yourModel.pre('save', function(next) {
this.link = "[http://www.stackoverflow.com/ask?id=][1]" + this._id;
next();
}
I'm not quite sure if you're trying to just generate a link in this way every time, or if the user should also be able to add their own link. If the later, you'll need to check if the link has been filled in in the pre-save hook.
I hope that helps and sorry it took so long to get an answer on this!
This was originally posted on discuss.emberjs.com. See:
http://discuss.emberjs.com/t/what-is-the-proper-use-of-store-filter-store-find-for-infinite-scrolling/3798/2
but that site seems to get worse and worse as far as quality of content these days so I'm hoping StackOverflow can rescue me.
Intent: Build a page in ember with ember-data implementing infinite scrolling.
Background Knowledge: Based on the emberjs.com api docs on ember-data, specifically the store.filter and store.find methods ( see: http://emberjs.com/api/data/classes/DS.Store.html#method_filter ) I should be able to set the model hook of a route to the promise of a store filter operation. The response of the promise should be a filtered record array which is a an array of items from the store filtered by a filter function which is suppose to be constantly updated whenever new items are pushed into the store. By combining this with the store.find method which will push items into the store, the filteredRecordArray should automatically update with the new items thus updating the model and resulting in new items showing on the page.
For instance, assume we have a Questions Route, Controller and a model of type Question.
App.QuestionsRoute = Ember.Route.extend({
model: function (urlParams) {
return this.get('store').filter('question', function (q) {
return true;
});
}
});
Then we have a controller with some method that will call store.find, this could be triggered by some event/action whether it be detecting scroll events or the user explicitly clicking to load more, regardless this method would be called to load more questions.
Example:
App.QuestionsController = Ember.ArrayController.extend({
...
loadMore: function (offset) {
return this.get('store').find('question', { skip: currentOffset});
}
...
});
And the template to render the items:
...
{{#each question in controller}}
{{question.title}}
{{/each}}
...
Notice, that with this method we do NOT have to add a function to the store.find promise which explicitly calls this.get('model').pushObjects(questions); In fact, trying to do that once you have already returned a filter record array to the model does not work. Either we manage the content of the model manually, or we let ember-data do the work and I would very much like to let Ember-data do the work.
This is is a very clean API; however, it does not seem to work they way I've written it. Based on the documentation I cannot see anything wrong.
Using the Ember-Inspector tool from chrome I can see that the new questions from the second find call are loaded into the store under the 'question' type but the page does not refresh until I change routes and come back. It seems like the is simply a problem with observers, which made me think that this would be a bug in Ember-Data, but I didn't want to jump to conclusions like that until I asked to see if I'm using Ember-Data as intended.
If someone doesn't know exactly what is wrong but knows how to use store.push/pushMany to recreate this scenario in a jsbin that would also help too. I'm just not familiar with how to use the lower level methods on the store.
Help is much appreciated.
I just made this pattern work for myself, but in the "traditional" way, i.e. without using store.filter().
I managed the "loadMore" part in the router itself :
actions: {
loadMore: function () {
var model = this.controller.get('model'), route = this;
if (!this.get('loading')) {
this.set('loading', true);
this.store.find('question', {offset: model.get('length')}).then(function (records) {
model.addObjects(records);
route.set('loading', false);
});
}
}
}
Since you already tried the traditional way (from what I see in your post on discuss), it seems that the key part is to use addObjects() instead of pushObjects() as you did.
For the records, here is the relevant part of my view to trigger the loadMore action:
didInsertElement: function() {
var controller = this.get('controller');
$(window).on('scroll', function() {
if ($(window).scrollTop() > $(document).height() - ($(window).height()*2)) {
controller.send('loadMore');
}
});
},
willDestroyElement: function() {
$(window).off('scroll');
}
I am now looking to move the loading property to the controller so that I get a nice loader for the user.
I have goggled a lot, but have not found a solution for my issue. The author of the widget references to the last answer of FAQ, but the FAQ does not have the answer or I cannot find it. I suppose it was updated since that time. Other fellows who faced the same issue and asked the same question just gone and did not provide any solution.
Anyway, in my case I have a table with button Pictures:
When a user clicks one of pictures button, modal dialog is shown. The user now can manage pictures for the chosen row. He can upload, delete pictures and so on. When the user opens the dialog for second row in the table he should see pictures for the second row only. It tells me that I have to clean the list of uploaded files every time user hits Pictures button to see the dialog. He will receive list of pictures which corresponds to chosen row from the server. Unfortunately, when I retrieve the list for the chosen row, the received files are added to the existing list.
Could you tell me how I can clean the list or reset the widget without removing files on the server side?
UPDATE I have used the following piece of code as a temporary solution.
jQuery.ajax({
url: "<YOUR URL HERE>",
dataType: 'json',
context: $('#fileupload')[0]
}).done(function (result) {
jQuery("#fileupload").find(".files").empty(); //this line solves the issue
jQuery(this).fileupload('option', 'done').call(this, null, { result: result });
});
Thank you.
i was also trying for one hour to get my upload work ;)
here is, how i solved this problem:
$('#html5FileInput').fileupload({
....
add: function (e, data) {
$.each(data.files, function (index, file) {
var newFileDiv = $(newfileDiv(file.name));
$('#fsUploadProgressHtml5').append(newFileDiv);
newFileDiv.find('a').bind('click', function (event) {
event.preventDefault();
var uploadFilesBox = $("#fsUploadProgressHtml5");
var remDiv = $(document.getElementById("fileDiv_" + event.data.filename));
removeFileFromArray(event.data.filename);
remDiv.remove();
data.files.length = 0;
...
});
data.context = newFileDiv;
});
...
)};
as you can see i create inside the add-event my file-dataset with 'newfileDiv(file.name)'. this creates a div with all information about the file (name, size, ...) and an ankor that exists for deleting the file from the list. on this ankor i bind a click-event in which i have the delete implementation.
hope this helps!
I know this isn't the most elegant solution, but I needed a very quick and dirty...so here's what I did (using jQuery).
//manually trigger the cancel button for all files...removes anything that isn't uploaded yet
$('.fileupload-buttonbar .cancel').first().trigger('click');
//check the checkbox that selects all files
if(!$('.fileupload-buttonbar .toggle').first().checked) {
$('.fileupload-buttonbar .toggle').first().trigger('click');
}
//manually trigger the delete button for all files
$('.fileupload-buttonbar .delete').first().trigger('click');
I know this isn't the best way. I know it isn't elegant...but it works for me and removes everything from the plugin.
If you have added file names or anything else from the plugin to any local arrays or objects, you'll need to clean those up manually (I have several handlers that fire on fileuploadadded, fileuploadsent, fileuploadcomplete, fileuploadfailed, and 'fileuploaddestroyed` events).
protected function get_file_objects($iteration_method = 'get_file_object') {
$upload_dir = $this->get_upload_path();
if (!is_dir($upload_dir)) {
return array();
}
return array_values(array_filter(array_map(
array($this, $iteration_method)
//scandir($upload_dir)
//File listing closed by Doss
)));
}
there is a scandir function responsible for listing the files. just uncomment it like i have done above and your problem is solved. it can be found in the UploadHandler.php file.