Django REST Framework : Serializer field level validation of OneToOne fields - python

I am creating a view that handles a new Customer registration. My Customer model OneToOne links to Person which again OneToOne links to User where User is AUTH_USER_MODEL. When I send a POST request for customer registration through the register_customer view, person_serialized.is_valid() returns {"user":["This field is required."]} as the user object is not available and passed on the PersonSerializer. I think there should be a better and cleaner to do what I am doing here. Any solutions?
models.py
class User(AbstractBaseUser, PermissionsMixin):
USERNAME_FIELD = 'mobile_number'
# Form validation errors
mobile_number_errors = {'required': 'Mobile number is required',
'invalid': 'Enter a valid 10 digit mobile number' +
'without spaces, + or isd code.'}
_mobile_regex_validator = RegexValidator(regex=r"^\d{10}$",
message="Phone number must be 10 digits without + or spaces.")
mobile_number = models.CharField("Mobile Number", max_length=10,
validators=[_mobile_regex_validator],
blank=False, null=False, unique=True,
error_messages=mobile_number_errors)
is_active = models.BooleanField("Is Active?", default=True)
is_staff = models.BooleanField("Is Staff?", default=False)
created = models.DateTimeField("Account created on", auto_now_add=True, blank=True, null=True)
modified = models.DateTimeField("Last Modified on", auto_now=True, blank=True, null=True)
objects = UserManager()
def __unicode__(self):
return self.mobile_number
def get_full_name(self):
return self.mobile_number
def get_short_name(self):
return self.mobile_number
class Person(models.Model):
user = models.OneToOneField('users.User')
GENDER_CHOICES = (('M', 'Male'),
('F', 'Female'),
('N', 'Not Specified'))
first_name = models.CharField("First Name", max_length=32, blank=False, null=False)
last_name = models.CharField("Last Name", max_length=32, blank=False, null=False)
gender = models.CharField("Gender", max_length=1, choices=GENDER_CHOICES, blank=False, default='N')
class Customer(models.Model):
person = models.OneToOneField('users.Person')
email_errors = {'required': 'Email field is required.',
'invalid': 'Enter a valid email id.'}
email = models.EmailField("Email", blank=False, null=False, unique=True,
error_messages=email_error
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'mobile_number',)
class PersonSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Person
fields = ('id', 'user', 'first_name', 'last_name', 'gender',)
class CustomerSerializer(serializers.ModelSerializer):
person = PersonSerializer()
class Meta:
model = Customer
fields = ('id', 'person', 'email',)
views.py
from django.shortcuts import render
from .serializers import UserSerializer, PersonSerializer, CustomerSerializer
from .models import User, Person, Customer
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
# Create your views here.
#api_view(['POST'])
def register_customer(request):
user_serialized = UserSerializer(data=request.data)
person_serialized = PersonSerializer(data=request.data)
customer_serialized = CustomerSerializer(data=request.data)
if user_serialized.is_valid() and person_serialized.is_valid() and customer_serialized.is_valid():
user = User.objects.create_user(mobile_number=user_serialized.data['mobile_number'],
password=user_serialized.init_data['password'])
person = Person(user=user, first_name=person_serialized.data['first_name'],
last_name=person_serialized.data['last_name'],
gender=person_serialized.data['gender'])
person.save()
customer = Customer(person=person, email=customer_serialized.data['email'])
customer.save()
return Response(request.data, status=status.HTTP_201_CREATED)
else:
return Response(person_serialized._errors, status=status.HTTP_400_BAD_REQUEST)

Related

Django User model saves twice into database

I am trying to create multi user registration system with Django. However, anytime I call the save() method to save a User type, it saves into the User table twice. The funny thing about the second model that is saved is that many required fields are empty.
I am using a custom user model that I created from AbstractBaseUser. I also rewrote the forms for the CustomUser model. For the multiple user types, I am using a profile model (Student model has a OneToOne field to the user model)
models.py:
class User(AbstractBaseUser, PermissionsMixin):
# I've removed some unimportant code here
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
class Types(models.TextChoices):
STUDENT = 'STUDENT', 'Student'
DEPARTMENT_USER = 'DEPARTMENT_USER', 'Department user'
ADMIN = 'ADMIN', 'Admin'
user_type = models.CharField(_('Type'), max_length=50, choices=Types.choices, default=Types.STUDENT)
first_name = models.CharField(_('First name'), max_length=70, blank=False, default="")
middle_name = models.CharField(_('Middle name'), max_length=70, blank=True, default="")
last_name = models.CharField(_('Last name'), max_length=70, blank=False, default="")
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False) # a admin user; non super-user
is_superuser = models.BooleanField(default=False) # a superuser
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['user_type', 'first_name', 'last_name'] # Email & Password are required by default.
objects = UserManager()
class Meta:
verbose_name = ('user')
verbose_name_plural = ('users')
#db_table = 'auth_user'
abstract = False
class AccountConfirmed(models.Model):
# Model to determine which users have confirmed their email addresses.
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='accountconfirmed')
email_confirmed = models.BooleanField(default=False)
reset_password = models.BooleanField(default=False)
class Meta:
app_label = 'auth'
# When the user model is created, through signals an AccountConfirmed model is also created.
# The email_confirmed and reset_password field is set to false.
#receiver(models.signals.post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
AccountConfirmed.objects.create(user=instance)
instance.accountconfirmed.save()
######################################################
######################################################
class Student(User):
# This is the model class for students
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='students')
matric_number = models.CharField(_('Matriculation number'), max_length=11, blank=False)
department = models.CharField(_('Department'), max_length=40, blank=False)
# has_graduated, level, etc. future possibilities
def __str__(self):
return f'{self.user.email}'
forms.py:
class StudentSignupForm(UserCreationForm):
# first_name = forms.CharField(max_length=70)
# middle_name = forms.CharField(max_length=70, required=False)
# last_name = forms.CharField(max_length=70)
matric_number = forms.CharField(min_length=10, max_length=11, help_text='Your Matric number must be 10 characters')
department = forms.CharField(max_length=40, help_text='e.g Computer Science')
class Meta(UserCreationForm.Meta):
model = User
fields = UserCreationForm.Meta.fields + ('matric_number', 'department')
#transaction.atomic
def save(self, commit=True):
# Save the User instance and get a reference to it
user = super().save(commit=False)
user.user_type = User.Types.STUDENT
user.is_active = False
#if commit:
user.save()
print(f' forms.py {user.email} {user.first_name}')
student = Student.objects.create(user=user, matric_number=self.cleaned_data.get('matric_number'), department=self.cleaned_data.get('department'))
# Add other details
# Return User instance, not Student instance
return user
views.py:
class StudentUserSignupView(CreateView):
model = User
template_name = 'account/signup.html'
form_class = StudentSignupForm
def get_context_data(self, **kwargs):
kwargs['user_type'] = 'STUDENT'
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = form.save()
#login(self.request, user)
send_verification_mail(self, user)
return redirect('verification_sent')
Anytime a user signs up, this is what the students table looks like:
Also, this is what the users table look like after signup (with the multiple saves)
So how do I correct the multiple saves in the user table?
Also, How is it even possible to save a model with most of the required fields empty?
As pointed out by #RaghavKundra, the line below was what caused the problem of saving multiple times to the database
class Student(User):
Instead of that, it should be
class Student(models.Model):

Python - Add custom user models in Django admin not working

I'm working on a project using Python(3.7) and Django(2.2) in which I have implemented my models for multiple user types with custom user model as the base model. Everything working fine except the admin side, I have register these modles to admin but when I try to add an object from admin interface it's giving an error.
Here's what I have tried so far:
From models.py:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
title = models.CharField(max_length=255, blank=False)
user_type = models.CharField(max_length=255, choices=USER_TYPE, blank=False)
gender = models.CharField(max_length=255, choices=CHOICES, blank=False)
contenst = models.CharField(max_length=255, blank=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['password']
objects = UserManager()
def get_absolute_url(self):
return "/users/%i/" % (self.pk)
class PersonalBelow18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
dob = models.DateField(blank=False)
customer_id = models.BigIntegerField(blank=False)
collection_use_personal_data = models.BooleanField(blank=False)
reference_identities = models.ForeignKey(Identities, blank=False, on_delete=models.CASCADE, related_name='refs')
def __str__(self):
return self.user.email+'\'s account with ' + str(self.customer_id)
class PersonalAbove18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField(blank=False)
customer_id = models.BigIntegerField(blank=False)
contact_email = models.EmailField(blank=False)
reference_identities = models.ForeignKey(Identities, blank=False, on_delete=models.CASCADE)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
collection_use_personal_data = models.BooleanField(blank=False)
def __str__(self):
return self.user.email+'\'s account with ' + str(self.customer_id)
class Parent(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contact_email = models.EmailField(blank=False)
customer_id = models.BigIntegerField(blank=True)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
collection_use_personal_data = models.BooleanField(blank=False)
class GroupContactPerson(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contact_email = models.EmailField(blank=False)
customer_id = models.BigIntegerField(blank=False)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
department = models.CharField(max_length=255, blank=False)
address = models.TextField(max_length=255, blank=False)
and here's how I register these models to admin:
From admin.py:
class UserAdmin(BaseUserAdmin):
fieldsets = (
(None, {'fields': ('email', 'password', 'title', 'user_type',
'gender', 'contenst', 'last_login')}),
('Permissions', {'fields': (
'is_active',
'is_staff',
'is_superuser',
'groups',
'user_permissions',
)}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')
}
),
)
list_display = ('email', 'title', 'is_staff', 'last_login')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ('groups', 'user_permissions',)
admin.site.register(User, UserAdmin)
admin.site.register(PersonalBelow18)
admin.site.register(PersonalAbove18)
admin.site.register(Parent)
admin.site.register(GroupContactPerson)
The Parent and GroupContactPerson models are working well on admin side but the when I try to add an object for PersonalBelow18 & PersonalAbove18 models, it gives the following error as:
TypeError: str returned non-string (type int)
Here's how I debug this problem in these models:
I start removing all fields one-by-one
Remove a field from model & form and perform migrations
Then test the admin
Then I found that when I removed the reference_identities field it start working, so I get that this model was returning an integer, so I fixed that model and it fix the issue.
In short, it's a good approach to find a path to the actual place of problem by removing fields one-by-one and test the admin.

How do I make a field optional to enter, in a django ModelForm?

Scenario:
I instantiate a ModelForm and pass it to a template which displays the form. When POST is submitted, code tries to search the database by any of the given inputs. I dont require all inputs to be entered as in the Model. I just need one (or more, if user desires to do an AND search) to be entered.
Question: How can I make any of the ModelForm fields optional, where in the Model, the field isnt optional. The field isnt optional in the Model because I have another ModelForm based on the same Model, where user is required to enter all his details.
My model:
class customer(models.Model):
# Need autoincrement, unique and primary
cstid = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=35)
age=models.IntegerField()
gender_choices = (('male', 'Male'),
('female', 'Female'))
gender = models.CharField(
choices=gender_choices, max_length=10, default='male')
maritalstatus_choices = ( ('married', 'Married'),
('unmarried', 'Unmarried'))
maritalstatus = models.CharField(
choices=maritalstatus_choices, max_length=10, default='Unmarried')
mobile = models.CharField(max_length=15, default='')
alternate = models.CharField(max_length=15, default='')
email = models.CharField(max_length=50, default='', blank=True)
address = models.CharField(max_length=80, default='', blank=True)
city = models.CharField(max_length=25, default='', blank=True)
occupation = models.CharField(max_length=25, default='', blank=True)
bloodgroup_choices = (('apos', 'A+'),
('aneg', 'A-'),
('bpos', 'B+'),
('bneg', 'B-'),
('opos', 'O+'),
('oneg', 'O-'),
('abpos', 'AB+'),
('abneg', 'AB-'),
('unspecified', '-')
)
bloodgroup = models.CharField(choices=bloodgroup_choices, max_length=3, default='-', blank=True)
class Meta:
unique_together = ["name", "mobile", "age"]
def __str__(self):
return self.name
My form:
class CheckinPatientMetaForm(ModelForm):
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]
views.py:
def checkin_patient(request):
results = ''
if request.method == 'POST':
form = CheckinPatientMetaForm(request.POST)
print("POST data",request.POST)
else:
form = CheckinPatientMetaForm()
return render(request, 'clinic/checkin.html', {'rnd_num': randomnumber(), 'form': form, 'SearchResults': results})
As #bdbd mentioned in comments, you can do it by specifying by required=False.
For example, if you want to age field to be optional, add it explicitly as
from django import forms
class CheckinPatientMetaForm(ModelForm):
age = forms.IntegerField(required=False)
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]

Django Rest Framework - object of type 'type' is not json serializable error

I am creating rest api using Django Rest Framework. I am using Djanfo 2.0.5 and python 3. I am getting error "Object of type 'type' is not JSON serializable." at the time of submitting form in browser. I tried with POSTMAN and also getting same error. I have attached snaps of my code of model, serializers and view classes.
from django.db import models
# Create your models here.
class User(models.Model):
full_name = models.CharField(max_length=255, blank=False)
email = models.EmailField(blank=False, unique=True)
password = models.CharField(max_length=100, blank=False)
profile_pic = models.FileField()
age = models.CharField(max_length=3, blank=False)
location_lat = models.CharField(max_length=100, blank=False)
location_long = models.CharField(max_length=100, blank=False)
address = models.CharField(max_length=255, blank=False)
experience_level = models.CharField(max_length=50, blank=False)
utr_rating = models.CharField(max_length=50, blank=False)
match_price = models.IntegerField
auth_token = models.CharField(max_length=100, blank=False, unique=True)
fb_token = models.CharField(max_length=100, unique=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return "{}".format(self.full_name)
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id',
'full_name',
'email',
'password',
'profile_pic',
'age',
'location_lat',
'location_long',
'address',
'experience_level',
'utr_rating',
'match_price',
'auth_token',
'fb_token',
'date_created',
'date_modified')
read_only_fields = ('date_created', 'date_modified', 'profile_pic')
from rest_framework import generics
from .serializers import UserSerializer
from .models import User
class CreateUser(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def perform_create(self, serializer):
serializer.save()
Traceback
This code will work..
class CreateUser(generics.ListCreateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = UserSerializer(data=request.data)
if not serializer.is_valid(raise_exception=False):
return Response({"status":"failure","status_message":serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response({"status":"success","status_message":"User Created Successfully"}, status=status.HTTP_200_OK)

Django: restricting possible dates

I am new to Django. I am using Django 1.8 and Python 3.4.
class Card(models.Model):
STATUS_EXPIRED = "EX"
STATUS_ACTIVE = "AC"
STATUS_DEACTIVATED = "DE"
STATUS_CHOICES = (
(STATUS_ACTIVE, "Active"),
(STATUS_EXPIRED, "Expired"),
(STATUS_DEACTIVATED, "Deactivated")
)
id = models.AutoField(primary_key = True)
series = models.CharField(verbose_name="Series", max_length=8, null=False, blank=False)
number = models.CharField(verbose_name="Number", max_length=16, null=False, blank=False)
issue_date = models.DateTimeField(verbose_name="Issue Date", auto_now=True, null=False, blank=False)
expire_date = models.DateTimeField(verbose_name="Expiry Date", auto_now=False, null=False, blank=False)
status = models.CharField(verbose_name="Status", max_length=3, null=False, blank=False, default="AC")
How do I ensure that expire_date is never less than issue_date in the database? How do I enforce this condition in Django-admin interface when creating objects of Card class?
You can do a form validation in admin like this;
from models import Card
from django.contrib import admin
from django import forms
class CardForm(forms.ModelForm):
class Meta:
model = Card
fields = ('series', 'number', 'issue_date', 'expire_date', 'status')
def clean(self):
issue_date = self.cleaned_data.get('issue_date')
expire_date = self.cleaned_data.get('expire_date')
if expire_date < issue_date:
raise forms.ValidationError("Wrong dates")
return self.cleaned_data
class CardAdmin(admin.ModelAdmin):
form = CardForm
list_display = ('series', 'number', 'issue_date', 'expire_date', 'status')
admin.site.register(Card, CardAdmin)
You can use a custom model form ( Model forms ) and validate the data before saving and while registering do something like
class TestAdmin(admin.ModelAdmin):
form = ModelForm
list_display = ('field1', 'field2')
admin.site.register(Lecture, LectureAdmin)

Categories