Multiple models and multiple serializer in single response in DRF - python-3.x

I'm creating a search functionality that returns all model fields like - users, posts, hashtags, images, and videos.
I'm trying to create multiple queryset with their multiple serializers to return in a single response.
this is my expected response result.
{
searchresults{
posts[
],images[
],videos[
],user[
]
}
}
I tried many methods to do this, but the response was unsuccessful.
I also tried that one but that required relationship.
class HashTagResponseSerializer(serializers.ModelSerializer):
class Meta:
model = HashtagName
fields = ["hashtag",]
def get_hashtag(self, obj):
...
class UserFollowSerializer(serializers.ModelSerializer):
hashtag = HashTagResponseSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['user_uuid', 'post_language', "username", "display_name",
"profile_picture", "hashtag "]

Related

DRF Intermediary Table - POSTing data to the Intermediary Table

I have a many-to-many relationship in my DB design and I am having trouble with the POST aspect. I currently have a table called Loads, Containers, and Container_Loads (this is the intermediary table).
My question is this:
I want to be able to send a POST request into the ContainerLoad intermediary table and just update that table with the values it requires which are: Load ID (PK of the Load table), Container ID (PK of the Container Table) and # of pallets (unique field to the intermediary table). I am able to GET/retrieve the records just fine, but when I try to send a POST request with a payload such as
{
"id":3,
"pallets":"4",
"containerNumberId":5,
"loadNumberId":53
}
(where containerNumberID and loadNumberID are the existing keys in their respective tables), it seems that my code wants to create a whole new Load entry as well (as it asks me for the remaining fields of the Load model), where as I just want to create an entry in the intermediary table without creating a new entry in the Load table.
So for the purpose of my project, a load can be on many containers [imagine that it's split because all of it couldn't fit on one] and a container can belong to many loads.
My models.py looks like this:
class ContainerLoad(models.Model):
id = models.AutoField(primary_key=True)
load_number = models.ForeignKey(Load,on_delete=models.CASCADE)
container_number = models.ForeignKey(Container,on_delete=models.CASCADE)
pallets = models.CharField(blank=True,null=True,default=0,max_length=20)
class Meta:
db_table = 'ContainerLoad'
#load model shortened for brevity
class Load(models.Model):
id = models.AutoField(primary_key=True)
bnsf_container_number = models.ManyToManyField(Container, through='ContainerLoad',through_fields=('load_number','container_number'))
class Meta:
db_table = "Load"
class Container(models.Model):
id = models.AutoField(primary_key=True)
container_number = models.CharField(max_length=15)
in_use = models.BooleanField()
class Meta:
db_table = "Container"
my serializers.py currently looks like this, the commented out section is from me attempting to get the POST to work)
class ContainerLoadSerializer(WritableNestedModelSerializer):
# load_number_id = LoadSerializer(read_only=False)
# container_number_id = ContainerSerializer(read_only=False)
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2
class LoadSerializer(WritableNestedModelSerializer):
primary_driver = DriverSerializer(read_only=False)
second_driver = DriverSerializer(allow_null=True,read_only=False)
third_driver = DriverSerializer(allow_null=True,read_only=False)
bnsf_container_number = ContainerSerializer(read_only=False)
pickup_location = LocationSerializer(read_only=False)
delivery_location = LocationSerializer(read_only=False)
broker = BrokerSerializer(read_only=False)
booked_by = EmployeeSerializer(read_only=False)
class Meta:
model = Load
fields = '__all__'
depth = 1
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class Meta:
model = Container
fields = '__all__'
depth = 1
And finally the views.py
class ContainerLoadViews(APIView):
def get(self, request, id=None):
if id:
container = ContainerLoad.objects.get(id=id)
serializer = ContainerLoadSerializer(container)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
containers = ContainerLoad.objects.all()
serializer = ContainerLoadSerializer(containers, many=True)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
def post(self, request):
serializer = ContainerLoadSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "Error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
If you look at the code of the save method of the BaseSerializer class you will see this:
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
How you are not passing the instance in the post function:
serializer = ContainerLoadSerializer(data=request.data)
The save is always calling to create. You should do something like.
try:
instance = ContainerLoad.object.get(id=request.data['id'])
except:
instance = None
serializer = ContainerLoadSerializer(instance=instance, data = request.data)
You have depth is set to 2 in your ContainerLoadSerializer Meta class, which is telling the serializer to generate a nested representation of your models.
https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
If you remove the depth attribute, the serializer should default back to expecting a primary key value, which is your desired behaviour.
Your serializer should look something like this:
class ContainerLoadSerializer(serializers.ModelSerializer):
class Meta:
model = ContainerLoad
fields = "__all__"
The solution to this was that I needed a nested response when reading the data from the ContainerLoad table but a simple write (not nested) function when POSTing the data.
The solution was to use the to_representation and to_internal_value methods (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior) available within DRF to override the behavior of the serializers. Here is the code that now works for both GET and POST requests and it is no longer asking me for fields related to the Load or Container models when inserting data.
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class ContainerFieldSerializer(serializers.Field):
def to_internal_value(self,value):
return Container.objects.get(id=value)
def to_representation(self,instance):
return ContainerSerializer(instance=instance).data
class Meta:
model = Container
fields = '__all__'
depth = 1
I did the same for the Load Serializer.
and then for my ContainerLoad Serializer I just assign the FK fields to the new classes I created:
class ContainerLoadSerializer(serializers.ModelSerializer):
cl_container = ContainerSerializer.ContainerFieldSerializer()
cl_load = LoadSerializer.LoadFieldSerializer()
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2

How to serialize a related object Django Rest Framework

class Flight(models.Model):
field_1
field_2
field_3
class Approach(models.Model):
flight_object(models.ForeignKey, 'Flight')
approach_type
number
Approach is related as an InlineFormset. How can I serialize Approach nested into Flight with the ability to create new approach objects. I'm unsure of the correct terminology which is making this more difficult to realize. My goal is to create a new Flight object with Approach as a related object in the FlightForm from a React Native project.
I assumed the expected response json
{
"field_1": "value1",
"field_2": "value2",
"field_3": "value3",
"approch_set": {
"approach_type": "value",
"number": 22
}
}
for this expected result we will define two serializers.
class ApproachSerializer(serializers.ModelSerializer):
class Meta:
model = Approach
fields = ('approach_type', 'number', )
class FlightSerializer(serializers.ModelSerializer):
approach_set = ApproachSerializer(many=true)
class Meta:
model = Flight
fields = "__all__"
and in view class you can return FlightSerializer data like this.
class FlightApiView(APIVIew):
def get(self, request):
flights = Flight.objects.all()
return Response({'data': FlightSerializer(flights, many=True).data})
for more you can read the documentation
approaches = SerializerMethodField(source='get_approaches')
def get_approaches(self, obj):
approach_queryset = Approach.objects.filter(flight_object=obj.pk)
return ApproachSerializer(approach_queryset, many=True).data
This is what finally worked, just want to come back and share it.
Thanks #shourav

DRF request is not defined for getting current user id

So What I've been trying to do is to have my API view only return objects that have their attributes post_user to the current id of the logged in user. These post_user attributes are populated as whenever I post it populates the variable with the current user's id through my serializer.
However, I am not successful as it says request is not defined. I just want to get the current user's id so that I can use it to filter my object returns
views.py
# To retrieve and list all posts with DRF
class ListPosts(generics.ListCreateAPIView):
queryset = Posts.objects.get(post_user=request.user.id)
serializer_class = PostsSerializer
permission_classes = (permissions.IsAuthenticated,)
serializers.py
# serializer for posts to be taken
class PostsSerializer(serializers.ModelSerializer):
class Meta:
model = Posts
fields = ('id','post_title','post_content',)
def create(self, validated_data):
posts = Posts.objects.create(
post_title=validated_data['post_title'],
post_content=validated_data['post_content'],
# gets the id of the current user
post_user=self.context['request'].user.id,
)
posts.save()
return posts
error is in line
queryset = Posts.objects.get(post_user=request.user.id)
here request is not define at class declaration time. Solution is you can override the get_queryset method.
class ListPosts(generics.ListCreateAPIView):
queryset = Posts.objects.all()
serializer_class = PostsSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self, *args, **kwargs):
return Posts.objects.filter(post_user=self.request.user)
Inherit CreateModelMixin's features inside PostsSerializer and try to define your create() method like def create(request, *args, **kwargs).
Finally, you can try to get user id using request.user.id.
For a better documentation, you can check https://www.django-rest-framework.org/api-guide/generic-views/.
Also check what are Mixins and why do we use it (if you do not know).
For a little and brief definition, Mixins are just class with methods that can be mostly inherited and used by our views.
If you have any doubt, please comment.

How can I list all the nodes in my database?

I'm trying to create a simple model Node and a simple web page that shows all of my nodes in a list. But it seems not working and everytime I change the code I got a new error. So I gave up and came to here.This is what I did:
I created a Node model:
class Node(models.Model):
ID = models.DecimalField(max_digits=9, decimal_places=6)
nb_solenoid = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
connexion = models.CharField(max_length=255)
def get_absolute_url(self):
return reverse("node:index", kwargs={"id": self.id})
with this form:
class NodeForm(forms.ModelForm):
ID = forms.DecimalField(initial=0)
nb_solenoid = forms.DecimalField(initial=1)
connexion = forms.CharField(required=False,
widget=forms.Textarea(
attrs={
"placeholder": "type of connexion"
}))
class Meta:
model = Node
fields = [
'ID',
'nb_solenoid',
'connexion'
]
And this is my views.py:
def index(request):
queryset = Node.objects.all()
context = {
"object_list": queryset
}
return render(request, "node/index.html", context)
This is my code in urls.py:
urlpatterns = [path('', views.index, name='index')]
When I call this url: http://localhost:8000/node I get this error now:
NoReverseMatch at /node
Reverse for 'index' with keyword arguments '{'id': 1}' not found. 1 pattern(s) tried: ['node$']
What is a NoReverseMatch error and how do I fix my problem? Let me say that I'm a Django beginner developer.
Thank you.
The issue is that your named url path node:index takes no arguments (presumably because that view is just listing out all the nodes, not a specific node), yet your model's get_absolute_url is trying to reverse the pattern with a kwarg of id. The core problem is your get_absolute_url method; however, you really probably also would benefit from just using class based generic views:
urls.py:
urlpatterns = [
path('nodes/', NodeList.as_view(), name="node-list"),
path('node/<id>/', NodeDetail.as_view(), name="node-detail")
]
view.py:
from django.views.generic import ListView, DetailView
from .models import Node
class NodeList(ListView):
model = Node
class NodeDetail(DetailView):
model = Node
models.py:
class Node(models.Model):
<snip>
def get_absolute_url(self):
return reverse("node-detail", kwargs={"id": self.id})
I really should have mentioned, documentation for class based generic views are at: https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-display/

How to implement django admin queryset filter with from extended user model class

I have extended Django default User model (just to save user state_id) class with following model.
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,)
state_id = models.IntegerField(blank=False)
def __str__(self):
return self.user.username
And now I want to filter the data by logged user's state_id. I tried to implement queryset filter but it does not do anything.
class VisVisitsAdmin(admin.ModelAdmin):
list_per_page = 10
list_display = ('visit_id','visit_no','user_name','mobile_number','program_name','state_name','district_name','block_name','school_name',)
list_filter = ('date_of_visit',)
def queryset(self, request):
qs = super(VisVisitsAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(state_id=request.user.UserProfileInfo.state_id)
def state_name(self, obj):
if obj.school_program:
return obj.school_program.school.cluster.block.district.state.name_of_state
state_name.short_description = 'state name'
You are trying to fetch the state_id from the userprofileinfo instance related to user object.
Everything seems fine but the instance is available as userprofileinfo and not UserProfileInfo , so change qs as :
qs.filter(state_id=request.user.userprofileinfo.state_id)

Resources