Add additional data to JSON response in ListCreateAPIView - django

class ItemListView(ListCreateAPIView):
model = Item
serializer_class = ItemSerializer # model serializer
def get_queryset(self):
return self.model.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Is there any chance to add additional data to JSON response in get method?

You could override ItemSerializer's to_representation() method, as
class ItemSerializer(serializers.ModelSerializer):
# your fields
def to_representation(self, instance):
data = super().to_representation(instance).copy()
data.update({"key": "value"})
return data

response = self.list(..)
response.data['hello'] = 'world'
return response

Related

Django: How to clean data when list_editable in admin page?

I have a model which has a field 'keywords'. When I use a form to create/modify records, I am able to clean this field and then save it.
class ILProjectForm(forms.ModelForm):
class Meta:
models = ILProject
fields = '__all__'
def clean_keywords(self):
k = self.cleaned_data.get('keywords')
if k:
k = ','.join([a.strip() for a in re.sub('\\s+', ' ', k).strip().split(',')])
return k
However, I am not sure how to run clean() to update the data when I am using the list_editable option in the admin page.
I tried something like this bit I get an error saying I cannot set an attribute. What is the correct way to update the data after it has been cleaned?
class MyAdminFormSet(BaseModelFormSet):
def clean(self):
print(type(self.cleaned_data))
recs = []
for r in self.cleaned_data:
if r['keywords']:
r['keywords'] = ','.join([a.strip() for a in re.sub('\\s+', ' ', r['keywords']).strip().split(',')])
print(r['keywords'])
recs.append(r)
self.cleaned_data = recs <-- this part is problematic.
class ILProjectAdmin(...)
...
def get_changelist_formset(self, request, **kwargs):
kwargs['formset'] = MyAdminFormSet
return super().get_changelist_formset(request, **kwargs)
It should be like this:
class ILProjectAdmin(...)
...
def get_changelist_form(self, request, **kwargs):
return ILProjectForm
Please refer this tip: Django: Access request object from admin's form.clean()
class ProjectRequestForm(forms.ModelForm):
class Meta:
model = ProjectRequest
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ProjectRequestForm, self).__init__(*args, **kwargs)
def clean(self):
if self.request.user.has_perm('access_role'):
raise ValidationError(f'No permission', code='invalid')
class ProjectRequestAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(ProjectRequestAdmin, self).get_form(request, obj, **kwargs)
class AdminFormWithRequest(form):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return form(*args, **kwargs)
return AdminFormWithRequest
def get_changelist_form(self, request, **kwargs):
class AdminFormWithRequest(ProjectRequestForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return ProjectRequestForm(*args, **kwargs)
return AdminFormWithRequest

How to remove put method in RetrieveUpdateAPIView from drf-spectacular API documentation?

I have the following view:
class PersonalInfos(generics.RetrieveUpdateAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
Everything works fine in the view, but the problem is that I am using drf-spectacular and it is showing me a PUT method in the documentation that we won't be needing in the API. My questions is, how can I customize drf-spectacular to not include a PUT method in the documentation?
You may use the #extend_schema decorator to exclude one or more methods from the schema generated, as shown below.
#extend_schema(methods=['PUT'], exclude=True)
I solved this using RetrieveAPIView instead of UpdateRetrieveAPIView and I have extended it with to include a PATCH method. RetrieveAPIView will handle a PATCH method perfectly and would not show automatically an UPDATE request in the API documentation. Here is the new code:
class PersonalInfos(generics.RetrieveAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
this should exclude the put method in drf-yasg
class MyView(generics.RetrieveUpdateAPIView):
#swagger_auto_schema(auto_schema=None)
def put(self, request, *args, **kwargs):
return
source: https://drf-yasg.readthedocs.io/en/stable/custom_spec.html#excluding-endpoints

Passing Model objects into function from viewset

I have a Django viewset, where I have a function, that sends an email, whenever the viewset, is used.
I override the create function, to send the email, where I have a function, that sends the email to the user.
I want to pass in some arguments, to the function, to display in the email (done with Django's template engine)
class ContactRequestViewSet(viewsets.ModelViewSet):
queryset = ContactRequest.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = ContactRequestSerializer
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
send_email()
return response
#function to send email
def send_email():
htmly = get_template('email.html')
d = {'company_name': 'dodo'} #i want this dictionary, to contain the attributes from the viewset
send_mail(
subject='Test email',
message='',
from_email='test#email.com',
recipient_list=['test#email.com'],
html_message= htmly.render(d)
)
right now I just have a sample placeholder as d but here I want to pass in attributes from the serializers/model, that the user provided, I tried passing in the serializer, and accessing its attributes, but I don't know how to do this the proper way
You can access response.data after this line
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
which will hold the serializer's data. Yet if you want an actual instance of your model this will not be sufficient and you will need to hack your way around a little bit. The CreateModelMixin that is used in ModelViewSet of django-rest-framework has the following methods:
class CreateModelMixin:
# ... more stuff here
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
so you could override the perform_create method to save the instance into an attribute of your ContactRequestViewSet like this:
def perform_create(self, serializer):
self.instance = serializer.save()
then in the create method you could do something like this:
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs) # This calls perform_create internally
send_email(self.instance)
return response

Redirect to url from "post-only" view

I have the following setup for a combination of a DetailView and a FormView:
class EventBookView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
view = EventBookBaseView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = EventBookFormView.as_view()
return view(request, *args, **kwargs)
class EventBookBaseView(DetailView):
template_name = "event_book.html"
model= Event
context_object_name = 'event'
class EventBookFormView(SingleObjectMixin, FormView):
template_name = "event_book.html"
form_class = PersonalInfoForm
model = Event
context_object_name = 'event'
def post(self , request , *args , **kwargs):
#do stuff
...
return redirect('user_bookings')
Unfortunately, the redirect to the url with the name 'user_bookings' is not working. How can I redirect to that url?
thanks!
You can use success_url, when Form is properly processed then view is redirected.
eg.
class MyFormView(FormView):
success_url = reverse_lazy('user_bookings')
EDIT: changed reverse to reverse_lazy
A POST method should not return a redirection.
If you need so, try to use HttpResponseRedirect.
Like:
return HttpResponseRedirect('/user_bookings/')

Implementing HATEOAS in Django REST framework

I'm trying to implement REST API that implements HATEOAS using Django REST Framework (DRF). I know that DRF itself doesn't support HATEOAS and I didn't find any examples of such implementation. Therefore I'm not sure on which level of DRF (Serializers / Views / Renderers) should I implement this functionality. Do you have some experiences, thoughts, insights or examples which could help me to start? Thank you.
Here is my solution implemented on level of Viewset:
from rest_framework import viewsets
from rest_framework import generics
from serializers import EventSerializer, BandSerializer
from rest_framework.response import Response
from rest_framework import status
from collections import OrderedDict
class LinksAwarePageNumberPagination(PageNumberPagination):
def get_paginated_response(self, data, links=[]):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data),
('_links', links),
]))
class HateoasModelViewSet(viewsets.ModelViewSet):
"""
This class should be inherited by viewsets that wants to provide hateoas links
You should override following methodes:
- get_list_links
- get_retrieve_links
- get_create_links
- get_update_links
- get_destroy_links
"""
pagination_class = LinksAwarePageNumberPagination
def get_list_links(self, request):
return {}
def get_retrieve_links(self, request, instance):
return {}
def get_create_links(self, request):
return {}
def get_update_links(self, request, instance):
return {}
def get_destroy_links(self, request, instance):
return {}
def get_paginated_response(self, data, links=None):
assert self.paginator is not None
return self.paginator.get_paginated_response(data, links)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data, links=self.get_list_links())
serializer = self.get_serializer(queryset, many=True)
return Response(OrderedDict([
('results', serializer.data),
('_links', self.get_list_links(request))
]))
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = serializer.data
data['_links'] = self.get_retrieve_links(request, instance)
return Response(data)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
data = serializer.data
data['_links'] = self.get_create_links(request)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
data = serializer.data
data['_links'] = self.get_update_links(request, instance)
return Response(serializer.data)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
data = {'_links': self.get_destroy_links(request, instance)}
self.perform_destroy(instance)
return Response(data, status=status.HTTP_204_NO_CONTENT)
Example of usage:
class EventViewSet(HateoasModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
def get_list_links(self, request):
return {
'self': {'href': request.build_absolute_uri(request.path)},
'related_link1': {'href': '...'},
}
I know it is a relevant old thread, but for those who came to this now, there is a library django-rest-framework-json-api, which uses the json:api specification. This specification is completely compatible with the HATEOAS implementation.
I am also quite new to this; here is a tutorial that I used as a guide by the Columbia university which I found really helpful.
On the plus side I find it is compatible with api documentation using swagger, using the drf-yasg and drf-yasg-json-api libraries.