[TOC]

Append New Tweet & Reorder:

it’s not enough to just prepend formatted tweet, and it needs to be rendered.

  • home.html

    function handleTweetCreateFormDidSubmit(event) {
    //...
      xhr.onload = function() {
        if (xhr.status === 201) {
          const newTweetJson = xhr.response
          console.log(newTweetJson.likes)
          const newTweetElement = formatTweetElement(newTweetJson)
          console.log(newTweetElement)
          const ogHtml = tweetsContainerElement.innerHTML //new
          //tweetsContainerElement.prepend(newTweetElement)
          tweetsContainerElement.innerHTML = (newTweetElement) + ogHtml
        }
      }
      xhr.send(myFormData)
    }
    

    This time is to change innerHTML by putting tweets in front of older tweets.
    to test, runserver and send something
    image.png
    so this time “append new tweet” is before all tweets, but the front one is temporatory, while after you reload the page, that tweet is saved and shown in the bottom.

  • Then we can remove 2 console.log() lines, we don’t need them now

    function handleTweetCreateFormDidSubmit(event) {
    //...
      xhr.onload = function() {
        if (xhr.status === 201) {
          const newTweetJson = xhr.response
          //console.log(newTweetJson.likes)
          const newTweetElement = formatTweetElement(newTweetJson)
          //console.log(newTweetElement)
          const ogHtml = tweetsContainerElement.innerHTML
          tweetsContainerElement.innerHTML = (newTweetElement) + ogHtml
        }
      }
      xhr.send(myFormData)
    }
    
  • let tweets order in id sequence, modify /tweets/models.py ```python from django.db import models import random

class Tweet(models.Model):

# id = models.AutoField(primary_key=True)
content = models.TextField(blank=True, null=True)
image = models.FileField(upload_to="images/", blank=True, null=True)

class Meta: # now
    ordering = ['-id']

def serialize(self):
    return {
        "id": self.id,
        "content": self.content,
        "likes": random.randint(0, 200)
    }

- make and apply migrations
```bash
(reactjs) [root@localhost Twittme]# ./manage.py makemigrations
Migrations for 'tweets':
  tweets/migrations/0002_auto_20200611_1758.py
    - Change Meta options on tweet
(reactjs) [root@localhost Twittme]# ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, tweets
Running migrations:
  Applying tweets.0002_auto_20200611_1758... OK

to test, runserver
image.png
and now it’s listed in id sequence, it’s “latest get first”

  • 2 more adjusts:

    • reset textarea after submitting in home.html

      function handleTweetCreateFormDidSubmit(event) {
      //...
      xhr.onload = function() {
       if (xhr.status === 201) {
         const newTweetJson = xhr.response
         const newTweetElement = formatTweetElement(newTweetJson)
         const ogHtml = tweetsContainerElement.innerHTML
         tweetsContainerElement.innerHTML = (newTweetElement) + ogHtml
         myForm.reset() //new
       }
      }
      xhr.send(myFormData)
      }
      
    • disable empty textarea in home.html

    • (I did similar things in /tweets/forms.py)
      <div class='row mb-3'>
      <div class='col-md-4 mx-auto col-10'>
      <form class='form' id='tweet-create-form' method='POST' action='/create-tweet'>
       {% csrf_token %}
       <input type='hidden' value='/' name='next' />
          <!--textarea class='form-control' name='content' placeholder='Your tweet...'-->
       <textarea required='required' class='form-control' name='content' placeholder='Your tweet...'></textarea>
       <button type='submit' class='btn btn-primary'>Tweet</button>
      </form>
      </div>
      </div>
      
      and when send with empty textarea, we have
      image.png

      Handling Form Errors:

      This part is to return errors when sending form data
  • /tweets/views.py

    def tweet_create_view(request, *args, **kwargs):
      form = TweetForm(request.POST or None)
      print('post data is', request.POST)
      next_url = request.POST.get("next") or None
      if form.is_valid():
          obj = form.save(commit=False)
          obj.save()
          if request.is_ajax():
              return JsonResponse(obj.serialize(), status=201) 
          if next_url != None and is_safe_url(next_url, ALLOWED_HOSTS):
              return redirect(next_url)
          form = TweetForm()
      if form.errors: # new
          if request.is_ajax():
              return JsonResponse(form.errors, status=400)
    
      return render(request, 'components/form.html', context={"form" : form})
    

    to test, runserver and input a very long text (>240 letters)
    image.png
    and we see error in the console log.

  • For further return, modify home.html

    function handleTweetCreateFormDidSubmit(event) {
    //...
      xhr.onload = function() {
        if (xhr.status === 201) {
          const newTweetJson = xhr.response
          const newTweetElement = formatTweetElement(newTweetJson)
          const ogHtml = tweetsContainerElement.innerHTML
          tweetsContainerElement.innerHTML = (newTweetElement) + ogHtml
          myForm.reset()
        } else if(xhr.status === 400){ //new
          const errorJson = xhr.response
          console.log(errorJson)
        }
      }
      xhr.onerror = function(){ //new, this is for some kind of "major" error
        alert("An error occured. Please try again later.")
      }
      xhr.send(myFormData)
    }
    

    to test, runserver and input a very long text (>240 letters)
    image.png
    now we have a more detailed error content in the console log, the first line says: “This tweet is too long”
    and to test that “major” error, we close server first and try to send something, we get:
    image.png

  • to simulate server error, we temporarily add this line in views.py

    def tweet_create_view(request, *args, **kwargs):
      print(abc) //temporarily
      //...
    

    to test, runserver and send something:
    image.png
    this time we get a status 500 (internal server error), and that’s because abc in tweet_create_view() is undefined.

  • add a status 500 handler home.html

    function handleTweetCreateFormDidSubmit(event) {
      //...
      xhr.onload = function() {
        if (xhr.status === 201) {
          const newTweetJson = xhr.response
          const newTweetElement = formatTweetElement(newTweetJson)
          const ogHtml = tweetsContainerElement.innerHTML
          tweetsContainerElement.innerHTML = (newTweetElement) + ogHtml
          myForm.reset()
        } else if(xhr.status === 400){
          const errorJson = xhr.response
          console.log(errorJson)
        } else if(xhr.status === 500){ //new
          alert("There was a server error. Please try again later.")
        }
      }
      xhr.onerror = function(){
        alert("An error occured. Please try again later.")
      }
      xhr.send(myFormData)
    }
    

    to test, runserver and input something:
    image.png
    now we both see the console log status 500 error and the alert.

Rendering the Error Message via Vanilla JavaScript:

it only shows basic techniques about Vanilla JavaScript.

  • prepare a more detail error report for status 400 in home.html

    function handleTweetCreateFormDidSubmit(event) {
    //...
      xhr.onload = function() {
        if (xhr.status === 201) {
          //...
        } else if(xhr.status === 400){
          const errorJson = xhr.response
          console.log(errorJson)
          const contentError = errorJson.content
          let contentErrorMsg;
          if(contentError) {
            contentErrorMsg = contentError[0] //only the first element has meaningful words
          } else{
            alert("An error occured. Please try again.")
          }
          console.log(contentErrorMsg)
        } else if(xhr.status === 500){
          alert("There was a server error. Please try again later.")
        }
      }
      xhr.onerror = function(){
        alert("An error occured. Please try again later.")
      }
      xhr.send(myFormData)
    }
    

    to test, runserver and send a long text(>240 letters):image.png
    now we have a specific error message: “This tweet is too long.”

  • we add an individual error div for home.html, and a corresponding display control function for that div ```html

    {% csrf_token %}
    <input type='hidden' value='/' name='next' />
    <textarea required='required' class='form-control' name='content' placeholder='Your tweet...'></textarea>
    <button type='submit' class='btn btn-primary'>Tweet</button>
    

Loading…

to test, runserver and still send a too long text(>240 letters):![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1591913424433-3d12cb17-95fb-4b5a-b955-b156e58edb30.png#align=left&display=inline&height=273&margin=%5Bobject%20Object%5D&name=image.png&originHeight=545&originWidth=1813&size=61488&status=done&style=none&width=906.5)<br />we see that error block now.<br />and after we send something normal:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1591913531736-f9fd0d9d-1f87-4d9f-91cb-44ab999d690f.png#align=left&display=inline&height=391&margin=%5Bobject%20Object%5D&name=image.png&originHeight=783&originWidth=1475&size=52822&status=done&style=none&width=737.5)<br />the error block disappears.

<a name="pf0LH"></a>
#### Users & Tweets:
apply users to this project

- define users in /tweets/models.py
```python
from django.db import models
from django.conf import settings # new
import random

User = settings.AUTH_USER_MODEL

class Tweet(models.Model):
    # maps to SQL data
    # id = models.AutoField(primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE) # many users can many tweets
    content = models.TextField(blank=True, null=True)
    image = models.FileField(upload_to="images/", blank=True, null=True)

    class Meta:
        ordering = ['-id']

    def serialize(self):
        return {
            "id": self.id,
            "content": self.content,
            "likes": random.randint(0, 200)
        }

on_delete=models.CASCADE can be considered as when user(s) is deleted, also delete relative tweets.

  • To start this step we need a superuser

    (reactjs) [root@localhost Twittme]# python manage.py createsuperuser
    Username (leave blank to use 'root'): 
    Email address: 
    Password: 
    Password (again): 
    Superuser created successfully.
    
  • makemigations and migrate cause we modified models.py

    (reactjs) [root@localhost Twittme]# python manage.py makemigrations
    You are trying to add a non-nullable field 'user' to tweet without a default; we can't do that (the database needs something to populate existing rows).
    Please select a fix:
    1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
    2) Quit, and let me add a default in models.py
    Select an option: 1
    Please enter the default value now, as valid Python
    The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
    Type 'exit' to exit this prompt
    >>> 1
    Migrations for 'tweets':
    tweets/migrations/0003_tweet_user.py
      - Add field user to tweet
    

    ```bash

(reactjs) [root@localhost Twittme]# ./manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, tweets Running migrations: Applying tweets.0003_tweet_user… OK ```