_description = "Preset for the lines that can be created in a bank statement reconciliation"
_columns = {
'name': fields.char('Button Label', required=True),
- 'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type','not in',('view','closed','consolidation'))]),
+ 'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type', 'not in', ('view', 'closed', 'consolidation'))]),
- 'label': fields.char('Label'),
- 'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')],
- 'Amount type', required=True),
- 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance').", required=True),
- 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use', 'in', ['purchase', 'all']), ('parent_id', '=', False)]),
+ 'label': fields.char('Journal Item Label'),
+ 'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')], 'Amount type', required=True),
+ 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), required=True, help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance')."),
+ 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use','in',('purchase','all')), ('parent_id','=',False)]),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='set null', domain=[('type','!=','view'), ('state','not in',('close','cancelled'))]),
+ 'company_id': fields.many2one('res.company', 'Company', required=True),
}
_defaults = {
'amount_type': 'percentage_of_balance',
_name = 'crm.tracking.mixin'
_columns = {
- 'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign', # old domain ="['|',('section_id','=',section_id),('section_id','=',False)]"
+ 'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign', # old domain ="['|',('team_id','=',team_id),('team_id','=',False)]"
help="This is a name that helps you keep track of your different campaign efforts Ex: Fall_Drive, Christmas_Special"),
'source_id': fields.many2one('crm.tracking.source', 'Source', help="This is the source of the link Ex: Search Engine, another domain, or name of email list"),
- 'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad"),
+ 'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad", oldname='channel_id'),
}
def tracking_fields(self):
<field name="partner_id" operator="child_of"/>
<field name="stage_id" domain="[]"/>
<field name="probability"/>
+ <field name="lost_reason"/>
<separator/>
<filter string="New" name="new"
- domain="[('probability', '=', 0), ('stage_id.sequence', '<=', 1)]"/>
+ domain="['&', ('stage_id.probability', '=', 0), ('stage_id.sequence', '<=', 1)]"/>
<filter string="Won" name="won"
- domain="[('probability', '=', 100), ('stage_id.fold', '=', True)]"/>
+ domain="['&', ('stage_id.probability', '=', 100), ('stage_id.fold', '=', True)]"/>
<filter string="Lost" name="lost"
- domain="[('probability', '=', 0), ('stage_id.fold', '=', True)]"/>
+ domain="['&', ('stage_id.probability', '=', 0), ('stage_id.fold', '=', True)]"/>
<separator/>
<filter string="My Opportunities" name="assigned_to_me"
domain="[('user_id', '=', uid)]"
<filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
<filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
<separator/>
- <filter string="New" name="new"
- domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
<filter string="Won" name="won"
- domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+ domain="['&', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
<filter string="Lost" name="lost"
- domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+ domain="['&', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
- <field name="section_id" context="{'invisible_section': False}"
+ <field name="team_id" context="{'invisible_team': False}"
groups="base.group_multi_salesteams"/>
<field name="user_id" string="Salesperson"/>
<group expand="0" string="Extended Filters">
<filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
<filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
<separator/>
- <filter string="New" name="new"
- domain="[('probability', '=', 0), ('stage_id.sequence', '<=', 1)]"/>
<filter string="Won" name="won"
- domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+ domain="['&', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
<filter string="Lost" name="lost"
- domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+ domain="['&', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
- <field name="section_id" context="{'invisible_section': False}"
+ <field name="team_id" context="{'invisible_team': False}"
groups="base.group_multi_salesteams"/>
<field name="user_id" string="Salesperson"/>
<group expand="0" string="Extended Filters">
access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
- access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
- access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_group_manager,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_manager,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
+access_mrp_bom_invoicing_payment,mrp.bom,model_mrp_bom,account.group_account_invoice,1,0,0,0
+access_mrp_production_invoicing_payment,mrp.production,model_mrp_production,account.group_account_invoice,1,1,1,0
<field name="company_id"/>
<field name="website_published"/>
<field name="environment"/>
+ <field name="validation"/>
+ <field name="auto_confirm"/>
</group>
<group>
<field name="fees_active"/>
input_to_focus.$el.focus();
});
},
-
- search_view_loaded: function(data) {
- var self = this;
- this.fields_view = data;
+ childFocused: function () {
+ this.$el.addClass('active');
+ this.view_id = this.view_id || data.view_id;
- if (data.type !== 'search' ||
- data.arch.tag !== 'search') {
- throw new Error(_.str.sprintf(
- "Got non-search view after asking for a search view: type %s, arch root %s",
- data.type, data.arch.tag));
- }
-
- return this.drawer_ready
- .then(this.proxy('setup_default_query'))
- .then(function () {
- self.trigger("search_view_loaded", data);
- self.ready.resolve();
- });
- },
- setup_default_query: function () {
- // Hacky implementation of CustomFilters#facet_for_defaults ensure
- // CustomFilters will be ready (and CustomFilters#filters will be
- // correctly filled) by the time this method executes.
- var custom_filters = this.drawer.custom_filters.filters;
- if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) {
- // Check for any is_default custom filter
- var personal_filter = _(custom_filters).find(function (filter) {
- return filter.user_id && filter.is_default;
- });
- if (personal_filter) {
- this.drawer.custom_filters.toggle_filter(personal_filter, true);
- return;
- }
-
- var global_filter = _(custom_filters).find(function (filter) {
- return !filter.user_id && filter.is_default;
- });
- if (global_filter) {
- this.drawer.custom_filters.toggle_filter(global_filter, true);
- return;
- }
- }
- // No custom filter, or no is_default custom filter, apply view defaults
- this.query.reset(_(arguments).compact(), {preventSearch: true});
- },
- /**
- * Extract search data from the view's facets.
- *
- * Result is an object with 4 (own) properties:
- *
- * errors
- * An array of any error generated during data validation and
- * extraction, contains the validation error objects
- * domains
- * Array of domains
- * contexts
- * Array of contexts
- * groupbys
- * Array of domains, in groupby order rather than view order
- *
- * @return {Object}
- */
- build_search_data: function () {
- var domains = [], contexts = [], groupbys = [], errors = [];
-
- this.query.each(function (facet) {
- var field = facet.get('field');
- try {
- var domain = field.get_domain(facet);
- if (domain) {
- domains.push(domain);
- }
- var context = field.get_context(facet);
- if (context) {
- contexts.push(context);
- }
- var group_by = field.get_groupby(facet);
- if (group_by) {
- groupbys.push.apply(groupbys, group_by);
- }
- } catch (e) {
- if (e instanceof instance.web.search.Invalid) {
- errors.push(e);
- } else {
- throw e;
- }
- }
- });
- return {
- domains: domains,
- contexts: contexts,
- groupbys: groupbys,
- errors: errors
- };
- },
- /**
- * Performs the search view collection of widget data.
- *
- * If the collection went well (all fields are valid), then triggers
- * :js:func:`instance.web.SearchView.on_search`.
- *
- * If at least one field failed its validation, triggers
- * :js:func:`instance.web.SearchView.on_invalid` instead.
- *
- * @param [_query]
- * @param {Object} [options]
- */
- do_search: function (_query, options) {
- if (options && options.preventSearch) {
- return;
- }
- var search = this.build_search_data();
- if (!_.isEmpty(search.errors)) {
- this.on_invalid(search.errors);
- return;
- }
- this.trigger('search_data', search.domains, search.contexts, search.groupbys);
- },
- /**
- * Triggered after the SearchView has collected all relevant domains and
- * contexts.
- *
- * It is provided with an Array of domains and an Array of contexts, which
- * may or may not be evaluated (each item can be either a valid domain or
- * context, or a string to evaluate in order in the sequence)
- *
- * It is also passed an array of contexts used for group_by (they are in
- * the correct order for group_by evaluation, which contexts may not be)
- *
- * @event
- * @param {Array} domains an array of literal domains or domain references
- * @param {Array} contexts an array of literal contexts or context refs
- * @param {Array} groupbys ordered contexts which may or may not have group_by keys
- */
- /**
- * Triggered after a validation error in the SearchView fields.
- *
- * Error objects have three keys:
- * * ``field`` is the name of the invalid field
- * * ``value`` is the invalid value
- * * ``message`` is the (in)validation message provided by the field
- *
- * @event
- * @param {Array} errors a never-empty array of error objects
- */
- on_invalid: function (errors) {
- this.do_notify(_t("Invalid Search"), _t("triggered from search view"));
- this.trigger('invalid_search', errors);
- },
-
- // The method appendTo is overwrited to be able to insert the drawer anywhere
- appendTo: function ($searchview_parent, $searchview_drawer_node) {
- var $searchview_drawer_node = $searchview_drawer_node || $searchview_parent;
-
- return $.when(
- this._super($searchview_parent),
- this.drawer.appendTo($searchview_drawer_node)
- );
- },
-
- destroy: function () {
- this.drawer.destroy();
- this.getParent().destroy.call(this);
- }
-});
-
-instance.web.SearchViewDrawer = instance.web.Widget.extend({
- template: "SearchViewDrawer",
-
- init: function(parent, searchview) {
- this._super(parent);
- this.searchview = searchview;
- this.searchview.set_drawer(this);
- this.ready = searchview.drawer_ready;
- this.controls = [];
- this.inputs = [];
- },
-
- toggle: function (visibility) {
- this.$el.toggle(visibility);
- var $view_manager_body = this.$el.closest('.oe_view_manager_body');
- if ($view_manager_body.length) {
- $view_manager_body.scrollTop(0);
- }
- },
-
- start: function() {
- var self = this;
- if (this.searchview.headless) return $.when(this._super(), this.searchview.ready);
- var filters_ready = this.searchview.fields_view_get
- .then(this.proxy('prepare_filters'));
- return $.when(this._super(), filters_ready).then(function () {
- var defaults = arguments[1][0];
- self.ready.resolve.apply(null, defaults);
- });
- },
- prepare_filters: function (data) {
- this.make_widgets(
- data['arch'].children,
- data.fields);
-
- this.add_common_inputs();
-
- // build drawer
- var in_drawer = this.select_for_drawer();
-
- var $first_col = this.$(".col-md-7"),
- $snd_col = this.$(".col-md-5");
-
- var add_custom_filters = in_drawer[0].appendTo($first_col),
- add_filters = in_drawer[1].appendTo($first_col),
- add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)),
- defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
- 'facet_for_defaults', this.searchview.defaults));
-
- return $.when(defaults_fetched, add_custom_filters, add_filters, add_rest);
- },
- /**
- * Sets up thingie where all the mess is put?
- */
- select_for_drawer: function () {
- return _(this.inputs).filter(function (input) {
- return input.in_drawer();
- });
- },
-
- /**
- * Builds a list of widget rows (each row is an array of widgets)
- *
- * @param {Array} items a list of nodes to convert to widgets
- * @param {Object} fields a mapping of field names to (ORM) field attributes
- * @param {Object} [group] group to put the new controls in
- */
- make_widgets: function (items, fields, group) {
- if (!group) {
- group = new instance.web.search.Group(
- this, 'q', {attrs: {string: _t("Filters")}});
- }
- var self = this;
- var filters = [];
- _.each(items, function (item) {
- if (filters.length && item.tag !== 'filter') {
- group.push(new instance.web.search.FilterGroup(filters, group));
- filters = [];
- }
-
- switch (item.tag) {
- case 'separator': case 'newline':
- break;
- case 'filter':
- filters.push(new instance.web.search.Filter(item, group));
- break;
- case 'group':
- self.add_separator();
- self.make_widgets(item.children, fields,
- new instance.web.search.Group(group, 'w', item));
- self.add_separator();
- break;
- case 'field':
- var field = this.make_field(
- item, fields[item['attrs'].name], group);
- group.push(field);
- // filters
- self.make_widgets(item.children, fields, group);
- break;
- }
- }, this);
-
- if (filters.length) {
- group.push(new instance.web.search.FilterGroup(filters, this));
- }
},
-
- add_separator: function () {
- if (!(_.last(this.inputs) instanceof instance.web.search.Separator))
- new instance.web.search.Separator(this);
+ childBlurred: function () {
+ this.$el.val('').removeClass('active').trigger('blur');
+ this.autocomplete.close();
},
/**
- * Creates a field for the provided field descriptor item (which comes
- * from fields_view_get)
- *
- * @param {Object} item fields_view_get node for the field
- * @param {Object} field fields_get result for the field
- * @param {Object} [parent]
- * @returns instance.web.search.Field
+ * Call the renderFacets method with the correct arguments.
+ * This is due to the fact that change events are called with two arguments
+ * (model, options) while add, reset and remove events are called with
+ * (collection, model, options) as arguments
*/
- make_field: function (item, field, parent) {
- // M2O combined with selection widget is pointless and broken in search views,
- // but has been used in the past for unsupported hacks -> ignore it
- if (field.type === "many2one" && item.attrs.widget === "selection"){
- item.attrs.widget = undefined;
- }
- var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
- if(obj) {
- return new (obj) (item, field, parent || this);
- } else {
- console.group('Unknown field type ' + field.type);
- console.error('View node', item);
- console.info('View field', field);
- console.info('In view', this);
- console.groupEnd();
- return null;
- }
- },
-
- add_common_inputs: function() {
- // add custom filters to this.inputs
- this.custom_filters = new instance.web.search.CustomFilters(this);
- // add Filters to this.inputs, need view.controls filled
- (new instance.web.search.Filters(this));
- (new instance.web.search.SaveFilter(this, this.custom_filters));
- // add Advanced to this.inputs
- (new instance.web.search.Advanced(this));
+ renderChangedFacets: function (model, options) {
+ this.renderFacets(undefined, model, options);
},
-
});
/**
self.$('.graph_options_selection label').last().toggle(result);
});
+ this.$buttons.find('button').tooltip();
+
- return this.model.call('fields_get', []).then(function (f) {
+ return this.model.call('fields_get', {
+ context: this.graph_view.dataset.context
+ }).then(function (f) {
self.fields = f;
self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')};
self.groupby_fields = self.get_groupby_fields();
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
<div>
- <field name="website_id" invisible="True" on_change="on_change_website_id(website_id)"/>
+ <field name="website_id" on_change="on_change_website_id(website_id)"/>
<group string="Domain">
+ <field name="website_name" />
<label for="google_analytics_key"/>
<div name="google_analytics_key">
<div>
self.pool['mail.message'].write(cr, SUPERUSER_ID, msg_ids, {'path': new_attribute}, context=context)
return content
- def create_history(self, cr, uid, ids, vals, context=None):
- for i in ids:
- history = self.pool.get('blog.post.history')
- if vals.get('content'):
- res = {
- 'content': vals.get('content', ''),
- 'post_id': i,
- }
- history.create(cr, uid, res)
-
+ def _check_for_publication(self, cr, uid, ids, vals, context=None):
+ if vals.get('website_published'):
+ base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
+ for post in self.browse(cr, uid, ids, context=context):
+ post.blog_id.message_post(
+ body='<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>' % {
+ 'post_publication': _('A new post %s has been published on the %s blog.') % (post.name, post.blog_id.name),
+ 'post_link': _('Click here to access the post.'),
+ 'base_url': base_url,
+ 'blog_slug': slug(post.blog_id),
+ 'post_slug': slug(post),
+ },
+ subtype='website_blog.mt_blog_blog_published',
+ context=context)
+ return True
+ return False
+
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
create_context = dict(context, mail_create_nolog=True)
post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context)
- self.create_history(cr, uid, [post_id], vals, context)
+ self._check_for_publication(cr, uid, [post_id], vals, context=context)
return post_id
def write(self, cr, uid, ids, vals, context=None):
if 'content' in vals:
vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
result = super(BlogPost, self).write(cr, uid, ids, vals, context)
- self.create_history(cr, uid, ids, vals, context)
+ self._check_for_publication(cr, uid, ids, vals, context=context)
return result
-
-class BlogPostHistory(osv.Model):
- _name = "blog.post.history"
- _description = "Blog Post History"
- _order = 'id DESC'
- _rec_name = "create_date"
-
- _columns = {
- 'post_id': fields.many2one('blog.post', 'Blog Post'),
- 'summary': fields.char('Summary', select=True),
- 'content': fields.text("Content"),
- 'create_date': fields.datetime("Date"),
- 'create_uid': fields.many2one('res.users', "Modified By"),
- }
-
- def getDiff(self, cr, uid, v1, v2, context=None):
- history_pool = self.pool.get('blog.post.history')
- text1 = history_pool.read(cr, uid, [v1], ['content'])[0]['content']
- text2 = history_pool.read(cr, uid, [v2], ['content'])[0]['content']
- line1 = line2 = ''
- if text1:
- line1 = text1.splitlines(1)
- if text2:
- line2 = text2.splitlines(1)
- if (not line1 and not line2) or (line1 == line2):
- raise osv.except_osv(_('Warning!'), _('There are no changes in revisions.'))
- diff = difflib.HtmlDiff()
- return diff.make_table(line1, line2, "Revision-%s" % (v1), "Revision-%s" % (v2), context=True)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r
blog_blog_all,blog.blog,model_blog_blog,,1,0,0,0\r
+ blog_blog,blog.blog,model_blog_blog,base.group_document_user,1,1,1,1\r
blog_post_all,blog.post,model_blog_post,,1,0,0,0\r
blog_post,blog.post,model_blog_post,base.group_document_user,1,1,1,1\r
-blog_post_history,blog.post.history,model_blog_post_history,base.group_document_user,1,0,1,0\r
blog_tag,blog.tag,model_blog_tag,,1,0,0,0\r
blog_tag_edition,blog.tag,model_blog_tag,base.group_document_user,1,1,1,1\r
</record>
<menuitem id="menu_blog" parent="menu_wiki" name="Blogs" action="action_blog_blog" sequence="20"/>
- <!-- History Tree view -->
- <record model="ir.ui.view" id="view_blog_history_tree">
- <field name="name">blog.post.history.tree</field>
- <field name="model">blog.post.history</field>
- <field name="arch" type="xml">
- <tree string="Document History">
- <field name="create_date"/>
- <field name="create_uid"/>
- <field name="post_id"/>
- </tree>
- </field>
- </record>
- <!-- History Form view -->
- <record model="ir.ui.view" id="view_blog_history_form">
- <field name="name">blog.post.history.form</field>
- <field name="model">blog.post.history</field>
- <field name="arch" type="xml">
- <form string="Blog Post History">
- <label for="post_id" class="oe_edit_only"/>
- <h1><field name="post_id" select="1" /></h1>
- <label for="create_date" class="oe_edit_only"/>
- <field name="create_date" readonly="1"/>
- </form>
- </field>
- </record>
- <!-- History Action -->
- <record model="ir.actions.act_window" id="action_history">
- <field name="name">Page history</field>
- <field name="res_model">blog.post.history</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form</field>
- </record>
- <menuitem id="menu_page_history" parent="menu_wiki" name="Pages history" action="action_history" sequence="30" groups="base.group_no_one"/>
- <act_window
- id="action_related_page_history"
- context="{'search_default_post_id': [active_id], 'default_post_id': active_id}"
- domain="[('post_id','=',active_id)]"
- name="Page History"
- res_model="blog.post.history"
- src_model="blog.post"/>
-
+ <record model="ir.ui.view" id="blog_tag_tree">
+ <field name="name">blog_tag_tree</field>
+ <field name="model">blog.tag</field>
+ <field name="arch" type="xml">
+ <tree string="Tag List" create="false">
+ <field name="name"/>
+ <field name="post_ids"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="blog_tag_form">
+ <field name="name">blog_tag_form</field>
+ <field name="model">blog.tag</field>
+ <field name="arch" type="xml">
+ <form string="Tag Form">
+ <sheet>
+ <group>
+ <field name="name"/>
+ </group>
+ <label for="post_ids" string="Used in: "/>
+ <field name="post_ids"/>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_tags">
+ <field name="name">Blog Tags</field>
+ <field name="res_model">blog.tag</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form,graph</field>
+ <field name="view_id" ref="blog_tag_tree"/>
+ </record>
+
+ <menuitem id="menu_blog_tag" parent="menu_wiki" name="Blog Tags" action="action_tags" sequence="40" />
</data>
</openerp>
@http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
def open_user(self, forum, user_id=0, **post):
- cr, uid, context = request.cr, request.uid, request.context
- User = request.registry['res.users']
- Post = request.registry['forum.post']
- Vote = request.registry['forum.post.vote']
- Activity = request.registry['mail.message']
- Followers = request.registry['mail.followers']
- Data = request.registry["ir.model.data"]
+ User = request.env['res.users']
+ Post = request.env['forum.post']
+ Vote = request.env['forum.post.vote']
+ Activity = request.env['mail.message']
+ Followers = request.env['mail.followers']
+ Data = request.env["ir.model.data"]
- user = User.browse(cr, SUPERUSER_ID, user_id, context=context)
- current_user = User.browse(cr, SUPERUSER_ID, uid, context=context)
+ user = User.sudo().search([('id', '=', user_id)])
++ current_user = request.env.user.sudo()
+ if not user or user.karma < 1:
+
+ # Users with high karma can see users with karma <= 0 for
+ # moderation purposes, IFF they have posted something (see below)
- if (not user.exists() or
++ if (not user or
+ (user.karma < 1 and current_user.karma < forum.karma_unlink_all)):
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
values = self._prepare_forum_values(forum=forum, **post)
- if user_id != request.session.uid and not user.website_published:
- return request.website.render("website_forum.private_profile", values)
+
# questions and answers by user
- user_questions, user_answers = [], []
- user_question_ids = Post.search(cr, uid, [
- ('parent_id', '=', False),
- ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
- ], order='create_date desc', context=context)
+ user_question_ids = Post.search([
+ ('parent_id', '=', False),
+ ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
+ order='create_date desc')
count_user_questions = len(user_question_ids)
+
+ if (user_id != request.session.uid and not
+ (user.website_published or
+ (count_user_questions and current_user.karma > forum.karma_unlink_all))):
+ return request.website.render("website_forum.private_profile", values)
+
# displaying only the 20 most recent questions
- user_questions = Post.browse(cr, uid, user_question_ids[:20], context=context)
+ user_questions = user_question_ids[:20]
- user_answer_ids = Post.search(cr, uid, [
- ('parent_id', '!=', False),
- ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
- ], order='create_date desc', context=context)
+ user_answer_ids = Post.search([
+ ('parent_id', '!=', False),
+ ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
+ order='create_date desc')
count_user_answers = len(user_answer_ids)
# displaying only the 20 most recent answers
- user_answers = Post.browse(cr, uid, user_answer_ids[:20], context=context)
+ user_answers = user_answer_ids[:20]
# showing questions which user following
- obj_ids = Followers.search(cr, SUPERUSER_ID, [('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)], context=context)
- post_ids = [follower.res_id for follower in Followers.browse(cr, SUPERUSER_ID, obj_ids, context=context)]
- que_ids = Post.search(cr, uid, [('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
- followed = Post.browse(cr, uid, que_ids, context=context)
+ post_ids = [follower.res_id for follower in Followers.sudo().search([('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)])]
+ followed = Post.search([('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
- #showing Favourite questions of user.
- fav_que_ids = Post.search(cr, uid, [('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
- favourite = Post.browse(cr, uid, fav_que_ids, context=context)
+ # showing Favourite questions of user.
+ favourite = Post.search([('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
- #votes which given on users questions and answers.
- data = Vote.read_group(cr, uid, [('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"], context=context)
+ # votes which given on users questions and answers.
+ data = Vote.read_group([('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"])
up_votes, down_votes = 0, 0
for rec in data:
if rec['vote'] == '1':
return f.read()
return False
- _defaults = {
- 'description': 'This community is for professionals and enthusiasts of our products and services.',
- 'faq': _get_default_faq,
- 'karma_gen_question_new': 0, # set to null for anti spam protection
- 'karma_gen_question_upvote': 5,
- 'karma_gen_question_downvote': -2,
- 'karma_gen_answer_upvote': 10,
- 'karma_gen_answer_downvote': -2,
- 'karma_gen_answer_accept': 2,
- 'karma_gen_answer_accepted': 15,
- 'karma_gen_answer_flagged': -100,
- 'karma_ask': 3, # set to not null for anti spam protection
- 'karma_answer': 3, # set to not null for anti spam protection
- 'karma_edit_own': 1,
- 'karma_edit_all': 300,
- 'karma_close_own': 100,
- 'karma_close_all': 500,
- 'karma_unlink_own': 500,
- 'karma_unlink_all': 1000,
- 'karma_upvote': 5,
- 'karma_downvote': 50,
- 'karma_answer_accept_own': 20,
- 'karma_answer_accept_all': 500,
- 'karma_editor_link_files': 20,
- 'karma_editor_clickable_link': 20,
- 'karma_comment_own': 3,
- 'karma_comment_all': 5,
- 'karma_comment_convert_own': 50,
- 'karma_comment_convert_all': 500,
- 'karma_comment_unlink_own': 50,
- 'karma_comment_unlink_all': 500,
- 'karma_retag': 75,
- 'karma_flag': 500,
- }
-
- def create(self, cr, uid, values, context=None):
- if context is None:
- context = {}
- create_context = dict(context, mail_create_nolog=True)
- return super(Forum, self).create(cr, uid, values, context=create_context)
-
- def _tag_to_write_vals(self, cr, uid, ids, tags='', context=None):
- User = self.pool['res.users']
- Tag = self.pool['forum.tag']
- result = {}
- for forum in self.browse(cr, uid, ids, context=context):
- post_tags = []
- existing_keep = []
- for tag in filter(None, tags.split(',')):
- if tag.startswith('_'): # it's a new tag
- # check that not already created meanwhile or maybe excluded by the limit on the search
- tag_ids = Tag.search(cr, uid, [('name', '=', tag[1:])], context=context)
- if tag_ids:
- existing_keep.append(int(tag_ids[0]))
- else:
- # check if user have Karma needed to create need tag
- user = User.browse(cr, uid, uid, context=context)
- if user.exists() and user.karma >= forum.karma_retag:
- post_tags.append((0, 0, {'name': tag[1:], 'forum_id': forum.id}))
+ # description and use
+ name = fields.Char('Forum Name', required=True, translate=True)
+ faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
+ description = fields.Html(
+ 'Description',
+ default='<p> This community is for professionals and enthusiasts of our products and services.'
+ 'Share and discuss the best content and new marketing ideas,'
+ 'build your professional profile and become a better marketer together.</p>')
+ default_order = fields.Selection([
+ ('create_date desc', 'Newest'),
+ ('write_date desc', 'Last Updated'),
+ ('vote_count desc', 'Most Voted'),
+ ('relevancy desc', 'Relevancy'),
+ ('child_count desc', 'Answered')],
+ string='Default Order', required=True, default='write_date desc')
+ relevancy_post_vote = fields.Float('First Relevancy Parameter', default=0.8)
+ relevancy_time_decay = fields.Float('Second Relevancy Parameter', default=1.8)
+ default_post_type = fields.Selection([
+ ('question', 'Question'),
+ ('discussion', 'Discussion'),
+ ('link', 'Link')],
+ string='Default Post', required=True, default='question')
+ allow_question = fields.Boolean('Questions', help="Users can answer only once per question. Contributors can edit answers and mark the right ones.", default=True)
+ allow_discussion = fields.Boolean('Discussions', default=True)
+ allow_link = fields.Boolean('Links', help="When clicking on the post, it redirects to an external link", default=True)
+ # karma generation
+ karma_gen_question_new = fields.Integer(string='Asking a question', default=2)
+ karma_gen_question_upvote = fields.Integer(string='Question upvoted', default=5)
+ karma_gen_question_downvote = fields.Integer(string='Question downvoted', default=-2)
+ karma_gen_answer_upvote = fields.Integer(string='Answer upvoted', default=10)
+ karma_gen_answer_downvote = fields.Integer(string='Answer downvoted', default=-2)
+ karma_gen_answer_accept = fields.Integer(string='Accepting an answer', default=2)
+ karma_gen_answer_accepted = fields.Integer(string='Answer accepted', default=15)
+ karma_gen_answer_flagged = fields.Integer(string='Answer flagged', default=-100)
+ # karma-based actions
+ karma_ask = fields.Integer(string='Ask a new question', default=3)
+ karma_answer = fields.Integer(string='Answer a question', default=3)
+ karma_edit_own = fields.Integer(string='Edit its own posts', default=1)
+ karma_edit_all = fields.Integer(string='Edit all posts', default=300)
+ karma_close_own = fields.Integer(string='Close its own posts', default=100)
+ karma_close_all = fields.Integer(string='Close all posts', default=500)
+ karma_unlink_own = fields.Integer(string='Delete its own posts', default=500)
+ karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
+ karma_upvote = fields.Integer(string='Upvote', default=5)
+ karma_downvote = fields.Integer(string='Downvote', default=50)
+ karma_answer_accept_own = fields.Integer(string='Accept an answer on its own questions', default=20)
+ karma_answer_accept_all = fields.Integer(string='Accept an answers to all questions', default=500)
+ karma_editor_link_files = fields.Integer(string='Linking files (Editor)', default=20)
+ karma_editor_clickable_link = fields.Integer(string='Add clickable links (Editor)', default=20)
+ karma_comment_own = fields.Integer(string='Comment its own posts', default=1)
+ karma_comment_all = fields.Integer(string='Comment all posts', default=1)
+ karma_comment_convert_own = fields.Integer(string='Convert its own answers to comments and vice versa', default=50)
+ karma_comment_convert_all = fields.Integer(string='Convert all answers to answers and vice versa', default=500)
+ karma_comment_unlink_own = fields.Integer(string='Unlink its own comments', default=50)
+ karma_comment_unlink_all = fields.Integer(string='Unlinnk all comments', default=500)
+ karma_retag = fields.Integer(string='Change question tags', default=75)
+ karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
+ karma_dofollow = fields.Integer(string='Disabled links', help='If the author has not enough karma, a nofollow attribute is added to links', default=500)
+
+ @api.model
+ def create(self, values):
+ return super(Forum, self.with_context(mail_create_nolog=True)).create(values)
+
+ @api.model
+ def _tag_to_write_vals(self, tags=''):
+ User = self.env['res.users']
+ Tag = self.env['forum.tag']
+ post_tags = []
++ existing_keep = []
+ for tag in filter(None, tags.split(',')):
+ if tag.startswith('_'): # it's a new tag
+ # check that not arleady created meanwhile or maybe excluded by the limit on the search
+ tag_ids = Tag.search([('name', '=', tag[1:])])
+ if tag_ids:
- post_tags.append((4, int(tag_ids[0])))
++ existing_keep.append(int(tag_ids[0]))
else:
- existing_keep.append(int(tag))
- post_tags.insert(0, [6, 0, existing_keep])
- result[forum.id] = post_tags
-
- return result
+ # check if user have Karma needed to create need tag
+ user = User.sudo().browse(self._uid)
+ if user.exists() and user.karma >= self.karma_retag:
- post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
++ post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
+ else:
- post_tags.append((4, int(tag)))
++ existing_keep.append(int(tag))
++ post_tags.insert(0, [6, 0, existing_keep])
+ return post_tags
-class Post(osv.Model):
+class Post(models.Model):
_name = 'forum.post'
_description = 'Forum Post'
_inherit = ['mail.thread', 'website.seo.metadata']
}
});
}
-
- function IsKarmaValid(eventNumber,minKarma){
- "use strict";
- if(parseInt($("#karma").val()) >= minKarma){
- CKEDITOR.tools.callFunction(eventNumber,this);
- return false;
- } else {
- alert("Sorry you need more than " + minKarma + " Karma.");
- }
- }
-
- //END-TODO Remove in master
-
- if ($('textarea.load_editor').length) {
- var editor = CKEDITOR.instances['content'];
- editor.on('instanceReady', CKEDITORLoadComplete);
- }
-
++
function CKEDITORLoadComplete(){
"use strict";
- $('.cke_button__link').on('click', function() { IsKarmaValid(33,30); });
- $('.cke_button__unlink').on('click', function() { IsKarmaValid(37,30); });
- $('.cke_button__image').on('click', function() { IsKarmaValid(41,30); });
+ $('.cke_button__link').attr('onclick','website_forum_IsKarmaValid(33,30)');
+ $('.cke_button__unlink').attr('onclick','website_forum_IsKarmaValid(37,30)');
+ $('.cke_button__image').attr('onclick','website_forum_IsKarmaValid(41,30)');
}
});
</form>
</template>
-<template id="assets_frontend" inherit_id="website.assets_frontend" name="website_forum assets">
- <xpath expr="." position="inside">
- <link rel='stylesheet' href="/web/static/lib/jquery.textext/jquery.textext.css"/>
- <link rel='stylesheet' href='/website_forum/static/src/css/website_forum.css'/>
- <script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"/>
+ <script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
+ <link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
+ <link rel="stylesheet" href="/website/static/lib/select2-bootstrap-css/select2-bootstrap.css"/>
+ <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
- </xpath>
-</template>
-
-<template id="assets_editor" inherit_id="website.assets_editor" name="Forum Editor" groups="base.group_user">
- <xpath expr="." position="inside">
- <script type="text/javascript" src="/website_forum/static/src/js/website.tour.forum.js"/>
- <script type="text/javascript" src="/website_forum/static/src/js/website_forum.editor.js"/>
- </xpath>
-</template>
-
<!-- Page Index -->
<template id="header" name="Forum Index">
<t t-call="website.layout">
</textarea>
<div t-if="not is_answer">
<br/>
- <input type="hidden" name="tag_type" value="select2"/>
- <input type="hidden" name="question_tag" class="form-control col-md-9 js_select2" placeholder="Tags" value="see data init value" t-attf-data-init-value="#{tags}"/>
+ <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
- <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-value="#{tags}"/>
++ <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-data-init-value="#{tags}"/>
<br/>
</div>
<button class="btn btn-primary btn-lg">Save</button>
</div>
</div>
<div id="email_designer" class="mb32" t-att-style="mode != 'email_designer' and 'display: none' or ''">
- <a class="mt16 btn btn-primary pull-right css_editable_mode_hidden"
+ <a class="mt16 btn btn-primary pull-right" id="save_and_continue"
t-attf-href="/web#return_label=Website&model=#{model}&id=#{res_id}&view_type=form">
- Save and Continue
+ Back to the mass mailing
</a>
<h1 class="page-header mt16">
Design Your Email
reference/qweb
reference/javascript
+ reference/translations
reference/reports
reference/workflows
+ reference/cdn
('kanban', 'Kanban'),
('search','Search'),
('qweb', 'QWeb')], string='View Type'),
- 'arch': fields.text('View Architecture', required=True),
+ 'arch': fields.function(_arch_get, fnct_inv=_arch_set, string='View Architecture', type="text", nodrop=True),
+ 'arch_db': fields.text('Arch Blob'),
+ 'arch_fs': fields.char('Arch Filename'),
- 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True),
+ 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='restrict', select=True),
'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'),
'field_parent': fields.char('Child Field'),
'model_data_id': fields.function(_get_model_data, type='many2one', relation='ir.model.data', string="Model Data",
<group>
<field name="ref"/>
<field name="lang"/>
- <field name="date"/>
</group>
<group>
- <field name="company_id" groups="base.group_multi_company" widget="selection"/>
- <field name="active"/>
++ <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
+ <field name="active"/>
</group>
</group>
+ <group name="container_row_2">
+ <group string="Sale" name="sale">
+ <field name="customer"/>
+ <field name="user_id"
+ context="{'default_groups_ref': ['base.group_partner_manager']}"/>
+ </group>
+ <group string="Purchase" name="purchase">
+ <field name="supplier"/>
+ </group>
+ </group>
+ <group name="container_row_3">
+ <group name="container_left"/>
+ <group name="container_right"/>
+ </group>
</page>
</notebook>
</sheet>