How to update custom user's model fields using APIView update method - django

I am trying to code up a APIView's update method so I can change model fields in my custom AbstractUser model.
I read the documentation for APIViews and other examples, but most of them involved another OneToOne 'profile' model like in this example and or coding up the serializer which I don't think it's necessary for user model(correct me if I'm wrong)
I am unsure how to implement the same update method for a user model.
This is my custom user model. I am trying to update referred_count and tokens fields here from frontend
users/models.py
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
username = None
first_name = models.CharField(max_length=100, default="unknown")
last_name = models.CharField(max_length=100, default="unknown", blank=True)
profile_pic = models.CharField(max_length=200, default="unknown")
premium = models.BooleanField(default=False)
referred_count = models.IntegerField(default=0)
tokens = models.IntegerField(default=0)
email = models.EmailField(unique=True, db_index=True)
secret_key = models.CharField(max_length=255, default=get_random_secret_key)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = UserManager()
class Meta:
swappable = "AUTH_USER_MODEL"
users/api.py
class UpdateFields(ApiAuthMixin, ApiErrorsMixin, APIView):
def update(self, request, *args, **kwargs):
# update various user fields based on request.data
# I am not sure what should go inside here.
return request.user.update(request, *args, **kwargs)
I want to send something like the following from frontend which will pass through the UpdateFields APIView and 'patch' these user fields
{
'tokens': 100,
'referred_count': 12,
}
users/urls.py
urlpatterns = [
path("me/", UserMeApi.as_view(), name="me"),
path("update/", UpdatePremium.as_view(), name="update"),
]

Try out this.
user = request.user
user.tokens = request.data.get('token')
user.referred_count = request.data.get('referred_count')
user.save()

Related

Django Rest Framework ModelSerializer giving error for primary field

I am working on a project where I have created custom user by extending AbstractBaseUser and PermissionMixin, the model class is following.
class User(AbstractBaseUser, PermissionsMixin):
phone_number = models.CharField(
primary_key=True, validators=[MinLengthValidator(10)], max_length=10
)
password = models.CharField(
null=False, blank=False, validators=[MinLengthValidator(8)], max_length=225
)
date_joined = models.DateTimeField(null=False, blank=False, default=timezone.now)
last_login = models.DateTimeField(null=True, blank=True)
last_logout = models.DateTimeField(null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = "phone_number"
REQUIRED_FIELDS = []
objects = CustomUserManager()
#staticmethod
def hash_password(sender, instance, *args, **kwargs):
if not instance.is_staff and not instance.is_superuser:
instance.set_password(instance.password)
def get_token(self):
return Token.objects.get(user=self)
def __str__(self):
return self.phone_number
# signals for Model User
pre_save.connect(User.hash_password, sender=User)
And the following ModelSerializer corresponding to it.
class UserLoginSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["phone_number", "password"]
Now if If I pass the post data as:-
{
"phone_number":8888888888,
"password":12345678
}
What I am getting:
The serializer.is_valid() is returning False.
if I am doing serializer.is_valid(raise_exception=True) then I am
getting response as:
{
"phone_number": [
"user with this phone number already exists."
]
}
My Doubts are:
I know that 8888888888 is already in the DataBase but I still want to
access it using serializer.validated_data.get('phone_number', None)
I also want to know the reason, why this is happening, it is acting
like as if I am trying to insert a record, but if I pass phone number
like 8888888887(Not present in the database), then its working fine
As phone_number is a primary key, it has unique validator by default.
You can add custom validators in the serializer as shown below.
class UserLoginSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["phone_number", "password"]
extra_kwargs = {
'phone_number': {
'validators': [MinLengthValidator(10)],
}
}
By adding the just the validators we require, the serializer only validates according to the given validators. if the 'validators': [], are set to empty list, then no validation is performed on the particular field.
I have added MinLengthValidator(10) validator, which you have used in the User Model. You can import it and use it here.
It is happening because phone_number is the primary key. The primary key is a unique field, so you cannot have duplicate records. Check here.

DRF Serializer foreign key reverse lookup

I'm trying to send the following HTTP Post API request to create a new EventInterest object. How can I accomplish this in a smallest payload instead of sending the entire object? I'm attempting an extra layer of security-through-obfuscation and instead of using the default integer pk, how can I use uuid for Event and username for User? .... Or do the extra SQL lookups negate the benefits of simplifying the payload and I should just use pk?
models.py
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True, blank=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(settings.AUTH_USER_MODEL) # from User
api.py
class EventInterestViewSet(mixins.CreateModelMixin, GenericViewSet):
queryset = models.EventInterest.objects.all()
serializer_class = serializers.EventInterestSerializer
lookup_field = 'uuid'
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
# event = serializers.SlugRelatedField(read_only=True, slug_field='uuid')
# recipient = serializers.SlugRelatedField(read_only=True, slug_field='username')
# sender = serializers.SlugRelatedField(read_only=True, slug_field='username')
class Meta:
model = models.EventInterest
fields = (
'event', #works with pk, want uuid
'sender', # works with pk, want username
)
HTTP Post:
{
"event": "da9290c6-f6f8-4d27-bfe0-d388ed911fe8",
"sender":"eX8gkxJNDREv" //this is the username field
}
You need to make your UUIDField as primary key. Just like this:
class Event(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
....
As you defined uuid in Event model, you have to define uuid in User model too. In order to do that, you have to extend the default user model. Then you have to override the create() method of EventInterestSerializer to do a lookup on respective UUID field instead of pk
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(User)
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
class Meta:
model = EventInterest
fields = ('event', 'sender',)
def create(self, validated_data):
try:
return EventInterest.objects.get(event__uuid=validated_data['event'],
sender__uuid=validated_data['sender'])
except EventInterest.DoesNotExist:
raise serializers.ValidationError("No matching data found")

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

Access field from intermediary model in Django Rest Framework

Having a hard time trying to access a field from an intermediary model in DRF.
Let's see the related models:
class School(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, verbose_name=_(u'Name'))
staff_order = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_(u'Staff ordering'), through='StaffOrder', related_name='school_staff_order')
class User(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=150, blank=True, null=True)
about_me = models.CharField(max_length=200, blank=True, null=True)
REQUIRED_FIELDS = ['email']
def __unicode__(self):
return u'{0}'.format(self.username)
class StaffOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
school = models.ForeignKey(School)
order = models.PositiveSmallIntegerField(default=0, verbose_name=_(u'Staff ordering for this school'))
class Meta:
verbose_name_plural = _(u'Staff ordering')
Now what I'm expecting is being able to read order field from StaffOrder in when returning a QuerySet for users (StaffSerializer). Here are the Serializers:
class StaffRoleSerializer(serializers.ModelSerializer):
class Meta:
model = StaffOrder
fields = (
'order',
)
class StaffSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=75, required=True)
email = serializers.CharField(max_length=75, required=True)
order = StaffRoleSerializer(source='school_staff_order')
class Meta:
model = User
What is returned in order for the StaffSerializer is a Manager, instead of the order field from the StaffOrder model related with this User.
A JSON expected response for Staff would be something like this:
[
{
"username": "Denise",
"email": "deniseburton#maximind.com",
"order": 9
},
{
"username": "Ina",
"email": "inaburton#maximind.com",
"order": 4
}
]
I'd love to be able to also write this value from the serializer, but I can do that in the Viewset, but I really need to read this value in the Serializer itself...any idea what I'm missing here?
First you have to understand that one user can have many staff orders. In your models you have defined it that way.
To get the json output you have specified in your question you need to query the StaffOrder objects instead of users.
class StaffOrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
class Meta:
model = StaffOrder
fields = ('order', )
Use this serializer class in a list view:
class StaffOrderListApi(generics.ListAPIView):
serializer_class = StaffOrderSerializer
def get_queryset(self):
# using distinct because same user can have multiple staff orders
# based on your example it seems you want distinct users
return StaffOrder.objects.all().distinct('user')
This will get you exactly the json you want.

Modelserializer using kwargs to get FK object

I'm creating a Django (1.8) webapp that saves racing laptimes and scoreboards. The database is populated using an API built using Django Rest Framework. It's the first time I'm trying to build a proper api using rest framework.
A quick overview of the models:
Event, A racing event/weekend
Session, A single race/practice/quali - FK Event
Car, A car taking part in a session - FK Session
Lap, Laps for specific car - FK Car
The Event is created manually, but the rest is supposed to be "dynamic" (get or create)
Right now I'm trying to create a new car using my API, but I'm stuck. To get the cars event and session I'm trying to use the url;
/api/results/skrotbilsracet-29042016/r1/cars/
The idea is to post data to this url and "get or create" a new car object.
To get the correct session object for the new car session FK, I need to use a custom function that takes the kwargs and tries to find the session.
The more I read about how to solve this, the more confused I get.
Could someone push me in the right direction?
This is my latest attempt at solving this, which just gives me "{"session":["This field is required."]}"
models.py
class Session(models.Model):
session_types = (
('p', 'Practice'),
('q', 'Qualification'),
('r', 'Race')
)
event_id = models.ForeignKey(Event, related_name='sessions')
name = models.CharField(max_length=2, blank=True)
current_session = models.BooleanField(default=True)
session_type = models.CharField(max_length=2,
choices=session_types)
started = models.DateTimeField(auto_now_add=True)
ended = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['started']
def save(self):
if not self.name:
# Get number of sessions
session_count = Session.objects.filter(event_id=self.event_id)\
.filter(session_type=self.session_type)\
.count()
session_count += 1
self.name = self.session_type + str(session_count)
super(Session, self).save()
def __unicode__(self):
string = self.started.strftime("%d-%m-%Y %H:%M") + ' - '
string += self.name.upper()
return(string)
class Car(models.Model):
session = models.ForeignKey(Session, related_name='cars')
number = models.IntegerField()
full_name = models.CharField(max_length=256, blank=True)
short_name = models.CharField(max_length=256, blank=True)
race_class = models.CharField(max_length=50, blank=True)
best_lap = models.IntegerField(blank=True, null=True)
best_lap_time = models.CharField(max_length=20, blank=True)
best_sector1 = models.CharField(max_length=20, blank=True)
best_sector2 = models.CharField(max_length=20, blank=True)
best_sector3 = models.CharField(max_length=20, blank=True)
best_speed = models.IntegerField(blank=True, null=True)
pitstops = models.IntegerField(blank=True, null=True)
total_time = models.CharField(max_length=20, blank=True)
transponder = models.CharField(max_length=50, blank=True)
apiUrls.py
urlpatterns = [
url(r'^raceslug/$', raceSlugView.as_view(), name='race-slug'),
url(r'^events/$', eventsView.as_view(), name='event-list'),
url(r'^session/$', getSessionView.as_view(), name='session-pk'),
url(r'^(?P<event_id>[a-z0-9\-]+)/$', eventView.as_view(), name='event-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/$', sessionView.as_view(), name='session-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/cars/$', carsView.as_view(), name='car-list'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/(?P<number>[0-9]+)/$', carView.as_view(), name='car-detail'),
]
urlpatterns = format_suffix_patterns(urlpatterns)
api.py
class carsView(generics.ListCreateAPIView):
serializer_class = carSerializer
def get_session(self, event_id, name):
print('Getting session')
# Get event object
try:
event = Event.objects.get(event_id=event_id)
print('Found event')
except ObjectDoesNotExist:
print('Did not find event')
return
# Get session object
try:
session = event.sessions.get(name=name)
print('Found session: ', session)
return session
except ObjectDoesNotExist:
print('Did not find session')
return
def get_queryset(self):
print('Getting queryset')
print('event_id: ' + self.kwargs['event_id'])
print('name: ' + self.kwargs['name'])
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
class Meta:
model = Car
fields = (
'session',
'number',
'full_name',
'short_name',
'race_class',
'best_lap',
'best_lap_time',
'best_sector1',
'best_sector2',
'best_sector3',
'best_speed',
'pitstops',
'total_time',
'transponder',
'laps')
Solution:
This is what I actually changed to get it working.
api.py
from rest_framework.serializers import ValidationError
class carsView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
number = self.request.POST.get('number')
car = session.cars.filter(number=number)
if car.exists():
raise ValidationError('Car already exists')
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
session = serializers.StringRelatedField(required=False)
...
I see that you're creating your session ID there:
def get_queryset(self):
...
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
Then you don't need it in a serializer, only in a model. So you can set it a snot required in a serializer, but it will still be required in a model.
I guess this answer could help you: Django REST Framework serializer field required=false