Angular post data is not going to Django REST API - django

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.

Related

how to authenticate Angular and Django Rest Framework

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 },
];

401 "Unauthorized" error in Django and Angular File Upload

I have created a Django and Angular application to upload files. It was working without errors until I integrated a login page. I have not been able to upload files since integration. I get 401 - "Unauthorized" error. What could have possibly gone wrong?
Auth-interceptor:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { catchError, Observable, throwError } from "rxjs";
import { LoginService } from "src/services/login.service";
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: LoginService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.isLoggedIn()) {
const token = this.authService.getAuthToken();
console.log("intercept",token)
// If we have a token, we set it to the header
request = request.clone({
setHeaders: {Authorization: `Token ${token}`}
});
}
return next.handle(request)
}
}
fileupload.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { LoginService } from 'src/services/login.service';
import { FileUploader, FileLikeObject } from 'ng2-file-upload';
import { concat, Observable } from 'rxjs';
import { HttpEvent, HttpEventType } from '#angular/common/http';
#Component({
selector: 'app-fileupload',
templateUrl: './fileupload.component.html',
styleUrls: ['./fileupload.component.scss']
})
export class FileuploadComponent {
DJANGO_SERVER = 'http://127.0.0.1:8081'
public uploader: FileUploader = new FileUploader({});
public hasBaseDropZoneOver: boolean = false;
constructor(private formBuilder: FormBuilder, private uploadService: LoginService) { }
fileOverBase(event): void {
this.hasBaseDropZoneOver = event;
}
getFiles(): FileLikeObject[] {
return this.uploader.queue.map((fileItem) => {
return fileItem.file;
});
}
upload() {
let files = this.getFiles();
console.log(files);
let requests= [];
files.forEach((file) => {
let formData = new FormData();
formData.append('file' , file.rawFile, file.name);
requests.push(this.uploadService.upload(formData));
console.log(requests,file)
});
concat(...requests).subscribe(
(res) => {
console.log(res);
},
}
);
}}
console.log(err);
}
);
}}
service:
public upload(formData) {
let token= localStorage.getItem('token');
return this.http.post<any>(`${this.DJANGO_SERVER}/upload/`, formData).pipe(map((res) => {
console.log(res)
})
)
}
Thank you
I resolved the issue. It was because I was usign interceptor and I was using third party API for authentication. So instead of Django token, the third party APIs token was sent in header of POST request.
How I resolved it?
I used Httpbackend to process POST requests to Django DB so that the request is not intercepted and then I added custom header (with Django token to the reuest). I used the code snippet on this website: https://levelup.gitconnected.com/the-correct-way-to-make-api-requests-in-an-angular-application-22a079fe8413

How do I display Django Rest Framework Foreign Key in Angular?

DISCLAIMER: This is gonna be a pretty long description of a problem, to which I can't seem to find sources for that could help me. So any piece of information is appreciated!
I might add that I am fairly new to Angular and text script.
I have set up my backend with Django and set up the Rest Framework. These are my models:
models.py
class Person(models.Model):
name = models.CharField(max_length = 250, blank = True)
job = models.ForeignKey('Job', on_delete = models.CASCADE, null = True)
def __str__(self):
return self.name
class Job(models.Model):
job_name = models.CharField(max_length = 200, blank = False)
rank = models.PositiveIntegerField(default = 0, unique = True)
def __str__(self):
return self.job_name
In the backend I have set up different job names (Doctor, Teacher, etc...). So when I create a new Person, I get to choose from one of the different jobs, which have been created prior to this. (This is exactly how I woukd want my frontend to work)
Here are my serializer.py, views.py and urls.py files:
serializer.py
class PersonSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Person
fields = ('id', 'name', 'job')
class JobSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Job
fields = ('id', 'job_name')
views.py
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
def list(self, request, *args, **kwargs):
person = Person.objects.all()
serializer = PersonSerializer(person, many = True, context = {'request': request})
return Response(serializer.data)
class JobViewSet(viewsets.ModelViewSet):
queryset = Job.objects.all()
serializer_class = JobSerializer
def list(self, request, *args, **kwargs):
job = Job.objects.all()
serializer = JobSerializer(job, many = True, context = {'request': request})
return Response(serializer.data)
urls.py
router = routers.DefaultRouter()
router.register('person', views.PersonViewSet)
router.register('job', views.JobViewSet)
urlpatterns = [
path('', include(router.urls)),
]
The Rest Framework has successfully been integrated and I can now create, read, update and delete (CRUD) my models.
In Angular I have created a person component with a service, which contains very simple functions, to use CRUD on my frontend.
person.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class PersonService {
backendURL = 'http://127.0.0.1:8000'
httpHeaders = new HttpHeaders({'Content-Type': 'application/json'})
constructor(private http:HttpClient) { }
getPerson():Observable<any> {
return this.http.get(this.backendURL + '/person/',
{headers: this.httpHeaders});
}
getOnePerson(id):Observable<any> {
return this.http.get(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
updatePerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.put(this.backendURL + '/person/' + person.id + '/', body,
{headers: this.httpHeaders});
}
createPerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.post(this.backendURL + '/person/', body,
{headers: this.httpHeaders});
}
deletePerson(id): Observable<any> {
return this.http.delete(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
}
And my component ts file:
person.component.ts
import { Component, OnInit } from '#angular/core';
import { PersonService } from '../../services/person.service';
#Component({
selector: 'app-person',
templateUrl: './person.component.html',
styleUrls: ['./person.component.css'],
providers: [PersonService]
})
export class PersonComponent implements OnInit {
person_all = [{ name: ''}, ];
selectedPerson;
constructor(private api: PersonService) {
this.getPerson();
this.selectedPerson = { id:-1, name: '', job: '', }
}
getPerson = () => {
this.api.getPerson().subscribe(
data => {
this.person_all = data;
},
error => {
console.log(error);
}
)
}
personClicked = (person) => {
this.api.getOnePerson(person.id).subscribe(
data => {
this.selectedPerson = data;
},
error => {
console.log(error);
}
)
}
updatePerson = () => {
this.api.updatePerson(this.selectedPerson).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
createPerson = () => {
this.api.createPerson(this.selectedPerson).subscribe(
data => {
this.person_all.push(data);
},
error => {
console.log(error);
}
);
}
deletePerson = () => {
this.api.deletePerson(this.selectedPerson.id).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
ngOnInit(): void {
}
}
In HTML I created a very simple view, in where I can see every created Person and some buttons to create, update or delete some.
person.component.html
<h2>Person List</h2>
<ul>
<li *ngFor="let person of person_all">
<h2 (click)="personClicked(person)">{{person.nachname}}</h2>
</li>
</ul>
Name <input type="text" [(ngModel)]="selectedPerson.name"/><br>
Job <input type="text" [(ngModel)]="selectedPerson.job"/><br>
<button *ngIf="selectedPerson.id != -1" (click)="updatePerson()"> PUT </button>
<button *ngIf="selectedPerson.id == -1" (click)="createPerson()"> CREATE </button>
<button *ngIf="selectedPerson.id != -1" (click)="deletePerson()"> DELETE </button>
This is my problem: When I try to create a new Person, I don't see the different job names, which I have set up in the backend. I want to know, how do I get a "list" of jobs to select from, similar to how it works in Django with a Foreign Key?
Thanks for the read and thanks in advance!

Send File via Fetch to Django REST Endpoint?

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')

angular save() method not working hero tutorial

Following the Angular (using 7.3.4) Hero Tutorial. Using angular with a django backend. I cannot seem to get a hero name to update:
I have a hero-detail.component.ts that has a save() method which should update the hero via the HeroService.updateHero() method. When i click the save button nothing happens at all...
I am suspicious that the heroService.updateHero method is pointing to the wrong url, but I am not sure where to point it or what to pass it to. Also using Pycharm and put in return this.http.put(this.heroesUrl, hero, httpOptions is red as an unresolved function, but i think this is just a Typescript Pycharm setting that should not matter.
Any pointers appreciated.
hero-detail.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { Hero } from '../hero'
import { ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common';
import { HeroService } from '../hero.service';
#Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
#Input() hero: Hero;
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const number = +this.route.snapshot.paramMap.get('number');
this.heroService.getHero(number)
.subscribe(hero => this.hero = hero);
}
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
goBack(): void {
this.location.back();
}
hero.service.ts
import { Injectable } from '#angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { Observable, of } from 'rxjs'
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { catchError, map, tap } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable({
providedIn: 'root'
})
export class HeroService {
private heroesUrl = 'http://127.0.0.1:8000/heroes/'; // URL to web api
constructor(
private http : HttpClient
) { }
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}
getHero(id:number): Observable<Hero>{
return this.http.get<Hero>(`${this.heroesUrl}${id}`);
}
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions)
}
}
}
heroes/views.py
from django.shortcuts import render
from rest_framework.response import Response
from .models import Hero
from rest_framework import generics
from .serializers import HeroSerializer
# Create your views here.
class HeroList(generics.ListAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
class Meta:
model = Hero
fields = ('number', 'name','id')
class HeroDetail(generics.GenericAPIView):
serializer_class = HeroSerializer
def get(self, request, id):
hero_detail = Hero.objects.get(id=id)
serializer = HeroSerializer(hero_detail)
return Response(serializer.data)
def put(self, request, id):
hero_detail = Hero.objects.get(id=id)
# hero_detail.name = request.data.get("name")
hero_detail.save()
serializer = HeroSerializer(hero_detail)
return Response(serializer.data)
class Meta:
model = Hero
fields = ('number', 'name','id')
hero-detail.component.html
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
</div>
</div>
<button (click)="goBack()">go back</button>
<button (click)="save()">save</button>
You should send the put request to '/heroes/hero_id/' for updates. With the restful APIs, in general, you should send
POST request to /heroes/ -> Create hero
GET request to /heroes/ -> List heroes
GET request to /heroes/id/ -> Retrieve single hero
PUT request to /heroes/id/ -> Update hero
DELETE request to /heroes/id/ -> Delete hero
So in your example, you should modify your updateHero method like this:
updateHero (hero: Hero): Observable<any> {
return this.http.put(`${this.heroesUrl}${hero.id}/` hero, httpOptions)
}