Overview
This package creates a minimal framework for creating AJAX endpoints of your own in Django without having to create all of the mappings, handling errors, building JSON, etc.
Additionally, this package allows you to create AJAX endpoints for manipulating Django models directly from JavaScript without much effort. This feature works in a similar manner to Django's ModelAdmin
feature in admin.py
.
Install
- Make sure you have the decorator package installed.
- Install the
ajax/
directory as you would any other application in your Django project or put it in your Django project'ssys.path
somewhere. The easiest way to install is through pip:
pip install -e git://github.com/joestump/django-ajax#egg=django-ajax
- Add
ajax
toINSTALLED_APPS
in yoursettings.py
. - In your Django project's
urls.py
add(r'^ajax/', include('ajax.urls'))
.
There are no models associated with this package so you don't need to worry about syncing your database or anything.
Settings
You can add the following settings to settings.py
:
AJAX_MAX_PER_PAGE
(optional: defaults to 100). Sets the maximum number of objects that can be returned per page using the built-inlist
method on aModelEndpoint
Usage
You can use this package to create ad-hoc AJAX endpoints or by exposing your Django applications's models via AJAX.
/ajax/{some_app_name}/{some_endpoint}.json
is treated as an ad-hoc AJAX endpoint and will be mapped tosome_app_name.endpoints.some_endpoint
. The functionsome_endpoint
must return adict
or anHttpResponse
of some sort. When adict
is returned, it is serialized as JSON and packaged up nicely for the client./ajax/{some_app_name}/{model}.json
will attempt to load up an instance ofModelEndpoint
for the given model and run an append operation to create a new record for the given model./ajax/{some_app_name}/{model}/{pk}/(update|delete|get).json
will attempt to load up an instance ofModelEndpoint
for the given model and run the given operation for the record specified bypk
.
All of your AJAX endpoints should be put into a file called endpoints.py
in your Django applications. AJAX will handle all of the rest of the magic.
Ad-Hoc Endpoint
The following is a simple example of an AJAX endpoint that just echo's back the POST. Keep in mind that ad-hoc AJAX endpoints basically work like regular Django views in that they get a request
object. All of the usual view decorators can be used here without issue (e.g. login_required
). The only thing to keep in mind is that views only get request
as an argument and must return a dict
or HttpResponse
.
from ajax.exceptions import AJAXError
def right_back_at_you(request):
if len(request.POST):
return request.POST
else:
raise AJAXError(500, 'Nothing to echo back.')
If you're surfacing a known error, it's best to use AJAXError
with a sane error code and a message. All other exceptions will be returned as a 500 with a generic error message.
From JavaScript you can easily access the endpoint using jQuery.
$.post('/ajax/my_app/right_back_at_you.json', {
name: "Joe Stump",
age: 31
});
You can also create endpoints from callable objects. The BaseEndpoint
class has two functions that are pretty helpful for encoding Django a QuerySet
or an instance of Model
.
# BaseEndpoint._encode_*` appears to have been depricated.
# You should use ajax.encoders.encoder in the future:
from ajax.encoders import encoder
encoder.encode(record)
BaseEndpoint._encode_data
takes a single argument,data
, which it assumes to be aQuerySet
(or something that looks and acts like one) and converts it into a vanilla list capable of being serialized usingsimplejson
. This uses Django's Python serializer and does some minor cleanup to make it a sane looking JSON payload.BaseEndpoint._encode_record
takes a DjangoModel
and encodes it into a normal Python dict. Additionally, it looks forForeignKey
's and hydrates them to include the full associated record.
The following code is a simple example of using the BaseEndpoint._encode_record
method. You could easily encode a bunch of users with BaseEndpoint._encode_data
by replacing a few lines of code in this example.
from django.contrib.auth.models import User
from ajax.endpoints import BaseEndpoint
from ajax.exceptions import AJAXError
class MyEndpoint(BaseEndpoint):
__call__(self, request):
try:
user = User.objects.get(pk=int(request.POST['user']))
except User.DoesNotExist:
raise AJAXError(404, 'Invalid user.')
return self._encode_record(user)
my_endpoint = MyEndpoint()
Model Endpoint
AJAX also offers a class, called ModelEndpoint
, that takes a Django model and exposes ways to manipulate it via AJAX.
import ajax
from my_app.models import Category
class CategoryEndpoint(ajax.endpoints.ModelEndpoint):
pass
ajax.endpoint.register(Category, CategoryEndpoint)
You can then send a POST to:
/ajax/my_app/category.json
to create a newCategory
./ajax/my_app/category/list.json
to get a list of Category. Additionally, you can POSTitems_per_page
(default is 20) andcurrent_page
(default is 1)./ajax/my_app/category/{pk}/update.json
to update aCategory
.pk
must be present in the path forupdate
,delete
, andget
. NOTE: This package assumes that thepk
argument is an integer of some sort. If you've mangled yourpk
fields in weird ways, this likely will not work as expected./ajax/my_app/category/{pk}/get.json
to get theCategory
as specified aspk
./ajax/my_app/category/{pk}/delete.json
to delete theCategory
as specified bypk
.
NOTE: Your Model name (e.g. category
in the above example) must be lowercase. Your request will fail otherwise.
Adding Ad-hoc endpoints to ModelEndpoints
You can also add you own custom methods to a ModelEndpoint. Adhoc methods in a ModelEndpoint observe the same rules as the get(), update() and delete() methods - with the noticeable exception that self.pk may not be set.
For example, you could add a method called about
that will display some info about the Model or Record (just used for illustration: not actually a good idea in real life):
import ajax
from my_app.models import Category
class CategoryEndpoint(ajax.endpoints.ModelEndpoint):
...
def about(self,request):
pk = self.pk
if pk:
return {"message" : "run an operation on record: %s"%self._get_record()}
else:
return {"message" : "run an operation on model: %s"%self.model.__name__}
Now, in addition to the endpoints above, this method would be available at:
/ajax/my_app/category/about.json
- would return "run an operation on model: ..."
or
/ajax/my_app/category/{pk}/about.json
- would return "run an operation on record: ..."
ForeignKey while Fetching
It should be noted that the AJAX package takes a liberal approach when it comes to instances of ForeignKey
it finds in model declarations. If a model that has a ForeignKey
is fetched it will be expanded to the full record, recursively. For instance, if a ForeignKey
to User
is in a given model, you will get the whole associated User
record when a row is fetched.
ForeignKey while Creating
In addition to expanding ForeignKey
while fetching, they are expanded when creating a record from the data in POST. If a ForeignKey
to User
is in a given model, and the field is called author
, ModelEndpoint
will detect that and automatically assume that request.POST['author']
is an appropriate pk
, instantiate it, and replace it with a full instance of the associated User
object.
Support for django-taggit
The popular django-taggit is great for adding tags to your Django models. The entire django-taggit
API is exposed via AJAX for models that have the tags
attribute.
/ajax/my_app/mymodel/{pk}/tags/add.json
will add the tags specified by thePOST
parametertags
, which is a comma separated list of tags./ajax/my_app/mymodel/{pk}/tags/remove.json
will remove the tags specified by thePOST
parametertags
./ajax/my_app/mymodel/{pk}/tags/set.json
will replace the object's tags specified bypk
with the tags specified by thePOST
parametertags
./ajax/my_app/mymodel/{pk}/tags/clear.json
will clear all tags from thepk
specified./ajax/my_app/mymodel/{pk}/tags/similar.json
will fetch objects that are similarly tagged to thepk
specified.
NOTE: The filtering options in the django-taggit
API are not available via AJAX at this point.
Security
There are a number of security features inherent in the framework along with ways to lock down your ad-hoc and model endpoints. You can use the decorator(s) outlined below as well as throwing appropriate AJAXError
exceptions from your ad-hoc endpoints. Of course, all exceptions raised and HttpResponse
objects returned are respected by default. For model endpoints you can, additionally, use can_create()
, can_update()
, can_delete()
, can_get()
, and authenticate()
to lock down various operations on the given model.
Framework Security
- All requests to an AJAX endpoint must be sent via
POST
, including a GET on a model'spk
. - The default
ModelEndpoint.authenticate()
method requires that a user is, at a minimum, logged in.
Decorators
ajax.decorators.login_required
The AJAX package offers a familiar decorator in ajax.decorators
called login_required
. It works in the same way that the Django decorator does and handles throwing a proper AJAXError
if the user isn't logged in.
from ajax.decorators import login_required
@login_required
def my_ajax_endpoint(request):
return {
'Hello, %s!' % request.user.username
}
ajax.decorators.allowed_methods
You can limit methods that are allowed on your ModelEndpoint
by decorating your overriding ModelEndpoint.authenticate() method with the allowed_methods
decorator. If a user tries to access a disallowed method, then they will receive an AJAXError
.
from ajax.decorators import allowed_methods
class CategoryEndpoint(ajax.endpoints.ModelEndpoint):
@allowed_methods('get','update','list')
def authenticate(self,request):
...
ModelEndpoint
The ModelEndpoint
class offers a number of methods that you can override to add more advanced security over your model-based enpoints. You can override these in your model-based endpoints to control who is able to access each model and in what manner.
(bool) ModelEndpoint.can_create(user)
Returns True
if the user
can create a new record
using the given model.
(bool) ModelEndpoint.can_update(user, record)
Returns True
if the user
can update the given record
.
(bool) ModelEndpoint.can_delete(user, record)
Returns True
if the user
can delete the given record
.
(bool) ModelEndpoint.can_get(user, record)
Returns True
if the user
can fetch the given record
.
(bool) ModelEndpoint.authenticate(request, application, method)
This method is ran before any other security-related operations. This method allows you to control overall authentication-related access prior to any of the can_*
methods being ran and before any model operations are executed. It is ran regardless of the operation being attempted.
Encoders
AJAX ships with a few helpful encoders. You can specify the model fields you would like to include or exclude using the IncludeEncoder
and ExcludeEncoder
, respectively.
To only include certain fields:
from ajax.encoders import encoder, IncludeEncoder
from my_app.models import Category
encoder.register(Category, IncludeEncoder(include=['foo', 'bar']))
To exclude a field:
from ajax.encoders import encoder, IncludeEncoder
from my_app.models import Category
encoder.register(Category, ExcludeEncoder(exclude=['foo']))
Todo
- Integrate Django's CSRF token support.