DRF Intermediary Table - POSTing data to the Intermediary Table - python-3.x

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

Related

How to filter Django serializer data?

I'm trying to filter data based on userName in JWT. This is how I've been trying to do it:
views.py:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user)
except ValidationError as v:
print("validation error", v)
return Response(person[0])
This works as I can get the Person data with print("Person: ", person[0]). The return Response(person[0]) however returns an error: TypeError: Object of type Person is not JSON serializable. I guess I could use a serializer class to return a valid JSON, am I right? I have this in my serializers.py:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
I just don't know how to use this with my view. If I use serializer instead of person = Person.objects.filter(userName=request.user), how is the filtering supposed to be done?
Please correct me if I'm not on right track at all.
You can add serializer to view, provide query result to serializer and get serialized data:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = TestSerializer # add serializer
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user).last()
data = self.serializer_class(instance=person).data # serialize query result
except ValidationError as v:
print("validation error", v)
raise v
return Response(data) # return serialized response
Here you can find some examples of using serializer with class based views.
Also note that your Person model might have security-sensitive fields like password, etc, so it's better to specify exact fields you need in serializer rather then use fields = '__all__'.
Are you using Django-Rest-Framework (based on your use of ModelSerializer)?
In pure Django, from the documentation on serializers, you can do something like:
from django.core import serializers
json_response = serializers.serialize("json", person[0])
return Response(json_response)
If you are using Django-Rest-Framework:
return Response(TestSerializer(person[0]).data)

DRF use nested serializer to create key object instead of array

Context
Say we take this example from the DRF relations guide.
# models.py
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ('album', 'order')
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
Using a serializer will get us this output
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
Will get us this output:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
Question
How can i use the serializer to get the output like this:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': {
1: {order: 1, title: 'Sunflower'},
2: {order:2, title: 'Whitetail'},
3: {order:3, title: 'Dinosaur Act'},
...
}
}
This way we have an object with tracks instead of a numeric array. So i can do this.props.album.tracks[2].title this instead of this.props.album.tracks.find(track => track.order == 2}).title in javascript.
``I have an use case in where this seems to be more convenient in Reactjs.
What i have tried
I thought about overriding the to_representation method. But i see that this will get me an recursive loop.
class TrackSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
print(self)
return '%s: { %s }' % (instance.order, self.to_representation(instance))
class Meta:
fields = '__all__'
model = Track
Furthermore i have searched and read the docs pretty well. But didn't find any solution for what i think should be a pretty logical solution to have out of the box. Making me think that i am wrong and missing something.
Thanks in advance.
Define a new TrackSerializer and use it in AlbumSerializer class as,
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('id', 'order', 'title')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['tracks'] = {track['id']: track for track in representation['tracks']}
return representation

How to create dynamic rest ModelSerializer?

I'm currently working on a big code base and i need to send emails from any potential module, that conduct in Circular Dependencies issues in python
so i tried to use apps.get_model() from django.apps but when serializers are declared the models are not ready.
So i'm trying to create a factory function who build the class at runtime instead of launch time
from rest_framework.serializers import ModelSerializer
def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
"""Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
"""
model_object = apps.get_model(model)
input_fields = fields
if options is None:
options = {}
class Serializer(ModelSerializer):
class Meta:
model = model_object
fields = input_fields
def create(self, validated_data):
# we won't permit to create data from thoses serializers.
raise NotImplementedError
# configure nested serializers.
for nested_field in nested_fields.values():
for key, nested_serializer_class in nested_field.items():
serializer_instance = nested_serializer_class(**options.get(key, {}))
print(model, key, serializer_instance)
setattr(Serializer, key, serializer_instance)
return Serializer
my tests models looks like
class Band(Model):
name = Charfield(max_length=255)
class Influencer(Model):
entity = Charfield(max_length=255)
class Submission(Model):
influencer = ForeignKey(Influencer, ...)
class Campaign(Model):
band = ForeignKey('band.Band', ...)
submissions = ManyToMany(Submission)
and my testing function is:
def test():
serializer = make_serializer(
model='submission.Campaign',
fields=['pk', 'submissions', 'band'],
options={'submissions': {'many': True}},
nested_fields={
'submissions': make_serializer(
model='submission.Submission',
fields=('influencer',),
nested_fields={
'influencer': make_serializer('influencer.Influencer', ('entity',))
},
),
'band': make_serializer('band.Band', ('name',))
}
)
return serializer
instead of having my fields correly with test()(Campaign.objects.last()).data i only got "pks" and my serialiser looks like:
Serializer():
pk = IntegerField(label='ID', read_only=True)
submissions = PrimaryKeyRelatedField(many=True, queryset=Submission.objects.all())
band = PrimaryKeyRelatedField(allow_null=True, queryset=Band.objects.all(), required=False)
i except and output like:
{
"pk": 1,
"band": {
"name": "BobMarley",
},
"submissions": [
{
"influencer": {"entity": "The influencer's name"}
}
]
}
but i got a ReturnDict containing:
{
"pk": 1,
"band": 523,
"submissions": [6, 7, 8]
}
thanks for your time
well after many headcaches i've found out that i CAN'T setattr on a class after it's declaration, so i use a trick based on a dict
def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
"""Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
"""
name = f'Serializer_{model}'
model_object = apps.get_model(model)
input_fields = fields
if options is None:
options = {}
def create(self, validated_data):
# we won't permit to create data from thoses serializers.
raise NotImplementedError
class Meta:
model = model_object
fields = input_fields
attrs = {"Meta": Meta}
# configure nested serializers.
for key, nested_serializer_class in nested_fields.items():
attrs[key] = nested_serializer_class(**options.get(key, {}))
attrs['create'] = create
return type(ModelDictSerializer)(name, (ModelDictSerializer,), attrs)
the syntax is something like:
campaign_serializer = make_serializer(
model='submission.Campaign',
fields=['pk', 'submissions', 'band'],
options={'submissions': {'many': True}},
submissions=make_serializer(
model='submission.Submission',
fields=('influencer',),
influencer=make_serializer('influencer.Influencer', ('entity',))
),
band=make_serializer('band.Band', ('name',))
)
and it's working like a charm:
Serializer_submission.Campaign(<Campaign: Campaign object (9665)>):
pk = IntegerField(label='ID', read_only=True)
submissions = Serializer_submission.Submission(many=True):
influencer = Serializer_influencer.Influencer():
entity = CharField(allow_blank=True, max_length=255, required=False)
band = Serializer_band.Band():
name = CharField(max_length=255)
i hope this will help someone else

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)

instance expected, got OrderedDict Django Rest Framework writable nested serializers

I am creating a survey kind of app, so i have three models Form, Questiosn, Choices[for multiple choice questions]
I followed this tutorial http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
It works fine for 1 level nested relations, but for 2 levels it gives
TypeError: 'Choice' instance expected, got OrderedDict([(u'title', u'option1')])
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
def create(self, validated_data):
choices_data = validated_data.pop("choices")
question = Question.objects.create(**validated_data)
for choice_data in choices_data:
Choice.objects.create(question=question, **choice_data)
return question
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active', 'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
Question.objects.create(form=form, **question_data)
return form
EDIT
Solved using the manual way, In FormSerializer override the create method,
#transaction.atomic
def create(self, validated_data):
try:
with transaction.atomic():
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
question = Question.objects.create(form=form,
title=question_data['title'],
type=question_data['type'],
required=question_data['required'])
if question.type == Question.RADIO or question.type == Question.CHECKBOX:
choices_data = question_data.pop('choices')
for choice_data in choices_data:
choice = Choice.objects.create(question=question, title=choice_data['title'])
return form
except Exception, e:
raise serializers.ValidationError("Cannot Save Form %s" % e)
I also struggled with this and I believe the proper way to handle this is:
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active',
'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
choices_data = question_data.pop('choices')
Question.objects.create(form=form, **question_data)
for choice_data in choices_data:
choice = Choice.objects.create(question=question, **choice_data)
return form
An easy way to screw this up is to not pop choices before creating the Question object. When you do that, you'll get an instance expected, got OrderedDict( 500 error.
Note also that you do not need to define create() on the QuestionSerializer. All child processing is done at the top level.

Resources