My api is setup with Django Rest Framework and I feel like I am fighting with Django trailing slashes. There seems to be some combination that I have not figured out. A basic query always returns all the objects at the api endpoint.
Here's the code:
// App config
var App = angular.module('App',['App.controllers','restangular','ngRoute','ngResource']);
App.config(function ($interpolateProvider, $httpProvider, $resourceProvider, RestangularProvider) {
RestangularProvider.setBaseUrl('/api/');
// Things I've tried
RestangularProvider.setBaseUrl('http://127.0.0.1:8000/api/');
RestangularProvider.setRequestSuffix('/');
// with suffix http://127.0.0.1:8000/api/tests/?value=404
// without suffix http://127.0.0.1:8000/api/tests?value=404
$resourceProvider.defaults.stripTrailingSlashes = false;
});
// In controller
// Items in database
// [{"itemID": 1,"value": 5,},{"itemID": 2,"value": 404,},{"itemID": 3,"value": 73,}]
var params = {'value': 404};
Restangular.all('tests').getList(params).then(function(items) {
var items = items
console.log(items)
});
// Even the standard $resource does the same thing.
var Foo = $resource('/api/tests', {value:'#somevalue'});
$scope.allItems = {};
Foo.query({value:404}, function(items) {
$scope.allItems = items;
});
I can see it trying to go to /tests?params but it faults over to tests/?params
"GET /api/tests?value=404 HTTP/1.1" 301 0
"GET /api/tests/?value=404 HTTP/1.1" 200 361
Maybe I'm structuring the query wrong? Is there a way to test queries by actually going to the address? Technically shouldn't navigating to http://127.0.0.1:8000/api/tests?value=404 bring up in DRF only the list of objects with a value of 404? DRF puts in the slash at the end of the url before the parameters (http://127.0.0.1:8000/api/tests/?value=404).
Does anyone have a tried and true method for working with Django Rest Framework?
Here's what I have figured out. Querying requires Django Rest Framework filtering. After installing django filter, you add filter_backends and filter_fields to your viewset. Good to go.
$ pip install django-filter
// In your serializers.py
// Create your serializer
from rest_framework import serializers
class FoobarSerializer(serializers.ModelSerializer):
class Meta:
model = Foobar
fields = ('foo','bar')
// -----
// In your main app urls.py (or wherever you put your viewset, I have them in urls so most api stuff is collected there)
from rest_framework import routers
from rest_framework import viewsets
from rest_framework import filters
from mainapp.serializers import FoobarSerializer
from app.models import Foobar
// Put your filter fields in your viewset
class FoobarViewSet(viewsets.ModelViewSet):
queryset = Foobar.objects.all()
serializer_class = FoobarSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('foo', 'bar')
// Register the viewset
router = routers.DefaultRouter()
router.register(r'foobars', FoobarViewSet)
urlpatterns = [
...
url(r'^api/', include(router.urls)),
]
// -----
// In your angular controller.js
// Restangular
var params = {'foo': 404};
Restangular.all('foobars').getList(params).then(function(items) {
$scope.items = items
});
// $resource
var Foo = $resource('/api/foobars', {value:'#somevalue'});
Foo.query({value:404}, function(items) {
$scope.items = items;
});
// $http
$http({method:'GET',url:'/api/foobars',params:{value:404}}).then(function(response) {
$scope.items = response.data;
});
Related
I'm using Django + Angular, and I have a dynamic url which works for first time when load my product page. I specified as a dynamic url in Django too, so the url look like this "product/home/:productName/:productId". Everything definitely works but as a response when I reload my page, it gets plain API response document. So, I compared the request details and when reload it gets document and angular pollifies not working. I was searching around didn't found anything.
url.py
path('product/home/<str:handle>/<int:id>', ProductGet)
view.py
#csrf_exempt
def ProductGet(request, handle, id):
product = Product.objects.get(id=id)
serializer = ProductSerializer(product, many=False)
return JsonResponse(serializer.data, safe=False)
So, this code works for first time, but then when I reload seems it changes host to Django and I'm getting as a response, my API response.
product-component.ts
ngOnInit(){
this.productService.data$.subscribe(res => this.update = res)
this.card = localStorage.getItem("card")
if(this.card === null){
this.card = null
}else {
this.card = JSON.parse(this.card)
}
this.router.params.subscribe((param:any)=> {
this.route = param.route
if(this.route === "home"){
this.route = {
route: "/",
name: "Основной"
}
}
console.log(param)
this.productService.getProduct(param.id, param.title).subscribe((res: any)=> {
this.product = res
this.selectedOption = res.options[0]
this.selectedOption.selectedOption = res.options[0].values[0]
this.selectedOptionName = res.options[0].values[0]
this.mainImg = this.host + res.image
this.isSale()
this.src1 = this.sanitizer.bypassSecurityTrustResourceUrl(this.product.specs.iframes[0].src)
this.src2 = this.sanitizer.bypassSecurityTrustResourceUrl(this.product.specs.iframes[1].src)
})
})
So, something with browser and framework host where angular didn't understand a request.So the fix.
app.module.ts
import {HashLocationStrategy, Location, LocationStrategy} from '#angular/common';
providers: [
{provide: LocationStrategy, useClass: HashLocationStrategy}
],
I created an app using Slim 2 a while ago and I'm trying to add Angular. It's been going well so far, but I can no longer use the CSRF protection that I was using since Angular is handling all my post requests. Below is the Before Middleware I had working.
<?php
namespace Cache\Middleware;
use Exception;
use Slim\Middleware;
class CsrfMiddleware extends Middleware {
protected $key;
public function call() {
$this->key = $this->app->config->get('csrf.key');
$this->app->hook('slim.before', [$this, 'check']);
$this->next->call();
}
public function check() {
if (!isset($_SESSION[$this->key])) {
$_SESSION[$this->key] = $this->app->hash->hash($this->app->randomlib->generateString(128));
}
$token = $_SESSION[$this->key];
if (in_array($this->app->request()->getMethod(), ['POST', 'PUT', 'DELETE'])) {
$submittedToken = $this->app->request()->post($this->key) ?: '';
if (!$this->app->hash->hashCheck($token, $submittedToken)) {
throw new Exception('CSRF token mismatch');
}
}
$this->app->view()->appendData([
'csrf_key' => $this->key,
'csrf_token' => $token
]);
}
}
I know that angular automatically looks for a token named XSRF-TOKEN and adds it to the header as X-XSRF-TOKEN. How can I modify the middleware below to write, read, and compare the correct values.
EDIT:
After looking at this again and checking the slim documentation, I changed the line:
$submittedToken = $this->app->request()->post($this->key) ?: '';
to this:
$submittedToken = $this->app->request->headers->get('X-XSRF-TOKEN') ?: '';
If I'm right, this assigns the $submittedToken the value passed as X-XSRF-TOKEN in the header. It's throwing the exception with the message from the middleware "CSRF token mismatch". This feels like progress. Below is the relevant Angular:
app.controller('itemsCtrl', ['$scope', '$http', function($scope, $http) {
// Initailize object when the page first loads
$scope.getAll = function() {
$http.post('/domain.com/admin/getNames').success(function(data) {
$scope.names = data;
});
}
EDIT
Below is where the php code stands now. I think this is working. I've received the expected CSRF error when I remove the cookie or alter the value of the $token before submitting a form. I'm a little concerned about what will happen when I have multiple users on. I haven't tested it yet. Based on this revision, does the protection appear sound?
<?php
namespace Cache\Middleware;
use Exception;
use Slim\Middleware;
class CsrfMiddleware extends Middleware {
protected $key;
public function call() {
$this->key = $this->app->config->get('csrf.key');
$this->app->hook('slim.before', [$this, 'check']);
$this->next->call();
}
public function check() {
// if (!isset($_SESSION[$this->key])) {
if (!isset($_SESSION[$this->key])) {
// $_SESSION[$this->key] = $this->app->hash->hash($this->app->randomlib->generateString(128));
$this->app->setcookie($this->key, $this->app->hash->hash($this->app->randomlib->generateString(128)));
}
// $token = $_SESSION[$this->key];
if(isset($_COOKIE[$this->key])) {
$token = $_COOKIE[$this->key];
}
if (in_array($this->app->request()->getMethod(), ['POST', 'PUT', 'DELETE'])) {
// $submittedToken = $this->app->request()->post($this->key) ?: '';
$submittedToken = $this->app->request->headers->get('X-XSRF-TOKEN') ?: '';
if (!$this->app->hash->hashCheck($token, $submittedToken)) {
throw new Exception('CSRF token mismatch');
}
}
}
}
From the Angular docs for $http Cross Site Request Forgery (XSRF) Protection:
The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, or the per-request config object.
So to change them to use different cookie name / header name, change those values.
I am struggling with 403/CSRF issues when trying to use Angular 2 to POST to my Django server.
Both my server code and my Angular code are running on the same 127.0.0.1 server.
When the Angular code is run the server returns a 403 4612 error
My Django View code looks like this (I am using the Django REST Framework):
rom django.utils import timezone
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.models import User
from .models import Item, Seen, Keyword, Flag
from django.utils.decorators import classonlymethod
from django.views.decorators.csrf import csrf_exempt
from rest_framework import viewsets
from items.serializers import ItemSerializer, UserSerializer
from rest_framework.authentication import SessionAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
authentication_classes = (CsrfExemptSessionAuthentication, )
#permission_classes = [IsAccountAdminOrReadOnly]
"""
Use the API call query params to determing what to return
API params can be:
?user=<users_id>&num=<num_of_items_to_return>&from=<user_id_of_items_to_show>
"""
def get_queryset(self):
this_user = self.request.query_params.get('user', None)
restrict_to_items_from_user_id = self.request.query_params.get('from', None)
quantity = self.request.query_params.get('num', 20)
if restrict_to_items_from_user_id is not None:
queryset = Item.objects.filter(owner=restrict_to_items_from_user_id, active=True).order_by('-date_added')[0:int(quantity)]
elif this_user is not None:
queryset = Item.objects.filter(active=True, credits_left__gt=0).exclude(pk__in=Seen.objects.filter(user_id=this_user).values_list('item_id', flat=True))[0:int(quantity)]
else:
queryset = Item.objects.filter(active=True, credits_left__gt=0)[0:int(quantity)]
return queryset
My Angular 2 code that does the POST looks like this:
import {Injectable, Inject} from 'angular2/core';
import {Http, Headers, HTTP_PROVIDERS} from 'angular2/http';
import {UserData} from './user-data';
import 'rxjs/add/operator/map';
#Injectable()
export class ConferenceData {
static get parameters(){
return [[Http], [UserData]];
}
constructor(http, user) {
// inject the Http provider and set to this instance
this.http = http;
this.user = user;
}
load() {
// Example of a PUT item
let body = JSON.stringify({ url: 'fred', item_type: 'P', owner_id: 2 });
let headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://127.0.0.1:8000/api/items/', body, {
headers: headers
})
.subscribe(
data => {
alert(JSON.stringify(data));
},
err => this.logError(err.json().message),
() => console.log('Authentication Complete')
);
}
}
I find CSRF issues really difficult to get to grips with!
EDIT: Added CORS settings
My CORS settings look like this:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'veeu.co',
'127.0.0.1'
)
CORS_ORIGIN_REGEX_WHITELIST = ()
CORS_URLS_REGEX = '^.*$'
CORS_ALLOW_METHODS = (
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'UPDATE',
'OPTIONS'
)
CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'x-csrftoken'
)
CORS_EXPOSE_HEADERS = ()
CORS_ALLOW_CREDENTIALS = False
Rather than disabling the CSRF protection, you can add the token as a header to your ajax requests. See the docs, in particular the last section for AngularJS.
You might have to use csrf_ensure for the initial Django view, to ensure that Django sets the csrf cookie.
I'm attempting to build an api with DRF.
Client is a cordova app backed with AngularJS.
When I try to post some user object using $resource I'm getting a 403 forbidden response from django.
Below is some code which I think is relevant for the issue:
The API Call:
$rootScope.user =
User.get({id: response.id}).$promise.then(angular.noop, function (e) {
if (e.status == 404) { //If not found, register the user.
$rootScope.user = new User();
Object.keys(response).forEach(function (key) {
$rootScope.user[key] = response[key];
});
$rootScope.user.$save(); //Fails here! 403.
}
else
console.log(JSON.stringify(e.msg));
});
The User factory:
.factory('User', function ($resource, serverConstants) {
return $resource(serverConstants.serverUrl + '/users/:id');
})
django view:
# Users
class UserSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.CharField(max_length=100,required=True)
email = serializers.EmailField(required=False,allow_blank=True)
joined = serializers.DateField(required=False,default=datetime.date.today)
class Meta:
model = models.User
fields = ('joined', 'id', 'email')
def get_validation_exclusions(self):
exclusions = super(UserSerializer, self).get_validation_exclusions()
return exclusions + ['owner']
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
PS: I've configured angular to use CSRF cookie and django to allow CORS
Thanks in advance!
Your /user/:id endpoint requires authenticated requests.
You need to authenticate your client's requests using one of the methods specified on the previous link.
Given your app runs in a WebView and then has a builtin cookies handling, SessionAuthentication is the more straightforward to implement.
If you want the endpoint to not require authentication, you can set its permission_classes attribute like so:
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
permission_classes = (AllowAny, )
I guess with DRF you mean the django-rest-framework.
If yes, have a look here:
http://www.django-rest-framework.org/api-guide/authentication/
You can make the view public but using AllowAny.
from rest_framework.permissions import AllowAny
from rest_framework import generics
restapi_permission_classes = (AllowAny,)
class MyListView(generics.ListCreateAPIView):
serializer_class = MyObjectSerializer
permission_classes = restapi_permission_classes
queryset = MyObject.objects.all()
However I'd recommend you to use proper authentication once you are done with testing. I've been using the token authentication.
Have a look at this post for more details:
Django Rest Framework Token Authentication
I am pretty new to Django, but I want to learn how to implement a DRF Token Authentication with Angularjs. Some of the tutorials I have found haven't been too helpful in showing how to set it up, along with their source code etc...
Also, for production purposes, is it more practical to use a third party package? Or set up my own (it's for a personal project, so time contribution is not an issue).
My Buggy Code for Token Auth: Github
In settings.py
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
In signals.py
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
In views.py
class ExampleAuthToken(APIView):
def post(self, request, format=None):
username = request.data.get("username")
password = request.data.get("password")
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User.objects.create_user(username=username)
user.set_password(password)
user.save()
content = {
'user': unicode(user.username),
'token': unicode(user.auth_token),
}
return Response(content)
In urls.py
urlpatterns = [
url(r'^authtoken/', ExampleAuthToken.as_view(), name='authtoken'),
]
To call using the angularjs;
var credentials = {
username: "root",
password: "root123"
};
$.post("http://localhost:8000/authtoken/", credentials {
success: function(data){
console.log(data);
}
}
I would definitely use a library. For token authentication there is the nifty django-rest-framework-jwt - it's straightforward to install and setup. To help with Angular JS looks like there is drf-angular-jwt (which uses DRF-JWT but I have not used it).