[MERGE] Merge with addons

bzr revid: jar@tinyerp.com-20140423114210-dr026gp4321ll88n
This commit is contained in:
Jaydeep Barot 2014-04-23 17:12:10 +05:30
commit 7258e428b9
52 changed files with 746 additions and 232 deletions

View File

@ -1636,6 +1636,7 @@
<field name="name"/>
<field name="active"/>
</group>
<label for="note"/>
<field name="note" placeholder="Payment term explanation for the customer..."/>
<separator string="Computation"/>
<field name="line_ids"/>
@ -2451,7 +2452,7 @@
<field name="sign"/>
<field name="style_overwrite"/>
</group>
<notebook>
<notebook attrs="{'invisible': [('type','not in',['accounts','account_type'])]}">
<page string="Report">
<group>
<field name="display_detail" attrs="{'invisible': [('type','not in',['accounts','account_type'])]}"/>

View File

@ -29,7 +29,7 @@ Module to automate letters for unpaid invoices, with multi-level recalls.
You can define your multiple levels of recall through the menu:
---------------------------------------------------------------
Configuration / Follow-Up Levels
Configuration / Follow-up / Follow-up Levels
Once it is defined, you can automatically print recalls every day through simply clicking on the menu:
------------------------------------------------------------------------------------------------------
@ -55,6 +55,7 @@ Note that if you want to check the follow-up level for a given partner/account e
'account_followup_view.xml',
'account_followup_customers.xml',
'wizard/account_followup_print_view.xml',
'res_config_view.xml',
'views/report_followup.xml',
'account_followup_reports.xml'
],

View File

@ -30,7 +30,7 @@ class followup(osv.osv):
_columns = {
'followup_line': fields.one2many('account_followup.followup.line', 'followup_id', 'Follow-up'),
'company_id': fields.many2one('res.company', 'Company', required=True),
'name': fields.related('company_id', 'name', string = "Name"),
'name': fields.related('company_id', 'name', string = "Name", readonly=True, type="char"),
}
_defaults = {
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account_followup.followup', context=c),
@ -479,4 +479,20 @@ class res_partner(osv.osv):
fnct_search=_payment_earliest_date_search),
}
class account_config_settings(osv.TransientModel):
_name = 'account.config.settings'
_inherit = 'account.config.settings'
def open_followup_level_form(self, cr, uid, ids, context=None):
res_ids = self.pool.get('account_followup.followup').search(cr, uid, [], context=context)
return {
'type': 'ir.actions.act_window',
'name': 'Payment Follow-ups',
'res_model': 'account_followup.followup',
'res_id': res_ids and res_ids[0] or False,
'view_mode': 'form,tree',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -50,11 +50,11 @@
<filter string="No Responsible" domain="[('payment_responsible_id', '=', False)]"/>
<filter string="My Follow-ups" domain="[('payment_responsible_id','=', uid)]" name="my"/>
</group>
<group expand="1" string="Group By...">
<filter string="Follow-up Responsible" context="{'group_by':'payment_responsible_id'}"/>
<filter string="Followup Level" context="{'group_by':'latest_followup_level_id'}"/>
</group>
</search>
<group string="Group By..." position="inside">
<filter string="Follow-up Responsible" context="{'group_by':'payment_responsible_id'}"/>
<filter string="Followup Level" context="{'group_by':'latest_followup_level_id'}"/>
</group>
</field>
</record>

View File

@ -71,7 +71,9 @@
<field name="model">account_followup.followup</field>
<field name="arch" type="xml">
<form string="Follow-up" version="7.0">
<h1><field name="company_id" widget="selection" class="oe_inline"/></h1>
<h1><field name="name"/></h1>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" widget="selection" class="oe_inline" groups="base.group_multi_company"/>
<p class="oe_grey">
To remind customers of paying their invoices, you can
define different actions depending on how severely
@ -113,6 +115,7 @@
<field name="res_model">account_followup.followup</field>
<field name="search_view_id" ref="view_account_followup_filter"/>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define follow-up levels and their related actions.
@ -130,8 +133,11 @@
parent="menu_finance_followup"
sequence="0"
id="menu_manual_reconcile_followup"/>
<menuitem id="account_followup_main_menu" parent="account.menu_finance_configuration" name="Follow-up"/>
<menuitem action="action_account_followup_definition_form" id="account_followup_menu" parent="account.menu_finance_configuration" name="Follow-up Levels"/>
<menuitem action="action_account_followup_definition_form" id="account_followup_menu" parent="account_followup_main_menu" name="Follow-up Levels"/>
<record id="account_move_line_partner_tree" model="ir.ui.view">
<field name="name">account.move.line.partner.tree</field>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_account_config_settings_inherit" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml">
<xpath expr="//label[@for='module_account_followup']" position="after">
<button type="object" name="open_followup_level_form" string="Configure your follow-up levels" class="oe_link"/>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -153,7 +153,7 @@
<field name="user_id"/>
<field name="note"/>
<field name="code"/>
<filter name="personal" string="My Salesteams" domain="['|', ('member_ids', '=', uid), ('user_id', '=', uid)]"/>
<filter name="personal" string="My Sales Teams" domain="['|', ('member_ids', '=', uid), ('user_id', '=', uid)]"/>
<group expand="0" string="Group By...">
<filter string="Team Leader" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Parent Sales Teams" domain="[]" context="{'group_by':'parent_id'}"/>

View File

@ -361,8 +361,8 @@
<filter string="Last Post (weekly)" name="group_message_last_post" domain="[]" context="{'group_by':'message_last_post:week'}"/>
</group>
<group string="Display">
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team" groups="base.group_multi_salesteams"/>
<filter string="Countries" context="{'invisible_country': False}" help="Countries"/>
<filter string="Sales Team" context="{'invisible_section': False}" domain="[]" help="Sales Team" groups="base.group_multi_salesteams"/>
</group>
</search>
</field>
@ -581,8 +581,8 @@
<filter string="Last Post (weekly)" name="group_message_last_post" domain="[]" context="{'group_by':'message_last_post:week'}"/>
</group>
<group string="Display">
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team" groups="base.group_multi_salesteams"/>
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
<filter string="Sales Team" context="{'invisible_section': False}" domain="[]" help="Sales Team" groups="base.group_multi_salesteams"/>
<filter string="Countries" context="{'invisible_country': False}" help="Countries"/>
</group>
</search>
</field>

View File

@ -216,7 +216,7 @@ class delivery_grid(osv.osv):
ok = True
break
if not ok:
raise osv.except_osv(_('No price available!'), _('No line matched this product or order in the chosen delivery grid.'))
raise osv.except_osv(_("Unable to fetch delivery method!"), _("Selected product in the delivery method doesn't fulfill any of the delivery grid(s) criteria."))
return price

View File

@ -43,9 +43,8 @@
<field name="free_if_more_than" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
<field name="amount" attrs="{'required':[('free_if_more_than','&lt;&gt;',False)], 'invisible':[('free_if_more_than','=',False)]}"/>
</div>
<field name="use_detailed_pricelist"/>
</group>
<newline/>
<field name="use_detailed_pricelist"/>
</group>
<field name="pricelist_ids" attrs="{'invisible':[('use_detailed_pricelist','=',False)]}" mode="tree">
<tree string="Delivery grids">
@ -142,7 +141,7 @@
</page>
<page string="Destination">
<group string="Countries">
<field name="country_ids"/>
<field name="country_ids" nolabel="1"/>
</group>
<group string="States">
<field colspan="2" name="state_ids" nolabel="1"/>

View File

@ -164,7 +164,7 @@
<field name="period">once</field>
<field name="visibility_mode">personal</field>
<field name="report_message_frequency">never</field>
<field name="user_domain">[('groups_id', 'in', ref('base.group_user'))]</field>
<field name="user_domain" eval="[('groups_id', '=', ref('base.group_user'))]" />
<field name="state">inprogress</field>
<field name="category">other</field>
</record>
@ -174,7 +174,7 @@
<field name="period">once</field>
<field name="visibility_mode">personal</field>
<field name="report_message_frequency">never</field>
<field name="user_domain">[('groups_id', 'in', ref('base.user_root'))]</field>
<field name="user_domain" eval="[('groups_id', '=', ref('base.group_erp_manager'))]" />
<field name="state">inprogress</field>
<field name="category">other</field>
</record>

View File

@ -21,14 +21,13 @@
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from datetime import date, datetime, timedelta
import calendar
import logging
import functools
_logger = logging.getLogger(__name__)
# display top 3 in ranking, could be db variable
@ -117,12 +116,6 @@ class gamification_challenge(osv.Model):
except ValueError:
return False
def _get_challenger_users(self, cr, uid, domain, context=None):
ref = functools.partial(self.pool['ir.model.data'].xmlid_to_res_id, cr, uid)
user_domain = eval(domain, {'ref': ref})
return self.pool['res.users'].search(cr, uid, user_domain, context=context)
_order = 'end_date, start_date, name, id'
_columns = {
'name': fields.char('Challenge Name', required=True, translate=True),
@ -218,7 +211,6 @@ class gamification_challenge(osv.Model):
def create(self, cr, uid, vals, context=None):
"""Overwrite the create method to add the user of groups"""
# add users when change the group auto-subscription
if vals.get('user_domain'):
user_ids = self._get_challenger_users(cr, uid, vals.get('user_domain'), context=context)
@ -240,14 +232,18 @@ class gamification_challenge(osv.Model):
if isinstance(ids, (int,long)):
ids = [ids]
if vals.get('state') == 'inprogress':
for challenge in self.browse(cr, uid, ids, context=context):
user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context)
write_op = [(4, user_id) for user_id in user_ids]
self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context)
self.message_subscribe_users(cr, uid, [challenge.id], user_ids, context=context)
if vals.get('user_domain'):
user_ids = self._get_challenger_users(cr, uid, vals.get('user_domain'), context=context)
self.generate_goals_from_challenge(cr, uid, ids, context=context)
if not vals.get('user_ids'):
vals['user_ids'] = []
vals['user_ids'] += [(4, user_id) for user_id in user_ids]
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
if vals.get('state') == 'inprogress':
self._recompute_challenge_users(cr, uid, ids, context=context)
self._generate_goals_from_challenge(cr, uid, ids, context=context)
elif vals.get('state') == 'done':
self.check_challenge_reward(cr, uid, ids, force=True, context=context)
@ -256,9 +252,6 @@ class gamification_challenge(osv.Model):
# resetting progress
if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', '=', 'inprogress')], context=context):
raise osv.except_osv("Error", "You can not reset a challenge with unfinished goals.")
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
return write_res
@ -314,18 +307,10 @@ class gamification_challenge(osv.Model):
# update every running goal already generated linked to selected challenges
goal_obj.update(cr, uid, goal_ids, context=context)
self._recompute_challenge_users(cr, uid, ids, context=context)
self._generate_goals_from_challenge(cr, uid, ids, context=context)
for challenge in self.browse(cr, uid, ids, context=context):
# in case of new users matching the domain
old_user_ids = [user.id for user in challenge.user_ids]
new_user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context)
to_remove_ids = list(set(old_user_ids) - set(new_user_ids))
to_add_ids = list(set(new_user_ids) - set(old_user_ids))
write_op = [(3, user_id) for user_id in to_remove_ids]
write_op += [(4, user_id) for user_id in to_add_ids]
self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context)
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
# goals closed but still opened at the last report date
closed_goals_to_report = goal_obj.search(cr, uid, [
@ -345,11 +330,37 @@ class gamification_challenge(osv.Model):
return True
def quick_update(self, cr, uid, challenge_id, context=None):
"""Update all the goals of a challenge, no generation of new goals"""
"""Update all the goals of a specific challenge, no generation of new goals"""
goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context)
self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context)
return True
def _get_challenger_users(self, cr, uid, domain, context=None):
user_domain = eval(ustr(domain))
return self.pool['res.users'].search(cr, uid, user_domain, context=context)
def _recompute_challenge_users(self, cr, uid, challenge_ids, context=None):
"""Recompute the domain to add new users and remove the one no longer matching the domain"""
for challenge in self.browse(cr, uid, challenge_ids, context=context):
if challenge.user_domain:
old_user_ids = [user.id for user in challenge.user_ids]
new_user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context)
to_remove_ids = list(set(old_user_ids) - set(new_user_ids))
to_add_ids = list(set(new_user_ids) - set(old_user_ids))
write_op = [(3, user_id) for user_id in to_remove_ids]
write_op += [(4, user_id) for user_id in to_add_ids]
if write_op:
self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context)
if to_remove_ids:
self.message_unsubscribe_users(cr, uid, [challenge.id], to_remove_ids, context=None)
if to_add_ids:
self.message_subscribe_users(cr, uid, [challenge.id], to_add_ids, context=context)
return True
def action_check(self, cr, uid, ids, context=None):
"""Check a challenge
@ -370,12 +381,17 @@ class gamification_challenge(osv.Model):
##### Automatic actions #####
def generate_goals_from_challenge(self, cr, uid, ids, context=None):
_logger.warning("Deprecated, use private method _generate_goals_from_challenge(...) instead.")
return self._generate_goals_from_challenge(cr, uid, ids, context=context)
def _generate_goals_from_challenge(self, cr, uid, ids, context=None):
"""Generate the goals for each line and user.
If goals already exist for this line and user, the line is skipped. This
can be called after each change in the list of users or lines.
:param list(int) ids: the list of challenge concerned"""
goal_obj = self.pool.get('gamification.goal')
to_update = []
for challenge in self.browse(cr, uid, ids, context=context):
(start_date, end_date) = start_end_date_for_period(challenge.period)
@ -387,45 +403,49 @@ class gamification_challenge(osv.Model):
end_date = challenge.end_date
for line in challenge.line_ids:
# FIXME: allow to restrict to a subset of users
for user in challenge.user_ids:
goal_obj = self.pool.get('gamification.goal')
domain = [('line_id', '=', line.id), ('user_id', '=', user.id)]
if start_date:
domain.append(('start_date', '=', start_date))
# there is potentially a lot of users
# detect the ones with no goal linked to this line
date_clause = ""
query_params = [line.id]
if start_date:
date_clause += "AND g.start_date = %s"
query_params.append(start_date)
if end_date:
date_clause += "AND g.end_date = %s"
query_params.append(end_date)
query = """SELECT u.id AS user_id
FROM res_users u
LEFT JOIN gamification_goal g
ON (u.id = g.user_id)
WHERE line_id = %s
{date_clause}
""".format(date_clause=date_clause)
# goal already existing for this line ?
if len(goal_obj.search(cr, uid, domain, context=context)) > 0:
cr.execute(query, query_params)
user_with_goal_ids = cr.dictfetchall()
user_without_goal_ids = list(set([user.id for user in challenge.user_ids]) - set([user['user_id'] for user in user_with_goal_ids]))
# resume canceled goals
domain.append(('state', '=', 'canceled'))
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
if canceled_goal_ids:
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
to_update.extend(canceled_goal_ids)
values = {
'definition_id': line.definition_id.id,
'line_id': line.id,
'target_goal': line.target_goal,
'state': 'inprogress',
}
# skip to next user
continue
if start_date:
values['start_date'] = start_date
if end_date:
values['end_date'] = end_date
values = {
'definition_id': line.definition_id.id,
'line_id': line.id,
'user_id': user.id,
'target_goal': line.target_goal,
'state': 'inprogress',
}
if challenge.remind_update_delay:
values['remind_update_delay'] = challenge.remind_update_delay
if start_date:
values['start_date'] = start_date
if end_date:
values['end_date'] = end_date
if challenge.remind_update_delay:
values['remind_update_delay'] = challenge.remind_update_delay
new_goal_id = goal_obj.create(cr, uid, values, context=context)
to_update.append(new_goal_id)
for user_id in user_without_goal_ids:
values.update({'user_id': user_id})
goal_id = goal_obj.create(cr, uid, values, context=context)
to_update.append(goal_id)
goal_obj.update(cr, uid, to_update, context=context)
@ -638,7 +658,7 @@ class gamification_challenge(osv.Model):
message = "%s has joined the challenge" % user.name
self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': [(3, user_id)], 'user_ids': [(4, user_id)]}, context=context)
return self.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
return self._generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
# TODO in trunk, remove unused parameter user_id
def discard_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):

View File

@ -19,7 +19,6 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import osv
from challenge import MAX_VISIBILITY_RANKING

View File

@ -48,6 +48,7 @@
<label for="user_domain" class="oe_edit_only" string="Assign Challenge To"/>
<div>
<field name="user_domain" widget="char_domain" options="{'model': 'res.users'}" />
<field name="user_ids" groups="base.group_no_one" widget="many2many_tags" />
</div>
</div>

View File

@ -130,7 +130,7 @@
<field name="name">Monthly Sales Targets</field>
<field name="period">monthly</field>
<field name="visibility_mode">ranking</field>
<field name="user_domain">[('groups_id', 'in', ref('base.group_sale_salesman'))]</field>
<field name="user_domain" eval="[('groups_id', 'in', ref('base.group_sale_salesman'))]" />
<field name="report_message_frequency">weekly</field>
</record>
@ -138,7 +138,7 @@
<field name="name">Lead Acquisition</field>
<field name="period">monthly</field>
<field name="visibility_mode">ranking</field>
<field name="user_domain">[('groups_id', 'in', ref('base.group_sale_salesman'))]</field>
<field name="user_domain" eval="[('groups_id', 'in', ref('base.group_sale_salesman'))]" />
<field name="report_message_frequency">weekly</field>
</record>

View File

@ -203,7 +203,7 @@
</record>
<record id="hr_expense_product" model="ir.actions.act_window">
<field name="name">Products</field>
<field name="name">Expense Categories</field>
<field name="res_model">product.product</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>

View File

@ -500,11 +500,15 @@
<field name="name">hr.contribution.register.form</field>
<field name="model">hr.contribution.register</field>
<field name="arch" type="xml">
<form string="Contribution">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<separator string="Description"/>
<field name="note" nolabel="1"/>
<form string="Contribution" version="7.0">
<group>
<field name="name"/>
<field name="company_id" groups="base.group_multi_company" widget="selection" class="oe_inline"/>
</group>
<newline/>
<group>
<separator string="Description"/><newline/>
<field name="note" nolabel="1"/></group>
</form>
</field>
</record>

View File

@ -65,7 +65,7 @@ class hr_timesheet_sheet(osv.osv):
def create(self, cr, uid, vals, *args, **argv):
if 'employee_id' in vals:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link him/her to a user.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product, like \'Consultant\'.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
@ -76,7 +76,7 @@ class hr_timesheet_sheet(osv.osv):
if 'employee_id' in vals:
new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id.id or False
if not new_user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link him/her to a user.'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:

View File

@ -21,12 +21,12 @@
<field name="model">res.partner.bank</field>
<field name="inherit_id" ref="base.view_partner_bank_form" />
<field name="arch" type="xml">
<field name="acc_number" position="before">
<field name="bank_code" />
<field name="office" />
<field name="acc_number" position="after">
<field name="bank_code" attrs="{'invisible':[('state','!=','rib')]}"/>
<field name="office" attrs="{'invisible':[('state','!=','rib')]}"/>
<newline />
<field name="rib_acc_number" />
<field name="key" />
<field name="rib_acc_number" attrs="{'invisible':[('state','!=','rib')]}"/>
<field name="key" attrs="{'invisible':[('state','!=','rib')]}"/>
<newline />
</field>
</field>

View File

@ -427,10 +427,14 @@
</header>
<sheet>
<group>
<field name='name'/>
<field name='category_id'/>
<field name='supplier'/>
<field name='price'/>
<group>
<field name='name'/>
<field name='category_id'/>
</group>
<group>
<field name='supplier'/>
<field name='price'/>
</group>
</group>
<label for='description'/>
<field name='description'/>
@ -461,7 +465,7 @@
<sheet>
<group string="Schedule Date">
<group>
<field name="alter_type"/>
<field name="alter_type" class="oe_inline"/>
<field name="specific_day" attrs="{'invisible': [('alter_type','!=','specific')], 'required':[('alter_type','=','specific')]}"/>
</group>
</group>

View File

@ -25,16 +25,14 @@ class TestMassMailing(osv.TransientModel):
test_emails = tools.email_split(wizard.email_to)
mail_ids = []
for test_mail in test_emails:
body = mailing.body_html
unsubscribe_url = self.pool['mail.mass_mailing'].get_unsubscribe_url(cr, uid, mailing.id, 0, email=test_mail, context=context)
body = tools.append_content_to_html(body, unsubscribe_url, plaintext=False, container_tag='p')
mail_values = {
'email_from': mailing.email_from,
'reply_to': mailing.reply_to,
'email_to': test_mail,
'subject': mailing.name,
'body_html': body,
'body_html': mailing.body_html,
'auto_delete': True,
'mailing_id': wizard.mass_mailing_id.id,
}
mail_ids.append(Mail.create(cr, uid, mail_values, context=context))
Mail.send(cr, uid, mail_ids, context=context)

View File

@ -12,15 +12,23 @@
<field name="model">product.pricelist.version</field>
<field name="arch" type="xml">
<form string="Pricelist Version" version="7.0">
<group col="4">
<field name="name"/>
<field name="active"/>
<field colspan="4" name="pricelist_id"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<group>
<group>
<field name="name"/>
<field name="pricelist_id"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<group>
<field name="date_start"/>
<field name="date_end"/>
<field name="active"/>
</group>
</group>
<field name="items_id"/>
<notebook>
<page string="Item List">
<field name="items_id"/>
</page>
</notebook>
</form>
</field>
</record>

View File

@ -585,10 +585,12 @@
<field name="model">product.ul</field>
<field name="arch" type="xml">
<form string="Packaging" version="7.0">
<sheet>
<group>
<field name="name"/>
<field name="type"/>
</group>
</sheet>
</form>
</field>
</record>

View File

@ -106,8 +106,8 @@
<separator/>
<filter string="No Timebox" domain="[('timebox_id', '=', False)]" help="Tasks having no timebox assigned yet"/>
<group expand="0" string="Display">
<filter string="Show Context" name="context_show" context="{'context_show': True}" domain="[]" icon="terp-camera_test" help="Show the context field"/>
<filter string="Show Deadlines" context="{'deadline_visible': False}" domain="[]" help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
<filter string="Context" name="context_show" context="{'context_show': True}" domain="[]" icon="terp-camera_test" help="Show the context field"/>
<filter string="Deadlines" context="{'deadline_visible': False}" domain="[]" help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
</group>
<group expand="0" string="Group By...">
<filter string="Stage" name="group_stage_id" context="{'group_by':'stage_id'}"/>

View File

@ -468,7 +468,7 @@ class purchase_order(osv.osv):
if not acc_id:
acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
if not acc_id:
raise osv.except_osv(_('Error!'), _('Define expense account for this company: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
raise osv.except_osv(_('Error!'), _('Define an expense account for this product: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
else:
acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context=context).id
fpos = po_line.order_id.fiscal_position or False
@ -590,7 +590,7 @@ class purchase_order(osv.osv):
if inv and inv.state not in ('cancel','draft'):
raise osv.except_osv(
_('Unable to cancel this purchase order.'),
_('You must first cancel all receptions related to this purchase order.'))
_('You must first cancel all invoices related to this purchase order.'))
self.pool.get('account.invoice') \
.signal_invoice_cancel(cr, uid, map(attrgetter('id'), purchase.invoice_ids))
self.write(cr,uid,ids,{'state':'cancel'})

View File

@ -37,15 +37,19 @@
<field name="state" widget="statusbar" statusbar_visible="draft,in_progress,done" statusbar_colors='{"in_progress":"blue"}'/>
</header>
<sheet>
<div class="oe_edit_only">
<label for="name" class="oe_inline"/>
<label for="origin" class="oe_inline"/>
<div class="pull-left">
<label for="name" class="oe_edit_only oe_inline"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
</div>
<div class="pull-left">
<label for="origin" class="oe_edit_only oe_inline"/>
<h1>
<label string="," attrs="{'invisible':[('origin','=',False)]}"/>
<field name="origin" class="oe_inline" placeholder="e.g. PO0025"/>
</h1>
</div>
<h1>
<field name="name" class="oe_inline"/>
<label string="," attrs="{'invisible':[('origin','=',False)]}"/>
<field name="origin" class="oe_inline" placeholder="e.g. PO0025"/>
</h1>
<group>
<group>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'purchase_requisition.group_purchase_requisition_user', 'purchase.group_purchase_user', 'account.group_account_invoice']}"/>

View File

@ -36,7 +36,7 @@ class purchase_requisition_partner(osv.osv_memory):
record_id = context and context.get('active_id', False) or False
tender = self.pool.get('purchase.requisition').browse(cr, uid, record_id, context=context)
if not tender.line_ids:
raise osv.except_osv(_('Error!'), _('No Product in Tender.'))
raise osv.except_osv(_('Error!'), _('Define product(s) you want to include in the call for bids.'))
return res
def create_order(self, cr, uid, ids, context=None):

View File

@ -19,7 +19,9 @@
#
##############################################################################
import logging
import openerp
import openerp.tests
_logger = logging.getLogger(__name__)

View File

@ -216,7 +216,7 @@
</group>
<group>
<field name="calendar_id"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="company_id" widget="selection" groups="base.group_multi_company" attrs="{'invisible':[('calendar_id','=',False)]}"/>
</group>
</group>
<group>

View File

@ -159,7 +159,7 @@ class sale_order(osv.osv):
if s['state'] in ['draft', 'cancel']:
unlink_ids.append(s['id'])
else:
raise osv.except_osv(_('Invalid Action!'), _('In order to delete a confirmed sales order, you must cancel it.\nTo do so, you must first cancel related picking for delivery orders.'))
raise osv.except_osv(_('Invalid Action!'), _('In order to delete a confirmed sales order, you must cancel it. \nTo do so, you must first cancel all related delivery order(s).'))
return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)

View File

@ -1195,7 +1195,7 @@ class stock_picking(osv.osv):
return True
for move in pick.move_lines:
if move.state == 'done':
raise osv.except_osv(_('Error!'), _('You cannot cancel the picking as some moves have been done. You should cancel the picking lines.'))
raise osv.except_osv(_('Error!'), _('You cannot cancel the picking as some moves have been done. You should cancel remaining moves of this picking.'))
return True
def unlink(self, cr, uid, ids, context=None):

View File

@ -1558,11 +1558,11 @@
<field name="model">stock.incoterms</field>
<field name="arch" type="xml">
<form string="Incoterms" version="7.0">
<group>
<sheet>
<field name="name"/>
<field name="code"/>
<field name="active"/>
</group>
</sheet>
</form>
</field>
</record>
@ -1691,10 +1691,10 @@
<field name="model">stock.journal</field>
<field name="arch" type="xml">
<form string="Stock Journal" version="7.0">
<group>
<sheet>
<field name="name"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice', 'stock.group_stock_manager']}"/>
</group>
</sheet>
</form>
</field>
</record>

View File

@ -34,6 +34,27 @@ import uuid
_logger = logging.getLogger(__name__)
class survey_stage(osv.Model):
"""Stages for Kanban view of surveys"""
_name = 'survey.stage'
_description = 'Survey Stage'
_order = 'sequence asc'
_columns = {
'name': fields.char(string="Name", required=True, translate=True),
'sequence': fields.integer(string="Sequence"),
'closed': fields.boolean(string="Closed", help="If closed, people won't be able to answer to surveys in this column."),
'fold': fields.boolean(string="Folded in kanban view")
}
_defaults = {
'sequence': 1,
'closed': False
}
_sql_constraints = [
('positive_sequence', 'CHECK(sequence >= 0)', 'Sequence number MUST be a natural')
]
class survey_survey(osv.Model):
'''Settings for a multi-page/multi-question survey.
@ -175,9 +196,15 @@ class survey_survey(osv.Model):
'quizz_mode': fields.boolean(string='Quizz mode')
}
def _default_stage(self, cr, uid, context=None):
ids = self.pool['survey.stage'].search(cr, uid, [], limit=1, context=context)
if ids:
return ids[0]
return False
_defaults = {
'color': 0,
'stage_id': lambda self, cr, uid, context: self.pool.get('survey.stage').search_read(cr, uid, fields=['id'], order='sequence asc', limit=1, context=context)[0]['id']
'stage_id': lambda self, *a, **kw: self._default_stage(*a, **kw)
}
def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
@ -456,26 +483,6 @@ class survey_survey(osv.Model):
}
class survey_stage(osv.Model):
"""Stages for Kanban view of surveys"""
_name = 'survey.stage'
_description = 'Survey Stage'
_order = 'sequence asc'
_columns = {
'name': fields.char(string="Name", required=True, translate=True),
'sequence': fields.integer(string="Sequence"),
'closed': fields.boolean(string="Closed", help="If closed, people won't be able to answer to surveys in this column."),
'fold': fields.boolean(string="Folded in kanban view")
}
_defaults = {
'sequence': 1,
'closed': False
}
_sql_constraints = [
('positive_sequence', 'CHECK(sequence >= 0)', 'Sequence number MUST be a natural')
]
class survey_page(osv.Model):

View File

@ -130,6 +130,7 @@
*/
var dummy = function () {};
var website = openerp.website;
website.add_template_file('/website/static/src/xml/website.snippets.xml');
@ -361,15 +362,21 @@
},
clean_for_save: function () {
var self = this;
$(website.snippet.globalSelector).each(function () {
var $snippet = $(this);
self.make_active($snippet);
self.make_active(false);
var editor = $snippet.data("snippet-editor");
if (editor) {
editor.clean_for_save();
$("*[contentEditable], *[attributeEditable]")
.removeAttr('contentEditable')
.removeAttr('attributeEditable');
var options = website.snippet.options;
var template = website.snippet.templateOptions;
for (var k in options) {
if (template[k] && options[k].prototype.clean_for_save !== dummy) {
var $snippet = this.dom_filter(template[k].selector);
$snippet.each(function () {
new options[k](self, null, $(this), k).clean_for_save();
});
}
});
}
},
make_active: function ($snippet) {
if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) {
@ -853,8 +860,7 @@
}
},
clean_for_save: function () {
}
clean_for_save: dummy
});
website.snippet.options.background = website.snippet.Option.extend({
@ -942,8 +948,7 @@
this.id = this.unique_id();
this.$target.attr("id", this.id);
this.$target.find("[data-slide]").attr("data-cke-saved-href", "#" + this.id);
this.$target.find("[data-slide-to]").attr("data-target", "#" + this.id);
this.$target.find("[data-target]").attr("data-target", "#" + this.id);
this.rebind_event();
},
on_clone: function ($clone) {
@ -963,7 +968,7 @@
},
clean_for_save: function () {
this._super();
this.$target.find(".item").removeClass("next prev left right");
$(".carousel").find(".item").removeClass("next prev left right active");
if(!this.$target.find(".item.active").length) {
this.$target.find(".item:first").addClass("active");
}
@ -1757,19 +1762,6 @@
}
this.$overlay.removeClass('oe_active');
},
/* clean_for_save
* function called just before save vue
*/
clean_for_save: function () {
for (var i in this.styles){
this.styles[i].clean_for_save();
}
this.$target.removeAttr('contentEditable')
.find('*').removeAttr('contentEditable');
this.$target.removeAttr('attributeEditable')
.find('*').removeAttr('attributeEditable');
},
});
})();

View File

@ -52,7 +52,7 @@ class BlogPost(osv.Model):
'name': fields.char('Title', required=True, translate=True),
'subtitle': fields.char('Sub Title', translate=True),
'author_id': fields.many2one('res.partner', 'Author'),
'background_image': fields.binary('Background Image'),
'background_image': fields.binary('Background Image', oldname='content_image'),
'blog_id': fields.many2one(
'blog.blog', 'Blog',
required=True, ondelete='cascade',

View File

@ -0,0 +1,23 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import certification
import controllers

View File

@ -0,0 +1,38 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Certified People',
'category': 'Website',
'summary': 'Display your network of certified people on your website',
'version': '1.0',
'author': 'OpenERP S.A.',
'depends': ['marketing', 'website'],
'description': """
Display your network of certified people on your website
""",
'data': [
'security/ir.model.access.csv',
'views/website_certification_views.xml',
'views/website_certification_templates.xml',
],
'installable': True,
}

View File

@ -0,0 +1,43 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import osv, fields
class certification_type(osv.Model):
_name = 'certification.type'
_order = 'name ASC'
_columns = {
'name': fields.char("Certification Type", required=True)
}
class certification_certification(osv.Model):
_name = 'certification.certification'
_order = 'certification_date DESC'
_columns = {
'partner_id': fields.many2one('res.partner', string="Partner", required=True),
'type_id': fields.many2one('certification.type', string="Certification", required=True),
'certification_date': fields.date("Certification Date", required=True),
'certification_score': fields.char("Certification Score", required=True),
'certification_hidden_score': fields.boolean("Hide score on website?")
}

View File

@ -0,0 +1,22 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import main

View File

@ -0,0 +1,49 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.web import http
from openerp.addons.web.http import request
class WebsiteCertifiedPartners(http.Controller):
@http.route(['/certifications',
'/certifications/<model("certification.type"):cert_type>'], type='http', auth='public',
website=True, multilang=True)
def certified_partners(self, cert_type=None, **post):
cr, uid, context = request.cr, request.uid, request.context
certification_obj = request.registry['certification.certification']
cert_type_obj = request.registry['certification.type']
domain = []
if cert_type:
domain.append(('type_id', '=', cert_type.id))
certifications_ids = certification_obj.search(cr, uid, domain, context=context)
certifications = certification_obj.browse(cr, uid, certifications_ids, context=context)
types = cert_type_obj.browse(cr, uid, cert_type_obj.search(cr, uid, [], context=context), context=context)
data = {
'certifications': certifications,
'types': types
}
return request.website.render("website_certification.certified_partners", data)

View File

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_certifications_public,certification.certification public,model_certification_certification,base.group_public,1,0,0,0
access_certifications_types_public,certification.type public,model_certification_type,base.group_public,1,0,0,0
access_certifications_users,certification.certification users,model_certification_certification,base.group_user,1,0,0,0
access_certifications_types_users,certification.type users,model_certification_type,base.group_user,1,0,0,0
access_certifications_marketing,certification.certification marketing,model_certification_certification,marketing.group_marketing_user,1,1,1,1
access_certifications_types_marketing,certification.type marketing,model_certification_type,marketing.group_marketing_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_certifications_public certification.certification public model_certification_certification base.group_public 1 0 0 0
3 access_certifications_types_public certification.type public model_certification_type base.group_public 1 0 0 0
4 access_certifications_users certification.certification users model_certification_certification base.group_user 1 0 0 0
5 access_certifications_types_users certification.type users model_certification_type base.group_user 1 0 0 0
6 access_certifications_marketing certification.certification marketing model_certification_certification marketing.group_marketing_user 1 1 1 1
7 access_certifications_types_marketing certification.type marketing model_certification_type marketing.group_marketing_user 1 1 1 1

View File

@ -0,0 +1,42 @@
<?xml version='1.0' encoding='utf-8'?>
<openerp>
<data>
<template id="certified_partners" name="Certified People">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
<div class="row">
<h1>Certified People</h1>
<div class="well">Filter by certification type:
<a href="/certifications"><span class="badge">all</span></a>
<t t-foreach="types" t-as="t">
<a t-att-href="'/certifications/%s' % slug(t)"><span class="badge" t-field="t.name" /></a>
</t>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead><tr>
<th>Name</th>
<th>Date</th>
<th>Type</th>
<th>Score</th>
</tr></thead>
<tbody>
<tr t-foreach="certifications" t-as="c">
<td><span t-field="c.partner_id"/></td>
<td><span t-field="c.certification_date"/></td>
<td><span t-field="c.type_id.name"/></td>
<td><t t-if="c.certification_hidden_score == False"><span t-field="c.certification_score"/></t></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</template>
</data>
</openerp>

View File

@ -0,0 +1,58 @@
<?xml version='1.0' encoding='utf-8'?>
<openerp>
<data>
<record model="ir.ui.view" id="certification_certification_tree">
<field name="name">view.certification.certification.tree</field>
<field name="model">certification.certification</field>
<field name="arch" type="xml">
<tree string="Granted Certifications">
<field name="partner_id" />
<field name="type_id" />
<field name="certification_date" />
<field name="certification_score" />
<field name="certification_hidden_score" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="certification_certification_form">
<field name="name">view.certification.certification.form</field>
<field name="model">certification.certification</field>
<field name="arch" type="xml">
<form string="Granted Certification" version="7.0">
<sheet>
<group nolabel="1">
<field name="partner_id" />
<field name="type_id" />
<field name="certification_date" />
<field name="certification_score" />
<field name="certification_hidden_score" />
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="certification_certification_search">
<field name="name">view.certification.certification.search</field>
<field name="model">certification.certification</field>
<field name="arch" type="xml">
<search string="Search Certification">
<field name="partner_id" />
<field name="type_id" />
<group expand="0" string="Group By...">
<filter string="Partner" name="group_by_partner" domain="[]" context="{'group_by': 'partner_id'}"/>
<filter string="Type" name="group_by_type" domain="[]" context="{'group_by': 'type_id'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_certifications_list">
<field name="name">Certifications</field>
<field name="res_model">certification.certification</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="certification_certification_search"/>
</record>
<menuitem name="Certifications" id="menu_certifications" parent="base.marketing_menu" sequence="35" />
<menuitem name="Certifications" id="menu_certifications_list" action="action_certifications_list" parent="menu_certifications" sequence="1"/>
</data>
</openerp>

View File

@ -32,12 +32,21 @@ class WebsiteForum(http.Controller):
return msg
def _prepare_forum_values(self, forum=None, **kwargs):
Forum = request.registry['forum.forum']
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, context=request.context)
public_uid = request.registry['website'].get_public_user(request.cr, request.uid, request.context)
values = {'user': user, 'is_public_user': user.id == public_uid,
'notifications': self._get_notifications(),
'header': kwargs.get('header', dict()),
'searches': kwargs.get('searches', dict())}
'searches': kwargs.get('searches', dict()),
'can_edit_own': True,
'can_edit_all': user.karma > Forum._karma_modo_edit_all,
'can_close_own': user.karma > Forum._karma_modo_close_own,
'can_close_all': user.karma > Forum._karma_modo_close_all,
'can_unlink_own': user.karma > Forum._karma_modo_unlink_own,
'can_unlink_all': user.karma > Forum._karma_modo_unlink_all,
'can_unlink_comment': user.karma > Forum._karma_modo_unlink_comment,
}
if forum:
values['forum'] = forum
elif kwargs.get('forum_id'):
@ -45,6 +54,14 @@ class WebsiteForum(http.Controller):
values.update(kwargs)
return values
def _has_enough_karma(self, karma_name, uid=None):
Forum = request.registry['forum.forum']
karma = hasattr(Forum, karma_name) and getattr(Forum, karma_name) or 0
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, uid or request.uid, context=request.context)
if user.karma < karma:
return False, {'error': 'not_enough_karma', 'karma': karma}
return True, {}
# Forum
# --------------------------------------------------
@ -204,6 +221,10 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/ask_for_close', type='http', auth="user", multilang=True, website=True)
def question_ask_for_close(self, forum, question, **post):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
cr, uid, context = request.cr, request.uid, request.context
Reason = request.registry['forum.post.reason']
reason_ids = Reason.search(cr, uid, [], context=context)
@ -211,7 +232,7 @@ class WebsiteForum(http.Controller):
values = self._prepare_forum_values(**post)
values.update({
'post': question,
'question': question,
'question': question,
'forum': forum,
'reasons': reasons,
@ -228,6 +249,10 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/close', type='http', auth="user", multilang=True, methods=['POST'], website=True)
def question_close(self, forum, question, **post):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {
'state': 'close',
'closed_uid': request.uid,
@ -238,17 +263,28 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/reopen', type='http', auth="user", multilang=True, website=True)
def question_reopen(self, forum, question, **kwarg):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'state': 'active'}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/delete', type='http', auth="user", multilang=True, website=True)
def question_delete(self, forum, question, **kwarg):
#instead of unlink record just change 'active' to false so user can undelete it.
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': False}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/undelete', type='http', auth="user", multilang=True, website=True)
def question_undelete(self, forum, question, **kwarg):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': True}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@ -287,19 +323,27 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
def post_toggle_correct(self, forum, post, **kwargs):
cr, uid, context = request.cr, request.uid, request.context
if post.parent_id is False:
return request.redirect('/')
if not request.session.uid:
return {'error': 'anonymous_user'}
# if user have not access to accept answer then reise warning
if post.parent_id is False or post.parent_id.create_uid.id != uid:
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
if post.parent_id.create_uid.id != uid:
return {'error': 'own_post'}
if post.create_uid.id == user.id and user.karma < request.registry['forum.forum']._karma_answer_accept_own:
return {'error': 'not_enough_karma', 'karma': 20}
# set all answers to False, only one can be accepted
request.registry['forum.post'].write(cr, uid, [c.id for c in post.parent_id.child_ids], {'is_correct': False}, context=context)
request.registry['forum.post'].write(cr, uid, [post.id, post.parent_id.id], {'is_correct': not post.is_correct}, context=context)
request.registry['forum.post'].write(cr, uid, [post.id], {'is_correct': not post.is_correct}, context=context)
return not post.is_correct
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/delete', type='http', auth="user", multilang=True, website=True)
def post_delete(self, forum, post, **kwargs):
check_res = self._has_enough_karma(post.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
question = post.parent_id
request.registry['forum.post'].unlink(request.cr, request.uid, [post.id], context=request.context)
if question:
@ -308,6 +352,10 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/edit', type='http', auth="user", website=True, multilang=True)
def post_edit(self, forum, post, **kwargs):
check_res = self._has_enough_karma(post.create_uid.id == request.uid and '_karma_modo_edit_own' or '_karma_modo_edit_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
tags = ""
for tag_name in post.tag_ids:
tags += tag_name.name + ","
@ -345,15 +393,15 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/upvote', type='json', auth="public", multilang=True, website=True)
def post_upvote(self, forum, post, **kwargs):
# check for karma and not self vote
if not request.session.uid:
return {'error': 'anonymous_user'}
if request.uid == post.create_uid.id:
return {'error': 'own_post'}
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
if user.karma <= 5:
return {'error': 'not_enough_karma', 'karma': 1}
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=True, context=request.context)
check_res = self._has_enough_karma('_karma_upvote')
if not check_res[0]:
return check_res[1]
upvote = True if not post.user_vote > 0 else False
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/downvote', type='json', auth="public", multilang=True, website=True)
def post_downvote(self, forum, post, **kwargs):
@ -361,10 +409,11 @@ class WebsiteForum(http.Controller):
return {'error': 'anonymous_user'}
if request.uid == post.create_uid.id:
return {'error': 'own_post'}
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
if user.karma <= 50:
return {'error': 'not_enough_karma', 'karma': 50}
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=False, context=request.context)
check_res = self._has_enough_karma('_karma_downvote')
if not check_res[0]:
return check_res[1]
upvote = True if post.user_vote < 0 else False
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
# User
# --------------------------------------------------
@ -392,6 +441,16 @@ class WebsiteForum(http.Controller):
return request.website.render("website_forum.users", values)
@http.route(['/forum/<model("forum.forum"):forum>/partner/<int:partner_id>'], type='http', auth="public", website=True, multilang=True)
def open_partner(self, forum, partner_id=0, **post):
cr, uid, context = request.cr, request.uid, request.context
pids = request.registry['res.partner'].search(cr, SUPERUSER_ID, [('id', '=', partner_id)], context=context)
if pids:
partner = request.registry['res.partner'].browse(cr, SUPERUSER_ID, pids[0], context=context)
if partner.user_ids:
return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), partner.user_ids[0].id))
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
@http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True, multilang=True)
def open_user(self, forum, user_id=0, **post):
cr, uid, context = request.cr, request.uid, request.context

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<!-- QUALITY (VOTES) -->
<!-- Teacher: at least 3 upvotes -->
@ -8,6 +8,7 @@
<field name="name">Teacher</field>
<field name="description">Received at least 3 upvote for an answer for the first time</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_teacher">
<field name="name">Teacher</field>
@ -42,6 +43,7 @@
<field name="name">Nice Answer</field>
<field name="description">Answer voted up 4 times</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_nice_answer">
<field name="name">Nice Answer (4)</field>
@ -76,6 +78,7 @@
<field name="name">Good Answer</field>
<field name="description">Answer voted up 6 times</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_good_answer">
<field name="name">Good Answer (6)</field>
@ -110,6 +113,7 @@
<field name="name">Great Answer</field>
<field name="description">Answer voted up 15 times</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_great_answer">
<field name="name">Great Answer (15)</field>
@ -146,6 +150,7 @@
<field name="name">Enlightened</field>
<field name="description">Answer was accepted with 3 or more votes</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_enlightened">
<field name="name">Enlightened</field>
@ -180,6 +185,7 @@
<field name="name">Guru</field>
<field name="description">Answer accepted with 15 or more votes</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_guru">
<field name="name">Guru (15)</field>
@ -256,6 +262,7 @@ for post in Post.browse(cr, uid, user_posts, context=context):
<field name="name">Self-Learner</field>
<field name="description">Answered own question with at least 4 up votes</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_self_learner">
<field name="name">Self-Learner</field>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<!-- Cleanup: answer or question edition -->
<!-- Not rollback feature in forum -->
@ -15,6 +15,7 @@
<field name="name">Critic</field>
<field name="description">First downvote</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_critic">
<field name="name">Critic</field>
@ -50,6 +51,7 @@
<field name="name">Disciplined</field>
<field name="description">Deleted own post with 3 or more upvotes</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_disciplined">
<field name="name">Disciplined</field>
@ -85,6 +87,7 @@
<field name="name">Editor</field>
<field name="description">First edit</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_editor">
<field name="name">Editor</field>
@ -223,6 +226,7 @@ result = int(len(data) >= 15)</field>
<field name="name">Supporter</field>
<field name="description">First upvote</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_supporter">
<field name="name">Supporter</field>
@ -258,6 +262,7 @@ result = int(len(data) >= 15)</field>
<field name="name">Peer Pressure</field>
<field name="description">Deleted own post with 3 or more downvotes</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_peer_pressure">
<field name="name">Peer Pressure</field>

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<!-- Biography: complet your profile -->
<record id="badge_p_1" model="gamification.badge">
<field name="name">Autobiographer</field>
<field name="description">Completed own biography</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_configure_profile">
<field name="name">Completed own biography</field>
@ -46,6 +47,7 @@
<field name="name">Commentator</field>
<field name="description">Posted 10 comments</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_commentator">
<field name="name">Commentator</field>
@ -81,6 +83,7 @@
<field name="name">Pundit</field>
<field name="description">Left comments with score of 10 or more</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_pundit">
<field name="name">Pundit</field>
@ -115,6 +118,7 @@
<field name="name">Chief Commentator</field>
<field name="description">Posted 100 comments</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.challenge" id="challenge_chief_commentator">
<field name="name">Chief Commentator</field>
@ -177,6 +181,7 @@ result = get_counter(cr, uid, context=context)
<field name="name">Taxonomist</field>
<field name="description">Created a tag used by 15 questions</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_taxonomist">
<field name="name">Taxonomist</field>

View File

@ -1,6 +1,6 @@
<!-- <?xml version="1.0" encoding="utf-8"?> -->
<openerp>
<data>
<data noupdate="1">
<!-- POPULARITY (VIEWS) -->
<!-- Popular: 150 views -->
@ -8,6 +8,7 @@
<field name="name">Popular Question</field>
<field name="description">Asked a question with at least 150 views</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_popular_question">
<field name="name">Popular Question (150)</field>
@ -43,6 +44,7 @@
<field name="name">Notable Question</field>
<field name="description">Asked a question with at least 250 views</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_notable_question">
<field name="name">Popular Question (250)</field>
@ -77,6 +79,7 @@
<field name="name">Famous Question</field>
<field name="description">Asked a question with at least 500 views</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_famous_question">
<field name="name">Popular Question (500)</field>
@ -113,6 +116,7 @@
<field name="name">Credible Question</field>
<field name="description">Question set as favorite by 1 user</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_favorite_question_1">
<field name="name">Favourite Question (1)</field>
@ -147,6 +151,7 @@
<field name="name">Favorite Question</field>
<field name="description">Question set as favorite by 5 users</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_favorite_question_5">
<field name="name">Favourite Question (5)</field>
@ -181,6 +186,7 @@
<field name="name">Stellar Question</field>
<field name="description">Question set as favorite by 25 users</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_stellar_question_25">
<field name="name">Favourite Question (25)</field>
@ -217,6 +223,7 @@
<field name="name">Student</field>
<field name="description">Asked first question with at least one up vote</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_student">
<field name="name">Upvoted question (1)</field>
@ -251,6 +258,7 @@
<field name="name">Nice Quesiotn</field>
<field name="description">Question voted up 4 times</field>
<field name="level">bronze</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_nice_question">
<field name="name">Upvoted question (4)</field>
@ -285,6 +293,7 @@
<field name="name">Good Question</field>
<field name="description">Question voted up 6 times</field>
<field name="level">silver</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_good_question">
<field name="name">Upvoted question (6)</field>
@ -319,6 +328,7 @@
<field name="name">Great Question</field>
<field name="description">Question voted up 15 times</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_great_question">
<field name="name">Upvoted question (15)</field>
@ -354,6 +364,7 @@
<field name="name">Scholar</field>
<field name="description">Asked a question and accepted an answer</field>
<field name="level">gold</field>
<field name="rule_auth">nobody</field>
</record>
<record model="gamification.goal.definition" id="definition_scholar">
<field name="name">Scholar</field>

View File

@ -9,9 +9,38 @@ from openerp.tools.translate import _
class Forum(osv.Model):
"""TDE TODO: set karma values for actions dynamic for a given forum"""
_name = 'forum.forum'
_description = 'Forums'
_inherit = ['website.seo.metadata']
# karma values
_karma_upvote = 5 # done
_karma_downvote = 50 # done
_karma_answer_accept_own = 20 # done
_karma_answer_accept_own_now = 50
_karma_answer_accept_all = 500
_karma_editor_link_files = 30 # done
_karma_editor_clickable_link = 50
_karma_comment = 1
_karma_modo_retag = 75
_karma_modo_flag = 100
_karma_modo_flag_see_all = 300
_karma_modo_unlink_comment = 750
_karma_modo_edit_own = 1 # done
_karma_modo_edit_all = 300 # done
_karma_modo_close_own = 100 # done
_karma_modo_close_all = 900 # done
_karma_modo_unlink_own = 500 # done
_karma_modo_unlink_all = 1000 # done
# karma generation
_karma_gen_quest_new = 2 # done
_karma_gen_upvote_quest = 5 # done
_karma_gen_downvote_quest = -2 # done
_karma_gen_upvote_ans = 10 # done
_karma_gen_downvote_ans = -2 # done
_karma_gen_ans_accept = 2 # done
_karma_gen_ans_accepted = 15 # done
_karma_gen_ans_flagged = -100
_columns = {
'name': fields.char('Name', required=True, translate=True),
@ -97,6 +126,13 @@ class Post(osv.Model):
res[post.id] = any(answer.create_uid.id == uid for answer in post.child_ids)
return res
def _get_has_validated_answer(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, False)
ans_ids = self.search(cr, uid, [('parent_id', 'in', ids), ('is_correct', '=', True)], context=context)
for answer in self.browse(cr, uid, ans_ids, context=context):
res[answer.parent_id.id] = True
return res
def _is_self_reply(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, False)
for post in self.browse(cr, uid, ids, context=context):
@ -143,7 +179,8 @@ class Post(osv.Model):
}),
# hierarchy
'parent_id': fields.many2one('forum.post', 'Question', ondelete='cascade'),
'self_reply': fields.function(_is_self_reply, 'Reply to own question', type='boolean',
'self_reply': fields.function(
_is_self_reply, 'Reply to own question', type='boolean',
store={
'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['parent_id', 'create_uid'], 10),
}),
@ -156,6 +193,12 @@ class Post(osv.Model):
'uid_has_answered': fields.function(
_get_uid_answered, string='Has Answered', type='boolean',
),
'has_validated_answer': fields.function(
_get_has_validated_answer, string='Has a Validated Answered', type='boolean',
store={
'forum.post': (_get_post_from_hierarchy, ['parent_id', 'child_ids', 'is_correct'], 10),
}
),
# closing
'closed_reason_id': fields.many2one('forum.post.reason', 'Reason'),
'closed_uid': fields.many2one('res.users', 'Closed by', select=1),
@ -183,10 +226,18 @@ class Post(osv.Model):
self.message_post(cr, uid, parent.id, subject=_('Re: %s') % parent.name, body=body, subtype='website_forum.mt_answer_new', context=context)
else:
self.message_post(cr, uid, post_id, subject=vals.get('name', ''), body=_('New Question Created'), subtype='website_forum.mt_question_new', context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], 2, context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], self.pool['forum.forum']._karma_gen_quest_new, context=context)
return post_id
def write(self, cr, uid, ids, vals, context=None):
Forum = self.pool['forum.forum']
# update karma when accepting/rejecting answers
if 'is_correct' in vals:
mult = 1 if vals['is_correct'] else -1
for post in self.browse(cr, uid, ids, context=context):
if vals['is_correct'] != post.is_correct:
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], Forum._karma_gen_ans_accepted * mult, context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], Forum._karma_gen_ans_accept * mult, context=context)
res = super(Post, self).write(cr, uid, ids, vals, context=context)
# if post content modify, notify followers
if 'content' in vals or 'name' in vals:
@ -198,11 +249,6 @@ class Post(osv.Model):
body, subtype = _('Question Edited'), 'website_forum.mt_question_edit'
obj_id = post.id
self.message_post(cr, uid, obj_id, body=_(body), subtype=subtype, context=context)
# update karma of related user when any answer accepted
if 'correct' in vals:
for post in self.browse(cr, uid, ids, context=context):
karma_value = 15 if vals.get('correct') else -15
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], {'karma': karma_value}, context=context)
return res
def vote(self, cr, uid, ids, upvote=True, context=None):
@ -252,16 +298,30 @@ class Vote(osv.Model):
def create(self, cr, uid, vals, context=None):
vote_id = super(Vote, self).create(cr, uid, vals, context=context)
karma_value = int(vals.get('vote', '1')) * 10
post = self.pool['forum.post'].browse(cr, uid, vals.get('post_id'), context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, post.create_uid.id, karma_value, context=context)
if vals.get('vote', '1') == '1':
karma = self.pool['forum.forum']._karma_upvote
elif vals.get('vote', '1') == '-1':
karma = self.pool['forum.forum']._karma_downvote
post = self.pool['forum.post'].browse(cr, uid, vals['post_id'], context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], karma, context=context)
return vote_id
def write(self, cr, uid, ids, values, context=None):
def _get_karma_value(old_vote, new_vote, up_karma, down_karma):
_karma_upd = {
'-1': {'-1': 0, '0': -1 * down_karma, '1': -1 * down_karma + up_karma},
'0': {'-1': 1 * down_karma, '0': 0, '1': up_karma},
'1': {'-1': -1 * up_karma + down_karma, '0': -1 * up_karma, '1': 0}
}
return _karma_upd[old_vote][new_vote]
if 'vote' in values:
Forum = self.pool['forum.forum']
for vote in self.browse(cr, uid, ids, context=context):
karma_value = (int(values.get('vote')) - int(vote.vote)) * 10
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, vote.post_id.create_uid.id, karma_value, context=context)
if vote.post_id.parent_id:
karma_value = _get_karma_value(vote.vote, values['vote'], Forum._karma_gen_upvote_ans, Forum._karma_gen_downvote_ans)
else:
karma_value = _get_karma_value(vote.vote, values['vote'], Forum._karma_gen_upvote_quest, Forum._karma_gen_downvote_quest)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.post_id.create_uid.id], karma_value, context=context)
res = super(Vote, self).write(cr, uid, ids, values, context=context)
return res

View File

@ -32,8 +32,6 @@ class Users(osv.Model):
}
def add_karma(self, cr, uid, ids, karma, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
for user in self.browse(cr, uid, ids, context=context):
self.write(cr, uid, [user.id], {'karma': user.karma + karma}, context=context)
return True
@ -44,4 +42,4 @@ class Users(osv.Model):
excluded_categories.append('forum')
else:
excluded_categories = ['forum']
return super(Users, self).get_serialised_gamification_summary(cr, uid, excluded_categories=excluded_categories, context=context)
return super(Users, self).get_serialised_gamification_summary(cr, uid, excluded_categories=excluded_categories, context=context)

View File

@ -18,9 +18,9 @@ $(document).ready(function () {
'</div>');
}
else if (data['error'] == 'not_enough_karma') {
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="position:absolute; margin-top: -30px; margin-left: 90px;">'+
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="max-width: 500px; position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, at least ' + data['karma'] + ' karma is required to vote'+
'Sorry, at least ' + data['karma'] + ' karma is required to vote. You can gain karma by answering questions and receiving votes.'+
'</div>');
}
vote_alert = $link.parent().find("#vote_alert");
@ -57,7 +57,12 @@ $(document).ready(function () {
} else if (data['error'] == 'own_post'){
var $warning = $('<div class="alert alert-danger alert-dismissable" id="correct_answer_alert" style="position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, the user who asked this question can only accept the answer as correct.'+
'Sorry, only the user who asked this question can accept the answer as correct.'+
'</div>');
} else if (data['error'] == 'not_enough_karma') {
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="max-width: 500px; position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, at least ' + data['karma'] + ' karma is required to accept your own answers. You can gain karma by answering questions and receiving votes.'+
'</div>');
}
correct_answer_alert = $link.parent().find("#correct_answer_alert");

View File

@ -333,7 +333,7 @@
them.
</p>
<form t-attf-action="/forum/#{ slug(forum) }/question/#{slug(post)}/close" method="post" role="form" class="form-horizontal mt32 mb64">
<input name="post_id" t-att-value="post.id" type="hidden"/>
<input name="post_id" t-att-value="question.id" type="hidden"/>
<div class="form-group">
<label class="col-md-3 control-label" for="reason">Question:</label>
<div class="col-md-8 mt8">
@ -345,7 +345,7 @@
<div class="col-md-8">
<select class="form-control" name="reason_id">
<t t-foreach="reasons or []" t-as="reason">
<option t-att-value="reason.id" t-att-selected="reason.id == post.closed_reason_id.id"><t t-esc="reason.name"/></option>
<option t-att-value="reason.id" t-att-selected="reason.id == question.closed_reason_id.id"><t t-esc="reason.name"/></option>
</t>
</select>
</div>
@ -447,16 +447,19 @@
Comment
</a>
</li>
<li t-if="question.state != 'close' and (user.id == question.create_uid.id or user.karma&gt;=100)">
<li t-if="question.state != 'close' and ((user.id == question.create_uid.id and can_close_own) or can_close_all)">
<a class="text-muted fa fa-times" t-attf-href="/forum/#{ slug(forum) }/question/#{slug(question)}/ask_for_close"> Close</a>
</li>
<li t-if="user.id == question.create_uid.id or user.karma&gt;=300">
<li t-if="question.state == 'close' and ((user.id == question.create_uid.id and can_close_own) or can_close_all)">
<a class="text-muted fa fa-undo" t-attf-href="/forum/#{ slug(forum) }/question/#{slug(question)/reopen"> Reopen</a>
</li>
<li t-if="(user.id == question.create_uid.id and can_edit_own) or can_edit_all">
<a class="text-muted fa fa-edit" t-attf-href="/forum/#{ slug(forum) }/post/#{slug(question)}/edit"> Edit</a>
</li>
<li t-if="question.active and user.id == question.create_uid.id or user.karma&gt;=1000">
<li t-if="question.active and ((user.id == question.create_uid.id and can_unlink_own) or can_unlink_all)">
<a class="text-muted fa fa-trash-o" t-attf-href="/forum/#{ slug(forum) }/question/#{slug(question)}/delete"> Delete</a>
</li>
<li t-if="uid == question.create_uid.id and not question.active">
<li t-if="not question.active and ((user.id == question.create_uid.id and can_unlink_own) or can_unlink_all)">
<a class="text-muted fa fa-trash-o" t-attf-href="/forum/#{ slug(forum) }/question/#{slug(question)}/undelete"> Undelete</a>
</li>
</ul>
@ -500,10 +503,10 @@
t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
</a>
</li>
<li t-if="user.id == answer.create_uid.id or user.karma&gt;=300">
<li t-if="(user.id == answer.create_uid.id and can_edit_own) or can_edit_all">
<a class="text-muted fa fa-edit" t-attf-href="/forum/#{slug(forum)}/post/#{slug(answer)}/edit"> Edit</a>
</li>
<li t-if="user.id == answer.create_uid.id or user.karma&gt;=1000">
<li t-if="(user.id == answer.create_uid.id and can_unlink_own) or can_unlink_all">
<a class="text-muted fa fa-trash-o" t-attf-href="/forum/#{slug(forum)}/post/#{slug(answer)}/delete"> Delete</a>
</li>
<li t-if="user.id == answer.create_uid.id">
@ -546,7 +549,7 @@
t-attf-href="/forum/#{slug(forum)}/post/#{slug(object)}/comment/#{slug(message)}/delete"
class="close comment_delete">&amp;times;</button>
<span t-field="message.body"/>
<a t-attf-href="/forum/#{slug(forum)}/user/#{message.id}"
<a t-attf-href="/forum/#{slug(forum)}/partner/#{message.author_id.id}"
t-field="message.author_id" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
style="display: inline-block;"/>
on <span t-field="message.date" t-field-options='{"format":"short"}'/>