[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 = {
'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:
msgs = ["Anonymization successful.",
@ -578,7 +578,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
try:
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'])
except Exception, e:
except:
res = None
return res

View File

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

View File

@ -410,6 +410,8 @@ property or property parameter."),
return res
cal = vobject.iCalendar()
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('dtstart').value = ics_datetime(event_obj.date)
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['RSVP'] = [str(attendee.rsvp)]
attendee_add.value = 'MAILTO:' + (attendee.email or '')
res = cal.serialize()
return res

View File

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

View File

@ -37,14 +37,14 @@
# USA.
from random import seed, sample
from string import letters, digits
from string import ascii_letters, digits
from osv import fields,osv
import pooler
from tools.translate import _
magic_md5 = '$1$'
def gen_salt( length=8, symbols=letters + digits ):
def gen_salt( length=8, symbols=ascii_letters + digits ):
seed()
return ''.join( sample( symbols, length ) )
@ -64,15 +64,16 @@ def gen_salt( length=8, symbols=letters + digits ):
# *
# * Poul-Henning Kamp
from sys import version_info
if version_info < (2,5):
from md5 import md5
else:
from hashlib import md5
#TODO: py>=2.6: from hashlib import md5
import hashlib
def encrypt_md5( raw_pw, salt, magic=magic_md5 ):
hash = md5( raw_pw + magic + salt )
stretch = md5( raw_pw + salt + raw_pw).digest()
hash = hashlib.md5()
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 ) ):
hash.update( stretch[i % 16] )
@ -89,7 +90,7 @@ def encrypt_md5( raw_pw, salt, magic=magic_md5 ):
saltedmd5 = hash.digest()
for i in range( 1000 ):
hash = md5()
hash = hashlib.md5()
if i & 1:
hash.update( raw_pw )
@ -166,19 +167,37 @@ class users(osv.osv):
}
def login(self, db, login, password):
cr = pooler.get_db(db).cursor()
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.
if not password:
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)
if not stored_pw:
# means couldn't encrypt or user is not active!
return False
# Calculate an encrypted password from the user-provided
# password.
@ -187,12 +206,14 @@ class users(osv.osv):
obj._salt_cache = {}
salt = obj._salt_cache[id] = stored_pw[len(magic_md5):11]
encrypted_pw = encrypt_md5(password, salt)
# 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()
cr.close()
cr.commit()
if res:
return res[0]
else:
@ -210,22 +231,28 @@ class users(osv.osv):
return True
cr = pooler.get_db(db).cursor()
if uid not in obj._salt_cache:
cr.execute('select login from res_users where id=%s', (int(uid),))
stored_login = cr.fetchone()
if stored_login:
stored_login = stored_login[0]
try:
if uid not in self._salt_cache.get(db, {}):
# If we don't have cache, we have to repeat the procedure
# through the login function.
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):
raise Exception('AccessDenied')
raise security.ExceptionNoTb('AccessDenied')
if res:
if self._uid_cache.has_key(db):
@ -234,20 +261,24 @@ class users(osv.osv):
else:
self._uid_cache[db] = {uid: passwd}
return bool(res)
def maybe_encrypt(self, cr, pw, id):
# If the password 'pw' is not encrypted, then encrypt all passwords
# in the db. Returns the (possibly newly) encrypted password for 'id'.
""" Return the password 'pw', making sure it is encrypted.
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):
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()
for i, p in res:
encrypted = p
if p and not p.startswith(magic_md5):
encrypted = encrypt_md5(p, gen_salt())
cr.execute('update res_users set password=%s where id=%s',
(encrypted.encode('utf-8'), int(i)))
encrypted = encrypt_md5(p, gen_salt())
cr.execute('UPDATE res_users SET password=%s where id=%s',
(encrypted, i))
if i == id:
encrypted_res = encrypted
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]
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 ''
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'), ()
def name_get(self, cr, uid, ids, context=None):

View File

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

View File

@ -45,9 +45,15 @@ class base_setup_config_choice(osv.osv_memory):
def get_users(self, cr, uid, context=None):
user_obj = self.pool.get('res.users')
user_ids = user_obj.search(cr, uid, [])
users = user_obj.browse(cr, uid, user_ids, context=context)
user_str = '\n'.join(map(lambda x: ' - %s :\n\t\tLogin : %s \n\t\tPassword : %s' % (x.name, x.login, x.password), users))
return _('The following users have been installed : \n')+ user_str
user_list = []
user_tmpl_nopass = _(' - %s :\n\t\tLogin : %s')
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 = {
'installed_users':fields.text('Installed Users', readonly=True),

View File

@ -168,6 +168,7 @@
<field name="res_model">basic.calendar</field>
<field name="view_type">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 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]['field'] = field.field_id.name
res[attr]['type'] = field.field_id.ttype
if field.fn == 'datetime_utc':
res[attr]['type'] = 'utc'
if field.fn == 'hours':
res[attr]['type'] = "timedelta"
if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
@ -254,7 +256,6 @@ class CalDAV(object):
@param name: Get Attribute Name
@param type: Get Attribute Type
"""
if self.__attribute__.get(name):
val = 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 type: Get Attribute Type
"""
for name in self.__attribute__:
if self.__attribute__[name]:
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())
else:
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":
vevent.add(field).value = timedelta(hours=data[map_field])
elif map_type == "many2one":
@ -829,6 +844,7 @@ class basic_calendar_fields(osv.osv):
'fn': fields.selection([('field', 'Use the field'),
('const', 'Expression as constant'),
('hours', 'Interval in hours'),
('datetime_utc', 'Datetime In UTC'),
], 'Function'),
'mapping': fields.text('Mapping'),
}

View File

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

View File

@ -54,7 +54,7 @@
<tree string="Phone Calls" colors="gray:state in ('draft', 'cancel','done','pending')">
<field name="date" string="Date"/>
<field name="name" string="Call Summary"/>
<field name="categ_id"/>
<field name="categ_id" string="Type" widget="selection"/>
<field name="user_id"/>
<field name="state"/>
<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',
'res_model': 'crm.phonecall',
'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',
'search_view_id': res['res_id']
}

View File

@ -117,8 +117,8 @@
<record model="basic.calendar.fields" id="map_event_13">
<field name="name" ref="caldav.field_event_dtstamp"/>
<field name="type_id" ref="base_calendar.calendar_lines_event" />
<field name="field_id" search="[('name','=','date'),('model_id.model','=','calendar.event')]" />
<field name="fn">field</field>
<field name="field_id" search="[('name','=','write_date'),('model_id.model','=','crm.meeting')]" />
<field name="fn">datetime_utc</field>
</record>
<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">
<field name="name" ref="caldav.field_event_dtstamp"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_date"/>
<field name="fn">field</field>
<field name="field_id" ref="crm.field_crm_meeting_write_date"/>
<field name="fn">datetime_utc</field>
</record>
<record id="basic_calendar_fields_25" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_description"/>

View File

@ -53,6 +53,8 @@
<field name="action_next"/>
<field name="categ_id" string="Type" select="1"/>
<field name="stage_id" invisible="1"/>
<field name="date_deadline" invisible="1"/>
<field name="date_closed" invisible="1"/>
<field name="state"/>
<button name="case_open" string="Open"
states="draft,pending" type="object"
@ -83,7 +85,7 @@
<field name="section_id" widget="selection" />
<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 icon="gtk-go-forward" string="" name="stage_next" type="object"/>
</group>

View File

@ -66,6 +66,7 @@ class crm_claim_report(osv.osv):
'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\
('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),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'),

View File

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

View File

@ -60,4 +60,4 @@ def get_precision(application):
return (16, res)
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):
_inherit = 'ir.attachment'
_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):
return os.path.join(DMS_ROOT_PATH, cr.dbname)
@ -135,6 +165,15 @@ class document_file(osv.osv):
return False
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):
if not default:
default = {}
@ -168,7 +207,7 @@ class document_file(osv.osv):
ids2 = []
for fbro in self.browse(cr, uid, ids, context=context):
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)
continue
fnode = nctx.get_file_node(cr, fbro)

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
@ -15,22 +15,24 @@
# 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/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
class document_configuration(osv.osv_memory):
_name='document.configuration'
_description = 'Auto Directory Configuration'
_inherit = 'res.config'
_inherit = 'res.config'
_columns = {
'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."),
'project': fields.boolean('Project', help="Auto directory configuration for Projects."),
}
def execute(self, cr, uid, ids, context=None):
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
else:
quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'})
dir_pool.write(cr, uid, [quta_dir_id], {
'type':'ressource',
'ressource_type_id': mid[0],
@ -86,7 +88,7 @@ class document_configuration(osv.osv_memory):
'include_name': 1,
'directory_id': quta_dir_id,
})
if conf.product and self.pool.get('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
else:
product_dir_id = data_pool.create(cr, uid, {'name': 'Products'})
mid = model_pool.search(cr, uid, [('model','=','product.product')])
dir_pool.write(cr, uid, [product_dir_id], {
'type':'ressource',
'ressource_type_id': mid[0],
})
})
if conf.project and self.pool.get('account.analytic.account'):
# 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
else:
project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'})
mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')])
dir_pool.write(cr, uid, [project_dir_id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': '[]',
'ressource_tree': 1
})
})
document_configuration()

View File

@ -45,6 +45,8 @@
<record model="ir.actions.todo" id="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="state" eval="'skip'" />
<field name="restart" eval="'onskip'" />
</record>
</data>
</openerp>

View File

@ -37,7 +37,12 @@ def get_plain_ftp(timeout=10.0):
def get_ftp_login(cr, uid, ormobj):
ftp = get_plain_ftp()
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)
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 []
if url:
url = url[1]
if url[-1] == '/':
url = url[:-1]
else:
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
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.
aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse')
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()

View File

@ -30,7 +30,7 @@
{
"name" : "WebDAV server for Document Management",
"version" : "2.2",
"version" : "2.3",
"author" : "OpenERP SA",
"category" : "Generic Modules/Others",
"website": "http://www.openerp.com",
@ -49,6 +49,9 @@
; since the messages are routed to the python logging, with
; levels "debug" and "debug_rpc" respectively, you can leave
; 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"],
"init_xml" : [],

View File

@ -686,7 +686,7 @@ class openerp_dav_handler(dav_interface):
except Exception:
node = False
objname = uri2[-1]
objname = misc.ustr(uri2[-1])
ret = None
if not node:
@ -719,7 +719,7 @@ class openerp_dav_handler(dav_interface):
etag = str(newchild.get_etag(cr))
except Exception, e:
self.parent.log_error("Cannot get etag for node: %s" % e)
ret = (hurl, etag)
ret = (str(hurl), etag)
else:
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 ''
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)
def dav_lock(self, cr, lock_data):
@ -345,11 +345,11 @@ class node_database(nodes.node_database):
return ('collection', 'DAV:')
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)
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)
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
self.user = res[0]['login']
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
def set_useragent(self, uastr):

View File

@ -45,13 +45,103 @@ class Text2(xml.dom.minidom.Text):
data = data.replace(">", "&gt;")
writer.write(data)
def createText2Node(doc, data):
if not isinstance(data, StringTypes):
raise TypeError, "node contents must be a string"
t = Text2()
t.data = data
t.ownerDocument = doc
return t
class Prop2xml(object):
""" A helper class to convert property structs to DAV:XML
Written to generalize the use of _prop_child(), a class is
needed to hold some persistent data accross the recursions
of _prop_elem_child().
"""
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
@ -73,78 +163,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1
def _prop_child(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(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)
propgen = Prop2xml(doc, namespaces, nsnum)
# write href information
uparts=urlparse.urlparse(uri)
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():
if v is None:
continue
_prop_child(gp, ns, p, v)
propgen._prop_child(gp, ns, p, v)
ps.appendChild(gp)
re.appendChild(ps)

View File

@ -1,7 +1,8 @@
# -*- encoding: utf-8 -*-
############################################################################9
#
# 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
# python-webdav library (http://code.google.com/p/pywebdav/ ),
@ -19,7 +20,7 @@
#
# This program is Free Software; you can redistribute it and/or
# 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.
#
# This program is distributed in the hope that it will be useful,
@ -33,20 +34,26 @@
###############################################################################
import logging
import netsvc
from dav_fs import openerp_dav_handler
from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler
from service import http_server
from service.websrv_lib import HTTPDir, FixSendError, HttpOptions
from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse
import urllib
import re
import time
from string import atoi
from DAV.errors import *
import addons
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 xml.dom import minidom
from redirect import RedirectHTTPHandler
khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
@ -66,6 +73,7 @@ def OpenDAVConfig(**kw):
class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
verbose = False
_logger = logging.getLogger('webdav')
protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
@ -75,8 +83,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
def get_userinfo(self,user,pw):
return False
def _log(self, message):
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
self._logger.debug(message)
def handle(self):
self._init_buffer()
@ -118,10 +127,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
return self.davpath
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):
netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args)
self._logger.warning(format % args)
def _prep_OPTIONS(self, opts):
ret = opts
@ -415,6 +424,140 @@ class DAVAuthProvider(OpenERPAuthProvider):
return True
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:
if (config.get_misc('webdav','enable',True)):
@ -430,10 +573,80 @@ try:
conf = OpenDAVConfig(**_dc)
handler._config = conf
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:
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
logging.getLogger('webdav').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

View File

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

View File

@ -25,7 +25,8 @@
"version" : "1.1",
"depends" : ["marketing",
"document",
"email_template"
"email_template",
"decimal_precision"
],
"author" : "OpenERP SA",
"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,
[('segment_id.campaign_id', '=', ca_obj.campaign_id.id)])
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
return result
_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
import time
from datetime import datetime, date
from operator import itemgetter
from itertools import groupby
from tools.misc import flatten
from tools.translate import _
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)]}),
'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"),
'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 = {
@ -322,7 +318,6 @@ class task(osv.osv):
# Compute: effective_hours, total_hours, progress
def _hours_get(self, cr, uid, ids, field_names, args, context=None):
project_obj = self.pool.get('project.project')
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),))
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)
}
_order = "sequence, priority, date_start, id"
_order = "sequence,priority, date_start, name, id"
def _check_recursion(self, cr, uid, ids, context=None):
obj_task = self.browse(cr, uid, ids[0], context=context)
@ -632,7 +627,7 @@ class task(osv.osv):
Delegate Task to another users.
"""
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'],
'user_id': delegate_data['user_id'],
'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">
<field name="name">Projects</field>
<field name="code">3</field>
</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"/>
</data>

View File

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

View File

@ -40,15 +40,15 @@ Features.
"init_xml": [],
"demo_xml": ["project_long_term_demo.xml"],
"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/schedule_project_phases.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/test_schedule_tasks_case1.yml'
'test/phase_constraint.yml',
'test/test_schedule_tasks_case1.yml',
],
"update_xml": [
"security/ir.model.access.csv",

View File

@ -19,15 +19,10 @@
#
##############################################################################
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from datetime import datetime
from tools.translate import _
from osv import fields, osv
from resource.faces import task as Task
import operator
from new import classobj
import types
import new
class project_phase(osv.osv):
_name = "project.phase"
@ -219,9 +214,7 @@ class project_phase(osv.osv):
if context is None:
context = {}
phase_ids = []
resource_pool = self.pool.get('resource.resource')
data_pool = self.pool.get('ir.model.data')
resource_allocation_pool = self.pool.get('project.resource.allocation')
uom_pool = self.pool.get('product.uom')
task_pool = self.pool.get('project.task')
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]
task_pool = self.pool.get('project.task')
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):
project = phase.project_id
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')
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')
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
exec(func_str)
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:
act_phase = phase_pool.browse(cr, uid, phase_id, context=context)
resources = act_phase.resource_ids
@ -558,7 +550,7 @@ def Project_%d():
#TODO: DO Resource allocation and compute availability
def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
if context == None:
contex = {}
context = {}
allocation = {}
return allocation
@ -573,8 +565,6 @@ def Project_%d():
task_pool = self.pool.get('project.task')
resource_pool = self.pool.get('resource.resource')
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')
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):
if context is None:
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)
duration = str(task.planned_hours )+ 'H'
str_resource = False

View File

@ -1,124 +1,124 @@
-
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"
date_start: !eval time.strftime('%Y-%m-%d')
-
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"
-
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"
hour_from : 09.00
hour_to : 17.00
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
-
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday2}:
!record {model: resource.calendar.attendance, id: rresource_calendar_hoursweekstest_P2day2}:
name: "Day2"
hour_from : 09.00
hour_to : 17.00
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
-
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday3}:
!record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day3}:
name: "Day3"
hour_from : 09.00
hour_to : 17.00
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
-
!record {model: resource.calendar.attendance, id: resource_calendar_atendanceday4}:
!record {model: resource.calendar.attendance, id: resource_calendar_hoursweekstest_P2day4}:
name: "Day4"
hour_from : 09.00
hour_to : 17.00
dayofweek : "3"
calendar_id : resource_calendar_hoursweekstest2
hour_from: 09.00
hour_to: 17.00
dayofweek: "3"
calendar_id: resource_calendar_hoursweekstest_P2
-
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"
hour_from : 09.00
hour_to : 17.00
dayofweek : "4"
calendar_id : resource_calendar_hoursweekstest2
hour_from: 09.00
hour_to: 17.00
dayofweek: "4"
calendar_id: resource_calendar_hoursweekstest_P2
-
Now Set working period to Project 'Develop yaml Project Module'
-
!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.
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')
duration: 5.0
name: "Analysis Flow for Yaml"
product_uom: product.uom_day
project_id: project_project_developyamlproject123
project_id: project_project_developyamlproject_2
state: draft
-
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
name: Develop Yaml
product_uom: product.uom_day
project_id: project_project_developyamlproject123
project_id: project_project_developyamlproject_2
previous_phase_ids:
- project_phase_analysisflowforyaml0
- project_project_developyamlproject_2_Phase1
-
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
name: Testing Yaml
product_uom: product.uom_day
project_id: project_project_developyamlproject123
project_id: project_project_developyamlproject_2
previous_phase_ids:
- project_phase_establishingprojectfeasibility0
- project_project_developyamlproject_2_Phase2
-
Compute Schedule of phases For One project
-
!record {model: project.compute.phases, id: project_compute_phases_0}:
project_id: project_project_developyamlproject123
project_id: project_project_developyamlproject_2
target_project: one
-
Schedule project phases using Compute Phase Scheduling
-
!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.
-
!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:
if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end):
raise AssertionError("Phases not scheduled")

View File

@ -1,7 +1,7 @@
-
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"
date_start: !eval time.strftime('%Y-%m-%d')
@ -9,67 +9,67 @@
Create 4 Project phase.
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')
duration: 6.0
product_uom: product.uom_day
name: "Analysis Flow for Yaml"
project_id: project_project_developyamlproject0
project_id: project_project_developyamlproject_2
responsible_id: project.res_users_analyst
state: draft
-
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
name: "Develop yaml"
product_uom: product.uom_day
project_id: project_project_developyamlproject0
project_id: project_project_developyamlproject_2
previous_phase_ids:
- project_phase_analysisflowforyaml0
- project_project_developyamlproject_2_phase_1
state: draft
-
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
name: Testing Yaml
product_uom: product.uom_day
project_id: project_project_developyamlproject0
project_id: project_project_developyamlproject_2
previous_phase_ids:
- project_phase_establishingprojectfeasibility0
- project_project_developyamlproject_2_phase_2
state: draft
-
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
name: Testing Yaml
product_uom: product.uom_day
project_id: project_project_developyamlproject0
project_id: project_project_developyamlproject_2
previous_phase_ids:
- project_phase_preparationofengineeringdesigns0
- project_project_developyamlproject_2_phase_3
state: draft
-
Compute Schedule of phases For One project
-
!record {model: project.compute.phases, id: project_compute_phases_0}:
project_id: project_project_developyamlproject0
project_id: project_project_developyamlproject_2
target_project: one
-
Schedule project phases using Compute Phase Scheduling
-
!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.
-
!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:
if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end):
raise AssertionError("Phases not scheduled")

View File

@ -1,7 +1,7 @@
-
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
members:
- project.res_users_project_manager
@ -19,12 +19,14 @@
duration: 200.0
name: Develop GUI in Outlook
product_uom: product.uom_day
project_id: project_project_project0
project_id: project_project_project4
state: draft
resource_ids:
- resource_id: project_long_term.resource_analyst
- resource_id: project_long_term.resource_developer
- 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'
@ -35,8 +37,8 @@
remaining_hours: 20.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
user_id: project.res_users_developer
project_id: project_project_project4
-
Create the phase task 'Develop GUI for Modules Configuration'
@ -47,8 +49,8 @@
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
user_id: project.res_users_developer
project_id: project_project_project4
-
Create the phase task 'Develop GUI for OpenERP Synchronisation'
-
@ -58,8 +60,8 @@
remaining_hours: 30.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
user_id: project.res_users_developer
project_id: project_project_project4
-
Create the phase task 'Design required GUI/Menus'
-
@ -69,8 +71,7 @@
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
user_id: project.res_users_designer
project_id: project_project_project4
-
Schedule phase tasks
@ -84,6 +85,6 @@
!python {model: project.phase}: |
phase = self.browse(cr, uid, [ref("project_phase_phase0")])[0]
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" % \
(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
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}:
date_start: '2010-12-30'
!record {model: project.project, id: project_project_project_case1}:
name: "Development and Testing"
date_start: !eval time.strftime('%Y-%m-%d')
balance: 0.0
credit: 0.0
currency_id: base.EUR
@ -13,7 +14,7 @@
effective_hours: 0.0
members:
- base.user_admin
name: Development and Testing
planned_hours: 0.0
progress_rate: 0.0
quantity: 0.0
@ -27,29 +28,29 @@
-
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
name: First Phase
product_uom: product.uom_day
project_id: project_project_project0
project_id: project_project_project_case1
state: draft
-
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
name: Second Phase
previous_phase_ids:
- project_phase_firstphase0
- project_phase_firstphase0_case1
product_uom: product.uom_day
project_id: project_project_project0
project_id: project_project_project_case1
state: draft
-
Now I create a record to compute the phase of project.
-
!record {model: project.compute.phases, id: project_compute_phases0}:
target_project: 'one'
project_id: project_project_project0
project_id: project_project_project_case1
-
I schedule the phases.
-
@ -59,7 +60,13 @@
I check the starting and ending dates of both phases.
-
!python {model: project.phase}: |
first_phase = self.browse(cr, uid, ref('project_phase_firstphase0'))
assert (first_phase.date_start == '2010-12-30' and first_phase.date_end == '2010-12-31'),'Dates are wrong!'
second_phase = self.browse(cr, uid, ref('project_phase_secondphase0'))
assert (second_phase.date_start == '2011-01-01' and second_phase.date_end == '2011-01-03'),'Dates are wrong!'
import datetime
from dateutil.relativedelta import *
start = (datetime.date.today()).strftime('%Y-%m-%d')
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}: |
task_ids = self.search(cr, uid, [('project_id','=',ref('project_project_projecta0'))])
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.")

View File

@ -17,10 +17,10 @@
</tabs>
<tabpanels>
<tabpanel id="configtab">
<vbox style="border:1px solid black">
<vbox style="border:1px solid">
<groupbox id="gpConnection" align ="center" >
<separator class="groove-thin" orient="horizontal" width="94"/>
<caption label="&gpConnection.label;"/>
<separator class="groove-thin" orient="horizontal" width="94"/>
<caption label="&gpConnection.label;"/>
<hbox>
<label align="right" id="url" value="&txturl.label;" width="80" />
<textbox id="txturl" width="200" readonly="true" />
@ -50,28 +50,28 @@
</vbox>
<separator class="groove-thin" orient="horizontal" width="10"/>
<vbox style="border:1px solid black" width="94">
<groupbox id="webgroup" >
<vbox>
<caption label="&webConnection.label;"/>
</vbox>
<separator class="groove-thin" orient="horizontal" width="10"/>
<hbox>
<label align="right" id="url" value="&txtweburl.label;" width="80" />
<textbox id="txtweburl" width="200" readonly="true"/>
</hbox>
<hbox >
<spacer width="113"/>
<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;"/>
</hbox>
</groupbox>
</vbox>
<vbox style="border:0.5px solid" >
<groupbox id="webgroup" align ="center">
<vbox>
<caption label="&webConnection.label;"/>
</vbox>
<separator class="groove-thin" orient="horizontal" width="10"/>
<hbox>
<label align="right" id="url" value="&txtweburl.label;" width="80" />
<textbox id="txtweburl" width="200" readonly="true"/>
</hbox>
<hbox >
<spacer width="113"/>
<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;"/>
</hbox>
</groupbox>
</vbox>
</tabpanel>
<tabpanel id="objecttab">
<groupbox id="gpObject" width="700" >
<groupbox id="gpObject" align ="center">
<caption label="&listDocument.header;"/>
<hbox>
<vbox>
@ -109,7 +109,9 @@
</tabpanel>
<tabpanel id="abouttab">
<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>
<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" >
<vbox>
<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"/>
<hbox>
<label id="lblsearch" control="txtvalueobj" value="&search.label;"/>
@ -57,7 +56,7 @@
<vbox>
<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"/>
<hbox align="left">
<vbox>
@ -77,7 +76,7 @@
<separator class="groove-thin" orient="horizontal" width="180" height="100"/>
</groupbox>
<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"/>
<hbox align="left">
<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 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 information.value "For more information, please visit our website">
<!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
from tools.translate import _
class BaseTemplateEditor(openobject.templating.TemplateEditor):
templates = ['/openobject/controllers/templates/base.mako']