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.
- 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.pathsomewhere. The easiest way to install is through pip:
pip install -e git://github.com/joestump/django-ajax#egg=django-ajax
- Add
ajaxtoINSTALLED_APPSin yoursettings.py. - In your Django project's
urls.pyadd(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.
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-inlistmethod on aModelEndpoint
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}.jsonis treated as an ad-hoc AJAX endpoint and will be mapped tosome_app_name.endpoints.some_endpoint. The functionsome_endpointmust return adictor anHttpResponseof some sort. When adictis returned, it is serialized as JSON and packaged up nicely for the client./ajax/{some_app_name}/{model}.jsonwill attempt to load up an instance ofModelEndpointfor 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).jsonwill attempt to load up an instance ofModelEndpointfor 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.
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_datatakes 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_recordtakes a DjangoModeland 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()
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.jsonto create a newCategory./ajax/my_app/category/list.jsonto 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.jsonto update aCategory.pkmust be present in the path forupdate,delete, andget. NOTE: This package assumes that thepkargument is an integer of some sort. If you've mangled yourpkfields in weird ways, this likely will not work as expected./ajax/my_app/category/{pk}/get.jsonto get theCategoryas specified aspk./ajax/my_app/category/{pk}/delete.jsonto delete theCategoryas specified bypk.
NOTE: Your Model name (e.g. category in the above example) must be lowercase. Your request will fail otherwise.
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 noticable 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: ..."
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.
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.
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.jsonwill add the tags specified by thePOSTparametertags, which is a comma separated list of tags./ajax/my_app/mymodel/{pk}/tags/remove.jsonwill remove the tags specified by thePOSTparametertags./ajax/my_app/mymodel/{pk}/tags/set.jsonwill replace the object's tags specified bypkwith the tags specified by thePOSTparametertags./ajax/my_app/mymodel/{pk}/tags/clear.jsonwill clear all tags from thepkspecified./ajax/my_app/mymodel/{pk}/tags/similar.jsonwill fetch objects that are similarly tagged to thepkspecified.
NOTE: The filtering options in the django-taggit API are not available via AJAX at this point.
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.
- 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.
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
}
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):
...
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.
Returns True if the user can create a new record using the given model.
Returns True if the user can update the given record.
Returns True if the user can delete the given record.
Returns True if the user can fetch the given record.
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.
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']))
- Integrate Django's CSRF token support.