@@ -51,27 +51,44 @@ def props_from_args(self, cl, args, itemid=None):
5151 value = arg .value
5252 if key not in class_props :
5353 continue
54- if isinstance (key , unicode ):
55- try :
56- key = key .encode ('ascii' )
57- except UnicodeEncodeError :
58- raise UsageError (
59- 'argument %r is no valid ascii keyword' % key
60- )
61- if isinstance (value , unicode ):
62- value = value .encode ('utf-8' )
63- if value :
64- try :
65- props [key ] = hyperdb .rawToHyperdb (
66- self .db , cl , itemid , key , value
67- )
68- except hyperdb .HyperdbValueError , msg :
69- raise UsageError (msg )
70- else :
71- props [key ] = None
54+ props [key ] = self .prop_from_arg (cl , key , value , itemid )
7255
7356 return props
7457
58+ def prop_from_arg (self , cl , key , value , itemid = None ):
59+ """Construct a property from the given argument,
60+ and return them after validation.
61+
62+ Args:
63+ cl (string): class object of the resource
64+ key (string): attribute key
65+ value (string): attribute value
66+ itemid (string, optional): itemid of the object
67+
68+ Returns:
69+ value: value of validated properties
70+
71+ """
72+ prop = None
73+ if isinstance (key , unicode ):
74+ try :
75+ key = key .encode ('ascii' )
76+ except UnicodeEncodeError :
77+ raise UsageError (
78+ 'argument %r is no valid ascii keyword' % key
79+ )
80+ if isinstance (value , unicode ):
81+ value = value .encode ('utf-8' )
82+ if value :
83+ try :
84+ prop = hyperdb .rawToHyperdb (
85+ self .db , cl , itemid , key , value
86+ )
87+ except hyperdb .HyperdbValueError , msg :
88+ raise UsageError (msg )
89+
90+ return prop
91+
7592 @staticmethod
7693 def error_obj (status , msg , source = None ):
7794 """Wrap the error data into an object. This function is temporally and
@@ -152,7 +169,7 @@ def get_element(self, class_name, item_id, input):
152169 'View' , self .db .getuid (), class_name , itemid = item_id
153170 ):
154171 raise Unauthorised (
155- 'Permission to view %s item %s denied' % (class_name , item_id )
172+ 'Permission to view %s%s denied' % (class_name , item_id )
156173 )
157174
158175 class_obj = self .db .getclass (class_name )
@@ -174,6 +191,46 @@ def get_element(self, class_name, item_id, input):
174191
175192 return 200 , result
176193
194+ def get_attribute (self , class_name , item_id , attr_name , input ):
195+ """GET resource from attribute URI.
196+
197+ This function returns only attribute has View permission
198+ class_name should be valid already
199+
200+ Args:
201+ class_name (string): class name of the resource (Ex: issue, msg)
202+ item_id (string): id of the resource (Ex: 12, 15)
203+ attr_name (string): attribute of the resource (Ex: title, nosy)
204+ input (list): the submitted form of the user
205+
206+ Returns:
207+ int: http status code 200 (OK)
208+ list: a dictionary represents the attribute
209+ id: id of the object
210+ type: class name of the attribute
211+ link: link to the attribute
212+ data: data of the requested attribute
213+ """
214+ if not self .db .security .hasPermission (
215+ 'View' , self .db .getuid (), class_name , attr_name , item_id
216+ ):
217+ raise Unauthorised (
218+ 'Permission to view %s%s %s denied' %
219+ (class_name , item_id , attr_name )
220+ )
221+
222+ class_obj = self .db .getclass (class_name )
223+ data = class_obj .get (item_id , attr_name )
224+ result = {
225+ 'id' : item_id ,
226+ 'type' : type (data ),
227+ 'link' : "%s%s%s/%s" %
228+ (self .base_path , class_name , item_id , attr_name ),
229+ 'data' : data
230+ }
231+
232+ return 200 , result
233+
177234 def post_collection (self , class_name , input ):
178235 """POST a new object to a class
179236
@@ -237,6 +294,10 @@ def post_element(self, class_name, item_id, input):
237294 """POST to an object of a class is not allowed"""
238295 raise Reject ('POST to an item is not allowed' )
239296
297+ def post_attribute (self , class_name , item_id , attr_name , input ):
298+ """POST to an attribute of an object is not allowed"""
299+ raise Reject ('POST to an attribute is not allowed' )
300+
240301 def put_collection (self , class_name , input ):
241302 """PUT a class is not allowed"""
242303 raise Reject ('PUT a class is not allowed' )
@@ -285,6 +346,54 @@ def put_element(self, class_name, item_id, input):
285346 }
286347 return 200 , result
287348
349+ def put_attribute (self , class_name , item_id , attr_name , input ):
350+ """PUT an attribute to an object
351+
352+ Args:
353+ class_name (string): class name of the resource (Ex: issue, msg)
354+ item_id (string): id of the resource (Ex: 12, 15)
355+ attr_name (string): attribute of the resource (Ex: title, nosy)
356+ input (list): the submitted form of the user
357+
358+ Returns:
359+ int: http status code 200 (OK)
360+ dict:a dictionary represents the modified object
361+ id: id of the object
362+ type: class name of the object
363+ link: link to the object
364+ attributes: a dictionary represent only changed attributes of
365+ the object
366+ """
367+ if not self .db .security .hasPermission (
368+ 'Edit' , self .db .getuid (), class_name , attr_name , item_id
369+ ):
370+ raise Unauthorised (
371+ 'Permission to edit %s%s %s denied' %
372+ (class_name , item_id , attr_name )
373+ )
374+
375+ class_obj = self .db .getclass (class_name )
376+ props = {
377+ attr_name : self .prop_from_arg (
378+ class_obj , attr_name , input ['data' ].value , item_id
379+ )
380+ }
381+
382+ try :
383+ result = class_obj .set (item_id , ** props )
384+ self .db .commit ()
385+ except (TypeError , IndexError , ValueError ), message :
386+ raise ValueError (message )
387+
388+ result = {
389+ 'id' : item_id ,
390+ 'type' : class_name ,
391+ 'link' : self .base_path + class_name + item_id ,
392+ 'attribute' : result
393+ }
394+
395+ return 200 , result
396+
288397 def delete_collection (self , class_name , input ):
289398 """DELETE all objects in a class
290399
@@ -352,11 +461,74 @@ def delete_element(self, class_name, item_id, input):
352461
353462 return 200 , result
354463
464+ def delete_attribute (self , class_name , item_id , attr_name , input ):
465+ """DELETE an attribute in a object by setting it to None or empty
466+
467+ Args:
468+ class_name (string): class name of the resource (Ex: issue, msg)
469+ item_id (string): id of the resource (Ex: 12, 15)
470+ attr_name (string): attribute of the resource (Ex: title, nosy)
471+ input (list): the submitted form of the user
472+
473+ Returns:
474+ int: http status code 200 (OK)
475+ dict:
476+ status (string): 'ok'
477+ """
478+ if not self .db .security .hasPermission (
479+ 'Edit' , self .db .getuid (), class_name , attr_name , item_id
480+ ):
481+ raise Unauthorised (
482+ 'Permission to delete %s%s %s denied' %
483+ (class_name , item_id , attr_name )
484+ )
485+
486+ class_obj = self .db .getclass (class_name )
487+ props = {}
488+ prop_obj = class_obj .get (item_id , attr_name )
489+ if isinstance (prop_obj , list ):
490+ props [attr_name ] = []
491+ else :
492+ props [attr_name ] = None
493+
494+ try :
495+ class_obj .set (item_id , ** props )
496+ self .db .commit ()
497+ except (TypeError , IndexError , ValueError ), message :
498+ raise ValueError (message )
499+
500+ result = {
501+ 'status' : 'ok'
502+ }
503+
504+ return 200 , result
505+
355506 def patch_collection (self , class_name , input ):
356507 """PATCH a class is not allowed"""
357508 raise Reject ('PATCH a class is not allowed' )
358509
359510 def patch_element (self , class_name , item_id , input ):
511+ """PATCH an object
512+
513+ Patch an element using 3 operators
514+ ADD : Append new value to the object's attribute
515+ REPLACE: Replace object's attribute
516+ REMOVE: Clear object's attribute
517+
518+ Args:
519+ class_name (string): class name of the resource (Ex: issue, msg)
520+ item_id (string): id of the resource (Ex: 12, 15)
521+ input (list): the submitted form of the user
522+
523+ Returns:
524+ int: http status code 200 (OK)
525+ dict: a dictionary represents the modified object
526+ id: id of the object
527+ type: class name of the object
528+ link: link to the object
529+ attributes: a dictionary represent only changed attributes of
530+ the object
531+ """
360532 try :
361533 op = input ['op' ].value .lower ()
362534 except KeyError :
@@ -401,6 +573,78 @@ def patch_element(self, class_name, item_id, input):
401573 }
402574 return 200 , result
403575
576+ def patch_attribute (self , class_name , item_id , attr_name , input ):
577+ """PATCH an attribute of an object
578+
579+ Patch an element using 3 operators
580+ ADD : Append new value to the attribute
581+ REPLACE: Replace attribute
582+ REMOVE: Clear attribute
583+
584+ Args:
585+ class_name (string): class name of the resource (Ex: issue, msg)
586+ item_id (string): id of the resource (Ex: 12, 15)
587+ attr_name (string): attribute of the resource (Ex: title, nosy)
588+ input (list): the submitted form of the user
589+
590+ Returns:
591+ int: http status code 200 (OK)
592+ dict: a dictionary represents the modified object
593+ id: id of the object
594+ type: class name of the object
595+ link: link to the object
596+ attributes: a dictionary represent only changed attributes of
597+ the object
598+ """
599+ try :
600+ op = input ['op' ].value .lower ()
601+ except KeyError :
602+ op = "replace"
603+ class_obj = self .db .getclass (class_name )
604+
605+ if not self .db .security .hasPermission (
606+ 'Edit' , self .db .getuid (), class_name , attr_name , item_id
607+ ):
608+ raise Unauthorised (
609+ 'Permission to edit %s%s %s denied' %
610+ (class_name , item_id , attr_name )
611+ )
612+
613+ prop = attr_name
614+ class_obj = self .db .getclass (class_name )
615+ props = {
616+ prop : self .prop_from_arg (
617+ class_obj , prop , input ['data' ].value , item_id
618+ )
619+ }
620+
621+ if op == 'add' :
622+ props [prop ] = class_obj .get (item_id , prop ) + props [prop ]
623+ elif op == 'replace' :
624+ pass
625+ elif op == 'remove' :
626+ current_prop = class_obj .get (item_id , prop )
627+ if isinstance (current_prop , list ):
628+ props [prop ] = []
629+ else :
630+ props [prop ] = None
631+ else :
632+ raise UsageError ('PATCH Operation %s is not allowed' % op )
633+
634+ try :
635+ result = class_obj .set (item_id , ** props )
636+ self .db .commit ()
637+ except (TypeError , IndexError , ValueError ), message :
638+ raise ValueError (message )
639+
640+ result = {
641+ 'id' : item_id ,
642+ 'type' : class_name ,
643+ 'link' : self .base_path + class_name + item_id ,
644+ 'attribute' : result
645+ }
646+ return 200 , result
647+
404648 def options_collection (self , class_name , input ):
405649 """OPTION return the HTTP Header for the class uri
406650
@@ -419,8 +663,20 @@ def options_element(self, class_name, item_id, input):
419663 """
420664 self .client .setHeader (
421665 "Accept-Patch" ,
422- "application/x-www-form-urlencoded, "
423- "multipart/form-data"
666+ "application/x-www-form-urlencoded, multipart/form-data"
667+ )
668+ return 204 , ""
669+
670+ def option_attribute (self , class_name , item_id , attr_name , input ):
671+ """OPTION return the HTTP Header for the attribute uri
672+
673+ Returns:
674+ int: http status code 204 (No content)
675+ body (string): an empty string
676+ """
677+ self .client .setHeader (
678+ "Accept-Patch" ,
679+ "application/x-www-form-urlencoded, multipart/form-data"
424680 )
425681 return 204 , ""
426682
@@ -430,7 +686,8 @@ def dispatch(self, method, uri, input):
430686 # 0 - rest
431687 # 1 - resource
432688 # 2 - attribute
433- resource_uri = uri .split ("/" )[1 ]
689+ uri_split = uri .split ("/" )
690+ resource_uri = uri_split [1 ]
434691
435692 # if X-HTTP-Method-Override is set, follow the override method
436693 headers = self .client .request .headers
@@ -486,9 +743,14 @@ def dispatch(self, method, uri, input):
486743 )(resource_uri , input )
487744 else :
488745 class_name , item_id = hyperdb .splitDesignator (resource_uri )
489- response_code , output = getattr (
490- self , "%s_element" % method .lower ()
491- )(class_name , item_id , input )
746+ if len (uri_split ) == 3 :
747+ response_code , output = getattr (
748+ self , "%s_attribute" % method .lower ()
749+ )(class_name , item_id , uri_split [2 ], input )
750+ else :
751+ response_code , output = getattr (
752+ self , "%s_element" % method .lower ()
753+ )(class_name , item_id , input )
492754 output = RestfulInstance .data_obj (output )
493755 self .client .response_code = response_code
494756 except IndexError , msg :
0 commit comments