In my Django API, I’m able to successfully create admin users as well as normal users. This is the code for my models.
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class CustomUserManager(BaseUserManager):
def create_user(self, phone, password=None, **extra_fields):
if not phone:
raise ValueError('The phone field must be set')
user = self.model(phone=phone, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, phone, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(phone, password, **extra_fields)
class Users(AbstractBaseUser, PermissionsMixin):
unique_code = models.TextField()
fname = models.TextField()
lname = models.TextField()
email = models.EmailField(unique=True)
phone = models.TextField(unique=True)
sex = models.TextField()
country = models.TextField()
date_of_birth = models.TextField()
image = models.TextField(default=None, blank=True, null=True)
district = models.TextField()
subCounty = models.TextField()
village = models.TextField()
number_of_dependents = models.TextField()
family_information = models.TextField()
next_of_kin_name = models.TextField()
next_of_kin_has_phone_number = models.IntegerField(default=None, blank=True, null=True)
next_of_kin_phone_number = models.TextField(default=None, blank=True, null=True)
pwd_type = models.TextField(default=None, blank=True, null=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
REQUIRED_FIELDS = ['unique_code', 'fname', 'lname', 'phone']
USERNAME_FIELD = 'email'
objects = CustomUserManager()
def __str__(self):
return self.phone
I’m using token authentication and this is my code for auth
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.response import Response
from rest_framework import status
from digi_save_vsla_api.auth import PhoneCodeBackend
from digi_save_vsla_api.serializers import LoginSerializer
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token
@csrf_exempt
def login_with_phone_unique_code(request):
if request.method == 'POST':
print('Received POST request data:', request.POST)
serializer = LoginSerializer(data=request.POST)
if serializer.is_valid():
phone = serializer.validated_data["phone"]
code = serializer.validated_data["unique_code"]
backend = PhoneCodeBackend()
user = backend.authenticate(request=request, phone=phone, unique_code=code)
print('User object: ', user)
print('User phone:', phone)
print('User code:', code)
if user is not None:
# user = get_user_model().objects.get(id=user.id)
token, created = Token.objects.get_or_create(user=user)
response_data = {
"status": status.HTTP_200_OK,
'success': True,
"Token": token.key if token else None,
'user': {
'fname': user.fname,
'lname': user.lname,
'email': user.email,
'image': user.image,
'unique_code':user.unique_code,
'phone': user.phone,
'sex': user.sex,
'country': user.country,
'date_of_birth': user.date_of_birth,
'district': user.district,
'subCounty': user.subCounty,
'village': user.village,
'number_of_dependents': user.number_of_dependents,
'family_information': user.family_information,
'next_of_kin_name': user.next_of_kin_name,
'next_of_kin_has_phone_number': user.next_of_kin_has_phone_number,
'next_of_kin_phone_number': user.next_of_kin_phone_number,
'pwd_type': user.pwd_type,
},
}
return JsonResponse(response_data, status=status.HTTP_200_OK)
else:
response = {
"status": status.HTTP_401_UNAUTHORIZED,
"message": "Invalid Email or Password",
}
return JsonResponse(response, status=status.HTTP_401_UNAUTHORIZED)
else:
response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": "Bad request",
"data": serializer.errors
}
return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST)
else:
response = {
"status": status.HTTP_405_METHOD_NOT_ALLOWED,
"message": "Method Not Allowed",
}
return JsonResponse(response, status=status.HTTP_405_METHOD_NOT_ALLOWED)
AND
from django.contrib.auth import get_user_model
from rest_framework.exceptions import AuthenticationFailed
from digi_save_vsla_api.models import Users
class PhoneCodeBackend:
def authenticate(self, request, phone=None, unique_code=None):
try:
user = Users.objects.get(unique_code=unique_code)
except Users.DoesNotExist:
raise AuthenticationFailed('User not found')
# Assuming your User model has a field 'unique_code'
if user.phone != phone:
raise AuthenticationFailed('Invalid phonr number')
return user
def get_user(self, user_id):
try:
return Users.objects.get(pk=user_id)
except Users.DoesNotExist:
return None
When I log in using the admin credentials, I’m able to successfully login and also generate a token. But the issue arises when logging in as a normal registered user, it raises the error below.
Received POST request data: <QueryDict: {'phone': ['+256701391158'], 'unique_code': ['LGZYBL']}>
User object: +256701391158
User phone: +256701391158
User code: LGZYBL
Internal Server Error: /login-with-phone-code/
Traceback (most recent call last):
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/models/query.py", line 916, in get_or_create
return self.get(**kwargs), False
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/models/query.py", line 637, in get
raise self.model.DoesNotExist(
rest_framework.authtoken.models.Token.DoesNotExist: Token matching query does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/backends/base/base.py", line 313, in _commit
return self.connection.commit()
sqlite3.IntegrityError: FOREIGN KEY constraint failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
return view_func(*args, **kwargs)
File "/home/mcrops/Documents/digi_save_api/digi_save_vsla_api/views/auth_view.py", line 28, in login_with_phone_unique_code
token, created = Token.objects.get_or_create(user=user)
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/models/query.py", line 923, in get_or_create
return self.create(**params), True
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/transaction.py", line 263, in __exit__
connection.commit()
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/utils/asyncio.py", line 26, in inner
return func(*args, **kwargs)
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/backends/base/base.py", line 337, in commit
self._commit()
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/backends/base/base.py", line 313, in _commit
return self.connection.commit()
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/utils.py", line 91, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/home/mcrops/Documents/digi_save_api/env/lib/python3.9/site-packages/django/db/backends/base/base.py", line 313, in _commit
return self.connection.commit()
django.db.utils.IntegrityError: FOREIGN KEY constraint failed
[15/Nov/2023 10:05:02] "POST /login-with-phone-code/ HTTP/1.1" 500 120682
This is my client side code
import 'package:flutter/material.dart';
import 'package:intl_phone_number_input/intl_phone_number_input.dart';
import 'package:omulimisa_digi_save_v2/database/constants.dart';
import 'package:omulimisa_digi_save_v2/database/getData.dart';
import 'package:omulimisa_digi_save_v2/database/getMeetings.dart';
import 'package:omulimisa_digi_save_v2/database/userData.dart';
import '/src/view/screens/start_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../database/localStorage.dart';
import '../widgets/start_card.dart';
import '../widgets/user_class.dart';
import 'package:connectivity/connectivity.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class PhoneForm extends StatefulWidget {
const PhoneForm({Key? key}) : super(key: key);
@override
_PhoneFormState createState() => _PhoneFormState();
}
class _PhoneFormState extends State<PhoneForm> {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
final String _initialCountry = 'UG';
final PhoneNumber _number = PhoneNumber(isoCode: 'UG');
final _passwordController = TextEditingController();
final controller = TextEditingController();
String? _country;
String? _phone;
String? _test;
Future<void> saveLoginStatus(bool isLoggedIn) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool('isLoggedIn', isLoggedIn);
}
DatabaseHelper dbHelper = DatabaseHelper.instance;
Future<List<Map<String, dynamic>>?> checkData() async {
final data = dbHelper.getUnsyncedUser();
return data;
}
Future<void> checkLoginStatus() async {
final prefs = await SharedPreferences.getInstance();
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
if (isLoggedIn) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const StartScreen(),
),
);
}
}
Future<void> saveUserData(User user) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('token', user.token);
prefs.setString('userFirstName', user.firstName);
prefs.setString('userLastName', user.lastName);
// prefs.setString('token', user.token!);
}
// Retrieve user data from shared preferences
Future<void> printUserData() async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('token');
final userFirstName = prefs.getString('userFirstName');
final userLastName = prefs.getString('userLastName');
if (token != null && userFirstName != null && userLastName != null) {
print('User ID: $token');
print('User First Name: $userFirstName');
print('User Last Name: $userLastName');
} else {
print('User data not found in shared preferences.');
}
}
void showNoInternetSnackBar(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('No internet connection. Please check your network settings.'),
duration: Duration(seconds: 5), // You can adjust the duration as needed
),
);
}
@override
void initState() {
super.initState();
checkData();
checkLoginStatus();
}
Future<void> loginUser(String phoneNumber, String pinCode) async {
DatabaseHelper dbHelper = DatabaseHelper.instance;
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
showNoInternetSnackBar(context); // Show the SnackBar
return;
}
// Perform the login process if internet is available
final apiUrl = Uri.parse('${ApiConstants.baseUrl}/login-with-phone-code/');
final headers = {'Content-Type': 'application/json'};
final body = json.encode({'phone': phoneNumber, 'unique_code': pinCode});
final Map<String, String> data = {
'phone': phoneNumber,
'unique_code': pinCode,
};
print('JSON: :$body');
print('Here');
final response = await http.post(apiUrl, body: data);
if (response.statusCode == 200) {
// Parse the response data
final Map<String, dynamic> responseData = json.decode(response.body);
print('Response: $responseData');
// // Access user data and token from responseData
// String token = responseData['token'];
Map<String, dynamic> userData = responseData['user'];
// getDataGroupWithApi();
// getDataMeetingWithApi();
String token = responseData['Token'];
String code = userData['unique_code'];
await saveUserData(User(
token: token,
firstName: userData['fname'],
lastName: userData['lname'],
));
syncUserDataWithApi();
int? userId = await dbHelper.getUserIdFromUniqueCode(code);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Welcome, ${userData['fname']} ${userData['lname']} your token is $token!'),
),
);
// print('User ID: ${userData['id']}');
print('First Name: ${userData['fname']}');
print('Last Name: ${userData['lname']}');
// String idString = userData['id'].toString();
// int userId = int.parse(idString);
print('User token: $token');
// Store the token securely
// await storage.write(key: 'token', value: token);
await saveLoginStatus(true);
await saveUserData(User(
id: userId,
token: token,
firstName: userData['fname'],
lastName: userData['lname'],
));
printUserData();
// Now you can use the token and user information as needed
// print('Token: $token');
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const StartScreen(),
),
);
} else {
// Handle errors or display appropriate messages
print('Failed to log in. Status code: ${response.statusCode}');
print('Response body: ${response.body}');
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
children: [
const Align(
alignment: Alignment.center,
child: StartCard(
theWidth: 500.0,
theHeight: 200.0,
borderRadius: 0,
theChild: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(
'DigiSave VSLA Mobile App',
style: TextStyle(
color: Colors.black,
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Enter your phone number and pin to login',
style: TextStyle(
fontSize: 14,
color: Color.fromARGB(255, 0, 20, 1),
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
),
Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Card(
elevation: 4,
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: InternationalPhoneNumberInput(
textStyle: const TextStyle(
color: Colors.black,
),
onInputChanged: (PhoneNumber number) {
setState(() {
_phone = number.phoneNumber;
_country = number.isoCode!;
});
},
onSaved: (PhoneNumber? number) {
if (number != null) {
print('Phone Number Saved: ${number.phoneNumber}');
_test = number.phoneNumber;
}
},
selectorConfig: const SelectorConfig(
selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
),
ignoreBlank: false,
autoValidateMode: AutovalidateMode.disabled,
selectorTextStyle: const TextStyle(color: Colors.black),
initialValue: _number,
textFieldController: controller,
formatInput: true,
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
inputDecoration: const InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.green, width: 2.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.black, width: 2.0),
),
),
),
),
),
const SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.all(16),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3),
),
],
),
child: TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Enter Pin',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
filled: true,
fillColor: Colors.white,
labelStyle: TextStyle(
color: Color.fromARGB(255, 82, 80, 80),
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your pin';
}
return null;
},
),
),
),
const SizedBox(height: 20),
Center(
child: ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
print('Phone number: $_test');
String uniqueCode = _passwordController.text;
print('Full Typed phone is: $_test');
if (_test != null) {
loginUser(_test!, uniqueCode);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Phone number is required.'),
),
);
}
}
},
style: TextButton.styleFrom(
foregroundColor: Colors.black,
backgroundColor: const Color.fromARGB(255, 1, 67, 3),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0))),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 12.0),
child: Text(
'Login',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.0,
color: Colors.white),
),
),
),
),
],
),
),
],
),
);
}
}
Kindly help me out, I don’t know what I’m doing wrong, tried out all solutions on the internet, but I couldn’t solve it.
2
Answers
For those that might encounter similar bug in the future. I was able to solve this by manually deleting my sqlite db, and the running migrations
The error you’re encountering seems to be related to the Token object creation in your Django API. Specifically, it mentions a FOREIGN KEY constraint failure. This usually occurs when you’re trying to create a Token object for a user, but the user referenced by the foreign key doesn’t exist.
Here’s a potential issue in your Django PhoneCodeBackend:
The problem is that you’re trying to find the user using the unique_code field, but you’re not checking if the phone number matches. This can lead to a situation where a user with the correct unique_code is not the one making the request.
Here’s an updated version:
This ensures that you’re checking both the phone number and the unique code when retrieving the user.
Additionally, make sure that the Users model has a valid implementation of the str method, as it is being used in your print statements. If not, you can update it like this:
After making these changes, try logging in with a normal registered user again and see if the issue persists.