I am trying to implement AJAX in my forms but I'm not able to submit optional QuerySelectField data to the database.
The field itself is populated with values from another table, it should be optional and hidden for users with lower access level (should ne NULL in such case). If selected, it should insert value of id column corresponding to the selected entry.
The problem is that AJAX seems to completely ignore it and always submits it as NULL.
I am completely new to this, tried searching for possible solution but couldn't find anything.
my forms.py:
class DodajAdmin(FlaskForm):
name = StringField(_l('Imię'), validators=[DataRequired()])
surname = StringField(_l('Nazwisko'), validators=[DataRequired()])
email = StringField(_l('Email'), validators=[DataRequired(), Email()])
phone = StringField(('Nr telefonu'), validators=[DataRequired(),
Length(min=8, max=12, message='Nieprawidłowy nr telefonu')])
PESEL = StringField(('PESEL'), validators=[DataRequired(), Length(min=11, max=11, message='Nieprawidłowy PESEL')])
pkk = StringField(('Numer PKK'), validators=[DataRequired(), Length(min=20, max=20, message='PKK musi składać się z dokładnie 20 znaków')])
res_city = StringField(('Miasto zamieszkania'), validators=[DataRequired()])
res_address = StringField(('Ulica i nr domu/mieszkania'), validators=[DataRequired()])
notes = StringField(('Uwagi'))
submit = SubmitField(_l('Dodaj kursanta'))
def validate_pkk(self, pkk):
kursant = Kursant.query.filter_by(PKK=pkk.data).first()
if kursant is not None:
raise ValidationError(_('Kursant o podanym numerze PKK już istnieje!'))
def validate_email(self, email):
kursant = Kursant.query.filter_by(email=email.data).first()
if kursant is not None:
raise ValidationError(_('Kursant o podanym adresie email już istnieje!'))
def validate_pesel(self, PESEL):
kursant = Kursant.query.filter_by(PESEL=PESEL.data).first()
if kursant is not None:
raise ValidationError(_('Kursant o podanym numerze PESEL już istnieje!'))
def instructors():
return User.query.all()
def getformclass():
formclass = DodajAdmin
if current_user.access_level > 1 :
setattr(formclass, "instructor", QuerySelectField('Instruktor prowadzący', query_factory=instructors, blank_text='Brak (jeszcze nie wybrano)', allow_blank=True))
return formclass
routes.py
#bp.route('/students/add/', methods=['GET', 'POST'])
#login_required
def dodaj_kursanta():
form_class = getformclass()
form = form_class()
access_level = User.query.filter(User.id == current_user.id)
icon = 'fas fa-user-plus'
if request.method == 'POST':
if form.validate():
if current_user.access_level > 1:
kursant = Kursant(name=form.name.data, surname=form.surname.data, email=form.email.data, PKK=form.pkk.data, \
PESEL=form.PESEL.data, phone=form.phone.data, res_city=form.res_city.data, res_address=form.res_address.data, instruktor=form.instructor.data, notes=form.notes.data, added_by=current_user.id)
kursant = Kursant(name=form.name.data, surname=form.surname.data, email=form.email.data, PKK=form.pkk.data, \
PESEL=form.PESEL.data, phone=form.phone.data, res_city=form.res_city.data, res_address=form.res_address.data, added_by=current_user.id)
db.session.add(kursant)
db.session.commit()
return '<div id="success-message" class="alert alert-success">Pomyślnie dodano kursanta.</div>'
return jsonify(form.errors), 400
return render_template('pages/dodaj-kursanta.html', title='Dodaj kursanta', form=form, icon=icon)
template
<form id="form" method="POST">
{{ form.csrf_token }}
<div class="text-danger my-2" id="csrf_token-error">
</div>
<div class="form-group">
{{ form.name(class="form-control", placeholder="Imię") }}
<div id="name-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.surname(class="form-control", placeholder="Nazwisko") }}
<div id="surname-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.email(class="form-control", placeholder="Adres email") }}
<div id="email-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.phone(class="form-control", type="number", placeholder="Telefon") }}
<div id="phone-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.PESEL(class="form-control", type="number", placeholder="PESEL") }}
<div id="pesel-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.pkk(class="form-control", placeholder="PKK") }}
<div id="pkk-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.res_city(class="form-control", placeholder="Miasto") }}
<div id="res_city-error" class="invalid-feedback"></div>
</div>
<div class="form-group">
{{ form.res_address(class="form-control", placeholder="Ulica/Dom") }}
<div id="res_address-error" class="invalid-feedback"></div>
</div>
{% if current_user.access_level > 1 %}
<div class="form-group">
<label class="my-1 me-2">Instruktor prowadzący</label>
{{ form.instructor(class="form-control") }}
</div>
{% endif %}
<div class="form-group">
{{ form.submit(class="btn-lg btn-primary", style="width: 100%") }}
</div>
</form>
<script>
const form = document.getElementById('form');
const successMessage = document.getElementById('success-message');
const fields = {
csrf_token: {
input: document.getElementById('csrf_token'),
error: document.getElementById('csrf_token-error')
},
name: {
input: document.getElementById('name'),
error: document.getElementById('name-error')
},
surname: {
input: document.getElementById('surname'),
error: document.getElementById('surname-error')
},
email: {
input: document.getElementById('email'),
error: document.getElementById('email-error')
},
phone: {
input: document.getElementById('phone'),
error: document.getElementById('phone-error')
},
PESEL: {
input: document.getElementById('PESEL'),
error: document.getElementById('pesel-error')
},
pkk: {
input: document.getElementById('pkk'),
error: document.getElementById('pkk-error')
},
res_city: {
input: document.getElementById('res_city'),
error: document.getElementById('res_city-error')
},
res_address: {
input: document.getElementById('res_address'),
error: document.getElementById('res_address-error')
},
instructor: {
input: document.getElementById('instructor'),
error: document.getElementById('instructor-error')
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch('/students/add/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
csrf_token: fields.csrf_token.input.value,
name: fields.name.input.value,
surname: fields.surname.input.value,
email: fields.email.input.value,
phone: fields.phone.input.value,
PESEL: fields.PESEL.input.value,
pkk: fields.pkk.input.value,
res_city: fields.res_city.input.value,
res_address: fields.res_address.input.value,
instructor: fields.instructor.input.value
})
});
if (response.ok) {
successMessage.innerHTML = await response.text();
form.style.display = 'none';
successMessage.style.display = 'block';
} else {
const errors = await response.json();
Object.keys(errors).forEach((key) => {
fields[key].input.classList.add('is-invalid');
fields[key].error.innerHTML = errors[key][0];
});
}
});
</script>
Ok, I've managed to resolve the issue although I'm not certain why it behaved like described in the first place.
Turns out that moving second occurrence of variable 'kursant' in routes.py into else condition (see below) caused the form to pass all information correctly. For some reason it was attempting to insert the data twice within a single db session.
updated routes.py
#bp.route('/students/add/', methods=['GET', 'POST'])
#login_required
def dodaj_kursanta():
form_class = getformclass()
form = form_class()
access_level = User.query.filter(User.id == current_user.id)
icon = 'fas fa-user-plus'
if request.method == 'POST':
if form.validate():
if current_user.access_level > 1:
kursant = Kursant(name=form.name.data, surname=form.surname.data, email=form.email.data, PKK=form.pkk.data, \
PESEL=form.PESEL.data, phone=form.phone.data, res_city=form.res_city.data, res_address=form.res_address.data, instruktor=form.instructor.data, notes=form.notes.data, added_by=current_user.id)
else:
kursant = Kursant(name=form.name.data, surname=form.surname.data, email=form.email.data, PKK=form.pkk.data, \
PESEL=form.PESEL.data, phone=form.phone.data, res_city=form.res_city.data, res_address=form.res_address.data, added_by=current_user.id)
db.session.add(kursant)
db.session.commit()
return '<div id="success-message" class="alert alert-success">Pomyślnie dodano kursanta.</div>'
return jsonify(form.errors), 400
return render_template('pages/dodaj-kursanta.html', title='Dodaj kursanta', form=form, icon=icon)
<template>
<div id="profile" class="collection with-header">
<div class="profview">
</div>
<div class="search blue">
<div class="input-field">
<div class="input-field">
<i class="material-icons prefix"></i>
<input type="text" id="usernames" v-model="usernames" />
<label class="white-text" for="email">Enter username</label>
</div>
<button
v-on:click="followlist"
class="btn btn-large btn-extended grey lighten-4 black-text"
>
Search
</button>
</div>
</div>
<div class="list right red">
{{ word }}
<li v-for="user in users" v-bind:key="user.id" class="collection-item">
<button v-on:click="followlist">{{user.name}}</button>
<!--<button v-on:click="seeoptions = !seeoptions">{{user.name}}</button>-->
<div id="options" v-if="seeoptions">
<li><button>Follow</button></li>
<li><button>Block</button></li>
<li><button>View Profile</button></li>
</div>
</li>
</div>
</div>
</template>
<style>
template{
background-color: blueviolet
}
</style>
<script>
//lets see if we can implement the search function using login as
// a template, change data to match with what u need
import db from './firebaseInit'
import firebase from "firebase";
var auth = firebase.auth();
var exists = false;
export default {
name: "followlist",
data: function () {
return {
users: [],
loading: true,
usernames: "",
word: 'This is a friends list',
content: 'Info of User',
content2: 'A search bar',
//seeoptions1: false,
// seeoptions2: false,
//seeoptions3: false
}
},
methods:{
followlist: function(e) {
db.collection('users').get().then((querySnapshot) =>{
this.loading = false
//console.log("succes");
//let inputtedname = document.getElementById("username").value;
//let stringName = inputtedname.toString()
querySnapshot.forEach((doc) => {
// console.log("succes");
// console.log(stringName);
var tempname = this.usernames;
var temp = doc.data().username;
// console.log(this.username);
// console.log(temp);
var curruser = this.username;
if(tempname == temp){
console.log(doc.data().username)
}
else
console.log("dont know")
})
// console.log(stringName)
})
},
},
created () {
// Boolean userExists = false;
db.collection('users').orderBy('dept').get().then((querySnapshot) => {
this.loading = false
querySnapshot.forEach((doc) => {
const data = {
'id': doc.id,
'name': doc.data().name,
'dept': doc.data().dept,
//'position': doc.data().position
}
this.users.push(data)
})
})
}
}
</script>
ignore all the commented out console logs, anyways, i tried setting both to a variable, i tried doing it by straight comparing this.username to the one in database. its getting the username from the database correctly, ive console logged it and it worked fine, outputted all the usernames from database. however at the if statement, it will not compare them correctly if they are matches. also ignore the bottom created () section, its nnot doing anything currently. just was used to test
Its probably due to a space in one of the string.
if(tempname.trim() == temp.trim()) {
console.log(doc.data().username)
}
How do you pass in the post id in angular if your using laravel ?
this is what i currently have, i tried referencing this
https://docs.angularjs.org/api/ng/service/$http
but im not really understanding.
Main.js
$scope.like = function() {
var post = {
// this doesn't work and i dont know how to pull in the post id.
id: "<% $post->id %>"
};
$http.post('post/like/'+ post).success(function(result) {
checkLike();
});
};
function checkLike(){
var post = {
id: "<% $post->id %>"
};
$http.get('post/'+ post '/islikedbyme').success(function(result) {
if (result == 'true') {
$scope.like_btn_text = "Delete Like";
} else {
$scope.like_btn_text = "Like";
}
});
};
Route
Route::get('post/{id}/islikedbyme', 'PostController#isLikedByMe');
Route::post('post/like', 'PostController#like');
Controller
public function isLikedByMe($id)
{
$post = Post::findOrFail($id)->first();
if (Like::whereUserId(Auth::id())->wherePostId($post->id)->exists()){
return 'true';
}
return 'false';
}
public function like(Post $post)
{
$existing_like = Like::withTrashed()->wherePostId($post->id)->whereUserId(Auth::id())->first();
if (is_null($existing_like)) {
Like::create([
'post_id' => $post->id,
'user_id' => Auth::id()
]);
} else {
if (is_null($existing_like->deleted_at)) {
$existing_like->delete();
} else {
$existing_like->restore();
}
}
}
this work i got the post id by using ng-init to pass in post id.
Html
<div id="mypost" class="col-md-8 panel-default" ng-repeat="post in myposts ">
<div id="eli-style-heading" class="panel-heading"><% post.user.name %></div> // i need someway to pull in the post id. so i used ng-init not sure if this best practices.
<div class="panel-body panel" ng-init="getL(post)">
<i style="color:tomato; float:right; font-size:24px;" ng-click="like(post)" class="glyphicon glyphicon-heart"></i>
<figure>
<p ng-model="post.body" editable-text="post.body" e-form="textBtnForm"> <% post.body %></p>
<p name="created_at" ng-model="post.created_at"> <% post.user.created_at | phpDate : "human" %></p>
</figure>
<span>
<i style="color:red;" class="glyphicon glyphicon-remove" ng-click="deletePost(post)" ng-if="post.deletable"></i>
<button ng-if="post.update" class="btn btn-default" ng-click="textBtnForm.$show()" ng-hide="textBtnForm.$visible">
Edit
</button>
<span><button ng-if="post.update" type="submit" class="btn btn-primary" ng-click="updatePost(post)">Update</button></span>
</span>
</div>
</div>
Main.js
$scope.like = function(post) {
$http.post('/post/like/'+ post.id).then(function(result) {
getL();
});
};
$scope.getL = function(post){
$http.get('/post/'+ post.id +'/islikedbyme').then(function(result) {
if (result == 'true') {
$scope.like_btn_text = "Delete Like";
} else {
$scope.like_btn_text = "Like";
}
});
}
Route
Route::get('post/{id}/islikedbyme', 'PostController#isLikedByMe');
Route::post('post/like/{post}', 'PostController#like');
Post Controller
public function like(Post $post, Request $request)
{
$existing_like = Like::withTrashed()->wherePostId($post->id)->whereUserId(auth()->id())->first();
if (is_null($existing_like)) {
Like::create([
'post_id' => $post->id,
'user_id' => auth()->user()->id
]);
} else {
if (is_null($existing_like->deleted_at)) {
$existing_like->delete();
} else {
$existing_like->restore();
}
}
}
I'm using mean.js to create a system and I change the mongoose part for sequelize and I trying to save multiple Objects from Angular to my database through sequelize.
I followed this answer to create multiple inputs dynamically on the Dia (Day) option for multiple schedules.
And I have my controller like this:
$scope.horarios = [];
$scope.itemsToAdd = [{
Day: '',
StartHour: '',
EndHour: ''
}];
$scope.add = function(itemToAdd) {
var index = $scope.itemsToAdd.indexOf(itemToAdd);
$scope.itemsToAdd.splice(index, 1);
$scope.horarios.push(angular.copy(itemToAdd))
};
$scope.addNew = function() {
$scope.itemsToAdd.push({
Day: '',
StartHour: '',
EndHour: ''
});
console.log($scope.itemsToAdd);
};
and view
<div class="col-xs-12" style="padding: 0" ng-repeat="itemToAdd in itemsToAdd">
<div class="form-group col-xs-12 col-sm-5" >
<label for="Days">Dia</label> <select class="form-control col-xs-12 col-sm-6" data-ng-model="itemToAdd.Day" id="Days" name="Days">
<option value="">---Seleccione uno---</option>
....
</select>
</div>
<div class="form-group col-xs-5 col-sm-3">
<label class="control-label" for="startHour">Hora Inicio</label> <input class="form-control" id="startHour" name="startHour" ng-model="itemToAdd.StartHour" type="time">
</div>
<div class="form-group col-xs-5 col-sm-3">
<label class="control-label" for="endHour">Hora Termino</label> <input class="form-control" id="endHour" name="endHour" ng-model="itemToAdd.EndHour" type="time">
</div>
<div class="col-xs-2 col-sm-1">
<button ng-click="addNew()" class="btn btn-success" style="position: relative; top:26px"><i class="glyphicon glyphicon-plus"></i></button>
</div>
</div>
Then I have my both controllers on client side with Angular:
// Create new course
$scope.create = function( isValid ) {
// Create new course object
var course = new Courses( $scope.course );
course.Schedule = $scope.itemsToAdd;
console.log($scope.course);
// Redirect after save
course.$save( function( response ) {
$scope.closeThisDialog();
notify( 'Genial. El Curso ha sido registrada exitosamente' );
// Clear form fields
$scope.course = '';
$scope.schedule = '';
}, function( errorResponse ) {
$scope.error = errorResponse.data.message;
} );
};
And sequelize:
exports.create = function(req, res) {
var schedule = req.body.Schedule;
req.body.schedule = undefined;
// req.body.userId = req.user.id;
db.Course.create(req.body)
.then(function(course) {
if (!course) {
return res.send('users/signup', {
errors: 'Could not create the course'
});
} else {
schedule.CourseId = course.dataValues.id;
db.Schedule.create(schedule)
.then(function(schedule) {
for (var i = schedule.dataValues.length - 1; i >= 0; i++) {
course.schedule = schedule.dataValues[i];
}
// course.schedule = schedule.dataValues;
})
.catch(function(err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
});
return res.jsonp(course);
}
})
.catch(function(err) {
return res.status(400)
.send({
message: errorHandler.getErrorMessage(err)
});
});
};
But honestly I don't have a clue how to save it or if my Angular controller is even the correct way to do it. Hope you can help me or give me hint how to do it.
In addition to updating a single instance, you can also create, update, and delete multiple instances at once. The functions you are looking for are called
Model.bulkCreat
http://docs.sequelizejs.com/en/2.0/docs/instances/#working-in-bulk-creating-updating-and-destroying-multiple-rows-at-once
I have an app build on angularjs, and laravel and for authentication I use Satellizer.
Currently the login work, but it only return display name. Here is the code:
satellizer.js
providers: {
facebook: {
name: 'facebook',
url: '/auth/facebook',
authorizationEndpoint: 'https://www.facebook.com/v2.3/dialog/oauth',
redirectUri: (window.location.origin || window.location.protocol + '//' + window.location.host) + '/',
requiredUrlParams: ['scope'],
scope: ['email'],
scopeDelimiter: ',',
display: 'popup',
type: '2.0',
popupOptions: { width: 580, height: 400 }
},
account.js
angular.module('MyApp')
.factory('Account', function($http) {
return {
getProfile: function() {
return $http.get('/api/me');
},
updateProfile: function(profileData) {
return $http.put('/api/me', profileData);
}
};
});
profile.js
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">Profile</div>
<div class="panel-body">
<legend><i class="ion-clipboard"></i> Edit My Profile</legend>
<form method="post" ng-submit="updateProfile()">
<div class="form-group">
<label class="control-label">Profile Picture</label>
<img class="profile-picture" ng-src="{{user.picture || 'http://placehold.it/100x100'}}">
</div>
<div class="form-group">
<label class="control-label"><i class="ion-person"></i> Display Name</label>
<input type="text" class="form-control" ng-model="user.displayName" />
</div>
<div class="form-group">
<label class="control-label"><i class="ion-at"></i> Email Address</label>
<input type="email" class="form-control" ng-model="user.email" />
</div>
<button class="btn btn-lg btn-success">Update Information</button>
</form>
</div>
</div>
auth controller in laravel php
public function facebook(Request $request)
{
$accessTokenUrl = 'https://graph.facebook.com/v2.3/oauth/access_token';
$graphApiUrl = 'https://graph.facebook.com/v2.3/me';
$params = [
'code' => $request->input('code'),
'client_id' => $request->input('clientId'),
'redirect_uri' => $request->input('redirectUri'),
'client_secret' => Config::get('app.facebook_secret')
];
$client = new GuzzleHttp\Client();
// Step 1. Exchange authorization code for access token.
$accessToken = $client->get($accessTokenUrl, ['query' => $params])->json();
// Step 2. Retrieve profile information about the current user.
$profile = $client->get($graphApiUrl, ['query' => $accessToken])->json();
// Step 3a. If user is already signed in then link accounts.
if ($request->header('Authorization'))
{
$user = User::where('facebook', '=', $profile['id']);
if ($user->first())
{
return response()->json(['message' => 'There is already a Facebook account that belongs to you'], 409);
}
$token = explode(' ', $request->header('Authorization'))[1];
$payload = (array) JWT::decode($token, Config::get('app.token_secret'), array('HS256'));
$user = User::find($payload['sub']);
dd($user);
$user->facebook = $profile['id'];
$user->displayName = $user->displayName || $profile['name'];
$user->save();
return response()->json(['token' => $this->createToken($user)]);
}
// Step 3b. Create a new user account or return an existing one.
else
{
$user = User::where('facebook', '=', $profile['id']);
if ($user->first())
{
return response()->json(['token' => $this->createToken($user->first())]);
}
$user = new User;
$user->facebook = $profile['id'];
$user->displayName = $profile['name'];
$user->save();
return response()->json(['token' => $this->createToken($user)]);
}
}
Thanks!
In a new Facebook Graph API you should include email and other things you want to get in Graph Api url, take a look at this: https://developers.facebook.com/docs/graph-api/using-graph-api/v2.5
So in your case the solution will be to update your Api urls like this:
Update:
authorizationEndpoint: 'https://www.facebook.com/v2.3/dialog/oauth',
With:
authorizationEndpoint: 'https://www.facebook.com/v2.5/dialog/oauth',
And update this:
$accessTokenUrl = 'https://graph.facebook.com/v2.3/oauth/access_token';
$graphApiUrl = 'https://graph.facebook.com/v2.3/me';
With this:
$accessTokenUrl = 'https://graph.facebook.com/v2.5/oauth/access_token';
$graphApiUrl = 'https://graph.facebook.com/v2.5/me?fields=id,name,email,picture,first_name,last_name';