[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:
commit
5af8021914
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 "Κωδικός"
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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!'
|
|
@ -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']
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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" : [],
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>OpenERP server</title>
|
||||
</head>
|
||||
<body>
|
||||
This is an OpenERP server. Nothing to GET here.
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
@ -45,13 +45,103 @@ class Text2(xml.dom.minidom.Text):
|
|||
data = data.replace(">", ">")
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
##############################################################################
|
||||
import event_project
|
||||
import wizard
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"version" : "1.1",
|
||||
"depends" : ["marketing",
|
||||
"document",
|
||||
"email_template"
|
||||
"email_template",
|
||||
"decimal_precision"
|
||||
],
|
||||
"author" : "OpenERP SA",
|
||||
"category": 'Generic Modules/Marketing',
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
|
@ -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'],
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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!'
|
||||
|
|
|
@ -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.")
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 & 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 |
|
@ -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
|
|
@ -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 *
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
import openobject.templating
|
||||
from tools.translate import _
|
||||
|
||||
class BaseTemplateEditor(openobject.templating.TemplateEditor):
|
||||
templates = ['/openobject/controllers/templates/base.mako']
|
||||
|
|
Loading…
Reference in New Issue