Merge lp:~mhall119/loco-team-portal/json-services into lp:loco-team-portal
- json-services
- Merge into 0.2
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 154 | ||||
Proposed branch: | lp:~mhall119/loco-team-portal/json-services | ||||
Merge into: | lp:loco-team-portal | ||||
Diff against target: |
215 lines (+189/-0) 4 files modified
loco_directory/services/__init__.py (+134/-0) loco_directory/services/urls.py (+20/-0) loco_directory/services/views.py (+34/-0) loco_directory/urls.py (+1/-0) |
||||
To merge this branch: | bzr merge lp:~mhall119/loco-team-portal/json-services | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Efrain Valles | Needs Fixing | ||
Review via email: mp+26697@code.launchpad.net |
This proposal supersedes a proposal from 2010-06-03.
Commit message
Description of the change
Provides read-only REST/JSON services for Django models used by LoCo Directory.
Daniel Holbach (dholbach) wrote : Posted in a previous version of this proposal | # |
Michael Hall (mhall119) wrote : | # |
I removed the functions in common.utils, which it turns out I didn't need because I'm not offering write capabilities (yet). That takes care of #1 above, as well as relying on "pk" and "__". I also removed the @author comments, those were just auto-generated by Eclipse when I created the new files.
/services/teams/1 might be usable to someone, but it cost me nothing to allow it, so I don't see any reason to take it out. Since this is an API, I don't think we really need a user-friendly URL scheme like /services/
/services/comments/ is the API for accessing comments on team events.
- 143. By Daniel Holbach
-
update translations
- 144. By Daniel Holbach
-
released 0.2.9
- 145. By Michael Hall
-
Check that user is authenticated (not AnonymousUser) before checking for group membership. (LP: #589226)
- 146. By Michael Hall
-
Add break that will clear the floating property of elements after the Attending Team Events list
- 147. By Daniel Holbach
-
update translation template
- 148. By Daniel Holbach
-
released 0.2.10
- 149. By Thomas Bechtold
Efrain Valles (effie-jayx) wrote : | # |
I agree with mhall119, urls don't need to be that friendly considering a ?lp_name can be used to fetch for a team with a LP name. I do beleive we should include some credits for the code borrowed.
- 150. By Daniel Holbach
-
apply patch from Seung Soo, Ha
- 151. By Daniel Holbach
-
update template
- 152. By Michael Hall <mhall@mhall-laptop>
-
Merge with trunk and updated copyright information in services
Michael Hall (mhall119) wrote : | # |
dholbach: Is there something you're waiting on for this branch to be merged?
Dave Walker (davewalker) wrote : | # |
Looking at the diff, I have some concerns about the Licence. http://
Daniel Holbach (dholbach) wrote : | # |
pyflakes output on the new code:
- loco_directory/
- (indent too much): loco_directory/
^
- loco_directory/
- loco_directory/
- loco_directory/
pylint (services/
- W: 15:model_service: Unused variable 'rem_url'
- W: 26:model_entity: Unused variable 'rem_url'
- W: 46:model_
- W: 72:get_model_id: Unused argument 'request'
- W: 87:decode: Unused argument 'url'
- W: 87:decode: Unused argument 'klass'
- W: 87:decode: Unused argument 'entity'
- W:104:JSONEncod
Questions:
- Is decode() ever used?
- Is there a reason why most of the code is in __init__.py?
Daniel Holbach (dholbach) wrote : | # |
Thanks a lot for your work on this.
- 153. By Michael Hall <mhall@mhall-laptop>
-
Removed unneeded imports
Daniel Holbach (dholbach) wrote : | # |
Do you think you can at least fix these two?
loco_directory/
^
loco_directory/
W:103:JSONEncod
Efrain Valles (effie-jayx) wrote : | # |
I have checked dholbach's remarks and I back his request for information
Why is most code in __init__.py?
I can help with fixing the two issues if mhall ack's them
- 154. By Michael Hall <mhall@mhall-laptop>
-
Fixed indentation and renamed variable 'object' to 'o'
Michael Hall (mhall119) wrote : | # |
The code is in __init__.py because I wanted the functions in the services.* namespace.
Preview Diff
1 | === added directory 'loco_directory/services' |
2 | === added file 'loco_directory/services/__init__.py' |
3 | --- loco_directory/services/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ loco_directory/services/__init__.py 2010-06-17 11:55:43 +0000 |
5 | @@ -0,0 +1,134 @@ |
6 | +# Portions of this code comes from the Oct09 project |
7 | +# developed by the Moffitt Cancer Center. |
8 | +# License: http://bitbucket.org/oct09/main/src/tip/COPYING |
9 | +# Project: http://bitbucket.org/oct09/main/wiki/Home |
10 | + |
11 | +from django.db.models import Model |
12 | +from django.http import HttpResponse |
13 | +from django.forms.fields import EMPTY_VALUES |
14 | +from django.utils import simplejson |
15 | +import re |
16 | +import decimal |
17 | + |
18 | +def model_service(model, request, url, include=None, exclude=None): |
19 | + (instance_id, rem_url) = get_model_id(request, url) |
20 | + |
21 | + try: |
22 | + if instance_id is not None and instance_id != '': |
23 | + return model_entity(model, request, url, include, exclude) |
24 | + else: |
25 | + return model_collection(model, request, url, include, exclude) |
26 | + except Exception, e: |
27 | + return HttpResponse(encode_error(e)) |
28 | + |
29 | +def model_entity(model, request, url, include=None, exclude=None): |
30 | + (entity_id, rem_url) = get_model_id(request, url) |
31 | + |
32 | + try: |
33 | + entity = model.objects.get(pk=entity_id) |
34 | + except: |
35 | + return HttpResponse(encode_error("Entity not found")) |
36 | + |
37 | + |
38 | + # Otherwise process the entity request |
39 | + if request.method == "GET": |
40 | + return HttpResponse(encode(entity, include, exclude), mimetype=get_mimetype()) |
41 | + |
42 | + elif request.method == "PUT": |
43 | + return HttpResponse(encode_error("Write operations are not supported")) |
44 | + |
45 | + elif request.method == "DELETE": |
46 | + return HttpResponse(encode_error("Write operations are not supported")) |
47 | + |
48 | + return HttpResponse(encode_error("%s Not Implemented"%request.method)) |
49 | + |
50 | +def model_collection(model, request, url, include=None, exclude=None): |
51 | + if request.method == "GET": |
52 | + results = do_search(model, request.GET, include, exclude) |
53 | + |
54 | + collection = list(results) |
55 | + return HttpResponse(encode(collection, include, exclude), mimetype=get_mimetype()) |
56 | + |
57 | + elif request.method == "POST": |
58 | + return HttpResponse(encode_error("Write operations are not supported")) |
59 | + |
60 | + return HttpResponse(encode_error("%s Not Implemented"%request.method)) |
61 | + |
62 | +def do_search(model, request, include=None, exclude=None): |
63 | + search_fields = include |
64 | + if search_fields is None: |
65 | + search_fields = [f.name for f in model._meta.local_fields+model._meta.many_to_many if not f.name.endswith("_ptr")] |
66 | + if exclude is not None: |
67 | + search_fields = [f for f in search_fields if f not in exclude] |
68 | + search_values = dict([(str(key), value) for (key, value) in request.items()]) |
69 | + |
70 | + results = model.objects.filter(**search_values) |
71 | + |
72 | + if '_sortby' in request and request['_sortby'] not in EMPTY_VALUES: |
73 | + results = results.order_by(request.get('_sortby')) |
74 | + return results |
75 | + |
76 | +def get_model_id(request, url): |
77 | + instance_id = None |
78 | + rem_url = None |
79 | + if url is not None and len(url) > 0: |
80 | + m = re.match(r"^([^/]+)/?(.*)", url) |
81 | + |
82 | + if m is not None: |
83 | + instance_id = m.group(1) |
84 | + rem_url = m.group(2) |
85 | + return (instance_id, rem_url) |
86 | + |
87 | +def encode(entity, include=None, exclude=None): |
88 | + json = JSONEncoder(include, exclude) |
89 | + return json.encode(entity) |
90 | + |
91 | +def decode(klass, entity, request, url=None): |
92 | + return request.raw_post_data |
93 | + |
94 | +def get_mimetype(): |
95 | + return "application/json" |
96 | + |
97 | +def encode_error(error): |
98 | + json = JSONEncoder() |
99 | + return json.encode({'error': unicode(error)}) |
100 | + |
101 | +class JSONEncoder(simplejson.JSONEncoder): |
102 | + |
103 | + def __init__(self, include=None, exclude=None): |
104 | + self.include = include |
105 | + self.exclude = exclude |
106 | + super(JSONEncoder, self).__init__() |
107 | + |
108 | + def get_field_value(self, o, field): |
109 | + f = getattr(o, field) |
110 | + try: |
111 | + if isinstance(f, Model): |
112 | + if hasattr(f, 'pk'): |
113 | + return self.default(f.pk) |
114 | + else: |
115 | + return None |
116 | + if isinstance(f, decimal.Decimal): |
117 | + return o._meta.get_field_by_name(field)[0]._format(f) |
118 | + else: |
119 | + return self.default(f) |
120 | + except Exception: |
121 | + return None |
122 | + |
123 | + def default(self, o=None): |
124 | + |
125 | + if isinstance(o, Model): |
126 | + model_fields = self.include |
127 | + if model_fields is None: |
128 | + model_fields = [f.name for f in o._meta.fields+o._meta.many_to_many if not f.name.endswith("_ptr")] |
129 | + if self.exclude is not None: |
130 | + model_fields = [f for f in model_fields if f not in self.exclude] |
131 | + d = dict([(field, self.get_field_value(o, field)) for field in model_fields]) |
132 | + return d |
133 | + elif o.__class__.__name__ == 'ManyRelatedManager' or o.__class__.__name__ == 'RelatedManager': |
134 | + return [r.pk for r in o.all()] |
135 | + elif isinstance(o, (int, long, float)): |
136 | + return o |
137 | + elif o is None: |
138 | + return None |
139 | + return str(o) |
140 | |
141 | === added file 'loco_directory/services/urls.py' |
142 | --- loco_directory/services/urls.py 1970-01-01 00:00:00 +0000 |
143 | +++ loco_directory/services/urls.py 2010-06-17 11:55:43 +0000 |
144 | @@ -0,0 +1,20 @@ |
145 | +''' |
146 | +Created on Jun 2, 2010 |
147 | + |
148 | +@author: mhall |
149 | +''' |
150 | +from django.conf.urls.defaults import * |
151 | + |
152 | +urlpatterns = patterns('', |
153 | + #venues |
154 | + url(r'^teams/(.*)$', 'services.views.team_service', name='team_service'), |
155 | + url(r'^events/(.*)$', 'services.views.team_event_service', name='team_event_service'), |
156 | + url(r'^global/(.*)$', 'services.views.global_event_service', name='global_event_service'), |
157 | + url(r'^comments/(.*)$', 'services.views.event_comment_service', name='event_comment_service'), |
158 | + url(r'^attendees/(.*)$', 'services.views.event_attendee_service', name='event_attendee_service'), |
159 | + url(r'^venues/(.*)$', 'services.views.venue_service', name='venue_service'), |
160 | + url(r'^users/(.*)$', 'services.views.user_service', name='user_service'), |
161 | + url(r'^groups/(.*)$', 'services.views.group_service', name='group_service'), |
162 | + url(r'^admins/(.*)$', 'services.views.admin_service', name='admin_service'), |
163 | +) |
164 | + |
165 | |
166 | === added file 'loco_directory/services/views.py' |
167 | --- loco_directory/services/views.py 1970-01-01 00:00:00 +0000 |
168 | +++ loco_directory/services/views.py 2010-06-17 11:55:43 +0000 |
169 | @@ -0,0 +1,34 @@ |
170 | +from teams.models import Team |
171 | +from events.models import GlobalEvent, TeamEvent, TeamEventComment, Attendee, TeamAdministrator |
172 | +from venues.models import Venue |
173 | +from django.contrib.auth.models import User, Group |
174 | + |
175 | +from services import model_service |
176 | + |
177 | +def team_service(request, url): |
178 | + return model_service(Team, request, url) |
179 | + |
180 | +def team_event_service(request, url): |
181 | + return model_service(TeamEvent, request, url) |
182 | + |
183 | +def global_event_service(request, url): |
184 | + return model_service(GlobalEvent, request, url) |
185 | + |
186 | +def event_attendee_service(request, url): |
187 | + return model_service(Attendee, request, url) |
188 | + |
189 | +def event_comment_service(request, url): |
190 | + return model_service(TeamEventComment, request, url) |
191 | + |
192 | +def venue_service(request, url): |
193 | + return model_service(Venue, request, url) |
194 | + |
195 | +def user_service(request, url): |
196 | + return model_service(User, request, url, include=['id', 'username', 'groups']) |
197 | + |
198 | +def group_service(request, url): |
199 | + return model_service(Group, request, url, exclude=['permissions']) |
200 | + |
201 | +def admin_service(request, url): |
202 | + return model_service(TeamAdministrator, request, url) |
203 | + |
204 | |
205 | === modified file 'loco_directory/urls.py' |
206 | --- loco_directory/urls.py 2009-12-22 23:09:58 +0000 |
207 | +++ loco_directory/urls.py 2010-06-17 11:55:43 +0000 |
208 | @@ -15,6 +15,7 @@ |
209 | url(r'^venues/', include('venues.urls')), |
210 | url(r'^logout$', 'common.views.site_logout'), |
211 | url(r'^jsi18n', 'django.views.i18n.javascript_catalog', name='jsi18n'), |
212 | + url(r'^services/', include('services.urls')), |
213 | ) |
214 | |
215 | if settings.STATIC_SERVE: |
Wow you put a lot of work into this.
- It might be worth noting that parts of the code are from http:// bitbucket. org/oct09/ experimental/ src/739a074f7ea 2/utils. py for reference. teams/< x>? :) teams/ubuntu- fr? comments/ " about?
- Is @author read or used by anything?
- Would it be worth displaying just a list of team names and team ids for /services/teams? Why would anyone use /services/
- is /services/teams/1 usable enough? I thought people would be after /services/
- what is "/services/
This needs a code review from a few more people. I'm not sure I can do the code fully justice. I feel slightly uneasy about relying on certain strings in field names like "__", "pk", etc, etc.