|
1 | 1 | ## Goals |
2 | 2 | - [ ] Create questions module |
3 | 3 | - [ ] Create questions and answers serializer |
| 4 | +- [ ] Create questions views |
| 5 | +- [ ] Create answers views |
| 6 | +- [ ] Create detail endpoints |
| 7 | +- [ ] Create like endpoint |
4 | 8 |
|
5 | 9 | ## Create `questions` module |
6 | 10 | ``` |
@@ -189,4 +193,212 @@ class QuestionSerializer(serializers.ModelSerializer): |
189 | 193 | def get_user_has_answered(self, instance): |
190 | 194 | request = self.context.get("request") |
191 | 195 | 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