I have two models that are related to each other with user_id, now I want to have a get request in which I will have fields from both the tables. How to make this possible? I guess it would be possible with foreign key, but how do I implement it.
Two models look like:
model1
class Account(AbstractBaseUser):
fullname = models.CharField(max_length=100, blank=True)
username = models.CharField(unique=True, max_length=50)
email = models.EmailField(unique=True)
phonenumber = models.IntegerField(null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
is_admin = models.BooleanField(default=False)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
models2
class Profile(models.Model):
User = get_user_model()
branch = models.CharField(max_length=20, null=True)
year = models.IntegerField(null=True)
image = models.ImageField(upload_to="accounts/images/", null=True, blank=True)
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
primary_key=False,
null=True
)
I want to display particular user details based on who is logged in at the moment.
My get request looks something like this:
def get(self, request, format=None):
current_user = request.user
acc = Account.objects.filter(pk=current_user.pk)
serializer = AccountSerializer(acc, many=True)
return Response(serializer.data)
But this will show only data as of Account model, I want data of Profile model too. How do I do it?
Update
serializers
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
return Account.objects.create_user(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.username = validated_data.get('username',
instance.username)
instance.fullname = validated_data.get('fullname',
instance.fullname)
password = validated_data.get('password', None)
instance.save()
return instance
Update2
After having a get requst I want something like:
{
"username": "Steve"
"fullname": "Steve Smith"
"phonenumber": "1234567890"
"email": "st#gmail.com"
"profile":[ {
"branch": "CS"
"year": 4
"image": path/to/folder
}]
}
class ProfileSerializer(ModelSerializer):
user = AccountSerializer(read_only=True)
class Meta:
model = Profile
fields = '__all__'
def get(self, request, format=None):
try:
profile= Profile.objects.get(user=request.user)
except Profile.DoseNotExist:
return Response('404')
serializer = ProfileSerializer(profile)
return Response(serializer.data)
this will return data like:
{
"your profile fields": "xxx"
...
"user": {
"your user fields": "xxx"
...
}
}
if you want your user info include profile info:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = serializers.SerializerMethodField()
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
def get_profile(self, instance):
try:
profile= Profile.objects.get(user=instance)
return ProfileSerializer(profile).data
except Profile.DoseNotExist:
return ''
or:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = serializers.ProfileSerializer(read_only=True)
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
this will return:
{
"username": "Steve"
"fullname": "Steve Smith"
"phonenumber": "1234567890"
"email": "st#gmail.com"
"profile":{
"branch": "CS"
"year": 4
"image": path/to/folder
}
}
as your profile is onetoone to user,so profile is JsonObject not JsonArray
Configure your AccountSerializer somewhat like this,
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = [f.name for f in model._meta.fields]
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = ProfileSerializer()
class Meta:
model = Account
fields = [f.name for f in model._meta.fields]
You may also override your create and update methods of your serializer accordingly.
Related
I want to validate email variable before UserSerializer, and then return filtered data. The following code works; but I declared "serializer" twice. If I want to use serializer once, how can I do it?
views.py
#api_view(['GET'])
def get_user(request):
email = request.data.get('email')
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
Users = User.objects.filter(email=email)
serializer = UserSerializer(Users, many= True)
return Response({"status": "success", "data": serializer.data})
else:
return Response({"status": "errors", "data": serializer.errors})
serializers.py
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
phone = serializers.CharField(required=False)
sex = ChoiceField(required=False, choices=User.TYPE_CHOICES)
class Meta:
model = User
fields = ('id', 'email', 'phone', 'name','sex', 'updated', 'created')
models.py
class User(models.Model):
TYPE_CHOICES = (
('0', 'men'),
('1', 'girl'),
('2', 'nobody'),
)
email = models.EmailField(unique=True, max_length=50)
phone = models.TextField(unique=True, max_length=11)
name = models.TextField(default="AKA")
sex = models.CharField(
max_length=2,
choices=TYPE_CHOICES,
default="0"
)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "users"
User anether serializer for Validating Email
class EmailSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
#api_view(['GET'])
def get_user(request):
serializer = EmailSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email']
Users = User.objects.filter(email=email)
serializer = UserSerializer(Users, many= True)
return Response({"status": "success", "data": serializer.data})
else:
return Response({"status": "errors", "data": serializer.errors})
I extended my user class to include other fields (city, country, bio). I would like to pass these down along to customizing my JWT access_token to include these fields as a payload in addition to username.
This is what I have in my models.py (extending my user class):
from django.db import models
from django.contrib.auth.models import User
#extending user model to include
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
city = models.CharField(max_length=50,blank=True)
country = models.CharField(max_length=50, blank=True)
bio = models.CharField(max_length=500, blank=True)
def __str__(self):
return self.user.username
serializers.py:
#changed from serializers.HyperLinked to ModelSerializer
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
#removed url from fields
fields = ['username', 'email', 'password', 'first_name', 'last_name', 'city', 'country', 'bio']
extra_kwargs = {
'password': {'write_only': True},
}
def create(self,validated_data):
user = Profile.objects.create_user(
username=validated_data['username'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
city=validated_data['city'],
country=validated_data['country'],
bio=validated_data['bio'],
email=validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
#customizing the payload we get from our access tokens
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
token['first_name'] = user.first_name
token['last_name'] = user.last_name
token['country'] = user.profile.country
token['city'] = user.profile.city
token['bio'] = user.profile.bio
return token
and api.py
#Register API
class RegisterApi(generics.GenericAPIView):
serializer_class = RegisterSerializer
#remove this if it doesn't work
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"message": "User Created Successfully. Now perform Login to get your token",
})
When I attempt to login now I get the error message:
AttributeError at /api/token/
'User' object has no attribute 'profile'
Request Method: POST
Request URL: http://127.0.0.1:8000/api/token/
I have these two models: order and route. The route has a oneToMany relation with Order as you can see:
class Order(models.Model):
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='customer')
retailer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='retailer')
date_publish = models.DateField(default=datetime.date.today)
date_available = models.DateField()
weight = models.DecimalField(decimal_places=2, max_digits=5)
description = models.CharField(max_length=500, null=True)
route = models.ForeignKey(Route, related_name='orders', null=True, on_delete=models.CASCADE)
class Route(models.Model):
day = models.DateField()
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
start_time = models.TimeField()
When a route is created I want to associate orders with that route, so I've done the following serializer:
class routeSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True)
class Meta:
model = Route
fields = ['day', 'warehouse', 'start_time', 'orders']
def create(self, validated_data):
orders_data = validated_data.pop('orders')
route = Route.objects.create(**validated_data)
for order_data in orders_data:
order_serializer = OrderSerializer(data=order_data)
order_serializer.is_valid(raise_exception=True)
orders = order_serializer.save()
orders.route = route
orders.save()
return route
class OrderSerializer(serializers.ModelSerializer):
ordertimelocation = orderTimelocationSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'customer', 'retailer', 'date_available', 'weight', 'description', 'ordertimelocation']
def create(self, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
order = Order.objects.create(**validated_data)
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = order
order_time_location.save()
return order
def update(self, instance, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
ordertimelocation = instance.ordertimelocation
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = instance
order_time_location.save()
return instance
Views:
class GetRoutes(generics.ListAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateRoute(generics.CreateAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateOrder(generics.CreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
class GetOrders(generics.ListAPIView):
serializer_class = OrderSerializer
def get_queryset(self):
us = self.kwargs.get('us')
return Order.objects.filter(customer_id=us)
class GetOrder(generics.RetrieveAPIView):
serializer_class = OrderSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('order')
return get_object_or_404(Order, id=item)
class UpdateOrder(generics.UpdateAPIView):
serializer_class = OrderSerializer
queryset = Order.objects.all()
Edit:
I also customised the default user model like this:
User models:
class UserManager(BaseUserManager):
def create_superuser(self, email, user_name, first_name, password, **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
if other_fields.get('is_staff') is not True:
raise ValueError(
'Superuser must be assigned to is_staff=True.')
if other_fields.get('is_superuser') is not True:
raise ValueError(
'Superuser must be assigned to is_superuser=True.')
return self.create_user(email, user_name, first_name, password, **other_fields)
def create_user(self, email, user_name, first_name, password, **other_fields):
if not email:
raise ValueError(_('You must provide an email address'))
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, first_name=first_name, **other_fields)
user.set_password(password)
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
GENDER_MALE = 0
GENDER_FEMALE = 1
GENDER_OTHER = 2
GENDER_CHOICES = [(GENDER_MALE, 'Male'), (GENDER_FEMALE, 'Female'), (GENDER_OTHER, 'Other')]
email = models.EmailField(_('email address'), unique=True)
user_name = models.CharField(max_length=150, unique=True)
first_name = models.CharField(max_length=150, blank=True)
last_name = models.CharField(max_length=150, blank=True)
start_date = models.DateTimeField(default=timezone.now)
is_staff = models.BooleanField(default=False)
is_retailer = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
gender = models.IntegerField(choices=GENDER_CHOICES)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['user_name', 'first_name']
def __str__(self):
return self.user_name
def isretailer(self):
return self.is_retailer
User serizalizer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'user_name', 'first_name', 'last_name')
Views:
class CustomUserCreate(APIView):
permission_classes = [AllowAny] #when a user create an account he isn't autenticated
def post(self, request, format='json'):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ListUsers(generics.ListAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
class UserDetail(generics.RetrieveAPIView):
serializer_class = UserSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('id')
return get_object_or_404(User, id=item)
I am sending a request like this:
{
"day" : "2021-12-12",
"warehouse": "1",
"start_time": "7:00",
"orders": [
{
"id": 15,
"customer": 1,
"retailer": 2,
"date_available": "2020-12-12",
"weight": "1.20",
"description": null,
"ordertimelocation": [
{
"longitude": "12.1223000000000000",
"latitude": "12.1223000000000000",
"time_interval": [
{
"start": "2021-07-21T10:10:00Z",
"end": "2021-07-21T10:10:00Z"
}
]
}
]
}
]
}
But the server returns a bad request:
{
"customer": [
"Incorrect type. Expected pk value, received User."
],
"retailer": [
"Incorrect type. Expected pk value, received User."
]
}
I'm new to django and I don't know what is a 'pk' value and why it is expecting it instead of User.
PK is primary key and in here is equal to id
For saving a record in db with it's relations, django needs PK of related object
But when you pass this pk to serializer, in validate() function of serializer, django if passed pk is existed in db and valid, if so, it would return it's model object
for example, you pass customer pk as 1, but after validation, there is a Customer object with id 1. and you are passing this object to order = Order.objects.create(**validated_data) but as i mentioned before, you should pass just PK
So one of solutions can be:
validated_data['customer'] = validated_data['customer'].id
validated_data['retailer'] = validated_data['retailer'].id
order = Order.objects.create(**validated_data)
And another solution is to overriding validate() function and control what to return
I have a OneToMany relation. One Construction and many Cameras.
I want to return all Building object fields in CameraSerializer
Problem
When I perform POST request (create new Camera object)
{
"name": "CameraName",
"url": "CameraUrl",
"building": 2
}
I have an error
{
"building": {
"nonFieldErrors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
Reason of error -- Django expects FULL Construction object, but I want to set only ID
How can I fix the error?
models.py
class Construction(models.Model):
""" Объект строительства"""
developer = models.ForeignKey(
Developer, related_name="constructions", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
plan_image = models.ImageField(upload_to=name_image, blank=True, null=True)
...
def __str__(self):
return self.name
class Camera(models.Model):
building = models.ForeignKey(
Construction, related_name="cameras", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
url = models.CharField(max_length=100)
...
def __str__(self):
return self.name
serializers.py
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = MyPointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer_name = serializers.SerializerMethodField()
events = serializers.SerializerMethodField()
class Meta:
model = Construction
fields = (
'id', 'developer', 'developer_name', 'name', 'plan_image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number', 'events'
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number', 'events')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
class CameraSerializer(serializers.ModelSerializer):
frames = FrameSerializer(many=True, read_only=True)
building = ConstructionSerializer()
class Meta:
model = Camera
fields = (
'id', 'building', 'name', 'url', 'zone_id_x', 'zone_id_y',
'proc_id', 'path_frames', 'frames'
)
read_only_fields = ('proc_id', 'path_frames', 'frames')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
instance.set_proc_id()
instance.set_path_frame()
return instance
views.py
class CameraView(viewsets.ModelViewSet):
serializer_class = CameraSerializer
queryset = Camera.objects.all()
def get_camera_create_serializer(self, *args, **kwargs):
kwargs["context"] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = self.get_camera_create_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = {"result": serializer.data}
return Response(
response, status=status.HTTP_201_CREATED, headers=headers
)
I would like to extend Django's user model using a OneToOneField in my Django api project, but I'm getting a weird error. I'm hoping someone can help me out. Below is my code and the error message.
models.py:
class Profile(models.Model):
yearOfExperience = models.PositiveIntegerField(default=1)
profession = models.CharField(max_length=250, blank=True, null=True)
dp = models.URLField(blank=True, null=True)
qualification = models.CharField(max_length=255, blank=True, null=True)
phoneNumber = models.CharField(max_length=255, blank=True, null=True)
#receiver(post_save, sender=CustomUser)
def create_user_profile(sender, instance=None, created=False, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
serializer:
class ProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source = 'pk', read_only = True)
username = serializers.CharField(source = 'user.username', read_only = True)
email = serializers.CharField(source = 'user.email', read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def create(self, validated_data, instance=None):
if 'user' in validated_data:
user_data = validated_data.pop('user')
user = CustomUser.objects.create(**validated_data)
profile = Profile.objects.update_or_create(user=user, **validated_data)
return user
apiView:
class ProfileListView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Error message:
File "/home/olaneat/Desktop/filez/project/django/funzone/lib/python3.7/site-packages/django/db/models/base.py", line 500, in __init__
raise TypeError("%s() got an unexpected keyword argument '%s'" % (cls.__name__, kwarg))
TypeError: CustomUser() got an unexpected keyword argument 'yearOfExperience'
I believe you're missing the user one to one field to your profile model so
add it there
class Profile(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
...
Since you're using post_save for your profile, there's no need for the .create in your serializer. So something like this:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only = True)
email = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def get_username(self, obj):
return obj.user.username
def get_email(self, obj):
return obj.user.email
now just post from the API view and the profile will be created.
If however, you want to keep the .create for the option to add new users from the profile API then perhaps something like this:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only = True)
email = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def create(self, validated_data, instance=None):
if 'user' in validated_data:
user = validated_data.pop('user')
else:
user = CustomUser.objects.create(**validated_data)
profile, created_profile = Profile.objects.update_or_create(user=user, **validated_data)
return profile
def get_username(self, obj):
return obj.user.username
def get_email(self, obj):
return obj.user.email