django-rest save array of data to db - django

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.

Related

Serializing context on Django CBV?

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

Django, updating DB column, rest_framework

A bit stuck at updating column in DB. I am sending put request to update a column. But an error returns.
assert isinstance(response, HttpResponseBase), ( AssertionError:
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <class 'NoneType'>
here is the front end. sending request.
updateInvoceProject(id, company_name){
return axios.put(API_HANDLER.database_api + "api/v1/update-project", {id, company_name})
},
serializer.py
class InvocesSerializer(serializers.ModelSerializer):
class Meta:
model = Invoces
fields = ("__all__")
view
#csrf_exempt
#api_view(["PUT"])
def update(request):
if request.method == "PUT":
serializer = InvocesSerializer(data=request.data)
if serializer.is_valid():
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
urls
urlpatterns = [
#
path("api/v1/update-project", invocesView.update, name="update-project"),
#
]
But in the end, the error I mentioned above is popping. Am I missing something here?
This happens because you are not returning anything when the serialized data is not valid.
you can simply make it like this, to make sure that it is going to return something in all cases.
#csrf_exempt
#api_view(["PUT"])
def update(request):
# if request.method == "PUT":
# You don't have to check for method, since you already defined it
# in api_view(...) decorator.
serializer = InvocesSerializer(data=request.data)
# Raises a ValidatinException which will be sent as a 400 response.
serializer.is_valid(raise_exception=True)
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
Better Solution
I suggest that you use DRF's UpdateAPIView (Do it the DRF way :D), in order to avoid getting into such errors, and also to avoid doing all the validation, and serialization by hand.
like the following:
1. Keep your InvocesSerializer, and create another one just for Updating company_name
# Inside serializers.py
class InvocesCompanyNameUpdateSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
return InvocesSerializer(instance).to_representation(instance)
class Meta:
model = Invoces
fields = ('company_name',)
2 . Create an UpdateAPIView class for that serializer
# Inside views.py
class InvoiceUpdateCompanyNameAPIView(UpdateAPIView):
http_method_names = ['put'] # This is only to allow PUT method on this view.
serializer_class = InvocesCompanyNameUpdateSerializer
queryset = Invoces.objects.all()
3 . Now append that view with a re_path to your urls.
# Inside urls.py
urlpatterns = [
#
# you have to add "pk" url path variable, so DRF use it internally to identify
# which object you want to update.
re_path(r"api/v1/update-project/(?P<pk>[\d]+)/",
invocesView.InvoiceUpdateCompanyNameAPIView.as_view(),
name="update-project"),
#
]

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)

Unable to get a non-model field in the validated_data of a Django Rest Framework serializer

I have an ItemCollection and Items in my Django models and I want to be able to remove Items from the collection through the UI. In a REST PUT request I add an extra boolean field deleted for each Item to signal that an Item should be deleted.
The correct way to handle this seems to be in the update method of the Serializer.
My problem is that this non-model deleted field gets removed during validation, so it is not available anymore. Adding deleted as a SerializerMethodField did not help. For now I get my deleted information from the initial_data attribute of the Serializer, but that does not feel right.
My current example code is below. Does anybody know a better approach?
Models:
class ItemCollection(models.Model):
description = models.CharField(max_length=256)
class Item(models.Model):
collection = models.ForeignKey(ItemCollection, related_name="items")
Serializers:
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from models import Item, ItemCollection
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
class ItemCollectionSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=False)
class Meta:
model = ItemCollection
def update(self, instance, validated_data):
instance.description = validated_data['description']
for item, item_obj in zip(
self.initial_data['items'], validated_data['items']):
if item['delete']:
instance.items.filter(id=item['id']).delete()
return instance
class ItemCollectionView(APIView):
def get(self, request, ic_id):
item_collection = get_object_or_404(ItemCollection, pk=ic_id)
serialized = ItemCollectionSerializer(item_collection).data
return Response(serialized)
def put(self, request, ic_id):
item_collection = get_object_or_404(ItemCollection, pk=ic_id)
serializer = ItemCollectionSerializer(
item_collection, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
And an example of the json in the PUT request:
{
"id": 2,
"items": [
{
"id": 3,
"collection": 2,
"delete": true
}
],
"description": "mycoll"
}
You can add non-model fields back by overwriting the to_internal_value fn:
def to_internal_value(self, data):
internal_value = super(MySerializer, self).to_internal_value(data)
my_non_model_field_raw_value = data.get("my_non_model_field")
my_non_model_field_value = ConvertRawValueInSomeCleverWay(my_non_model_field_raw_value)
internal_value.update({
"my_non_model_field": my_non_model_field_value
})
return internal_value
Then you can process it however you want in create or update.
If you're doing a PUT request, your view is probably calling self.perform_update(serializer). Change it for
serializer.save(<my_non_model_field>=request.data.get('<my_non_model_field>', <default_value>)
All kwargs are passed down to validated_data to your serializer.
Make sure to properly transform incoming value (to boolean, to int, etc.)

Adding root element to json response (django-rest-framework)

I am trying to determine the best way to add a root element to all json responses using django and django-rest-framework.
I think adding a custom renderer is the best way to accomplish what I want to achieve and this is what I have come up with so far:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
#override the render method
def render(self, data, accepted_media_type=None, renderer_context=None):
#call super, as we really just want to mess with the data returned
json_str = super(CustomJSONRenderer, self).render(data, accepted_media_type, renderer_context)
root_element = 'contact'
#wrap the json string in the desired root element
ret = '{%s: %s}' % (root_element, json_str)
return ret
The tricky part now is dynamically setting the root_element based on the view that render() is being called from.
Any pointers/advice would be greatly appreciated,
Cheers
For posterity, below is the final solution. It has grown slightly from the original as it now reformats paginated results as well.
Also I should have specified before, that the reason for the JSON root element is for integration with an Ember front end solution.
serializer:
from rest_framework.serializers import ModelSerializer
from api.models import Contact
class ContactSerializer(ModelSerializer):
class Meta:
model = Contact
#define the resource we wish to use for the root element of the response
resource_name = 'contact'
fields = ('id', 'first_name', 'last_name', 'phone_number', 'company')
renderer:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
"""
Override the render method of the django rest framework JSONRenderer to allow the following:
* adding a resource_name root element to all GET requests formatted with JSON
* reformatting paginated results to the following structure {meta: {}, resource_name: [{},{}]}
NB: This solution requires a custom pagination serializer and an attribute of 'resource_name'
defined in the serializer
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
#determine the resource name for this request - default to objects if not defined
resource = getattr(renderer_context.get('view').get_serializer().Meta, 'resource_name', 'objects')
#check if the results have been paginated
if data.get('paginated_results'):
#add the resource key and copy the results
response_data['meta'] = data.get('meta')
response_data[resource] = data.get('paginated_results')
else:
response_data[resource] = data
#call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
pagination:
from rest_framework import pagination, serializers
class CustomMetaSerializer(serializers.Serializer):
next_page = pagination.NextPageField(source='*')
prev_page = pagination.PreviousPageField(source='*')
record_count = serializers.Field(source='paginator.count')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
# Takes the page object as the source
meta = CustomMetaSerializer(source='*')
results_field = 'paginated_results'
Credit to ever.wakeful for getting me 95% of the way there.
Personally, I wanted to add meta data to every api request for a certain object, regardless of whether or not it was paginated. I also wanted to simply pass in a dict object that I defined manually.
Tweaked Custom Renderer
class CustomJSONRenderer(renderers.JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
# Name the object list
object_list = 'results'
try:
meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict')
except:
meta_dict = dict()
try:
data.get('paginated_results')
response_data['meta'] = data['meta']
response_data[object_list] = data['results']
except:
response_data[object_list] = data
response_data['meta'] = dict()
# Add custom meta data
response_data['meta'].update(meta_dict)
# Call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
Parent Serializer and View Example
class MovieListSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
meta_dict = dict()
meta_dict['foo'] = 'bar'
class MovieViewSet(generics.ListAPIView):
queryset = Movie.objects.exclude(image__exact = "")
serializer_class = MovieListSerializer
permission_classes = (IsAdminOrReadOnly,)
renderer_classes = (CustomJSONRenderer,)
pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10