Skip to content
This repository was archived by the owner on Oct 9, 2018. It is now read-only.

Commit 8c72bb7

Browse files
committed
Merge pull request #30 from odesk/v0.5.6
version 0.5.6
2 parents 15c8f38 + 1e98577 commit 8c72bb7

File tree

7 files changed

+173
-55
lines changed

7 files changed

+173
-55
lines changed

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: python
2+
3+
python:
4+
- "2.6"
5+
- "2.7"
6+
7+
install: "pip install -r requirements.py"
8+
9+
script: nosetests
10+
11+
notifications:
12+
email:
13+
recipients:
14+
- apisupport@odesk.com
15+
on_failure: change

changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
Changelog
66
***************
77

8+
.. _0.5.6:
9+
10+
Version 0.5.6
11+
-------------
12+
* Added new API call - :py:meth:`List categories (v2) <odesk.routers.provider.Provider_V2.get_categories_metadata>`.
13+
* Added new API call - :py:meth:`Get Work Diary by Contract <odesk.routers.team.Team_V2.get_workdiaries_by_contract>`.
14+
* Recent changes from API Changelog - Wednesday, 2015-01-12
15+
* Recent changes from API Changelog - Wednesday, 2014-12-03
16+
* Recent changes from API Changelog - Friday, 2014-11-21
17+
* Recent changes from API Changelog - Friday, 2014-10-31
18+
819
.. _0.5.5:
920

1021
Version 0.5.5.1

odesk/routers/hr.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,7 @@ def get_company_users(self, company_referece, active=True):
175175
"""team api"""
176176

177177
def post_team_adjustment(self, team_reference, engagement_reference,
178-
comments, amount=None, charge_amount=None,
179-
notes=None):
178+
comments, charge_amount, notes=None):
180179
"""
181180
Add bonus to an engagement.
182181
@@ -188,11 +187,7 @@ def post_team_adjustment(self, team_reference, engagement_reference,
188187
:comments: Comments about this adjustment, e.g.
189188
"Bonus for a good job"
190189
191-
:amount: (conditionally optional) The amount that
192-
the provider should receive, e.g. 100
193-
194-
:charge_amount: (conditionally optional) The amount that
195-
will be charged to the employer, e.g. 110
190+
:charge_amount: The amount that will be charged to the employer, e.g. 110
196191
197192
:notes: (optional) Notes
198193
@@ -206,17 +201,11 @@ def post_team_adjustment(self, team_reference, engagement_reference,
206201

207202
data['engagement__reference'] = engagement_reference
208203
data['comments'] = comments
204+
205+
if charge_amount is None or charge_amount == 0:
206+
raise ApiValueError('Missed obligatory parameter ``charge_amount``')
209207

210-
if (amount and charge_amount) or (amount is None and
211-
charge_amount is None):
212-
raise ApiValueError('Either only one of the parameters ``amount``'
213-
' or ``charge_amount`` should be specified')
214-
215-
if amount:
216-
data['amount'] = amount
217-
218-
if charge_amount:
219-
data['charge_amount'] = charge_amount
208+
data['charge_amount'] = charge_amount
220209

221210
if notes:
222211
data['notes'] = notes
@@ -350,8 +339,8 @@ def get_job(self, job_reference):
350339
return result.get('job', result)
351340

352341
def post_job(self, buyer_team_reference, title, job_type, description,
353-
visibility, category, subcategory, budget=None, duration=None,
354-
start_date=None, end_date=None, skills=None):
342+
visibility, category=None, subcategory=None, budget=None, duration=None,
343+
start_date=None, skills=None, subcategory2=None):
355344
"""
356345
Post a job.
357346
@@ -382,10 +371,10 @@ def post_job(self, buyer_team_reference, title, job_type, description,
382371
where the buyer wants to control
383372
the potential applicants
384373
385-
:category: The category of job, e.g. 'Web Development'
374+
:category: (conditionally optional) The category of job, e.g. 'Web Development'
386375
(where to get? - see Metadata API)
387376
388-
:subcategory: The subcategory of job, e.g.
377+
:subcategory: (conditionally optional) The subcategory of job, e.g.
389378
'Web Programming'
390379
(where to get? - see Metadata API)
391380
@@ -402,14 +391,14 @@ def post_job(self, buyer_team_reference, title, job_type, description,
402391
included the job will default to
403392
starting immediately.
404393
405-
:end_date: (deprecated) This parameter remains
406-
for compatibility reasons and
407-
will be removed in future.
408-
409394
:skills: (optional) Skills required for the job.
410395
Must be a list or tuple even of one item,
411396
e.g. ``['python']``
412397
398+
:subcategory2: (conditionally optional) The subcategory (V2) of job, e.g.
399+
'Web & Mobile Programming'
400+
(where to get? - see Metadata API, List Categories (V2))
401+
413402
"""
414403
url = 'jobs'
415404
data = {}
@@ -425,8 +414,17 @@ def post_job(self, buyer_team_reference, title, job_type, description,
425414
assert_parameter('visibility', visibility, self.JOB_VISIBILITY_OPTIONS)
426415
data['visibility'] = visibility
427416

428-
data['category'] = category
429-
data['subcategory'] = subcategory
417+
if (category is None or subcategory is None) and subcategory2 is None:
418+
raise ApiValueError('Either one of the sub/category V1 or V2 parameters '
419+
'must be specified')
420+
if category:
421+
data['category'] = category
422+
423+
if subcategory:
424+
data['subcategory'] = subcategory
425+
426+
if subcategory2:
427+
data['subcategory2'] = subcategory2
430428

431429
if budget is None and duration is None:
432430
raise ApiValueError('Either one of the ``budget``or ``duration`` '
@@ -446,7 +444,7 @@ def post_job(self, buyer_team_reference, title, job_type, description,
446444

447445
def update_job(self, job_id, buyer_team_reference, title, description,
448446
visibility, category=None, subcategory=None, budget=None,
449-
duration=None, start_date=None, end_date=None, status=None):
447+
duration=None, start_date=None, status=None):
450448
"""
451449
Update a job.
452450
@@ -495,10 +493,6 @@ def update_job(self, job_id, buyer_team_reference, title, description,
495493
included the job will default to
496494
starting immediately.
497495
498-
:end_date: (deprecated) This parameter remains
499-
for compatibility reasons and
500-
will be removed in future.
501-
502496
:status: (required) The status of the job,
503497
e.g. 'filled'.
504498
Possible values are:

odesk/routers/offers.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def send_client_offer(self, title, job_type, charge_rate,
7070
contractor_org=None, context=None,
7171
charge_upfront_percent=None, weekly_limit=None,
7272
weekly_stipend=None, expires_on=None,
73-
close_on_accept=None):
73+
close_on_accept=None, related_jobcategory=None, milestone=None):
7474
"""
7575
Send client offer to the freelancer.
7676
@@ -102,18 +102,28 @@ def send_client_offer(self, title, job_type, charge_rate,
102102
Example: ``context[job_posting_ref] = {{ opening_id }} & context[job_application_ref] = {{ application_id }}``
103103
where ``job_posting_ref`` is a job key, for example ``~01c8e0xxxxxxxx05255``.
104104
105-
:charge_upfront_percent: The percentage of the budget amount that the freelancer is paid on acceptance of the offer
105+
:charge_upfront_percent: (deprecated) This parameter remains for compatibility reasons and will be removed
106+
in next releases those come after February 6th 2015.
107+
The percentage of the budget amount that the freelancer is paid on acceptance of the offer
106108
(for fixed price jobs only).
107109
108110
:weekly_limit: The maximum number of hours per week the freelancer can bill for.
109111
110112
:weekly_stipend: An additional payment to be issued to the freelancer each week.
111113
112-
:expires_on: Time when the offer expires. This should be a UNIX UTC timestamp. For example: ``1400785324``.
114+
:expires_on: (deprecated) This parameter remains for compatibility reasons and will be removed
115+
in next releases those come after February 6th 2015.
116+
Time when the offer expires. This should be a UNIX UTC timestamp. For example: ``1400785324``.
113117
114118
:close_on_accept: If the value is ``1``, it automatically closes the related job post if this offer is accepted.
115119
The default value is ``1``. Valid values: ``0``, ``1``.
116120
121+
:related_jobcategory: Related job category. For example: ``9``.
122+
123+
:milestones: (required after February 6th 2015) Array of milestones for fixed-priced jobs in the following format:
124+
`milestones[0][$key]`, ..., `milestones[N][$key]`, where key is one of the following -
125+
`milestone_description` (string), `deposit_amount` (float), `due_date` (string in format mm-dd-yyyy)
126+
117127
"""
118128
data = {}
119129

@@ -155,6 +165,12 @@ def send_client_offer(self, title, job_type, charge_rate,
155165
if close_on_accept:
156166
data['close_on_accept'] = close_on_accept
157167

168+
if related_jobcategory:
169+
data['related_jobcategory'] = related_jobcategory
170+
171+
if milestones:
172+
data['milestones'] = milestones
173+
158174
url = 'clients/offers'
159175
return self.post(url, data)
160176

odesk/routers/provider.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,15 @@ class Provider_V2(Namespace):
325325
api_url = 'profiles/'
326326
version = 2
327327

328+
def get_categories_metadata(self):
329+
"""
330+
Returns list of all categories (v2) available for job/contractor profiles.
331+
332+
"""
333+
url = 'metadata/categories'
334+
result = self.get(url)
335+
return result.get('categories', result)
336+
328337
def search_providers(self, data=None, page_offset=0, page_size=20):
329338
"""Search providers.
330339

odesk/routers/team.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,36 @@ def get_snapshots(self, company_or_team_id, online=None, disabled=None):
219219
snapshots = [snapshots]
220220

221221
return snapshots
222+
223+
def get_workdiaries_by_contract(self, contract_id, date, tz=None):
224+
"""
225+
Retrieve workdiary snapshots by contract
226+
227+
*Parameters:*
228+
:contract_id: The Contract ID.
229+
230+
:date: The target date in `yyyymmdd` format.
231+
232+
:tz: (optional) Time zone to use. Possible values:
233+
* 'mine' (default)
234+
* 'user'
235+
* 'gmt'
236+
237+
"""
238+
url = 'workdiarie/contracts/{0}/{1}'.format(contract_id, date)
239+
240+
data = {}
241+
242+
if tz:
243+
assert_parameter('tz', tz, self.TZ_CHOICES)
244+
data['tz'] = tz
245+
246+
result = self.get(url, data)
247+
if 'error' in result:
248+
return result
249+
250+
snapshots = result.get('snapshots', data).get('snapshot', [])
251+
if not isinstance(snapshots, list):
252+
snapshots = [snapshots]
253+
254+
return result['snapshots']['user'], snapshots

odesk/tests.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ def test_team():
372372
eq_(te.get_workdiaries(1, 1, 1), (teamrooms_dict['snapshots']['user'],
373373
[teamrooms_dict['snapshots']['snapshot']]))
374374

375+
#test get_workdiaries_by_contract
376+
eq_(te_v2.get_workdiaries_by_contract(1, 1), (teamrooms_dict['snapshots']['user'],
377+
[teamrooms_dict['snapshots']['snapshot']]))
378+
375379

376380
teamrooms_dict_none = {'teamrooms': '',
377381
'teamroom': '',
@@ -646,31 +650,16 @@ def patched_urlopen_hradjustment(*args, **kwargs):
646650
def test_hrv2_post_adjustment():
647651
hr = get_client().hr
648652

649-
# Using ``amount``
650-
result = hr.post_team_adjustment(
651-
1, 2, 'a test', amount=100, notes='test note')
652-
assert result == adjustments[u'adjustment'], result
653-
654653
# Using ``charge_amount``
655654
result = hr.post_team_adjustment(
656655
1, 2, 'a test', charge_amount=100, notes='test note')
657656
assert result == adjustments[u'adjustment'], result
658657

659658
try:
660-
# Using ``amount`` and ``charge_amount`` will raise error
661-
hr.post_team_adjustment(
662-
1, 2, 'a test', amount=100, charge_amount=110, notes='test note')
663-
raise Exception('No error ApiValueError was raised when using'
664-
'both ``amount`` and ``charge_amount``')
665-
except ApiValueError:
666-
pass
667-
668-
try:
669-
# If both ``amount`` and ``charge_amount`` are absent,
659+
# If ``charge_amount`` is absent,
670660
# error should be raised
671-
hr.post_team_adjustment(1, 2, 'a test', notes='test note')
672-
raise Exception('No error ApiValueError was raised when both'
673-
'both ``amount`` and ``charge_amount`` are absent')
661+
hr.post_team_adjustment(1, 2, 'a test', notes='test note', charge_amount=0)
662+
raise Exception('No error ApiValueError was raised when ``charge_amount`` is absent')
674663
except ApiValueError:
675664
pass
676665

@@ -717,11 +706,46 @@ def test_hr_restart_contract():
717706
'budget': 100,
718707
'duration': 10,
719708
'start_date': 'some start date',
720-
'end_date': 'some end date',
721709
'skills': ['Python', 'JS']
722710
}
723711

724712

713+
job_data2 = {
714+
'buyer_team_reference': 111,
715+
'title': 'Test job from API',
716+
'job_type': 'hourly',
717+
'description': 'this is test job, please do not apply to it',
718+
'visibility': 'odesk',
719+
'subcategory2': 'Web & Mobile Development',
720+
'budget': 100,
721+
'duration': 10,
722+
'start_date': 'some start date',
723+
'skills': ['Python', 'JS']
724+
}
725+
726+
727+
def patched_urlopen_job_data_parameters2(self, method, url, **kwargs):
728+
post_dict = urlparse.parse_qs(kwargs.get('body'))
729+
post_dict.pop('oauth_timestamp')
730+
post_dict.pop('oauth_signature')
731+
post_dict.pop('oauth_nonce')
732+
eq_(
733+
dict(post_dict.items()),
734+
{'buyer_team__reference': ['111'],
735+
'subcategory2': ['Web & Mobile Development'],
736+
'title': ['Test job from API'],
737+
'skills': ['Python;JS'], 'job_type': ['hourly'],
738+
'oauth_consumer_key': ['public'],
739+
'oauth_signature_method': ['HMAC-SHA1'], 'budget': ['100'],
740+
'visibility': ['odesk'],
741+
'oauth_version': ['1.0'], 'oauth_token': ['some token'],
742+
'oauth_body_hash': ['2jmj7l5rSw0yVb/vlWAYkK/YBwk='],
743+
'duration': ['10'],
744+
'start_date': ['some start date'],
745+
'description': ['this is test job, please do not apply to it']})
746+
return MicroMock(data='{"some":"data"}', status=200)
747+
748+
725749
def patched_urlopen_job_data_parameters(self, method, url, **kwargs):
726750
post_dict = urlparse.parse_qs(kwargs.get('body'))
727751
post_dict.pop('oauth_timestamp')
@@ -731,7 +755,7 @@ def patched_urlopen_job_data_parameters(self, method, url, **kwargs):
731755
dict(post_dict.items()),
732756
{'category': ['Web Development'], 'buyer_team__reference': ['111'],
733757
'subcategory': ['Other - Web Development'],
734-
'end_date': ['some end date'], 'title': ['Test job from API'],
758+
'title': ['Test job from API'],
735759
'skills': ['Python;JS'], 'job_type': ['hourly'],
736760
'oauth_consumer_key': ['public'],
737761
'oauth_signature_method': ['HMAC-SHA1'], 'budget': ['100'],
@@ -750,6 +774,22 @@ def test_job_data_parameters():
750774
hr.post_job(**job_data)
751775

752776

777+
@patch('urllib3.PoolManager.urlopen', patched_urlopen_job_data_parameters2)
778+
def test_job_data_parameters_subcategory2():
779+
hr = get_client().hr
780+
hr.post_job(**job_data2)
781+
782+
783+
@patch('urllib3.PoolManager.urlopen', patched_urlopen_job_data_parameters)
784+
def test_job_data_no_category():
785+
hr = get_client().hr
786+
787+
try:
788+
hr.post_job('111', 'test', 'hourly', 'descr', 'odesk')
789+
raise Exception('Request should raise ApiValueError exception.')
790+
except ApiValueError:
791+
pass
792+
753793
provider_dict = {'profile':
754794
{u'response_time': u'31.0000000000000000',
755795
u'dev_agency_ref': u'',

0 commit comments

Comments
 (0)