Django Admin:

  • print the user to the terminal who send a request in /tweets/views.py

    1. def home_view(request, *args, **kwargs):
    2. print(request.user)
    3. return render(request, "pages/home.html", context={}, status=200)

    to test, runserver and see the terminal, and the user is

    1. AnonymousUser
  • step 2, access http://localhost:8000/admin to use that root account we setup before

image.png
get back to http://localhost:8000/
the user is:

  1. root

image.png

  • register “Tweet” into admin page in /tweets/admin.py ```python from django.contrib import admin

Register your models here.

from .models import Tweet

admin.site.register(Tweet)

  1. and we see Tweets here<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592007167127-0c1dea53-005c-470b-804d-b65c71738bfc.png#align=left&display=inline&height=327&margin=%5Bobject%20Object%5D&name=image.png&originHeight=654&originWidth=1492&size=65048&status=done&style=none&width=746)<br />so it has all tweets data that we created before<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592007208626-23afedb2-94bc-486e-bb45-212eec87542a.png#align=left&display=inline&height=427&margin=%5Bobject%20Object%5D&name=image.png&originHeight=853&originWidth=1443&size=91215&status=done&style=none&width=721.5)
  2. - we use python shell to test some commands
  3. ```bash
  4. (reactjs) [root@localhost Twittme]# ./manage.py shell
  5. Python 3.6.5 (default, Sep 10 2018, 09:39:42)
  6. [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
  7. Type "help", "copyright", "credits" or "license" for more information.
  8. (InteractiveConsole)
  9. >>> from tweets.models import Tweet
  10. >>> qs = Tweet.objects.filter(content="abc") # tweets that content is "abc"
  11. >>> qs
  12. <QuerySet []>
  13. >>> qs = Tweet.objects.filter(content="Hello World") # tweets that content is "Hello World"
  14. >>> qs
  15. <QuerySet [<Tweet: Tweet object (1)>]>
  16. >>> qs = Tweet.objects.filter(user="1") # tweets that sent from user id 1
  17. >>> qs
  18. <QuerySet [<Tweet: Tweet object (48)>, <Tweet: Tweet object (47)>, <Tweet: Tweet object (46)>, <Tweet: Tweet object (45)>, <Tweet: Tweet object (44)>, <Tweet: Tweet object (43)>, <Tweet: Tweet object (42)>, <Tweet: Tweet object (41)>, <Tweet: Tweet object (40)>, <Tweet: Tweet object (39)>, <Tweet: Tweet object (38)>, <Tweet: Tweet object (37)>, <Tweet: Tweet object (36)>, <Tweet: Tweet object (35)>, <Tweet: Tweet object (34)>, <Tweet: Tweet object (33)>, <Tweet: Tweet object (32)>, <Tweet: Tweet object (31)>, <Tweet: Tweet object (30)>, <Tweet: Tweet object (29)>, '...(remaining elements truncated)...']>
  19. >>> qs = Tweet.objects.filter(user__username="root") # tweets taht sent from user name "root"
  20. >>> qs
  21. <QuerySet [<Tweet: Tweet object (48)>, <Tweet: Tweet object (47)>, <Tweet: Tweet object (46)>, <Tweet: Tweet object (45)>, <Tweet: Tweet object (44)>, <Tweet: Tweet object (43)>, <Tweet: Tweet object (42)>, <Tweet: Tweet object (41)>, <Tweet: Tweet object (40)>, <Tweet: Tweet object (39)>, <Tweet: Tweet object (38)>, <Tweet: Tweet object (37)>, <Tweet: Tweet object (36)>, <Tweet: Tweet object (35)>, <Tweet: Tweet object (34)>, <Tweet: Tweet object (33)>, <Tweet: Tweet object (32)>, <Tweet: Tweet object (31)>, <Tweet: Tweet object (30)>, <Tweet: Tweet object (29)>, '...(remaining elements truncated)...']>
  22. >>> qs = Tweet.objects.filter(user__username__iexact="rOOt") # same as the previous one but ignore uppercase/lowercase
  23. >>> qs
  24. <QuerySet [<Tweet: Tweet object (48)>, <Tweet: Tweet object (47)>, <Tweet: Tweet object (46)>, <Tweet: Tweet object (45)>, <Tweet: Tweet object (44)>, <Tweet: Tweet object (43)>, <Tweet: Tweet object (42)>, <Tweet: Tweet object (41)>, <Tweet: Tweet object (40)>, <Tweet: Tweet object (39)>, <Tweet: Tweet object (38)>, <Tweet: Tweet object (37)>, <Tweet: Tweet object (36)>, <Tweet: Tweet object (35)>, <Tweet: Tweet object (34)>, <Tweet: Tweet object (33)>, <Tweet: Tweet object (32)>, <Tweet: Tweet object (31)>, <Tweet: Tweet object (30)>, <Tweet: Tweet object (29)>, '...(remaining elements truncated)...']>
  25. >>> Tweet.objects.filter(content__iexact="HeLLo WoRLd") # tweets that content is "Hello World" (ignore uppercase/lowercase)
  26. <QuerySet [<Tweet: Tweet object (1)>]>
  27. >>>

and we need to do those commands in admin.

  • so add a new class in /tweets/admin.py ```python from django.contrib import admin

Register your models here.

from .models import Tweet

class TweetAdmin(admin.ModelAdmin): search_fields = [‘userusername’, ‘useremail’] # only allow searches about username and email class Meta: model = Tweet

admin.site.register(Tweet, TweetAdmin)

  1. - also allow admin page to search content, and let it show the user who sent tweets
  2. ```python
  3. from django.contrib import admin
  4. # Register your models here.
  5. from .models import Tweet
  6. class TweetAdmin(admin.ModelAdmin):
  7. list_display = ['__str__', 'user']
  8. search_fields = ['content', 'user__username', 'user__email'] # only allow searches about content, username and email
  9. class Meta:
  10. model = Tweet
  11. admin.site.register(Tweet, TweetAdmin)

to test, runserver:
image.png
image.png
1, 9, and 10 inlucdes phrase “Hello World”

  • we can also let contents of tweets shown as title in /tweets/models.py ```python from django.db import models from django.conf import settings import random

User = settings.AUTH_USER_MODEL

class Tweet(models.Model):

  1. # maps to SQL data
  2. # id = models.AutoField(primary_key=True)
  3. user = models.ForeignKey(User, on_delete=models.CASCADE) # many users can many tweets
  4. content = models.TextField(blank=True, null=True)
  5. image = models.FileField(upload_to="images/", blank=True, null=True)
  6. def __str__(self): # new
  7. return self.content
  8. class Meta:
  9. ordering = ['-id']
  10. def serialize(self):
  11. return {
  12. "id": self.id,
  13. "content": self.content,
  14. "likes": random.randint(0, 200)
  15. }
  1. to test, runserver:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592092607955-73fe13e6-f9b0-4a0a-9cf1-494316cc3908.png#align=left&display=inline&height=417&margin=%5Bobject%20Object%5D&name=image.png&originHeight=834&originWidth=1474&size=74079&status=done&style=none&width=737)
  2. <a name="NWQHH"></a>
  3. #### Associate Authenticated User to Object:
  4. - first, log out
  5. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592095670382-1bce1a66-39c1-4397-ae26-fc5aebf5b5bf.png#align=left&display=inline&height=359&margin=%5Bobject%20Object%5D&name=image.png&originHeight=717&originWidth=1474&size=35397&status=done&style=none&width=737)
  6. - get back to the homepage to send something
  7. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592095774165-52a149d0-9f85-42c3-b97d-8da926110e75.png#align=left&display=inline&height=422&margin=%5Bobject%20Object%5D&name=image.png&originHeight=843&originWidth=1898&size=169233&status=done&style=none&width=949)<br />got server error because not corresponding sender/user id.
  8. - to handle this, modify /tweets/views.py
  9. ```python
  10. def home_view(request, *args, **kwargs):
  11. print(request.user or None) # new
  12. return render(request, "pages/home.html", context={}, status=200)
  13. def tweet_create_view(request, *args, **kwargs):
  14. if not request.user.is_authenticated: # new
  15. if request.is_ajax():
  16. return JsonResponse({}, status=401)
  17. return redirect(settings.LOGIN_URL)
  18. form = TweetForm(request.POST or None)
  19. print('post data is', request.POST)
  20. next_url = request.POST.get("next") or None
  21. if form.is_valid():
  22. obj = form.save(commit=False)
  23. obj.user = request.user or None # anonymous user
  24. obj.save()
  25. if request.is_ajax():
  26. return JsonResponse(obj.serialize(), status=201)
  27. if next_url != None and is_safe_url(next_url, ALLOWED_HOSTS):
  28. return redirect(next_url)
  29. form = TweetForm()
  30. if form.errors:
  31. if request.is_ajax():
  32. return JsonResponse(form.errors, status=400)
  33. return render(request, 'components/form.html', context={"form" : form})

also add LOGIN_URL in /Twittme/settings.py

  1. LOGIN_URL = "/login"

to test, runserver and the terminal displays:

  1. AnonymousUser

so the home page did not successfully send a status 401.

  • again, modify /tweets/views.py

    1. def tweet_create_view(request, *args, **kwargs):
    2. user = request.user # new
    3. if not request.user.is_authenticated:
    4. user = None # new
    5. if request.is_ajax():
    6. return JsonResponse({}, status=401)
    7. return redirect(settings.LOGIN_URL)
    8. form = TweetForm(request.POST or None)
    9. print('post data is', request.POST)
    10. next_url = request.POST.get("next") or None
    11. if form.is_valid():
    12. obj = form.save(commit=False)
    13. obj.user = request.user # new
    14. obj.save()
    15. if request.is_ajax():
    16. return JsonResponse(obj.serialize(), status=201)
    17. if next_url != None and is_safe_url(next_url, ALLOWED_HOSTS):
    18. return redirect(next_url)
    19. form = TweetForm()
    20. if form.errors:
    21. if request.is_ajax():
    22. return JsonResponse(form.errors, status=400)

    to test, runserver and send something when not login
    image.png
    now we have a 401 status.
    and also if we login and send a tweet, it works as before.

    Permissions & Roadmap:

  • add how to handle status 401 in home.html

    1. function handleTweetCreateFormDidSubmit(event) {
    2. //...
    3. xhr.onload = function() {
    4. if (xhr.status === 201) {
    5. //...
    6. } else if(xhr.status === 400){
    7. //...
    8. } else if(xhr.status === 401){ // new
    9. alert("You must login!") //new alert
    10. window.location.href = "/login" //redirect to /login
    11. } else if(xhr.status === 500){
    12. alert("There was a server error. Please try again later.")
    13. }
    14. }
    15. xhr.onerror = function(){
    16. alert("An error occured. Please try again later.")
    17. }
    18. xhr.send(myFormData)
    19. }

    to test, runserver and send something while not login
    image.png
    get the alert, and
    image.png
    redirected to login(now we have no template to handle /login)

  • we need a user account before any tweet methods

  • add “User Permissions” tag in todo.md ```
  1. Tweets -> User Permissions
    1. -> Creating
    2. -> Text
    3. -> Image -> Media Storage Server
    4. -> Delete
    5. -> Retweeting
    6. -> Liking

//…

  1. <a name="qzW9G"></a>
  2. #### Install Django Rest Framework:
  3. In this step, we will install Django Rest Framework to use it's intergrated methods to substitute some parts before.
  4. - homepage of Django Rest Framework: [https://www.django-rest-framework.org/](https://www.django-rest-framework.org/)
  5. - install it in pipenv
  6. ```bash
  7. (reactjs) [root@localhost Twittme]# pipenv install djangorestframework
  8. Installing djangorestframework…
  9. Adding djangorestframework to Pipfile's [packages]…
  10. ✔ Installation Succeeded
  11. Pipfile.lock (866705) out of date, updating to (18de66)…
  12. Locking [dev-packages] dependencies…
  13. Building requirements...
  14. Resolving dependencies...
  15. ✔ Success!
  16. Locking [packages] dependencies…
  17. Building requirements...
  18. Resolving dependencies...
  19. ✔ Success!
  20. Updated Pipfile.lock (18de66)!
  21. Installing dependencies from Pipfile.lock (18de66)…
  22. 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
  • add new app in /Twittme/settings.py

    1. INSTALLED_APPS = [
    2. 'django.contrib.admin',
    3. 'django.contrib.auth',
    4. 'django.contrib.contenttypes',
    5. 'django.contrib.sessions',
    6. 'django.contrib.messages',
    7. 'django.contrib.staticfiles',
    8. # third-party
    9. 'rest_framework',
    10. # internal
    11. 'tweets',
    12. ]

    Django Forms to Django Rest Framework Serializer:

    In this part, change django froms into serializer of django rest framework.

  • for code reuse, copy ‘MAX_TWEET_LENGTH’ in /tweets/forms.py to /Twittme/settings.py

    1. ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
    2. LOGIN_URL = "/login"
    3. MAX_TWEET_LENGTH = 240
  • create serializers.py in /tweets ```python from django.conf import settings from rest_framework import serializers

MAX_TWEET_LENGTH = settings.MAX_TWEET_LENGTH # better also apply this on forms.py from .models import Tweet

class TweetSerializer(serializers.ModelSerializer): class Meta: model = Tweet fields = [‘content’]

  1. def validate_content(self, value):
  2. if len(value) > MAX_TWEET_LENGTH: # if too long
  3. raise serializers.ValidationError("This tweet is too long")
  4. return value
  1. - also update forms.py
  2. ```python
  3. from django.conf import settings # new
  4. from django import forms
  5. from .models import Tweet
  6. # MAX_TWEET_LENGTH = 240
  7. MAX_TWEET_LENGTH = settings.MAX_TWEET_LENGTH # new
  8. class TweetForm(forms.ModelForm):
  9. class Meta:
  10. model = Tweet
  11. fields = ['content']
  12. def clean_content(self):
  13. content = self.cleaned_data.get("content")
  14. if len(content) > MAX_TWEET_LENGTH:
  15. raise forms.ValidationError("This tweet is too long")
  16. if len(content) == 0:
  17. raise forms.ValidationError("Content cannot be blank")
  18. return content
  • add a new create view in views.py
  • also rename the old create view method ```python from .models import Tweet from .forms import TweetForm from .serializers import TweetSerializer # new

def tweet_create_view(request, args, *kwargs): serializer = TweetSerializer(data=request.POST or None) if serializer.is_valid(): obj = serializer.save(user=request.user) print(obj) return JsonResponse(serializer.data, status=201) return JsonResponse({}, status=400)

def tweet_create_view(request, args, *kwargs):

def tweet_create_view_pure_django(request, args, *kwargs):

  1. # ...
  1. here status '201' is used to answer POST request, a bit different from 200<br />to test, runserver and send something<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592181474903-c2f1419d-b556-4a4b-a29a-ed9ed1a2ce2e.png#align=left&display=inline&height=405&margin=%5Bobject%20Object%5D&name=image.png&originHeight=809&originWidth=1718&size=124963&status=done&style=none&width=859)<br />'obj' is shown as 'Tweet object (49)', corresponding to 'serialize' I just sent.<br />also 'likes' is not initialized, so that is shown as 'undefined' in the button right now.
  2. <a name="YIZDj"></a>
  3. #### Django Views to Django Rest Framework Views:
  4. (continue)
  5. - modify views.py
  6. ```python
  7. from rest_framework.decorators import api_view # new
  8. from rest_framework.response import Response # new
  9. # ...
  10. @api_view(['POST']) # http method the client === POST
  11. def tweet_create_view(request, *args, **kwargs):
  12. serializer = TweetSerializer(data=request.POST)
  13. if serializer.is_valid(raise_exception=True): # send back what the error is
  14. serializer.save(user=request.user)
  15. return Response(serializer.data, status=201)
  16. return Response({}, status=400)
  17. # ...

to test, runserver
image.png
and this create view still works.

Then change list_view after create_view:

  • again, add and rename in views.py ```python @api_view([‘GET’]) # http method the client === POST def tweet_list_view(request, args, *kwargs): qs = Tweet.objects.all() # grab all objects in Tweet serializer = TweetSerializer(qs, many=True) return Response(serializer.data)

def tweet_list_view(request, args, *kwargs):

def tweet_list_view_pure_django(request, args, *kwargs):

  1. # ...
  1. runserver and see [http://localhost:8000/tweets](http://localhost:8000/tweets)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592184917114-c0ef7725-3d59-4475-b720-4f97c8e22059.png#align=left&display=inline&height=412&margin=%5Bobject%20Object%5D&name=image.png&originHeight=825&originWidth=1470&size=81503&status=done&style=none&width=735)<br />serializer.data looks like above.
  2. - to let the display become same as before, in home.html
  3. ```javascript
  4. function loadTweets(tweetsElement){
  5. //...
  6. xhr.onload = function() {
  7. const serverResponse = xhr.response
  8. //var listedItems = serverResponse.response
  9. var listedItems = serverResponse //new
  10. //...
  11. }
  12. xhr.send()
  13. }

to test, runserver
image.png
now there is no difference except undefined likes.

Finally, detail_view:

  • add and rename in home.html ```python @api_view([‘GET’]) # http method the client === GET def tweet_detail_view(request, tweet_id, args, *kwargs): qs = Tweet.objects.filter(id=tweet_id) if not qs.exists():
    1. return Response({}, status=404)
    obj = qs.first() serializer = TweetSerializer(obj) return Response(serializer.data, status=200)

def tweet_detail_view(request, tweet_id, args, *kwargs):

def tweet_detail_view_pure_django(request, tweet_id, args, *kwargs):

  1. # ...
  1. to test, runserver, and access such as [http://localhost:8000/tweets/1](http://localhost:8000/tweets/1)![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1592185687742-6f8cac7f-7fa5-44ea-b695-777941b594d3.png#align=left&display=inline&height=266&margin=%5Bobject%20Object%5D&name=image.png&originHeight=532&originWidth=1383&size=52182&status=done&style=none&width=691.5)<br />and it's more detailed then json page before.
  2. <a name="NE944"></a>
  3. #### Permissions and Authentication Classes Decorators for DRF APIs:
  4. - modify views.py to add IsAuthenticated factor into the permission verification step
  5. ```python
  6. from rest_framework.decorators import api_view, permission_classes # new
  7. from rest_framework.permissions import IsAuthenticated # new
  8. # ...
  9. @api_view(['POST']) # http method the client === POST
  10. @permission_classes([IsAuthenticated])
  11. def tweet_create_view(request, *args, **kwargs):
  12. serializer = TweetSerializer(data=request.POST)
  13. if serializer.is_valid(raise_exception=True): # send back what the error is
  14. serializer.save(user=request.user)
  15. return Response(serializer.data, status=201)
  16. return Response({}, status=400)
  17. # ...

to test, runserver and send something while not login
image.png
the status code this time is ‘403’ (not authorized)

  • a new handler in home.html

    1. function handleTweetCreateFormDidSubmit(event) {
    2. //...
    3. xhr.onload = function() {
    4. if (xhr.status === 201) {
    5. //...
    6. } else if(xhr.status === 400){
    7. //...
    8. } else if(xhr.status === 401){
    9. alert("You must login!")
    10. window.location.href = "/login"
    11. } else if(xhr.status === 403){ // new
    12. alert("You must login!") //new alert
    13. window.location.href = "/login" //redirect to /login
    14. } else if(xhr.status === 500){
    15. alert("There was a server error. Please try again later.")
    16. }
    17. }
    18. xhr.onerror = function(){
    19. alert("An error occured. Please try again later.")
    20. }
    21. xhr.send(myFormData)
    22. }

    and if the status code is 403, the page will be redirected to /login

  • also add session authentication in views.py

  • about session authentication: https://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication
  • image.png

    1. # ...
    2. from rest_framework.authentication import SessionAuthentication # new
    3. from rest_framework.decorators import api_view, permission_classes, authentication_classes # new
    4. # ...
    5. @api_view(['POST']) # http method the client === POST
    6. @authentication_classes([SessionAuthentication]) # new
    7. @permission_classes([IsAuthenticated])
    8. def tweet_create_view(request, *args, **kwargs):
    9. serializer = TweetSerializer(data=request.POST)
    10. if serializer.is_valid(raise_exception=True): # send back what the error is
    11. serializer.save(user=request.user)
    12. return Response(serializer.data, status=201)
    13. return Response({}, status=400)
  • set default classes of rest framework in /Twittme/settings.py

  • https://www.django-rest-framework.org/api-guide/settings/
    1. REST_FRAMEWORK = {
    2. 'DEFAULT_AUTHENTICATION_CLASSES': [
    3. 'rest_framework.authentication.SessionAuthentication'
    4. ],
    5. 'DEFAULT_RENDERER_CLASSES': [
    6. 'rest_framework.renderers.JSONRenderer',
    7. ]
    8. }
    after this setup, http://localhost:8000/tweets only shows json data.
    image.png