Debian, Dojo, Django, Python

Делюсь опытом в описанных технологиях. Блог в первую очередь выполняет роль памяток для меня самого.

Показаны сообщения с ярлыком json. Показать все сообщения
Показаны сообщения с ярлыком json. Показать все сообщения

Django, JSON и формы

Очень интересные результаты выдает Google при поиске по словам django json forms. Большая часть ссылок ведет на Stack Overflow, но там всё одно и то же. Как правило, всё сводится к тому, чтобы: 1) сменить стек технологий; 2) разобрать request.POST как словарь (это не работает); 3) передать данные каким-то другим способом.

А вот рабочее решение, проверенное в Django 1.4.22 (из-за некоторых особенностей мне сейчас приходится пользоваться именно таким старьем).

views.py
# -*- coding: utf-8 -*-

from __future__ import absolute_imports
from django.utils import simplejson as json
from django import http

from app.items import forms
from app.items import models
from api.items import serializers

from rest import response


def api_post_view(request):
    if request.method == 'POST' and request.is_ajax():
        # Ну да, всего одна строка. А вы что думали?
        raw_data = json.loads(request.POST)
        new_item_form = forms.CreateItemForm(raw_data)
        if new_item_form.is_valid():
            new_item = new_item_form.save()
            serializer = serializers.ItemReadSerializer()
            return reponse.JsonResponse(serializer.serialize(new_item))
        else:
            return response.JsonResponse(new_tem_form.errors)
    else:
        return http.HttpResponseBadRequest()

Насчет JsonResponse тоже надо пару слов сказать. В старых версиях такого класса нет, но он отлично портируется из новых. Вот код:

rest.response
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json
from django.http.response import HttpResponse

class JsonResponse(HttpResponse):
    """
    An HTTP response class that consumes data to be serialized to JSON.

    :param data: Data to be dumped into json. By default only ``dict`` objects
      are allowed to be passed due to a security flaw before EcmaScript 5. See
      the ``safe`` parameter for more information.
    :param encoder: Should be a json encoder class. Defaults to
      ``django.core.serializers.json.DjangoJSONEncoder``.
    :param safe: Controls if only ``dict`` objects may be serialized. Defaults
      to ``True``.
    :param json_dumps_params: A dictionary of kwargs passed to json.dumps().
    """

    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
                 json_dumps_params=None, **kwargs):
        if safe and not isinstance(data, dict):
            raise TypeError(
                'In order to allow non-dict objects to be serialized set the '
                'safe parameter to False.'
            )
        if json_dumps_params is None:
            json_dumps_params = {}
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder, **json_dumps_params)
        super().__init__(content=data, **kwargs)

AngularJS, Django и POST

Введение

Начал писать приложение на Angular. Стало сразу две проблемы.

  • Как передавать CSRF-Token Django при каждом запросе?
  • Почему данные, отправляемые через POST, не видны в объекте request Django?
  • CSRF-Token

    Для CSRF-Token нашлось очень простое решение. Достаточно подключить модуль angular-cookies и при старте приложения задать параметры сервиса $http. С учётом того, что скобки для Angular у меня заменены с {{ и }} на {$ и $}, заготовка главного модуля приложения у меня выглядит так:

    (function(A){
        "use strict";
        A.module('DesktopApplication', ['ngCookies']).config(function($interpolateProvider){
            $interpolateProvider.startSymbol('{$');
            $interpolateProvider.endSymbol('$}');
    
        }).run(['$http', '$cookies', function ($http, $cookies){
            $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
            $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
            }]);
    }(this.angular));

    Естественно, на сервере при отдаче шаблонов нужно, чтобы соответствующие Cookies были установлены. Делается очень просто:

    return render_to_response('template.html', RequestContext(request))

    Angular и POST

    Отдельной болью стала отправка данных на сервер через POST. Было потрачено несколько часов на чтение Тостера, официальной документации по Angular и Django, пока наконец я не наткнулся на решение одного из пользователей StackOverflow. Суть решения такова: Angular при отправке данных не превращает их в ожидаемый на бекэнде формат, а передаёт их как строку.

    Допустим, у нас имеется такой объект:

    userdata = {
        login: 'userlogin',
        password: '123456'
    };

    Попробуем отправить эти данные:

    $http.post('/login/', userdata).success(function(data){
        //Что-то делаем с полученными в ответ данными
    });

    В консоли Firebug будет выведена строка запроса:

    "{"login": "userlogin", "password": "123456"}"

    Попробуем получить эти данные:

    def login_view(request):
        if request.method == 'POST' and request.is_ajax():
            username = request.POST.get('login', '')
            password = request.POST.get('password', '')

    Мы ожидаем, что в третьей и четвёртой строках будет примерно следующее:

    username = 'username'
    password = '123456'

    На самом деле этого не происходит, т.к. данные должны быть переданы в таком виде:

    login=userlogin&password=123456

    Проблема в том, что в текущей версии Angular (1.3.4 на момент написания статьи) нет функции для приведения объекта в такой вид. Поэтому автор посоветовал использовать старый добрый jQuery, точнее его функцию param:

    $http.post('/login/', $.param(userdata)).success(function(data){
        //Обработка ответа сервера
    });

    Вот такой код будет работать. Так же пользователи предлагали самописные функции, выполняющие ту же работу, но я советую использовать проверенные решения. Кроме этого обязательно присутствие в заголовке пост параметра Content-Type, равного application/x-www-form-urlencoded, соответствующий код приведён выше.