Serializing context on Django CBV? - django

I'm having issues serializing a 3rd party package (django-organizations) as I want to receive the context in JSON.
The class itself looks like this:
class OrganizationUserMixin(OrganizationMixin, JSONResponseMixin):
"""Mixin used like a SingleObjectMixin to fetch an organization user"""
user_model = OrganizationUser
org_user_context_name = 'organization_user'
def get_user_model(self):
return self.user_model
def get_context_data(self, **kwargs):
kwargs = super(OrganizationUserMixin, self).get_context_data(**kwargs)
kwargs.update({self.org_user_context_name: self.object,
self.org_context_name: self.object.organization})
return kwargs
def get_object(self):
""" Returns the OrganizationUser object based on the primary keys for both
the organization and the organization user.
"""
if hasattr(self, 'organization_user'):
return self.organization_user
organization_pk = self.kwargs.get('organization_pk', None)
user_pk = self.kwargs.get('user_pk', None)
self.organization_user = get_object_or_404(
self.get_user_model().objects.select_related(),
user__pk=user_pk, organization__pk=organization_pk)
return self.organization_user
And I'm trying to pass this custom JSONResponseMixin to my OrganizationUserMixin class:
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(
self.get_data(context),
**response_kwargs
)
def get_data(self, context):
print(context)
return context
And then overriding the render_to_response in OrganizationUserMixin as such:
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
If I print context
It looks something like this
# {
# 'object': <OrganizationUser: Erik (MyOrgName)>,
# 'organizationuser': <OrganizationUser: Erik (MyOrgName)>,
# 'organization': <Organization: MyOrgName>,
# 'view': <organizations.views.OrganizationUserDetail object at 0x1091a3ac0>,
# 'organization_user': <OrganizationUser: Erik (MyOrgName)>
# }
The error message I get is TypeError: Object of type OrganizationUser is not JSON serializable
How can I serialize the context in my JSONResponseMixin?

You have two options here, either use Django rest framework (DRF) or implement functions that performs serialization for the models.
Option 1
DRF is a more sustainable solution as you grow the API side of your application, as it would abstract most of de/serialization work, and provide you with alot of other useful functionalities, such as Routers, ViewSets, and other.
Example Code
# serializers.py
from rest_framework import serializers
class OrganizationUserSerializer(serializers.ModelSerializer):
class Meta:
model = OrganizationUser
fields = '__all__'
# views.py
from rest_framework import generics
class OrganizationUser(generics.RetrieveModelMixin):
queryset = OrganizationUser.objects.all()
serializer_class = OrganizationUserSerializer
Option 2
That being said, if you the JsonResponseMixin is sufficient for most of your needs, and your application is not mainly reliant on the API, you can get away with just adding serialization functions for your models and calling them in your JsonResponseMixin.get_data()
Example code:
# Models.py
class OrganizationUser(models.Model):
...
def to_json(self):
# assuming you have a field name and organization
return {"name": self.name, "organization": self.organization.to_json()}
# mixins.py
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(
self.get_data(context),
**response_kwargs
)
def get_data(self, context):
data = {}
for key, val in context:
if hasattr(val, "to_json"):
data[key] = val.to_json()
else:
data[key] = val
return data

Related

Django Queryset - rename original values and re-use original value names

I'm completing a challenge for a job and I'm a little confused with this endpoint's response.
I have the following models:
Attribute
AttributeValue
ProductAttribute
I need to get all attributes that are linked to a given product ID. I have managed to get the values but I can't give them the correct names in the response. The relevant code is in the get_attributes_from_product function:
# src/api/viewsets/attribute.py
from django.db.models import F
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from api import errors
from api.models import Attribute, AttributeValue, ProductAttribute
from api.serializers import AttributeSerializer, AttributeValueSerializer, AttributeValueExtendedSerializer
import logging
logger = logging.getLogger(__name__)
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
"""
list: Return a list of attributes
retrieve: Return a attribute by ID.
"""
queryset = Attribute.objects.all()
serializer_class = AttributeSerializer
#action(detail=False, url_path='values/<int:attribute_id>')
def get_values_from_attribute(self, request, *args, **kwargs):
"""
Get Values Attribute from Attribute ID
"""
attribute_id = int(kwargs['attribute_id'])
# Filter queryset to find all values for attribute
response = AttributeValue.objects.filter(attribute_id=attribute_id).values(
'attribute_value_id', 'value')
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
"""
Get all Attributes with Product ID
"""
product_id = int(kwargs['product_id'])
# Filter all attributes in product
response = ProductAttribute.objects.filter(product_id=product_id).annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value')).values(
attribute_name=F('attribute_value__attribute_id__name'),
attribute_value_id=F('attribute_value_id'),
attribute_value=F('attribute_value__value')
)
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
If I change attribute_value_id=F('attribute_value_id') and attribute_value=F('attribute_value__value') to attribute_value_id1=F('attribute_value_id') and attribute_value1=F('attribute_value__value') the response is successful and all the values are correct, but obviously the key names are wrong.
It should return the following keys: attribute_name, attribute_value_id and attribute_value.
The django ORM will not overwrite existing model attributes with the names of annotated fields.
In order to use names that collide with existing model attributes, you need to use a
serializer class or just format the queryset rows before returning the response.
An example of using a serializer can be found in the django rest-framework
documentation.
Without using a queryset, you can use a list of dict objects in the response. This
is a shortcut though, and using a serializer would probably be better.
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
def render_product_attribute_row(self, row):
row["attribute_value_id"] = row.pop("tmp_attribute_value_id")
row["attribute_value"] = row.pop("tmp_attribute_value")
return row
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
product_id = int(kwargs['product_id'])
queryset = ProductAttribute.objects.filter(product_id=product_id)
queryset = queryset.annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value'),
)
queryset = queryset.values(
attribute_name=F('attribute_value__attribute_id__name'),
tmp_attribute_value_id=F('attribute_value_id'),
tmp_attribute_value=F('attribute_value__value'),
)
if queryset.exists():
status_code = 200
else:
status_code = 204
response = [self.render_product_attribute_row(row) for row in queryset]
return Response(response, status_code)

HttpResponse error django generic template

I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset

django-rest save array of data to db

hi im trying to save a form data into db.
i provided print of requset.data for you as you see requirement have two items.
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,... in table row...
where is my mistake ... thanks
also print of request.data.get('requirement') will retun second item
this is print of request.data in sever:
<QueryDict: {'requirement': ['hello', 'bye'], 'audience': ['adasd'], 'achievement': ['asdasd'], 'section': ['410101010'], 'title': ['asdasd'], 'mini_description': ['asdad'], 'full_description': ['asdasd'], 'video_length': ['10101'], 'video_level': ['P'], 'price': [''], 'free': ['true'], 'image': [<InMemoryUploadedFile: p.gif (image/gif)>]}>
view:
class StoreCreateAPIView(generics.CreateAPIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def post(self, request, *args, **kwargs):
if request.method == 'POST':
print(request.data)
file_serial = ProductSerializer(data=request.data, context={"request": request})
if file_serial.is_valid():
file_serial.save(author_id=request.user.id)
requirement = request.data['requirement']
audience = request.data.get('audience')
achievement = request.data.get('achievement')
sections = request.data.get('section')
print(request.data['requirement'])
pid = file_serial.data.get('product_id')
for item in requirement :
req = ProductRequiredItems(
item = item,
product_id = pid
)
req.save()
First of all, overriding CreateAPIView's post method in your code makes your custom perform_create method useless, unless you explicitly call it from within your customized post method. Otherwise it will never be called.
also print of request.data.get('requirement') will retun second item
It does return the last item as per Django docs for QueryDict.__getitem__(key).
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,...
This is because of the above functionality of QueryDict. When you do:
requirement = request.data['requirement']
# requirement = request.__getitem__('requirement')
it will call QueryDict.__getitem__(key) method and thus return only the last item (which is string in you example).
Answer:
You can simply override CreateAPIView's create method, and let your serializer handle all the rest.
# views.py
from django.shortcuts import render
from rest_framework import generics, status
from rest_framework.response import Response
from .models import MyObj
from .serializers import MyObjSerializer
class MyObjView(generics.CreateAPIView):
serializer_class = MyObjSerializer
queryset = MyObj.objects.all()
def create(self, request, *args, **kwargs):
# The QueryDicts at request.POST and request.GET will be immutable
# when accessed in a normal request/response cycle.
# To get a mutable version you need to use QueryDict.copy().
req_data = request.data.copy()
requirements = req_data.pop('requirement')
serializers_data = []
for requirement in requirements:
req_data ['requirement'] = requirement
serializer = self.get_serializer(data=req_data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
serializers_data.append(serializer.data)
return Response(serializers_data, status=status.HTTP_201_CREATED)
# serializers.py
from rest_framework import serializers
from .models import MyObj
class MyObjSerializer(serializers.ModelSerializer):
class Meta:
model = MyObj
fields = '__all__'
Have a look at DRF CreateModelMixin. It defines create & perform_create methods that are used used in CreateAPIView upon executing POST request. I just altered them slightly to handle your specific case.
Hope it helps.

Django rest framework, use different serializers in the same ModelViewSet

I would like to provide two different serializers and yet be able to benefit from all the facilities of ModelViewSet:
When viewing a list of objects, I would like each object to have an url which redirects to its details and every other relation appear using __unicode __ of the target model;
example:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "emilio",
"accesso": "CHI",
"membri": [
"emilio",
"michele",
"luisa",
"ivan",
"saverio"
]
}
When viewing the details of an object, I would like to use the default HyperlinkedModelSerializer
example:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "http://127.0.0.1:8000/database/utenti/3/",
"accesso": "CHI",
"membri": [
"http://127.0.0.1:8000/database/utenti/3/",
"http://127.0.0.1:8000/database/utenti/4/",
"http://127.0.0.1:8000/database/utenti/5/",
"http://127.0.0.1:8000/database/utenti/6/",
"http://127.0.0.1:8000/database/utenti/7/"
]
}
I managed to make all this work as I wish in the following way:
serializers.py
# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
membri = serializers.RelatedField(many = True)
creatore = serializers.RelatedField(many = False)
class Meta:
model = models.Gruppi
# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Gruppi
views.py
class DualSerializerViewSet(viewsets.ModelViewSet):
"""
ViewSet providing different serializers for list and detail views.
Use list_serializer and detail_serializer to provide them
"""
def list(self, *args, **kwargs):
self.serializer_class = self.list_serializer
return viewsets.ModelViewSet.list(self, *args, **kwargs)
def retrieve(self, *args, **kwargs):
self.serializer_class = self.detail_serializer
return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)
class GruppiViewSet(DualSerializerViewSet):
model = models.Gruppi
list_serializer = serializers.ListaGruppi
detail_serializer = serializers.DettaglioGruppi
# etc.
Basically I detect when the user is requesting a list view or a detailed view and change serializer_class to suit my needs. I am not really satisfied with this code though, it looks like a dirty hack and, most importantly, what if two users request a list and a detail at the same moment?
Is there a better way to achieve this using ModelViewSets or do I have to fall back using GenericAPIView?
EDIT:
Here's how to do it using a custom base ModelViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action,
self.serializers['default'])
class GruppiViewSet(MultiSerializerViewSet):
model = models.Gruppi
serializers = {
'list': serializers.ListaGruppi,
'detail': serializers.DettaglioGruppi,
# etc.
}
Override your get_serializer_class method. This method is used in your model mixins to retrieve the proper Serializer class.
Note that there is also a get_serializer method which returns an instance of the correct Serializer
class DualSerializerViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == 'list':
return serializers.ListaGruppi
if self.action == 'retrieve':
return serializers.DettaglioGruppi
return serializers.Default # I dont' know what you want for create/destroy/update.
You may find this mixin useful, it overrides the get_serializer_class method and allows you to declare a dict that maps action and serializer class or fallback to the usual behavior.
class MultiSerializerViewSetMixin(object):
def get_serializer_class(self):
"""
Look for serializer class in self.serializer_action_classes, which
should be a dict mapping action name (key) to serializer class (value),
i.e.:
class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
serializer_class = MyDefaultSerializer
serializer_action_classes = {
'list': MyListSerializer,
'my_action': MyActionSerializer,
}
#action
def my_action:
...
If there's no entry for that action then just fallback to the regular
get_serializer_class lookup: self.serializer_class, DefaultSerializer.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(MultiSerializerViewSetMixin, self).get_serializer_class()
This answer is the same as the accepted answer but I prefer to do in this way.
Generic views
get_serializer_class(self):
Returns the class that should be used for the serializer. Defaults to returning the serializer_class attribute.
May be overridden to provide dynamic behavior, such as using different serializers for reading and write operations or providing different serializers to the different types of users.
the serializer_class attribute.
class DualSerializerViewSet(viewsets.ModelViewSet):
# mapping serializer into the action
serializer_classes = {
'list': serializers.ListaGruppi,
'retrieve': serializers.DettaglioGruppi,
# ... other actions
}
default_serializer_class = DefaultSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)
Regarding providing different serializers, why is nobody going for the approach that checks the HTTP method? It's clearer IMO and requires no extra checks.
def get_serializer_class(self):
if self.request.method == 'POST':
return NewRackItemSerializer
return RackItemSerializer
Credits/source: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718
Based on #gonz and #user2734679 answers I've created this small python package that gives this functionality in form a child class of ModelViewset. Here is how it works.
from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2
class MyViewSet(CustomSerializerViewSet):
serializer_class = DefaultSerializer
custom_serializer_classes = {
'create': CustomSerializer1,
'update': CustomSerializer2,
}
Just want to addon to existing solutions. If you want a different serializer for your viewset's extra actions (i.e. using #action decorator), you can add kwargs in the decorator like so:
#action(methods=['POST'], serializer_class=YourSpecialSerializer)
def your_extra_action(self, request):
serializer = self.get_serializer(data=request.data)
...
Although pre-defining multiple Serializers in or way or another does seem to be the most obviously documented way, FWIW there is an alternative approach that draws on other documented code and which enables passing arguments to the serializer as it is instantiated. I think it would probably tend to be more worthwhile if you needed to generate logic based on various factors, such as user admin levels, the action being called, perhaps even attributes of the instance.
The first piece of the puzzle is the documentation on dynamically modifying a serializer at the point of instantiation. That documentation doesn't explain how to call this code from a viewset or how to modify the readonly status of fields after they've been initated - but that's not very hard.
The second piece - the get_serializer method is also documented - (just a bit further down the page from get_serializer_class under 'other methods') so it should be safe to rely on (and the source is very simple, which hopefully means less chance of unintended side effects resulting from modification). Check the source under the GenericAPIView (the ModelViewSet - and all the other built in viewset classes it seems - inherit from the GenericAPIView which, defines get_serializer.
Putting the two together you could do something like this:
In a serializers file (for me base_serializers.py):
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Adding this next line to the documented example
read_only_fields = kwargs.pop('read_only_fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# another bit we're adding to documented example, to take care of readonly fields
if read_only_fields is not None:
for f in read_only_fields:
try:
self.fields[f].read_only = True
exceptKeyError:
#not in fields anyway
pass
Then in your viewset you might do something like this:
class MyViewSet(viewsets.ModelViewSet):
# ...permissions and all that stuff
def get_serializer(self, *args, **kwargs):
# the next line is taken from the source
kwargs['context'] = self.get_serializer_context()
# ... then whatever logic you want for this class e.g:
if self.action == "list":
rofs = ('field_a', 'field_b')
fs = ('field_a', 'field_c')
if self.action == “retrieve”:
rofs = ('field_a', 'field_c’, ‘field_d’)
fs = ('field_a', 'field_b’)
# add all your further elses, elifs, drawing on info re the actions,
# the user, the instance, anything passed to the method to define your read only fields and fields ...
# and finally instantiate the specific class you want (or you could just
# use get_serializer_class if you've defined it).
# Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
kwargs['read_only_fields'] = rofs
kwargs['fields'] = fs
return MyDynamicSerializer(*args, **kwargs)
And that should be it! Using MyViewSet should now instantiate your MyDynamicSerializer with the arguments you'd like - and assuming your serializer inherits from your DynamicFieldsModelSerializer, it should know just what to do.
Perhaps its worth mentioning that it can makes special sense if you want to adapt the serializer in some other ways …e.g. to do things like take in a read_only_exceptions list and use it to whitelist rather than blacklist fields (which I tend to do). I also find it useful to set the fields to an empty tuple if its not passed and then just remove the check for None ... and I set my fields definitions on my inheriting Serializers to 'all'. This means no fields that aren't passed when instantiating the serializer survive by accident and I also don't have to compare the serializer invocation with the inheriting serializer class definition to know what's been included...e.g within the init of the DynamicFieldsModelSerializer:
# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....
NB If I just wanted two or three classes that mapped to distinct actions and/or I didn't want any specially dynamic serializer behaviour, I might well use one of the approaches mentioned by others here, but I thought this worth presenting as an alternative, particularly given its other uses.
With all other solutions mentioned, I was unable to find how to instantiate the class using get_serializer_class function and unable to find custom validation function as well. For those who are still lost just like I was and want full implementation please check the answer below.
views.py
from rest_framework.response import Response
from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer
class ProjectViewSet(viewsets.ModelViewSet):
action_serializers = {
'generate_id': ProjectIDGeneratorSerializer,
'create': ProjectCreateSerializer,
}
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(ProjectViewSet, self).get_serializer_class()
# You can create custom function
def generate_id(self, request):
serializer = self.get_serializer_class()(data=request.GET)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
def create(self, request, **kwargs):
serializer = self.get_serializer_class()(data=request.data)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
serializers.py
import random
from rest_framework import serializers
from project.models import Project
class ProjectIDGeneratorSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
class Meta:
fields = ['projectName']
def validate(self, attrs):
project_name = attrs.get('projectName')
project_id = project_name.replace(' ', '-')
return {'projectID': project_id}
class ProjectCreateSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
projectID = serializers.CharField(write_only=True)
class Meta:
model = Project
fields = ['projectName', 'projectID']
def to_representation(self, instance: Project):
data = dict()
data['projectName'] = instance.name
data['projectID'] = instance.projectID
data['createdAt'] = instance.createdAt
data['updatedAt'] = instance.updatedAt
representation = {
'message': f'Project {instance.name} has been created.',
}
return representation
def validate(self, attrs):
print('attrs', dict(attrs))
project_name = attrs.get('projectName')
project_id = attrs.get('projectID')
if Project.objects.filter(projectID=project_id).first():
raise serializers.ValidationError(f'Project with ID {project_id} already exist')
project = Project.objects.create(projectID=project_id,
name=project_name)
print('user', self.context['user'])
project.user.add(self.context["user"])
project.save()
return self.to_representation(project)
urls.py
from django.urls import path
from .views import ProjectViewSet
urlpatterns = [
path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]
models.py
# Create your models here.
from django.db import models
from authentication.models import User
class Project(models.Model):
id = models.AutoField(primary_key=True)
projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
user = models.ManyToManyField(User)
name = models.CharField(max_length=255, blank=False)
createdAt = models.DateTimeField(auto_now_add=True)
updatedAt = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
You can map your all serializers with the action using a dictionary in class and then get them from "get_serializer_class" method. Here is what I am using to get different serializers in different cases.
class RushesViewSet(viewsets.ModelViewSet):
serializer_class = DetailedRushesSerializer
queryset = Rushes.objects.all().order_by('ingested_on')
permission_classes = (IsAuthenticated,)
filter_backends = (filters.SearchFilter,
django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter)
pagination_class = ShortResultsSetPagination
search_fields = ('title', 'asset_version__title',
'asset_version__video__title')
filter_class = RushesFilter
action_serializer_classes = {
"create": RushesSerializer,
"update": RushesSerializer,
"retrieve": DetailedRushesSerializer,
"list": DetailedRushesSerializer,
"partial_update": RushesSerializer,
}
def get_serializer_context(self):
return {'request': self.request}
def get_serializer_class(self):
try:
return self.action_serializer_classes[self.action]
except (KeyError, AttributeError):
error_logger.error("---Exception occurred---")
return super(RushesViewSet, self).get_serializer_class()

Django rest framework add field in serializer for using with post method only

am trying to build a serializer using HyperlinkedModelSerializer, yet I have a scenario where I want to add a field which does not exist in the model but I will require the value for validating the transaction, I found the below snippet
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework import serializers
import logging
# initiate logger
logging.getLogger(__name__)
class PostHyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
"""
Options for PostHyperlinkedModelSerializer
"""
def __init__(self, meta):
super(PostHyperlinkedModelSerializerOptions, self).__init__(meta)
self.postonly_fields = getattr(meta, 'postonly_fields', ())
class PostHyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
_options_class = PostHyperlinkedModelSerializerOptions
def to_native(self, obj):
"""
Serialize objects -> primitives.
"""
ret = self._dict_class()
ret.fields = {}
for field_name, field in self.fields.items():
# Ignore all postonly_fields fron serialization
if field_name in self.opts.postonly_fields:
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
ret[key] = value
ret.fields[key] = field
return ret
def restore_object(self, attrs, instance=None):
model_attrs, post_attrs = {}, {}
for attr, value in attrs.iteritems():
if attr in self.opts.postonly_fields:
post_attrs[attr] = value
else:
model_attrs[attr] = value
obj = super(PostHyperlinkedModelSerializer,
self).restore_object(model_attrs, instance)
# Method to process ignored postonly_fields
self.process_postonly_fields(obj, post_attrs)
return obj
def process_postonly_fields(self, obj, post_attrs):
"""
Placeholder method for processing data sent in POST.
"""
pass
class PurchaseSerializer(PostHyperlinkedModelSerializer):
""" PurchaseSerializer
"""
currency = serializers.Field(source='currency_used')
class Meta:
model = DiwanyaProduct
postonly_fields = ['currency', ]
Am using the above class, PostHyperlinkedModelSerializer, but for some reason the above is causing a problem with the browsable api interface for rest framework. Field labels are disappearing, plus the new field "currency" is not showing in the form (see screenshot below for reference). Any one can help on that?
Whoever wrote the code probably didn't need browsable api (normal requests will work fine).
In order to fix the api change to_native to this:
def to_native(self, obj):
"""
Serialize objects -> primitives.
"""
ret = self._dict_class()
ret.fields = self._dict_class()
for field_name, field in self.fields.items():
if field.read_only and obj is None:
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
method = getattr(self, 'transform_%s' % field_name, None)
if callable(method):
value = method(obj, value)
if field_name not in self.opts.postonly_fields:
ret[key] = value
ret.fields[key] = self.augment_field(field, field_name, key, value)
return ret