DRF clear m2m field via patch with SlugRelatedField - django-rest-framework

Currently i'm using a SlugRelatedField in my ModelSerializer to represent a m2m-Field.
manager = serializers.SlugRelatedField(
many=True,
queryset=get_user_model().objects.all(),
slug_field='username',
required=False,
allow_null=True)
if i send now a patch request with e.g.
'manager': 'user1' or 'manager: ['user1', 'user2'] it works as expected.
but how can i achieve, that the m2m field gets cleared, when i send an empty array 'manager': []
at the moment nothing happens, when i do so.
If i try to clear it with 'manager': '' i get an "object with username= does not exist" error.
maybe the SlugRelatedField isn't the right choice for me? i just wanted to achieve handling users with their username instead of their pk.
Edit:
I can do a PUT Request with not sending 'manager' at all to clear it.
Is there maybe no way to clear a m2m field with a PATCH Request?
r = requests.patch('http://localhost:8000/api/projects/dfo2/',
auth = ('lala','lilu'),
data = {'developer': []}
)
result:
{'manager': ['peter', 'fllow'], 'description': '','name_short': 'dfo2', 'developer': ['peter'], 'name': 'asdf'}
what works is:
r = requests.patch('http://localhost:8000/api/projects/dfo2/',
auth = ('lala','lilu'),
data = '{"developer": []}',
headers = {'Content-type': 'application/json'}
)
but why does this not work:
r = requests.patch('http://localhost:8000/api/projects/dfo2/',
auth = ('lala','lilu'),
data = {"developer": []},
headers = {'Content-type': 'application/x-www-form-urlencoded'}
)

Without seeing your full code it is hard to know, but generally it seems like you are not dealing with the data properly. One piece of advice I always give people with DRF is to think about serializers as a tool for giving and validating data, but not necessarily describing what to do with it.
With that in mind, I suggest you try something like the following, although again it is hard without seeing the models and full serializer code:
class Projects(models.Model):
managers = models.ManyToManyField('auth.Users')
class ProjectSerializer(serializers.Serializer):
managers = serializers.SlugRelatedField(
many=True,
source='managers.all',
queryset=get_user_model().objects.all(),
slug_field='username',
required=False,
allow_null=True)
def update(self, instance, validated_data):
[instance.managers.add(manager) for manager in validated_data['managers']
return instance

Related

is creating serializer mandatory for each View in django rest frame work

I am working on app where I didnt use serializer in one view I just want to ask am I doing something wrong.
I am getting a committee id in the url and in the body I am getting the usr_id whos status I want to change if someone sends a post request to this end point.
This is my url
path('committee/<int:id>/accept-request/', RequestAcceptView.as_view(), name="accept-request"),
this is my view.py
class RequestAcceptView(APIView):
def post(self, request, id):
user_id = request.data['user']
print("iddddd",user_id )
try :
approve_request = Member.objects.filter(
Q (com=Committee.objects.get(id=id))
).update(mem_status="fully_approved")
return Response(SuccessResponse(msg="Request accepted"))
except:
return Response(ErrorResponse(msg="invalid))
I want to know I am not using serializer here for any purpose, is it fine? should I remove serializer file?
No, it is not when you are using ApiView. Generic and ModelView requires them. You should also think about if you want to have auto generated documentation you need the serializer and it will also perform validation (because you are not using the id field, but request.data.
If request.data and I'd are the same, then you might want to delete the serializer

drf-spectacular: how to show the primary key in examples section of Swagger

I'm trying to show the primary key in the examples section of Swagger, I'm using drf-spectacular and my code looks like:
Serializers.py
class SerializerExample(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id','name')
Views.py
class BooksBulkUpdate(APIView):
#extend_schema(
request=SerializerExample(many=True),
responses={200:''},
)
def put(self, request, format=None):
with transaction.atomic():
for data in request.data:
book = Book.objects.get(pk=data['id'])
serializer = SerializerExample(book, data=data, partial=True)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response()
Only the name field is showing:
The only solution that I found was using an inline serializer which is not the ideal solution because if I update my book serializer I'd have to remember to update also this inline serializer. I wonder if there is a better way of doing this.
AFAIK swagger shows input request schema.
For example, you want to add new person and your model is
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
So you allowed to set only name parameter
Even if you post
{
"id": "someUUID",
"name": "NAME",
}
id will be ignored and Django create it automatically by own logic (because it is read only)
But you can set id field writeable:
class SerializerExample(serializers.ModelSerializer):
id = serializers.UUIDField(write_only=True)
name = serializers.CharField(write_only=True)
class Meta:
model = Person
fields = ('id','name')
write_only=True means that field will be active when you saving new data and receiving id from request json.
In opposite read_only=True will print id field at response (if you trying get data) but ignore it when you saving new data.
So you try to describe API for data adding, and of course that is not allow to set id field in request json.
Not sure if this theory applicable to your case, but hope that will be helpful.

How to integrate/install drf-yasg to Django Rest project? Redoc/Swagger appears empty

I'm trying to integrate the drf-yasg to my Django Rest project. I installed the library via pip and added these code lines to the url.py as below.
schema_view = get_schema_view(
openapi.Info(
title="Costifier API",
default_version='v1',
description="Costifier API'ye hoşgeldiniz.",
terms_of_service="https://costifier.sfmyazilim.com",
contact=openapi.Contact(email="info#sfmyazilim.com"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('', index),
path('admin/', admin.site.urls),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), #<-- Here
path('api/', include('sfmAPI.urls')),
]
One of my views is;
class PredictionView(views.APIView):
permission_classes = [AllowAny]
throttle_classes = [AnonymousUserThrottle]
queryset = Prediction.objects.all()
serializer_class = PredictionSerializer
def post(self, request, format=None):
serializer = PredictionSerializer(data=request.data)
if serializer.is_valid():
input_map_dict = json.loads(serializer.validated_data['input_map'])
username = serializer.validated_data['customer_name']
prediction_results = SmartRegression.smart_predict(username,
serializer.validated_data['model_name'],
input_map_dict,
isMember(username))
result = {
'inputs': serializer.data,
'error': '0',
'message': 'Successful',
'predicted_value': prediction_results[0],
'confidence': prediction_results[1],
'feature_importance': prediction_results[2]
}
return Response(status=status.HTTP_200_OK, data=result)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My /redoc page is created. However, it has no content. It just has the APIView names such as below.
How can I fill the documentation?
The reason is you're using APIView instead of a generic view.
In the Django-Rest-Framework docs for schemas it mentions the below:
Note: The automatic introspection of components, and many operation parameters relies on the relevant attributes and methods of GenericAPIView: get_serializer(), pagination_class, filter_backends, etc. For basic APIView subclasses, default introspection is essentially limited to the URL kwarg path parameters for this reason.
A work around for this if you want to stick to using APIView instead of a generic view, is to use #swagger_auto_schema decorator (from drf-yasg) above each of your views. It's a bit of a pain but you should be able to find a few post on stackoverflow around using this. An example is:
DRF YASG CustomizingDRF YASG Customizing
Django-Rest-Framework does have a manual method of setting the parameters but most people would prefer to stick to the AutoSchema doing the work. I haven't been able to find much on how to use it for a beginner either. Hence drf-yasg seems a good way to go.
I believe for your post view above the below decorator should be what you're looking for just above the post method:
#swagger_auto_schema(
request_body=PredictionSerializer,
responses={
'200': 'OK Request',
'400': "Bad Request"
},
)
Also you want to add something like the below to the very top of your view since it will pull through as the description in the schema/documentations.
'''
def post(self, request, format=None):
"Description of your View"
'''
...

Django rest framework - Raising exception / Handling empty results while filtering

I have a user profile class and am checking if a user exists and if not want to create that user.
Am using the filter class for userprofile so that the client can call :
http://localhost:8000/users/?email=a#b.com
and if the result is empty will create a user with the email address.
Is there a way to intercept the query result and raise an exception when its empty and handle that to create the user.
If there is a better way would like to be corrected as well.
class UserQueryFilter(django_filters.rest_framework.FilterSet):
email = django_filters.CharFilter(name="user__email")
username = django_filters.CharFilter(name="user__username")
class Meta:
model = UserProfile
fields = ['email', 'username']
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
filter_class = UserQueryFilter
Any help is appreciated.
Thanks
Anand
Django Rest Framework provide a functionality that is disabled by default. Maybe it could give you another approach to resolve your problem: PUT as create
In other hand, if you really need to create the user through a GET request with a querystring, you can use a MethodFilter from django-filters, for example:
class UserFilters(FilterSet):
user = MethodFilter(action='filter_user')
class Meta:
model = User
fields = ['user']
def filter_user(self, queryset, value):
if not value:
# Here Raise Exception
else:
# Check if the user exists, if not create it
users = queryset.filter(Q(username=value) | Q(email=value))
if not users.exists:
user = User.objects.create(...)
return queryset.filter(pk=user.id)
else:
return users
Hope this can help you. I'm not pretty sure about it works in that exact way but it's the idea.
Personally, I recommend you that try to execute that tasks through a more appropriate request like POST or PUT and manage in the corresponding method.

POSTing to URL of one resource to create another, different resource

In my REST API I have two entities: Test and TestRun. I want to be able to send a POST request to create a TestRun (with the appropriate TestRun fields), but the URL of this request must be api/v1/test/{id}/start instead of api/v1/testrun.
I know that using #detail_route I can customise the URL, but then the request is still sent to api/v1/test/{id}:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
#detail_route(methods=['post'], url_path='start')
def start_test(self, request, pk=None):
pass
class TestRunViewSet(viewsets.ModelViewSet):
queryset = TestRun.objects.all()
serializer_class = TestRunSerializer
Perhaps some highly customised router is needed here?
OK, I have the basic example. I think you have few problems, so first things first:
My views:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
#detail_route(methods=['post'], url_path='start', serializer_class=TestRunSerializer)
def start_test(self, request, pk=None):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
# add here TestRun object
return Response(serializer.data, status=status.HTTP_200_OK)
class TestRunViewSet(viewsets.ModelViewSet):
queryset = TestRun.objects.all()
serializer_class = TestRunSerializer
My urls:
router = SimpleRouter()
router.register('test', TestViewSet)
router.register('test-run', TestRunViewSet)
urlpatterns = router.urls
and settings urls:
urlpatterns = [
url(r'^api/v1/', include('droute.urls'))
]
In this scenario you have full CRUD for Test and TestRun models - one is under api/vi/test and second in api/v1/test-run;
The detail_route decorator creates for you additional route: /api/v1/test/:id/start
But this do not mean that CRUD under api/v1/test-run is no longer accessible.
If you do not want to do not allow creation on api/v1/test-run you should use there ReadOnlyModelViewSet as a base for TestRunViewSet - this will allow only GET on the list endpoint: api/v1/test-run and on the details endpoint: api/v1/test-run//
You do not need to make magic in routers - as in example SimpleRouter is enough for that case.
Things are getting little bit more complicated if you want to make nested routers. You can search stackoverflow - there were many articles about that. But to be honest I would discourage you to use nested routers, I never feel that working with this is a pleasure :) You can check here:
https://github.com/alanjds/drf-nested-routers
I think (but I have little or nono information) that the best API for you would be something like this:
/api/v1/test -> CRUD for TEST
/api/v1/test/:id/start -> start the test POST
/api/v1/test/:id/runs -> get the runs list GET (list_route on TestViewSet or Nested router)
/api/v1/test/:id/runs/:run_id -> get the run details GET (and here is a problem - because it implies that you need nesting :(, or some custom view attached to the urls;)
Happy coding, hope this helps.

Resources