[MERGE] lp:~openerp-dev/openobject-addons/trunk-dev-addons1 : Bug fixed by Addons Team 1

project_long_term improverment
Fix yml of project_long_term
Add yml
    * CRM (Recurrent Meeting)
    * CRM (for lead opportunity : stage change, opening, closing, won, lost)

bzr revid: tfr@openerp.com-20110114093428-5u92lg4b4zoeumwv
This commit is contained in:
Thibault Francois 2011-01-14 10:34:28 +01:00
commit 5af8021914
61 changed files with 945 additions and 588 deletions

View File

@ -450,7 +450,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
values = { values = {
'state': 'anonymized', 'state': 'anonymized',
} }
res = ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context) ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context)
# add a result message in the wizard: # add a result message in the wizard:
msgs = ["Anonymization successful.", msgs = ["Anonymization successful.",
@ -578,7 +578,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
try: try:
idn = self.pool.get('ir.model.data')._get_id(cr, uid, mod, id_str) idn = self.pool.get('ir.model.data')._get_id(cr, uid, mod, id_str)
res = int(self.pool.get('ir.model.data').read(cr, uid, [idn], ['res_id'])[0]['res_id']) res = int(self.pool.get('ir.model.data').read(cr, uid, [idn], ['res_id'])[0]['res_id'])
except Exception, e: except:
res = None res = None
return res return res

View File

@ -18,13 +18,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
from osv import fields, osv, orm from osv import fields, osv
from tools import config
from tools.translate import _
import ir
import netsvc import netsvc
import os
import time
import tools import tools
def _type_get(self, cr, uid, context=None): def _type_get(self, cr, uid, context=None):

View File

@ -410,6 +410,8 @@ property or property parameter."),
return res return res
cal = vobject.iCalendar() cal = vobject.iCalendar()
event = cal.add('vevent') event = cal.add('vevent')
if not event_obj.date_deadline or not event_obj.date:
raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S')) event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
event.add('dtstart').value = ics_datetime(event_obj.date) event.add('dtstart').value = ics_datetime(event_obj.date)
event.add('dtend').value = ics_datetime(event_obj.date_deadline) event.add('dtend').value = ics_datetime(event_obj.date_deadline)
@ -461,7 +463,6 @@ property or property parameter."),
attendee_add.params['ROLE'] = [str(attendee.role)] attendee_add.params['ROLE'] = [str(attendee.role)]
attendee_add.params['RSVP'] = [str(attendee.rsvp)] attendee_add.params['RSVP'] = [str(attendee.rsvp)]
attendee_add.value = 'MAILTO:' + (attendee.email or '') attendee_add.value = 'MAILTO:' + (attendee.email or '')
res = cal.serialize() res = cal.serialize()
return res return res

View File

@ -77,6 +77,7 @@
<tree string="Invitation details"> <tree string="Invitation details">
<field name="sent_by_uid" string="Invitation From" /> <field name="sent_by_uid" string="Invitation From" />
<field name="role" string="My Role"/> <field name="role" string="My Role"/>
<field name="user_id" invisible="1"/>
<field name="cutype" string="Invitation type"/> <field name="cutype" string="Invitation type"/>
<field name="state" /> <field name="state" />
<field name="rsvp" string="Required to Join"/> <field name="rsvp" string="Required to Join"/>

View File

@ -37,14 +37,14 @@
# USA. # USA.
from random import seed, sample from random import seed, sample
from string import letters, digits from string import ascii_letters, digits
from osv import fields,osv from osv import fields,osv
import pooler import pooler
from tools.translate import _ from tools.translate import _
magic_md5 = '$1$' magic_md5 = '$1$'
def gen_salt( length=8, symbols=letters + digits ): def gen_salt( length=8, symbols=ascii_letters + digits ):
seed() seed()
return ''.join( sample( symbols, length ) ) return ''.join( sample( symbols, length ) )
@ -64,15 +64,16 @@ def gen_salt( length=8, symbols=letters + digits ):
# * # *
# * Poul-Henning Kamp # * Poul-Henning Kamp
from sys import version_info
if version_info < (2,5): #TODO: py>=2.6: from hashlib import md5
from md5 import md5 import hashlib
else:
from hashlib import md5
def encrypt_md5( raw_pw, salt, magic=magic_md5 ): def encrypt_md5( raw_pw, salt, magic=magic_md5 ):
hash = md5( raw_pw + magic + salt ) hash = hashlib.md5()
stretch = md5( raw_pw + salt + raw_pw).digest() hash.update( raw_pw + magic + salt )
st = hashlib.md5()
st.update( raw_pw + salt + raw_pw)
stretch = st.digest()
for i in range( 0, len( raw_pw ) ): for i in range( 0, len( raw_pw ) ):
hash.update( stretch[i % 16] ) hash.update( stretch[i % 16] )
@ -89,7 +90,7 @@ def encrypt_md5( raw_pw, salt, magic=magic_md5 ):
saltedmd5 = hash.digest() saltedmd5 = hash.digest()
for i in range( 1000 ): for i in range( 1000 ):
hash = md5() hash = hashlib.md5()
if i & 1: if i & 1:
hash.update( raw_pw ) hash.update( raw_pw )
@ -166,19 +167,37 @@ class users(osv.osv):
} }
def login(self, db, login, password): def login(self, db, login, password):
cr = pooler.get_db(db).cursor() if not password:
cr.execute('select password, id from res_users where login=%s',
(login.encode('utf-8'),))
stored_pw = id = cr.fetchone()
if stored_pw:
stored_pw = stored_pw[0]
id = id[1]
else:
# Return early if there is no such login.
return False return False
if db is False:
raise RuntimeError("Cannot authenticate to False db!")
cr = None
try:
cr = pooler.get_db(db).cursor()
return self._login(cr, db, login, password)
except Exception:
import logging
logging.getLogger('netsvc').exception('Could not authenticate')
return Exception('Access Denied')
finally:
if cr is not None:
cr.close()
def _login(self, cr, db, login, password):
cr.execute( 'SELECT password, id FROM res_users WHERE login=%s',
(login.encode('utf-8'),))
if cr.rowcount:
stored_pw, id = cr.fetchone()
else:
# Return early if no one has a login name like that.
return False
stored_pw = self.maybe_encrypt(cr, stored_pw, id) stored_pw = self.maybe_encrypt(cr, stored_pw, id)
if not stored_pw:
# means couldn't encrypt or user is not active!
return False
# Calculate an encrypted password from the user-provided # Calculate an encrypted password from the user-provided
# password. # password.
@ -187,12 +206,14 @@ class users(osv.osv):
obj._salt_cache = {} obj._salt_cache = {}
salt = obj._salt_cache[id] = stored_pw[len(magic_md5):11] salt = obj._salt_cache[id] = stored_pw[len(magic_md5):11]
encrypted_pw = encrypt_md5(password, salt) encrypted_pw = encrypt_md5(password, salt)
# Check if the encrypted password matches against the one in the db. # Check if the encrypted password matches against the one in the db.
cr.execute('select id from res_users where id=%s and password=%s and active', (int(id), encrypted_pw.encode('utf-8'))) cr.execute('UPDATE res_users SET date=now() ' \
'WHERE id=%s AND password=%s AND active RETURNING id',
(int(id), encrypted_pw.encode('utf-8')))
res = cr.fetchone() res = cr.fetchone()
cr.close() cr.commit()
if res: if res:
return res[0] return res[0]
else: else:
@ -210,22 +231,28 @@ class users(osv.osv):
return True return True
cr = pooler.get_db(db).cursor() cr = pooler.get_db(db).cursor()
if uid not in obj._salt_cache: try:
cr.execute('select login from res_users where id=%s', (int(uid),)) if uid not in self._salt_cache.get(db, {}):
stored_login = cr.fetchone() # If we don't have cache, we have to repeat the procedure
if stored_login: # through the login function.
stored_login = stored_login[0] cr.execute( 'SELECT login FROM res_users WHERE id=%s', (uid,) )
stored_login = cr.fetchone()
if stored_login:
stored_login = stored_login[0]
res = self._login(cr, db, stored_login, passwd)
if not res:
raise security.ExceptionNoTb('AccessDenied')
else:
salt = self._salt_cache[db][uid]
cr.execute('SELECT COUNT(*) FROM res_users WHERE id=%s AND password=%s',
(int(uid), encrypt_md5(passwd, salt)))
res = cr.fetchone()[0]
finally:
cr.close()
if not self.login(db,stored_login,passwd):
return False
salt = obj._salt_cache[uid]
cr.execute('select count(id) from res_users where id=%s and password=%s',
(int(uid), encrypt_md5(passwd, salt)))
res = cr.fetchone()[0]
cr.close()
if not bool(res): if not bool(res):
raise Exception('AccessDenied') raise security.ExceptionNoTb('AccessDenied')
if res: if res:
if self._uid_cache.has_key(db): if self._uid_cache.has_key(db):
@ -234,20 +261,24 @@ class users(osv.osv):
else: else:
self._uid_cache[db] = {uid: passwd} self._uid_cache[db] = {uid: passwd}
return bool(res) return bool(res)
def maybe_encrypt(self, cr, pw, id): def maybe_encrypt(self, cr, pw, id):
# If the password 'pw' is not encrypted, then encrypt all passwords """ Return the password 'pw', making sure it is encrypted.
# in the db. Returns the (possibly newly) encrypted password for 'id'.
If the password 'pw' is not encrypted, then encrypt all active passwords
in the db. Returns the (possibly newly) encrypted password for 'id'.
"""
if not pw.startswith(magic_md5): if not pw.startswith(magic_md5):
cr.execute('select id, password from res_users') cr.execute("SELECT id, password FROM res_users " \
"WHERE active=true AND password NOT LIKE '$%'")
# Note that we skip all passwords like $.., in anticipation for
# more than md5 magic prefixes.
res = cr.fetchall() res = cr.fetchall()
for i, p in res: for i, p in res:
encrypted = p encrypted = encrypt_md5(p, gen_salt())
if p and not p.startswith(magic_md5): cr.execute('UPDATE res_users SET password=%s where id=%s',
encrypted = encrypt_md5(p, gen_salt()) (encrypted, i))
cr.execute('update res_users set password=%s where id=%s',
(encrypted.encode('utf-8'), int(i)))
if i == id: if i == id:
encrypted_res = encrypted encrypted_res = encrypted
cr.commit() cr.commit()

View File

@ -0,0 +1,27 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * base_crypt
#
# Copyright (C) 2008,2009 P. Christeas <p_christ@hol.gr>
# <> <>, 2009.
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-05-30 15:14:08+0000\n"
"PO-Revision-Date: 2009-03-27 14:30+0200\n"
"Last-Translator: <> <>\n"
"Language-Team: <>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#~ msgid "Module for password encryption."
#~ msgstr "Άρθρωμα για κρυπτογράφηση κωδικών."
#~ msgid "Invalid XML for View Architecture!"
#~ msgstr "Άκυρο XML για αρχιτεκτονική προβολής!"
#~ msgid "Password"
#~ msgstr "Κωδικός"

View File

@ -117,7 +117,7 @@ class res_partner_bank(osv.osv):
iban_country = self.browse(cr, uid, ids)[0].iban[:2] iban_country = self.browse(cr, uid, ids)[0].iban[:2]
if default_iban_check(iban_country): if default_iban_check(iban_country):
iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or '' iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or ''
return _('The IBAN does not seems to be correct. You should have entered something like this %s'), (iban_example) return _('The IBAN does not seem to be correct. You should have entered something like this %s'), (iban_example)
return _('The IBAN is invalid, It should begin with the country code'), () return _('The IBAN is invalid, It should begin with the country code'), ()
def name_get(self, cr, uid, ids, context=None): def name_get(self, cr, uid, ids, context=None):

View File

@ -25,7 +25,7 @@ from StringIO import StringIO
import base64 import base64
import pooler import pooler
import addons import addons
import sys
class report_xml(osv.osv): class report_xml(osv.osv):
_inherit = 'ir.actions.report.xml' _inherit = 'ir.actions.report.xml'
@ -60,12 +60,12 @@ class report_xml(osv.osv):
def report_get(self, cr, uid, report_id, context=None): def report_get(self, cr, uid, report_id, context=None):
report = self.browse(cr, uid, report_id, context=context) report = self.browse(cr, uid, report_id, context=context)
reload(sys) sxw_data=(report.report_sxw_content).encode("iso-8859-1", "replace")
sys.setdefaultencoding( "latin-1" ) rml_data= (report.report_rml_content).encode("iso-8859-1", "replace")
return { return {
'file_type' : report.report_type, 'file_type' : report.report_type,
'report_sxw_content': report.report_sxw_content and base64.encodestring(report.report_sxw_content) or False, 'report_sxw_content': report.report_sxw_content and base64.encodestring(sxw_data) or False,
'report_rml_content': report.report_rml_content and base64.encodestring(report.report_rml_content) or False 'report_rml_content': report.report_rml_content and base64.encodestring(rml_data) or False
} }
report_xml() report_xml()

View File

@ -45,9 +45,15 @@ class base_setup_config_choice(osv.osv_memory):
def get_users(self, cr, uid, context=None): def get_users(self, cr, uid, context=None):
user_obj = self.pool.get('res.users') user_obj = self.pool.get('res.users')
user_ids = user_obj.search(cr, uid, []) user_ids = user_obj.search(cr, uid, [])
users = user_obj.browse(cr, uid, user_ids, context=context) user_list = []
user_str = '\n'.join(map(lambda x: ' - %s :\n\t\tLogin : %s \n\t\tPassword : %s' % (x.name, x.login, x.password), users)) user_tmpl_nopass = _(' - %s :\n\t\tLogin : %s')
return _('The following users have been installed : \n')+ user_str user_tmpl_pass = _(' - %s :\n\t\tLogin : %s \n\t\tPassword : %s')
for user in user_obj.browse(cr, uid, user_ids, context=context):
if user.password and not user.password.startswith('$'):
user_list.append(user_tmpl_pass % (user.name, user.login, user.password))
else:
user_list.append(user_tmpl_nopass % (user.name, user.login))
return _('The following users have been installed : \n')+ '\n'.join(user_list)
_columns = { _columns = {
'installed_users':fields.text('Installed Users', readonly=True), 'installed_users':fields.text('Installed Users', readonly=True),

View File

@ -168,6 +168,7 @@
<field name="res_model">basic.calendar</field> <field name="res_model">basic.calendar</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="help">"Calendars" allow you to Customize calendar event and todo attribute with any of OpenERP model.Caledars provide iCal Import/Export functionality.Webdav server that provides remote access to calendar.Help You to synchronize Meeting with Calendars client.You can access Calendars using CalDAV clients, like sunbird, Calendar Evaluation, Mobile.</field>
</record> </record>
<record id="action_caldav_view1" model="ir.actions.act_window.view"> <record id="action_caldav_view1" model="ir.actions.act_window.view">

View File

@ -164,6 +164,8 @@ def get_attribute_mapping(cr, uid, calname, context=None):
res[attr] = {} res[attr] = {}
res[attr]['field'] = field.field_id.name res[attr]['field'] = field.field_id.name
res[attr]['type'] = field.field_id.ttype res[attr]['type'] = field.field_id.ttype
if field.fn == 'datetime_utc':
res[attr]['type'] = 'utc'
if field.fn == 'hours': if field.fn == 'hours':
res[attr]['type'] = "timedelta" res[attr]['type'] = "timedelta"
if res[attr]['type'] in ('one2many', 'many2many', 'many2one'): if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
@ -254,7 +256,6 @@ class CalDAV(object):
@param name: Get Attribute Name @param name: Get Attribute Name
@param type: Get Attribute Type @param type: Get Attribute Type
""" """
if self.__attribute__.get(name): if self.__attribute__.get(name):
val = self.__attribute__.get(name).get(type, None) val = self.__attribute__.get(name).get(type, None)
valtype = self.__attribute__.get(name).get('type', None) valtype = self.__attribute__.get(name).get('type', None)
@ -273,7 +274,6 @@ class CalDAV(object):
@param self: The object pointer, @param self: The object pointer,
@param type: Get Attribute Type @param type: Get Attribute Type
""" """
for name in self.__attribute__: for name in self.__attribute__:
if self.__attribute__[name]: if self.__attribute__[name]:
self.__attribute__[name][type] = None self.__attribute__[name][type] = None
@ -446,6 +446,21 @@ class CalDAV(object):
dtfield.value = self.format_date_tz(parser.parse(data[map_field]), tzval.title()) dtfield.value = self.format_date_tz(parser.parse(data[map_field]), tzval.title())
else: else:
dtfield.value = parser.parse(data[map_field]) dtfield.value = parser.parse(data[map_field])
elif map_type == 'utc'and data[map_field]:
if tzval:
local = pytz.timezone (tzval.title())
naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
local_dt = naive.replace (tzinfo = local)
utc_dt = local_dt.astimezone (pytz.utc)
vevent.add(field).value = utc_dt
else:
utc_timezone = pytz.timezone ('UTC')
naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
local_dt = naive.replace (tzinfo = utc_timezone)
utc_dt = local_dt.astimezone (pytz.utc)
vevent.add(field).value = utc_dt
elif map_type == "timedelta": elif map_type == "timedelta":
vevent.add(field).value = timedelta(hours=data[map_field]) vevent.add(field).value = timedelta(hours=data[map_field])
elif map_type == "many2one": elif map_type == "many2one":
@ -829,6 +844,7 @@ class basic_calendar_fields(osv.osv):
'fn': fields.selection([('field', 'Use the field'), 'fn': fields.selection([('field', 'Use the field'),
('const', 'Expression as constant'), ('const', 'Expression as constant'),
('hours', 'Interval in hours'), ('hours', 'Interval in hours'),
('datetime_utc', 'Datetime In UTC'),
], 'Function'), ], 'Function'),
'mapping': fields.text('Mapping'), 'mapping': fields.text('Mapping'),
} }

View File

@ -126,6 +126,7 @@ Create dashboard for CRM that includes:
'test/test_crm_opportunity.yml', 'test/test_crm_opportunity.yml',
'test/test_crm_phonecall.yml', 'test/test_crm_phonecall.yml',
'test/test_crm_recurrent_meeting.yml', 'test/test_crm_recurrent_meeting.yml',
'test/test_crm_stage_changes.yml',
], ],
'installable': True, 'installable': True,
'active': False, 'active': False,

View File

@ -54,7 +54,7 @@
<tree string="Phone Calls" colors="gray:state in ('draft', 'cancel','done','pending')"> <tree string="Phone Calls" colors="gray:state in ('draft', 'cancel','done','pending')">
<field name="date" string="Date"/> <field name="date" string="Date"/>
<field name="name" string="Call Summary"/> <field name="name" string="Call Summary"/>
<field name="categ_id"/> <field name="categ_id" string="Type" widget="selection"/>
<field name="user_id"/> <field name="user_id"/>
<field name="state"/> <field name="state"/>
<button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/> <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/>

View File

@ -0,0 +1,90 @@
-
In order to test the changes on stage of a lead or an opportunity with OpenERP,
I create some leads and test the stage changes.
-
I want to change the probability to 0.0 when the opportunity is marked as lost.
So I set its Change probability automatically true.
-
!record {model: crm.case.stage, id: crm.stage_opportunity6}:
name: Lost
on_change: true
probability: 0.0
section_ids:
- crm.section_sales_department
sequence: 0.0
type: opportunity
-
I create a lead 'OpenERP Presentation'.
-
!record {model: crm.lead, id: crm_lead_openerppresentation0}:
categ_id: crm.categ_oppor4
day_close: 0.0
day_open: 0.0
name: OpenERP Presentation
planned_revenue: 0.0
probability: 0.0
section_id: crm.section_sales_department
-
I open the lead.
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref('crm_lead_openerppresentation0')])
-
I find that this lead can be converted to opportunity.
-
!record {model: crm.lead2opportunity, id: crm_lead2opportunity0}:
name: OpenERP Presentation
probability: 60.0
planned_revenue: 45000.0
-
So I convert the lead to opportunity.
-
!python {model: crm.lead2opportunity}: |
self.action_apply(cr, uid, [ref('crm_lead2opportunity0')], context={'active_id': ref('crm_lead_openerppresentation0')})
-
I check that lead is now converted to opportunity.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref('crm_lead_openerppresentation0'))
assert lead.type == 'opportunity', 'Lead is not converted to opportunity!'
-
I mark this opportunity as lost.
-
!python {model: crm.lead}: |
self.case_mark_lost(cr, uid, [ref('crm_lead_openerppresentation0')])
-
Now I check whether the probability is set according to stage change or not.
-
!python {model: crm.lead}: |
opportunity = self.browse(cr, uid, ref('crm_lead_openerppresentation0'))
assert opportunity.stage_id.id == ref('crm.stage_opportunity6'), 'Stage is not changed!'
assert opportunity.probability == 0.0, 'Probability is wrong!'
-
I create one more opportunity.
-
!record {model: crm.lead, id: crm_lead_partnerdemo0}:
categ_id: crm.categ_oppor3
day_close: 0.0
day_open: 0.0
name: Partner Demo
planned_revenue: 50000.0
probability: 70.0
section_id: crm.section_sales_department
type: opportunity
-
I open this opportunity.
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref('crm_lead_partnerdemo0')])
-
I mark this opportunity as won.
-
!python {model: crm.lead}: |
self.case_close(cr, uid, [ref('crm_lead_partnerdemo0')])
-
I check whether the stage is changed to 'Won' and probability is 100.0 or not.
-
!python {model: crm.lead}: |
opportunity = self.browse(cr, uid, ref('crm_lead_partnerdemo0'))
assert opportunity.stage_id.id == ref('crm.stage_opportunity5'), 'Stage is not changed!'
assert opportunity.probability == 100.0, 'Probability is wrong!'

View File

@ -137,7 +137,7 @@ class crm_opportunity2phonecall(osv.osv_memory):
'view_mode': 'tree,form', 'view_mode': 'tree,form',
'res_model': 'crm.phonecall', 'res_model': 'crm.phonecall',
'res_id' : new_case, 'res_id' : new_case,
'views': [(id3, 'form'), (id2, 'tree'), (False, 'calendar'), (False, 'graph')], 'views': [(id3, 'form'), (id2, 'tree'), (False, 'calendar')],
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'search_view_id': res['res_id'] 'search_view_id': res['res_id']
} }

View File

@ -117,8 +117,8 @@
<record model="basic.calendar.fields" id="map_event_13"> <record model="basic.calendar.fields" id="map_event_13">
<field name="name" ref="caldav.field_event_dtstamp"/> <field name="name" ref="caldav.field_event_dtstamp"/>
<field name="type_id" ref="base_calendar.calendar_lines_event" /> <field name="type_id" ref="base_calendar.calendar_lines_event" />
<field name="field_id" search="[('name','=','date'),('model_id.model','=','calendar.event')]" /> <field name="field_id" search="[('name','=','write_date'),('model_id.model','=','crm.meeting')]" />
<field name="fn">field</field> <field name="fn">datetime_utc</field>
</record> </record>
<record model="basic.calendar.fields" id="map_event_14"> <record model="basic.calendar.fields" id="map_event_14">

View File

@ -176,8 +176,8 @@
<record id="basic_calendar_fields_24" model="basic.calendar.fields"> <record id="basic_calendar_fields_24" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_dtstamp"/> <field name="name" ref="caldav.field_event_dtstamp"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/> <field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_date"/> <field name="field_id" ref="crm.field_crm_meeting_write_date"/>
<field name="fn">field</field> <field name="fn">datetime_utc</field>
</record> </record>
<record id="basic_calendar_fields_25" model="basic.calendar.fields"> <record id="basic_calendar_fields_25" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_description"/> <field name="name" ref="caldav.field_event_description"/>

View File

@ -53,6 +53,8 @@
<field name="action_next"/> <field name="action_next"/>
<field name="categ_id" string="Type" select="1"/> <field name="categ_id" string="Type" select="1"/>
<field name="stage_id" invisible="1"/> <field name="stage_id" invisible="1"/>
<field name="date_deadline" invisible="1"/>
<field name="date_closed" invisible="1"/>
<field name="state"/> <field name="state"/>
<button name="case_open" string="Open" <button name="case_open" string="Open"
states="draft,pending" type="object" states="draft,pending" type="object"
@ -83,7 +85,7 @@
<field name="section_id" widget="selection" /> <field name="section_id" widget="selection" />
<group colspan="2" col="4"> <group colspan="2" col="4">
<field name="stage_id" domain="[('type','=','claim'),('section_ids', '=', section_id)]"/> <field name="stage_id" domain="[('type','=','claim')]"/>
<button name="stage_previous" string="" type="object" icon="gtk-go-back" /> <button name="stage_previous" string="" type="object" icon="gtk-go-back" />
<button icon="gtk-go-forward" string="" name="stage_next" type="object"/> <button icon="gtk-go-forward" string="" name="stage_next" type="object"/>
</group> </group>

View File

@ -66,6 +66,7 @@ class crm_claim_report(osv.osv):
'categ_id': fields.many2one('crm.case.categ', 'Category',\ 'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\ domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]", readonly=True), ('object_id.model', '=', 'crm.claim')]", readonly=True),
'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"),
'partner_id': fields.many2one('res.partner', 'Partner', readonly=True), 'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True),
'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'), 'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'),

View File

@ -18,6 +18,7 @@
<field name="partner_id" invisible="1"/> <field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/> <field name="day" invisible="1"/>
<field name="nbr" string="#Claim" sum="#Claim"/> <field name="nbr" string="#Claim" sum="#Claim"/>
<field name="email" sum="# Mails"/>
<field name="delay_close" avg="Avg Closing Delay"/> <field name="delay_close" avg="Avg Closing Delay"/>
<field name="email"/> <field name="email"/>
<field name="probability" widget="progressbar"/> <field name="probability" widget="progressbar"/>

View File

@ -60,4 +60,4 @@ def get_precision(application):
return (16, res) return (16, res)
return change_digit return change_digit
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -35,6 +35,36 @@ DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['roo
class document_file(osv.osv): class document_file(osv.osv):
_inherit = 'ir.attachment' _inherit = 'ir.attachment'
_rec_name = 'datas_fname' _rec_name = 'datas_fname'
def _attach_parent_id(self, cr, uid, ids=None, context=None):
"""Migrate ir.attachments to the document module.
When the 'document' module is loaded on a db that has had plain attachments,
they will need to be attached to some parent folder, and be converted from
base64-in-bytea to raw-in-bytea format.
This function performs the internal migration, once and forever, for these
attachments. It cannot be done through the nominal ORM maintenance code,
because the root folder is only created after the document_data.xml file
is loaded.
It also establishes the parent_id NOT NULL constraint that ir.attachment
should have had (but would have failed if plain attachments contained null
values).
"""
parent_id = self.pool.get('document.directory')._get_root_directory(cr,uid)
if not parent_id:
logging.getLogger('document').warning("at _attach_parent_id(), still not able to set the parent!")
return False
if ids is not None:
raise NotImplementedError("Ids is just there by convention! Don't use it yet, please.")
cr.execute("UPDATE ir_attachment " \
"SET parent_id = %s, db_datas = decode(encode(db_datas,'escape'), 'base64') " \
"WHERE parent_id IS NULL", (parent_id,))
cr.execute("ALTER TABLE ir_attachment ALTER parent_id SET NOT NULL")
return True
def _get_filestore(self, cr): def _get_filestore(self, cr):
return os.path.join(DMS_ROOT_PATH, cr.dbname) return os.path.join(DMS_ROOT_PATH, cr.dbname)
@ -135,6 +165,15 @@ class document_file(osv.osv):
return False return False
return True return True
def check(self, cr, uid, ids, mode, context=None, values=None):
"""Check access wrt. res_model, relax the rule of ir.attachment parent
With 'document' installed, everybody will have access to attachments of
any resources they can *read*.
"""
return super(document_file, self).check(cr, uid, ids, mode='read',
context=context, values=values)
def copy(self, cr, uid, id, default=None, context=None): def copy(self, cr, uid, id, default=None, context=None):
if not default: if not default:
default = {} default = {}
@ -168,7 +207,7 @@ class document_file(osv.osv):
ids2 = [] ids2 = []
for fbro in self.browse(cr, uid, ids, context=context): for fbro in self.browse(cr, uid, ids, context=context):
if ('parent_id' not in vals or fbro.parent_id.id == vals['parent_id']) \ if ('parent_id' not in vals or fbro.parent_id.id == vals['parent_id']) \
and ('name' not in vals or fbro.name == vals['name']) : and ('name' not in vals or fbro.name == vals['name']):
ids2.append(fbro.id) ids2.append(fbro.id)
continue continue
fnode = nctx.get_file_node(cr, fbro) fnode = nctx.get_file_node(cr, fbro)

View File

@ -99,8 +99,11 @@
<field name="user_id" eval="False"/> <field name="user_id" eval="False"/>
<field name="parent_id" ref="dir_root"/> <field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field> <field name="ressource_id">0</field>
</record> </record>
<!-- After we have setup the root directory, migrate the attachments
to point to that. -->
<function model="ir.attachment" name="_attach_parent_id"/>
</data> </data>
</openerp> </openerp>

View File

@ -95,13 +95,9 @@ class DocIndex(indexer):
return ['.doc'] return ['.doc']
def _doIndexFile(self,fname): def _doIndexFile(self,fname):
fp = Popen(['antiword', fname], shell=False, stdout=PIPE).stdout pop = Popen(['antiword', fname], shell=False, stdout=PIPE)
try: (data, _) = pop.communicate()
file_data = _to_unicode(fp.read()) return _to_unicode(data)
finally:
fp.close()
return file_data
cntIndex.register(DocIndex()) cntIndex.register(DocIndex())
@ -162,13 +158,9 @@ class PdfIndex(indexer):
return ['.pdf'] return ['.pdf']
def _doIndexFile(self,fname): def _doIndexFile(self,fname):
fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout pop = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE)
try: (data, _) = pop.communicate()
file_data = _to_unicode( fp.read()) return _to_unicode(data)
finally:
fp.close()
return file_data
cntIndex.register(PdfIndex()) cntIndex.register(PdfIndex())

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# OpenERP, Open Source Management Solution # OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# #
@ -15,22 +15,24 @@
# GNU Affero General Public License for more details. # GNU Affero General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
from osv import osv, fields from osv import osv, fields
class document_configuration(osv.osv_memory): class document_configuration(osv.osv_memory):
_name='document.configuration' _name='document.configuration'
_description = 'Auto Directory Configuration' _description = 'Auto Directory Configuration'
_inherit = 'res.config' _inherit = 'res.config'
_columns = { _columns = {
'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."), 'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."),
'product' : fields.boolean('Product', help="Auto directory configuration for Products."), 'product' : fields.boolean('Product', help="Auto directory configuration for Products."),
'project': fields.boolean('Project', help="Auto directory configuration for Projects."), 'project': fields.boolean('Project', help="Auto directory configuration for Projects."),
} }
def execute(self, cr, uid, ids, context=None): def execute(self, cr, uid, ids, context=None):
conf_id = ids and ids[0] or False conf_id = ids and ids[0] or False
@ -58,7 +60,7 @@ class document_configuration(osv.osv_memory):
quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'}) quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'})
dir_pool.write(cr, uid, [quta_dir_id], { dir_pool.write(cr, uid, [quta_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
@ -86,7 +88,7 @@ class document_configuration(osv.osv_memory):
'include_name': 1, 'include_name': 1,
'directory_id': quta_dir_id, 'directory_id': quta_dir_id,
}) })
if conf.product and self.pool.get('product.product'): if conf.product and self.pool.get('product.product'):
# Product # Product
@ -95,12 +97,12 @@ class document_configuration(osv.osv_memory):
product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
product_dir_id = data_pool.create(cr, uid, {'name': 'Products'}) product_dir_id = data_pool.create(cr, uid, {'name': 'Products'})
mid = model_pool.search(cr, uid, [('model','=','product.product')]) mid = model_pool.search(cr, uid, [('model','=','product.product')])
dir_pool.write(cr, uid, [product_dir_id], { dir_pool.write(cr, uid, [product_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
}) })
if conf.project and self.pool.get('account.analytic.account'): if conf.project and self.pool.get('account.analytic.account'):
# Project # Project
@ -109,12 +111,12 @@ class document_configuration(osv.osv_memory):
project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'}) project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'})
mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')]) mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')])
dir_pool.write(cr, uid, [project_dir_id], { dir_pool.write(cr, uid, [project_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
'domain': '[]', 'domain': '[]',
'ressource_tree': 1 'ressource_tree': 1
}) })
document_configuration() document_configuration()

View File

@ -45,6 +45,8 @@
<record model="ir.actions.todo" id="config_auto_directory"> <record model="ir.actions.todo" id="config_auto_directory">
<field name="action_id" ref="action_config_auto_directory"/> <field name="action_id" ref="action_config_auto_directory"/>
<field name="groups_id" eval="[(6,0,[ref('base.group_extended')])]"/> <field name="groups_id" eval="[(6,0,[ref('base.group_extended')])]"/>
<field name="state" eval="'skip'" />
<field name="restart" eval="'onskip'" />
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -37,7 +37,12 @@ def get_plain_ftp(timeout=10.0):
def get_ftp_login(cr, uid, ormobj): def get_ftp_login(cr, uid, ormobj):
ftp = get_plain_ftp() ftp = get_plain_ftp()
user = ormobj.pool.get('res.users').read(cr, uid, uid) user = ormobj.pool.get('res.users').read(cr, uid, uid)
ftp.login(user.get('login',''), user.get('login','')) passwd = user.get('password','')
if passwd.startswith("$1$"):
# md5 by base crypt. We cannot decode, wild guess
# that passwd = login
passwd = user.get('login', '')
ftp.login(user.get('login',''), passwd)
ftp.cwd("/" + cr.dbname) ftp.cwd("/" + cr.dbname)
return ftp return ftp

View File

@ -43,9 +43,11 @@ class document_ftp_browse(osv.osv_memory):
url = ftp_url.url and ftp_url.url.split('ftp://') or [] url = ftp_url.url and ftp_url.url.split('ftp://') or []
if url: if url:
url = url[1] url = url[1]
if url[-1] == '/':
url = url[:-1]
else: else:
url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT) url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT)
res['url'] = 'ftp://%s@%s'%(current_user.login, url) res['url'] = 'ftp://%s@%s/%s'%(current_user.login, url, cr.dbname)
return res return res
def browse_ftp(self, cr, uid, ids, context=None): def browse_ftp(self, cr, uid, ids, context=None):

View File

@ -44,6 +44,7 @@ class document_ftp_configuration(osv.osv_memory):
# Update the action for FTP browse. # Update the action for FTP browse.
aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse') aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse')
aid = data_pool.browse(cr, uid, aid, context=context).res_id aid = data_pool.browse(cr, uid, aid, context=context).res_id
self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost:8021')+'/'}) self.pool.get('ir.actions.url').write(cr, uid, [aid],
{'url': 'ftp://'+(conf.host or 'localhost:8021')+'/' + cr.dbname+'/'})
document_ftp_configuration() document_ftp_configuration()

View File

@ -30,7 +30,7 @@
{ {
"name" : "WebDAV server for Document Management", "name" : "WebDAV server for Document Management",
"version" : "2.2", "version" : "2.3",
"author" : "OpenERP SA", "author" : "OpenERP SA",
"category" : "Generic Modules/Others", "category" : "Generic Modules/Others",
"website": "http://www.openerp.com", "website": "http://www.openerp.com",
@ -49,6 +49,9 @@
; since the messages are routed to the python logging, with ; since the messages are routed to the python logging, with
; levels "debug" and "debug_rpc" respectively, you can leave ; levels "debug" and "debug_rpc" respectively, you can leave
; these options on ; these options on
Also implements IETF RFC 5785 for services discovery on a http server,
which needs explicit configuration in openerp-server.conf, too.
""", """,
"depends" : ["base", "document"], "depends" : ["base", "document"],
"init_xml" : [], "init_xml" : [],

View File

@ -686,7 +686,7 @@ class openerp_dav_handler(dav_interface):
except Exception: except Exception:
node = False node = False
objname = uri2[-1] objname = misc.ustr(uri2[-1])
ret = None ret = None
if not node: if not node:
@ -719,7 +719,7 @@ class openerp_dav_handler(dav_interface):
etag = str(newchild.get_etag(cr)) etag = str(newchild.get_etag(cr))
except Exception, e: except Exception, e:
self.parent.log_error("Cannot get etag for node: %s" % e) self.parent.log_error("Cannot get etag for node: %s" % e)
ret = (hurl, etag) ret = (str(hurl), etag)
else: else:
self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr) self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)

View File

@ -0,0 +1,29 @@
=================
Well-known URIs
=================
In accordance to IETF RFC 5785 [1], we shall publish a few locations
on the root of our http server, so that clients can discover our
services (CalDAV, eg.).
This module merely installs a special http request handler, that will
redirect the URIs from "http://our-server:port/.well-known/xxx' to
the correct path for each xxx service.
Note that well-known URIs cannot have a database-specific behaviour,
they are server-wide. So, we have to explicitly chose one of our databases
to serve at them. By default, the database of the configuration file
is chosen
Example config:
[http-well-known]
num_services = 2
db_name = openerp-main ; must define that for path_1 below
service_1 = caldav
path_1 = /webdav/%(db_name)s/Calendars/
service_2 = foo
path_2 = /other_db/static/Foo.html
[1] http://www.rfc-editor.org/rfc/rfc5785.txt

View File

@ -299,7 +299,7 @@ class node_file(node_acl_mixin, nodes.node_file):
return '' return ''
def get_dav_props(self, cr): def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir, return self._get_dav_props_hlpr(cr, nodes.node_file,
'document.webdav.file.property', 'file_id', self.file_id) 'document.webdav.file.property', 'file_id', self.file_id)
def dav_lock(self, cr, lock_data): def dav_lock(self, cr, lock_data):
@ -345,11 +345,11 @@ class node_database(nodes.node_database):
return ('collection', 'DAV:') return ('collection', 'DAV:')
def get_dav_props(self, cr): def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir, return self._get_dav_props_hlpr(cr, nodes.node_database,
'document.webdav.dir.property', 'dir_id', False) 'document.webdav.dir.property', 'dir_id', False)
def get_dav_eprop(self, cr, ns, prop): def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop, return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop,
'document.webdav.dir.property', 'dir_id', False) 'document.webdav.dir.property', 'dir_id', False)
class node_res_obj(node_acl_mixin, nodes.node_res_obj): class node_res_obj(node_acl_mixin, nodes.node_res_obj):

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>OpenERP server</title>
</head>
<body>
This is an OpenERP server. Nothing to GET here.
</body>
</html>

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010 OpenERP s.a. (<http://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 logging
import urlparse
from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions
from service.http_server import HttpLogHandler
class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
_logger = logging.getLogger('httpd.well-known')
_HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD', 'PROPFIND'] }
redirect_paths = {}
def __init__(self,request, client_address, server):
HTTPHandler.__init__(self,request,client_address,server)
def send_head(self):
"""Common code for GET and HEAD commands.
It will either send the correct redirect (Location) response
or a 404.
"""
if self.path.endswith('/'):
self.path = self.path[:-1]
if not self.path:
# Return an empty page
self.send_response(200)
self.send_header("Content-Length", 0)
self.end_headers()
return None
redir_path = self._find_redirect()
if redir_path is False:
self.send_error(404, "File not found")
return None
elif redir_path is None:
return None
server_proto = getattr(self.server, 'proto', 'http').lower()
addr, port = self.server.server_name, self.server.server_port
try:
addr, port = self.request.getsockname()
except Exception, e:
self.log_error("Cannot calculate own address:" , e)
if self.headers.has_key('Host'):
uparts = list(urlparse.urlparse("%s://%s:%d"% (server_proto, addr,port)))
uparts[1] = self.headers['Host']
baseuri = urlparse.urlunparse(uparts)
else:
baseuri = "%s://%s:%d"% (server_proto, addr, port )
location = baseuri + redir_path
# relative uri: location = self.redirect_paths[self.path]
self.send_response(301)
self.send_header("Location", location)
self.send_header("Content-Length", 0)
self.end_headers()
# Do we need a Cache-content: header here?
self._logger.debug("redirecting %s to %s", self.path, redir_path)
return None
def do_PROPFIND(self):
self._get_ignore_body()
return self.do_HEAD()
def _find_redirect(self):
""" Locate self.path among the redirects we can handle
@return The new path, False for 404 or None for already sent error
"""
return self.redirect_paths.get(self.path, False)
def _get_ignore_body(self):
if not self.headers.has_key("content-length"):
return
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
got = ''
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
got = self.rfile.read(chunk_size)
size_remaining -= len(got)
#eof

View File

@ -365,6 +365,10 @@ class DAVClient(object):
assert res, "uid %s not found" % uid assert res, "uid %s not found" % uid
self.user = res[0]['login'] self.user = res[0]['login']
self.passwd = res[0]['password'] self.passwd = res[0]['password']
if self.passwd.startswith('$1$'):
# md5 by base crypt. We cannot decode, wild guess
# that passwd = login
self.passwd = self.user
return True return True
def set_useragent(self, uastr): def set_useragent(self, uastr):

View File

@ -45,13 +45,103 @@ class Text2(xml.dom.minidom.Text):
data = data.replace(">", "&gt;") data = data.replace(">", "&gt;")
writer.write(data) writer.write(data)
def createText2Node(doc, data): class Prop2xml(object):
if not isinstance(data, StringTypes): """ A helper class to convert property structs to DAV:XML
raise TypeError, "node contents must be a string"
t = Text2() Written to generalize the use of _prop_child(), a class is
t.data = data needed to hold some persistent data accross the recursions
t.ownerDocument = doc of _prop_elem_child().
return t """
def __init__(self, doc, namespaces, nsnum):
""" Init the structure
@param doc the xml doc element
@param namespaces a dict of namespaces
@param nsnum the next namespace number to define
"""
self.doc = doc
self.namespaces = namespaces
self.nsnum = nsnum
def createText2Node(self, data):
if not isinstance(data, StringTypes):
raise TypeError, "node contents must be a string"
t = Text2()
t.data = data
t.ownerDocument = self.doc
return t
def _prop_child(self, xnode, ns, prop, value):
"""Append a property xml node to xnode, with <prop>value</prop>
And a little smarter than that, it will consider namespace and
also allow nested properties etc.
:param ns the namespace of the <prop/> node
:param prop the name of the property
:param value the value. Can be:
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
tuple ('elem', 'ns', sub-elems) for sub-node with elements
tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with
optional elements and attributes
list, of above tuples
"""
if ns == 'DAV:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
pe = self.doc.createElement(ns_prefix+str(prop))
if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
pe.appendChild(value)
else:
if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
# hack, to go..
if value == 1:
ve = self.doc.createElement("D:collection")
pe.appendChild(ve)
else:
self._prop_elem_child(pe, ns, value, ns_prefix)
xnode.appendChild(pe)
def _prop_elem_child(self, pnode, pns, v, pns_prefix):
if isinstance(v, list):
for vit in v:
self._prop_elem_child(pnode, pns, vit, pns_prefix)
elif isinstance(v,tuple):
need_ns = False
if v[1] == pns:
ns_prefix = pns_prefix
elif v[1] == 'DAV:':
ns_prefix = 'D:'
elif v[1] in self.namespaces:
ns_prefix="ns"+str(self.namespaces.index(v[1]))+":"
else:
ns_prefix="ns"+str(self.nsnum)+":"
need_ns = True
ve = self.doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(self.nsnum), v[1])
if len(v) > 2 and v[2] is not None:
if isinstance(v[2], (list, tuple)):
# support nested elements like:
# ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
self._prop_elem_child(ve, v[1], v[2], ns_prefix)
else:
vt = self.createText2Node(tools.ustr(v[2]))
ve.appendChild(vt)
if len(v) > 3 and v[3]:
assert isinstance(v[3], dict)
for ak, av in v[3].items():
ve.setAttribute(ak, av)
pnode.appendChild(ve)
else:
ve = self.createText2Node(tools.ustr(v))
pnode.appendChild(ve)
super_mk_prop_response = PROPFIND.mk_prop_response super_mk_prop_response = PROPFIND.mk_prop_response
@ -73,78 +163,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
re.setAttribute("xmlns:ns"+str(nsnum),nsname) re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1 nsnum=nsnum+1
def _prop_child(xnode, ns, prop, value): propgen = Prop2xml(doc, namespaces, nsnum)
"""Append a property xml node to xnode, with <prop>value</prop>
And a little smarter than that, it will consider namespace and
also allow nested properties etc.
:param ns the namespace of the <prop/> node
:param prop the name of the property
:param value the value. Can be:
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
tuple ('elem', 'ns', sub-elems) for sub-node with elements
tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with
optional elements and attributes
list, of above tuples
"""
if ns == 'DAV:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(namespaces.index(ns))+":"
pe=doc.createElement(ns_prefix+str(prop))
if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
pe.appendChild(value)
else:
if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
# hack, to go..
if value == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
else:
_prop_elem_child(pe, ns, value, ns_prefix)
xnode.appendChild(pe)
def _prop_elem_child(pnode, pns, v, pns_prefix):
if isinstance(v, list):
for vit in v:
_prop_elem_child(pnode, pns, vit, pns_prefix)
elif isinstance(v,tuple):
need_ns = False
if v[1] == pns:
ns_prefix = pns_prefix
elif v[1] == 'DAV:':
ns_prefix = 'D:'
elif v[1] in namespaces:
ns_prefix="ns"+str(namespaces.index(v[1]))+":"
else:
ns_prefix="ns"+str(nsnum)+":"
need_ns = True
ve=doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
if len(v) > 2 and v[2] is not None:
if isinstance(v[2], (list, tuple)):
# support nested elements like:
# ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
_prop_elem_child(ve, v[1], v[2], ns_prefix)
else:
vt=createText2Node(doc,tools.ustr(v[2]))
ve.appendChild(vt)
if len(v) > 3 and v[3]:
assert isinstance(v[3], dict)
for ak, av in v[3].items():
ve.setAttribute(ak, av)
pnode.appendChild(ve)
else:
ve=createText2Node(doc, tools.ustr(v))
pnode.appendChild(ve)
# write href information # write href information
uparts=urlparse.urlparse(uri) uparts=urlparse.urlparse(uri)
fileloc=uparts[2] fileloc=uparts[2]
@ -180,7 +199,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
for p,v in good_props[ns].items(): for p,v in good_props[ns].items():
if v is None: if v is None:
continue continue
_prop_child(gp, ns, p, v) propgen._prop_child(gp, ns, p, v)
ps.appendChild(gp) ps.appendChild(gp)
re.appendChild(ps) re.appendChild(ps)

View File

@ -1,7 +1,8 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
############################################################################9
# #
# Copyright P. Christeas <p_christ@hol.gr> 2008-2010 # Copyright P. Christeas <p_christ@hol.gr> 2008-2010
# Copyright OpenERP SA, 2010 (http://www.openerp.com )
# #
# Disclaimer: Many of the functions below borrow code from the # Disclaimer: Many of the functions below borrow code from the
# python-webdav library (http://code.google.com/p/pywebdav/ ), # python-webdav library (http://code.google.com/p/pywebdav/ ),
@ -19,7 +20,7 @@
# #
# This program is Free Software; you can redistribute it and/or # This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
@ -33,20 +34,26 @@
############################################################################### ###############################################################################
import logging
import netsvc import netsvc
from dav_fs import openerp_dav_handler from dav_fs import openerp_dav_handler
from tools.config import config from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler from DAV.WebDAVServer import DAVRequestHandler
from service import http_server
from service.websrv_lib import HTTPDir, FixSendError, HttpOptions from service.websrv_lib import HTTPDir, FixSendError, HttpOptions
from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse import urlparse
import urllib import urllib
import re import re
import time
from string import atoi from string import atoi
from DAV.errors import * import addons
from DAV.utils import IfParser, TagList from DAV.utils import IfParser, TagList
from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
from DAV.propfind import PROPFIND
# from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
from xml.dom import minidom from xml.dom import minidom
from redirect import RedirectHTTPHandler
khtml_re = re.compile(r' KHTML/([0-9\.]+) ') khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
@ -66,6 +73,7 @@ def OpenDAVConfig(**kw):
class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
verbose = False verbose = False
_logger = logging.getLogger('webdav')
protocol_version = 'HTTP/1.1' protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1', '2'], _HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
@ -75,8 +83,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
def get_userinfo(self,user,pw): def get_userinfo(self,user,pw):
return False return False
def _log(self, message): def _log(self, message):
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message) self._logger.debug(message)
def handle(self): def handle(self):
self._init_buffer() self._init_buffer()
@ -118,10 +127,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
return self.davpath return self.davpath
def log_message(self, format, *args): def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args) self._logger.log(netsvc.logging.DEBUG_RPC,format % args)
def log_error(self, format, *args): def log_error(self, format, *args):
netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args) self._logger.warning(format % args)
def _prep_OPTIONS(self, opts): def _prep_OPTIONS(self, opts):
ret = opts ret = opts
@ -415,6 +424,140 @@ class DAVAuthProvider(OpenERPAuthProvider):
return True return True
return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address) return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address)
class dummy_dav_interface(object):
""" Dummy dav interface """
verbose = True
PROPS={"DAV:" : ('creationdate',
'displayname',
'getlastmodified',
'resourcetype',
),
}
M_NS={"DAV:" : "_get_dav", }
def __init__(self, parent):
self.parent = parent
def get_propnames(self,uri):
return self.PROPS
def get_prop(self,uri,ns,propname):
if self.M_NS.has_key(ns):
prefix=self.M_NS[ns]
else:
raise DAV_NotFound
mname=prefix+"_"+propname.replace('-', '_')
try:
m=getattr(self,mname)
r=m(uri)
return r
except AttributeError:
raise DAV_NotFound
def get_data(self, uri, range=None):
raise DAV_NotFound
def _get_dav_creationdate(self,uri):
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _get_dav_getlastmodified(self,uri):
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
def _get_dav_displayname(self, uri):
return uri
def _get_dav_resourcetype(self, uri):
return ('collection', 'DAV:')
def exists(self, uri):
""" return 1 or None depending on if a resource exists """
uri2 = uri.split('/')
if len(uri2) < 3:
return True
logging.getLogger('webdav').debug("Requested uri: %s", uri)
return None # no
def is_collection(self, uri):
""" return 1 or None depending on if a resource is a collection """
return None # no
class DAVStaticHandler(http_server.StaticHTTPHandler):
""" A variant of the Static handler, which will serve dummy DAV requests
"""
verbose = False
protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD',
'PROPFIND', 'OPTIONS', 'REPORT', ]
}
def send_body(self, content, code, message='OK', content_type='text/xml'):
self.send_response(int(code), message)
self.send_header("Content-Type", content_type)
# self.send_header('Connection', 'close')
self.send_header('Content-Length', len(content) or 0)
self.end_headers()
if hasattr(self, '_flush'):
self._flush()
if self.command != 'HEAD':
self.wfile.write(content)
def do_PROPFIND(self):
"""Answer to PROPFIND with generic data.
A rough copy of python-webdav's do_PROPFIND, but hacked to work
statically.
"""
dc = dummy_dav_interface(self)
# read the body containing the xml request
# iff there is no body then this is an ALLPROP request
body = None
if self.headers.has_key('Content-Length'):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
path = self.path.rstrip('/')
uri = urllib.unquote(path)
pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
try:
DATA = '%s\n' % pf.createResponse()
except DAV_Error, (ec,dd):
return self.send_error(ec,dd)
except Exception:
self.log_exception("Cannot PROPFIND")
raise
# work around MSIE DAV bug for creation and modified date
# taken from Resource.py @ Zope webdav
if (self.headers.get('User-Agent') ==
'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
'<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
'<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
self.send_body(DATA, '207','Multi-Status','Multiple responses')
def not_get_baseuri(self):
baseuri = '/'
if self.headers.has_key('Host'):
uparts = list(urlparse.urlparse('/'))
uparts[1] = self.headers['Host']
baseuri = urlparse.urlunparse(uparts)
return baseuri
def get_davpath(self):
return ''
try: try:
if (config.get_misc('webdav','enable',True)): if (config.get_misc('webdav','enable',True)):
@ -430,10 +573,80 @@ try:
conf = OpenDAVConfig(**_dc) conf = OpenDAVConfig(**_dc)
handler._config = conf handler._config = conf
reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider())) reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider()))
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% directory) logging.getLogger('webdav').info("WebDAV service registered at path: %s/ "% directory)
if not (config.get_misc('webdav', 'no_root_hack', False)):
# Now, replace the static http handler with the dav-enabled one.
# If a static-http service has been specified for our server, then
# read its configuration and use that dir_path.
# NOTE: this will _break_ any other service that would be registered
# at the root path in future.
base_path = False
if config.get_misc('static-http','enable', False):
base_path = config.get_misc('static-http', 'base_path', '/')
if base_path and base_path == '/':
dir_path = config.get_misc('static-http', 'dir_path', False)
else:
dir_path = addons.get_module_resource('document_webdav','public_html')
# an _ugly_ hack: we put that dir back in tools.config.misc, so that
# the StaticHttpHandler can find its dir_path.
config.misc.setdefault('static-http',{})['dir_path'] = dir_path
if reg_http_service(HTTPDir('/', DAVStaticHandler)):
logging.getLogger("web-services").info("WebDAV registered HTTP dir %s for /" % \
(dir_path))
except Exception, e: except Exception, e:
logger = netsvc.Logger() logging.getLogger('webdav').error('Cannot launch webdav: %s' % e)
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
def init_well_known():
reps = RedirectHTTPHandler.redirect_paths
num_svcs = config.get_misc('http-well-known', 'num_services', '0')
for nsv in range(1, int(num_svcs)+1):
uri = config.get_misc('http-well-known', 'service_%d' % nsv, False)
path = config.get_misc('http-well-known', 'path_%d' % nsv, False)
if not (uri and path):
continue
reps['/'+uri] = path
if int(num_svcs):
if http_server.reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)):
logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" )
init_well_known()
class PrincipalsRedirect(RedirectHTTPHandler):
redirect_paths = {}
def _find_redirect(self):
for b, r in self.redirect_paths.items():
if self.path.startswith(b):
return r + self.path[len(b):]
return False
def init_principals_redirect():
""" Some devices like the iPhone will look under /principals/users/xxx for
the user's properties. In OpenERP we _cannot_ have a stray /principals/...
working path, since we have a database path and the /webdav/ component. So,
the best solution is to redirect the url with 301. Luckily, it does work in
the device. The trick is that we need to hard-code the database to use, either
the one centrally defined in the config, or a "forced" one in the webdav
section.
"""
dbname = config.get_misc('webdav', 'principal_dbname', False)
if (not dbname) and not config.get_misc('webdav', 'no_principals_redirect', False):
dbname = config.get('db_name', False)
if dbname:
PrincipalsRedirect.redirect_paths[''] = '/webdav/%s/principals' % dbname
reg_http_service(HTTPDir('/principals', PrincipalsRedirect))
logging.getLogger("web-services").info(
"Registered HTTP redirect handler for /principals to the %s db.",
dbname)
init_principals_redirect()
#eof #eof

View File

@ -20,5 +20,6 @@
############################################################################## ##############################################################################
import event_project import event_project
import wizard import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -25,7 +25,8 @@
"version" : "1.1", "version" : "1.1",
"depends" : ["marketing", "depends" : ["marketing",
"document", "document",
"email_template" "email_template",
"decimal_precision"
], ],
"author" : "OpenERP SA", "author" : "OpenERP SA",
"category": 'Generic Modules/Marketing', "category": 'Generic Modules/Marketing',

View File

@ -40,7 +40,7 @@ class campaign_analysis(osv.osv):
wi_ids = self.pool.get('marketing.campaign.workitem').search(cr, uid, wi_ids = self.pool.get('marketing.campaign.workitem').search(cr, uid,
[('segment_id.campaign_id', '=', ca_obj.campaign_id.id)]) [('segment_id.campaign_id', '=', ca_obj.campaign_id.id)])
total_cost = ca_obj.activity_id.variable_cost + \ total_cost = ca_obj.activity_id.variable_cost + \
((ca_obj.campaign_id.fixed_cost or 0.00) / len(wi_ids)) ((ca_obj.campaign_id.fixed_cost or 1.00) / len(wi_ids))
result[ca_obj.id] = total_cost result[ca_obj.id] = total_cost
return result return result
_columns = { _columns = {

View File

@ -1,238 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="workflow" id="wkf_pos">
<field name="name">Pos workflow</field>
<field name="osv">pos.order</field>
<field name="on_create">True</field>
</record>
<!-- Roles definition -->
<record model="res.roles" id="role_pos">
<field name="name">POS - Confirmation</field>
</record>
<!--Activities-->
<record model="workflow.activity" id="act_draft">
<field name="wkf_id" ref="wkf_pos"/>
<field name="flow_start">True</field>
<field name="name">draft</field>
</record>
<record model="workflow.activity" id="act_payment">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">payment</field>
<field name="kind">function</field>
<field name="action">write({'state': 'payment'})</field>
</record>
<record model="workflow.activity" id="act_rebate">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">rebate</field>
<field name="kind">function</field>
<field name="action">write({'state': 'rebate'})</field>
</record>
<record model="workflow.activity" id="act_unbalanced">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">unbalanced</field>
<field name="kind">function</field>
<field name="action">write({'state': 'unbalanced'})</field>
</record>
<record model="workflow.activity" id="act_cofinoga">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cofinoga</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cofinoga'})</field>
</record>
<record model="workflow.activity" id="act_collectivites">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">collectivites</field>
<field name="kind">function</field>
<field name="action">write({'state': 'collectivites'})</field>
</record>
<record model="workflow.activity" id="act_cadeaux">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cadeaux</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cadeaux'})</field>
</record>
<record model="workflow.activity" id="act_collectivites">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">collectivites</field>
<field name="kind">function</field>
<field name="action">write({'state': 'collectivites', 'invoice_wanted': True})</field>
</record>
<record model="workflow.activity" id="act_cadeaux">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cadeaux</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cadeaux'})</field>
</record>
<record model="workflow.activity" id="act_paid">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">paid</field>
<field name="action">action_paid()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_done">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">done</field>
<field name="flow_stop">True</field>
<field name="action">action_done()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_invoiced">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">invoiced</field>
<field name="flow_stop">True</field>
<field name="action">action_invoice()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_cancel">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">cancel</field>
<field name="flow_stop">True</field>
<field name="action">action_cancel()</field>
<field name="kind">function</field>
</record>
<!--Transitions-->
<record model="workflow.transition" id="trans_draft_payment">
<field name="act_from" ref="act_draft" />
<field name="act_to" ref="act_payment" />
<field name="signal">start_payment</field>
</record>
<record model="workflow.transition" id="trans_payment_paid">
<field name="act_from" ref="act_payment"/>
<field name="act_to" ref="act_paid"/>
<field name="condition">test_paid() and not(test_rebate() or test_cofinoga() or test_cadeaux() or test_collectivites())</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_payment_rebate">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_rebate" />
<field name="condition">test_rebate()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_rebate_paid">
<field name="act_from" ref="act_rebate" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_rebate</field>
</record>
<record model="workflow.transition" id="trans_payment_unbalanced">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_unbalanced" />
<field name="condition">not test_paid()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_unbalanced">
<field name="act_from" ref="act_unbalanced" />
<field name="act_to" ref="act_paid" />
<field name="condition">test_paid()</field>
</record>
<record model="workflow.transition" id="trans_payment_cofinoga">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cofinoga" />
<field name="condition">test_cofinoga()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cofinoga_paid">
<field name="act_from" ref="act_cofinoga" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cofinoga</field>
</record>
<record model="workflow.transition" id="trans_payment_collectivites">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_collectivites" />
<field name="condition">test_collectivites()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_collectivites_paid">
<field name="act_from" ref="act_collectivites" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_collectivites</field>
</record>
<record model="workflow.transition" id="trans_payment_cadeaux">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cadeaux" />
<field name="condition">test_cadeaux()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cadeaux_paid">
<field name="act_from" ref="act_cadeaux" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cadeaux</field>
</record>
<record model="workflow.transition" id="trans_payment_collectivites">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_collectivites" />
<field name="condition">test_collectivites()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_collectivites_paid">
<field name="act_from" ref="act_collectivites" />
<field name="act_to" ref="act_invoiced" />
<field name="signal">ok_collectivites</field>
</record>
<record model="workflow.transition" id="trans_payment_cadeaux">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cadeaux" />
<field name="condition">test_cadeaux()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cadeaux_paid">
<field name="act_from" ref="act_cadeaux" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cadeaux</field>
</record>
<record model="workflow.transition" id="trans_paid_done">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_done"/>
<field name="signal">done</field>
</record>
<record model="workflow.transition" id="trans_paid_invoice">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_invoiced"/>
<field name="signal">invoice</field>
</record>
<record model="workflow.transition" id="trans_paid_cancel">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">cancel</field>
</record>
</data>
</openerp>

View File

@ -22,10 +22,7 @@
from lxml import etree from lxml import etree
import time import time
from datetime import datetime, date from datetime import datetime, date
from operator import itemgetter
from itertools import groupby
from tools.misc import flatten
from tools.translate import _ from tools.translate import _
from osv import fields, osv from osv import fields, osv
@ -130,7 +127,6 @@ class project(osv.osv):
'warn_manager': fields.boolean('Warn Manager', help="If you check this field, the project manager will receive a request each time a task is completed by his team.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'warn_manager': fields.boolean('Warn Manager', help="If you check this field, the project manager will receive a request each time a task is completed by his team.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members', help="Project's member. Not used in any computation, just for information purpose, but a user has to be member of a project to add a the to this project.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members', help="Project's member. Not used in any computation, just for information purpose, but a user has to be member of a project to add a the to this project.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'parent_id': fields.many2one('project.project', 'Parent Project'),
'tasks': fields.one2many('project.task', 'project_id', "Project tasks"), 'tasks': fields.one2many('project.task', 'project_id', "Project tasks"),
'planned_hours': fields.function(_progress_rate, multi="progress", method=True, string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.", 'planned_hours': fields.function(_progress_rate, multi="progress", method=True, string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = { store = {
@ -322,7 +318,6 @@ class task(osv.osv):
# Compute: effective_hours, total_hours, progress # Compute: effective_hours, total_hours, progress
def _hours_get(self, cr, uid, ids, field_names, args, context=None): def _hours_get(self, cr, uid, ids, field_names, args, context=None):
project_obj = self.pool.get('project.project')
res = {} res = {}
cr.execute("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id IN %s GROUP BY task_id",(tuple(ids),)) cr.execute("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id IN %s GROUP BY task_id",(tuple(ids),))
hours = dict(cr.fetchall()) hours = dict(cr.fetchall())
@ -454,7 +449,7 @@ class task(osv.osv):
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c) 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c)
} }
_order = "sequence, priority, date_start, id" _order = "sequence,priority, date_start, name, id"
def _check_recursion(self, cr, uid, ids, context=None): def _check_recursion(self, cr, uid, ids, context=None):
obj_task = self.browse(cr, uid, ids[0], context=context) obj_task = self.browse(cr, uid, ids[0], context=context)
@ -632,7 +627,7 @@ class task(osv.osv):
Delegate Task to another users. Delegate Task to another users.
""" """
task = self.browse(cr, uid, task_id, context=context) task = self.browse(cr, uid, task_id, context=context)
new_task_id = self.copy(cr, uid, task.id, { self.copy(cr, uid, task.id, {
'name': delegate_data['name'], 'name': delegate_data['name'],
'user_id': delegate_data['user_id'], 'user_id': delegate_data['user_id'],
'planned_hours': delegate_data['planned_hours'], 'planned_hours': delegate_data['planned_hours'],

View File

@ -17,14 +17,13 @@
<!-- <!--
Resource: project.project Resource: project.project
--> -->
<record id="all_projects_account" model="account.analytic.account"> <record id="all_projects_account" model="account.analytic.account">
<field name="name">Projects</field> <field name="name">Projects</field>
<field name="code">3</field> <field name="code">3</field>
</record> </record>
<function eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)" id="parent_project_default_set" model="ir.values" name="set"/> <function eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)" id="parent_project_default_set" model="ir.values" name="set"/>
</data> </data>

View File

@ -240,7 +240,6 @@
<field name="name">Merge</field> <field name="name">Merge</field>
</record> </record>
<!-- Projects -->
<!-- Projects --> <!-- Projects -->
<!-- <record id="all_projects_account" model="project.project"> <!-- <record id="all_projects_account" model="project.project">
<field name="name">Projects</field> <field name="name">Projects</field>

View File

@ -40,15 +40,15 @@ Features.
"init_xml": [], "init_xml": [],
"demo_xml": ["project_long_term_demo.xml"], "demo_xml": ["project_long_term_demo.xml"],
"test": [ "test": [
'test/project_schedule_consecutive_day.yml',
'test/project_schedule_without_wroking_hour.yml',
'test/schedule_project_phases.yml',
'test/phase_constraint.yml',
'test/test_schedule_phases_case2.yml',
'test/test_schedule_phases_case1.yml', 'test/test_schedule_phases_case1.yml',
'test/schedule_project_phases.yml',
'test/schedule_project_tasks.yml', 'test/schedule_project_tasks.yml',
'test/test_schedule_phases_case2.yml',
'test/project_schedule_consecutive_day.yml',
'test/project_schedule_without_wroking_hour.yml',
'test/schedule_phase_tasks.yml', 'test/schedule_phase_tasks.yml',
'test/test_schedule_tasks_case1.yml' 'test/phase_constraint.yml',
'test/test_schedule_tasks_case1.yml',
], ],
"update_xml": [ "update_xml": [
"security/ir.model.access.csv", "security/ir.model.access.csv",

View File

@ -19,15 +19,10 @@
# #
############################################################################## ##############################################################################
from datetime import datetime, timedelta from datetime import datetime
from dateutil.relativedelta import relativedelta
from tools.translate import _ from tools.translate import _
from osv import fields, osv from osv import fields, osv
from resource.faces import task as Task from resource.faces import task as Task
import operator
from new import classobj
import types
import new
class project_phase(osv.osv): class project_phase(osv.osv):
_name = "project.phase" _name = "project.phase"
@ -219,9 +214,7 @@ class project_phase(osv.osv):
if context is None: if context is None:
context = {} context = {}
phase_ids = [] phase_ids = []
resource_pool = self.pool.get('resource.resource')
data_pool = self.pool.get('ir.model.data') data_pool = self.pool.get('ir.model.data')
resource_allocation_pool = self.pool.get('project.resource.allocation')
uom_pool = self.pool.get('product.uom') uom_pool = self.pool.get('product.uom')
task_pool = self.pool.get('project.task') task_pool = self.pool.get('project.task')
data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
@ -304,9 +297,6 @@ class project_phase(osv.osv):
ids = [ids] ids = [ids]
task_pool = self.pool.get('project.task') task_pool = self.pool.get('project.task')
resource_pool = self.pool.get('resource.resource') resource_pool = self.pool.get('resource.resource')
data_pool = self.pool.get('ir.model.data')
resource_allocation_pool = self.pool.get('project.resource.allocation')
for phase in self.browse(cr, uid, ids, context=context): for phase in self.browse(cr, uid, ids, context=context):
project = phase.project_id project = phase.project_id
calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
@ -448,7 +438,6 @@ class project(osv.osv):
resource_pool = self.pool.get('resource.resource') resource_pool = self.pool.get('resource.resource')
data_pool = self.pool.get('ir.model.data') data_pool = self.pool.get('ir.model.data')
resource_allocation_pool = self.pool.get('project.resource.allocation') resource_allocation_pool = self.pool.get('project.resource.allocation')
uom_pool = self.pool.get('product.uom')
data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
for project in self.browse(cr, uid, ids, context=context): for project in self.browse(cr, uid, ids, context=context):
@ -517,8 +506,11 @@ def Project_%d():
# Allocating Memory for the required Project and Pahses and Resources # Allocating Memory for the required Project and Pahses and Resources
exec(func_str) exec(func_str)
Project = eval('Project_%d' % project.id) Project = eval('Project_%d' % project.id)
project = Task.BalancedProject(Project) project = None
try:
project = Task.BalancedProject(Project)
except :
raise osv.except_osv(_('Error !'),_('Invalid resource allocation, Phase Scheduling is not possible.\nPlease check project member resource.'))
for phase_id in phase_ids: for phase_id in phase_ids:
act_phase = phase_pool.browse(cr, uid, phase_id, context=context) act_phase = phase_pool.browse(cr, uid, phase_id, context=context)
resources = act_phase.resource_ids resources = act_phase.resource_ids
@ -558,7 +550,7 @@ def Project_%d():
#TODO: DO Resource allocation and compute availability #TODO: DO Resource allocation and compute availability
def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None): def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
if context == None: if context == None:
contex = {} context = {}
allocation = {} allocation = {}
return allocation return allocation
@ -573,8 +565,6 @@ def Project_%d():
task_pool = self.pool.get('project.task') task_pool = self.pool.get('project.task')
resource_pool = self.pool.get('resource.resource') resource_pool = self.pool.get('resource.resource')
data_pool = self.pool.get('ir.model.data') data_pool = self.pool.get('ir.model.data')
resource_allocation_pool = self.pool.get('project.resource.allocation')
uom_pool = self.pool.get('product.uom')
data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
for project in self.browse(cr, uid, ids, context=context): for project in self.browse(cr, uid, ids, context=context):
@ -684,9 +674,6 @@ class project_task(osv.osv):
def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None): def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
if context is None: if context is None:
context = {} context = {}
phase_pool = self.pool.get('project.phase')
resource_pool = self.pool.get('resource.resource')
resource_allocation_pool = self.pool.get('project.resource.allocation')
task = self.browse(cr, uid, task_id, context=context) task = self.browse(cr, uid, task_id, context=context)
duration = str(task.planned_hours )+ 'H' duration = str(task.planned_hours )+ 'H'
str_resource = False str_resource = False

View File

@ -1,124 +1,124 @@
- -
Create a project 'Develop yaml Project Module'. Create a project 'Develop yaml Project Module'.
- -
!record {model: project.project, id: project_project_developyamlproject123}: !record {model: project.project, id: project_project_developyamlproject_2}:
name: "Develop Yaml Project Module" name: "Develop Yaml Project Module"
date_start: !eval time.strftime('%Y-%m-%d') date_start: !eval time.strftime('%Y-%m-%d')
- -
I have set Working Time from Monday to Friday from 9am to 17pm. I have set Working Time from Monday to Friday from 9am to 17pm.
- -
!record {model: resource.calendar, id: resource_calendar_hoursweekstest2}: !record {model: resource.calendar, id: resource_calendar_hoursweekstest_P2}:
name: "from Monday to Friday, from 9am to 17pm" name: "from Monday to Friday, from 9am to 17pm"
- -
I have set Day1 Working Time for Monday to Friday from 9am to 17pm working hour I have set Day1 Working Time for Monday to Friday from 9am to 17pm working hour
- -
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday1}: !record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day1}:
name: "Day1" name: "Day1"
hour_from : 09.00 hour_from : 09.00
hour_to : 17.00 hour_to : 17.00
dayofweek : "0" dayofweek : "0"
calendar_id : resource_calendar_hoursweekstest2 calendar_id : resource_calendar_hoursweekstest_P2
- -
I have set Day2 Working Time for Monday to Friday from 9am to 17pm working hour I have set Day2 Working Time for Monday to Friday from 9am to 17pm working hour
- -
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday2}: !record {model: resource.calendar.attendance, id: rresource_calendar_hoursweekstest_P2day2}:
name: "Day2" name: "Day2"
hour_from : 09.00 hour_from : 09.00
hour_to : 17.00 hour_to : 17.00
dayofweek : "1" dayofweek : "1"
calendar_id : resource_calendar_hoursweekstest2 calendar_id : resource_calendar_hoursweekstest_P2
- -
I have set Day3 Working Time for Monday to Friday from 9am to 17pm working hour I have set Day3 Working Time for Monday to Friday from 9am to 17pm working hour
- -
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday3}: !record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day3}:
name: "Day3" name: "Day3"
hour_from : 09.00 hour_from : 09.00
hour_to : 17.00 hour_to : 17.00
dayofweek : "2" dayofweek : "2"
calendar_id : resource_calendar_hoursweekstest2 calendar_id : resource_calendar_hoursweekstest_P2
- -
I have set Day4 Working Time for Monday to Friday from 9am to 17pm working hour I have set Day4 Working Time for Monday to Friday from 9am to 17pm working hour
- -
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday4}: !record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day4}:
name: "Day4" name: "Day4"
hour_from : 09.00 hour_from: 09.00
hour_to : 17.00 hour_to: 17.00
dayofweek : "3" dayofweek: "3"
calendar_id : resource_calendar_hoursweekstest2 calendar_id: resource_calendar_hoursweekstest_P2
- -
I have set Day5 Working Time for Monday to Friday from 9am to 17pm working hour I have set Day5 Working Time for Monday to Friday from 9am to 17pm working hour
- -
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday5}: !record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day5}:
name: "Day5" name: "Day5"
hour_from : 09.00 hour_from: 09.00
hour_to : 17.00 hour_to: 17.00
dayofweek : "4" dayofweek: "4"
calendar_id : resource_calendar_hoursweekstest2 calendar_id: resource_calendar_hoursweekstest_P2
- -
Now Set working period to Project 'Develop yaml Project Module' Now Set working period to Project 'Develop yaml Project Module'
- -
!python {model: project.project}: | !python {model: project.project}: |
self.write(cr, uid, [ref("project_project_developyamlproject123")], {'resource_calendar_id': ref("resource_calendar_hoursweekstest2")}) self.write(cr, uid, [ref("project_project_developyamlproject_2")], {'resource_calendar_id': ref("resource_calendar_hoursweekstest_P2")})
- -
Create 3 a project phase. Create 3 a project phase.
First 'Analysis Flow for Yaml'Project Phase First 'Analysis Flow for Yaml'Project Phase
- -
!record {model: project.phase, id: project_phase_analysisflowforyaml0}: !record {model: project.phase, id: project_project_developyamlproject_2_Phase1}:
date_start: !eval time.strftime('%Y-%m-%d') date_start: !eval time.strftime('%Y-%m-%d')
duration: 5.0 duration: 5.0
name: "Analysis Flow for Yaml" name: "Analysis Flow for Yaml"
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject123 project_id: project_project_developyamlproject_2
state: draft state: draft
- -
Create project phase 'Develop yaml' Create project phase 'Develop yaml'
- -
!record {model: project.phase, id: project_phase_establishingprojectfeasibility0}: !record {model: project.phase, id: project_project_developyamlproject_2_Phase2}:
duration: 5.0 duration: 5.0
name: Develop Yaml name: Develop Yaml
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject123 project_id: project_project_developyamlproject_2
previous_phase_ids: previous_phase_ids:
- project_phase_analysisflowforyaml0 - project_project_developyamlproject_2_Phase1
- -
Create project phase 'Test Yaml' Create project phase 'Test Yaml'
- -
!record {model: project.phase, id: project_phase_preparationofengineeringdesigns0}: !record {model: project.phase, id: project_project_developyamlproject_2_Phase3}:
duration: 5.0 duration: 5.0
name: Testing Yaml name: Testing Yaml
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject123 project_id: project_project_developyamlproject_2
previous_phase_ids: previous_phase_ids:
- project_phase_establishingprojectfeasibility0 - project_project_developyamlproject_2_Phase2
- -
Compute Schedule of phases For One project Compute Schedule of phases For One project
- -
!record {model: project.compute.phases, id: project_compute_phases_0}: !record {model: project.compute.phases, id: project_compute_phases_0}:
project_id: project_project_developyamlproject123 project_id: project_project_developyamlproject_2
target_project: one target_project: one
- -
Schedule project phases using Compute Phase Scheduling Schedule project phases using Compute Phase Scheduling
- -
!python {model: project.project}: | !python {model: project.project}: |
self.schedule_phases(cr, uid, [ref("project_project_developyamlproject123")]) self.schedule_phases(cr, uid, [ref("project_project_developyamlproject_2")])
- -
After scheduling, Check that phases scheduled, check that either of phase's start_date, end_date not null. After scheduling, Check that phases scheduled, check that either of phase's start_date, end_date not null.
- -
!python {model: project.project}: | !python {model: project.project}: |
proj=self.browse(cr, uid, [ref("project_project_developyamlproject123")])[0] proj=self.browse(cr, uid, [ref("project_project_developyamlproject_2")])[0]
for phase in proj.phase_ids: for phase in proj.phase_ids:
if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end): if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end):
raise AssertionError("Phases not scheduled") raise AssertionError("Phases not scheduled")

View File

@ -1,7 +1,7 @@
- -
Create a project 'Develop yaml Implementation Module'. Create a project 'Develop yaml Implementation Module'.
- -
!record {model: project.project, id: project_project_developyamlproject0}: !record {model: project.project, id: project_project_developyamlproject_2}:
name: "Develop Yaml Project Module" name: "Develop Yaml Project Module"
date_start: !eval time.strftime('%Y-%m-%d') date_start: !eval time.strftime('%Y-%m-%d')
@ -9,67 +9,67 @@
Create 4 Project phase. Create 4 Project phase.
First Project Phase 'Analysis Flow for Yaml' First Project Phase 'Analysis Flow for Yaml'
- -
!record {model: project.phase, id: project_phase_analysisflowforyaml0}: !record {model: project.phase, id: project_project_developyamlproject_2_phase_1}:
date_start: !eval time.strftime('%Y-%m-%d') date_start: !eval time.strftime('%Y-%m-%d')
duration: 6.0 duration: 6.0
product_uom: product.uom_day product_uom: product.uom_day
name: "Analysis Flow for Yaml" name: "Analysis Flow for Yaml"
project_id: project_project_developyamlproject0 project_id: project_project_developyamlproject_2
responsible_id: project.res_users_analyst responsible_id: project.res_users_analyst
state: draft state: draft
- -
Create project phase 'Develop yaml' Create project phase 'Develop yaml'
- -
!record {model: project.phase, id: project_phase_establishingprojectfeasibility0}: !record {model: project.phase, id: project_project_developyamlproject_2_phase_2}:
duration: 6.0 duration: 6.0
name: "Develop yaml" name: "Develop yaml"
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject0 project_id: project_project_developyamlproject_2
previous_phase_ids: previous_phase_ids:
- project_phase_analysisflowforyaml0 - project_project_developyamlproject_2_phase_1
state: draft state: draft
- -
Create project phase 'Test Yaml' Create project phase 'Test Yaml'
- -
!record {model: project.phase, id: project_phase_preparationofengineeringdesigns0}: !record {model: project.phase, id: project_project_developyamlproject_2_phase_3}:
duration: 6.0 duration: 6.0
name: Testing Yaml name: Testing Yaml
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject0 project_id: project_project_developyamlproject_2
previous_phase_ids: previous_phase_ids:
- project_phase_establishingprojectfeasibility0 - project_project_developyamlproject_2_phase_2
state: draft state: draft
- -
Create project phase 'Implement Yaml' Create project phase 'Implement Yaml'
- -
!record {model: project.phase, id: project_phase_implementingreadycase0}: !record {model: project.phase, id: project_project_developyamlproject_2_phase_4}:
duration: 6.0 duration: 6.0
name: Testing Yaml name: Testing Yaml
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_developyamlproject0 project_id: project_project_developyamlproject_2
previous_phase_ids: previous_phase_ids:
- project_phase_preparationofengineeringdesigns0 - project_project_developyamlproject_2_phase_3
state: draft state: draft
- -
Compute Schedule of phases For One project Compute Schedule of phases For One project
- -
!record {model: project.compute.phases, id: project_compute_phases_0}: !record {model: project.compute.phases, id: project_compute_phases_0}:
project_id: project_project_developyamlproject0 project_id: project_project_developyamlproject_2
target_project: one target_project: one
- -
Schedule project phases using Compute Phase Scheduling Schedule project phases using Compute Phase Scheduling
- -
!python {model: project.project}: | !python {model: project.project}: |
self.schedule_phases(cr, uid, [ref("project_project_developyamlproject0")]) self.schedule_phases(cr, uid, [ref("project_project_developyamlproject_2")])
- -
After scheduling, Check that phases scheduled, check that either of phase's start_date, end_date not null. After scheduling, Check that phases scheduled, check that either of phase's start_date, end_date not null.
- -
!python {model: project.project}: | !python {model: project.project}: |
proj=self.browse(cr, uid, [ref("project_project_developyamlproject0")])[0] proj=self.browse(cr, uid, [ref("project_project_developyamlproject_2")])[0]
for phase in proj.phase_ids: for phase in proj.phase_ids:
if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end): if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end):
raise AssertionError("Phases not scheduled") raise AssertionError("Phases not scheduled")

View File

@ -1,7 +1,7 @@
- -
Create project 'Develop an outlook-openerp synchronization plugin' Create project 'Develop an outlook-openerp synchronization plugin'
- -
!record {model: project.project, id: project_project_project0}: !record {model: project.project, id: project_project_project4}:
name: Develop an outlook-openerp synchronization plugin name: Develop an outlook-openerp synchronization plugin
members: members:
- project.res_users_project_manager - project.res_users_project_manager
@ -19,12 +19,14 @@
duration: 200.0 duration: 200.0
name: Develop GUI in Outlook name: Develop GUI in Outlook
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_project0 project_id: project_project_project4
state: draft state: draft
resource_ids: resource_ids:
- resource_id: project_long_term.resource_analyst - resource_id: project_long_term.resource_analyst
- resource_id: project_long_term.resource_developer - resource_id: project_long_term.resource_developer
- resource_id: project_long_term.resource_designer - resource_id: project_long_term.resource_designer
- resource_id: project_long_term.resource_tester
- resource_id: project_long_term.resource_project_manager
- -
Create the phase task 'Develop GUI for Server Configuration' Create the phase task 'Develop GUI for Server Configuration'
@ -35,8 +37,8 @@
remaining_hours: 20.0 remaining_hours: 20.0
state: draft state: draft
phase_id: project_phase_phase0 phase_id: project_phase_phase0
project_id: project_project_project0 project_id: project_project_project4
user_id: project.res_users_developer
- -
Create the phase task 'Develop GUI for Modules Configuration' Create the phase task 'Develop GUI for Modules Configuration'
@ -47,8 +49,8 @@
remaining_hours: 25.0 remaining_hours: 25.0
state: draft state: draft
phase_id: project_phase_phase0 phase_id: project_phase_phase0
project_id: project_project_project0 project_id: project_project_project4
user_id: project.res_users_developer
- -
Create the phase task 'Develop GUI for OpenERP Synchronisation' Create the phase task 'Develop GUI for OpenERP Synchronisation'
- -
@ -58,8 +60,8 @@
remaining_hours: 30.0 remaining_hours: 30.0
state: draft state: draft
phase_id: project_phase_phase0 phase_id: project_phase_phase0
project_id: project_project_project0 project_id: project_project_project4
user_id: project.res_users_developer
- -
Create the phase task 'Design required GUI/Menus' Create the phase task 'Design required GUI/Menus'
- -
@ -69,8 +71,7 @@
remaining_hours: 25.0 remaining_hours: 25.0
state: draft state: draft
phase_id: project_phase_phase0 phase_id: project_phase_phase0
project_id: project_project_project0 project_id: project_project_project4
user_id: project.res_users_designer
- -
Schedule phase tasks Schedule phase tasks
@ -84,6 +85,6 @@
!python {model: project.phase}: | !python {model: project.phase}: |
phase = self.browse(cr, uid, [ref("project_phase_phase0")])[0] phase = self.browse(cr, uid, [ref("project_phase_phase0")])[0]
for task in phase.task_ids: for task in phase.task_ids:
if (not task.user_id) or (not task.date_start) or (not task.date_end): if (not task.date_start) or (not task.date_end):
raise AssertionError("Phase Tasks not scheduled: %d uid=%r start=%r end=%r" % \ raise AssertionError("Phase Tasks not scheduled: %d uid=%r start=%r end=%r" % \
(task.id, task.user_id, task.date_start, task.date_end)) (task.id, task.user_id, task.date_start, task.date_end))

View File

@ -2,10 +2,11 @@
In order to test scheduling of project phases, I create two different phases and In order to test scheduling of project phases, I create two different phases and
test it with two different dates for scheduling. test it with two different dates for scheduling.
- -
I create a project 'Development and Testing'. I create a project Development and Testing.
- -
!record {model: project.project, id: project_project_project0}: !record {model: project.project, id: project_project_project_case1}:
date_start: '2010-12-30' name: "Development and Testing"
date_start: !eval time.strftime('%Y-%m-%d')
balance: 0.0 balance: 0.0
credit: 0.0 credit: 0.0
currency_id: base.EUR currency_id: base.EUR
@ -13,7 +14,7 @@
effective_hours: 0.0 effective_hours: 0.0
members: members:
- base.user_admin - base.user_admin
name: Development and Testing
planned_hours: 0.0 planned_hours: 0.0
progress_rate: 0.0 progress_rate: 0.0
quantity: 0.0 quantity: 0.0
@ -27,29 +28,29 @@
- -
I create first phase of the project. I create first phase of the project.
- -
!record {model: project.phase, id: project_phase_firstphase0}: !record {model: project.phase, id: project_phase_firstphase0_case1}:
duration: 2.0 duration: 2.0
name: First Phase name: First Phase
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_project0 project_id: project_project_project_case1
state: draft state: draft
- -
I create second phase of the project. I create second phase of the project.
- -
!record {model: project.phase, id: project_phase_secondphase0}: !record {model: project.phase, id: project_phase_secondphase0_case2}:
duration: 3.0 duration: 3.0
name: Second Phase name: Second Phase
previous_phase_ids: previous_phase_ids:
- project_phase_firstphase0 - project_phase_firstphase0_case1
product_uom: product.uom_day product_uom: product.uom_day
project_id: project_project_project0 project_id: project_project_project_case1
state: draft state: draft
- -
Now I create a record to compute the phase of project. Now I create a record to compute the phase of project.
- -
!record {model: project.compute.phases, id: project_compute_phases0}: !record {model: project.compute.phases, id: project_compute_phases0}:
target_project: 'one' target_project: 'one'
project_id: project_project_project0 project_id: project_project_project_case1
- -
I schedule the phases. I schedule the phases.
- -
@ -59,7 +60,13 @@
I check the starting and ending dates of both phases. I check the starting and ending dates of both phases.
- -
!python {model: project.phase}: | !python {model: project.phase}: |
first_phase = self.browse(cr, uid, ref('project_phase_firstphase0')) import datetime
assert (first_phase.date_start == '2010-12-30' and first_phase.date_end == '2010-12-31'),'Dates are wrong!' from dateutil.relativedelta import *
second_phase = self.browse(cr, uid, ref('project_phase_secondphase0')) start = (datetime.date.today()).strftime('%Y-%m-%d')
assert (second_phase.date_start == '2011-01-01' and second_phase.date_end == '2011-01-03'),'Dates are wrong!' end = (datetime.date.today() + relativedelta(days=2)).strftime('%Y-%m-%d')
first_phase = self.browse(cr, uid, ref('project_phase_firstphase0_case1'))
assert (first_phase.date_start == start and first_phase.date_end == end),'Dates are wrong!'
second_phase = self.browse(cr, uid, ref('project_phase_secondphase0_case2'))
start = first_phase.date_end
end = (datetime.date.today() + relativedelta(days=5)).strftime('%Y-%m-%d')
assert (second_phase.date_start == start and second_phase.date_end == end),'Dates are wrong!'

View File

@ -259,5 +259,5 @@
!python {model: project.task}: | !python {model: project.task}: |
task_ids = self.search(cr, uid, [('project_id','=',ref('project_project_projecta0'))]) task_ids = self.search(cr, uid, [('project_id','=',ref('project_project_projecta0'))])
for task in self.browse(cr, uid, task_ids): for task in self.browse(cr, uid, task_ids):
if (not task.user_id) or (not task.date_start) or (not task.date_end): if (not task.date_start) or (not task.date_end):
raise AssertionError("Tasks are not scheduled.") raise AssertionError("Tasks are not scheduled.")

View File

@ -17,10 +17,10 @@
</tabs> </tabs>
<tabpanels> <tabpanels>
<tabpanel id="configtab"> <tabpanel id="configtab">
<vbox style="border:1px solid black"> <vbox style="border:1px solid">
<groupbox id="gpConnection" align ="center" > <groupbox id="gpConnection" align ="center" >
<separator class="groove-thin" orient="horizontal" width="94"/> <separator class="groove-thin" orient="horizontal" width="94"/>
<caption label="&gpConnection.label;"/> <caption label="&gpConnection.label;"/>
<hbox> <hbox>
<label align="right" id="url" value="&txturl.label;" width="80" /> <label align="right" id="url" value="&txturl.label;" width="80" />
<textbox id="txturl" width="200" readonly="true" /> <textbox id="txturl" width="200" readonly="true" />
@ -50,28 +50,28 @@
</vbox> </vbox>
<separator class="groove-thin" orient="horizontal" width="10"/> <separator class="groove-thin" orient="horizontal" width="10"/>
<vbox style="border:1px solid black" width="94"> <vbox style="border:0.5px solid" >
<groupbox id="webgroup" > <groupbox id="webgroup" align ="center">
<vbox> <vbox>
<caption label="&webConnection.label;"/> <caption label="&webConnection.label;"/>
</vbox> </vbox>
<separator class="groove-thin" orient="horizontal" width="10"/> <separator class="groove-thin" orient="horizontal" width="10"/>
<hbox> <hbox>
<label align="right" id="url" value="&txtweburl.label;" width="80" /> <label align="right" id="url" value="&txtweburl.label;" width="80" />
<textbox id="txtweburl" width="200" readonly="true"/> <textbox id="txtweburl" width="200" readonly="true"/>
</hbox> </hbox>
<hbox > <hbox >
<spacer width="113"/> <spacer width="113"/>
<button align="center" id="websetconnection" label="&setconnection.label;" oncommand="openConfigChangeweb();" image="&imagesearch.value;"/> <button align="center" id="websetconnection" label="&setconnection.label;" oncommand="openConfigChangeweb();" image="&imagesearch.value;"/>
<button align="center" id="webopenconnection" label="&openconnection.label;" oncommand="testConnection_web();" image="&imageok.value;"/> <button align="center" id="webopenconnection" label="&openconnection.label;" oncommand="testConnection_web();" image="&imageok.value;"/>
</hbox> </hbox>
</groupbox> </groupbox>
</vbox> </vbox>
</tabpanel> </tabpanel>
<tabpanel id="objecttab"> <tabpanel id="objecttab">
<groupbox id="gpObject" width="700" > <groupbox id="gpObject" align ="center">
<caption label="&listDocument.header;"/> <caption label="&listDocument.header;"/>
<hbox> <hbox>
<vbox> <vbox>
@ -109,7 +109,9 @@
</tabpanel> </tabpanel>
<tabpanel id="abouttab"> <tabpanel id="abouttab">
<groupbox id="gpAbout" width="770" align="center"> <groupbox id="gpAbout" width="770" align="center">
<caption label="&gpAbout.label;"/> <caption label="&gpAbout.label;" align="center"/>
<vbox style="border:1px solid black" width="770"/>
<description>&openerp.paresent;</description> <description>&openerp.paresent;</description>
<image src="chrome://openerp_plugin/skin/logo.png" sizemode="stretch" align="center"/> <image src="chrome://openerp_plugin/skin/logo.png" sizemode="stretch" align="center"/>

View File

@ -17,8 +17,7 @@
<hbox id="root1" height="380" width="800" > <hbox id="root1" height="380" width="800" >
<vbox> <vbox>
<caption label="&gptinyobj.label;" /> <caption label="&gptinyobj.label;" />
<groupbox id="existsobjectgroup" width="400" style="border:1px solid black"> <groupbox id="existsobjectgroup" style="border:1px solid black">
<separator class="groove-thin" orient="horizontal" width="400"/> <separator class="groove-thin" orient="horizontal" width="400"/>
<hbox> <hbox>
<label id="lblsearch" control="txtvalueobj" value="&search.label;"/> <label id="lblsearch" control="txtvalueobj" value="&search.label;"/>
@ -57,7 +56,7 @@
<vbox> <vbox>
<caption label="&newobject.label;" /> <caption label="&newobject.label;" />
<groupbox id="newobjectgroup" width="400" align="left" style="border:1px solid black;"> <groupbox id="newobjectgroup" align="left" style="border:1px solid black;" height="100" >
<separator class="groove-thin" orient="horizontal" width="400" height="30"/> <separator class="groove-thin" orient="horizontal" width="400" height="30"/>
<hbox align="left"> <hbox align="left">
<vbox> <vbox>
@ -77,7 +76,7 @@
<separator class="groove-thin" orient="horizontal" width="180" height="100"/> <separator class="groove-thin" orient="horizontal" width="180" height="100"/>
</groupbox> </groupbox>
<caption label="&newcontact.label;" /> <caption label="&newcontact.label;" />
<groupbox id="newcontactgroup" width="400" align="left" style="border:1px solid black;"> <groupbox id="newcontactgroup" align="left" style="border:1px solid black;">
<separator class="groove-thin" orient="horizontal" width="400" height="30"/> <separator class="groove-thin" orient="horizontal" width="400" height="30"/>
<hbox align="left"> <hbox align="left">
<vbox> <vbox>

View File

@ -42,7 +42,7 @@ Licenced under the terms of OpenERP Public License (OEPL) v1.1 ">
<!ENTITY tinyerp_s.value "The Tiny Company"> <!ENTITY tinyerp_s.value "The Tiny Company">
<!ENTITY imageicon.value "chrome://openerp_plugin/skin/NEWT1.png"> <!ENTITY imageicon.value "chrome://openerp_plugin/skin/NEWT1.png">
<!ENTITY gpAbout.label "About OpenERP Thunderbird Plugin"> <!ENTITY gpAbout.label "OpenERP Thunderbird Plugin :">
<!ENTITY develop.value "Based on original work, copyright 2003-2009, by Tiny &amp; Axelor "> <!ENTITY develop.value "Based on original work, copyright 2003-2009, by Tiny &amp; Axelor ">
<!ENTITY information.value "For more information, please visit our website"> <!ENTITY information.value "For more information, please visit our website">
<!ENTITY contact.label "Contact Us"> <!ENTITY contact.label "Contact Us">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,11 @@
all: xpi
jar:
jar cvf chrome/openerp_plugin.jar chrome/openerp_plugin/*
xpi: jar
zip -r ../openerp_plugin.xpi *
clean:
rm ../openerp_plugin.xpi
rm chrome/openerp_plugin.jar

View File

@ -1,12 +0,0 @@
# Use this Shell Script for Cleaning & Compressing the chnages in Plugin
# Run this script using jar command.
rm chrome/openerp_plugin.jar
rm ../openerp_plugin.xpi
cd chrome/openerp_plugin/
jar cvf openerp_plugin.jar *
cd ..
mv openerp_plugin/openerp_plugin.jar openerp_plugin.jar
cd ..
zip -r ../openerp_plugin.xpi *

View File

@ -19,6 +19,7 @@
# #
############################################################################## ##############################################################################
import openobject.templating import openobject.templating
from tools.translate import _
class BaseTemplateEditor(openobject.templating.TemplateEditor): class BaseTemplateEditor(openobject.templating.TemplateEditor):
templates = ['/openobject/controllers/templates/base.mako'] templates = ['/openobject/controllers/templates/base.mako']