Commit 0a011ec0118ac87a87bf709ecc9f05cd466dd6df
Exists in
master
and in
39 other branches
Merge pull request #147 from TracyWebTech/adding_api
Adding api - updates #93
Showing
9 changed files
with
315 additions
and
9 deletions
Show diff stats
requirements.txt
@@ -17,6 +17,7 @@ python-memcached==1.53 | @@ -17,6 +17,7 @@ python-memcached==1.53 | ||
17 | django-hitcounter==0.1.1 | 17 | django-hitcounter==0.1.1 |
18 | Pillow==2.2.1 | 18 | Pillow==2.2.1 |
19 | django-i18n-model==0.0.7 | 19 | django-i18n-model==0.0.7 |
20 | +django-tastypie==0.11.0 | ||
20 | 21 | ||
21 | gunicorn==18.0 | 22 | gunicorn==18.0 |
22 | gevent==0.13.8 | 23 | gevent==0.13.8 |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +import datetime | ||
3 | +from south.db import db | ||
4 | +from south.v2 import SchemaMigration | ||
5 | +from django.db import models | ||
6 | + | ||
7 | + | ||
8 | +class Migration(SchemaMigration): | ||
9 | + | ||
10 | + def forwards(self, orm): | ||
11 | + pass | ||
12 | + | ||
13 | + def backwards(self, orm): | ||
14 | + pass | ||
15 | + | ||
16 | + models = { | ||
17 | + | ||
18 | + } | ||
19 | + | ||
20 | + complete_apps = ['api'] | ||
0 | \ No newline at end of file | 21 | \ No newline at end of file |
@@ -0,0 +1,117 @@ | @@ -0,0 +1,117 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | + | ||
3 | +from tastypie import fields | ||
4 | +from tastypie.constants import ALL_WITH_RELATIONS, ALL | ||
5 | +from tastypie.resources import ModelResource | ||
6 | + | ||
7 | +from accounts.models import User | ||
8 | +from super_archives.models import Message, EmailAddress | ||
9 | +from proxy.models import Revision, Ticket, Wiki | ||
10 | + | ||
11 | + | ||
12 | +class UserResource(ModelResource): | ||
13 | + class Meta: | ||
14 | + queryset = User.objects.filter(is_active=True) | ||
15 | + resource_name = 'user' | ||
16 | + fields = ['username', 'institution', 'role', 'bio', 'first_name', | ||
17 | + 'last_name', 'email'] | ||
18 | + allowed_methods = ['get', ] | ||
19 | + filtering = { | ||
20 | + 'email': ('exact', ), | ||
21 | + 'username': ALL, | ||
22 | + 'institution': ALL, | ||
23 | + 'role': ALL, | ||
24 | + 'bio': ALL, | ||
25 | + } | ||
26 | + | ||
27 | + def dehydrate_email(self, bundle): | ||
28 | + return '' | ||
29 | + | ||
30 | + | ||
31 | +class EmailAddressResource(ModelResource): | ||
32 | + user = fields.ForeignKey(UserResource, 'user', full=False, null=True) | ||
33 | + | ||
34 | + class Meta: | ||
35 | + queryset = EmailAddress.objects.all() | ||
36 | + resource_name = 'emailaddress' | ||
37 | + excludes = ['md5', ] | ||
38 | + allowed_methods = ['get', ] | ||
39 | + filtering = { | ||
40 | + 'address': ('exact', ), | ||
41 | + 'user': ALL_WITH_RELATIONS, | ||
42 | + 'real_name': ALL, | ||
43 | + } | ||
44 | + | ||
45 | + def dehydrate_address(self, bundle): | ||
46 | + return '' | ||
47 | + | ||
48 | + | ||
49 | +class MessageResource(ModelResource): | ||
50 | + from_address = fields.ForeignKey(EmailAddressResource, 'from_address', | ||
51 | + full=False) | ||
52 | + | ||
53 | + class Meta: | ||
54 | + queryset = Message.objects.all() | ||
55 | + resource_name = 'message' | ||
56 | + excludes = ['spam', 'subject_clean', 'message_id'] | ||
57 | + filtering = { | ||
58 | + 'from_address': ALL_WITH_RELATIONS, | ||
59 | + 'subject': ALL, | ||
60 | + 'body': ALL, | ||
61 | + 'received_time': ALL, | ||
62 | + } | ||
63 | + | ||
64 | + | ||
65 | +class RevisionResource(ModelResource): | ||
66 | + class Meta: | ||
67 | + queryset = Revision.objects.all() | ||
68 | + resource_name = 'revision' | ||
69 | + excludes = ['collaborators', ] | ||
70 | + filtering = { | ||
71 | + 'key': ALL, | ||
72 | + 'rev': ALL, | ||
73 | + 'author': ALL, | ||
74 | + 'message': ALL, | ||
75 | + 'repository_name': ALL, | ||
76 | + 'created': ALL, | ||
77 | + } | ||
78 | + | ||
79 | + | ||
80 | +class TicketResource(ModelResource): | ||
81 | + class Meta: | ||
82 | + queryset = Ticket.objects.all() | ||
83 | + resource_name = 'ticket' | ||
84 | + excludes = ['collaborators', ] | ||
85 | + filtering = { | ||
86 | + 'id': ALL, | ||
87 | + 'summary': ALL, | ||
88 | + 'description': ALL, | ||
89 | + 'milestone': ALL, | ||
90 | + 'priority': ALL, | ||
91 | + 'component': ALL, | ||
92 | + 'version': ALL, | ||
93 | + 'severity': ALL, | ||
94 | + 'reporter': ALL, | ||
95 | + 'author': ALL, | ||
96 | + 'status': ALL, | ||
97 | + 'keywords': ALL, | ||
98 | + 'created': ALL, | ||
99 | + 'modified': ALL, | ||
100 | + 'modified_by': ALL, | ||
101 | + } | ||
102 | + | ||
103 | + | ||
104 | +class WikiResource(ModelResource): | ||
105 | + class Meta: | ||
106 | + queryset = Wiki.objects.all() | ||
107 | + resource_name = 'wiki' | ||
108 | + excludes = ['collaborators', ] | ||
109 | + filtering = { | ||
110 | + 'name': ALL, | ||
111 | + 'wiki_text': ALL, | ||
112 | + 'author': ALL, | ||
113 | + 'name': ALL, | ||
114 | + 'created': ALL, | ||
115 | + 'modified': ALL, | ||
116 | + 'modified_by': ALL, | ||
117 | + } |
src/api/urls.py
1 | +# -*- coding: utf-8 -*- | ||
1 | 2 | ||
2 | from django.conf.urls import patterns, include, url | 3 | from django.conf.urls import patterns, include, url |
3 | 4 | ||
5 | +from tastypie.api import Api | ||
6 | + | ||
7 | +from .models import (UserResource, EmailAddressResource, MessageResource, | ||
8 | + RevisionResource, TicketResource, WikiResource) | ||
4 | from .views import VoteView | 9 | from .views import VoteView |
5 | 10 | ||
6 | 11 | ||
12 | +api = Api(api_name='v1') | ||
13 | +api.register(UserResource()) | ||
14 | +api.register(EmailAddressResource()) | ||
15 | +api.register(MessageResource()) | ||
16 | +api.register(RevisionResource()) | ||
17 | +api.register(TicketResource()) | ||
18 | +api.register(WikiResource()) | ||
19 | + | ||
20 | + | ||
7 | urlpatterns = patterns('', | 21 | urlpatterns = patterns('', |
8 | url(r'message/(?P<msg_id>\d+)/vote$', VoteView.as_view()), | 22 | url(r'message/(?P<msg_id>\d+)/vote$', VoteView.as_view()), |
23 | + | ||
24 | + # tastypie urls | ||
25 | + url(r'', include(api.urls)), | ||
9 | ) | 26 | ) |
src/colab/custom_settings.py
@@ -298,6 +298,10 @@ CONVERSEJS_ALLOW_CONTACT_REQUESTS = False | @@ -298,6 +298,10 @@ CONVERSEJS_ALLOW_CONTACT_REQUESTS = False | ||
298 | CONVERSEJS_SHOW_ONLY_ONLINE_USERS = True | 298 | CONVERSEJS_SHOW_ONLY_ONLINE_USERS = True |
299 | 299 | ||
300 | 300 | ||
301 | +# Tastypie settings | ||
302 | +TASTYPIE_DEFAULT_FORMATS = ['json', ] | ||
303 | + | ||
304 | + | ||
301 | try: | 305 | try: |
302 | from local_settings import * | 306 | from local_settings import * |
303 | except ImportError: | 307 | except ImportError: |
src/colab/urls.py
@@ -12,6 +12,8 @@ admin.autodiscover() | @@ -12,6 +12,8 @@ admin.autodiscover() | ||
12 | 12 | ||
13 | urlpatterns = patterns('', | 13 | urlpatterns = patterns('', |
14 | url(r'^$', 'home.views.index', name='home'), | 14 | url(r'^$', 'home.views.index', name='home'), |
15 | + url(r'open-data/$', TemplateView.as_view(template_name='open-data.html'), | ||
16 | + name='opendata'), | ||
15 | 17 | ||
16 | url(r'^search/', include('search.urls')), | 18 | url(r'^search/', include('search.urls')), |
17 | url(r'^archives/', include('super_archives.urls')), | 19 | url(r'^archives/', include('super_archives.urls')), |
src/templates/base.html
@@ -164,16 +164,17 @@ | @@ -164,16 +164,17 @@ | ||
164 | <div class="row"> </div> | 164 | <div class="row"> </div> |
165 | 165 | ||
166 | {% block footer %} | 166 | {% block footer %} |
167 | - <p class="col-lg-12 text-center">{% trans "Last email imported at" %} {{ last_imported_message.received_time|date:'DATETIME_FORMAT' }}</p> | ||
168 | <div class="row"> | 167 | <div class="row"> |
169 | - </div> | ||
170 | - <div class="row"> | ||
171 | - <p class="col-lg-12 text-center"> | ||
172 | - {% trans "The contents of this site is published under license" %} | ||
173 | - <a href="http://creativecommons.org/licenses/by-nc-sa/2.0/br/"> | ||
174 | - {% trans "Creative Commons - attribution, non-commercial" %} | ||
175 | - </a> | ||
176 | - </p> | 168 | + <p class="col-lg-12 text-center"> |
169 | + <a href="{% url 'opendata' %}"><img src="{{ STATIC_URL }}img/opendata3.png"/></a> | ||
170 | + </p> | ||
171 | + <p class="col-lg-12 text-center">{% trans "Last email imported at" %} {{ last_imported_message.received_time|date:'DATETIME_FORMAT' }}</p> | ||
172 | + <p class="col-lg-12 text-center"> | ||
173 | + {% trans "The contents of this site is published under license" %} | ||
174 | + <a href="http://creativecommons.org/licenses/by-nc-sa/2.0/br/"> | ||
175 | + {% trans "Creative Commons - attribution, non-commercial" %} | ||
176 | + </a> | ||
177 | + </p> | ||
177 | </div> | 178 | </div> |
178 | {% endblock %} | 179 | {% endblock %} |
179 | 180 |
@@ -0,0 +1,144 @@ | @@ -0,0 +1,144 @@ | ||
1 | +{% extends "base.html" %} | ||
2 | +{% load i18n %} | ||
3 | + | ||
4 | +{% block main-content %} | ||
5 | + <div class="col-lg-12"> | ||
6 | + <h2>{% trans "OpenData - Communities Interlegis" %}</h2> | ||
7 | + <p>{% trans "If you are interested in any other data that is not provided by this API, please contact us via the ticketing system (you must be registered in order to create a ticket)." %}</p> | ||
8 | + | ||
9 | + <h3>{% trans "Retrieving data via API" %}</h3> | ||
10 | + <p>{% trans "Colab API works through HTTP/REST, always returning JSON objects." %}</p> | ||
11 | + <p> | ||
12 | + {% trans "The base API URL is" %}: | ||
13 | + {% url 'api_v1_top_level' api_name='v1' as BASE_API_URL %} | ||
14 | + <a href="{{ BASE_API_URL }}">{{ BASE_API_URL }}</a> | ||
15 | + </p> | ||
16 | + | ||
17 | + <ul class="list-unstyled"> | ||
18 | + <li> | ||
19 | + <p>{% trans "Each model listed below has a resource_uri field available, which is the object's data URI." %}</p> | ||
20 | + <p>{% trans "The following list contains the available models to retrieve data and its fields available for filtering" %}:</p> | ||
21 | + <ul> | ||
22 | + <li> | ||
23 | + <strong><a href="{{ BASE_API_URL }}user/">user</a></strong>: | ||
24 | + {% trans "Fields" %}: <i>username, email, institution, role, first_name, last_name and bio</i> | ||
25 | + <p><strong>{% trans "The email field is not shown for user's privacy, but you can use it to filter" %}</strong></p> | ||
26 | + <ul> | ||
27 | + <li><i>username</i> - {% trans "The user's username" %}</li> | ||
28 | + <li><i>email</i> - {% trans "The user's email address" %}</li> | ||
29 | + <li><i>institution</i> - {% trans "What is the user's institution" %}</li> | ||
30 | + <li><i>role</i> - {% trans "What is the user's role" %}</li> | ||
31 | + <li><i>first_name</i> - {% trans "The user's first name" %}</li> | ||
32 | + <li><i>last_name</i> - {% trans "The user's last name" %}</li> | ||
33 | + <li><i>bio</i> - {% trans "A mini bio of the user" %}</li> | ||
34 | + </ul> | ||
35 | + <br /> | ||
36 | + </li> | ||
37 | + <li> | ||
38 | + <strong><a href="{{ BASE_API_URL }}emailaddress/">emailaddress</a></strong>: | ||
39 | + {% trans "Fields" %}: <i>user, address and real_name</i> | ||
40 | + <p><strong>{% trans "The address field is not shown for user's privacy, but you can use it to filter" %}</strong></p> | ||
41 | + <ul> | ||
42 | + <li><i>user</i> - {% trans "It has a relationshop with the user described above" %}</li> | ||
43 | + <li><i>address</i> - {% trans "An email address" %}</li> | ||
44 | + <li><i>real_name</i> - {% trans "The user's real name" %}</li> | ||
45 | + </ul> | ||
46 | + <br /> | ||
47 | + </li> | ||
48 | + <li> | ||
49 | + <strong><a href="{{ BASE_API_URL }}message/">message</a></strong>: | ||
50 | + {% trans "Fields" %}: <i>from_address, body, id, received_time and subject</i> | ||
51 | + <ul> | ||
52 | + <li><i>from_address</i> - {% trans "It has a relationship with the emailaddress described above" %}</li> | ||
53 | + <li><i>body</i> - {% trans "The message's body" %}</li> | ||
54 | + <li><i>subject</i> - {% trans "The message's subject" %}</li> | ||
55 | + <li><i>id</i> - {% trans "The message's id" %}</li> | ||
56 | + <li><i>received_time</i> - {% trans "The message's received time" %}</li> | ||
57 | + </ul> | ||
58 | + <br /> | ||
59 | + </li> | ||
60 | + <li> | ||
61 | + <strong><a href="{{ BASE_API_URL }}revision/">revision</a></strong>: | ||
62 | + {% trans "Fields" %}: <i>author, created, key, message and repository_name</i> | ||
63 | + <ul> | ||
64 | + <li><i>author</i> - {% trans "The revision's author username" %}</li> | ||
65 | + <li><i>created</i> - {% trans "When the revision's were created" %}</li> | ||
66 | + <li><i>key</i> - {% trans "The revision's key" %}</li> | ||
67 | + <li><i>message</i> - {% trans "The revision's message" %}</li> | ||
68 | + <li><i>repository_name</i> - {% trans "The revision's repository name" %}</li> | ||
69 | + </ul> | ||
70 | + <br /> | ||
71 | + </li> | ||
72 | + <li> | ||
73 | + <strong><a href="{{ BASE_API_URL }}ticket/">ticket</a></strong>: | ||
74 | + {% trans "Fields" %}: <i>author, component, created, description, id, keywords, milestone, modified, modified_by, priority, reporter, severity, status, summary and version</i> | ||
75 | + <ul> | ||
76 | + <li><i>author</i> - {% trans "The ticket's author username" %}</li> | ||
77 | + <li><i>component</i> - {% trans "The ticket's component" %}</li> | ||
78 | + <li><i>created</i> - {% trans "When the ticket's were created" %}</li> | ||
79 | + <li><i>description</i> - {% trans "The ticket's description" %}</li> | ||
80 | + <li><i>id</i> - {% trans "The ticket's id" %}</li> | ||
81 | + <li><i>keywords</i> - {% trans "The ticket's keywords" %}</li> | ||
82 | + <li><i>milestone</i> - {% trans "The ticket's milestone" %}</li> | ||
83 | + <li><i>modified</i> - {% trans "The time of the last modification" %}</li> | ||
84 | + <li><i>modified_by</i> - {% trans "The username of the last user who modified the ticket" %}</li> | ||
85 | + <li><i>priority</i> - {% trans "The ticket's priority" %}</li> | ||
86 | + <li><i>severity</i> - {% trans "The ticket's severity" %}</li> | ||
87 | + <li><i>status</i> - {% trans "The ticket's status" %}</li> | ||
88 | + <li><i>summary</i> - {% trans "The ticket's summary" %}</li> | ||
89 | + <li><i>version</i> - {% trans "The ticket's version" %}</li> | ||
90 | + </ul> | ||
91 | + <br /> | ||
92 | + </li> | ||
93 | + <li> | ||
94 | + <strong><a href="{{ BASE_API_URL }}wiki/">wiki</a></strong>: | ||
95 | + {% trans "Fields" %}: <i>author, created, modified, modified_by, name and wiki_text</i> | ||
96 | + <ul> | ||
97 | + <li><i>author</i> - {% trans "The wiki's author username" %}</li> | ||
98 | + <li><i>created</i> - {% trans "When the wiki's were created" %}</li> | ||
99 | + <li><i>modified</i> - {% trans "The time of the last modification" %}</li> | ||
100 | + <li><i>modified_by</i> - {% trans "The username of the last user who modified the wiki" %}</li> | ||
101 | + <li><i>name</i> - {% trans "The wiki's name" %}</li> | ||
102 | + <li><i>wiki_text</i> - {% trans "the wiki's content" %}</li> | ||
103 | + </ul> | ||
104 | + </li> | ||
105 | + </ul> | ||
106 | + </li> | ||
107 | + </ul> | ||
108 | + <ul class="list-unstyled"> | ||
109 | + <li><h3>{% trans 'Parameters' %}:</h3></li> | ||
110 | + <li class="divider"></li> | ||
111 | + <li> | ||
112 | + <h4><i>limit</i> - {% trans "Results per page" %}</h4> | ||
113 | + {% trans "Number of results to be displayed per page." %} | ||
114 | + <i>{% trans "Default: 20" %}</i>. | ||
115 | + </li> | ||
116 | + <li class="divider"></li> | ||
117 | + <li> | ||
118 | + <h4><i>offset</i> - {% trans "Starts of n element" %}</h4> | ||
119 | + {% trans "Where n is the index of the first result to appear in the page." %} | ||
120 | + <i>{% trans "Default: 0" %}</i>. | ||
121 | + </li> | ||
122 | + <li class="divider"><h3>{% trans "Filtering" %}:</h3></li> | ||
123 | + <li> | ||
124 | + <h4><i>fieldname</i> - {% trans "The field name" %}</h4> | ||
125 | + {% trans "If you are looking for a specific wiki, and you know the wiki's name, you can filter it as below" %} | ||
126 | + <p><i>...{{ BASE_API_URL }}wiki/?name={% trans "WikiName" %}</i></p> | ||
127 | + <p>{% trans "Where "name" is the fieldname and "WikiName" is the value you want to filter." %}</p> | ||
128 | + <h4>{% trans "Usage" %}</h4> | ||
129 | + <p>{% trans "You can also filter using Django lookup fields with the double underscores, just as below" %}</p> | ||
130 | + <p><i>...{{ BASE_API_URL }}wiki/?wiki_text__startswith={% trans "Wiki" %}</i></p> | ||
131 | + <p><i>...{{ BASE_API_URL }}ticket/?author__endswith={% trans "test" %}</i></p> | ||
132 | + <p><i>...{{ BASE_API_URL }}message/?body__contains={% trans "test" %}</i></p> | ||
133 | + <h4>{% trans "Usage with relationships" %}</h4> | ||
134 | + <p>{% trans "You can use related fields to filter too. So, you can filter by any field of emailaddress using the 'from_address' field of message, which has a relation to emailaddress. You will achieve the related fields by using double underscore and the field's name. See the example below" %}</p> | ||
135 | + <p><i>...{{ BASE_API_URL }}message/?from_address__real_name__contains=Name</i></p> | ||
136 | + <p>{% trans "So, real_name is a field of emailaddress, and you had access to this field by a message field called from_address and using double underscore to say you want to use a field of that relationship" %}</p> | ||
137 | + <p><strong>{% trans "Note: email filters must be exact. Which means that __contains, __startswith, __endswith and others won't work" %}</strong></p> | ||
138 | + <p>{% trans "Another example of usage with relations. Used to retrieve all messages of a given user, using the username or the email field" %}</p> | ||
139 | + <p><i>...{{ BASE_API_URL }}message/?from_address__user__username=username</i></p> | ||
140 | + <p><i>...{{ BASE_API_URL }}message/?from_address__user__email=usermailexample@host.com</i></p> | ||
141 | + </li> | ||
142 | + </ul> | ||
143 | + </div> | ||
144 | +{% endblock %} |