I need to build an application where only logged in users can access a type of information using Angular in Front and Django Rest Framework in back.
I just can't do this authentication, I tried and researched in every way, even using simple-jwt, I can't succeed.
Could you please help me or suggest me a site with a complete example.
Here is my code below:
settings.py
# ALLOWED_HOSTS=['*']
# CORS_ORIGIN_ALLOW_ALL = True
ALLOWED_HOSTS=['localhost', '127.0.0.1']
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
'http://localhost:4200',
)
# JWT settings
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'AUTH_HEADER_TYPES': ('Bearer',),
}
accounts/views.py
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = MyUser.objects.all()
serializer_class = UserSerializer
# renderer_classes = (UserJSONRenderer,)
permission_classes = [permissions.IsAuthenticated]
account/urls.py
# app_name = 'accounts'
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
And from my Angular
auth.service.ts
import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { tap, shareReplay } from 'rxjs/operators';
import * as jwtDecode from 'jwt-decode';
import * as moment from 'moment';
const AUTH_API = "http://localhost:8000/users/";
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private http: HttpClient) { }
login(username: string, password: string): Observable<any> {
return this.http.post(
AUTH_API + 'login/',
{
username,
password,
},
httpOptions,
);
}
register(username: string, email: string, password: string): Observable<any> {
return this.http.post(
AUTH_API + 'signup',
{
username,
email,
password,
},
httpOptions
);
}
logout(): Observable<any> {
return this.http.post(AUTH_API + 'signout', { }, httpOptions);
}
}
http.itercepto.ts
import { Injectable } from '#angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
withCredentials: true,
});
return next.handle(req);
}
}
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true },
];
Related
Goal: I am trying to get access to specific user data using JWT. Specifically, I aim to get the user id from the JWT token using request.user.id.
Problem: I am able to run the api and refresh the tokens. Once the tokens expire, I am able to refresh them. However, after that happens, I don't seem to be able to access to my API
However, once the tokens are refreshed, I get an error when I try to load the data again: "Unauthorized: /api/load/".
Is there something that is clearly wrong here?
Django view for api/load/
class LoadDataSet(APIView):
serializer_class = LoadDataSerializer
queryset = UserPantry.objects.all()
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def post(self, request, format=None):
user = request.user.id
print(user)
return Response(status=status.HTTP_204_NO_CONTENT)
The api call in react native:
const res = await request({
url:'/api/load/',
method: 'POST',
data: {'user_id': String(user_id)}
})
Django JWT settings
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
],
'DATETETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M'],
'DATIME_FORMAT': '%Y-%m-%d %H:%M',
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=3),
'REFRESH_TOKEN_LIFETIME': timedelta(days=60),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('JWT', 'Bearer'),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
# 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
# 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
# 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
React native JWT request and refresh
import axios from 'axios';
import { store } from '../store/store';
import jwt_decode from "jwt-decode";
import dayjs from 'dayjs'
import { api_address } from '../store/api_address';
import { UPDATE_ACCESS_TOKEN } from '../store/actions/types';
import { getCsrfToken } from '../store/actions/auth';
const client = axios.create({
baseURL: api_address,
});
export const request = ({...options}) =>{
client.defaults.headers.common['Content-Type'] = 'application/json';
client.defaults.headers.common['X-CSRFToken'] = getCsrfToken();
let auth_access = store.getState().auth?.access
let auth_refresh = store.getState().auth?.refresh
const user = jwt_decode(auth_access)
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
if(isExpired){
axios.post(`${api_address}/api/token/refresh/`,
{ "refresh": auth_refresh}).then((res) => {
console.log('res.status',res.status)
let out = res.data
store.dispatch({
type: UPDATE_ACCESS_TOKEN,
payload: out
})
client.defaults.headers.common['Authorization'] = `JWT ${out?.access}`;
}).catch( )
}else{
client.defaults.headers.common['Authorization'] = `JWT ${store.getState().auth?.access}`;
}
const onSuccess = response => response
const onError = error =>{
// console.log(error)
return error
}
return client(options).then(onSuccess).catch(onError)
}
i am trying to develop django/angular project and using base user model for django user.
I tried to implement JWT auth in both side. On django side i am using rest_framework_jwt library.
I checked the token flow from client-side to server-side. With a simple login page on angular i use my username and password for logging in. It's accepting my data and redirects me to the main page where i use a service to get "feed" data from server-side. In this step it gives me 403 error. By the way when i use Postman for checking the scenerios im adding jwt as a authorization header like "JWT " and it still give me "Authentication credentials were not provided". Waiting your helps. Thanks.
django -> settings.py middleware ;
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
django -> settings py rest_framework ;
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATON_CLASSES':(
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
'NON_FIELD_ERRORS_KEY': 'global',
}
i use these settings for jwt
#JWT SETTINGS
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(days=2) # datetime imported at start
}
using classic urls.py ( its and included part so it begins with api/auth/ )
urlpatterns = [
path('login/', obtain_jwt_token),
path('refresh-token/', refresh_jwt_token),
]
in front end i have a interceptor, authservice and feedservice
authservice
import { Injectable } from '#angular/core';
import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { tap, shareReplay } from 'rxjs/operators';
import * as JwtDecode from 'jwt-decode';
import * as moment from 'moment';
import { JWTPayload } from '../interfaces/JWTPayload';
// logs in and out, notifies other components with subscription
#Injectable({
providedIn: 'root'
})
export class AuthenticationService {
URL_API = 'http://localhost:80/api/auth';
constructor(private http: HttpClient) { }
private setSession(authResult) {
const token = authResult.token;
const payload = <JWTPayload>JwtDecode(token);
const expiresAt = moment.unix(payload.exp);
localStorage.setItem('token', authResult.token);
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
}
get token(): string {
return localStorage.getItem('token');
}
login(username: string, password: string) {
return this.http.post(`${this.URL_API}/login/`, { username, password })
.pipe(tap(
response => {
console.log("API CALL RESPONSE FOR LOGIN => ", response);
this.setSession(response);
}
),
shareReplay(),
);
}
logout() {
localStorage.removeItem('token');
localStorage.removeItem('expires_at');
console.log("CIKIS YAPILDI ");
}
refreshToken() {
if (moment().isBetween(this.getExpiration().subtract(1, 'days'), this.getExpiration())) {
return this.http.post(
`${this.URL_API}/refresh-token/`,
{ token: this.token }
).pipe(
tap(response => { this.setSession(response); console.log("response for refresh token=>", response); }),
shareReplay(),
).subscribe();
}
}
getExpiration() {
const expiration = localStorage.getItem('expires_at');
const expiresAt = JSON.parse(expiration);
console.log("expires=>", moment(expiresAt).calendar());
return moment(expiresAt);
}
isLoggedIn() {
return moment().isBefore(this.getExpiration());
}
isLoggedOut() {
return !this.isLoggedIn();
}
}
interceptor
import { Injectable } from '#angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { Observable } from 'rxjs';
// intercepts every api call and adds jwt header to call.
#Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
console.log("firs token check => ", token);
if (token) {
console.log("gecerli token=> ", token)
const cloned = req.clone({
headers: req.headers.set('Authorization', 'JWT '.concat(token))
});
console.log("cloned ->", cloned.body);
return next.handle(cloned);
} else {
console.log("next.handle(req) => ", next.handle(req));
return next.handle(req);
}
}
}
feedservice
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from "rxjs";
import { Post } from '../interfaces/Post';
#Injectable({
providedIn: 'root'
})
export class FeedService {
API_URL = 'http://localhost:80'
constructor(private httpClient: HttpClient) { }
public getFeed(): Observable<Post[]> {
return this.httpClient.get<Post[]>(`${this.API_URL}/api/feed/`);
}
public retrievePost(post_id: number): Observable<Post> {
return this.httpClient.get<Post>(`${this.API_URL}/api/feed/${post_id}`)
}
public postFeed(post: Post) {
return this.httpClient.post(`${this.API_URL}/api/feed/`, post)
}
}
and in finale there is login component
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router, ActivatedRoute } from '#angular/router';
import { first } from 'rxjs/operators';
import { AuthenticationService } from '../services/authentication.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup
returnUrl: string
loading = false;
submitted = false;
error = '';
constructor(
private formBuilder: FormBuilder,
private authenticationService: AuthenticationService,
private router: Router,
private route: ActivatedRoute
) {
//redirect home if already logged in
if (authenticationService.isLoggedIn()) {
console.log("isLoggedIn() => already logged in")
router.navigate(['/']);
}
}
ngOnInit(): void {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
//get returl url or redirect to /
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
}
//easy way to access fields
get f() { return this.loginForm.controls }
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.loginForm.invalid) { console.log("if code block => invalid credentials"); return; }
this.loading = true;
this.authenticationService.login(this.f.username.value, this.f.password.value)
.subscribe(
success => { console.log("succesfull login"); this.router.navigate([this.returnUrl]); },
error => {
this.error = error;
this.loading = false;
console.log("errors =>", error);
}
);
}
}
I figured it out. In my other api's view i added those lines
permission_classes = [ IsAuthenticated ]
authentication_classes = [ JSONWebTokenAuthentication ]
and it worked!
I'm trying to upload a file via Fetch, to a Django REST endpoint.
Component with Fetch Code:
function myComponent(props) {
const classes = useStyles();
const [{token}] = useContext();
function handleImageUpload(files) {
let formData = new FormData()
formData.append('file', files[0])
fetch(receiveSpreadsheetEndpoint, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
}
function Dropzone(props) {
const [files, setFiles] = useState([]);
function handleChange(event) {
setFiles(event[0]);
}
return (
<>
<DropzoneArea
onChange={event => handleChange(event)}
/>
<Button
variant="contained"
color="primary"
onClick={event => handleImageUpload([files])}
>
Primary
</Button>
</>
)
}
return (
<Container className={classes.root} height="100%">
<Dropzone
acceptedFiles={['.csv', 'text/*', 'text/csv']}
showPreviews={true}
showFileNamesInPreview={true}
/>
</Container>
);
}
Django REST endpoint:
class ReceiveFileData(APIView):
permission_classes = (IsAuthenticated,)
parser_classes = (JSONParser, FormParser, MultiPartParser)
def post(self, request):
my_file = request.stream.read
data = {} <== a breakpoint is set here
return Response(data, status=status.HTTP_200_OK)
After my_file = request.stream.read, my_file contains b''.
What am I leaving out?
UPDATE
Here's the request object when it arrives at the REST endpoint on the server:
This is now working. Here is working code in case it may be helpful to others.
REACT
import React, {useEffect, useRef, useState, Component} from 'react';
import 'typeface-roboto';
import {makeStyles} from '#material-ui/styles';
import {useContext} from "../../context";
import Container from "#material-ui/core/Container";
import {DropzoneArea} from 'material-ui-dropzone'
import Button from "#material-ui/core/Button";
//ENDPOINTS
const receiveSpreadsheetEndpoint = '/spreadsheet_generator/api/receive-spreadsheet/';
function UploadSpreadsheet(props) {
const classes = useStyles();
const [{token}] = useContext();
function handleImageUpload(event, theFile) {
event.stopPropagation();
event.preventDefault();
let formData = new FormData()
formData.append('file', theFile)
fetch(receiveSpreadsheetEndpoint, {
method: 'POST',
body: formData,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
}
function Dropzone(props) {
const [file, setFile] = useState(null);
function handleChange(event) {
setFile(event[0]);
}
return (
<>
<DropzoneArea
onChange={event => handleChange(event)}
filesLimit={1}
/>
<Button
variant="contained"
color="primary"
onClick={event => handleImageUpload(event, file)}
>
Primary
</Button>
</>
)
}
return (
<Container className={classes.root} height="100%">
<Dropzone
acceptedFiles={['.csv', 'text/*', 'text/csv']}
showPreviews={true}
showFileNamesInPreview={true}
/>
</Container>
);
}
//https://material-ui.com/components/buttons/
const useStyles = makeStyles(theme => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
},
}))
;
export default UploadSpreadsheet;
DJANGO REST ENDPOINT
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser, FileUploadParser
import logging
from server_modules.spreadsheet.models import SourceSpreadsheet
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s %(levelname)s %(message)s',
)
def create_spreadsheet_record(file):
# insert record to SourceSpreadsheet
SourceSpreadsheet.objects.create(file=file)
class ReceiveData(APIView):
permission_classes = (IsAuthenticated,)
parser_classes = (FileUploadParser,)
def post(self, request):
success = True
message = 'OK'
file = request.stream.FILES['file']
# save to db
try:
create__spreadsheet_record(file)
except Exception as e:
print("Error calling ReceiveData: " + str(e))
message = str(e)
success = False
data = {'message': message }
if (success):
return Response(data, status=status.HTTP_200_OK)
else:
return Response(data, status=status.HTTP_400_BAD_REQUEST)
DJANGO MODEL
class SourceSpreadsheet(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
file = models.FileField(upload_to='source_spreadsheets/%Y/%m')
BASE.PY
Add:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
I'm using Angular 6 and Django REST Framework
The view of DRF is
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return AmountGiven.objects.filter(
contact__user=self.request.user
)
def perform_create(self, serializer):
save_data = {}
print(self.request.POST)
# validate user and to save_data dictionary
contact_pk = self.request.POST.get('contact', None)
print(contact_pk)
if not contact_pk:
raise ValidationError({'contact': ['Contact is required']})
contact = Contact.objects.filter(
user=self.request.user,
pk=contact_pk
).first()
if not contact:
raise ValidationError({'contact': ['Contact does not exists']})
# add contact to save_data dictionary
save_data['contact'] = contact
# process mode_of_payment is in request
mode_of_payment_pk = self.request.POST.get('mode_of_payment', None)
if mode_of_payment_pk:
mode_of_payment = ModeOfPayment.objects.get(pk=mode_of_payment_pk)
if not mode_of_payment:
raise ValidationError({'mode_of_payment': ['Not a valid mode of payment']})
# add mode_of_payment to save_data dictionary
save_data['mode_of_payment'] = mode_of_payment
# pass save_data dictionary to save()
serializer.save(**save_data)
AmountGivenSerializer in serializers.py
class AmountGivenSerializer(serializers.ModelSerializer):
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'interest_rate', 'duration', 'given_date', 'promised_return_date',
'mode_of_payment', 'transaction_number', 'interest_to_pay', 'total_payable', 'amount_due',
'total_returned', 'comment', 'modified', 'created'
)
and in Angular component
import { Component, OnInit } from '#angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '#angular/forms';
import {AmountGiven} from '../amount-given.model';
import {AmountGivenService} from '../amount-given.service';
import {ActivatedRoute} from '#angular/router';
#Component({
selector: 'app-amount-given-add',
templateUrl: './amount-given-add.component.html',
styleUrls: ['./amount-given-add.component.css']
})
export class AmountGivenAddComponent implements OnInit {
addMoneyForm: FormGroup;
submitted = false;
contact_id: string;
amountGiven: AmountGiven;
constructor(
private formBuilder: FormBuilder,
private amountGivenService: AmountGivenService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.route.params.subscribe(
param => {
this.contact_id = param['contact_id'];
}
);
this.addMoneyForm = this.formBuilder.group({
amount: new FormControl('', [
Validators.required
]),
interest_rate: new FormControl(),
duration: new FormControl(),
given_date: new FormControl(),
promised_return_date: new FormControl(),
transaction_number: new FormControl(),
mode_of_payment: new FormControl(),
comment: new FormControl()
});
}
get f() {
return this.addMoneyForm.controls;
}
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.addMoneyForm.invalid) {
console.log('invalid');
return;
}
const data = this.addMoneyForm.value;
data.contact = this.contact_id;
this.amountGivenService.add(data).subscribe(res => {
console.log('req completed', res);
});
}
}
But when form is submited it returns validation error from server for contact field.
{"contact":["Contact is required"]}
The request header has the contact parameter
sending the same request from Postman is working fine. The Javascript XHR code of Postman request is
var data = new FormData();
data.append("amount", "5000");
data.append("contact", "65827a1f-003e-4bb3-8a90-6c4321c533e6");
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://koober-production.herokuapp.com/api/amount-given/");
xhr.setRequestHeader("Authorization", "Bearer ATjIuQ6hLzc55wHaXIzHmcKafEzk1B");
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Postman-Token", "28d9d33a-f0a6-431c-8936-da4f6565ece4");
xhr.send(data);
could not understand this issue is related to Angular or Django because Postman request is working fine with Djano and Angular is sending the request in the parameter.
Edit 2: AmountGivenService
import { Injectable } from '#angular/core';
import {ResourceProviderService} from '../../resource-provider.service';
import {AuthService} from '../../auth/auth.service';
import {Observable, of} from 'rxjs';
import {AmountGiven} from './amount-given.model';
import {AppHttpClient} from '../../app-http-client';
#Injectable({
providedIn: 'root'
})
export class AmountGivenService {
private url = 'amount-given/';
constructor(
private http: AppHttpClient
) { }
add(data): Observable<AmountGiven> {
return this.http.Post<AmountGiven>(this.url, data);
}
}
which is furhter using AppHttpClient
import { Injectable } from '#angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '#angular/common/http';
import {Observable, of} from 'rxjs';
import {catchError} from 'rxjs/operators';
import {ResourceProviderService} from './resource-provider.service';
export interface IRequestOptions {
headers?: HttpHeaders;
observe?: 'body';
params?: HttpParams;
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
body?: any;
}
export function appHttpClientCreator(http: HttpClient, resource: ResourceProviderService) {
return new AppHttpClient(http, resource);
}
#Injectable({
providedIn: 'root'
})
export class AppHttpClient {
private api_url = this.resource.url + '/api/';
constructor(
public http: HttpClient,
private resource: ResourceProviderService
) {
}
public Post<T>(endPoint: string, params: Object, options?: IRequestOptions): Observable<T> {
return this.http.post<T>(this.api_url + endPoint, params, options)
.pipe(
catchError(this.handleError('post', endPoint, null))
);
}
private handleError<T> (operation = 'operation', endpoint = '', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
console.log(error.message);
console.log(operation);
console.log(endpoint);
return of(result as T);
};
}
}
The problem is that you're trying to access:
self.request.POST.get('contact', None)
in DRF you need to do:
self.request.data.get('contact', None)
It works from postman because you're constructing a FormData object which is fundamentally different from the JSON body angular will send and that is expected by a RESTful API like DRF.
from a more general standpoint, in DRF, serializers should perform the request validation using the built in validators, rather than manually validating in the view.
I'm hesitant to add this as the answer just yet, but it feels like you need to add something along the lines of:
<form method="POST" action="url/to/your-DRF-endpoint" enctype="application/x-www-form-urlencoded">
....
</form>
Either within your template or within your Angular POST request code you need to make sure the data is posted as application/x-www-form-urlencoded.
Essentially, Django expects application/x-www-form-urlencoded, and without it your self.request.POST object will be empty and hence self.request.POST.get('contact', None) will be empty and default to None.
I'm new to Angular and I'm creating a test Application to accelerate my understanding of the topic. Recently I encountered a challenge to integrate Angular2(FrontEnd) with Django(Backend) by fetching the data using REST APIs.
File: library.service.ts
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Rx';
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
// Project Modules
import { Library } from '../models';
#Injectable()
export class LibraryService {
private librariesUrl = 'http://127.0.0.1:8000/api/library/create-library/';
constructor(private http: Http) { }
private headers = new Headers({'Content-Type': 'application/json'});
private extractData(res: Response) {
return res.json();
}
private handleError (error: any) {
return Observable.throw(error.message || 'Server error');
}
getAll(): Observable<Library[]> {
return this.http.get(this.librariesUrl, {headers: this.headers})
.map((res) => this.extractData(res.json())).catch((err) => this.handleError(err));
}
}
File: libraries.component.ts
import { Component, OnInit} from '#angular/core';
import {HttpClient} from '#angular/common/http';
// Project Modules
import { Library } from '../models';
import { LibraryService } from './library.service';
#Component({
selector: 'app-libraries',
templateUrl: './libraries.component.html',
styleUrls: ['./libraries.component.css'],
})
export class LibrariesComponent implements OnInit {
libraries: Library[];
personalLibraries: Library[];
collaborativeLibraries: Library[];
constructor(private libraryService: LibraryService, private http: HttpClient) { }
ngOnInit(): void {
/*this.http.get('http://127.0.0.1:8000/api/library/create-library/').subscribe((data: Library[]) => {
console.log(data);
this.personalLibraries = data;
});*/
this.libraryService.getAll().subscribe(response => this.personalLibraries = response);
}
}
REST API
# Django Modules
from django.shortcuts import get_object_or_404
# REST Modules
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view, authentication_classes, permission_classes
# Project Modules
from .models import Resource, ResourceUserAssociation, Collection, Library
from mysite.utils import get_value_or_404, get_value_or_default, get_boolean
from .serializers import LibrarySerializer, CollectionSerializer
# TODO: Check user authentication here
class CreatorLibraryAPI(APIView):
def get(self, request, format=None):
# slug = get_value_or_404(request.GET, 'slug')
lib_object = Library.objects.filter(type='personal')
sdata = LibrarySerializer(lib_object, many=True).data
return Response(sdata, status=status.HTTP_200_OK)
JSON I'm Expecting
[
{
"slug": "tech-stack",
"title": "Technology Stack",
"description": "Library of resources related to Technology",
"type": "personal"
},
{
"slug": "biz-stack",
"title": "Technology Stack",
"description": "Library of resources related to Business",
"type": "personal"
},
{
"slug": "design-stack",
"title": "Design Stack",
"description": "Library of resources related to Design",
"type": "personal"
}
]
Important
When I try to fetch data in the Component only, then I successfully get the result [See the commented code in libraries.components.ts]. But somehow it's not working in the Service, am I doing something wrong with Observables?
Note
This problem is very similar to Question here.
Big thanks to the community in advance :)
Few changes I've made:
Used HttpClient instead of Http. This allows me to remove .map() as HttpClien already returns the JSON (instead of the whole response).
Correct File: library.service.ts
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Rx';
import { Injectable } from '#angular/core';
import { Headers } from '#angular/http';
import {HttpClient} from '#angular/common/http';
// Project Modules
import { Library } from '../models';
#Injectable()
export class LibraryService {
private librariesUrl = 'http://127.0.0.1:8000/api/library/create-library/';
constructor(private http: HttpClient) { }
private headers = new Headers({'Content-Type': 'application/json'});
private handleError (error: any) {
console.log('---------- CATCH ERROR ----------');
return Observable.throw(error.message || 'this is some random server error');
}
getAll(): Observable<Library[]> {
return this.http.get(this.librariesUrl).catch((err) => this.handleError(err));
}
}