Build for the Feed:

replace static css and static js files:

Because we have ended our setup of a bunch of features like pagination, feed view and user link in reactjs part, now we want to duplicate those settings to django part.

  • npm run build to generate css and js files in the terminal, directory /twittme-web ```bash [root@localhost twittme-web]# npm run build

twittme-web@0.1.0 build /root/SourceCode/Python/reactjs/Twittme/twittme-web react-scripts build

Creating an optimized production build… Compiled successfully.

File sizes after gzip:

40.07 KB (+3 B) build/static/js/2.6697c676.chunk.js 3.18 KB (+490 B) build/static/js/main.211cfe2e.chunk.js 762 B build/static/js/runtime~main.a8a9905a.js 556 B (+19 B) build/static/css/main.0b57e251.chunk.css

The project was built assuming it is hosted at the server root. You can control this with the homepage field in your package.json. For example, add this to build it for GitHub Pages:

“homepage” : “http://myname.github.io/myapp“,

The build folder is ready to be deployed. You may serve it with a static server:

npm install -g serve serve -s build

Find out more about deployment here:

https://bit.ly/CRA-deploy

  1. - delete
  2. - static css directory /static/css
  3. - static js directory /static/js
  4. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597337736003-8fc119f7-02eb-4cad-af71-33a6da72608d.png#align=left&display=inline&height=267&margin=%5Bobject%20Object%5D&name=image.png&originHeight=533&originWidth=395&size=33853&status=done&style=none&width=197.5)
  5. - copy the whole directory /twittme-web/build/static/css into /static
  6. - copy the whole directory /twittme-web/build/static/js into /static
  7. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597337894656-3eb9c685-4457-4ada-b97a-5de6d9c22453.png#align=left&display=inline&height=271&margin=%5Bobject%20Object%5D&name=image.png&originHeight=541&originWidth=383&size=28391&status=done&style=none&width=191.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597337957072-b233bed5-40ed-461d-9a0d-178cd6fe011b.png#align=left&display=inline&height=282&margin=%5Bobject%20Object%5D&name=image.png&originHeight=564&originWidth=386&size=30539&status=done&style=none&width=193)<br />files in /twittme-web/build/static/css and /twittme-web/build/static/js are what we just generated by "npm run build"
  8. - compare names in /templates/react/css.html and /templates/react/js.html with those names of those replaced files.
  9. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597338161563-a5af14d4-8024-4f23-adc1-ef919f94e22a.png#align=left&display=inline&height=431&margin=%5Bobject%20Object%5D&name=image.png&originHeight=862&originWidth=1284&size=92157&status=done&style=none&width=642)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597338207541-a0aa60e5-ee85-43c0-b68b-926119f1d182.png#align=left&display=inline&height=421&margin=%5Bobject%20Object%5D&name=image.png&originHeight=841&originWidth=1163&size=90664&status=done&style=none&width=581.5)<br />because we changed static files manually, we also need to replace those references manually.
  10. - rename files in /twittme-web/build/static/js and /twittme-web/build/static/css same as names in /templates/react/css.html and /templates/react/js.html
  11. OR<br />copy names in /twittme-web/build/static/js and /twittme-web/build/static/css then paste into corresponding positions in /templates/react/css.html and /templates/react/js.html
  12. whatever the propose is to let /templates/react/css.html and /templates/react/js.html can refer to /twittme-web/build/static/js and /twittme-web/build/static/css
  13. after names changed:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597339034831-1e7091c8-3d94-4cd8-8226-5d1d0ffc194d.png#align=left&display=inline&height=426&margin=%5Bobject%20Object%5D&name=image.png&originHeight=851&originWidth=1063&size=85209&status=done&style=none&width=531.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597339073270-ecb54030-faaf-4288-90fc-e1cd4150b058.png#align=left&display=inline&height=415&margin=%5Bobject%20Object%5D&name=image.png&originHeight=829&originWidth=1021&size=85792&status=done&style=none&width=510.5)
  14. - run "collectstatics"
  15. ```bash
  16. [root@localhost Twittme]# python3 ./manage.py collectstatic
  17. You have requested to collect static files at the destination
  18. location as specified in your settings:
  19. /root/SourceCode/Python/reactjs/Twittme/static-root
  20. This will overwrite existing files!
  21. Are you sure you want to do this?
  22. Type 'yes' to continue, or 'no' to cancel: yes
  23. 8 static files copied to '/root/SourceCode/Python/reactjs/Twittme/static-root', 164 unmodified.

to test, runserver and access http://localhost:80/
image.png
all features that added to reactjs, such as user profile link from user picture or username, pagination of at most 20 tweets each page, and a user picture with the initial letter of username are totally moved into django part now.

import Feed:

  • create template /templates/pages/feed.html ```html {% extends ‘base.html’ %}

{% block content %}

{% if request.user.is_authenticated %}

{% else %}
{% endif %}

{% endblock content %}

  1. - disable DevAuthentication in /Twittme/settings.py
  2. ```html
  3. if DEBUG:
  4. DEFAULT_RENDERER_CLASSES += [
  5. 'rest_framework.renderers.BrowsableAPIRenderer',
  6. ]
  7. # DEFAULT_AUTHENTICATION_CLASSES += [
  8. # 'Twittme.rest_api.dev.DevAuthentication'
  9. # ]
  • reuse abadndoned home_view in /tweets/views.py

    1. def home_view(request, *args, **kwargs):
    2. # username = None
    3. # if request.user.is_authenticated:
    4. # username = request.user.username
    5. # return render(request, "pages/home.html", context={"username": username}, status=200)
    6. return render(request, "pages/feed.html") # new
  • apply home_view (feed_view for real now) in /Twittme/urls.py ```python from tweets.views import ( home_view, # new tweets_list_view, tweets_detail_view, )

urlpatterns = [ path(‘’, home_view), # new path(‘admin/‘, admin.site.urls), path(‘api/tweets/‘, include(‘tweets.api.urls’)),

  1. # path('', tweets_list_view),
  2. path('global', tweets_list_view), # new
  3. path('login/', login_view),
  4. path('logout/', logout_view),
  5. path('register/', register_view),
  6. path('<int:tweet_id>', tweets_detail_view),
  7. re_path(r'profiles?/', include('profiles.urls')),
  8. re_path(r'api/profiles?/', include('profiles.api.urls')),

]

  1. to test, runserver and make sure you are login<br />access [http://localhost:80/](http://localhost/)<br />click a user picture or username, or access [http://localhost/profiles/](http://localhost/profiles/cfe)[a exist user]<br />to get into the feed (profile) page<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597352397187-6f170108-6ac8-4394-a215-ed973d25ec86.png#align=left&display=inline&height=326&margin=%5Bobject%20Object%5D&name=image.png&originHeight=651&originWidth=906&size=31194&status=done&style=none&width=453)<br />For example, we access [http://localhost/profiles/](http://localhost/profiles/cfe)cfe<br />Then we only can see tweets sent by cfe<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597352472219-2ab28b5e-3a81-40f7-b30e-5aea01677ca4.png#align=left&display=inline&height=476&margin=%5Bobject%20Object%5D&name=image.png&originHeight=952&originWidth=1418&size=53463&status=done&style=none&width=709)<br />Then, we access [http://localhost/profiles/](http://localhost/profiles/cfe)cfe2<br />This user actually exists, but it sent no tweets, so we have a profile page but nothing shown. <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597352540574-0de7de05-9db4-4371-b1f2-7149d2a32e6f.png#align=left&display=inline&height=395&margin=%5Bobject%20Object%5D&name=image.png&originHeight=791&originWidth=1363&size=30229&status=done&style=none&width=681.5)<br />Finally, we access a profile page of a user who does not exist.<br />Then we get a 404.<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597352647522-032b7863-c17d-49a2-9802-0446f4be7088.png#align=left&display=inline&height=338&margin=%5Bobject%20Object%5D&name=image.png&originHeight=675&originWidth=1792&size=70043&status=done&style=none&width=896)<br />Those test cases means the build for the feed is successful.
  2. <a name="xi69y"></a>
  3. ### User Profile API Detail:
  4. For a twitter like application, we still need something more for a user profile.<br />We first pass necessary data about user profile in API.
  5. - create a profile_detail_api_view in /profiles/api/views.py
  6. - this view is basically modified from profile_detail_view in /profiles/views.py
  7. ```python
  8. # ...
  9. from ..serializers import PublicProfileSerializer # new
  10. # ...
  11. # new, response with serialized profile object
  12. @api_view(['GET'])
  13. def profile_detail_api_view(request, username, *args, **kwargs):
  14. # get the profile for the passed username
  15. qs = Profile.objects.filter(user__username=username)
  16. if not qs.exists(): # if user not found
  17. return Response({"detail": "User not found"}, status=404)
  18. profile_obj = qs.first()
  19. data = PublicProfileSerializer(instance=profile_obj)
  20. return Response(data.data, status=200)
  • add a path to apply this view in /profiles/api/urls.py ```python from django.urls import path

from .views import ( user_follow_view, profile_detail_api_view, # new )

urlpatterns = [ path(‘/‘, profile_detail_api_view), # new path(‘/follow’, user_follow_view), ]

  1. with this, the [http://localhost/profiles/](http://localhost/profiles/cfe)[a exist user] will not only just display tweets sent by that user.
  2. to test, runserver<br />we use root as the sample user this time, access [http://localhost/api/profiles/root/](http://localhost/api/profiles/root/)<br />**don't forget the last '/'** **after the username**<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597354376077-ea34a04c-63bd-4de9-be46-9907ea1687cf.png#align=left&display=inline&height=388&margin=%5Bobject%20Object%5D&name=image.png&originHeight=776&originWidth=1231&size=62512&status=done&style=none&width=615.5)<br />so if we need to create a profile, those information should be quite enough.
  3. <a name="ElfKd"></a>
  4. ### Passing the Request to Serializers:
  5. <a name="e4Pho"></a>
  6. #### check "is this user followed by some other users":
  7. - add "is_following" in returned context of profile_detail_view in /profiles/views.py
  8. - is_following means is the user who send the request following this profile
  9. ```python
  10. def profile_detail_view(request, username, *args, **kwargs):
  11. # get the profile for passed username
  12. qs = Profile.objects.filter(user__username=username)
  13. if not qs.exists():
  14. raise Http404
  15. profile_obj = qs.first()
  16. # new, check "is the user who sent request following this profile?"
  17. is_following = False
  18. if request.user.is_authenticated:
  19. user = request.user
  20. is_following = user in profile_obj.followers.all()
  21. # OR
  22. # is_following = profile_obj in user.following.all()
  23. context = {
  24. "username": username,
  25. "profile": profile_obj,
  26. "is_following": is_following, # new
  27. }
  28. return render(request, "profiles/detail.html", context)
  • in /profiles/api/views.py

    • let profile_detail_api_view return “request” as the context
      1. @api_view(['GET'])
      2. def profile_detail_api_view(request, username, *args, **kwargs):
      3. # get the profile for the passed username
      4. qs = Profile.objects.filter(user__username=username)
      5. if not qs.exists():
      6. return Response({"detail": "User not found"}, status=404)
      7. profile_obj = qs.first()
      8. # data = PublicProfileSerializer(instance=profile_obj)
      9. data = PublicProfileSerializer(instance=profile_obj, context={"request": request})
      10. return Response(data.data, status=200)
  • in /profiles/serializers.py

    • add “is_following” as a read_only SerializerMethodField and part of “fields”
    • write a method to get “is_following” ```python from rest_framework import serializers

from .models import Profile

class PublicProfileSerializer(serializers.ModelSerializer):

  1. first_name = serializers.SerializerMethodField(read_only=True)
  2. last_name = serializers.SerializerMethodField(read_only=True)
  3. is_following = serializers.SerializerMethodField(read_only=True) # new
  4. username = serializers.SerializerMethodField(read_only=True)
  5. follower_count = serializers.SerializerMethodField(read_only=True)
  6. following_count = serializers.SerializerMethodField(read_only=True)
  7. class Meta:
  8. model = Profile
  9. fields = [
  10. 'first_name',
  11. 'last_name',
  12. 'id',
  13. 'bio',
  14. 'location',
  15. 'follower_count',
  16. 'following_count',
  17. "is_following", # new
  18. 'username',
  19. ]
  20. # new, is the user who sent request following this profile?
  21. def get_is_following(self, obj):
  22. # request???
  23. is_following = False
  24. context = self.context
  25. request = context.get("request")
  26. if request:
  27. user = request.user
  28. is_following = user in obj.followers.all()
  29. return is_following
  30. def get_first_name(self, obj):
  31. return obj.user.first_name
  32. def get_last_name(self, obj):
  33. return obj.user.last_name
  34. def get_username(self, obj):
  35. return obj.user.username
  36. def get_following_count(self, obj):
  37. return obj.user.following.count()
  38. def get_follower_count(self, obj):
  39. return obj.followers.count()
  1. to test, runserver and access [http://localhost/api/profiles/cfe/](http://localhost/api/profiles/cfe/) as "root"<br />(don't forget the last "/")<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597496189139-b61c64fc-309e-464d-84ed-de54b23cc16e.png#align=left&display=inline&height=411&margin=%5Bobject%20Object%5D&name=image.png&originHeight=822&originWidth=1675&size=78116&status=done&style=none&width=837.5)<br />"is _following": false,<br />so root is not following cfe.
  2. in admin profile page, we add cfe as the follower of root<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597496394549-5696da48-2f24-4865-8812-a8547c22afae.png#align=left&display=inline&height=489&margin=%5Bobject%20Object%5D&name=image.png&originHeight=977&originWidth=1343&size=82649&status=done&style=none&width=671.5)<br />logout and login again as cfe, access [http://localhost/api/profiles/root/](http://localhost/api/profiles/root/)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597496443697-4ca96142-2912-4754-8f8f-910c8ac6680c.png#align=left&display=inline&height=451&margin=%5Bobject%20Object%5D&name=image.png&originHeight=902&originWidth=1660&size=80254&status=done&style=none&width=830)<br />because cfe is following root, "is_following" is true.<br />but in [http://localhost/api/tweets/](http://localhost/api/tweets/) "is_following" is still false, so we haven't actually using this context. <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597496683629-701be02c-b262-41e3-adc0-c9fe008b13e8.png#align=left&display=inline&height=478&margin=%5Bobject%20Object%5D&name=image.png&originHeight=955&originWidth=848&size=65050&status=done&style=none&width=424)<br />This page belongs to /tweets
  3. - so we pass this context to the serializer in /tweets/api/views.py
  4. ```python
  5. def get_paginated_queryset_response(qs, request):
  6. paginator = PageNumberPagination()
  7. paginator.page_size = 20
  8. paginated_qs = paginator.paginate_queryset(qs, request)
  9. # serializer = TweetSerializer(paginated_qs, many=True)
  10. serializer = TweetSerializer(paginated_qs, many=True, context={"request": request})
  11. return paginator.get_paginated_response(serializer.data)

now access http://localhost/api/tweets/
image.png
we see this time for each tweets sent by root, “is_following” is true if I am logged in as cfe, so that the status of “is_following” is successfully passed into all tweets.