Display Tweet User Details:

User link and User picture:

  • in /twittme-web/src/tweets/detail.js
    • create function UserLink and UserPicture, they return corresponding HTML blocks
    • replace return of tweets to apply UserLink and UserPicture ```javascript import React, { useState } from “react”;

import { ActionBtn } from “./buttons”;

//return user link function UserLink(props) { const { user, includeFullName } = props; //pick user and is include fullname const nameDisplay = includeFullName === true ? ${user.first_name} ${user.last_name} : null; //if include full name, display them

const handleUserLink = (event) => { window.location.href = /profiles/${user.username}; //link to the profile of that user }; //copied part of return from Tweet funciton below return ( {nameDisplay} @{user.username} ); }

//return user picture function UserPicture(props) { const { user } = props; //pick user

//reutrn an HTML span block to show an icon of the first letter in username //copied part of return from Tweet funciton below return ( {user.username[0]} ); }

//…

export function Tweet(props) { //…

/ return (

{isRetweet === true && (
Retweet via @{retweeter.username}
)}
{tweet.user.username[0]}

{tweet.user.first_name} {tweet.user.last_name}@{tweet.user.username}

{tweet.content}

{actionTweet && hideActions !== true && ( )} {isDetail === true ? null : ( )}
); /

return (

{isRetweet === true && (
Retweet via
)}

{tweet.content}

{actionTweet && hideActions !== true && ( )} {isDetail === true ? null : ( )}
); }

  1. runserver and npm start to test:<br />For now, the url is [http://localhost:30/](http://localhost:30/)<br />we click a "@root"<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597180362909-5919b42c-2bc5-4184-af70-eb28a1fa8265.png#align=left&display=inline&height=489&margin=%5Bobject%20Object%5D&name=image.png&originHeight=978&originWidth=771&size=42710&status=done&style=none&width=385.5)<br />Now the url is [http://localhost:30/profiles/root](http://localhost:30/profiles/root)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597180411573-9c5b40e8-3bd2-4a3d-aff6-33b31ebe0ccc.png#align=left&display=inline&height=477&margin=%5Bobject%20Object%5D&name=image.png&originHeight=954&originWidth=656&size=35887&status=done&style=none&width=328)<br />we try another one, click a "@cfe"<br />Now the url is [http://localhost:30/profiles/cfe](http://localhost:30/profiles/cfe)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597180473086-ec512aad-3f01-42ee-8321-a9bc4b35ad53.png#align=left&display=inline&height=484&margin=%5Bobject%20Object%5D&name=image.png&originHeight=968&originWidth=635&size=35326&status=done&style=none&width=317.5)<br />so we are able to redirect to the profile link with shown **username**. <br />**Also**, we can click **user picture**s to redirect to another /profiles/ page.
  2. <a name="hpjQK"></a>
  3. #### Change User link, add User display:
  4. Both username and user picture redirect us to another link, so we can separate link function, and let username or user picture has this link to increase code reuse.
  5. - in /twittme-web/src/tweets/detail.js
  6. - create function UserDisplay and move contents in UserLink into UserDisplay
  7. - Let UserLink work just as an appropriate link
  8. - apply UserLink in UserDisplay and UserPicture
  9. - in Tweet(), change UserLink into UserDisplay
  10. ```javascript
  11. import React, { useState } from "react";
  12. import { ActionBtn } from "./buttons";
  13. //return user link
  14. function UserLink(props) {
  15. //const { user, includeFullName } = props;
  16. const { username } = props;
  17. /*
  18. const nameDisplay =
  19. includeFullName === true ? `${user.first_name} ${user.last_name} ` : null;
  20. */
  21. const handleUserLink = (event) => {
  22. //window.location.href = `/profiles/${user.username}`;
  23. window.location.href = `/profiles/${username}`;
  24. };
  25. /*
  26. return (
  27. <React.Fragment>
  28. {nameDisplay}
  29. <span onClick={handleUserLink}>@{user.username}</span>
  30. </React.Fragment>
  31. );
  32. */
  33. return (
  34. <span className="pointer" onClick={handleUserLink}>
  35. {props.children}
  36. </span>
  37. );
  38. }
  39. //new
  40. function UserDisplay(props) {
  41. const { user, includeFullName } = props;
  42. const nameDisplay =
  43. includeFullName === true ? `${user.first_name} ${user.last_name} ` : null;
  44. return (
  45. <React.Fragment>
  46. {nameDisplay}
  47. <UserLink username={user.username}>@{user.username}</UserLink>
  48. </React.Fragment>
  49. );
  50. }
  51. //return user picture
  52. function UserPicture(props) {
  53. const { user } = props; //pick user
  54. /*
  55. return (
  56. <span className="mx-1 px-3 py-2 rounded-circle bg-dark text-white">
  57. {user.username[0]}
  58. </span>
  59. );
  60. */
  61. return (
  62. <UserLink username={user.username}>
  63. <span className="mx-1 px-3 py-2 rounded-circle bg-dark text-white">
  64. {user.username[0]}
  65. </span>
  66. </UserLink>
  67. );
  68. }
  69. //...
  70. export function Tweet(props) {
  71. //...
  72. /*
  73. return (
  74. <div className={className}>
  75. {isRetweet === true && (
  76. <div className="mb-2">
  77. <span className="small text-muted">
  78. Retweet via <UserLink user={retweeter} />
  79. </span>
  80. </div>
  81. )}
  82. <div className="d-flex">
  83. <div className="">
  84. <UserPicture user={tweet.user} />
  85. </div>
  86. <div className="col-11">
  87. <div>
  88. <p>
  89. <UserLink includeFullName user={tweet.user} />
  90. </p>
  91. <p>{tweet.content}</p>
  92. <ParentTweet tweet={tweet} retweeter={tweet.user} />
  93. </div>
  94. <div className="btn btn-group px-0">
  95. {actionTweet && hideActions !== true && (
  96. <React.Fragment>
  97. <ActionBtn
  98. tweet={actionTweet}
  99. didPerformAction={handlePerformAction}
  100. action={{ type: "like", display: "Likes" }}
  101. />
  102. <ActionBtn
  103. tweet={actionTweet}
  104. didPerformAction={handlePerformAction}
  105. action={{ type: "unlike", display: "Unlike" }}
  106. />
  107. <ActionBtn
  108. tweet={actionTweet}
  109. didPerformAction={handlePerformAction}
  110. action={{ type: "retweet", display: "Retweet" }}
  111. />
  112. </React.Fragment>
  113. )}
  114. {isDetail === true ? null : (
  115. <button
  116. className="btn btn-outline-primary btn-sm"
  117. onClick={handleLink}
  118. >
  119. View
  120. </button>
  121. )}
  122. </div>
  123. </div>
  124. </div>
  125. </div>
  126. );
  127. */
  128. return (
  129. <div className={className}>
  130. {isRetweet === true && (
  131. <div className="mb-2">
  132. <span className="small text-muted">
  133. Retweet via <UserDisplay user={retweeter} />
  134. </span>
  135. </div>
  136. )}
  137. <div className="d-flex">
  138. <div className="">
  139. <UserPicture user={tweet.user} />
  140. </div>
  141. <div className="col-11">
  142. <div>
  143. <p>
  144. <UserDisplay includeFullName user={tweet.user} />
  145. </p>
  146. <p>{tweet.content}</p>
  147. <ParentTweet tweet={tweet} retweeter={tweet.user} />
  148. </div>
  149. <div className="btn btn-group px-0">
  150. {actionTweet && hideActions !== true && (
  151. <React.Fragment>
  152. <ActionBtn
  153. tweet={actionTweet}
  154. didPerformAction={handlePerformAction}
  155. action={{ type: "like", display: "Likes" }}
  156. />
  157. <ActionBtn
  158. tweet={actionTweet}
  159. didPerformAction={handlePerformAction}
  160. action={{ type: "unlike", display: "Unlike" }}
  161. />
  162. <ActionBtn
  163. tweet={actionTweet}
  164. didPerformAction={handlePerformAction}
  165. action={{ type: "retweet", display: "Retweet" }}
  166. />
  167. </React.Fragment>
  168. )}
  169. {isDetail === true ? null : (
  170. <button
  171. className="btn btn-outline-primary btn-sm"
  172. onClick={handleLink}
  173. >
  174. View
  175. </button>
  176. )}
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. );
  182. }
  • add details about “pointer” in /twittme-web/src/index.css

    1. .pointer {
    2. cursor: pointer !important;
    3. }

    the page work as before.

  • we create a directory /twittme-web/src/profiles

    • create /twittme-web/src/profiles/components.js by coping and modifying some functions in /twittme-web/src/tweets/detail.js ```javascript //import React, { useState } from “react”; import React from “react”;

//import { ActionBtn } from “./buttons”;

//function UserLink(props) { export function UserLink(props) { const { username } = props;

const handleUserLink = (event) => { window.location.href = /profiles/${username}; };

return ( {props.children} ); }

//function UserDisplay(props) { export function UserDisplay(props) { const { user, includeFullName } = props; const nameDisplay = includeFullName === true ? ${user.first_name} ${user.last_name} : null;

return ( {nameDisplay} @{user.username} ); }

//function UserPicture(props) { export function UserPicture(props) { const { user } = props;

return ( {user.username[0]} ); }

  1. - create /twittme-web/src/profiles/index.js to export 3 functions
  2. ```javascript
  3. import {UserPicture, UserDisplay, UserLink} from './components'
  4. export {
  5. UserPicture, UserDisplay, UserLink
  6. }
  • import UserPicture, UserDisplay, UserLink in /twittme-web/src/tweets/detail.js ```javascript import React, { useState } from “react”;

import { ActionBtn } from “./buttons”;

import { //new UserDisplay, UserPicture } from ‘../profiles’

/* function UserLink(props) { const { username } = props;

const handleUserLink = (event) => { window.location.href = /profiles/${username}; };

return ( {props.children} ); }

function UserDisplay(props) { const { user, includeFullName } = props; const nameDisplay = includeFullName === true ? ${user.first_name} ${user.last_name} : null;

return ( {nameDisplay} @{user.username} ); }

function UserPicture(props) { const { user } = props;

return ( {user.username[0]} ); } */

  1. the page totally work as before, but we successfully moved user profile related methods into a directory.
  2. <a name="4NBON"></a>
  3. ### Feed View Component:
  4. This part we need to handle components of feed view. <br />We can create a feed list to separate from tweet list to benefit our possible future changes.
  5. - create /twittme-web/src/tweets/feed.js
  6. - it's basically modified from /twittme-web/src/tweets/list.js
  7. ```javascript
  8. import React, { useEffect, useState } from "react";
  9. import { apiTweetFeed } from "./lookup";
  10. import { Tweet } from "./detail";
  11. export function FeedList(props) {
  12. const [tweetsInit, setTweetsInit] = useState([]);
  13. const [tweets, setTweets] = useState([]);
  14. const [nextUrl, setNextUrl] = useState(null);
  15. const [tweetsDidSet, setTweetsDidSet] = useState(false);
  16. useEffect(() => {
  17. const final = [...props.newTweets].concat(tweetsInit); //[...content] means a new list with the content
  18. if (final.length !== tweets.length) {
  19. setTweets(final);
  20. }
  21. }, [props.newTweets, tweets, tweetsInit]);
  22. useEffect(() => {
  23. if (tweetsDidSet === false) {
  24. const handleTweetListLookup = (response, status) => {
  25. if (status === 200) {
  26. setNextUrl(response.next); //new
  27. console.log(response);
  28. //setTweetsInit(response);
  29. setTweetsInit(response.results);
  30. setTweetsDidSet(true);
  31. } else {
  32. alert("There was an error");
  33. }
  34. };
  35. apiTweetFeed(handleTweetListLookup);
  36. }
  37. }, [tweetsInit, tweetsDidSet, setTweetsDidSet, props.username]);
  38. const handleDidRetweet = (newTweet) => {
  39. const updateTweetsInit = [...tweetsInit]; //grabbing tweetsInit list
  40. updateTweetsInit.unshift(newTweet); //add newTweet to the beginning of updateTweetsInit
  41. setTweetsInit(updateTweetsInit); //update status
  42. const updateFinalTweets = [...tweets]; //grabbing tweets list
  43. updateFinalTweets.unshift(tweets); //add tweets to the beginning of updateFinalTweets
  44. setTweets(updateFinalTweets); //update status
  45. };
  46. const handleLoadNext = (event) => {
  47. event.preventDefault();
  48. if (nextUrl !== null) {
  49. const handleLoadNextResponse = (response, status) => {
  50. if (status === 200) {
  51. setNextUrl(response.next);
  52. const newTweets = [...tweets].concat(response.results);
  53. setTweetsInit(newTweets);
  54. setTweets(newTweets);
  55. } else {
  56. alert("There was an error");
  57. }
  58. };
  59. apiTweetFeed(handleLoadNextResponse, nextUrl);
  60. }
  61. };
  62. return (
  63. <React.Fragment>
  64. {tweets.map((item, index) => {
  65. return (
  66. <Tweet
  67. tweet={item}
  68. didRetweet={handleDidRetweet}
  69. className="my-5 py-5 border bg-white text-dark"
  70. key={`${index}-{item.id}`}
  71. />
  72. );
  73. })}
  74. {nextUrl !== null && (
  75. <button onClick={handleLoadNext} className="btn btn-outline-primary">
  76. Load next
  77. </button>
  78. )}
  79. </React.Fragment>
  80. );
  81. }
  • add function apiTweetFeed in /twittme-web/src/tweets/lookup.js to get the feed list
    • modified from apiTweetList ```javascript //…

//new export function apiTweetFeed(callback, nextUrl) { let endpoint = “/tweets/feed/“

if (nextUrl !== null && nextUrl !== undefined) {
//endpoint = nextUrl.replace(“http://localhost:8000/api“, “”) endpoint = nextUrl.replace(“http://localhost/api“, “”) }

backendLookup(“GET”, endpoint, callback); }

export function apiTweetList(username, callback, nextUrl) { let endpoint = “/tweets/“ if(username){ endpoint = /tweets/?username=${username} }

if (nextUrl !== null && nextUrl !== undefined) { //new //endpoint = nextUrl.replace(“http://localhost:8000/api“, “”) endpoint = nextUrl.replace(“http://localhost/api“, “”) }

backendLookup(“GET”, endpoint, callback); }

  1. - add function FeedComponent in /twittme-web/src/tweets/components.js to organize feed components
  2. - modified from TweetsComponent
  3. ```javascript
  4. import React, { useState, useEffect } from "react";
  5. import { TweetsList } from "./list";
  6. import { TweetCreate } from "./create";
  7. import { apiTweetDetail } from "./lookup";
  8. import { Tweet } from "./detail";
  9. import { FeedList } from "./feed"; //new
  10. //new
  11. export function FeedComponent(props) {
  12. const [newTweets, setNewTweets] = useState([]);
  13. const canTweet = props.canTweet === "false" ? false : true;
  14. const handleNewTweet = (newTweet) => {
  15. let tempNewTweets = [...newTweets];
  16. tempNewTweets.unshift(newTweet);
  17. setNewTweets(tempNewTweets);
  18. };
  19. return (
  20. <div className={props.className}>
  21. {canTweet === true && (
  22. <TweetCreate didTweet={handleNewTweet} className="col-12 mb-3" />
  23. )}
  24. <FeedList newTweets={newTweets} {...props} />
  25. </div>
  26. );
  27. }
  28. export function TweetsComponent(props) {
  29. const [newTweets, setNewTweets] = useState([]);
  30. const canTweet = props.canTweet === "false" ? false : true;
  31. const handleNewTweet = (newTweet) => {
  32. let tempNewTweets = [...newTweets];
  33. tempNewTweets.unshift(newTweet);
  34. setNewTweets(tempNewTweets);
  35. };
  36. return (
  37. <div className={props.className}>
  38. {canTweet === true && (
  39. <TweetCreate didTweet={handleNewTweet} className="col-12 mb-3" />
  40. )}
  41. <TweetsList newTweets={newTweets} {...props} />
  42. </div>
  43. );
  44. }
  • export FeedComponent in /twittme-web/src/tweets/index.js ```javascript import { TweetsComponent, TweetDetailComponent, FeedComponent, //new } from “./components”;

import { ActionBtn } from “./buttons”;

import { Tweet } from “./detail”;

import { TweetsList } from “./list”;

import { TweetCreate } from “./create”;

export { ActionBtn, Tweet, TweetsList, TweetsComponent, TweetCreate, TweetDetailComponent, FeedComponent, //new };

  1. - prepare to render FeedComponent in /twittme-web/src/index.js
  2. ```javascript
  3. import React from "react";
  4. import ReactDOM from "react-dom";
  5. import "./index.css";
  6. import App from "./App";
  7. import * as serviceWorker from "./serviceWorker";
  8. import {
  9. TweetsComponent,
  10. TweetDetailComponent,
  11. FeedComponent, //new
  12. } from "./tweets";
  13. //...
  14. const e = React.createElement;
  15. const tweetsEl = document.getElementById("tweetme-2");
  16. if (tweetsEl) {
  17. const MyComponent = e(TweetsComponent, tweetsEl.dataset);
  18. ReactDOM.render(MyComponent, tweetsEl);
  19. }
  20. //new
  21. const tweetFeedEl = document.getElementById("tweetme-2-feed");
  22. if (tweetFeedEl) {
  23. const MyComponent = e(FeedComponent, tweetFeedEl.dataset);
  24. ReactDOM.render(MyComponent, tweetFeedEl);
  25. }
  26. //...
  • in body block of /twittme-web/public/index.html

    • add div block “tweetme-2-feed”
    • hide other 2 div blocks for now ```html

    ``` to test, runserver and npm start
    image.png
    we meet a status 403 and keeps refreshing.

  • in /twittme-web/src/lookup/components.js

    • add a restrict “if already login, redirect to /login?showLoginRequired=true“ and stop refreshing. ```javascript export function backendLookup(method, endpoint, callback, data) { //…

    xhr.onload = function () { if (xhr.status === 403) {

    1. const detail = xhr.response.detail;
    2. if(detail === "Authentication credentials were not provided."){
    3. if(window.location.href.indexOf("login") === -1){ //new, stop refreshing if login
    4. window.location.href = "/login?showLoginRequired=true"
    5. }
    6. }

    } callback(xhr.response, xhr.status); }; xhr.onerror = function (e) { console.log(“error”, e); callback({ message: “The request was an error” }, 400); };

    xhr.send(jsonData); }

  1. - uncomment 'Twittme.rest_api.dev.DevAuthentication' Python/reactjs/Twittme/Twittme/settings.py to fix 403
  2. ```python
  3. DEFAULT_RENDERER_CLASSES = [
  4. 'rest_framework.renderers.JSONRenderer',
  5. ]
  6. DEFAULT_AUTHENTICATION_CLASSES = [
  7. 'rest_framework.authentication.SessionAuthentication'
  8. ]
  9. if DEBUG:
  10. DEFAULT_RENDERER_CLASSES += [
  11. 'rest_framework.renderers.BrowsableAPIRenderer',
  12. ]
  13. # uncomment
  14. DEFAULT_AUTHENTICATION_CLASSES += [
  15. 'Twittme.rest_api.dev.DevAuthentication'
  16. ]
  17. REST_FRAMEWORK = {
  18. 'DEFAULT_AUTHENTICATION_CLASSES': DEFAULT_AUTHENTICATION_CLASSES,
  19. 'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER_CLASSES
  20. }

get back to http://localhost:30/
send something
image.png
Get the problem “Authentication credentials were not provided.”
image.png

  • SessionAuthentication is not necessary anymore for tweet_create_view in /tweets/api/views.py

    1. @api_view(['POST'])
    2. # @authentication_classes([SessionAuthentication])
    3. @permission_classes([IsAuthenticated])
    4. def tweet_create_view(request, *args, **kwargs):
    5. print(request.user)
    6. serializer = TweetCreateSerializer(data=request.data)
    7. if serializer.is_valid(raise_exception=True): # send back what the error is
    8. serializer.save(user=request.user)
    9. return Response(serializer.data, status=201)
    10. return Response({}, status=400)

    try to send again
    image.png
    successful
    image.png

  • in /Twittme/rest_api/dev.py ```python from rest_framework import authentication from django.contrib.auth import get_user_model

from django.contrib.auth import authenticate

User = get_user_model()

class DevAuthentication(authentication.BasicAuthentication): def authenticate(self, request):

  1. # qs = User.objects.all()
  2. qs = User.objects.filter(id=1)
  3. user = qs.order_by("?").first()
  4. return (user, None)
  1. we see the filter id is 1 (root), so we change it into 2 (cfe)
  2. ```python
  3. from rest_framework import authentication
  4. from django.contrib.auth import get_user_model
  5. from django.contrib.auth import authenticate
  6. User = get_user_model()
  7. class DevAuthentication(authentication.BasicAuthentication):
  8. def authenticate(self, request):
  9. # qs = User.objects.all()
  10. qs = User.objects.filter(id=2)
  11. user = qs.order_by("?").first()
  12. return (user, None)

reboot the server and see the homepage
now we only see tweets from cfe, so this is the feed view of cfe.
image.png