Django, updating DB column, rest_framework - django

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"),
#
]

Related

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.

how to use django rest filtering with mongoengine for list filtering

views.py
from __future__ import unicode_literals
from rest_framework_mongoengine.viewsets import ModelViewSet as MongoModelViewSet
from app.serializers import *
from rest_framework_mongoengine.generics import *
from rest_framework import filters
def index_view(request):
context = {}
return TemplateResponse(request, 'index.html', context)
class ToolViewSet(MongoModelViewSet):
serializer_class = ToolSerializer
my_filter_fields = ('crop', 'district','taluka','circle','year',)
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
for field in self.my_filter_fields: # iterate over the filter fields
field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
if field_value:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = Tool.objects.all()
filtering_kwargs = self.get_kwargs_for_filtering() # get the fields with values for filtering
if filtering_kwargs:
queryset = Tool.objects.filter(**filtering_kwargs) # filter the queryset based on 'filtering_kwargs'
return queryset
This is my code.
it's work for filtering exactly what we want to serach.
for example : http://api/tool/?district=Nasik&crop=banana
But, it's not working for list fileds
for example : http://api/tool/?district=Nasik&district=Pune
this gives output: [] (empty)
How to get this filter working to get all district "Nasik" OR district "Pune"
Can you please help me what I have to changes in my code for get data if I search http://api/tool/?crops=guava,banana
Or http:../tool/?district=Nasik,Pune
gives me correct output?
I recommend taking a look at django-rest-framework-filters. You can then use stuff like __in in your query params...
But if you want to proceed with your approach, I would add the logic in your get_kwargs_for_filtering method somehow like this:
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
for field in self.my_filter_fields: # iterate over the filter fields
field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
if field_value:
if ',' in field_value: # put your queryParams into an array and use the built-in django filter method '__in'
filtering_kwargs[field + '__in'] = field_value.split(',')
else:
filtering_kwargs[field] = field_value
return filtering_kwargs
With this, a call to http://api/tool/?crops=guava,banana should produce the desired behavior.
I can't test if it works now, but this should lead you to the right direction.
Although the question is somewhat old. This is a question that is still important and valid in later versions of Django, DRF and MongoEngine.
At the time of this answer I am running
Django==4.1.3
djangorestframework==3.14
django-rest-framework-mongoengine==3.4.1
mongoengine==0.24.2
django-filter==22.1
# patched version of django-mongoengine-filter to support Django 4.0
# https://github.com/oussjarrousse/django-mongoengine-filter
# Pull request https://github.com/barseghyanartur/django-mongoengine-filter/pull/16
django-mongoengine-filter>=0.3.5
The idea in this answer is to add filtering support to django-rest-framework-mongoengine using django-mongoengine-filter that is an replacement or an extension to django-filter and should work the same way as django-filter.
First let's edit the project/settings.py file. Find the INSTALLED_APPS variable and make sure the following "Django apps" are added:
# in settings.py:
INSTALLED_APPS = [
# ...,
"rest_framework",
"rest_framework_mongoengine",
"django_filters",
# ...,
]
the app django_filters is required to add classes related to filtering infrastructure, and other things including html templates for DRF.
Then in the variable REST_FRAMEWORK we need to edit the values associated with the key: DEFAULT_FILTER_BACKENDS
# in settings.py:
REST_FRAMEWORK = {
# ...
"DEFAULT_FILTER_BACKENDS": [
"filters.DjangoMongoEngineFilterBackend",
# ...
],
# ...
}
DjangoMongoEngineFilterBackend is a custom built filter backend that we need to add to the folder (depending on how you structure your project) in the file filters
# in filters.py:
from django_filters.rest_framework.backends import DjangoFilterBackend
class DjangoMongoEngineFilterBackend(DjangoFilterBackend):
# filterset_base = django_mongoengine_filter.FilterSet
"""
Patching the DjangoFilterBackend to allow for MongoEngine support
"""
def get_filterset_class(self, view, queryset=None):
"""
Return the `FilterSet` class used to filter the queryset.
"""
filterset_class = getattr(view, "filterset_class", None)
filterset_fields = getattr(view, "filterset_fields", None)
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
element = queryset.first()
if element:
queryset_model = element.__class__
assert issubclass(
queryset_model, filterset_model
), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
str(queryset_model),
)
return filterset_class
if filterset_fields and queryset is not None:
MetaBase = getattr(self.filterset_base, "Meta", object)
element = queryset.first()
if element:
queryset_model = element.__class__
class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
model = queryset_model
fields = filterset_fields
return AutoFilterSet
return None
This custom filter backend will not raise the exceptions that the original django-filter filter backend would raise. The django-filter DjangoFilterBackend access the key model in QuerySet as in queryset.model, however that key does not exist in MongoEngine.
Maybe making it available in MongoEngine should be considered:
https://github.com/MongoEngine/mongoengine/issues/2707
https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues/294
Now we can add a custom filter to the ViewSet:
# in views.py
from rest_framework_mongoengine.viewsets import ModelViewSet
class MyModelViewSet(ModelViewSet):
serializer_class = MyModelSerializer
filter_fields = ["a_string_field", "a_boolean_field"]
filterset_class = MyModelFilter
def get_queryset(self):
queryset = MyModel.objects.all()
return queryset
Finally let's get back to filters.py and add the MyModelFilter
# in filters.py
from django_mongoengine_filter import FilterSet, StringField, BooleanField
class MyModelFilter(FilterSet):
"""
MyModelFilter is a FilterSet that is designed to work with the django-filter.
However the original django-mongoengine-filter is outdated and is causing some troubles
with Django>=4.0.
"""
class Meta:
model = MyModel
fields = [
"a_string_field",
"a_boolean_field",
]
a_string_field = StringFilter()
a_boolean_field = BooleanFilter()
That should do the trick.

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