Skip to content

Commit e9500ed

Browse files
authored
Merge pull request #120 from anjlapastora/master
Add Questions and Answers module
2 parents d37423b + 50aa05e commit e9500ed

2 files changed

Lines changed: 214 additions & 2 deletions

File tree

_sidebar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
- [Setting up Django and virtual environments](django/02_setup.md)
5656
- [Creating your first Django application](django/03_start_project.md)
5757
- [Creating Users module](django/04_create_users_module.md)
58-
- [Creating endpoints](django/05_endpoints.md)
58+
- [Creating Users endpoints](django/05_endpoints.md)
5959
- [Creating Questions module](django/06_create_questions_module.md)
6060

6161
- Resources

django/06_create_questions_module.md

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
## Goals
22
- [ ] Create questions module
33
- [ ] Create questions and answers serializer
4+
- [ ] Create questions views
5+
- [ ] Create answers views
6+
- [ ] Create detail endpoints
7+
- [ ] Create like endpoint
48

59
## Create `questions` module
610
```
@@ -189,4 +193,212 @@ class QuestionSerializer(serializers.ModelSerializer):
189193
def get_user_has_answered(self, instance):
190194
request = self.context.get("request")
191195
return instance.answers.filter(author=request.user).exists()
192-
```
196+
```
197+
198+
## Create `questions` views
199+
In this part we will create a viewset for module `questions`. It is a class-based view without method handlers such as `get` or `post`, but it provides actions such as `.list()` and `.create()`.
200+
questions/api/views.py
201+
202+
```
203+
from rest_framework import serializers, viewsets
204+
from rest_framework.permissions import IsAuthenticated
205+
206+
from questions.api.serializers import QuestionSerializer
207+
from questions.api.permissions import IsAuthorOrReadOnly
208+
from questions.models import Question
209+
210+
211+
class QuestionViewSet(viewsets.ModelViewSet):
212+
queryset = Question.objects.all()
213+
lookup_field = 'slug'
214+
serializer_class = QuestionSerializer
215+
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
216+
217+
def perform_create(self, serializer):
218+
serializer.save(author=self.request.user)
219+
220+
221+
```
222+
223+
Then we will create a `permissions.py` file in `questions/api` folder to secure the api and restrict other users from accessing the apis. We will create a class `IsAuthorOrReadOnly`.
224+
225+
```
226+
from rest_framework import permissions
227+
228+
229+
class IsAuthorOrReadOnly(permissions.BasePermission):
230+
231+
def has_object_permission(self, request, view, obj):
232+
if request.method in permissions.SAFE_METHODS:
233+
return True
234+
return obj.author == request.user
235+
```
236+
237+
238+
In `questions/api/urls.py`, register the view.
239+
```
240+
from django.urls import include, path
241+
from rest_framework import routers, urlpatterns
242+
from rest_framework.routers import DefaultRouter
243+
244+
from questions.api import views as qv
245+
246+
router = DefaultRouter()
247+
router.register(r"questions", qv.QuestionViewSet)
248+
249+
250+
urlpatterns = [
251+
path("", include(router.urls))
252+
]
253+
```
254+
255+
After that, register the `questions` url in the `forumapp/urls.py` file. Register this inside the urlpatterns.
256+
```
257+
path('api/', include('questions.api.urls')),
258+
```
259+
260+
Now, try the newly created api by accessing it in your browser then by typing `localhost:8000/api/questions/`. You may access the api with the question instance. You may also try to accessor update an individual questions by adding the slug value at the end of the search bar.
261+
```
262+
example:
263+
http://localhost:8000/api/questions/first-question-does-it-work-7ue6r8/
264+
```
265+
266+
## Create `answers` views
267+
In `questions/api/views.py`, we are going to create two separate views, first to let the users answer the question, and second to list all of the answers for the questions.
268+
269+
```
270+
from rest_framework import generics, viewsets
271+
from rest_framework.exceptions import ValidationError
272+
from rest_framework.generics import get_object_or_404
273+
274+
275+
from questions.api.serializers import AnswerSerializer, QuestionSerializer
276+
from questions.models import Answer, Question
277+
278+
279+
class AnswerCreateAPIView(generics.CreateAPIView):
280+
queryset = Answer.objects.all()
281+
serializer_class = AnswerSerializer
282+
permission_classes = [IsAuthenticated]
283+
284+
def perform_create(self, serializer):
285+
request_user = self.request.user
286+
kwarg_slug = self.kwargs.get("slug")
287+
question = get_object_or_404(Question, slug=kwarg_slug)
288+
289+
if question.answers.filter(author=request_user).exists():
290+
raise ValidationError("You have already answered this question!")
291+
serializer.save(author=self.request.user, question=question)
292+
```
293+
294+
Then register the View to `questions/api/urls.py`
295+
```
296+
path("questions/<slug:slug>/answer/",
297+
qv.AnswerCreateAPIView.as_view(), name='answer-create')
298+
```
299+
300+
Now, go back to your browser then check whether you see an answer field, like in the example below:
301+
```
302+
http://localhost:8000/api/questions/do-you-like-guitars-norzk0/answer/
303+
304+
```
305+
306+
Once you accessed the link in your browser, you may notice that you cannot access a list of all of the answers in each question. The next thing that we need to do is to create a view that will list them all.
307+
308+
In `questions/api/views.py`, create another class called `AnswerListAPIView`.
309+
310+
```
311+
class AnswerListAPIView(generics.ListAPIView):
312+
serializer_class = AnswerSerializer
313+
permission_classes = [IsAuthenticated]
314+
315+
def get_queryset(self):
316+
kwarg_slug = self.kwargs.get('slug')
317+
return Answer.objects.filter(question__slug=kwarg_slug).order_by('-created_at')
318+
```
319+
320+
Same as what we did earlier, register the view `AnswerListAPIView` to `questions/api/urls.py`.
321+
322+
```
323+
path("questions/<slug:slug>/answers/",
324+
qv.AnswerListAPIView.as_view(), name='answer-list')
325+
```
326+
327+
Perfect! Now, go back to your browser, then access this link `http://localhost:8000/api/questions/<slug_url>/answers/`. You may now check the list of the answers in each question. :tada:
328+
329+
330+
## Create detail endpoint
331+
To finish our backend setup, we need two more endpoints; one for Retrieving, Updating and Deleting answers, and one for the "like" feature.
332+
333+
In `questions/api/views.py`, add a class called `AnswerRUDApiView`.
334+
335+
```
336+
class AnswerRUDApiView(generics.RetrieveUpdateDestroyAPIView):
337+
queryset = Answer.objects.all()
338+
serializer_class = AnswerSerializer
339+
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
340+
```
341+
Then register again in `questions/api/urls.py`.
342+
```
343+
path("answers/<int:pk>/",
344+
qv.AnswerRUDApiView.as_view(), name='answer-detail')
345+
```
346+
Try accessing the url `http://localhost:8000/api/questions/<slug_api>/answers/`, you can see a list of answers with id. After that, get the id, then use that id in url `http://localhost:8000/api/answers/<id>/`. You may see the details of the answer. Try also updating and deleting the answer.
347+
348+
## Create like endpoint
349+
Let us now create the last endpoint - the like endpoint.
350+
In `questions/api/views.py`, add a class called `AnswerLikeAPIView`.
351+
```
352+
from rest_framework import generics, status, viewsets
353+
354+
from rest_framework.response import Response
355+
from rest_framework.views import APIView
356+
357+
358+
class AnswerLikeAPIView(APIView):
359+
serializer_class = AnswerSerializer
360+
permission_classes = [IsAuthenticated]
361+
362+
def delete(self, request, pk):
363+
answer = get_object_or_404(Answer, pk=pk)
364+
user = request.user
365+
366+
answer.voters.remove(user)
367+
answer.save()
368+
369+
serializer_context = {'request': request}
370+
serializer = self.serializer_class(answer, context=serializer_context)
371+
372+
return Response(serializer.data, status=status.HTTP_200_OK)
373+
374+
def post(self, request, pk):
375+
answer = get_object_or_404(Answer, pk=pk)
376+
user = request.user
377+
378+
answer.voters.add(user)
379+
answer.save()
380+
381+
serializer_context = {'request': request}
382+
serializer = self.serializer_class(answer, context=serializer_context)
383+
384+
return Response(serializer.data, status=status.HTTP_200_OK)
385+
```
386+
387+
Register the view in `questions/api/urls.py`.
388+
389+
```
390+
path("answers/<int:pk>/like/",
391+
qv.AnswerLikeAPIView.as_view(), name='answer-like')
392+
```
393+
394+
Access the created endpoint by typing in `http://localhost:8000/api/answers/<id>/like/`.
395+
396+
The last thing that we need to do is to set the pagination in `settings.py` file.
397+
```
398+
REST_FRAMEWORK = {
399+
...
400+
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
401+
'PAGE_SIZE': 2
402+
}
403+
```
404+

0 commit comments

Comments
 (0)