Lit2.0 how to submit form data to backend - django

Am using Lit2.0, Material Web components, Django (backend).
one reference: https://www.thinktecture.com/en/web-components/flaws/
I don't understand how to submit form data from Lit component to backend (Django)
form.html contains Lit component (basic-form)
<form id="id_demo" method="post" action="">
{% csrf_token %}
<basic-form></basic-form>
<button type="submit" class="mdc-button mdc-button--raised">Submit</button>
</form>
basic-form is a Lit component and it contains Material web components
import {LitElement, html} from "lit";
// const template = document.createElement('template');
// template.innerHTML = `
// <slot></slot>
// `;
export class BasicForm extends LitElement {
static properties = {
form: '',
};
constructor() {
super();
// this.shadow = this.attachShadow({ mode: 'open' });
// this.shadow.appendChild(template.content.cloneNode(true));
}
render() {
return html`
<mwc-textfield name="first_name"></mwc-textfield>
`;
}
}
customElements.define('basic-form', BasicForm);
Could someone guide me to the right direction.

You can take the value of textfield element on blur and save it as property of basic-form. Then on form submit you can take the basic-form.value property:
basic-form
export class BasicForm extends LitElement {
static properties = {
value: ''
}
onBlur() {
this.value = e.target.value;
}
render() {
return html`
<mwc-textfield name="first_name" #onBlur="${this.onBlur}></mwc-textfield>
`;
}
form.html
<form id="id_demo" method="post" action="">
{% csrf_token %}
<basic-form></basic-form>
<button type="submit" class="mdc-button mdc-button--raised">Submit</button>
</form>
<script>
const form = document.getElementById("id_demo");
const basicForm = form.querySelector('basic-form');
const onSubmit = (event) => {
console.log(basicForm.value);
}
form.addEventListener('submit', onSubmit);
</script>

Actually this is not that easy at all. You have to use ElementInternals: https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals
"The ElementInternals interface of the Document_Object_Model gives web developers a way to allow custom elements to fully participate in HTML forms."
That said here is an example:
<script type="module">
import {
LitElement,
html,
css
} from "https://unpkg.com/lit-element/lit-element.js?module";
class MyItem extends LitElement {
static get formAssociated() {
return true;
}
static get properties() {
return {
name: { type: String, reflect: true },
required: { type: Boolean, reflect: true },
value: { type: String }
};
}
constructor() {
super();
this.internals = this.attachInternals();
this.name = name;
this.required = false;
this.value = '';
this._required = false;
}
render() {
return html`
<label for="input"><slot></slot></label>
<input type="${this.type}" name="${this.name}" id="input" .value="${this.value}" ?required="${this.required}" #input="${this._onInput}">
`;
}
_onInput(event) {
this.value = event.target.value
this.internals.setFormValue(this.value);
}
firstUpdated(...args) {
super.firstUpdated(...args);
/** This ensures our element always participates in the form */
this.internals.setFormValue(this.value);
}
}
customElements.define("my-item", MyItem);
const formElem = document.querySelector('#formElem');
formElem.addEventListener('submit', event => {
event.preventDefault();
const form = event.target;
/** Get all of the form data */
const formData = new FormData(form);
formData.forEach((value, key) => console.log(`${key}: ${value}`));
});
</script>
<form id="formElem">
<my-item name="firstName">First name</my-item>
<button type="submit">Submit</button>
</form>

Related

I need to Delete any object from my DRF / django connected database through angular 13

I am getting the id which i have to delete but the last line of service.ts that is of delete method is not getting executed...
the files and code snippets I used are : -
COMPONENT.HTML
<li *ngFor="let source of sources$ | async | filter: filterTerm">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{source.name}}</h5>
<p>URL:- <a href ='{{source.url}}'>{{source.url}}</a></p>
<a class="btn btn-primary" href='fetch/{{source.id}}' role="button">fetch</a>
<button class="btn btn-primary" (click)="deleteSource(source.id)">delete </button>
<br>
</div>
</div>
</li>
I tried to console the id geeting from html and the Id i am getting is correct.
//component.ts
export class SourcesComponent implements OnInit {
filterTerm!: string;
sources$ !: Observable<sources[]>;
// deletedSource !: sources;
constructor(private sourcesService: SourcesService) { }
// prepareDeleteSource(deleteSource: sources){
// this.deletedSource = deleteSource;
// }
ngOnInit(): void {
this.Source();
}
Source(){
this.sources$ = this.sourcesService.Sources()
}
deleteSource(id : string){
console.log(id)
this.sourcesService.deleteSource(id);
}
//service.ts
export class SourcesService {
API_URL = 'http://127.0.0.1:8000/sourceapi';
constructor(private http: HttpClient) { }
// let csrf = this._cookieService.get("csrftoken");
// if (typeof(csrf) === 'undefined') {
// csrf = '';
// }
/** GET sources from the server */
Sources() : Observable<sources[]> {
return this.http.get<sources[]>(this.API_URL,);
}
/** POST: add a new source to the server */
addSource(source : sources[]): Observable<sources[]>{
return this.http.post<sources[]> (this.API_URL, source);
//console.log(user);
}
deleteSource(id: string): Observable<number>{
let httpheaders=new HttpHeaders()
.set('Content-type','application/Json');
let options={
headers:httpheaders
};
console.log(id)
return this.http.delete<number>(this.API_URL +'/'+id)
}
}
Angular HTTP functions return cold observables. This means that this.http.delete<number>(this.API_URL +'/'+id) will return an observable, which will not do anything unless someone subscribes to it. So no HTTP call will be performed, since no one is watching the result.
If you do not want to use the result of this call, you have different options to trigger a subscription.
simply call subscribe on the observable:
deleteSource(id : string){
console.log(id)
this.sourcesService.deleteSource(id).subscribe();
}
Convert it to a promise and await it (or don't, if not needed) using lastValueFrom:
async deleteSource(id : string){
console.log(id)
await lastValueFrom(this.sourcesService.deleteSource(id));
}

Image is not passing to view (cropperjs, django, ajax)

I have an user profile where he can crop his image, and so after cropping, and printing data in view nothing is passed to view
Form
<form method="post" action="change_photo/" id="avatar_changing_form">
{% csrf_token %}
<label for="upload_image">
{% if item.image%}
<img src="{{item.image.url}}" alt="" style="max-height:300px">
{%else%}
<img src="{%static 'avatar_sample.png' %}" id="uploaded_image"
class="img-responsive img-circle" />
{%endif%}
<div class="overlay">
<div class="text">Click to Change Profile Image</div>
</div>
<!-- <input type="file" name="image" class="image" id="upload_image" style="display:none" /> -->
{{imageForm.image}}
</label>
</form>
JS & Ajax
$(document).ready(function(){
const imageForm = document.getElementById('avatar_changing_form')
const confirmBtn = document.getElementById('crop')
const input = document.getElementById('upload_image')
const csrf = document.getElementsByName('csrfmiddlewaretoken')
var $modal = $('#modal');
var image = document.getElementById('sample_image');
var cropper;
$('#upload_image').change(function (event) {
var files = event.target.files;
var done = function (url) {
image.src = url;
$modal.modal('show');
};
if (files && files.length > 0) {
reader = new FileReader();
reader.onload = function (event) {
done(reader.result);
};
reader.readAsDataURL(files[0]);
}
});
$modal.on('shown.bs.modal', function () {
cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 3,
preview: '.preview'
});
}).on('hidden.bs.modal', function () {
cropper.destroy();
cropper = null;
});
$('#crop').click(function () {
cropper.getCroppedCanvas().toBlob((blob) => {
console.log('confirmed')
const fd = new FormData();
fd.append('csrfmiddlewaretoken', csrf[0].value)
fd.append('file', blob, 'my-image.png');
$.ajax({
type: 'POST',
url: imageForm.action,
enctype: 'multipart/form-data',
data: fd,
success: function (response) {
console.log('success', response)
$modal.modal('hide');
$('#uploaded_image').attr('src', fd);
},
error: function (error) {
console.log('error', error)
},
cache: false,
contentType: false,
processData: false,
})
});
});
})
View
def change_photo(request):
if request.user.is_authenticated and Guide.objects.filter(user = request.user).exists():
item = Guide.objects.get(user=request.user)
if request.method == "POST":
Photoform = ChangeImageForm(request.POST or None, request.FILES or None, instance = item)
if Photoform.is_valid():
print(Photoform.cleaned_data['image'])
Photoform.save()
return HttpResponseRedirect('/profile/')
form
class ChangeImageForm(ModelForm):
class Meta:
model = Guide
fields = ['image']
def __init__(self, *args, **kwargs):
super(ChangeImageForm, self).__init__(*args, **kwargs)
self.fields['image'].widget = FileInput(attrs={
'name':'image',
'class':'image',
'id':'upload_image',
'style':'display:none'
})
When i print image field from cleaned data in terminal displays "none", and when i load image through admin everything working good, can pls someone tell me where the problem is?

Upload image file from Angular to Django REST API

I want to select an image and POST that to the Django REST API that I have built but I am getting an error in doing so
""product_image": ["The submitted data was not a file. Check the encoding type on the form."]"
I can create and update strings and integers but when I try add the image field I get this error. I can POST images through Postman so I don't think its my API that's at fault.
Can anyone help me out here as I have looked around but can't seem to find anything.
add-product.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { ApiService } from 'src/app/api.service';
import { Product } from 'src/app/models/Product';
import { Location } from '#angular/common';
import { Shop } from '../../../models/Shop';
#Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.css']
})
export class AddProductComponent implements OnInit {
product!: Product;
selectedFile!: File;
colours = [
'Red',
'Blue',
'Orange',
'Yellow',
'White',
'Black',
'Navy',
'Brown',
'Purple',
'Pink'
]
categories = [
'Food & Beverages',
'Clothing & Accessories',
'Home & Garden',
'Health & Beauty',
'Sports & Leisure',
'Electronic & Computing'
]
stock = [
'In Stock',
'Out Of Stock',
]
productForm = new FormGroup({
name: new FormControl(''),
price: new FormControl(0.00),
product_image: new FormControl(''),
product_description: new FormControl(''),
quantity: new FormControl(0),
colour: new FormControl(''),
stock: new FormControl(''),
shipping_fee: new FormControl(0.00),
weight: new FormControl(0.00),
shop: new FormControl(''),
category: new FormControl(''),
});
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private location: Location,
private router: Router,
private postService: ApiService
) { }
ngOnInit(): void {
}
goBack(): void {
this.location.back();
}
onFileSelected(event: any) {
this.selectedFile = <File>event.target.files[0]
console.log(event)
}
addProduct(): void {
console.log(this.productForm.value)
this.apiService.addProduct(
this.productForm.value
).subscribe(() => this.goBack(),
result => console.log(result),
);
}
}
add-product.component.html
<form [formGroup]="productForm">
<label>Product Name</label><br>
<input type="text" formControlName="name"><br>
<label>Price</label><br>
<input type="text" formControlName="price"/><br>
<input type="file" formControlName="product_image" (change)="onFileSelected($event)"><br>
<label>Description</label><br>
<textarea type="text" formControlName="product_description"></textarea><br>
<label>Quantity of Products</label><br>
<input type="text" formControlName="quantity"/><br>
<label>Colour of Product</label><br>
<select formControlName="colour">
<option value="" disabled>Choose a Colour</option>
<option *ngFor="let colour of colours" [ngValue]="colour">
{{ colour }}
</option>
</select><br>
<label>Stock Availability</label><br>
<select formControlName="stock">
<option value="" disabled>Stock Availability</option>
<option *ngFor="let stock of stock" [ngValue]="stock">
{{ stock }}
</option>
</select><br>
<label>Shipping Fee Price</label><br>
<input type="text" formControlName="shipping_fee"/><br>
<label>Weight (kg)</label><br>
<input type="text" formControlName="weight"/><br>
<label>Shop</label><br>
<input type="text" formControlName="shop"/><br>
<label>Category</label><br>
<select formControlName="category">
<option value="" disabled>Choose a Category</option>
<option *ngFor="let category of categories" [ngValue]="category">
{{ category }}
</option>
</select><br>
</form>
<button type="submit" (click)="addProduct()">Add</button>
<button (click)="goBack()">go back</button>
api.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Product } from './models/Product';
import { CookieService } from 'ngx-cookie-service';
import { Category } from './models/Category';
import { Shop } from './models/Shop';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ApiService {
baseUrl = 'http://127.0.0.1:8000/';
baseProductUrl = `${this.baseUrl}app/products/`
baseCategoryUrl = `${this.baseUrl}app/categories/`
baseShopUrl = `${this.baseUrl}app/shops/`
headers = new HttpHeaders({
'Content-Type': 'application/json',
});
constructor(
private httpClient: HttpClient,
private cookieService: CookieService,
) { }
/* Product CRUD */
/* POST: add a product to the server */
addProduct(product: Product) {
const productUrl = `${this.baseProductUrl}`
return this.httpClient.post<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// DELETE: delete the product from the server
deleteProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.delete<Product>(productUrl, {headers: this.getAuthHeaders()})
}
// PUT: update the product on the server
updateProduct(product_code: number, product: Product): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.put<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// GET: get all products from the server
getProducts() {
return this.httpClient.get<Product[]>(this.baseProductUrl, {headers: this.getAuthHeaders()});
}
// GET: get one product from the server
getProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.get<Product>(productUrl, {headers: this.getAuthHeaders()});
}
// GET: get all categories from the server
getCategories() {
return this.httpClient.get<Category[]>(this.baseCategoryUrl, {headers: this.getAuthHeaders()});
}
// GET: get one category from the server
getCategory(id: number): Observable<Category> {
const url = `${this.baseCategoryUrl}${id}/`;
return this.httpClient.get<Category>(url, {headers: this.getAuthHeaders()});
}
// GET: get all shops from the server
getShops() {
return this.httpClient.get<Shop[]>(this.baseShopUrl, {headers: this.getAuthHeaders()});
}
// GET: get one shop from the server
getShop(business_reg: string): Observable<Shop> {
const url = `${this.baseShopUrl}${business_reg}/`;
return this.httpClient.get<Shop>(url, {headers: this.getAuthHeaders()});
}
loginUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}auth/`, body, {headers: this.headers});
}
registerUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}user/users/`, body, {headers: this.headers});
}
getAuthHeaders() {
const token = this.cookieService.get('ur-token')
return new HttpHeaders({
'Content-Type': 'application/json',
Authorization: `Token ${token}`
});
}
}
The content-type must be multipart/form-data instead of application-json
that way you can send files to an API

Getting credit card brand and show error message when using hosted fields in Braintree

I am trying to create payment page using braintree's hosted fields.
I have created sandbox account.
But i am not getting additional details like Card brand, error message like Drop in UI.
How to get those functionalities using Hosted fields.
import React from 'react';
var braintree = require('braintree-web');
class BillingComponent extends React.Component {
constructor(props) {
super(props);
this.clientDidCreate = this.clientDidCreate.bind(this);
this.hostedFieldsDidCreate = this.hostedFieldsDidCreate.bind(this);
this.submitHandler = this.submitHandler.bind(this);
this.showPaymentPage = this.showPaymentPage.bind(this);
this.state = {
hostedFields: null,
errorOccurred: false,
};
}
componentDidCatch(error, info) {
this.setState({errorOccurred: true});
}
componentDidMount() {
this.showPaymentPage();
}
showPaymentPage() {
braintree.client.create({
authorization: 'sandbox_xxxxx_xxxxxxx'
}, this.clientDidCreate);
}
clientDidCreate(err, client) {
braintree.hostedFields.create({
onFieldEvent: function (event) {console.log(JSON.stringify(event))},
client: client,
styles: {
'input': {
'font-size': '16pt',
'color': '#020202'
},
'.number': {
'font-family': 'monospace'
},
'.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
'card-brand-id': true,
supportedCardBrands: 'visa'
},
cvv: {
selector: '#cvv',
type: 'password'
},
expirationDate: {
selector: '#expiration-date',
prefill: "12/21"
}
}
}, this.hostedFieldsDidCreate);
}
hostedFieldsDidCreate(err, hostedFields) {
let submitBtn = document.getElementById('my-submit');
this.setState({hostedFields: hostedFields});
submitBtn.addEventListener('click', this.submitHandler);
submitBtn.removeAttribute('disabled');
}
submitHandler(event) {
let submitBtn = document.getElementById('my-submit');
event.preventDefault();
submitBtn.setAttribute('disabled', 'disabled');
this.state.hostedFields.tokenize(
function (err, payload) {
if (err) {
submitBtn.removeAttribute('disabled');
console.error(err);
}
else {
let form = document.getElementById('my-sample-form');
form['payment_method_nonce'].value = payload.nonce;
alert(payload.nonce);
// form.submit();
}
});
}
render() {
return (
<div className="user-prelogin">
<div className="row gutter-reset">
<div className="col">
<div className="prelogin-container">
<form action="/" id="my-sample-form">
<input type="hidden" name="payment_method_nonce"/>
<label htmlFor="card-number">Card Number</label>
<div className="form-control" id="card-number"></div>
<label htmlFor="cvv">CVV</label>
<div className="form-control" id="cvv"></div>
<label htmlFor="expiration-date">Expiration Date</label>
<div className="form-control" id="expiration-date"></div>
<input id="my-submit" type="submit" value="Pay" disabled/>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default BillingComponent;
I am able to get basic functionalities like getting nonce from card details. But i am unable to display card brand image/error message in the page as we show in Drop in UI.
How to show card brand image and error message using hosted fields?
Page created using Hosted fields:
Page created Drop in UI - Which shows error message
Page created Drop in UI - Which shows card brand
Though we do not get exact UI like Drop in UI, we can get the card type and display it ourselves by using listeners on cardTypeChange.
hostedFieldsDidCreate(err, hostedFields) {
this.setState({hostedFields: hostedFields});
if (hostedFields !== undefined) {
hostedFields.on('cardTypeChange', this.cardTypeProcessor);
hostedFields.on('validityChange', this.cardValidator);
}
this.setState({load: false});
}
cardTypeProcessor(event) {
if (event.cards.length === 1) {
const cardType = event.cards[0].type;
this.setState({cardType: cardType});
} else {
this.setState({cardType: null});
}
}
cardValidator(event) {
const fieldName = event.emittedBy;
let field = event.fields[fieldName];
let validCard = this.state.validCard;
// Remove any previously applied error or warning classes
$(field.container).removeClass('is-valid');
$(field.container).removeClass('is-invalid');
if (field.isValid) {
validCard[fieldName] = true;
$(field.container).addClass('is-valid');
} else if (field.isPotentiallyValid) {
// skip adding classes if the field is
// not valid, but is potentially valid
} else {
$(field.container).addClass('is-invalid');
validCard[fieldName] = false;
}
this.setState({validCard: validCard});
}
Got the following response from braintree support team.
Hosted fields styling can be found in our developer documentation. Regarding the logos, you can download them from the card types official websites -
Mastercard
Visa
AMEX
Discover
JCB
Or online from other vendors.
Note: Drop-In UI will automatically fetch the brand logos and provide validation errors unlike hosted fields as it is less customizable.

SPFx - having trouble read person field from SP list

I'm trying to get data from person field in SharePoint. My code always returns 8 rows (its correct) but at items that consists of Person it returns [object Obejct].
enter image description here
export interface SPUser {
Pracovnik: String;
}
.
.
private getListData(): void {
this._getListData().then((response) => {
this._renderList(response);
});
}
private _renderList(items: SPUser[]): void {
let html: string = '<table class="TFtable" border=1 width=100% style="border-collapse: collapse;">';
html += `<th>Title</th>`;
items.forEach((item: SPUser) => {
if(item.Pracovnik != null) {
html += `
<tr> <td>${item.Pracovnik}</td> </tr>
`};
});
html += `</table>`;
const listContainer: Element = this.domElement.querySelector('#spGetListItems');
listContainer.innerHTML = html;
}
private async _getListData(): Promise<SPUser[]> {
return pnp.sp.web.lists.getByTitle("org_struktura").items.select("Pracovnik/ID").expand("Pracovnik").get().then((response) => {
return response;
});
}
public render(): void {
this.domElement.innerHTML = `
<div class="parentContainer" style="background-color: lightgrey">
<div style="background-color: lightgrey" id="spGetListItems" />
</div>
`;
this.getListData();
}
Any idea what is wrong please?
Sample demo to map SharePoint list item to a Type object.
export interface IReactItem{
Id:number,
Title:string,
Description:string,
User:{ID:number,EMail:string},
enableEdit:boolean
}
private async _getListData(): Promise<IReactItem[]> {
return pnp.sp.web.lists.getByTitle("TestList").items.select("Id,Title,Description,User/ID,User/EMail").expand("User").get().then((response:IReactItem[]) => {
return response;
});
}