Reading and creating nested relation in single serializer django - python

I have two models Category and Products, and a classic one to many relatoin between them. Each product belongs to a category.
I am using Django-Rest-Framework.
I am using ModelViewSet as a ViewSet and ModelSerializer as a Serializer.
I created the ProductViewSet which has the full CRUD opertions.
What I am trying to achieve is to include the relation in the READ operations ( list , retrieve ). The response should look something like that.
{
"id" : 2,
"name" : "foo product" ,
"category" : {
"id" : 4,
"name" : "foo relation"
}
}
So I set the depth field in Meta equal to 1.
class ProductSerializer(ModelSerializer):
class Meta:
model = Product
exclude = ('createdAt',)
depth = 1
What makes the category read only.But I want it to be writable in the other actions (POST , PUT).
I know I can solve the problem using two serializes but this is a repetitive problem and I don't want to write two serializes for each model I have.
I tried to add category_id field in the serializer but I had to override the create method to make it work correctly.
And again I am trying to write less code. because I would end up overriding all of the app serializers.
class ProductSerializer(ModelSerializer):
category_id = PrimaryKeyRelatedField(queryset=Category.objects.all())
class Meta:
model = Product
exclude = ('createdAt',)
depth = 1
def create(self, validated_data):
category_id = validated_data.pop('category_id').id
validated_data['category_id'] = category_id
return super().create(validated_data)
This is my first Django framework and I am trying to make it as neat as possible.

Related

Django Rest Framework: source / re-name without re-confirming the field settings

I'd like to change many fields name in DRF ModelSerializer without the need to re-typing the fields.
According a post on SO (ref), one can re-name a field name within the serializer by using source, such as:
newName = serializers.CharField(source='old_name')
However, this method takes away the benefits of using a ModelSerializer as you essentially do the work twice. This become heavy when you have many fields adhering to one internal naming convention but want to display another naming convention within the API.
in my case, I have a model field such as:
product_uid = models.UUIDField(primary_key=False, unique=True, default=uuid.uuid4, editable=False)
In the API, I'd like the field to be called 'uid'.
If I would do the following:
uid = serializers.UUIDField(source=product_uid)
would result in editable=True
Is there a way to reference to a ModelField and keep its definition intact according the Model (as you normally do when using serializers.ModelSerializer) but only change the name, e.g. something like: uid = serializers.ModelField(source=product_uid) ?
If you want your field not to be editable, you can use the read_only parameter (https://www.django-rest-framework.org/api-guide/fields/#read_only)
You can try:
uid = serializers.UUIDField(source=product_uid, read_only=True)
You can also use the ModelSerializer using extra_kwargs :
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['product_uid', 'field_a', 'field_b']
extra_kwargs = {'product_uid': {'source': 'uid'}} # Add `'read_only': True` if needed
If you have many fields, you can generate extra_kwargs programmatically

DRF Serializer field for intermediate mode

I am making am app to control the presence of students. I have 4 models:
class Student(models.Model):
name = models.CharField(max_length=70)
class Justification(models.Model):
name = models.CharField(max_length=70)
class Session(models.Model):
date = models.DateTimeField()
present = models.ManyToManyField(Student)
absences = models.ManyToManyField(Student, related_name='absences_set', through='Absence')
class Absence(models.Model):
session = models.ForeignKey(Session, on_delete=models.CASCADE)
atleta = models.ForeignKey(Student, on_delete=models.CASCADE)
justification = models.ForeignKey(Justification, on_delete=models.CASCADE)
The models have more fields and different names (I translated the names to English) but this is basically it.
I am using DRF framework to make an API. I have setup the endpoints (and serializers) for Student, Justification and Absence but I can't figure out how to make the serializer for the Session model. I want it to work when someone makes the following POST (I only need an endpoint to create Sessions) request (I am using a ViewSet for the view):
{
"date": "2019-02-01T10:08:52-02:00"
"present": [
2
],
"absences": [
{
"student": 1,
"justification": 1
}
]
}
But the absences are not created. How can I make this nested relationship work?
ps: I can only make one request that's why I don't want to make one request to create a Session and then many requests to create Absences and need it all together. If there is a way to create all of them on the same request (but not only the same JSON object) I am okay with this solution
If i understand properly you want to create corresponding absences and season at same Season end-point. I think Justification and Student both model serve same, they are just student's instance and keep student information if i am not wrong. So i don't think there is actually any need to keep Justfication model. Corresponding absences ( students ) in Season Model need to ask for Justification. So my advice to keep model structure as like these
class Student(models.Model):
name = models.CharField(max_length=70)
class Session(models.Model):
date = models.DateTimeField()
present = models.ManyToManyField(Student)
absences = models.ManyToManyField(Student, related_name='absences_set', through='Absence')
class Absence(models.Model):
session = models.OneToOneField(Session, on_delete=models.CASCADE) # You can also keep Foreign-key
atleta = models.ForeignKey(Student, on_delete=models.CASCADE)
And then there are two possible way to create Absence model instance corresponding to Season Post endpoint. We can overwrite the post method of SeasonViewset and write our logic there or even can overwrite the SeasonSrealizer-create method to do same.
My preferable option is to overwrite post method of SeasonViewset. And these can be done as like following - over writing DRF CreateMixins
class SeasonViewSet(viewsets.ModelViewSet):
# your declare serializers and others thing
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
season_instance = self.perform_create(serializer)
# creating Absence's instance and you need to add other fields as necessary
Absence.objects.create(season=season_instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

How can I resolve custom fields for django models using django_graphene?

Looking at graphene_django, I see they have a bunch of resolvers picking up django model fields mapping them to graphene types.
I have a subclass of JSONField I'd also like to be picked up.
:
# models
class Recipe(models.Model):
name = models.CharField(max_length=100)
instructions = models.TextField()
ingredients = models.ManyToManyField(
Ingredient, related_name='recipes'
)
custom_field = JSONFieldSubclass(....)
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = ???
I know I could write a separate field and resolver pair for a Query, but I'd prefer it to be available as part of the schema for that model.
What I realize I could do:
class RecipeQuery:
custom_field = graphene.JSONString(id=graphene.ID(required=True))
def resolve_custom_field(self, info, **kwargs):
id = kwargs.get('id')
instance = get_item_by_id(id)
return instance.custom_field.to_json()
But -- this means a separate round trip, to get the id then get the custom_field for that item, right?
Is there a way I could have it seen as part of the RecipeType schema?
Ok, I can get it working by using:
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = graphene.JSONString(resolver=lambda my_obj, resolve_obj: my_obj.custom_field.to_json())
(the custom_field has a to_json method)
I figured it out without deeply figuring out what is happening in this map between graphene types and the django model field types.
It's based on this:
https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers
Same function name, but parameterized differently.

How to use PrimaryKeyRelatedField to update categories on a many-to-many relationship

The Django Rest Framework has a PrimaryKeyRelatedField which lists ID's on my many-to-many relationship with categories...
class CatalogueItemsSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='catalogue_item_detail')
name = serializers.CharField(min_length=2, max_length=60)
categories = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
I would like to override the create() get a lot of posted ID's (traditionally DRF wants me to send URL's) on the categories field.
JSON POST:
{
"name": "test",
"categories": [1,2]
}
Serializer Create:
def create(self, validated_data):
categories = validated_data.pop('categories', None)
print(categories) # This shows and empty/None even when ID are posted!
The issue I have is categories will be empty above, I assume because PrimaryKeyRelatedField is set to read_only=True but this HAS to be true on according to DRF.
My question is... How to use PrimaryKeyRelatedField with validated_data.pop to get a list of ID's I submitted?
I could use self.context.get("request").data['categories'] but this is nasty and requires me to turn it into a list + other stuff.
For anyone else having this issue I found a solution that allows PrimaryKeyRelatedField to be read/writeable. Just add queryset
categories = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all())
It will still filter on the correct relationship which is a bit confusing.

Django model creation jointly unique fields

I'm trying to create a model for Django that looks like this:
class Device(Model):
UDID = CharField(length=64, primary_key=True)
# more irrelevant stuff
class DeviceProperty(Model):
device = ForeignKey(Device)
name = CharField(length=255)
value = CharField(length=255)
readOnly = BooleanField()
But then, for data-integrity reasons, a single device shouldn't have two properties with the same name. So I would need to make the device and name fields of DeviceProperty jointly unique.
A simple way to achieve this would be by having a composite primary key on the two fields, but this raises a few issues, and more importantly isn't supported by Django.
I haven't found anything about this in the Django documentation. Have I missed anything?
unique_together is what you want.
class DeviceProperty(Model):
…
class Meta:
unique_together = ['device', 'name']

Categories