DRF Serializer Relations / JSON API Framework - python

*Update 1: Attempts with corresponding errors now shown in serializer code
*Update 2: I've narrowed the issue to a conflict with the JSON Api framework I'm using
I'm fairly new to Python and Django and I'm struggling quite a bit with relations. I'm able to create the relations in the database however I've been unable to serialize the models together for a response object, despite trying every method in the documentation which seems very straightforward. My goal is to return both models from an APIView on login and if this code doesn't error I get a response with just the user model. The errors vary based on the different techniques and are similar to what other users get when having trouble with relations, however their fixes haven't solved my problem.
I'm wondering if there's something obvious I'm not doing right. I'm happy to share more code but I'm really at a loss as to how to proceed.
Fyi: My models are in different apps which is why the fk reference is users.User
Models:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=50, unique=True)
password = models.CharField(max_length=100)
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
has_business = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['password']
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def __str__(self):
return self.email
class Business(models.Model):
name = models.CharField(max_length=50, unique=True)
address = models.CharField(max_length=100)
phone = models.CharField(max_length=20)
description = models.CharField(max_length=500)
user = models.ForeignKey(
'users.User',
related_name='business',
unique=False,
on_delete=models.CASCADE
)
has_driver = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
Serializer:
class UserSerializer(serializers.ModelSerializer):
token = srs.SerializerMethodField()
biz = BusinessSerializer(many=True, read_only=True,
source='business.id')
error: just returns user model
biz = BusinessSerializer(read_only=True, many=True, source='business')
error: ForeignKey' object has no attribute
biz = serializers.RelatedField(many=True, read_only=True)
error: 'User' object has no attribute 'biz'
biz = serializers.ReadOnlyField(source='businesses.id')
error: RelatedManager object at 0x7fed496fe208> is not JSON
serializable
biz = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
error:'User' object has no attribute 'biz'
class Meta:
model = models.User
fields = ('id', 'email', 'password', 'name', 'token', 'has_business', 'biz')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = models.User(
email = validated_data['email'],
name = validated_data['name'],
phone = validated_data['phone'],
location = validated_data['location'],
)
user.set_password(validated_data['password'])
user.save()
return user
View:
class LoginUser(APIView):
permission_classes = (permissions.AllowAny,)
resource_name = 'users'
def post(self, request):
email = request.POST.get('email', False)
password = request.POST.get('password', False)
if(email and password):
lu = UserAuth()
authenticated_user = lu.auth(email=email, password=password)
if authenticated_user is not None:
if authenticated_user.is_active:
serializer = serializers.UserSerializer(authenticated_user, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)

You have to use the related_name in your serializer.
You can take a look here
In this example you can see that the Track Model (analog to your Business Model) has the field called album with related_name='tracks'
Then in the AlbumSerializer (the analog to your User Model):
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
So, in summary you have to use your related_name business
This is the example with a StringRelatedField, but you can of course return the complete object using this

Turns out I was using a beta version (pip install djangorestframework-jsonapi==2.0.0-beta.1) of the JSON API Framework and it wasn't handling relations very well. I picked the beta because it was the one the documentation lists which is kind of odd. You have to dig a bit for the more stable versions. I'm embarrassed to say how long that took me to figure out.
Here's the stable version as of this day: pip install djangorestframework-jsonapi==2.2.0

Related

In Django Rest Api, how do you return only the Items the owner uploaded

The Viewset def list looks like this:
class ThreeDimensionalModelViewSet(viewsets.ViewSet):
serializer_class = ThreeDimensionalModelSerializer
queryset = ThreeDimensionalModel.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def list(self, request):
models = ThreeDimensionalModel.objects.all()
serializer = ThreeDimensionalModelSerializer(models, many=True)
print(request.user.id)
return Response(serializer.data)
The serializer looks like this:
class ThreeDimensionalModelSerializer(serializers.ModelSerializer):
class Meta:
model = ThreeDimensionalModel
fields = ['File', 'Uploaded', 'Owner', 'Previous', 'SharedWithUser']
read_only_fields = ['Owner']
The model looks like this:
class ThreeDimensionalModel(models.Model):
File = models.FileField(upload_to='models')
Owner = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, related_name='Owner')
Uploaded = models.DateTimeField(auto_now_add=True)
Previous = models.ForeignKey("self", on_delete=models.SET_NULL, default=None, null=True)
SharedWithUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='SharedWithUser')
When a user requests models at /api/models it should only show the models that are the same owner Id as his.
If no additional data is sent with that request then obviously you can't filter by user.
The straightforward way to do it is that for logged in users the cookie will contain user information such as userId.
When your endpoint recognizes the user who made the requested is logged in, it will use that as the filter for the query instead of all() as seen in the Django docs
https://docs.djangoproject.com/en/3.2/topics/db/queries/#retrieving-specific-objects-with-filters
To summarize - if the user is not logged in (or supplies the information as part of the request in some way) then the request is anonymous and there is no way to know who made it

Django problem overriding all auth Signup forms

Thanks in advance, i'm learning Django and can't figure how to override all auth forms. Quick explanation first, I have a custom user model
class PersoUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="Email Adress", max_length=200, unique=True)
username = models.CharField(
verbose_name="username", max_length=200, unique=True)
first_name = models.CharField(verbose_name="firstname", max_length=200)
last_name = models.CharField(verbose_name="lastname", max_length=200)
date_of_birth = models.DateField(verbose_name="birthday")
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
objects = PersoUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth", "username"]
....
and I would want to add date_of_birth field to my signup page, so I followed the official doc to override the specif form used by all auth SignupView https://django-allauth.readthedocs.io/en/latest/forms.html#signup-allauth-account-forms-signupform
which leads to ( in Book_store/forms.py )
from all auth.account.forms import SignupForm from users. models import PersoUser
class PersoUserRegisterForm(SignupForm):
class Meta:
model = PersoUser
fields = ["username", "email", "first_name",
"last_name", "date_of_birth", "password1", "password2"]
def save(self, request):
# Ensure you call the parent class's save.
# .save() returns a User object.
user = super(PersoUserRegisterForm, self).save(request)
# Add your processing here.
# You must return the original result.
return user
in my settings/base.py
ACCOUNT_FORMS = {'signup': 'Book_store.forms.PersoUserRegisterForm'}
My account/signup.html template just refers to {{form.as_p}} and it doesn't display the extra fields specified in PersouserRegisterForm just the default ones
I don't see what I'm missing, Thanks for reading
EDIT: And Signing up fail because it violates not-null constraint for date_of_birth
You are overriding the save method but not saving user with the date_of_birth field .
def save(self, request):
user = super(PersoUserRegisterForm, self).save(request)
user.date_of_birth = self.cleaned_data['date_of_birth']
user.save()
return user

How to create/update nested Serializers with Filefields

I have a python 3.6 + django 1.10 + djangorestframework 3.6.4 project.
I have 2 Model classes called Report and ReportFile. I want to create the CRUD operations to get, post and put those files together with a report.
1 Report has a type (which doesn't really matter here) and can have many ReportFiles that the user should be able to upload.
The modelclasses look like this:
class Report(CreationModificationMixin):
report_type = models.ForeignKey(ReportType, on_delete=models.SET_NULL,
null=True, related_name='issues',
verbose_name='Report', editable=False)
name = models.CharField(max_length=50, blank=True)
class ReportFile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
report = models.ForeignKey(Report, on_delete=models.CASCADE, related_name='files')
uploaded_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name='uploads',
null=True)
file = models.FileField(upload_to=upload_report_to, max_length=500)
and the serializer class:
class ReportSerializer(serializers.ModelSerializer):
files = ReportFileSerializer(many=True)
formats = serializers.SerializerMethodField()
class Meta:
model = Report
fields = ('id', 'name', 'report_type', 'files')
def create(self, validated_data):
rfs_data = validated_data.pop('files')
rf = Report.objects.create(**validated_data)
for rf_data in rfs_data:
ReportFile.objects.create(Report=rf, **rf_data)
return rf
and the ViewSet:
class ReportViewSet(viewsets.ModelViewSet):
serializer_class = ReportSerializer
queryset = Report.objects.all().prefetch_related('report_type')
But I cannot manage to upload the files correctly. Because first I cannot manage to upload that correctly with Postman and I somehow also doubt that this is the correct way to go. Can somebody hint me how I should do this?
Thank you a lot!

Not Null constraint failed on Nested serializer due to "unable do get repr for QuerySet class" error

Trying to get basic Messaging functionality working in my DRF project. I seem to have a problem with the nested serializer validation. I'm new to DRF and have been reading the docs for a few days but I must have missed something.
The error happens inside the line message = Message.objects.create(**validated_data) in my serializer.The request returns an Integrity error, NOT NULL constraint failed on accounts_message.sender_id, but through debugging I have found that this is caused by an error in query.py in the QuerySet method : self: unable to get repr for <class 'django.db.models.query.QuerySet'> model: <class 'accounts.models.Message'> query: none using: none
This seems to be whats causing my issue, since the dict generated by the serializer seems to have all of the data thats being passed in the request, including the supposed null sender_id. However, I am not sure how to fix this issue. Do I need to override repr? or Query Set? This feels like im stuck on something relatively simple.
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'email')
extra_kwargs = {
'id': {
'validators': [UnicodeUsernameValidator()],
}
}
class MessageSerializer(serializers.ModelSerializer):
sender = UserSerializer()
recipients = UserSerializer(many=True)
class Meta:
model = Message
fields = ('id', 'subject', 'body', 'read', 'sender', 'recipients')
def create(self, validated_data):
sender_data = validated_data.pop('sender')
recipient_data = validated_data.pop('recipients')
message = Message.objects.create(**validated_data)
for user in recipient_data:
user= User.message_recip.get_or_create(id=user['id'], name=user['name'])
message.recipients.add(user)
for user in sender_data:
user= User.objects.get_or_create(id=user['id'], name=user['name'])
message.sender.add(user)
return message
Models.py
from django.db import models
class User(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30)
email = models.CharField(max_length=75)
def __str__(self):
return self.name
class Message(models.Model):
id = models.IntegerField(primary_key=True)
subject = models.CharField(max_length=250)
body = models.CharField(max_length=5000)
read = models.BooleanField(default=False)
sender = models.ForeignKey(User, related_name='message_sender')
recipients = models.ForeignKey(User, related_name='message_recip', default=1)
Views.py
class MessageList(APIView):
def get(self, request):
messages = Message.objects.all()
serializer = MessageSerializer(messages, many=True)
return Response(serializer.data)
def post(self, request):
serializer = MessageSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class User(APIView):
def get(self, request):
user = User.objects.get(id)
serializer = UserSerializer(user, many=False)
return Response(serializer.data)
If you give null=True and blank=True options to your ForeignKeys, you will not get IntegrityError anymore. Since you haven't added null & blank options, Django ORM creates a constraint in your DB and will not let you to add empty values to your foreign keys. Django Model Field Reference

Django rest framework auto-populate filed with user.id

I cant find a way to auto-populate the field owner of my model.I am using the DRF .If i use ForeignKey the user can choose the owner from a drop down box , but there is no point in that.PLZ HELP i cant make it work.The views.py is not include cause i think there is nothing to do with it.
models.py
class Note(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
cr_date = models.DateTimeField(auto_now_add=True)
owner = models.CharField(max_length=100)
# also tried:
# owner = models.ForeignKey(User, related_name='entries')
class Meta:
ordering = ('-cr_date',)
def __unicode__(self):
return self.title
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', "username", 'first_name', 'last_name', )
class NoteSerializer(serializers.ModelSerializer):
owner = request.user.id <--- wrong , but is what a need.
# also tried :
# owner = UserSerializer(required=True)
class Meta:
model = Note
fields = ('title', 'body' )
Django Rest Framework provides a pre_save() method (in generic views & mixins) which you can override.
class NoteSerializer(serializers.ModelSerializer):
owner = serializers.Field(source='owner.username') # Make sure owner is associated with the User model in your models.py
Then something like this in your view class:
def pre_save(self, obj):
obj.owner = self.request.user
REFERENCES
http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions#associating-snippets-with-users
https://github.com/tomchristie/django-rest-framework/issues/409#issuecomment-10428031

Categories