I recently discovered DRF and I'm lost with the quantity of views, viewsets and other possibilities.
I have a Python3/Django 1.8 application with an extended user profile:
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
class Profile(models.Model):
GENDER = (
('male', _('MALE')),
('female', _('FEMALE')),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.CharField(default='', max_length=500, null=True, blank=True)
gender = models.CharField(max_length=10, choices=GENDER, null=True, blank=True)
city = models.CharField(default='', max_length=30, null=True, blank=True)
country = models.CharField(default='', max_length=30, null=True, blank=True)
I would like to allow external mobile applications connected with oauth2/token Bearer to get the current connected user's profile through the api and editing it using those routes:
GET or PUT /api/profile
GET or PUT /api/user
My first intention was using only one route for manipulate both models (through /api/profile) but I failed and I'm not sure if it's a good practice to mix two models behind one route.
I tried lot of things. My last attempt was to get the profile like this:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'password', 'email', 'groups')
password = serializers.CharField(write_only=True)
class UserViewSet(viewsets.ModelViewSet):
#list_route(methods=['get', 'post'], permission_classes=[permissions.IsAuthenticated])
def profile(self, request):
u = User.objects.filter(pk=request.user.pk)[0]
p = Profile.objects.filter(user=u)[0]
return Response({"id": u.id, "first_name": u.first_name, "last_name": u.last_name, "email": u.email,
"city": p.city, "country": p.country, "bio": p.bio})
permission_classes = [permissions.IsAuthenticated]
queryset = User.objects.all()
serializer_class = UserSerializer
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
Problem is: I'm failed trying to implement the same thing for PUT requests. Furthermore I would like to do the security and defensive coding part on the API side and in this situation I don't even using the serializers.
Could you guys help me to find the right thing to do? Do you have any tips, suggestions?
Cheers
I think this is what you want:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('bio', 'gender', 'city', 'country')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('url', 'username', 'password', 'email', 'groups', 'profile')
Or if you want it flat:
class UserSerializer(serializers.ModelSerializer):
bio = serializers.CharField(source='profile.bio')
gender = serializers.CharField(source='profile.gender')
#same for the others
class Meta:
model = User
fields = ('url', 'username', 'password', 'email', 'groups', 'bio', 'gender')
I didn't test it, but should be close to what you want, or close to it at least.
Related
i would like to add data to table which having a foreignkey relatonship with user model through django rest api.
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# username = None
email = models.EmailField(verbose_name='email',max_length=50,unique=True)
phone = models.CharField(max_length=17,blank=True)
REQUIRED_FIELDS = [
'first_name',
'last_name',
'phone',
'username',
]
USERNAME_FIELD = 'email'
def get_username(self):
return self.email
class UserInfo(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
address = models.CharField(max_length=50)
zipcode = models.CharField(max_length=20)
medication = models.CharField(max_length=25)
def __str__(self):
return str(self.user)
serializers.py
from djoser.serializers import UserCreateSerializer
class UserCreateSerializerCustom(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta,):
model = User
fields = (
'id',
'email',
'username',
'password',
'first_name',
'last_name',
'phone',
)
## User Additional Info Serializers
class UserAdditionalSerializers(serializers.ModelSerializer):
user = UserCreateSerializerCustom()
class Meta:
model = UserInfo
fields = (
'user',
'address',
'zipcode',
'medication',
)
views.py
class UserAdditionalView(generics.ListCreateAPIView):
queryset = UserInfo.objects.all()
serializer_class = UserAdditionalSerializers
how will i send user instance with the data....??
i'm using token authentication for the api. Which is the best way to achieve this..??
I have a custom User model and the User Profile model.
class User(AbstractUser):
"""Custom User authentication class to use email as username"""
username = None
email = models.EmailField(verbose_name='email', max_length=255, unique=True,
error_messages={
'unique': _(
"A user is already registered with this email address"),
}, )
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.email
class UserProfile(models.Model):
user = models.OneToOneField(User, to_field='email', on_delete=models.CASCADE)
emp_id = models.CharField(max_length=20, blank=False, default='0', null=False)
department = models.CharField(max_length=50, blank=True, default='', null=True)
I am trying to write a serializer that combines both these models are produces a nested JSON.
for example:
{
"email":"user#gmail.com",
"is_active":true,
"profile":
{
"emp_id":2,
"department":2
}
}
This is what I tried to do
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user', 'emp_id', 'department')
class UserPairSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'profile')
But for some reason, there is neither the field profile in my response nor am I getting any errors.
I tried following this docs: https://www.django-rest-framework.org/api-guide/relations/
What is the issue and how do I solve this?
As per the documentation implicitly refering to this, 'reverse' queries are done using the name of the Model, lowercased (in this case user.userprofile).
So you have two options:
Either you specify a custom related_name on the user field on the UserProfile model.
class UserProfile(models.Model):
user = models.OneToOneField(User, to_field='email', on_delete=models.CASCADE, related_name='profile')
Or, you specify a source argument on your nested serializer (see documentation):
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user', 'emp_id', 'department')
class UserPairSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer(read_only=True, source='userprofile')
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'profile')
I have the following models:
class User(AbstractBaseUser, PermissionsMixin):
SUPERVISOR = 1
REVIEWER = 2
VERIFIER = 3
READ_ONLY = 4
USER_TYPE = [
(SUPERVISOR, 'Supervisor'),
(REVIEWER, 'Reviewer'),
(VERIFIER, 'Verifier'),
(READ_ONLY, 'Read Only'),
]
email = models.EmailField(max_length=50, unique=True)
name = models.CharField(max_length=100)
phone = models.CharField(max_length=50, null=True)
role = models.IntegerField(
choices=USER_TYPE,
default=READ_ONLY
)
is_active = models.BooleanField(default=True)
class Comment(models.Model):
text = models.TextField()
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT
)
View:
class CommentViewSet(BaseCertViewSet):
queryset = Comment.objects.all()
serializer_class = serializers.CommentSerializer
Serializer:
class CommentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
class Meta:
model = Comment
fields = ('id', 'text', 'user',)
read_only_fields = ('id',)
My question: when I hit the comment API endpoint, I'd like it to return the user role from the user model as well. How do I go about doing that?
I believe you can use a QuerySet.annotation:
EDIT: F is from django.db.models so you have to import that as well.
queryset = Comment.objects.annotate(user_role=F("user__role")) in your CommentViewSet
EDIT:
In order to get the serializer to recognize the field that you are trying to add to the QuerySet, you must also define the field on the serializer like this:
class CommentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
# add line below to your code
user_role = IntegerField()
class Meta:
model = Comment
# you have to add it to the list of fields as well
fields = ('id', 'text', 'user', 'user_role')
read_only_fields = ('id',)
The solution that worked for me (not sure it's the most elegant one, happy to change to a better way of doing that):
class CommentSerializer(serializers.ModelSerializer):
"""Serializer for Comment object"""
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
role = serializers.SerializerMethodField()
def get_role(self, obj):
user = obj.user_id
role = User.objects.only('id').get(
id=user).role
return role
class Meta:
model = Comment
fields = ('id', 'value', 'text', 'user', 'role',
'date_created', 'date_updated')
read_only_fields = ('id',)
I would like to modify Django UserCreationForm so that it would support creating my custom user. There are required fields company and role which both should offer some kind of selection to pick the correct choice (there will be only 3 roles but there can be hundreds of companies).
I believe I need to extend UserCreationForm and modify UserAdmin. However, I have followed several different examples but so far in vain. Below is the model. How can I command Django to add the extra fields to the user creation form?
ROLE_CHOICES = [
('role1', 'Role 1'),
('role1', 'Role 2'),
('janitor', 'Janitor'),
]
class Company(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
class Meta:
ordering = ('created',)
db_table = "company"
def __str__(self):
return self.name
class CustomUser(AbstractUser):
created = models.DateTimeField(auto_now_add=True)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=200, unique=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='%(class)s_company')
role = models.CharField(
max_length=100,
choices=ROLE_CHOICES,
default='janitor',
)
phone_number = models.CharField(null=True, blank=True, max_length=20)
class Meta:
ordering = ('created',)
db_table = "custom_user"
def __str__(self):
return self.username
You don't have to extend UserCreationForm. just use this:
forms.py
from django import forms
from .models import CustomUser
class UserRegistrationForm(forms.ModelForm): # you can name it anything
class Meta:
model = CustomUser
fields = ('username', 'email', 'company',....) # add the fields here you want in form just not created. it's auto fill
Use this form.
If you want admin. Write this in admins.py
from .models import CustomUser
class CustomUserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'company'...) # add fields as you want
admin.site.register(CustomUser, CustomUserAdmin)
Hope this help. If not, please comment.
I've simplified my code to make problem more clear.
I have two related models in my Django REST project. In my models.py:
class ClinicalResearch(models.Model):
name = models.CharField(max_length=150, null=True)
description = models.TextField(blank=True, null=True)
class Patient(models.Model):
research = models.ForeignKey(ClinicalResearch, on_delete=models.CASCADE, blank=True)
first_name = models.CharField(max_length=50)
second_name = models.CharField(max_length=50, blank=True, null=True)
middle_name = models.CharField(max_length=50, blank=True, null=True)
location = models.CharField(max_length=50, blank=True)
birth_date = models.DateTimeField(blank=True, null=True)
observation_date = models.DateTimeField(blank=True, null=True)
desease = models.CharField(max_length=50, blank=True, null=True)
desease_date = models.DateTimeField(blank=True, null=True)
gender = models.CharField(max_length=10)
Then I create a serialiser:
class PatientSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Patient
fields = ('id', 'research_id', 'first_name', 'second_name', 'middle_name', 'location', 'birth_date', 'observation_date', 'desease', 'desease_date', 'gender')
def create(self, validated_data):
# for tests only
print(validated_data)
return Patient(**validated_data)
View set:
class PatientViewSet(viewsets.ModelViewSet):
queryset = Patient.objects.all()
serializer_class = PatientSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('id', 'research_id', 'first_name', 'second_name', 'middle_name', 'location', 'birth_date', 'observation_date', 'desease', 'desease_date', 'gender')
Router:
router.register(r'patients', ItemViewSet)
This implementation works fine for models without foreign keys. But when I post something like:
{
'id': null,
'research_id': 1,
'first_name': "john",
'second_name': "doe",
'middle_name', "",
'location': "Love Street",
'birth_date': "...",
'observation_date': "...",
'desease': "Lovefool",
'desease_date': "...",
"gender": "male to fem"
}
(the project with id=1 exists)
my ItemSerializer doesn't pass { research_id: 1 } to validated_fields.
The project_id field is completely lost during validation.
How to configure Serializer or ViewSet to make it workable?
Looks like problem related to HyperlinkedModelSerializer in this case Django expecting url for related object instead of id. Try to override type of serializer's field research:
class PatientSerializer(serializers.HyperlinkedModelSerializer):
research = serializers.PrimaryKeyRelatedField()
class Meta:
model = Patient
fields = ('id', 'research', 'first_name', 'second_name', 'middle_name', 'location', 'birth_date', 'observation_date', 'desease', 'desease_date', 'gender')
def create(self, validated_data):
# for tests only
print(validated_data)
return Patient(**validated_data)
# If you need URLs for representation use this method
def to_representation(self, instance):
"""Set field type for output only"""
self.fields['research'] = serializers.HyperlinkedRelatedField()
return super().to_representation(instance)
You have to link it:
accept
Looks like problem related to HyperlinkedModelSerializer in this case Django expecting url for related object instead of id. Try to override type of serializer's field research:
class PatientSerializer(serializers.ModelSerializer):
research = serializers.PrimaryKeyRelatedField(queryset=ClinicalResearch.objects.all())
class Meta:
model = Patient
fields = ('id', 'research', 'first_name', 'second_name', 'middle_name', 'location', 'birth_date', 'observation_date', 'desease', 'desease_date', 'gender')
def create(self, validated_data):
# for tests only
print(validated_data)
return Patient.objects.create(**validated_data)