Mercurial > p > roundup > code
comparison roundup/rest.py @ 5596:55fa81de6a57 REST-rebased
Added summary page
Change data uri of class methods from /rest/class to /rest/data/class
committer: Ralf Schlatterbeck <rsc@runtux.com>
| author | Chau Nguyen <dangchau1991@yahoo.com> |
|---|---|
| date | Wed, 30 Jan 2019 10:26:35 +0100 |
| parents | 65caddd54da2 |
| children | de9933cfcfc4 |
comparison
equal
deleted
inserted
replaced
| 5595:65caddd54da2 | 5596:55fa81de6a57 |
|---|---|
| 12 import sys | 12 import sys |
| 13 import time | 13 import time |
| 14 import traceback | 14 import traceback |
| 15 import xml | 15 import xml |
| 16 from roundup import hyperdb | 16 from roundup import hyperdb |
| 17 from roundup import date | |
| 17 from roundup.exceptions import * | 18 from roundup.exceptions import * |
| 18 | 19 |
| 19 | 20 |
| 20 def _data_decorator(func): | 21 def _data_decorator(func): |
| 21 """Wrap the returned data into an object.""" | 22 """Wrap the returned data into an object.""" |
| 68 'data': data | 69 'data': data |
| 69 } | 70 } |
| 70 return result | 71 return result |
| 71 return format_object | 72 return format_object |
| 72 | 73 |
| 74 | |
| 73 def parse_accept_header(accept): | 75 def parse_accept_header(accept): |
| 74 """ | 76 """ |
| 75 Parse the Accept header *accept*, returning a list with 3-tuples of | 77 Parse the Accept header *accept*, returning a list with 3-tuples of |
| 76 [(str(media_type), dict(params), float(q_value)),] ordered by q values. | 78 [(str(media_type), dict(params), float(q_value)),] ordered by q values. |
| 77 | 79 |
| 109 # add the version as a media param | 111 # add the version as a media param |
| 110 try: | 112 try: |
| 111 version = media_params.append(('version', | 113 version = media_params.append(('version', |
| 112 float(rest))) | 114 float(rest))) |
| 113 except ValueError: | 115 except ValueError: |
| 114 version = 1.0 # could not be parsed | 116 version = 1.0 # could not be parsed |
| 115 # add the vendor code as a media param | 117 # add the vendor code as a media param |
| 116 media_params.append(('vendor', vnd)) | 118 media_params.append(('vendor', vnd)) |
| 117 # and re-write media_type to something like application/json so | 119 # and re-write media_type to something like application/json so |
| 118 # it can be used usefully when looking up emitters | 120 # it can be used usefully when looking up emitters |
| 119 media_type = '{}/{}'.format(typ, extra) | 121 media_type = '{}/{}'.format(typ, extra) |
| 127 else: | 129 else: |
| 128 media_params.append((key, value)) | 130 media_params.append((key, value)) |
| 129 result.append((media_type, dict(media_params), q)) | 131 result.append((media_type, dict(media_params), q)) |
| 130 result.sort(lambda x, y: -cmp(x[2], y[2])) | 132 result.sort(lambda x, y: -cmp(x[2], y[2])) |
| 131 return result | 133 return result |
| 134 | |
| 132 | 135 |
| 133 class RestfulInstance(object): | 136 class RestfulInstance(object): |
| 134 """The RestfulInstance performs REST request from the client""" | 137 """The RestfulInstance performs REST request from the client""" |
| 135 | 138 |
| 136 __default_patch_op = "replace" # default operator for PATCH method | 139 __default_patch_op = "replace" # default operator for PATCH method |
| 875 "Accept-Patch", | 878 "Accept-Patch", |
| 876 "application/x-www-form-urlencoded, multipart/form-data" | 879 "application/x-www-form-urlencoded, multipart/form-data" |
| 877 ) | 880 ) |
| 878 return 204, "" | 881 return 204, "" |
| 879 | 882 |
| 883 @_data_decorator | |
| 884 def summary(self, input): | |
| 885 """Get a summary of resource from class URI. | |
| 886 | |
| 887 This function returns only items have View permission | |
| 888 class_name should be valid already | |
| 889 | |
| 890 Args: | |
| 891 class_name (string): class name of the resource (Ex: issue, msg) | |
| 892 input (list): the submitted form of the user | |
| 893 | |
| 894 Returns: | |
| 895 int: http status code 200 (OK) | |
| 896 list: | |
| 897 """ | |
| 898 if not self.db.security.hasPermission( | |
| 899 'View', self.db.getuid(), 'issue' | |
| 900 ) and not self.db.security.hasPermission( | |
| 901 'View', self.db.getuid(), 'status' | |
| 902 ) and not self.db.security.hasPermission( | |
| 903 'View', self.db.getuid(), 'issue' | |
| 904 ): | |
| 905 raise Unauthorised('Permission to view summary denied') | |
| 906 | |
| 907 old = date.Date('-1w') | |
| 908 | |
| 909 created = [] | |
| 910 summary = {} | |
| 911 messages = [] | |
| 912 | |
| 913 # loop through all the recently-active issues | |
| 914 for issue_id in self.db.issue.filter(None, {'activity': '-1w;'}): | |
| 915 num = 0 | |
| 916 status_name = self.db.status.get( | |
| 917 self.db.issue.get(issue_id, 'status'), | |
| 918 'name' | |
| 919 ) | |
| 920 issue_object = { | |
| 921 'id': issue_id, | |
| 922 'link': self.base_path + 'issue' + issue_id, | |
| 923 'title': self.db.issue.get(issue_id, 'title') | |
| 924 } | |
| 925 for x, ts, uid, action, data in self.db.issue.history(issue_id): | |
| 926 if ts < old: | |
| 927 continue | |
| 928 if action == 'create': | |
| 929 created.append(issue_object) | |
| 930 elif action == 'set' and 'messages' in data: | |
| 931 num += 1 | |
| 932 summary.setdefault(status_name, []).append(issue_object) | |
| 933 messages.append((num, issue_object)) | |
| 934 | |
| 935 messages.sort(reverse=True) | |
| 936 | |
| 937 result = { | |
| 938 'created': created, | |
| 939 'summary': summary, | |
| 940 'most_discussed': messages[:10] | |
| 941 } | |
| 942 | |
| 943 return 200, result | |
| 944 | |
| 880 def dispatch(self, method, uri, input): | 945 def dispatch(self, method, uri, input): |
| 881 """format and process the request""" | 946 """format and process the request""" |
| 882 # if X-HTTP-Method-Override is set, follow the override method | 947 # if X-HTTP-Method-Override is set, follow the override method |
| 883 headers = self.client.request.headers | 948 headers = self.client.request.headers |
| 884 method = headers.getheader('X-HTTP-Method-Override') or method | 949 method = headers.getheader('X-HTTP-Method-Override') or method |
| 918 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" | 983 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" |
| 919 ) | 984 ) |
| 920 | 985 |
| 921 # PATH is split to multiple pieces | 986 # PATH is split to multiple pieces |
| 922 # 0 - rest | 987 # 0 - rest |
| 923 # 1 - resource | 988 # 1 - data |
| 924 # 2 - attribute | 989 # 2 - resource |
| 925 uri_split = uri.split("/") | 990 # 3 - attribute |
| 926 resource_uri = uri_split[1] | 991 uri_split = uri.lower().split("/") |
| 927 | |
| 928 try: | |
| 929 class_name, item_id = hyperdb.splitDesignator(resource_uri) | |
| 930 except hyperdb.DesignatorError: | |
| 931 class_name = resource_uri | |
| 932 item_id = None | |
| 933 | 992 |
| 934 # Call the appropriate method | 993 # Call the appropriate method |
| 935 if (class_name not in self.db.classes) or (len(uri_split) > 3): | 994 if len(uri_split) == 2 and uri_split[1] == 'summary': |
| 995 output = self.summary(input) | |
| 996 elif 4 >= len(uri_split) > 2 and uri_split[1] == 'data': | |
| 997 resource_uri = uri_split[2] | |
| 998 try: | |
| 999 class_name, item_id = hyperdb.splitDesignator(resource_uri) | |
| 1000 except hyperdb.DesignatorError: | |
| 1001 class_name = resource_uri | |
| 1002 item_id = None | |
| 1003 | |
| 1004 if class_name not in self.db.classes: | |
| 1005 output = self.error_obj(404, "Not found") | |
| 1006 elif item_id is None: | |
| 1007 if len(uri_split) == 3: | |
| 1008 output = getattr( | |
| 1009 self, "%s_collection" % method.lower() | |
| 1010 )(class_name, input) | |
| 1011 else: | |
| 1012 output = self.error_obj(404, "Not found") | |
| 1013 else: | |
| 1014 if len(uri_split) == 3: | |
| 1015 output = getattr( | |
| 1016 self, "%s_element" % method.lower() | |
| 1017 )(class_name, item_id, input) | |
| 1018 else: | |
| 1019 output = getattr( | |
| 1020 self, "%s_attribute" % method.lower() | |
| 1021 )(class_name, item_id, uri_split[3], input) | |
| 1022 else: | |
| 936 output = self.error_obj(404, "Not found") | 1023 output = self.error_obj(404, "Not found") |
| 937 elif item_id is None: | |
| 938 if len(uri_split) == 2: | |
| 939 output = getattr( | |
| 940 self, "%s_collection" % method.lower() | |
| 941 )(class_name, input) | |
| 942 else: | |
| 943 if len(uri_split) == 2: | |
| 944 output = getattr( | |
| 945 self, "%s_element" % method.lower() | |
| 946 )(class_name, item_id, input) | |
| 947 else: | |
| 948 output = getattr( | |
| 949 self, "%s_attribute" % method.lower() | |
| 950 )(class_name, item_id, uri_split[2], input) | |
| 951 | 1024 |
| 952 # Format the content type | 1025 # Format the content type |
| 953 if data_type.lower() == "json": | 1026 if data_type.lower() == "json": |
| 954 self.client.setHeader("Content-Type", "application/json") | 1027 self.client.setHeader("Content-Type", "application/json") |
| 955 if pretty_output: | 1028 if pretty_output: |
