]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/xmlrpc.py
Add XML-RPC interface and command line client
[patchwork] / apps / patchwork / xmlrpc.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Nate Case <ncase@xes-inc.com>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 #
20 # The XML-RPC interface provides a watered down, read-only interface to
21 # the Patchwork database.  It's intended to be safe to export to the public
22 # Internet.  A small subset of the object data is included, and the type
23 # of requests/queries you can do is limited by the methods
24 # that we export.
25
26 from patchwork.models import Patch, Project, Person, Bundle, State
27
28 # We allow most of the Django field lookup types for remote queries
29 LOOKUP_TYPES = ["iexact", "contains", "icontains", "gt", "gte", "lt",
30                 "in", "startswith", "istartswith", "endswith",
31                 "iendswith", "range", "year", "month", "day", "isnull" ]
32
33 #######################################################################
34 # Helper functions
35 #######################################################################
36
37 def project_to_dict(obj):
38     """Return a trimmed down dictionary representation of a Project
39     object which is OK to send to the client."""
40     return \
41         {
42          'id'           : obj.id,
43          'linkname'     : obj.linkname,
44          'name'         : obj.name,
45         }
46
47 def person_to_dict(obj):
48     """Return a trimmed down dictionary representation of a Person
49     object which is OK to send to the client."""
50     return \
51         {
52          'id'           : obj.id,
53          'email'        : obj.email,
54          'name'         : obj.name,
55          'user'         : str(obj.user),
56         }
57
58 def patch_to_dict(obj):
59     """Return a trimmed down dictionary representation of a Patch
60     object which is OK to send to the client."""
61     return \
62         {
63          'id'           : obj.id,
64          'date'         : str(obj.date),
65          'filename'     : obj.filename(),
66          'msgid'        : obj.msgid,
67          'name'         : obj.name,
68          'project'      : str(obj.project),
69          'project_id'   : obj.project_id,
70          'state'        : str(obj.state),
71          'state_id'     : obj.state_id,
72          'submitter'    : str(obj.submitter),
73          'submitter_id' : obj.submitter_id,
74          'delegate'     : str(obj.delegate),
75          'delegate_id'  : max(obj.delegate_id, 0),
76          'commit_ref'   : max(obj.commit_ref, ''),
77         }
78
79 def bundle_to_dict(obj):
80     """Return a trimmed down dictionary representation of a Bundle
81     object which is OK to send to the client."""
82     return \
83         {
84          'id'           : obj.id,
85          'name'         : obj.name,
86          'n_patches'    : obj.n_patches(),
87          'public_url'   : obj.public_url(),
88         }
89
90 def state_to_dict(obj):
91     """Return a trimmed down dictionary representation of a State
92     object which is OK to send to the client."""
93     return \
94         {
95          'id'           : obj.id,
96          'name'         : obj.name,
97         }
98
99 #######################################################################
100 # Public XML-RPC methods
101 #######################################################################
102
103 def pw_rpc_version():
104     """Return Patchwork XML-RPC interface version."""
105     return 1
106
107 def project_list(search_str="", max_count=0):
108     """Get a list of projects matching the given filters."""
109     try:
110         if len(search_str) > 0:
111             projects = Project.objects.filter(name__icontains = search_str)
112         else:
113             projects = Project.objects.all()
114
115         if max_count > 0:
116             return map(project_to_dict, projects)[:max_count]
117         else:
118             return map(project_to_dict, projects)
119     except:
120         return []
121
122 def project_get(project_id):
123     """Return structure for the given project ID."""
124     try:
125         project = Project.objects.filter(id = project_id)[0]
126         return project_to_dict(project)
127     except:
128         return {}
129
130 def person_list(search_str="", max_count=0):
131     """Get a list of Person objects matching the given filters."""
132     try:
133         if len(search_str) > 0:
134             people = (Person.objects.filter(name__icontains = search_str) |
135                 Person.objects.filter(email__icontains = search_str))
136         else:
137             people = Person.objects.all()
138
139         if max_count > 0:
140             return map(person_to_dict, people)[:max_count]
141         else:
142             return map(person_to_dict, people)
143
144     except:
145         return []
146
147 def person_get(person_id):
148     """Return structure for the given person ID."""
149     try:
150         person = Person.objects.filter(id = person_id)[0]
151         return person_to_dict(person)
152     except:
153         return {}
154
155 def patch_list(filter={}):
156     """Get a list of patches matching the given filters."""
157     try:
158         # We allow access to many of the fields.  But, some fields are
159         # filtered by raw object so we must lookup by ID instead over
160         # XML-RPC.
161         ok_fields = [
162             "id",
163             "name",
164             "project_id",
165             "submitter_id",
166             "delegate_id",
167             "state_id",
168             "date",
169             "commit_ref",
170             "hash",
171             "msgid",
172             "name",
173             "max_count",
174             ]
175
176         dfilter = {}
177         max_count = 0
178
179         for key in filter:
180             parts = key.split("__")
181             if ok_fields.count(parts[0]) == 0:
182                 # Invalid field given
183                 return []
184             if len(parts) > 1:
185                 if LOOKUP_TYPES.count(parts[1]) == 0:
186                     # Invalid lookup type given
187                     return []
188
189             if parts[0] == 'project_id':
190                 dfilter['project'] = Project.objects.filter(id =
191                                         filter[key])[0]
192             elif parts[0] == 'submitter_id':
193                 dfilter['submitter'] = Person.objects.filter(id =
194                                         filter[key])[0]
195             elif parts[0] == 'state_id':
196                 dfilter['state'] = State.objects.filter(id =
197                                         filter[key])[0]
198             elif parts[0] == 'max_count':
199                 max_count = filter[key]
200             else:
201                 dfilter[key] = filter[key]
202
203         patches = Patch.objects.filter(**dfilter)
204
205         if max_count > 0:
206             return map(patch_to_dict, patches)[:max_count]
207         else:
208             return map(patch_to_dict, patches)
209
210     except:
211         return []
212
213 def patch_get(patch_id):
214     """Return structure for the given patch ID."""
215     try:
216         patch = Patch.objects.filter(id = patch_id)[0]
217         return patch_to_dict(patch)
218     except:
219         return {}
220
221 def patch_get_mbox(patch_id):
222     """Return mbox string for the given patch ID."""
223     try:
224         patch = Patch.objects.filter(id = patch_id)[0]
225         return patch.mbox().as_string()
226     except:
227         return ""
228
229 def patch_get_diff(patch_id):
230     """Return diff for the given patch ID."""
231     try:
232         patch = Patch.objects.filter(id = patch_id)[0]
233         return patch.content
234     except:
235         return ""
236
237 def state_list(search_str="", max_count=0):
238     """Get a list of state structures matching the given search string."""
239     try:
240         if len(search_str) > 0:
241             states = State.objects.filter(name__icontains = search_str)
242         else:
243             states = State.objects.all()
244
245         if max_count > 0:
246             return map(state_to_dict, states)[:max_count]
247         else:
248             return map(state_to_dict, states)
249     except:
250         return []
251
252 def state_get(state_id):
253     """Return structure for the given state ID."""
254     try:
255         state = State.objects.filter(id = state_id)[0]
256         return state_to_dict(state)
257     except:
258         return {}