Docs says only mapping of GET
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
tests.py:
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
factory = APIRequestFactory()
view = RoleViewSet.as_view()
url = reverse('api:role-list')
data = {
'name': 'FirstAdmin',
'type': Role.RoleType.admin,
'company': 1,
}
request = factory.post(url, data=data, format='json')
force_authenticate(request, user=aoo)
response = view(request)
assert 201 == response.data
viewsets.py
class RoleViewSetPermission(permissions.BasePermission):
message = 'Only Manager or Admin are allowed'
def has_permission(self, request, view):
user = request.user
return user.has_perm('roles.add_role') \
and user.has_perm('roles.change_role') \
and user.has_perm('roles.delete_role')
class RoleViewSet(viewsets.ModelViewSet):
permission_classes = (RoleViewSetPermission,)
queryset = Role.objects.all()
serializer_class = RoleSerializer
filter_backends = (filters.DjangoFilterBackend, SearchFilter)
filter_class = RoleFilter
search_fields = ('name', 'description', 'user__username', 'company__name', 'company__name_th')
def filter_queryset(self, queryset):
try:
company = self.request.user.user_profile.companyappid.company
except AttributeError:
logger.error(f'{self.request.user} has AttributeError')
return Role.objects.none()
else:
logger.info(f'{self.request.user} is {company} member')
return queryset.filter(company=company)
Trackback:
cls = <class 'poinkbackend.apps.roles.api.viewsets.RoleViewSet'>, actions = None, initkwargs = {}
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The suffix initkwarg is reserved for identifying the viewset type
# eg. 'List' or 'Instance'.
cls.suffix = None
# actions must not be empty
if not actions:
> raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
E TypeError: The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`
../../.pyenv/versions/3.6.3/envs/poink/lib/python3.6/site-packages/rest_framework/viewsets.py:55: TypeError
Question:
How to do force_authenticate and request.post to the viewsets?
I have no problem with get. It has an answer already in the SO
References:
http://www.django-rest-framework.org/api-guide/viewsets/
I have to use APIClient not APIRequestFactory.
I though it has only one way to do testing.
Here is my example.
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
client = APIClient()
client.force_authenticate(user=aoo)
url = reverse('api:role-list')
singh = Company.objects.get(name='Singh')
data = {
'name': 'HairCut',
'type': Role.RoleType.admin,
'company': singh.id, # Must be his companyid. Reason is in the RoleSerializer docstring
}
response = client.post(url, data, format='json')
assert 201 == response.status_code
Related
I want to create a service using Django Rest API. I have a function. The result of this function should return 2 values and I should return these values in JSON API format.
The function will work like this. I will receive the features_list as a parameter and I will use it to create a result and display it as a service in json format in def prediction function.
I created a sample API (I guess) it is class PredictionSet in my views but I actually want to make service the def prediction function in my views.
I cannot understand how to apply it. I am so confused. Any help would be appreciated.
models.py
class Liquidity(models.Model):
pred_y = models.CharField(max_length=600)
score = models.FloatField()
views.py
class PredictionSet(viewsets.ModelViewSet):
queryset = Liquidity.objects.all()
serializer_class = LiquiditySerializer
def prediction(request, features_list):
filename = config.FINAL_MODEL_PATH
classifier = pickle.load(open(filename, 'rb'))
scale_file = config.SCALER_PATH
scaler = pickle.load(open(scale_file, 'rb'))
sample = np.array(features_list).reshape(1, -1)
sample_scaled = scaler.transform(sample)
pred_y = classifier.predict(sample_scaled)
prob_y = classifier.predict_proba(sample_scaled)
if prob_y[0][1] < 0.5:
score = 0
elif prob_y[0][1] <= 0.69:
score = 1
else:
score = 2
pred_y = pred_y[0]
prediction_obj = Liquidity.objects.get_or_create(pred_y=pred_y, score=score)
prediction_result = prediction_obj.pred_y
prediction_score = prediction_obj.score
context = {
'prediction_result ': prediction_result,
'prediction_score ': prediction_score,
}
return context
serializer.py
class LiquiditySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Liquidity
fields = '__all__'
If you want to return custom JSON from a ModelViewset in DRF, you can override .list() and/or .retrieve() like this :
from rest_framework import status
from rest_framework.response import Response
class PredictionSet(viewsets.ModelViewSet):
queryset = Liquidity.objects.all()
serializer_class = LiquiditySerializer
# Your custom function definition
def prediction(self, request, features_list):
# The content
def retrieve(self, request, *args, **kwargs):
result = prediction(...) # Call your custom service and got result
# Return the result as JSON (url = /api/v1/predictions/1) an object
return Response({'data': result}, status=status.HTTP_200_OK)
def list(self, request, *args, **kwargs):
result = prediction(...) # Call your custom service and got result
# Return the result as JSON (url = /api/v1/predictions) a list of objects
return Response({'data': result}, status=status.HTTP_200_OK)
For more details follow this link
I am sending files from the frontend and I want to save file location in database I am using DRF.
Currently, I am trying to save only one file but i am getting error
"'dict' object has no attribute '_committed'"
Can Anyone please give me some suggestions on what should I do?
Request Body I am getting for on file:-
{'id': "Please provide Site Contact person's Name", 'service': {'id': 3, 'service_name': 'Site
Survey', 'cost_per_kw': 1, 'min_size': 101, 'upto_size': 10000, 'tat': 5, 'creation_date':
None}, 'question': 3, 'creation_date': None, 'real_estate': 0, 'answer': 'VIkrant Patil',
'attachement': [{'name': 'Vikrant Patil - Offer Letter (1).pdf', 'size': 172518, 'url':
'blob:http://localhost:8000/cad9de5d-a12b-41a2-90f7-38deff67fd0f', '_file': {}}], 'project':
189}
My view.py logic for saving only one file:-
class ServiceProjectAnswerViewSet(viewsets.ModelViewSet):
model = ServiceProjectAnswer
serializer_class = ServiceProjectAnswerSerializer
# parser_classes = (MultiPartParser, FormParser)
def create(self, request, *args, **kwargs):
print(request.data)
instance = None
answers = request.data.copy()
data = {}
attachements = answers['attachement']
print(attachements[0])
data['answer'] = answers['answer']
data['project'] = answers['project']
data['question'] = answers['question']
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance = serializer.save()
# for attachement in attachements:
AnswerAttachments.objects.create(
answer = instance,
# attachement = attachement
attachement = attachements[0]
)
except Exception as e:
print(e)
return Response(str(e), status=400)
return Response(serializer.data, status=201)
My view.py logic for saving only multiple files :-
In my responce when I a sending multiple files I am sending list of Objects every object has values which i want that is why I have use for loop to iterate.
def create(self, request, *args, **kwargs):
instance = None
print(request.data)
answers = request.data.copy()
for answer in answers:
data = {}
attachements = answer['attachement']
print(attachements,"attachement")
data['answer'] = answer['answer']
data['project'] = answer['project']
data['question'] = answer['question']
print(data,"data")
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance=serializer.save()
for attachement in attachements:
print(attachement)
# attach = {}
# attach['name'] = attachement['name']
# attach['size'] = attachement['size']
# attach['url'] = attachement['url']
print(type(attachement))
AnswerAttachments.objects.create(
answer=instance,
attachement=attachement
)
except Exception as e:
print(e)
return Response(str(e), status=400)
else:
return Response({'message': ('Failed to save answers')}, status=400)
return Response('Answers are saved successfully!!', status=200)
Try something like this,
const formData = new FormData();
formData.append('file', file);
const requestOptions = {
method: "POST",
body:formData
};
Means,try to send body of request in multiform instead of application/json.
I have a class-based view:
class Create(View):
note_id = None
http_method_names = ['post', 'patch']
default_title = "You fool! This was left empty"
default_body = "Why did you leave this blank :("
def dispatch(self, *args, **kwargs):
method = self.request.POST.get('_method', '').lower()
print('method = ', method)
if method == 'patch':
return self.patch(*args, **kwargs)
elif method == 'post':
self.post(*args, **kwargs)
return super(Create, self).dispatch(*args, **kwargs)
def post(self, note_id):
date = datetime.date.today()
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note = Note(note_title=title, note_body=body, publish_date=date, edit_date=None)
note.save()
return HttpResponseRedirect(reverse('notes:note'))
def patch(self, note_id):
note = Note.objects.get(id=note_id)
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# if something changed
if title != note.note_title or body != note.note_body:
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note.note_title = title
note.note_body = body
note.edit_date = datetime.date.today()
note.save()
return HttpResponseRedirect(reverse('notes:note'))
and in url.py I have
urlpatterns = [
path('<int:note_id>/create/', views.Create.as_view(), name='create'),
path('<int:note_id>/edit/', views.Create.as_view(), name='edit')
]
Previously, with function-based views the note_id would just be passed to the function automatically. I can not figure out the equivalent of this in class based views. I've tried explictiely passing note_id to each function, but that did not work. I tried included Create.as_view(note_id=note_id) in my url.py, but to no avail.
Currently, running this code I get:
post() got multiple values for argument 'note_id'
As mentioned in the comment above, you need to access these values through self.kwargs. e.g:
class Create(View):
note_id = None # Delete this line
...
# delete the dispatch method - no need to overwrite this.
# Don't include note_id as an argument, but request should be an argument
def post(self, request):
note_id = self.kwargs['note_id']
....
def put(self, request): # Likewise here
note_id = self.kwargs['note_id']
....
It doesn't look like you need to write a custom dispatch method. If all you are doing is calling the appropriate method, then the dispatch method that comes for free with View is just fine :)
I am creating a REST-API with django and the django rest framework, therefore I am using ReadOnlyModelViewSet. In my unit test for testing create / POST method, I am expecting HTTP_405_METHOD_NOT_ALLOWED but actually it is HTTP_403_FORBIDDEN. For the update / PUT it is HTTP_405 as expected. Is this behavior the default behavior or do I have a dumb bug?
And yes the user is authenticated.
The Viewset:
class StudentSolutionReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StudentSolution.objects.all()
serializer_class = StudentSolutionSerializer
def get_permissions(self):
if self.request.method == 'POST':
return (permissions.IsAuthenticated(), )
return (permissions.IsAuthenticated(), IsStudentSolutionOwnerOrAdmin(),)
def get_queryset(self):
if self.request.user.is_staff:
return StudentSolution.objects.all(
course_assignment=self.kwargs['course_assignment_pk']
).order_by('student__last_name', 'course_assignment__due_date')
return StudentSolution.objects.filter(
student=self.request.user,
course_assignment=self.kwargs['course_assignment_pk']
).order_by('course_assignment__due_date')
Edit 1:
class StudentSolutionReadOnlyModelViewSetTests(TestCase):
def setup(self):
#[..]
def test_create_user_not_allowed(self):
data = {
'course_assignment': self.course_assignment.id,
'student': self.user.id
}
url = self.generate_url(self.course_assignment.id)
self.csrf_client.credentials(HTTP_AUTHORIZATION='JWT ' + self.user_token)
resp = self.csrf_client.post(
self.url,
data=json.dumps(data),
content_type='application/json'
)
self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
I am using the same authentication and csrf stuff for all methods, as well as generate_url method.
I have my custom pagination class.
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data):
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Also I have a generics.ListCreateAPIView class, which has custom queryset method and pagination_class = BasicPagination. I wanna pass self.kwargs.get("obj_type") to pagination class so that it displays obj_type not results. Here is my class view. How can I pass self.kwargs to pagination class?
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None
I am assuming that by this -
it displays obj_type not results
you mean that you want the key in your response to be obj_type instead of "results". That is obj_type is a string in your code.
I had a similar requirement where I wanted to modify the response based on certain conditions. As a workaround, I added all the required parameters to data itself, through which I customised the paginated response.
def get_paginated_response(self, data):
if self.get_next_link():
next_page = data["page_no"] + 1
else:
next_page = 0
response = {
"next": next_page,
'count': self.page.paginator.count,
'cards': data["cards"],
'companies': data["companies"],
'positions': data["positions"],
'cities':data["cities"]
}
tags = data.get('tags', None)
if tags is not None:
response['tags'] = tags
return Response(data=response)
In your case you can do something like:
ret[data['obj_type']] = data['results']
And prior to this, in the queryset:
data = {'results': api_controller.get_translation(obj_type, pk), 'obj_type': obj_type}
It's late I know. But in your view-set you can override get_paginated_response and list methods:
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None
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, extra_keys={'obj_type': self.kwargs.get("obj_type")})
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_paginated_response(self, data, extra_keys):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data, extra_keys)
and in your pagination class you can get extra_keys as argument:
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data, extra_keys):
# here you can get your passed keys
# for example for obj_type
obj_type = extra_keys['obj_type']
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Note: I used dictinary to pass your variable because if there be another variables to pass this is neater. but you can simply just pass your desired variable.