How to include extra data in the Django serializer response? - django

I am trying to make a livesearch with Django and jQuery. What I've done is make the javascript ask for some data with the getJSON function, then I've set up a view in Django that returns a JSON response automated by the Django serializer.
And this works well, it returns a json response with the text/javascript content-type. To avoid sending all the data, (alot that i dont need) i did this:
response.write(serializers.serialize("json", soknad_list, fields=('name', 'image', 'genre')))
But for instance, the 'genre' field is a manyToMany field, so is it possible to get the values from genre.all.0 for instance and not just the genre ID?
And the model has a function get_absolute _url, is it possible to include this in the JSON response, if so how?
So i guess my question is, is it possible to include stuff other than the raw field data in the JSON response, if not, how would you go about solving my problem?

Django's JSON serialization is based on simplejson, which you can use directly and extend at will to handle whatever kind of objects. So you mostly have two options here: either manually build a list of dicts with relevant data and pass it to simplejson.dumps() (which by default support strings, lists, dicts and numerics), or write your own json encoder that knows how to serialize your specific dataset. FWIW, here's a (not well tested but worked so far) Django model aware json encoder:
from django.utils import simplejson
from django.utils import datetime_safe
from django.utils.functional import Promise
from django.utils.translation import force_unicode
from django.utils.encoding import smart_unicode
from django.core.serializers.json import DjangoJSONEncoder
class ModelJSONEncoder(DjangoJSONEncoder):
"""
(simplejson) DjangoJSONEncoder subclass that knows how to encode fields.
(adated from django.serializers, which, strangely, didn't
factor out this part of the algorithm)
"""
def handle_field(self, obj, field):
return smart_unicode(getattr(obj, field.name), strings_only=True)
def handle_fk_field(self, obj, field):
related = getattr(obj, field.name)
if related is not None:
if field.rel.field_name == related._meta.pk.name:
# Related to remote object via primary key
related = related._get_pk_val()
else:
# Related to remote object via other field
related = getattr(related, field.rel.field_name)
return smart_unicode(related, strings_only=True)
def handle_m2m_field(self, obj, field):
if field.creates_table:
return [
smart_unicode(related._get_pk_val(), strings_only=True)
for related
in getattr(obj, field.name).iterator()
]
def handle_model(self, obj):
dic = {}
for field in obj._meta.local_fields:
if field.serialize:
if field.rel is None:
dic[field.name] = self.handle_field(obj, field)
else:
dic[field.name] = self.handle_fk_field(obj, field)
for field in obj._meta.many_to_many:
if field.serialize:
dic[field.name] = self.handle_m2m_field(obj, field)
return dic
def default(self, obj):
if isinstance(o, Promise):
return force_unicode(o)
if isinstance(obj, Model):
return self.handle_model(obj)
return super(ModelJSONEncoder, self).default(obj)
HTH

There is a handy django third party app / serializer that will allow you to include extra data. It also allows you to include model relations and exclude a list of fields.
It's available at
http://code.google.com/p/wadofstuff/wiki/DjangoFullSerializers

I found out that the simplest thing was to not use the serializer at all. I dont know why I didnt think of this before, but i just used a generic object list view and changed the mimetype to text/javascript and made a JSON template insted of a html template.
Very simple, and that way i managed to get all the data i wanted into the JSON response. This way you can add everything that you can add to a html template into a JSON response, even paginating.
Example extraction of the view i created:
return object_list(request, queryset = object_list,
template_name = 'search/results.js', template_object_name = 'result',
paginate_by = 12, mimetype = 'text/javascript')

Related

Make a Django model form safe

I am building a high performance API. I have been using Tastypie for ages and sometimes I just need more simplicity. For this API I have decided to use Django Simple Rest (https://github.com/croach/django-simple-rest). It provides the base of what is needed and I can use forms and the ORM to validate and save data with no generic API library overhead.
I want to verify the data that is coming in. I am using model forms to do so. It's nice and simple, it verifies data against the model but I need a little bit more.
I want to make sure no script or HTML gets posted. For some fields I might allow HTML. I know I can use html5lib to do all sorts of validation and I probably will but the only examples I have seen are where you specify every field. I am trying to work out a way to by default prevent javascript or HTML being entered into a field and to be able to override as appropriate. I don't want to have to describe every model in forms, I want something generic.
Here is my simplerest put function.
def put(self, request, *args, **kwargs):
data = json.loads(request.body)
try:
todo = Item.objects.get(id=kwargs.get('id'))
except Item.DoesNotExist:
return HttpNotFound()
form = TodoForm( instance=todo, data=data )
if not form.is_valid():
return JsonFormErrors( form )
form.save()
return JsonStatus(True, message="saved successfully")
Here is my form.
from django import forms
from .models import *
class TodoForm(forms.ModelForm):
class Meta:
model = Item
fields = ('id', 'text')
What is the best way to provide generic protection to all my put methods and forms with an ability to override the behaviour if I want to accept HTML.
I appreciate your help!
Rich
You might be able to create a new class inheriting from ModelForm that cleans each of the values immediately.
I was thinking of something like this:
from django import forms
from lxml.html.clean import clean_html
class SanitizedModelForm(ModelForm):
def __init__(self, data=None, *args, **kwargs):
if data is None:
data = {}
sanitized_data = {}
for key, value in data:
sanitized_data[key] = clean_html(value)
super(SanitizedModelForm, self).__init__(sanitized_data, *args, **kwargs)
I'm not sure if clean_html is the right method for your scenario.

Django models __unicode__: How to return a value containing localized datetimes?

I have a localized Django App, localisation works well and the configuration is ok…
For forms needs, I use __unicode__ method for model to render ModelChoiceFields, but how do I format localized date in the unicode return?
In this method I have no access to current timezone, how to display my TimeSpanList correctly to my users? Currently, it displays UTC. I tryed django.template.defaultfilters.date and Simon Charette's django.utils.formats.localize which didn't helped as they probably lacked contextual data…
class TimeSpan(models.Model):
start = models.DateTimeField(_("start"))
end = models.DateTimeField(_("end"))
def __unicode__(self):
return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)
class TimeSpanChooserForm(forms.Form):
time_span = forms.ModelChoiceField(
label=_("time span"), queryset=TimeSpan.objects.all())
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(TimeSpanChooserForm, self).__init__(*args, **kwargs)
How to know current locale without current request object to localize those datetimes? (If there is a way)
note: to me __unicode__ seems like the only way to display entries in ModelChoiceField.
note 2: to me, Yuji 'Tomita' Tomita comment is the best current answer but it lacks a usable exemple…
Thanks for reminding me about this question. It's a bit involved for a complete answer.
I've created a demo method that shows this in action. Run test() to see output.
The remaining challenge is getting the client timezone. This will probably be done with some JavaScript that adds a parameter to your GET/POST or adds a custom header to be read in the view.
Since this is an unknown, I just stubbed it out with a method that returns a random time zone. You should update it to reflect your client timezone retrieval method.
Here's a self documenting example:
Models
from django.utils.formats import localize
class TimeSpan(models.Model):
start = models.DateTimeField("start")
end = models.DateTimeField("end")
def __unicode__(self):
return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)
Form
from django import forms
class TimeSpanChooserForm(forms.Form):
time_span = forms.ModelChoiceField(
label=("time span"), queryset=TimeSpan.objects.all())
def __init__(self, *args, **kwargs):
# get request and TZ from view. This is a special form..
self.request = kwargs.pop("request", None)
self.tz = kwargs.pop('tz', None)
super(TimeSpanChooserForm, self).__init__(*args, **kwargs)
# modify the choices attribute to add custom labels.
self.fields['time_span'].choices = [
self.make_tz_aware_choice(self.request, timespan) for timespan in self.fields['time_span'].queryset
]
def make_tz_aware_choice(self, request, timespan):
""" Generate a TZ aware choice tuple.
Must return (value, label). In the case of a ModelChoiceField, (instance id, label).
"""
start = timespan.start.replace(tzinfo=self.tz)
end = timespan.end.replace(tzinfo=self.tz)
label = "From {tz} {start} to: {tz} {end}".format(
start=start,
end=end,
tz=self.tz,
)
return (timespan.id, label)
View
import random
import pytz
from django import http
from django import template
def view(request):
""" Render a form with overridden choices respecting TZ
"""
def get_tz_from_request(request):
""" Get the client timezone from request.
How you do this is up to you. Likely from JS passed as a parmeter.
"""
random_timezone = random.choice(pytz.all_timezones)
return pytz.timezone(random_timezone)
form = TimeSpanChooserForm(request.POST or None, request=request, tz=get_tz_from_request(request))
ctx = template.Context({
'form': form,
})
rendered_template = template.Template("{{ form }}").render(ctx)
return http.HttpResponse(rendered_template)
def test():
from django.test import RequestFactory
rf = RequestFactory()
r = rf.get('/')
for i in range(10):
print str(view(r))
You can use the django.utils.formats.localize function.
from django.db import models
from django.utils.formats import localize
from django.utils.translation import ugettext
class TimeSpan(models.Model):
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))
def __unicode__(self):
return ugettext("from %(start)s to %(end)s") % {
'start': localize(self.start),
'end': localize(self.end),
}
You can test the following work with those manipulation.
from django.utils import timezone
from django.utils.translation import override
with override('fr'):
print(TimeSpan(start=timezone.now(), end=timezone.now()))
with override('en'):
print(TimeSpan(start=timezone.now(), end=timezone.now()))
Both should display different formating.
If you want to make sure dates are displayed in a specific timezone you must make sure USE_TZ = True in your settings and set the TIME_ZONE setting to the one you want to use. You can set this timezone per request by using a middleware that calls django.utils.timezone.activate with the desired timezone.
You can also use the django-sundial package which takes care of this for you.
Try this:
def __unicode__(self):
return u'%s %s'%(self.start,self.end)
Buy using this it will return combined start and end datetime objects.This worked for me

Django DRF Model Serailzier - Serialize model property

How can I add property of a Model in its JSON made using django.core.serializers to return in AJAX call?
I have a Model:
class MyModel(models.Model):
...
#property
def property_field(self):
return some_value;
Then in views, I send a JSON of this model in an AJAX call. The problem is, I don't know how to send value returned by this property. I tried
query_set = MyModel.objects.flter(...)
serializers.serialize('json', query_set, fields=('...', ... , 'a_property'))
But this doesn't work. How can I pass the value of this property?
Looks like there is no easy way to accomplish this without subclassing your own serializer as the default serializer only goes through the db fields.
Edit:
Previous answer was written a long time ago. There are easier ways to do this now, as expected:
class MyModelSerializer(serializers.ModelSerializer):
property_field = serializers.CharField(
source="property_field",
read_only=True,
)
This should add property_field property in the serailzed OrderedDict which you can return to client
Usage:
serializer = MyModelSerializer(MyModel.objects.all())
return Response(serializer.data)
Old Answer
This JSON Serializer should work:
from StringIO import StringIO
from django.core.serializers.json import Serializer
class JSONSerializer(Serializer):
def serialize(self, queryset, attributes, **options):
self.options = options
self.stream = options.get("stream", StringIO())
self.start_serialization()
self.first = True
for obj in queryset:
self.start_object(obj)
for field in attributes:
self.handle_field(obj, field)
self.end_object(obj)
if self.first:
self.first = False
self.end_serialization()
return self.getvalue()
def handle_field(self, obj, field):
self._current[field] = getattr(obj, field)
(based on this answer, posted by Tim Edgar ^ and changed using this gist)
Usage:
json = JSONSerializer().serialize(modelName.objects.all(), ('attr1', 'property1', ...))

Pseudo-form in Django admin that generates a json object on save

I have a model with a field for a json object. This object is used on the site to control some css variables, among other things.
Right now in the admin, I have a text field where a user can save a json object. I'd like to show a form with all the attributes that, upon saving, will generate a json object.
Basically, the user sees, and the data is stored, like this:
{
"name":"hookedonwinter",
"user-id":123,
"basics":{
"height":150,
"weight":150
}
}
And I'd rather have the user see this:
Name: <input field>
User Id: <input field>
Height: <input field>
Weight: <input field>
and the data still be stored in json.
Any guidance would be appreciated. Links to docs that explain this, doubly appreciated.
Thanks!
Idea
Basically what you need to do is render your JSON into fields.
Create field for your model that stores JSON data.
Create form field
Create widget that:
Renders fields as multiple inputs
Takes data from POST/GET and transforms it back into JSON
You can also skip steps 1, 2 by overriding widget for TextField.
Documentation links
Widgets: https://docs.djangoproject.com/en/1.3/ref/forms/widgets/
Django code for reference how to create widgets: https://code.djangoproject.com/browser/django/trunk/django/forms/widgets.py
Proof of concept
I tried coding this solution and here is solution that worked for me without some edge cases.
fields.py
import json
from django.db import models
from django import forms
from django import utils
from django.utils.translation import ugettext_lazy as _
class JSONEditableField(models.Field):
description = _("JSON")
def formfield(self, **kwargs):
defaults = {'form_class': JSONEditableFormField}
defaults.update(kwargs)
return super(JSONEditableField, self).formfield(**defaults)
class JSONEditableWidget(forms.Widget):
def as_field(self, name, key, value):
""" Render key, value as field """
attrs = self.build_attrs(name="%s__%s" % (name, key))
attrs['value'] = utils.encoding.force_unicode(value)
return u'%s: <input%s />' % (key, forms.util.flatatt(attrs))
def to_fields(self, name, json_obj):
"""Get list of rendered fields for json object"""
inputs = []
for key, value in json_obj.items():
if type(value) in (str, unicode, int):
inputs.append(self.as_field(name, key, value))
elif type(value) in (dict,):
inputs.extend(self.to_fields("%s__%s" % (name, key), value))
return inputs
def value_from_datadict(self, data, files, name):
"""
Take values from POST or GET and convert back to JSON..
Basically what this does is it takes all data variables
that starts with fieldname__ and converts
fieldname__key__key = value into json[key][key] = value
TODO: cleaner syntax?
TODO: integer values don't need to be stored as string
"""
json_obj = {}
separator = "__"
for key, value in data.items():
if key.startswith(name+separator):
dict_key = key[len(name+separator):].split(separator)
prev_dict = json_obj
for k in dict_key[:-1]:
if prev_dict.has_key(k):
prev_dict = prev_dict[k]
else:
prev_dict[k] = {}
prev_dict = prev_dict[k]
prev_dict[dict_key[-1:][0]] = value
return json.dumps(prev_dict)
def render(self, name, value, attrs=None):
# TODO: handle empty value (render text field?)
if value is None or value == '':
value = '{}'
json_obj = json.loads(value)
inputs = self.to_fields(name, json_obj)
# render json as well
inputs.append(value)
return utils.safestring.mark_safe(u"<br />".join(inputs))
class JSONEditableFormField(forms.Field):
widget = JSONEditableWidget
models.py
from django.db import models
from .fields import JSONEditableField
class Foo(models.Model):
text = models.TextField()
json = JSONEditableField()
Hope this helps and here is how it looks:
I had similar task. I resolved it by creating Django form widget. You can try it for yours applications django-SplitJSONWidget-form
Interesting question! I'd like to see good and elegant solution for it :)
But it seems to me, that django-admin is not suitable for your task. I'd try to play with Forms. Smth like this:
class HmmForm(forms.Form):
name = forms.CharField(max_length = 128)
user_id = forms.IntegerField()
height = forms.IntegerField()
weight = forms.IntegerField()
def test(request, pk):
form = HmmForm()
if pk > 0:
hmm = Hmm.objects.get(pk = pk)
form = HmmForm( initial = {"name": hmm.name} )
return render_to_response("test/test.html", {"form": form})
And then simple render form in template, as you wish:
{{ form.as_table }} or {{ form.as_p }}
It's looks simple like this:
#Creating custom form
class MyCoolForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('field_that_stores_json', )
#field_that_shows_json1 = forms.CharField()
#field_that_shows_jsons = forms.CharField()
def __init__(self, *args, **kwargs):
#Deserizlize field that stores json here
def save(self, *args, **kwargs):
#Serialize fields that shows json here
After all, just set this form as a form for admin.
P.S.: Also you can write your own widget for form, that transforms json object into fields on js level and has textarea underneath.
Basically it sounds like you want a custom widget for your text field. The snippet on this page gives an example on how to render json key-value pairs. Even if it doesn't suit your needs entirely, especially as your nested json adds some complexity, it perhaps can give you some ideas.
As for the pure storage and retrieval of json objects into Python dicts, a few reusable JSONField implementations exist, like this one. You might want to add it to the mix.
Try using YAML as the format for user input, and then deserialize the object and serialize it back to json in the back end. Django already has serializers for that.
django-submodel may help you, although it cannot represent layered key-value now.
It's a pity to miss such HUGE bounty =p

Use forms.TextArea for custom JSON field in Django admin site

I'm using a custom Django field to represent JSON encoded data:
class JSONField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value == "":
return None
try:
if isinstance(value, basestring):
return json_decode(value)
except ValueError:
pass
return value
def get_prep_value(self, value):
if value == "":
return None
if isinstance(value, dict) or isinstance(value, dict):
value = json_encode(value)
return super(JSONField, self).get_prep_value(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value,connection=None)
The field itself works fine. However editting via the admin site is not possible since the string from the database is JSON-decoded and converted to a dictionary, so when the admin site is rendered, not the actual JSON string from the database is displayed (e.g. {"foo": "bar"}), but its dictionary representation (e.g. {u'foo': u'bar'}).
Obviously this leads to problems when saving the database object, because the dictionary's string representation is not a valid JSON string.
What I'd like to have is the admin site showing the actual database value (i.e. the string as it is saved in the database), instead of the string representation of the Python object returned by to_python.
My attempt would be to write a custom widget for this that just calls json_encode again on the dictionary - but is there a better way?
value_from_object will help solve the problem. It's implementation depends on what serializer has been used, but for simplejson should looks like:
from django.utils import simplejson as json
from django.core.serializers.json import DjangoJSONEncoder
class JSONField(models.TextField):
....
def value_from_object(self, obj):
return json.dumps(super(JSONField, self).value_from_object(obj))