I want create User, which hold Foreign Key to his Country.
Obviously I want to make it required field.
But when I send POST request without 'country' field, DRF Serializer doesn't throw an error.
Nowhere in the code have I allowed the field to be empty, it's the same as the rest.
I took a step further, and in the create() method of my ModelViewSet I decided to print serializer.validated_data
class UserViewSet(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
permission_classes = []
def create(self, request, format=None):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
print(serializer.data)
I send following POST request:
And my serializer.validated_data value was:
OrderedDict([('password', 'test1231112'), ('first_name', 'Jarosław'), ('last_name', 'Psikuta'), ('phone', '2999111331'), ('email', 'rweww#gmail.css'), ('description', 'fajny uzytkownik'), ('address', 'Mieszka 3/4'), ('date_of_birth', datetime.date(1997, 10, 13))])
I realised that serializer just don't see my country field.
I already write some code to check if my country field exist:
country_code = request.data.get('country', None)
if not country_code:
return Response({'country': 'This field is required'}, status=status.HTTP_400_BAD_REQUEST)
but I know that's wrong approach. It's additional code, and actually serializer should do that work for me.
Here You have rest of my code:
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
read_only_fields = ('id', 'last_login', 'is_superuser', 'is_staff', 'is_active','date_joined', 'groups', 'user_permissions')
extra_kwargs = {
'password': {'write_only': True}
}
depth = 1
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
return user
models.py
class User(AbstractUser):
EMPLOYEE_TYPES = [
('R', 'Regular'),
('S', 'Specialist')
]
username_validator = None
username = None
first_name = models.CharField(_('first name'), max_length=30)
last_name = models.CharField(_('last name'), max_length=150)
phone = models.CharField(max_length=15, unique=True)
employee_type = models.CharField(max_length=1, choices=EMPLOYEE_TYPES, default='R')
email = models.EmailField(_('email address'), unique=True, max_length=255)
description = models.CharField(blank=True, max_length=500, default='')
address = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='users')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name', 'date_of_birth', 'address', 'phone', 'country']
I deleted depth = 1 from serializer Metaclass and it works.
Related
When I run api request I get a following error:
AttributeError: Got AttributeError when attempting to get a value for field email on serializer UserSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the tuple instance.
Original exception text was: 'tuple' object has no attribute 'email'.
New user gets inserted in database anyway, email field is filleld properly.
View:
class Register(APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
return Response(serializer.data, status=status.HTTP_201_CREATED)
Serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email', 'username', 'name', 'password']
def create(self, validated_data):
user = User.objects.create_user(**validated_data),
return user
Model:
class User(AbstractBaseUser):
email = models.EmailField(max_length=254, unique=True)
username = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=60)
date_of_birth = models.DateField(blank=True, null=True)
bio = models.CharField(default='', max_length=10000)
photo = models.ImageField(max_length=255, null=True, blank=True)
email_verified_at = models.DateTimeField(null=True, blank=True)
email_token_time = models.DateTimeField(null=True, blank=True)
email_token = models.CharField(default='', max_length=64)
password_token_time = models.DateTimeField(null=True, blank=True)
password_token = models.CharField(default='', max_length=64)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_seen = models.DateTimeField(null=True, blank=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'name']
objects = UserManager()
class Meta:
db_table = "User"
def __str__(self):
return self.username
I also have custom user manager, but that is probably irrelevant, and works as user does get inserted to database.
You have typo in this line:
user = User.objects.create_user(**validated_data),
It contains comma , in the last of line. So user become a tuple of user instance, not just user instance. It become (user,).
Should return user instance.
I have a TwitchChannel model which has a ForeignKey relationship to CustomUser.
class TwitchChannel(models.Model):
login = models.CharField(max_length=25)
display_name = models.CharField(max_length=25)
twitch_user_id = models.CharField(max_length=50)
email = models.EmailField(null=True, blank=True)
profile_image_url = models.URLField(null=True, blank=True)
access_token = models.CharField(default="none", max_length=100)
refresh_token = models.CharField(default="none", max_length=100)
live = models.BooleanField(default=False)
channel_data = JSONField()
created = models.DateTimeField(auto_now=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True)
def save(self, **kwargs):
make_password(self.access_token)
make_password(self.refresh_token)
super().save(**kwargs)
def __str__(self):
return self.display_name
def get_channel_url(self):
return f"https://twitch.tv/{self.login}"
In my UserSerializer I want to include this TwitchChannel when it exists. The following are my serializers.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
twitch_channel = TwitchChannelSerializer(read_only=True, many=True)
def create(self, validated_data):
user = UserModel.objects.create(
email=validated_data["email"],
first_name=validated_data["first_name"],
last_name=validated_data["last_name"]
)
user.set_password(validated_data["password"])
user.save()
return user
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
class Meta:
model = UserModel
depth = 3
fields = ( "id", "first_name", "last_name", "email", "password", "twitch_channel")
extra_kwargs = {"password": {"write_only": True, }}
class TwitchChannelSerializer(serializers.Serializer):
class Meta:
model = TwitchChannel
fields = (
'user_id',
'login',
'display_name',
'email',
'profile_image_url',
'access_token',
'refresh_token',
'live',
'channel_data',
'created',
'user'
)
However, when I do a request for the user the field isn't even included at all.
{
"id": 3,
"first_name": "Patrick",
"last_name": "Hanford",
"email": "testing#streambeacon.tv"
}
I get no error, but the field is non-existent.
You can use source argument to specify model's field name. By default django use modelname_set name for reverse foreign key relation, so you can use this:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
twitch_channel = TwitchChannelSerializer(read_only=True, many=True, source="twitchchannel_set")
Also you need to use ModelSerializer for both classes:
class TwitchChannelSerializer(serializers.ModelSerializer):
I am trying to implement simple api in Django Rest Framework.
I have following models in models.py:
class Entry(BaseModel):
company_name = models.CharField(max_length=256, null=True, blank=True)
first_name = models.CharField(null=True, default=None, max_length=32)
last_name = models.CharField(null=True, default=None, max_length=32)
code = models.CharField(null=True, default=None, max_length=12)
class Meta:
db_table = 'entry'
class Admin(admin.ModelAdmin):
list_display = ('company_name', 'code')
list_display_links = ('company_name', )
ordering = ('-created',)
class EntryContactData(BaseModel):
entry = models.ForeignKey(Entry, related_name='contact')
email = models.CharField(max_length=256, null=True, blank=True)
website = models.CharField(max_length=64, null=True, blank=True)
phone = models.CharField(max_length=64, null=True, blank=True)
My API serializers.py:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from core.models import Entry, EntryContactData
class EntryContactSerializer(serializers.ModelSerializer):
class Meta:
model = EntryContactData
fields = ('uuid', 'email', 'website', 'phone')
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(many=False, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')
And my API views:
from core.models import Entry
from .serializers import EntrySerializer
class EntryViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
queryset = Entry.objects.all()
def retrieve(self, request, pk=None):
queryset = Entry.objects.all()
entry = get_object_or_404(queryset, code=pk)
serializer = EntrySerializer(entry, context={'request': request})
return Response(serializer.data)
When I want to retrieve single entry its contact field is empty:
{
"uuid": "e6818508-a172-44e1-b927-3c087d2f9773",
"company_name": "COMPANY NAME",
"first_name": "FIRSTNAME",
"last_name": "LASTTNAME",
"contact": {}
}
So it doesn't contain any of fields defined in EntryContactSerializer
What am I doing wrong? How can I force it to return all fields included in serializer? Thank you guys.
Try setting many=True in EntrySerializer, and provide a source attribute to the serializer,
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(source='contact', many=True, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')
I'm facing a problem using python2.7 with django rest-framework. When I serialize my JSON data, a field is omitted by the serializer and I don't understand why. Here is some details.
The missing field is "country". When I'm doing POST or PUT requests on /campaigns/:id
class CampaignSerializer(serializers.HyperlinkedModelSerializer):
created_by = UserFullSerializer(read_only=True)
country = CountrySerializer(read_only=True)
class Meta:
model = Campaign
fields = ('id', 'created_by', 'name', 'media', 'status', 'begin', 'end', 'country')
class CampaignFullSerializer(serializers.HyperlinkedModelSerializer):
client = ClientSerializer(read_only=True)
collection = CollectionSerializer(read_only=True)
created_by = UserFullSerializer(read_only=True)
updated_by = UserFullSerializer(read_only=True)
channels = ChannelSerializer(read_only=True, many=True)
country = CountrySerializer(read_only=True)
class Meta:
model = Campaign
fields = ('id',
'client',
'name',
'media',
'status',
'begin',
'end',
'created_at',
'created_by',
'updated_at',
'updated_by',
'collection',
'channels',
'country')
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'code')
class Country(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=255)
class Campaign(models.Model):
name = models.CharField(max_length=255)
media = models.IntegerField(choices=constant.MEDIA_CHOICES, default=0)
status = models.IntegerField(choices=constant.STATUS_CHOICES, default=2)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, blank=True, null=True, related_name="created_by")
updated_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
updated_by = models.ForeignKey(User, blank=True, null=True, related_name="updated_by")
client = models.ForeignKey(client.Client)
begin = models.DateField(blank=True, null=True)
end = models.DateField(blank=True, null=True)
collection = models.ForeignKey(collection.Collection, blank=True, null=True)
country = models.ForeignKey(country.Country, blank=True, null=True)
mediaplan = models.ForeignKey(mediaplan.Mediaplan, blank=True, null=True, default=None)
channels = models.ManyToManyField(channel.Channel)
When I'm doing POST on /campaign/id with the following JSON, everything works except the country field.
{
...
"channels": [],
"country": {
"id": 74,
"name": "France",
"code": "FR"
}
On the controller side when I print the request.data I got all the fields. I'm not overriding the create method of the controller.
{
...
u'country': {u'code': u'AL', u'id': 3, u'name': u'Albania'}
}
My controller looks like:
class CampaignViewSet(viewsets.ModelViewSet):
queryset = Campaign.objects.all()
serializer_class = CampaignSerializer
def create(self, request):
logger.info(request.data)
return super(CampaignViewSet, self).create(request, *args, **kwargs)
I tried to override the create method of my CountrySerializer and when I print the content of validated_data, the country field is missing in the OrderedDict..
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'code')
def create(self, validated_data):
logger.info(validated_data)
I'm really lost, I can't find my mistake, maybe you will. Thanks for your time.
Your CountrySerializer is read only as a nested serializer by default (per http://www.django-rest-framework.org/api-guide/relations/#nested-relationships) so you have to override the create/update method of the Campaign serializer for POST/PUT. You've tried to override it on the Country serializer instead.
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)